mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-02 23:38:46 +00:00
1040 lines
38 KiB
C
1040 lines
38 KiB
C
//-----------------------------------------------------------------------------
|
|
// Borrowed initially from https://github.com/pellepl/spiffs
|
|
// Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976 at gmail.com)
|
|
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// See LICENSE.txt for the text of the license.
|
|
//-----------------------------------------------------------------------------
|
|
// SPIFFS api for RDV40 Integration
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#define SPIFFS_CFG_PHYS_ERASE_SZ (4 * 1024)
|
|
#define SPIFFS_CFG_PHYS_ADDR (0)
|
|
#define SPIFFS_CFG_LOG_PAGE_SZ (256)
|
|
#define SPIFFS_CFG_LOG_BLOCK_SZ (4 * 1024)
|
|
#define LOG_PAGE_SIZE 256
|
|
#define RDV40_SPIFFS_WORKBUF_SZ (LOG_PAGE_SIZE * 2)
|
|
// Experimental : 4 full pages(LOG_PAGE_SIZE + file descript size) of cache for
|
|
// Reading and writing if reading cache is stable, writing cache may need more
|
|
// testing regarding power loss, page consistency checks, Garbage collector
|
|
// Flushing handling... in doubt, use maximal safetylevel as, in most of the
|
|
// case, will ensure a flush by rollbacking to previous Unmounted state
|
|
#define RDV40_SPIFFS_CACHE_SZ ((LOG_PAGE_SIZE + 32) * 4)
|
|
#define SPIFFS_FD_SIZE (32)
|
|
#define RDV40_SPIFFS_MAX_FD (3)
|
|
#define RDV40_SPIFFS_FDBUF_SZ (SPIFFS_FD_SIZE * RDV40_SPIFFS_MAX_FD)
|
|
|
|
#define RDV40_LLERASE_BLOCKSIZE (64*1024)
|
|
|
|
#define RDV40_SPIFFS_LAZY_HEADER \
|
|
int changed = 0; \
|
|
if ((level == RDV40_SPIFFS_SAFETY_LAZY) || (level == RDV40_SPIFFS_SAFETY_SAFE)) { \
|
|
changed = rdv40_spiffs_lazy_mount(); \
|
|
}
|
|
|
|
#define RDV40_SPIFFS_SAFE_FOOTER \
|
|
if (level == RDV40_SPIFFS_SAFETY_SAFE) { \
|
|
changed = rdv40_spiffs_lazy_mount_rollback(changed); \
|
|
} \
|
|
return changed;
|
|
|
|
#define RDV40_SPIFFS_SAFE_FUNCTION(RDV40_SPIFFS_LLFUNCT) \
|
|
RDV40_SPIFFS_LAZY_HEADER \
|
|
RDV40_SPIFFS_LLFUNCT \
|
|
RDV40_SPIFFS_SAFE_FOOTER
|
|
|
|
#include "spiffs.h"
|
|
#include "BigBuf.h"
|
|
#include "dbprint.h"
|
|
#include "pm3_cmd.h"
|
|
#include "string.h"
|
|
|
|
///// FLASH LEVEL R/W/E operations for feeding SPIFFS Driver/////////////////
|
|
//
|
|
// SPIFFS_CHECK_RES() only treats a NEGATIVE result as an error, so the 128 / 129 / 130
|
|
// these used to return meant every flash failure was reported to SPIFFS as success.
|
|
// A failed erase that reads back as success is the worst of them: SPIFFS then writes
|
|
// into a sector that still holds the old content, and since a NOR write can only clear
|
|
// bits, page headers and file data silently AND together instead of being replaced.
|
|
static s32_t rdv40_spiffs_llread(u32_t addr, u32_t size, u8_t *dst) {
|
|
|
|
if (!Flash_ReadData(addr, dst, size)) {
|
|
return SPIFFS_ERR_NOT_READABLE;
|
|
}
|
|
return SPIFFS_OK;
|
|
}
|
|
|
|
static s32_t rdv40_spiffs_llwrite(u32_t addr, u32_t size, u8_t *src) {
|
|
|
|
if (FlashInit() == false) {
|
|
return SPIFFS_ERR_NOT_WRITABLE;
|
|
}
|
|
|
|
if (Flash_Write(addr, src, size) != size) {
|
|
return SPIFFS_ERR_NOT_WRITABLE;
|
|
}
|
|
return SPIFFS_OK;
|
|
}
|
|
|
|
// A sector erase that did not take - the chip was still busy, or the write enable
|
|
// latch was gone by the time the command arrived - leaves the old content in place.
|
|
// Flash_Erase4k() only reports that the command was sent, so read the sector back.
|
|
static bool flash_sector_is_erased(uint32_t addr) {
|
|
|
|
static uint8_t buf[64];
|
|
|
|
for (uint32_t offset = 0; offset < SPIFFS_CFG_LOG_BLOCK_SZ; offset += sizeof(buf)) {
|
|
|
|
if (Flash_ReadDataCont(addr + offset, buf, sizeof(buf)) != sizeof(buf)) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < sizeof(buf); i++) {
|
|
if (buf[i] != 0xFF) {
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
Dbprintf("SPIFFS erase verify failed at 0x%05x, read %02x", addr + offset + i, buf[i]);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static s32_t rdv40_spiffs_llerase(u32_t addr, u32_t size) {
|
|
(void)size; // always one SPIFFS_CFG_LOG_BLOCK_SZ sector
|
|
|
|
if (FlashInit() == false) {
|
|
return SPIFFS_ERR_ERASE_FAIL;
|
|
}
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) Dbprintf("LLERASEDBG : Orig addr : %d\n", addr);
|
|
|
|
uint32_t sector_addr = addr;
|
|
uint8_t block, sector = 0;
|
|
block = addr / RDV40_LLERASE_BLOCKSIZE;
|
|
if (block) {
|
|
addr = addr - (block * RDV40_LLERASE_BLOCKSIZE);
|
|
}
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) Dbprintf("LLERASEDBG : Result addr : %d\n", addr);
|
|
|
|
sector = addr / SPIFFS_CFG_LOG_BLOCK_SZ;
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) Dbprintf("LLERASEDBG : block : %d, sector : %d \n", block, sector);
|
|
|
|
for (uint8_t attempt = 0; attempt < 2; attempt++) {
|
|
|
|
// true == still busy when the timeout ran out
|
|
if (Flash_CheckBusy(BUSY_TIMEOUT)) {
|
|
Dbprintf("SPIFFS erase: flash still busy before erasing sector 0x%05x", sector_addr);
|
|
continue;
|
|
}
|
|
|
|
Flash_WriteEnable();
|
|
|
|
if (Flash_Erase4k(block, sector) == false) {
|
|
Dbprintf("SPIFFS erase: bad sector address, block %u sector %u", block, sector);
|
|
break;
|
|
}
|
|
|
|
if (Flash_CheckBusy(BUSY_TIMEOUT)) {
|
|
Dbprintf("SPIFFS erase: sector 0x%05x did not finish in %u us", sector_addr, BUSY_TIMEOUT);
|
|
continue;
|
|
}
|
|
|
|
if (flash_sector_is_erased(sector_addr)) {
|
|
FlashStop();
|
|
return SPIFFS_OK;
|
|
}
|
|
|
|
Dbprintf("SPIFFS erase: sector 0x%05x did not erase, retrying", sector_addr);
|
|
}
|
|
|
|
FlashStop();
|
|
Dbprintf(_RED_("SPIFFS erase of sector 0x%05x failed, filesystem not written"), sector_addr);
|
|
return SPIFFS_ERR_ERASE_FAIL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////// SPIFFS LOW LEVEL OPERATIONS /////////////////////////////////////////////
|
|
static u8_t spiffs_work_buf[RDV40_SPIFFS_WORKBUF_SZ] __attribute__((aligned));
|
|
static u8_t spiffs_fds[RDV40_SPIFFS_FDBUF_SZ] __attribute__((aligned));
|
|
static u8_t spiffs_cache_buf[RDV40_SPIFFS_CACHE_SZ] __attribute__((aligned));
|
|
|
|
static spiffs fs;
|
|
|
|
// One file descriptor held open across the packets of an upload.
|
|
//
|
|
// SPIFFS has no directory: SPIFFS_open() by name walks the object lookup page of
|
|
// every block and reads the header of each index page it meets to compare names.
|
|
// Doing that per packet makes an upload cost packets x filesystem size - measured
|
|
// on a 2MB filesystem, one open reads ~120KB of flash, so storing a 512KB file
|
|
// spent 16MB of reads finding the same file 131 times over.
|
|
//
|
|
// Only append_to_spiffs() fills this in. Every other path into the filesystem
|
|
// calls spiffs_close_cached() first, so a held descriptor can never be seen by
|
|
// another operation - the descriptor is closed, and its writes flushed, before
|
|
// anything else looks at the file.
|
|
static spiffs_file g_cached_fd = -1;
|
|
static char g_cached_fn[SPIFFS_OBJ_NAME_LEN] = {0};
|
|
|
|
static int rdv40_spiffs_mounted(void);
|
|
|
|
static void spiffs_close_cached(void) {
|
|
|
|
if (g_cached_fd < 0) {
|
|
return;
|
|
}
|
|
|
|
// an unmount already invalidated it, do not touch the dead handle
|
|
if (rdv40_spiffs_mounted()) {
|
|
SPIFFS_close(&fs, g_cached_fd);
|
|
}
|
|
|
|
g_cached_fd = -1;
|
|
g_cached_fn[0] = '\0';
|
|
}
|
|
|
|
static enum spiffs_mount_status {
|
|
RDV40_SPIFFS_UNMOUNTED,
|
|
RDV40_SPIFFS_MOUNTED,
|
|
RDV40_SPIFFS_UNKNOWN
|
|
} RDV40_SPIFFS_MOUNT_STATUS;
|
|
|
|
static int rdv40_spiffs_mounted(void) {
|
|
int ret = 0;
|
|
|
|
switch (RDV40_SPIFFS_MOUNT_STATUS) {
|
|
case RDV40_SPIFFS_MOUNTED:
|
|
ret = 1;
|
|
break;
|
|
case RDV40_SPIFFS_UNMOUNTED:
|
|
case RDV40_SPIFFS_UNKNOWN:
|
|
default:
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rdv40_spiffs_mount(void) {
|
|
if (rdv40_spiffs_mounted()) {
|
|
Dbprintf("ERR: SPIFFS already mounted !");
|
|
return SPIFFS_ERR_MOUNTED;
|
|
}
|
|
|
|
spiffs_config cfg;
|
|
cfg.hal_read_f = rdv40_spiffs_llread;
|
|
cfg.hal_write_f = rdv40_spiffs_llwrite;
|
|
cfg.hal_erase_f = rdv40_spiffs_llerase;
|
|
|
|
// uncached version
|
|
// int ret = SPIFFS_mount(&fs, &cfg, spiffs_work_buf, spiffs_fds,
|
|
// sizeof(spiffs_fds), 0, 0, 0); cached version, experimental
|
|
int ret = SPIFFS_mount(
|
|
&fs,
|
|
&cfg,
|
|
spiffs_work_buf,
|
|
spiffs_fds,
|
|
sizeof(spiffs_fds),
|
|
spiffs_cache_buf,
|
|
sizeof(spiffs_cache_buf),
|
|
0
|
|
);
|
|
|
|
if (ret == SPIFFS_OK) {
|
|
RDV40_SPIFFS_MOUNT_STATUS = RDV40_SPIFFS_MOUNTED;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int rdv40_spiffs_unmount(void) {
|
|
if (!rdv40_spiffs_mounted()) {
|
|
Dbprintf("ERR: SPIFFS not mounted !");
|
|
return SPIFFS_ERR_NOT_MOUNTED;
|
|
}
|
|
|
|
// the descriptor does not survive the unmount, flush it out first
|
|
spiffs_close_cached();
|
|
|
|
SPIFFS_clearerr(&fs);
|
|
SPIFFS_unmount(&fs);
|
|
|
|
int ret = SPIFFS_errno(&fs);
|
|
if (ret == SPIFFS_OK) {
|
|
RDV40_SPIFFS_MOUNT_STATUS = RDV40_SPIFFS_UNMOUNTED;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int rdv40_spiffs_check(void) {
|
|
spiffs_close_cached();
|
|
rdv40_spiffs_lazy_mount();
|
|
SPIFFS_check(&fs);
|
|
SPIFFS_gc_quick(&fs, 0);
|
|
rdv40_spiffs_lazy_unmount();
|
|
rdv40_spiffs_lazy_mount();
|
|
return SPIFFS_gc(&fs, 8192) == SPIFFS_OK;
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///// Base RDV40_SPIFFS_SAFETY_NORMAL operations////////////////////////////////
|
|
|
|
// RDV40_SPIFFS_SAFE_FUNCTION() returns the mount-change flag, not a result, so a
|
|
// failed write had no way of reaching the caller - the client happily reported
|
|
// "Wrote N bytes" for a file the device never stored. The last write result is
|
|
// kept here instead, see rdv40_spiffs_write_status()
|
|
static int g_spiffs_write_res = SPIFFS_OK;
|
|
|
|
int rdv40_spiffs_write_status(void) {
|
|
return g_spiffs_write_res;
|
|
}
|
|
|
|
void write_to_spiffs(const char *filename, const uint8_t *src, uint32_t size) {
|
|
g_spiffs_write_res = SPIFFS_OK;
|
|
spiffs_close_cached();
|
|
spiffs_file fd = SPIFFS_open(&fs, filename, SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_RDWR, 0);
|
|
if (fd < 0) {
|
|
g_spiffs_write_res = SPIFFS_errno(&fs);
|
|
Dbprintf("open errno %i\n", g_spiffs_write_res);
|
|
return;
|
|
}
|
|
// Note: SPIFFS_write() doesn't declare third parameter as const (but should)
|
|
if (SPIFFS_write(&fs, fd, (void *)src, size) < 0) {
|
|
g_spiffs_write_res = SPIFFS_errno(&fs);
|
|
Dbprintf("wr errno %i\n", g_spiffs_write_res);
|
|
}
|
|
SPIFFS_close(&fs, fd);
|
|
}
|
|
|
|
void append_to_spiffs(const char *filename, const uint8_t *src, uint32_t size) {
|
|
g_spiffs_write_res = SPIFFS_OK;
|
|
|
|
// reuse the descriptor when this is another packet of the same file
|
|
if ((g_cached_fd < 0) || (strncmp(filename, g_cached_fn, sizeof(g_cached_fn)) != 0)) {
|
|
|
|
spiffs_close_cached();
|
|
|
|
spiffs_file fd = SPIFFS_open(&fs, filename, SPIFFS_APPEND | SPIFFS_RDWR, 0);
|
|
if (fd < 0) {
|
|
g_spiffs_write_res = SPIFFS_errno(&fs);
|
|
Dbprintf("open errno %i\n", g_spiffs_write_res);
|
|
return;
|
|
}
|
|
|
|
g_cached_fd = fd;
|
|
strncpy(g_cached_fn, filename, sizeof(g_cached_fn) - 1);
|
|
g_cached_fn[sizeof(g_cached_fn) - 1] = '\0';
|
|
}
|
|
|
|
// Note: SPIFFS_write() doesn't declare third parameter as const (but should)
|
|
if (SPIFFS_write(&fs, g_cached_fd, (void *)src, size) < 0) {
|
|
g_spiffs_write_res = SPIFFS_errno(&fs);
|
|
Dbprintf("errno %i\n", g_spiffs_write_res);
|
|
// do not hold on to a descriptor that just failed
|
|
spiffs_close_cached();
|
|
}
|
|
}
|
|
|
|
void read_from_spiffs(const char *filename, uint8_t *dst, uint32_t size) {
|
|
spiffs_close_cached();
|
|
spiffs_file fd = SPIFFS_open(&fs, filename, SPIFFS_RDWR, 0);
|
|
if (SPIFFS_read(&fs, fd, dst, size) < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
}
|
|
SPIFFS_close(&fs, fd);
|
|
}
|
|
|
|
static void rename_in_spiffs(const char *old_filename, const char *new_filename) {
|
|
spiffs_close_cached();
|
|
if (SPIFFS_rename(&fs, old_filename, new_filename) < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
}
|
|
}
|
|
|
|
static void remove_from_spiffs(const char *filename) {
|
|
spiffs_close_cached();
|
|
if (SPIFFS_remove(&fs, filename) < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
}
|
|
}
|
|
|
|
uint32_t size_in_spiffs(const char *filename) {
|
|
spiffs_close_cached();
|
|
spiffs_stat s;
|
|
if (SPIFFS_stat(&fs, filename, &s) < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
return 0;
|
|
}
|
|
return s.size;
|
|
}
|
|
|
|
static rdv40_spiffs_fsinfo info_of_spiffs(void) {
|
|
spiffs_close_cached();
|
|
rdv40_spiffs_fsinfo fsinfo;
|
|
fsinfo.blockSize = SPIFFS_CFG_LOG_BLOCK_SZ;
|
|
fsinfo.pageSize = LOG_PAGE_SIZE;
|
|
fsinfo.maxOpenFiles = RDV40_SPIFFS_MAX_FD;
|
|
fsinfo.maxPathLength = SPIFFS_OBJ_NAME_LEN;
|
|
|
|
if (SPIFFS_info(&fs, &fsinfo.totalBytes, &fsinfo.usedBytes) < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
}
|
|
|
|
fsinfo.freeBytes = fsinfo.totalBytes - fsinfo.usedBytes;
|
|
// Rounding without float may be improved
|
|
fsinfo.usedPercent = ((100 * fsinfo.usedBytes) + (fsinfo.totalBytes / 2)) / fsinfo.totalBytes;
|
|
fsinfo.freePercent = (100 - fsinfo.usedPercent);
|
|
return fsinfo;
|
|
}
|
|
|
|
int exists_in_spiffs(const char *filename) {
|
|
spiffs_close_cached();
|
|
spiffs_stat stat;
|
|
int rc = SPIFFS_stat(&fs, filename, &stat);
|
|
return (rc == SPIFFS_OK);
|
|
}
|
|
|
|
static RDV40SpiFFSFileType filetype_in_spiffs(const char *filename) {
|
|
RDV40SpiFFSFileType filetype = RDV40_SPIFFS_FILETYPE_UNKNOWN;
|
|
// SPIFFS_OBJ_NAME_LEN + 4 : a 31 char name plus ".lnk" and the terminator
|
|
char symlinked[SPIFFS_OBJ_NAME_LEN + 4];
|
|
sprintf(symlinked, "%s.lnk", filename);
|
|
|
|
if (exists_in_spiffs(filename)) {
|
|
filetype = RDV40_SPIFFS_FILETYPE_REAL;
|
|
}
|
|
|
|
if (exists_in_spiffs(symlinked)) {
|
|
if (filetype != RDV40_SPIFFS_FILETYPE_UNKNOWN) {
|
|
filetype = RDV40_SPIFFS_FILETYPE_BOTH;
|
|
} else {
|
|
filetype = RDV40_SPIFFS_FILETYPE_SYMLINK;
|
|
}
|
|
}
|
|
|
|
if (g_dbglevel >= DBG_DEBUG) {
|
|
switch (filetype) {
|
|
case RDV40_SPIFFS_FILETYPE_REAL:
|
|
Dbprintf("Filetype is " _YELLOW_("RDV40_SPIFFS_FILETYPE_REAL"));
|
|
break;
|
|
case RDV40_SPIFFS_FILETYPE_SYMLINK:
|
|
Dbprintf("Filetype is " _YELLOW_("RDV40_SPIFFS_FILETYPE_SYMLINK"));
|
|
break;
|
|
case RDV40_SPIFFS_FILETYPE_BOTH:
|
|
Dbprintf("Filetype is " _YELLOW_("RDV40_SPIFFS_FILETYPE_BOTH"));
|
|
break;
|
|
case RDV40_SPIFFS_FILETYPE_UNKNOWN:
|
|
Dbprintf("Filetype is " _YELLOW_("RDV40_SPIFFS_FILETYPE_UNKNOWN"));
|
|
break;
|
|
}
|
|
}
|
|
return filetype;
|
|
}
|
|
|
|
static void copy_in_spiffs(const char *src, const char *dst) {
|
|
|
|
spiffs_close_cached();
|
|
|
|
// no BigBuf_calloc(filesize) here: a file can be bigger than BigBuf. Copy it
|
|
// in SPIFFS_WRITE_CHUNK_SIZE chunks instead, which is also what
|
|
// rdv40_spiffs_write() writes in
|
|
uint8_t *mem = BigBuf_calloc(SPIFFS_WRITE_CHUNK_SIZE);
|
|
if (mem == NULL) {
|
|
Dbprintf("error, cannot allocate copy buffer");
|
|
return;
|
|
}
|
|
|
|
spiffs_file fd = SPIFFS_open(&fs, src, SPIFFS_RDONLY, 0);
|
|
if (fd < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
return;
|
|
}
|
|
|
|
bool first = true;
|
|
while (true) {
|
|
|
|
s32_t len = SPIFFS_read(&fs, fd, mem, SPIFFS_WRITE_CHUNK_SIZE);
|
|
if (len < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
break;
|
|
}
|
|
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
|
|
// the first chunk creates / truncates the destination, the rest append to it
|
|
if (first) {
|
|
write_to_spiffs(dst, mem, (uint32_t)len);
|
|
first = false;
|
|
} else {
|
|
append_to_spiffs(dst, mem, (uint32_t)len);
|
|
}
|
|
}
|
|
|
|
SPIFFS_close(&fs, fd);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////// Abstract Operations for base Safetyness /////////////////////////////////
|
|
//
|
|
// mount if not already
|
|
// As an "hint" to the behavior one should adopt after his or her laziness
|
|
// it will return 0 if the call was a noop, either because it did not need to
|
|
// change OR because it wasn't ABLE to change :)
|
|
// 1 if the mount status actually changed
|
|
// so you know what to do IN CASE you wished to set things "back to previous
|
|
// state"
|
|
int rdv40_spiffs_lazy_mount(void) {
|
|
int changed = 0;
|
|
if (!rdv40_spiffs_mounted()) {
|
|
changed = rdv40_spiffs_mount();
|
|
/* if changed = 0 = SPIFFS_OK then all went well then the change
|
|
* actually occurred :)*/
|
|
changed = !changed;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
// unmount if not already
|
|
int rdv40_spiffs_lazy_unmount(void) {
|
|
int changed = 0;
|
|
if (rdv40_spiffs_mounted()) {
|
|
changed = rdv40_spiffs_unmount();
|
|
changed = !changed;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
// Before further Reading, it is required to have in mind that UNMOUTING is
|
|
// important in some ways Because it is the ONLY operation which ensure that
|
|
// -> all Caches and writings are flushed to the FS
|
|
// -> all FD are properly closed
|
|
// -> Every best effort has been done to ensure consistency and integrity of the
|
|
// state reputated to be the actual state of the Filesystem.
|
|
//---
|
|
|
|
// This will "toggle" mount status
|
|
// on "changement" conditional
|
|
// so it is for the former lazy_ mounting function to actually rollback or not
|
|
// depending on the result of the previous This is super lazy implementation as
|
|
// it is either a toggle to previous state or again a noop as everything was a
|
|
// nonevent If you have a function which NEEDS mounting but you want to exit
|
|
// this function in the very state mounting was before your intervention all
|
|
// things can now be transparent like
|
|
/*
|
|
void my_lazy_spiffs_act(){
|
|
uint8_t changed = rdv40_spiffs_lazy_mount();
|
|
[..] Do what you have to do with spiffs
|
|
rdv40_spiffs_lazy_rollback(changed)
|
|
}
|
|
*/
|
|
// The exact same goes for needed unmouting with eventual rollback, you just
|
|
// have to use lazy_unmount insted of lazy mount This way, you can ensure
|
|
// consistency in operation even with complex chain of mounting and unmounting
|
|
// Lets's say you are in a function which needs to mount if not already, and in
|
|
// the middle of itself calls function which indeed will need to unmount if not
|
|
// already. Well you better use safe or wrapped function which are made to
|
|
// rollback to previous state, so you can continue right after to do your
|
|
// things.
|
|
//
|
|
// As an extreme example: let's imagine that we have a function which is made
|
|
// to FORMAT the whole SPIFFS if a SPECIFIC content is written in the 4 first
|
|
// byte of that file. Also in such a case it should quickly create a bunch of
|
|
// skkeleton to get itself back to a known and wanted state. This behavior has
|
|
// to be done at every "manual" (not a lazy or internal event) mounting, just
|
|
// like an action upon boot.
|
|
/*
|
|
void my_spiffs_boot(){
|
|
uint8_t resetret[4];
|
|
// this lazy_mount since needed and can also report back the change on
|
|
state implied by eventual mount, if needed rdv40_spiffs_lazy_read((const char
|
|
*)".SHOULDRESET",(uint8_t *)resetret,4); if( resetret == "YESS" ) { uint8_t
|
|
changed = rdv40_spiffs_lazy_format(void); // this will imply change only if we where
|
|
already mounted beforehand, was the case after our reading without further
|
|
rollback rdv40_spiffs_lazy_mount_rollback(changed); // so if we were mounted
|
|
just get back to this state. If not, just don't.
|
|
[...]
|
|
}
|
|
[...]
|
|
}
|
|
*/
|
|
// Again : This will "toggle" spiffs mount status only if a "change" occurred
|
|
// (and should be fed by the result of a spiffs_lazy* function) If everything
|
|
// went well, it will return SPIFFS_OK if everything went well, and a report
|
|
// back the chain a SPI_ERRNO if not.
|
|
int rdv40_spiffs_lazy_mount_rollback(int changed) {
|
|
if (!changed) {
|
|
return SPIFFS_OK;
|
|
}
|
|
|
|
if (rdv40_spiffs_mounted()) {
|
|
return rdv40_spiffs_unmount();
|
|
}
|
|
return rdv40_spiffs_mount();
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// High level functions with SafetyLevel
|
|
// Beware that different safety level makes different return behavior
|
|
//
|
|
// RDV40_SPIFFS_SAFETY_NORMAL : will operate withtout further change on mount
|
|
// status RDV40_SPIFFS_SAFETY_LAZY : will ensure mount status already being in
|
|
// correct state before ops,
|
|
// will return !false if mount state had to change
|
|
// RDV40_SPIFFS_SAFETY_SAFE : will do same as RDV40_SPIFFS_SAFETY_LAZY
|
|
// will also safely rollback to previous state IF
|
|
// mount state had to change will return SPIFFS_OK /
|
|
// 0 / false if everything went well
|
|
|
|
// TODO : this functions are common enough to be unified with a switchcase
|
|
// statement or some function taking function parameters
|
|
// TODO : forbid writing to a filename which already exists as lnk !
|
|
// TODO : forbid writing to a filename.lnk which already exists without lnk !
|
|
// Note: Writing in SPIFFS_WRITE_CHUNK_SIZE (8192) byte chucks helps to ensure "free space" has been erased by GC (Garbage collection)
|
|
int rdv40_spiffs_write(const char *filename, const uint8_t *src, uint32_t size, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
uint32_t idx;
|
|
if (size <= SPIFFS_WRITE_CHUNK_SIZE) {
|
|
// write small file
|
|
write_to_spiffs(filename, src, size);
|
|
size = 0;
|
|
} else { //
|
|
// write first SPIFFS_WRITE_CHUNK_SIZE bytes
|
|
// need to write the first chuck of data, then append
|
|
write_to_spiffs(filename, src, SPIFFS_WRITE_CHUNK_SIZE);
|
|
}
|
|
// append remaing SPIFFS_WRITE_CHUNK_SIZE byte chuncks
|
|
for (idx = 1; idx < (size / SPIFFS_WRITE_CHUNK_SIZE); idx++) {
|
|
append_to_spiffs(filename, &src[SPIFFS_WRITE_CHUNK_SIZE * idx], SPIFFS_WRITE_CHUNK_SIZE);
|
|
}
|
|
// append remaing bytes
|
|
if (((int64_t)size - (SPIFFS_WRITE_CHUNK_SIZE * idx)) > 0) {
|
|
append_to_spiffs(filename, &src[SPIFFS_WRITE_CHUNK_SIZE * idx], size - (SPIFFS_WRITE_CHUNK_SIZE * idx));
|
|
}
|
|
)
|
|
}
|
|
|
|
int rdv40_spiffs_append(const char *filename, const uint8_t *src, uint32_t size, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
uint32_t idx;
|
|
// Append any SPIFFS_WRITE_CHUNK_SIZE byte chunks
|
|
for (idx = 0; idx < (size / SPIFFS_WRITE_CHUNK_SIZE); idx++) {
|
|
append_to_spiffs(filename, &src[SPIFFS_WRITE_CHUNK_SIZE * idx], SPIFFS_WRITE_CHUNK_SIZE);
|
|
}
|
|
// Append remain bytes
|
|
if (((int64_t)size - (SPIFFS_WRITE_CHUNK_SIZE * idx)) > 0) {
|
|
append_to_spiffs(filename, &src[SPIFFS_WRITE_CHUNK_SIZE * idx], size - (SPIFFS_WRITE_CHUNK_SIZE * idx));
|
|
}
|
|
)
|
|
}
|
|
|
|
// todo integrate reading symlinks transparently
|
|
int rdv40_spiffs_read(const char *filename, uint8_t *dst, uint32_t size, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
read_from_spiffs(filename, dst, size);
|
|
)
|
|
}
|
|
|
|
// TODO : forbid writing to a filename which already exists as lnk !
|
|
// TODO : forbid writing to a filename.lnk which already exists without lnk !
|
|
int rdv40_spiffs_rename(const char *old_filename, const char *new_filename, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION( //
|
|
rename_in_spiffs(old_filename, new_filename); //
|
|
)
|
|
}
|
|
int rdv40_spiffs_remove(const char *filename, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION( //
|
|
remove_from_spiffs(filename); //
|
|
)
|
|
}
|
|
|
|
int rdv40_spiffs_copy(const char *src_filename, const char *dst_filename, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION( //
|
|
copy_in_spiffs(src_filename, dst_filename); //
|
|
)
|
|
}
|
|
|
|
int rdv40_spiffs_stat(const char *filename, uint32_t *size_in_bytes, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION( //
|
|
*size_in_bytes = size_in_spiffs(filename); //
|
|
)
|
|
}
|
|
|
|
static int rdv40_spiffs_getfsinfo(rdv40_spiffs_fsinfo *fsinfo, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION( //
|
|
*fsinfo = info_of_spiffs(); //
|
|
)
|
|
}
|
|
|
|
// test for symlink from filename
|
|
int rdv40_spiffs_is_symlink(const char *s) {
|
|
int ret = 0;
|
|
|
|
if (s != NULL) {
|
|
size_t size = strlen(s);
|
|
|
|
if (size >= 4 && s[size - 4] == '.' && s[size - 3] == 'l' && s[size - 2] == 'n' && s[size - 1] == 'k') {
|
|
ret = 1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// since FILENAME can't be longer than 32 Bytes as of hard configuration, we're
|
|
// safe with Such maximum. So the "size" variable is actually the known/intended
|
|
// size of DESTINATION file, may it be known (may we provide a "stat from
|
|
// symlink ?")
|
|
// ATTENTION : you must NOT provide the whole filename (so please do not include the .lnk extension)
|
|
// TODO : integrate in read_function
|
|
int rdv40_spiffs_read_as_symlink(const char *filename, uint8_t *dst, uint32_t size, RDV40SpiFFSSafetyLevel level) {
|
|
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
char linkdest[SPIFFS_OBJ_NAME_LEN];
|
|
char linkfilename[SPIFFS_OBJ_NAME_LEN + 4];
|
|
sprintf(linkfilename, "%s.lnk", filename);
|
|
|
|
if (g_dbglevel >= DBG_DEBUG)
|
|
Dbprintf("Link real filename is " _YELLOW_("%s"), linkfilename);
|
|
|
|
read_from_spiffs((char *)linkfilename, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
|
|
|
|
if (g_dbglevel >= DBG_DEBUG)
|
|
Dbprintf("Symlink destination is " _YELLOW_("%s"), linkdest);
|
|
|
|
read_from_spiffs((char *)linkdest, (uint8_t *)dst, size);
|
|
)
|
|
}
|
|
|
|
// BEWARE ! This function is DESTRUCTIVE as it will UPDATE an existing symlink
|
|
// Since it creates a .lnk extension file it may be minor to mistake the order of arguments
|
|
// Still please use this function with care.
|
|
// Also, remind that it will NOT check if destination filename actually exists
|
|
// As a mnenotechnic, think about the "ln" unix command, which order is the same as "cp" unix command
|
|
// in regard of arguments orders.
|
|
// Eg :
|
|
// rdv40_spiffs_make_symlink((uint8_t *)"hello", (uint8_t *)"world", RDV40_SPIFFS_SAFETY_SAFE)
|
|
// will generate a file named "world.lnk" with the path to file "hello" written in
|
|
// which you can then read back with :
|
|
// rdv40_spiffs_read_as_symlink((uint8_t *)"world",(uint8_t *) buffer, orig_file_size, RDV40_SPIFFS_SAFETY_SAFE);
|
|
// TODO : FORBID creating a symlink with a basename (before.lnk) which already exists as a file !
|
|
int rdv40_spiffs_make_symlink(const char *linkdest, const char *filename, RDV40SpiFFSSafetyLevel level) {
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
char linkfilename[SPIFFS_OBJ_NAME_LEN + 4];
|
|
sprintf(linkfilename, "%s.lnk", filename);
|
|
write_to_spiffs(linkfilename, (const uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
|
|
)
|
|
}
|
|
|
|
// filename and filename.lnk will both the existence-checked
|
|
// if filename exists, read will be used, if filename.lnk exists, read_as_symlink will be used
|
|
// Both existence is not handled right now and should not happen or create a default fallback behavior
|
|
// Still, this case won't happen when the write(s) functions will check for both symlink and real file
|
|
// preexistence, avoiding a link being created if filename exists, or avoiding a file being created if
|
|
// symlink exists with same name
|
|
int rdv40_spiffs_read_as_filetype(const char *filename, uint8_t *dst, uint32_t size, RDV40SpiFFSSafetyLevel level) {
|
|
|
|
RDV40_SPIFFS_SAFE_FUNCTION(
|
|
|
|
RDV40SpiFFSFileType filetype = filetype_in_spiffs((char *)filename);
|
|
|
|
switch (filetype) {
|
|
case RDV40_SPIFFS_FILETYPE_REAL: {
|
|
rdv40_spiffs_read(filename, dst, size, level);
|
|
break;
|
|
}
|
|
case RDV40_SPIFFS_FILETYPE_SYMLINK: {
|
|
rdv40_spiffs_read_as_symlink(filename, dst, size, level);
|
|
break;
|
|
}
|
|
case RDV40_SPIFFS_FILETYPE_BOTH:
|
|
case RDV40_SPIFFS_FILETYPE_UNKNOWN:
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
// Resolve a filename to the real file it names, following a .lnk symlink the same
|
|
// way rdv40_spiffs_read_as_filetype() does. Expects the filesystem to be mounted.
|
|
static int resolve_in_spiffs(const char *filename, char *dst, uint16_t dstlen) {
|
|
|
|
switch (filetype_in_spiffs(filename)) {
|
|
case RDV40_SPIFFS_FILETYPE_REAL: {
|
|
strncpy(dst, filename, dstlen - 1);
|
|
dst[dstlen - 1] = 0;
|
|
return PM3_SUCCESS;
|
|
}
|
|
case RDV40_SPIFFS_FILETYPE_SYMLINK: {
|
|
char linkfilename[SPIFFS_OBJ_NAME_LEN + 4];
|
|
sprintf(linkfilename, "%s.lnk", filename);
|
|
read_from_spiffs(linkfilename, (uint8_t *)dst, MIN(dstlen, (uint16_t)SPIFFS_OBJ_NAME_LEN));
|
|
dst[dstlen - 1] = 0;
|
|
return PM3_SUCCESS;
|
|
}
|
|
case RDV40_SPIFFS_FILETYPE_BOTH:
|
|
case RDV40_SPIFFS_FILETYPE_UNKNOWN:
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
// Read a file out in chunks, handing each one to `cb` as it arrives.
|
|
// Expects the filesystem to be mounted. Returns bytes streamed, or a negative PM3_E*
|
|
static int stream_from_spiffs(const char *filename, uint32_t offset, uint32_t size, uint8_t *chunkbuf, uint16_t chunklen, spiffs_chunk_cb_t cb) {
|
|
|
|
spiffs_close_cached();
|
|
|
|
char target[SPIFFS_OBJ_NAME_LEN] = {0};
|
|
int res = resolve_in_spiffs(filename, target, sizeof(target));
|
|
if (res != PM3_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
spiffs_file fd = SPIFFS_open(&fs, target, SPIFFS_RDONLY, 0);
|
|
if (fd < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
if (offset && (SPIFFS_lseek(&fs, fd, offset, SPIFFS_SEEK_SET) < 0)) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
SPIFFS_close(&fs, fd);
|
|
return PM3_EFILE;
|
|
}
|
|
|
|
res = PM3_SUCCESS;
|
|
uint32_t sent = 0;
|
|
|
|
while (sent < size) {
|
|
|
|
// SPIFFS_read clamps to the end of the file and returns 0 once there
|
|
s32_t len = SPIFFS_read(&fs, fd, chunkbuf, MIN(size - sent, (uint32_t)chunklen));
|
|
if (len < 0) {
|
|
Dbprintf("errno %i\n", SPIFFS_errno(&fs));
|
|
res = PM3_EFILE;
|
|
break;
|
|
}
|
|
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
|
|
// offsets are relative to the start of the transfer, not to the file
|
|
res = cb(sent, chunkbuf, (uint16_t)len);
|
|
if (res != PM3_SUCCESS) {
|
|
break;
|
|
}
|
|
|
|
sent += (uint32_t)len;
|
|
}
|
|
|
|
SPIFFS_close(&fs, fd);
|
|
return (res == PM3_SUCCESS) ? (int)sent : res;
|
|
}
|
|
|
|
// Stream `size` bytes from `offset` of a file, one `chunklen` chunk at a time.
|
|
//
|
|
// The download path used to read the whole file into BigBuf first. That caps a
|
|
// download at what BigBuf holds, and back when BigBuf_calloc() took a uint16_t
|
|
// the request wrapped for files >= 64KB: a short buffer, then a full size read
|
|
// run straight past the end of it. Here the file is opened once and only one
|
|
// chunk is ever buffered, so file size no longer enters into it.
|
|
//
|
|
// Returns the number of bytes streamed, or a negative PM3_E* on failure.
|
|
//
|
|
// Not RDV40_SPIFFS_SAFE_FUNCTION() : that macro returns the mount-change flag,
|
|
// and the caller needs to know how much of the file actually went out.
|
|
int rdv40_spiffs_read_stream(const char *filename, uint32_t offset, uint32_t size, uint8_t *chunkbuf, uint16_t chunklen, spiffs_chunk_cb_t cb, RDV40SpiFFSSafetyLevel level) {
|
|
|
|
if ((filename == NULL) || (chunkbuf == NULL) || (chunklen == 0) || (cb == NULL)) {
|
|
return PM3_EINVARG;
|
|
}
|
|
|
|
int changed = 0;
|
|
if ((level == RDV40_SPIFFS_SAFETY_LAZY) || (level == RDV40_SPIFFS_SAFETY_SAFE)) {
|
|
changed = rdv40_spiffs_lazy_mount();
|
|
}
|
|
|
|
int res = stream_from_spiffs(filename, offset, size, chunkbuf, chunklen, cb);
|
|
|
|
if (level == RDV40_SPIFFS_SAFETY_SAFE) {
|
|
rdv40_spiffs_lazy_mount_rollback(changed);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// TODO regarding reads/write and symlinks :
|
|
// Provide a higher level readFile function which
|
|
// - don't need a size to be provided, getting it from STAT call and using bigbuff malloc
|
|
// - send back the whole read file as return Result
|
|
// Maybe a good think to implement a VFS api here.
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////// MISC HIGH LEVEL FUNCTIONS ////////////////////////////////////////////
|
|
#define SPIFFS_BANNER DbpString(_CYAN_("Flash Memory FileSystem tree (SPIFFS)"));
|
|
|
|
void rdv40_spiffs_safe_print_fsinfo(void) {
|
|
rdv40_spiffs_fsinfo fsinfo;
|
|
rdv40_spiffs_getfsinfo(&fsinfo, RDV40_SPIFFS_SAFETY_SAFE);
|
|
|
|
Dbprintf(" Logical block size... " _YELLOW_("%d")" bytes", fsinfo.blockSize);
|
|
Dbprintf(" Logical page size.... " _YELLOW_("%d")" bytes", fsinfo.pageSize);
|
|
Dbprintf(" Max open files....... " _YELLOW_("%d")" file descriptors", fsinfo.maxOpenFiles);
|
|
Dbprintf(" Max path length...... " _YELLOW_("%d")" chars", fsinfo.maxPathLength);
|
|
DbpString("");
|
|
Dbprintf(" Filesystem size used available use% mounted");
|
|
DbpString("------------------------------------------------------------------");
|
|
Dbprintf(" spiffs %6d B %6d B %6d B " _YELLOW_("%2d%")" /"
|
|
, fsinfo.totalBytes
|
|
, fsinfo.usedBytes
|
|
, fsinfo.freeBytes
|
|
, fsinfo.usedPercent
|
|
);
|
|
DbpString("");
|
|
}
|
|
|
|
// this function is safe and WILL rollback since it is only a PRINTING function,
|
|
// not a function intended to give any sort of struct to manipulate the FS
|
|
// objects
|
|
// TODO : Fake the Directory availability by splitting strings , buffering,
|
|
// maintaining prefix list sorting, unique_checking, THEN outputting precomputed
|
|
// tree Other solution would be to add directory support to SPIFFS, but that we
|
|
// don't want, as prefix are way easier and lighter in every aspect.
|
|
void rdv40_spiffs_safe_print_tree(void) {
|
|
int changed = rdv40_spiffs_lazy_mount();
|
|
spiffs_DIR d;
|
|
struct spiffs_dirent e;
|
|
struct spiffs_dirent *pe = &e;
|
|
|
|
spiffs_close_cached();
|
|
|
|
char *resolvedlink = (char *)BigBuf_calloc(11 + SPIFFS_OBJ_NAME_LEN);
|
|
char *linkdest = (char *)BigBuf_calloc(SPIFFS_OBJ_NAME_LEN);
|
|
if (resolvedlink == NULL || linkdest == NULL) {
|
|
DbpString("Failed to allocate memory");
|
|
rdv40_spiffs_lazy_mount_rollback(changed);
|
|
BigBuf_free();
|
|
return;
|
|
}
|
|
|
|
bool printed = false;
|
|
|
|
SPIFFS_opendir(&fs, "/", &d);
|
|
while ((pe = SPIFFS_readdir(&d, pe))) {
|
|
|
|
memset(resolvedlink, 0, 11 + SPIFFS_OBJ_NAME_LEN);
|
|
|
|
if (rdv40_spiffs_is_symlink((const char *)pe->name)) {
|
|
|
|
read_from_spiffs((char *)pe->name, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
|
|
sprintf(resolvedlink, "(.lnk) --> %s", linkdest);
|
|
char *linkname = (char *)pe->name;
|
|
int len = strlen(linkname);
|
|
if (len >= 4 && strcmp(&linkname[len - 4], ".lnk") == 0) {
|
|
linkname[len - 4] = '\0';
|
|
}
|
|
}
|
|
|
|
Dbprintf("[%04u] " _YELLOW_("%5i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink);
|
|
printed = true;
|
|
}
|
|
if (printed == false) {
|
|
DbpString("<empty>");
|
|
}
|
|
SPIFFS_closedir(&d);
|
|
rdv40_spiffs_lazy_mount_rollback(changed);
|
|
BigBuf_free();
|
|
}
|
|
|
|
void rdv40_spiffs_safe_wipe(void) {
|
|
|
|
spiffs_close_cached();
|
|
|
|
int changed = rdv40_spiffs_lazy_mount();
|
|
|
|
spiffs_DIR d;
|
|
struct spiffs_dirent e;
|
|
struct spiffs_dirent *pe = &e;
|
|
SPIFFS_opendir(&fs, "/", &d);
|
|
|
|
while ((pe = SPIFFS_readdir(&d, pe))) {
|
|
|
|
if (rdv40_spiffs_is_symlink((const char *)pe->name)) {
|
|
|
|
char linkdest[SPIFFS_OBJ_NAME_LEN];
|
|
read_from_spiffs((char *)pe->name, (uint8_t *)linkdest, SPIFFS_OBJ_NAME_LEN);
|
|
|
|
remove_from_spiffs(linkdest);
|
|
Dbprintf("removed %s", linkdest);
|
|
|
|
remove_from_spiffs((char *)pe->name);
|
|
Dbprintf(".lnk removed %s", pe->name);
|
|
|
|
} else {
|
|
remove_from_spiffs((char *)pe->name);
|
|
Dbprintf("removed %s", pe->name);
|
|
}
|
|
}
|
|
|
|
SPIFFS_closedir(&d);
|
|
rdv40_spiffs_lazy_mount_rollback(changed);
|
|
}
|
|
|
|
// Selftest function
|
|
void test_spiffs(void) {
|
|
Dbprintf("----------------------------------------------");
|
|
Dbprintf("Testing SPIFFS operations");
|
|
Dbprintf("----------------------------------------------");
|
|
Dbprintf("-- all test are made using lazy safetylevel");
|
|
|
|
Dbprintf(" Mounting filesystem (lazy).......");
|
|
int changed = rdv40_spiffs_lazy_mount();
|
|
|
|
Dbprintf(" Printing tree..............");
|
|
rdv40_spiffs_safe_print_tree();
|
|
|
|
Dbprintf(" Writing 'I love Proxmark3 RDV4' in a testspiffs.txt");
|
|
|
|
// Since We lazy_mounted manually before hand, the write safety level will
|
|
// just imply noops
|
|
rdv40_spiffs_write((char *)"testspiffs.txt", (uint8_t *)"I love Proxmark3 RDV4", 21, RDV40_SPIFFS_SAFETY_SAFE);
|
|
|
|
Dbprintf(" Printing tree again.......");
|
|
rdv40_spiffs_safe_print_tree();
|
|
|
|
Dbprintf(" Making a symlink to testspiffs.txt");
|
|
rdv40_spiffs_make_symlink((char *)"testspiffs.txt", (char *)"linktotestspiffs.txt", RDV40_SPIFFS_SAFETY_SAFE);
|
|
|
|
Dbprintf(" Printing tree again.......");
|
|
rdv40_spiffs_safe_print_tree();
|
|
|
|
// TODO READBACK, rename,print tree read back, remove, print tree;
|
|
Dbprintf(" Rollbacking The mount status IF things have changed");
|
|
rdv40_spiffs_lazy_mount_rollback(changed);
|
|
|
|
Dbprintf(_GREEN_("All done"));
|
|
return;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|