Files
proxmark3/armsrc/spiffs.c
T
iceman1001andClaude Opus 5 (1M context) 5b909402b5 BigBuf: stop narrowing sizes, and size the 14a modulation buffer to fit
Three things that all cost memory or correctness once BigBuf is larger
than 64KB, which it already is on PM5.

BigBuf_max_traceLen() returned a uint16_t while s_bigbuf_hi is a
uint32_t. At 33272 on AT91 that is harmless; on AT32, where BigBuf runs
to several hundred kbyte, it truncates and LF sampling asks for a
fraction of the buffer that is there. Widened, along with the four
callers that assigned it straight back into a uint16_t.

BigBuf_malloc() and BigBuf_calloc() took a uint16_t chunksize, so a
request of exactly 65536 arrived as 0 and anything above wrapped. It
returned NULL for the 65536 case by accident, not by design. Both take a
uint32_t now and the guard tests the upper bound explicitly, so an
oversized request fails like any other allocation that does not fit.

The 14a tag simulation allocated its dynamic modulation buffer as a flat
512, or 4096 for ST25TA. prepare_tag_modulation() memcpy's the encoded
answer out of the ToSend buffer and tosend_stuffbit() hard caps that at
TOSEND_BUFFER_SIZE, so 1788 of ST25TA's 4096 could never be reached --
it was the largest single allocation of any tag simulation for nothing.
Meanwhile 512 is 68 bytes short of what a full 64 byte response encodes
to, at 9 bytes per byte plus 4, so those answers were refused at
modulation time.

Both are now derived from the response size and capped at what ToSend
can hold: 580 for the default tag types, 2308 for ST25TA. That also
fixes the reason the 4096 never helped -- all six call sites passed the
512 macro as max_buffer_size rather than the size actually allocated, so
ST25TA allocated 4096 and then bounds checked against 512.

Worst case for a simulation that also holds emulator memory drops from
12247 bytes of BigBuf to 10459.

Built for client, RDV4 and PM3GENERIC. Not yet run on hardware.

Co-Authored-By: Claude Opus 5 (1M context)
2026-09-14 13:06:14 +02:00

1033 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);
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;
}
///////////////////////////////////////////////////////////////////////////////