mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2026-09-16 12:32:36 +00:00
* SubGhz: share the remaining duplicated serialize handlers Follow-up toaf2370573. Two families of decoder serialize handlers were still copied per protocol: the one writing generic.data_2 as "Data" (beninca_arc, jarolift, kinggates_stylo_4k) and the one writing TE (holtek_th12x, legrand, smc5326). Both now live in common.c and are pointed at straight from the vtable, so the per-protocol functions and their declarations are gone. The data_2 block also existed three more times in the encoder create_data paths of those same files; they share the helper too. princeton is that TE handler plus a Guard_time write, so it calls the helper and adds its own field. honeywell had generic and decoder in the reverse order, which was the only reasonaf2370573could not fold six of its handlers - swapping the two members folds them. The helpers log through generic.protocol_name instead of each file's TAG, so they match the vtable slot signature and need no per-protocol wrapper. Two diagnostics-only consequences: converted paths log under the protocol's user-facing name rather than TAG, and the encoder create_data failure message merges with the serialize one. Nothing in the tree filters on log tags. SUBGHZ_ASSERT_*_LAYOUT pins the punned prefixes at compile time. Pinning the tail offset alone would not have caught honeywell's swap: decoder and generic are both multiples of 8, so te lands at the same offset either way. Verified by restoring the old order and watching the build fail. .text 696,740 -> 695,828 (-912 B), .rodata unchanged, api_symbols.csv untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGhz: share the protocol alloc bodies 48 decoder and 48 encoder alloc functions differed only in the struct type, the protocol descriptor and, for encoders, the repeat and upload-buffer constants. They now call subghz_protocol_{decoder,encoder}_common_alloc and keep a thunk each - the vtable slot passes only SubGhzEnvironment*, so the per-protocol constants have to come from somewhere. Keeping the thunk rather than making alloc data-driven is deliberate. Hanging instance_size/repeat/size_upload off SubGhzProtocol and letting alloc be NULL would remove all 96 thunks, but it moves the same constants into .rodata at about 12 B per protocol across the whole registry, adds a dispatch branch, and changes a struct that is FAP ABI (raw.h is an exported SDK header and includes base.h). Net saving is roughly zero. SubGhzProtocolEncoderCommon stays {base, encoder} so protocols that only use free/stop/yield are not forced to carry a generic; the alloc helper puns through a nested SubGhzProtocolEncoderCommonGeneric instead, mirroring how SubGhzProtocolDecoderCommonTe extends the decoder type. SUBGHZ_ASSERT_*_LAYOUT now covers all 114 structs any shared helper puns through, not just the ones converted here - 14 of those were already being punned by free/stop/yield/reset/serialize with no guard at all. The asserts cost no flash. 23 allocs keep their own body for doing real extra work (keystore setup, rainbow-table names, extra buffers). Most could call the helper and then add their own lines the way princeton does for serialize, worth roughly 440 B; left for a follow-up so this commit stays mechanical. .text 695,828 -> 693,396 (-2,432 B), .rodata unchanged, api_symbols.csv untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGhz: share the deserialize bodies that only differ by a callback Two more shared helpers in common.c: - subghz_protocol_encoder_common_deserialize(ctx, ff, min_count_bit, get_upload) covers 11 encoders whose bodies were identical apart from the bit count and their get_upload - subghz_protocol_decoder_common_deserialize_te(ctx, ff, min_count_bit) covers the three decoders that read TE back, mirroring the serialize_te helper The per-protocol get_upload functions now take void* instead of being cast to the callback type. Calling through an incompatible function pointer type is formally undefined even though it is harmless on AAPCS, and enabling LTO is on the roadmap for this same effort - not a combination worth keeping. Deliberately not converted: The 35 decoders whose deserialize is just a tail call to subghz_block_generic_deserialize_check_count_bit compile to 8 bytes each. A decoder thunk is 6 bytes, so folding them would gain about 70 B - real, but a poor return for touching 35 files and adding a second hop to a call that is already a tail call into shared code. The six encoders that call a per-protocol check_remote_controller before get_upload would need a fifth argument, which spills to the stack on AAPCS and costs more per thunk than it saves. They can instead be folded into the helper above by giving their get_upload a bool return and moving the check inside it, which also reaches allstar_firefly/elplast and, once the shared stop resets encoder.front, came_twee/marantec/power_smart/hormann/magellan - about 390 B across 13 protocols with no new helper. Left as a follow-up because it changes those functions rather than just moving them. .text 693,396 -> 692,900 (-496 B), .rodata unchanged, api_symbols.csv untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Storage: share the command-dispatch bodies Twelve public storage calls had the same S_API macro body and differed only in the StorageCommand they sent. Four static helpers now carry it: storage_file_command_bool file_truncate, file_sync, file_eof, dir_rewind storage_file_command_uint64 file_tell, file_size storage_sd_command sd_format, sd_unmount, sd_mount, sd_status storage_path_command common_remove, common_mkdir Split by return macro on purpose - S_RETURN_BOOL, S_RETURN_UINT64 and S_RETURN_ERROR read different members of the SAReturn union, so one helper for all twelve would read the wrong member. Each pairing was checked against what storage_processing.c actually writes. The public symbols stay as one-line thunks: all twelve are exported in api_symbols.csv, so the bodies could move but the symbols could not. file_close and dir_close share the shape but do work between the epilogue and the return, so they keep their own bodies. .text 692,900 -> 692,296 (-604 B), .rodata unchanged, api_symbols.csv untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGhz: fold the allocs and deserializes the earlier reviews left behind Follow-up to84aca75eband5f4f4cc2b, both of which deliberately stopped at the mechanical cases and listed what they skipped. 15 more allocs now call the shared helpers and keep only their own extra work - keystore lookups, rainbow-table names, extra buffers: alutech_at_4n, beninca_arc, bin_raw, dickert_mahs, faac_slh, jarolift, keeloq, kinggates_stylo_4k, nice_flor_s, telcoma_edge. 8 more encoders join subghz_protocol_encoder_common_deserialize. Their get_upload had no failure path and returned void, so it becomes bool/void* and returns true; five of them also move their check_remote_controller call from deserialize into the top of get_upload, which needs a forward declaration since get_upload is defined above it. min_count_bit widens from uint8_t to uint16_t in both deserialize helpers. subghz_block_generic_deserialize_check_count_bit compares it against a uint16_t, so the narrower parameter would have silently truncated any protocol above 255 bits. None exists today; the tree's largest is 128. The get_upload doc says "true Always; this encoder has no failure path" rather than "on success". These eight have no bound check, unlike the eleven converted in5f4f4cc2b, and claiming success would imply a validation none of them do. They cannot overrun today - deserialize_check_count_bit pins data_count_bit by equality before get_upload runs - but keyfinder has only 4 spare entries in a 60-entry buffer, so adding the guards is worth its own commit. Not done here, deliberately: the five moved check_remote_controller calls write only serial/btn/cnt on the encoder instance, which nothing reads, so they could be deleted outright rather than moved. Deleting a store is a behaviour change and belongs with the encoder.front reset in its own commit. .text 692,296 -> 691,848 (-448 B), .rodata unchanged, api_symbols.csv untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * upd changelog Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1042 lines
28 KiB
C
1042 lines
28 KiB
C
#include <core/log.h>
|
|
#include <core/record.h>
|
|
#include "storage.h"
|
|
#include "storage_i.h" // IWYU pragma: keep
|
|
#include "storage_message.h"
|
|
#include <toolbox/stream/file_stream.h>
|
|
#include <toolbox/dir_walk.h>
|
|
#include "toolbox/path.h"
|
|
|
|
#define MAX_NAME_LENGTH 256
|
|
#define MAX_EXT_LEN 16
|
|
#define FILE_BUFFER_SIZE 512
|
|
|
|
#define TAG "StorageApi"
|
|
|
|
#define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked();
|
|
|
|
#define S_FILE_API_PROLOGUE \
|
|
furi_check(file); \
|
|
Storage* storage = file->storage; \
|
|
furi_check(storage);
|
|
|
|
#define S_API_EPILOGUE \
|
|
furi_check( \
|
|
furi_message_queue_put(storage->message_queue, &message, FuriWaitForever) == \
|
|
FuriStatusOk); \
|
|
api_lock_wait_unlock_and_free(lock)
|
|
|
|
#define S_API_MESSAGE(_command) \
|
|
SAReturn return_data; \
|
|
StorageMessage message = { \
|
|
.lock = lock, \
|
|
.command = _command, \
|
|
.data = &data, \
|
|
.return_data = &return_data, \
|
|
};
|
|
|
|
#define S_API_DATA_FILE \
|
|
SAData data = { \
|
|
.file = { \
|
|
.file = file, \
|
|
}};
|
|
|
|
#define S_RETURN_BOOL (return_data.bool_value);
|
|
#define S_RETURN_UINT16 (return_data.uint16_value);
|
|
#define S_RETURN_UINT64 (return_data.uint64_value);
|
|
#define S_RETURN_ERROR (return_data.error_value);
|
|
#define S_RETURN_CSTRING (return_data.cstring_value);
|
|
|
|
typedef enum {
|
|
StorageEventFlagFileClose = (1 << 0),
|
|
} StorageEventFlag;
|
|
|
|
/****************** SHARED DISPATCH ******************/
|
|
|
|
// These four carry the whole body of a dozen API calls that differ only in the command.
|
|
|
|
static bool storage_file_command_bool(File* file, StorageCommand command) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
S_API_DATA_FILE;
|
|
S_API_MESSAGE(command);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
static uint64_t storage_file_command_uint64(File* file, StorageCommand command) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
S_API_DATA_FILE;
|
|
S_API_MESSAGE(command);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_UINT64;
|
|
}
|
|
|
|
static FS_Error storage_path_command(Storage* storage, const char* path, StorageCommand command) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
SAData data = {
|
|
.path = {
|
|
.path = path,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
S_API_MESSAGE(command);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
static FS_Error storage_sd_command(Storage* storage, StorageCommand command) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
SAData data = {};
|
|
S_API_MESSAGE(command);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
/****************** FILE ******************/
|
|
|
|
static bool storage_file_open_internal(
|
|
File* file,
|
|
const char* path,
|
|
FS_AccessMode access_mode,
|
|
FS_OpenMode open_mode) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.fopen = {
|
|
.file = file,
|
|
.path = path,
|
|
.access_mode = access_mode,
|
|
.open_mode = open_mode,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
file->type = FileTypeOpenFile;
|
|
|
|
S_API_MESSAGE(StorageCommandFileOpen);
|
|
S_API_EPILOGUE;
|
|
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
static void storage_file_close_callback(const void* message, void* context) {
|
|
const StorageEvent* storage_event = message;
|
|
|
|
if(storage_event->type == StorageEventTypeFileClose ||
|
|
storage_event->type == StorageEventTypeDirClose) {
|
|
furi_assert(context);
|
|
FuriEventFlag* event = context;
|
|
furi_event_flag_set(event, StorageEventFlagFileClose);
|
|
}
|
|
}
|
|
|
|
bool storage_file_open(
|
|
File* file,
|
|
const char* path,
|
|
FS_AccessMode access_mode,
|
|
FS_OpenMode open_mode) {
|
|
furi_check(file);
|
|
|
|
bool result;
|
|
FuriEventFlag* event = furi_event_flag_alloc();
|
|
FuriPubSubSubscription* subscription = furi_pubsub_subscribe(
|
|
storage_get_pubsub(file->storage), storage_file_close_callback, event);
|
|
|
|
do {
|
|
result = storage_file_open_internal(file, path, access_mode, open_mode);
|
|
|
|
if(!result && file->error_id == FSE_ALREADY_OPEN) {
|
|
furi_event_flag_wait(
|
|
event, StorageEventFlagFileClose, FuriFlagWaitAny, FuriWaitForever);
|
|
} else {
|
|
break;
|
|
}
|
|
} while(true);
|
|
|
|
furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription);
|
|
furi_event_flag_free(event);
|
|
|
|
FURI_LOG_T(
|
|
TAG,
|
|
"File %p - %p open (%s)",
|
|
(void*)((uint32_t)file - SRAM_BASE),
|
|
(void*)(file->file_id - SRAM_BASE),
|
|
path);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool storage_file_close(File* file) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
S_API_DATA_FILE;
|
|
S_API_MESSAGE(StorageCommandFileClose);
|
|
S_API_EPILOGUE;
|
|
|
|
FURI_LOG_T(
|
|
TAG,
|
|
"File %p - %p closed",
|
|
(void*)((uint32_t)file - SRAM_BASE),
|
|
(void*)(file->file_id - SRAM_BASE));
|
|
file->type = FileTypeClosed;
|
|
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
static uint16_t storage_file_read_underlying(File* file, void* buff, uint16_t bytes_to_read) {
|
|
if(bytes_to_read == 0) {
|
|
return 0;
|
|
}
|
|
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.fread = {
|
|
.file = file,
|
|
.buff = buff,
|
|
.bytes_to_read = bytes_to_read,
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandFileRead);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_UINT16;
|
|
}
|
|
|
|
static uint16_t
|
|
storage_file_write_underlying(File* file, const void* buff, uint16_t bytes_to_write) {
|
|
if(bytes_to_write == 0) {
|
|
return 0;
|
|
}
|
|
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.fwrite = {
|
|
.file = file,
|
|
.buff = buff,
|
|
.bytes_to_write = bytes_to_write,
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandFileWrite);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_UINT16;
|
|
}
|
|
|
|
size_t storage_file_read(File* file, void* buff, size_t to_read) {
|
|
size_t total = 0;
|
|
|
|
const size_t max_chunk = UINT16_MAX;
|
|
do {
|
|
const size_t chunk = MIN((to_read - total), max_chunk);
|
|
size_t read = storage_file_read_underlying(file, buff + total, chunk);
|
|
total += read;
|
|
|
|
if(storage_file_get_error(file) != FSE_OK || read != chunk) {
|
|
break;
|
|
}
|
|
} while(total != to_read);
|
|
|
|
return total;
|
|
}
|
|
|
|
size_t storage_file_write(File* file, const void* buff, size_t to_write) {
|
|
furi_check(file);
|
|
|
|
size_t total = 0;
|
|
|
|
const size_t max_chunk = UINT16_MAX;
|
|
do {
|
|
const size_t chunk = MIN((to_write - total), max_chunk);
|
|
size_t written = storage_file_write_underlying(file, buff + total, chunk);
|
|
total += written;
|
|
|
|
if(storage_file_get_error(file) != FSE_OK || written != chunk) {
|
|
break;
|
|
}
|
|
} while(total != to_write);
|
|
|
|
return total;
|
|
}
|
|
|
|
bool storage_file_seek(File* file, uint32_t offset, bool from_start) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.fseek = {
|
|
.file = file,
|
|
.offset = offset,
|
|
.from_start = from_start,
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandFileSeek);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
uint64_t storage_file_tell(File* file) {
|
|
return storage_file_command_uint64(file, StorageCommandFileTell);
|
|
}
|
|
|
|
bool storage_file_truncate(File* file) {
|
|
return storage_file_command_bool(file, StorageCommandFileTruncate);
|
|
}
|
|
|
|
uint64_t storage_file_size(File* file) {
|
|
return storage_file_command_uint64(file, StorageCommandFileSize);
|
|
}
|
|
|
|
bool storage_file_sync(File* file) {
|
|
return storage_file_command_bool(file, StorageCommandFileSync);
|
|
}
|
|
|
|
bool storage_file_eof(File* file) {
|
|
return storage_file_command_bool(file, StorageCommandFileEof);
|
|
}
|
|
|
|
bool storage_file_exists(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
|
|
bool exist = false;
|
|
FileInfo fileinfo;
|
|
FS_Error error = storage_common_stat(storage, path, &fileinfo);
|
|
|
|
if(error == FSE_OK && !file_info_is_dir(&fileinfo)) {
|
|
exist = true;
|
|
}
|
|
|
|
return exist;
|
|
}
|
|
|
|
bool storage_file_copy_to_file(File* source, File* destination, size_t size) {
|
|
furi_check(source);
|
|
furi_check(destination);
|
|
|
|
uint8_t* buffer = malloc(FILE_BUFFER_SIZE);
|
|
|
|
while(size) {
|
|
uint32_t read_size = size > FILE_BUFFER_SIZE ? FILE_BUFFER_SIZE : size;
|
|
if(storage_file_read(source, buffer, read_size) != read_size) {
|
|
break;
|
|
}
|
|
|
|
if(storage_file_write(destination, buffer, read_size) != read_size) {
|
|
break;
|
|
}
|
|
|
|
size -= read_size;
|
|
}
|
|
|
|
free(buffer);
|
|
return size == 0;
|
|
}
|
|
|
|
/****************** DIR ******************/
|
|
|
|
static bool storage_dir_open_internal(File* file, const char* path) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.dopen = {
|
|
.file = file,
|
|
.path = path,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
file->type = FileTypeOpenDir;
|
|
|
|
S_API_MESSAGE(StorageCommandDirOpen);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
bool storage_dir_open(File* file, const char* path) {
|
|
furi_check(file);
|
|
|
|
bool result;
|
|
FuriEventFlag* event = furi_event_flag_alloc();
|
|
FuriPubSubSubscription* subscription = furi_pubsub_subscribe(
|
|
storage_get_pubsub(file->storage), storage_file_close_callback, event);
|
|
|
|
do {
|
|
result = storage_dir_open_internal(file, path);
|
|
|
|
if(!result && file->error_id == FSE_ALREADY_OPEN) {
|
|
furi_event_flag_wait(
|
|
event, StorageEventFlagFileClose, FuriFlagWaitAny, FuriWaitForever);
|
|
} else {
|
|
break;
|
|
}
|
|
} while(true);
|
|
|
|
furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription);
|
|
furi_event_flag_free(event);
|
|
|
|
FURI_LOG_T(
|
|
TAG,
|
|
"Dir %p - %p open (%s)",
|
|
(void*)((uint32_t)file - SRAM_BASE),
|
|
(void*)(file->file_id - SRAM_BASE),
|
|
path);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool storage_dir_close(File* file) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
S_API_DATA_FILE;
|
|
S_API_MESSAGE(StorageCommandDirClose);
|
|
S_API_EPILOGUE;
|
|
|
|
FURI_LOG_T(
|
|
TAG,
|
|
"Dir %p - %p closed",
|
|
(void*)((uint32_t)file - SRAM_BASE),
|
|
(void*)(file->file_id - SRAM_BASE));
|
|
|
|
file->type = FileTypeClosed;
|
|
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length) {
|
|
S_FILE_API_PROLOGUE;
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.dread = {
|
|
.file = file,
|
|
.fileinfo = fileinfo,
|
|
.name = name,
|
|
.name_length = name_length,
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandDirRead);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
bool storage_dir_rewind(File* file) {
|
|
return storage_file_command_bool(file, StorageCommandDirRewind);
|
|
}
|
|
|
|
bool storage_dir_exists(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
|
|
bool exist = false;
|
|
FileInfo fileinfo;
|
|
FS_Error error = storage_common_stat(storage, path, &fileinfo);
|
|
|
|
if(error == FSE_OK && file_info_is_dir(&fileinfo)) {
|
|
exist = true;
|
|
}
|
|
|
|
return exist;
|
|
}
|
|
/****************** COMMON ******************/
|
|
|
|
FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) {
|
|
furi_check(storage);
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.ctimestamp = {
|
|
.path = path,
|
|
.timestamp = timestamp,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandCommonTimestamp);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
SAData data = {
|
|
.cstat = {
|
|
.path = path,
|
|
.fileinfo = fileinfo,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandCommonStat);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
FS_Error storage_common_remove(Storage* storage, const char* path) {
|
|
return storage_path_command(storage, path, StorageCommandCommonRemove);
|
|
}
|
|
|
|
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) {
|
|
furi_check(storage);
|
|
FS_Error error;
|
|
|
|
do {
|
|
if(!storage_common_exists(storage, old_path)) {
|
|
error = FSE_INVALID_NAME;
|
|
break;
|
|
}
|
|
|
|
if(storage_dir_exists(storage, old_path)) {
|
|
// Cannot overwrite a file with a directory
|
|
if(storage_file_exists(storage, new_path)) {
|
|
error = FSE_INVALID_NAME;
|
|
break;
|
|
}
|
|
|
|
// Cannot rename a directory to itself or to a nested directory
|
|
if(storage_common_is_subdir(storage, old_path, new_path)) {
|
|
error = FSE_INVALID_NAME;
|
|
break;
|
|
}
|
|
|
|
// Renaming a regular file to itself does nothing and always succeeds
|
|
} else if(storage_common_equivalent_path(storage, old_path, new_path)) {
|
|
error = FSE_OK;
|
|
break;
|
|
}
|
|
|
|
if(storage_file_exists(storage, new_path)) {
|
|
storage_common_remove(storage, new_path);
|
|
}
|
|
|
|
error = storage_common_copy(storage, old_path, new_path);
|
|
if(error != FSE_OK) {
|
|
break;
|
|
}
|
|
|
|
if(!storage_simply_remove_recursive(storage, old_path)) {
|
|
error = FSE_INTERNAL;
|
|
}
|
|
} while(false);
|
|
|
|
return error;
|
|
}
|
|
|
|
static FS_Error
|
|
storage_copy_recursive(Storage* storage, const char* old_path, const char* new_path) {
|
|
FS_Error error = storage_common_mkdir(storage, new_path);
|
|
DirWalk* dir_walk = dir_walk_alloc(storage);
|
|
FuriString* path;
|
|
FuriString* tmp_new_path;
|
|
FuriString* tmp_old_path;
|
|
FileInfo fileinfo;
|
|
path = furi_string_alloc();
|
|
tmp_new_path = furi_string_alloc();
|
|
tmp_old_path = furi_string_alloc();
|
|
|
|
do {
|
|
if(error != FSE_OK) break;
|
|
|
|
if(!dir_walk_open(dir_walk, old_path)) {
|
|
error = dir_walk_get_error(dir_walk);
|
|
break;
|
|
}
|
|
|
|
while(1) {
|
|
DirWalkResult res = dir_walk_read(dir_walk, path, &fileinfo);
|
|
|
|
if(res == DirWalkError) {
|
|
error = dir_walk_get_error(dir_walk);
|
|
break;
|
|
} else if(res == DirWalkLast) {
|
|
break;
|
|
} else {
|
|
furi_string_set(tmp_old_path, path);
|
|
furi_string_right(path, strlen(old_path));
|
|
furi_string_printf(tmp_new_path, "%s%s", new_path, furi_string_get_cstr(path));
|
|
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path));
|
|
} else {
|
|
error = storage_common_copy(
|
|
storage,
|
|
furi_string_get_cstr(tmp_old_path),
|
|
furi_string_get_cstr(tmp_new_path));
|
|
}
|
|
|
|
if(error != FSE_OK) break;
|
|
}
|
|
}
|
|
|
|
} while(false);
|
|
|
|
furi_string_free(tmp_new_path);
|
|
furi_string_free(tmp_old_path);
|
|
furi_string_free(path);
|
|
dir_walk_free(dir_walk);
|
|
return error;
|
|
}
|
|
|
|
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) {
|
|
furi_check(storage);
|
|
|
|
FS_Error error;
|
|
|
|
FileInfo fileinfo;
|
|
error = storage_common_stat(storage, old_path, &fileinfo);
|
|
|
|
if(error == FSE_OK) {
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
error = storage_copy_recursive(storage, old_path, new_path);
|
|
} else {
|
|
Stream* stream_from = file_stream_alloc(storage);
|
|
Stream* stream_to = file_stream_alloc(storage);
|
|
|
|
do {
|
|
if(!file_stream_open(stream_from, old_path, FSAM_READ, FSOM_OPEN_EXISTING)) break;
|
|
if(!file_stream_open(stream_to, new_path, FSAM_WRITE, FSOM_CREATE_NEW)) break;
|
|
stream_copy_full(stream_from, stream_to);
|
|
} while(false);
|
|
|
|
error = file_stream_get_error(stream_from);
|
|
if(error == FSE_OK) {
|
|
error = file_stream_get_error(stream_to);
|
|
}
|
|
|
|
stream_free(stream_from);
|
|
stream_free(stream_to);
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static FS_Error
|
|
storage_merge_recursive(Storage* storage, const char* old_path, const char* new_path) {
|
|
FS_Error error = FSE_OK;
|
|
DirWalk* dir_walk = dir_walk_alloc(storage);
|
|
FuriString *path, *file_basename, *tmp_new_path;
|
|
FileInfo fileinfo;
|
|
path = furi_string_alloc();
|
|
file_basename = furi_string_alloc();
|
|
tmp_new_path = furi_string_alloc();
|
|
|
|
do {
|
|
if(!storage_simply_mkdir(storage, new_path)) break;
|
|
|
|
dir_walk_set_recursive(dir_walk, false);
|
|
if(!dir_walk_open(dir_walk, old_path)) {
|
|
error = dir_walk_get_error(dir_walk);
|
|
break;
|
|
}
|
|
|
|
while(1) {
|
|
DirWalkResult res = dir_walk_read(dir_walk, path, &fileinfo);
|
|
|
|
if(res == DirWalkError) {
|
|
error = dir_walk_get_error(dir_walk);
|
|
break;
|
|
} else if(res == DirWalkLast) {
|
|
break;
|
|
} else {
|
|
path_extract_basename(furi_string_get_cstr(path), file_basename);
|
|
path_concat(new_path, furi_string_get_cstr(file_basename), tmp_new_path);
|
|
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
if(storage_common_stat(
|
|
storage, furi_string_get_cstr(tmp_new_path), &fileinfo) == FSE_OK) {
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
error =
|
|
storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path));
|
|
if(error != FSE_OK && error != FSE_EXIST) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error = storage_common_merge(
|
|
storage, furi_string_get_cstr(path), furi_string_get_cstr(tmp_new_path));
|
|
|
|
if(error != FSE_OK) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} while(false);
|
|
|
|
furi_string_free(tmp_new_path);
|
|
furi_string_free(file_basename);
|
|
furi_string_free(path);
|
|
dir_walk_free(dir_walk);
|
|
return error;
|
|
}
|
|
|
|
FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path) {
|
|
furi_check(storage);
|
|
|
|
FS_Error error;
|
|
const char* new_path_tmp = NULL;
|
|
FuriString* new_path_next = NULL;
|
|
new_path_next = furi_string_alloc();
|
|
|
|
FileInfo fileinfo;
|
|
error = storage_common_stat(storage, old_path, &fileinfo);
|
|
|
|
if(error == FSE_OK) {
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
error = storage_merge_recursive(storage, old_path, new_path);
|
|
} else {
|
|
error = storage_common_stat(storage, new_path, &fileinfo);
|
|
if(error == FSE_OK) {
|
|
furi_string_set(new_path_next, new_path);
|
|
FuriString* dir_path;
|
|
FuriString* filename;
|
|
char extension[MAX_EXT_LEN] = {0};
|
|
|
|
dir_path = furi_string_alloc();
|
|
filename = furi_string_alloc();
|
|
|
|
path_extract_filename(new_path_next, filename, true);
|
|
path_extract_dirname(new_path, dir_path);
|
|
path_extract_extension(new_path_next, extension, MAX_EXT_LEN);
|
|
|
|
storage_get_next_filename(
|
|
storage,
|
|
furi_string_get_cstr(dir_path),
|
|
furi_string_get_cstr(filename),
|
|
extension,
|
|
new_path_next,
|
|
255);
|
|
furi_string_cat_printf(
|
|
dir_path, "/%s%s", furi_string_get_cstr(new_path_next), extension);
|
|
furi_string_set(new_path_next, dir_path);
|
|
|
|
furi_string_free(dir_path);
|
|
furi_string_free(filename);
|
|
new_path_tmp = furi_string_get_cstr(new_path_next);
|
|
} else {
|
|
new_path_tmp = new_path;
|
|
}
|
|
Stream* stream_from = file_stream_alloc(storage);
|
|
Stream* stream_to = file_stream_alloc(storage);
|
|
|
|
do {
|
|
if(!file_stream_open(stream_from, old_path, FSAM_READ, FSOM_OPEN_EXISTING)) break;
|
|
if(!file_stream_open(stream_to, new_path_tmp, FSAM_WRITE, FSOM_CREATE_NEW)) break;
|
|
stream_copy_full(stream_from, stream_to);
|
|
} while(false);
|
|
|
|
error = file_stream_get_error(stream_from);
|
|
if(error == FSE_OK) {
|
|
error = file_stream_get_error(stream_to);
|
|
}
|
|
|
|
stream_free(stream_from);
|
|
stream_free(stream_to);
|
|
}
|
|
}
|
|
|
|
furi_string_free(new_path_next);
|
|
|
|
return error;
|
|
}
|
|
|
|
FS_Error storage_common_mkdir(Storage* storage, const char* path) {
|
|
return storage_path_command(storage, path, StorageCommandCommonMkDir);
|
|
}
|
|
|
|
FS_Error storage_common_fs_info(
|
|
Storage* storage,
|
|
const char* fs_path,
|
|
uint64_t* total_space,
|
|
uint64_t* free_space) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.cfsinfo = {
|
|
.fs_path = fs_path,
|
|
.total_space = total_space,
|
|
.free_space = free_space,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandCommonFSInfo);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.cresolvepath = {
|
|
.path = path,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandCommonResolvePath);
|
|
S_API_EPILOGUE;
|
|
}
|
|
|
|
FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest) {
|
|
furi_check(storage);
|
|
|
|
if(!storage_common_exists(storage, source)) {
|
|
return FSE_OK;
|
|
}
|
|
|
|
FS_Error error = storage_common_merge(storage, source, dest);
|
|
|
|
if(error == FSE_OK) {
|
|
storage_simply_remove_recursive(storage, source);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
bool storage_common_exists(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
|
|
FileInfo file_info;
|
|
return storage_common_stat(storage, path, &file_info) == FSE_OK;
|
|
}
|
|
|
|
static bool storage_internal_equivalent_path(
|
|
Storage* storage,
|
|
const char* path1,
|
|
const char* path2,
|
|
bool check_subdir) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
|
|
SAData data = {
|
|
.cequivpath = {
|
|
.path1 = path1,
|
|
.path2 = path2,
|
|
.check_subdir = check_subdir,
|
|
.thread_id = furi_thread_get_current_id(),
|
|
}};
|
|
|
|
S_API_MESSAGE(StorageCommandCommonEquivalentPath);
|
|
S_API_EPILOGUE;
|
|
|
|
return S_RETURN_BOOL;
|
|
}
|
|
|
|
bool storage_common_equivalent_path(Storage* storage, const char* path1, const char* path2) {
|
|
return storage_internal_equivalent_path(storage, path1, path2, false);
|
|
}
|
|
|
|
bool storage_common_is_subdir(Storage* storage, const char* parent, const char* child) {
|
|
return storage_internal_equivalent_path(storage, parent, child, true);
|
|
}
|
|
|
|
/****************** ERROR ******************/
|
|
|
|
const char* storage_error_get_desc(FS_Error error_id) {
|
|
return filesystem_api_error_get_desc(error_id);
|
|
}
|
|
|
|
FS_Error storage_file_get_error(File* file) {
|
|
furi_check(file);
|
|
return file->error_id;
|
|
}
|
|
|
|
int32_t storage_file_get_internal_error(File* file) {
|
|
furi_check(file);
|
|
return file->internal_error_id;
|
|
}
|
|
|
|
const char* storage_file_get_error_desc(File* file) {
|
|
furi_check(file);
|
|
return filesystem_api_error_get_desc(file->error_id);
|
|
}
|
|
|
|
/****************** Raw SD API ******************/
|
|
|
|
FS_Error storage_sd_format(Storage* storage) {
|
|
return storage_sd_command(storage, StorageCommandSDFormat);
|
|
}
|
|
|
|
FS_Error storage_sd_unmount(Storage* storage) {
|
|
return storage_sd_command(storage, StorageCommandSDUnmount);
|
|
}
|
|
|
|
FS_Error storage_sd_mount(Storage* storage) {
|
|
return storage_sd_command(storage, StorageCommandSDMount);
|
|
}
|
|
|
|
FS_Error storage_sd_info(Storage* storage, SDInfo* info) {
|
|
furi_check(storage);
|
|
|
|
S_API_PROLOGUE;
|
|
SAData data = {
|
|
.sdinfo = {
|
|
.info = info,
|
|
}};
|
|
S_API_MESSAGE(StorageCommandSDInfo);
|
|
S_API_EPILOGUE;
|
|
return S_RETURN_ERROR;
|
|
}
|
|
|
|
FS_Error storage_sd_status(Storage* storage) {
|
|
return storage_sd_command(storage, StorageCommandSDStatus);
|
|
}
|
|
|
|
File* storage_file_alloc(Storage* storage) {
|
|
furi_check(storage);
|
|
|
|
File* file = malloc(sizeof(File));
|
|
file->type = FileTypeClosed;
|
|
file->storage = storage;
|
|
|
|
FURI_LOG_T(TAG, "File/Dir %p alloc", (void*)((uint32_t)file - SRAM_BASE));
|
|
|
|
return file;
|
|
}
|
|
|
|
bool storage_file_is_open(File* file) {
|
|
furi_check(file);
|
|
return file->type != FileTypeClosed;
|
|
}
|
|
|
|
bool storage_file_is_dir(File* file) {
|
|
furi_check(file);
|
|
return file->type == FileTypeOpenDir;
|
|
}
|
|
|
|
void storage_file_free(File* file) {
|
|
furi_check(file);
|
|
|
|
if(storage_file_is_open(file)) {
|
|
if(storage_file_is_dir(file)) {
|
|
storage_dir_close(file);
|
|
} else {
|
|
storage_file_close(file);
|
|
}
|
|
}
|
|
|
|
FURI_LOG_T(TAG, "File/Dir %p free", (void*)((uint32_t)file - SRAM_BASE));
|
|
free(file);
|
|
}
|
|
|
|
FuriPubSub* storage_get_pubsub(Storage* storage) {
|
|
furi_check(storage);
|
|
return storage->pubsub;
|
|
}
|
|
|
|
bool storage_simply_remove_recursive(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
furi_check(path);
|
|
FileInfo fileinfo;
|
|
bool result = false;
|
|
FuriString* fullname;
|
|
FuriString* cur_dir;
|
|
|
|
if(storage_simply_remove(storage, path)) {
|
|
return true;
|
|
}
|
|
|
|
char* name = malloc(MAX_NAME_LENGTH + 1); //-V799
|
|
File* dir = storage_file_alloc(storage);
|
|
cur_dir = furi_string_alloc_set(path);
|
|
bool go_deeper = false;
|
|
|
|
while(1) {
|
|
if(!storage_dir_open(dir, furi_string_get_cstr(cur_dir))) {
|
|
storage_dir_close(dir);
|
|
break;
|
|
}
|
|
|
|
while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
|
|
if(file_info_is_dir(&fileinfo)) {
|
|
furi_string_cat_printf(cur_dir, "/%s", name); //-V576
|
|
go_deeper = true;
|
|
break;
|
|
}
|
|
|
|
fullname = furi_string_alloc_printf("%s/%s", furi_string_get_cstr(cur_dir), name);
|
|
FS_Error error = storage_common_remove(storage, furi_string_get_cstr(fullname));
|
|
furi_check(error == FSE_OK);
|
|
furi_string_free(fullname);
|
|
}
|
|
storage_dir_close(dir);
|
|
|
|
if(go_deeper) {
|
|
go_deeper = false;
|
|
continue;
|
|
}
|
|
|
|
FS_Error error = storage_common_remove(storage, furi_string_get_cstr(cur_dir));
|
|
furi_check(error == FSE_OK);
|
|
|
|
if(furi_string_cmp(cur_dir, path)) {
|
|
size_t last_char = furi_string_search_rchar(cur_dir, '/');
|
|
furi_assert(last_char != FURI_STRING_FAILURE);
|
|
furi_string_left(cur_dir, last_char);
|
|
} else {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
storage_file_free(dir);
|
|
furi_string_free(cur_dir);
|
|
free(name);
|
|
return result;
|
|
} //-V773
|
|
|
|
bool storage_simply_remove(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
|
|
FS_Error result;
|
|
result = storage_common_remove(storage, path);
|
|
return result == FSE_OK || result == FSE_NOT_EXIST;
|
|
}
|
|
|
|
bool storage_simply_mkdir(Storage* storage, const char* path) {
|
|
furi_check(storage);
|
|
|
|
FS_Error result;
|
|
result = storage_common_mkdir(storage, path);
|
|
return result == FSE_OK || result == FSE_EXIST;
|
|
}
|
|
|
|
void storage_get_next_filename(
|
|
Storage* storage,
|
|
const char* dirname,
|
|
const char* filename,
|
|
const char* fileextension,
|
|
FuriString* nextfilename,
|
|
uint8_t max_len) {
|
|
furi_check(storage);
|
|
|
|
FuriString* temp_str;
|
|
uint16_t num = 0;
|
|
|
|
temp_str = furi_string_alloc_printf("%s/%s%s", dirname, filename, fileextension);
|
|
|
|
while(storage_common_stat(storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK) {
|
|
num++;
|
|
furi_string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension);
|
|
}
|
|
if(num && (max_len > strlen(filename))) {
|
|
furi_string_printf(nextfilename, "%s%d", filename, num);
|
|
} else {
|
|
furi_string_printf(nextfilename, "%s", filename);
|
|
}
|
|
|
|
furi_string_free(temp_str);
|
|
}
|