Merge branch 'dev' into nfc-plugin-cache-names

This commit is contained in:
あく
2024-06-30 19:21:42 +01:00
committed by GitHub
41 changed files with 2228 additions and 413 deletions

View File

@@ -1,6 +1,9 @@
#include "../test.h" // IWYU pragma: keep
#include <toolbox/compress.h>
#include <toolbox/md5_calc.h>
#include <toolbox/tar/tar_archive.h>
#include <toolbox/dir_walk.h>
#include <furi.h>
#include <furi_hal.h>
@@ -56,7 +59,7 @@ static void compress_test_reference_comp_decomp() {
furi_record_close(RECORD_STORAGE);
uint8_t* temp_buffer = malloc(1024);
Compress* comp = compress_alloc(1024);
Compress* comp = compress_alloc(CompressTypeHeatshrink, &compress_config_heatshrink_default);
size_t encoded_size = 0;
mu_assert(
@@ -98,7 +101,7 @@ static void compress_test_random_comp_decomp() {
// We only fill half of the buffer with random data, so if anything goes wrong, there's no overflow
static const size_t src_data_size = src_buffer_size / 2;
Compress* comp = compress_alloc(src_buffer_size);
Compress* comp = compress_alloc(CompressTypeHeatshrink, &compress_config_heatshrink_default);
uint8_t* src_buff = malloc(src_buffer_size);
uint8_t* encoded_buff = malloc(encoded_buffer_size);
uint8_t* decoded_buff = malloc(src_buffer_size);
@@ -146,9 +149,200 @@ static void compress_test_random_comp_decomp() {
compress_free(comp);
}
static int32_t hs_unpacker_file_read(void* context, uint8_t* buffer, size_t size) {
File* file = (File*)context;
return storage_file_read(file, buffer, size);
}
static int32_t hs_unpacker_file_write(void* context, uint8_t* buffer, size_t size) {
File* file = (File*)context;
return storage_file_write(file, buffer, size);
}
/*
Source file was generated with:
```python3
import random, string
random.seed(1337)
with open("hsstream.out.bin", "wb") as f:
for c in random.choices(string.printable, k=1024):
for _ in range(random.randint(1, 10)):
f.write(c.encode())
```
It was compressed with heatshrink using the following command:
`python3 -m heatshrink2 compress -w 9 -l 4 hsstream.out.bin hsstream.in.bin`
*/
#define HSSTREAM_IN COMPRESS_UNIT_TESTS_PATH("hsstream.in.bin")
#define HSSTREAM_OUT COMPRESS_UNIT_TESTS_PATH("hsstream.out.bin")
static void compress_test_heatshrink_stream() {
Storage* api = furi_record_open(RECORD_STORAGE);
File* comp_file = storage_file_alloc(api);
File* dest_file = storage_file_alloc(api);
CompressConfigHeatshrink config = {
.window_sz2 = 9,
.lookahead_sz2 = 4,
.input_buffer_sz = 128,
};
Compress* compress = compress_alloc(CompressTypeHeatshrink, &config);
do {
storage_simply_remove(api, HSSTREAM_OUT);
mu_assert(
storage_file_open(comp_file, HSSTREAM_IN, FSAM_READ, FSOM_OPEN_EXISTING),
"Failed to open compressed file");
mu_assert(
storage_file_open(dest_file, HSSTREAM_OUT, FSAM_WRITE, FSOM_OPEN_ALWAYS),
"Failed to open decompressed file");
mu_assert(
compress_decode_streamed(
compress, hs_unpacker_file_read, comp_file, hs_unpacker_file_write, dest_file),
"Decompression failed");
storage_file_close(dest_file);
unsigned char md5[16];
FS_Error file_error;
mu_assert(
md5_calc_file(dest_file, HSSTREAM_OUT, md5, &file_error), "Failed to calculate md5");
const unsigned char expected_md5[16] = {
0xa3,
0x70,
0xe8,
0x8b,
0xa9,
0x42,
0x74,
0xf4,
0xaa,
0x12,
0x8d,
0x41,
0xd2,
0xb6,
0x71,
0xc9};
mu_assert(memcmp(md5, expected_md5, sizeof(md5)) == 0, "MD5 mismatch after decompression");
storage_simply_remove(api, HSSTREAM_OUT);
} while(false);
compress_free(compress);
storage_file_free(comp_file);
storage_file_free(dest_file);
furi_record_close(RECORD_STORAGE);
}
#define HS_TAR_PATH COMPRESS_UNIT_TESTS_PATH("test.ths")
#define HS_TAR_EXTRACT_PATH COMPRESS_UNIT_TESTS_PATH("tar_out")
static bool file_counter(const char* name, bool is_dir, void* context) {
UNUSED(name);
UNUSED(is_dir);
int32_t* n_entries = (int32_t*)context;
(*n_entries)++;
return true;
}
/*
Heatshrink tar file contents and MD5 sums:
file1.txt: 64295676ceed5cce2d0dcac402e4bda4
file2.txt: 188f67f297eedd7bf3d6a4d3c2fc31c4
dir/file3.txt: 34d98ad8135ffe502dba374690136d16
dir/big_file.txt: ee169c1e1791a4d319dbfaefaa850e98
dir/nested_dir/file4.txt: e099fcb2aaa0672375eaedc549247ee6
dir/nested_dir/empty_file.txt: d41d8cd98f00b204e9800998ecf8427e
XOR of all MD5 sums: 92ed5729786d0e1176d047e35f52d376
*/
static void compress_test_heatshrink_tar() {
Storage* api = furi_record_open(RECORD_STORAGE);
TarArchive* archive = tar_archive_alloc(api);
FuriString* path = furi_string_alloc();
FileInfo fileinfo;
File* file = storage_file_alloc(api);
do {
storage_simply_remove_recursive(api, HS_TAR_EXTRACT_PATH);
mu_assert(storage_simply_mkdir(api, HS_TAR_EXTRACT_PATH), "Failed to create extract dir");
mu_assert(
tar_archive_get_mode_for_path(HS_TAR_PATH) == TarOpenModeReadHeatshrink,
"Invalid mode for heatshrink tar");
mu_assert(
tar_archive_open(archive, HS_TAR_PATH, TarOpenModeReadHeatshrink),
"Failed to open heatshrink tar");
int32_t n_entries = 0;
tar_archive_set_file_callback(archive, file_counter, &n_entries);
mu_assert(
tar_archive_unpack_to(archive, HS_TAR_EXTRACT_PATH, NULL),
"Failed to unpack heatshrink tar");
mu_assert(n_entries == 9, "Invalid number of entries in heatshrink tar");
uint8_t md5_total[16] = {0}, md5_file[16];
DirWalk* dir_walk = dir_walk_alloc(api);
mu_assert(dir_walk_open(dir_walk, HS_TAR_EXTRACT_PATH), "Failed to open dirwalk");
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
if(file_info_is_dir(&fileinfo)) {
continue;
}
mu_assert(
md5_calc_file(file, furi_string_get_cstr(path), md5_file, NULL),
"Failed to calc md5");
for(size_t i = 0; i < 16; i++) {
md5_total[i] ^= md5_file[i];
}
}
dir_walk_free(dir_walk);
static const unsigned char expected_md5[16] = {
0x92,
0xed,
0x57,
0x29,
0x78,
0x6d,
0x0e,
0x11,
0x76,
0xd0,
0x47,
0xe3,
0x5f,
0x52,
0xd3,
0x76};
mu_assert(memcmp(md5_total, expected_md5, sizeof(md5_total)) == 0, "MD5 mismatch");
storage_simply_remove_recursive(api, HS_TAR_EXTRACT_PATH);
} while(false);
storage_file_free(file);
furi_string_free(path);
tar_archive_free(archive);
furi_record_close(RECORD_STORAGE);
}
MU_TEST_SUITE(test_compress) {
MU_RUN_TEST(compress_test_random_comp_decomp);
MU_RUN_TEST(compress_test_reference_comp_decomp);
MU_RUN_TEST(compress_test_heatshrink_stream);
MU_RUN_TEST(compress_test_heatshrink_tar);
}
int run_minunit_test_compress(void) {

View File

@@ -36,7 +36,22 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventByteEditResult) {
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
furi_string_printf(
ibutton->file_path,
"%s/%s%s",
IBUTTON_APP_FOLDER,
ibutton->key_name,
IBUTTON_APP_FILENAME_EXTENSION);
if(ibutton_save_key(ibutton)) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
} else {
const uint32_t possible_scenes[] = {
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
scene_manager_search_and_switch_to_previous_scene_one_of(
ibutton->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
}
} else if(event.event == iButtonCustomEventByteEditChanged) {
ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);
}

View File

@@ -41,9 +41,17 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
iButton* ibutton = context;
bool consumed = false;
const bool is_new_file = furi_string_empty(ibutton->file_path);
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventTextEditResult) {
if(!is_new_file) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_remove(storage, furi_string_get_cstr(ibutton->file_path));
furi_record_close(RECORD_STORAGE);
}
furi_string_printf(
ibutton->file_path,
"%s/%s%s",

View File

@@ -6,6 +6,7 @@ enum SubmenuIndex {
SubmenuIndexWriteBlank,
SubmenuIndexWriteCopy,
SubmenuIndexEdit,
SubmenuIndexRename,
SubmenuIndexDelete,
SubmenuIndexInfo,
};
@@ -34,6 +35,7 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) {
}
submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Rename", SubmenuIndexRename, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Delete", SubmenuIndexDelete, ibutton_submenu_callback, ibutton);
submenu_add_item(submenu, "Info", SubmenuIndexInfo, ibutton_submenu_callback, ibutton);
@@ -61,6 +63,8 @@ bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent even
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
} else if(event.event == SubmenuIndexEdit) {
scene_manager_next_scene(scene_manager, iButtonSceneAddValue);
} else if(event.event == SubmenuIndexRename) {
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
} else if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(scene_manager, iButtonSceneDeleteConfirm);
} else if(event.event == SubmenuIndexInfo) {

View File

@@ -31,8 +31,17 @@ bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) {
consumed = true;
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size);
scene_manager_next_scene(scene_manager, LfRfidSceneSaveName);
scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 1);
if(!furi_string_empty(app->file_name)) {
lfrfid_delete_key(app);
}
if(lfrfid_save_key(app)) {
scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, LfRfidSceneSavedKeyMenu);
}
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_set_scene_state(scene_manager, LfRfidSceneSaveData, 0);

View File

@@ -5,6 +5,7 @@ typedef enum {
SubmenuIndexEmulate,
SubmenuIndexWrite,
SubmenuIndexEdit,
SubmenuIndexRename,
SubmenuIndexDelete,
SubmenuIndexInfo,
} SubmenuIndex;
@@ -25,6 +26,8 @@ void lfrfid_scene_saved_key_menu_on_enter(void* context) {
submenu, "Write", SubmenuIndexWrite, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
submenu, "Edit", SubmenuIndexEdit, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
submenu, "Rename", SubmenuIndexRename, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
submenu, "Delete", SubmenuIndexDelete, lfrfid_scene_saved_key_menu_submenu_callback, app);
submenu_add_item(
@@ -51,6 +54,9 @@ bool lfrfid_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event
} else if(event.event == SubmenuIndexEdit) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveData);
consumed = true;
} else if(event.event == SubmenuIndexRename) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveName);
consumed = true;
} else if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(app->scene_manager, LfRfidSceneDeleteConfirm);
consumed = true;

View File

@@ -22,7 +22,7 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) {
AlignCenter,
AlignTop,
FontSecondary,
"Now use Mfkey32 to extract \nkeys: lab.flipper.net/nfc-tools");
"Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools");
widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25);
widget_add_button_element(
instance->widget,

View File

@@ -700,21 +700,21 @@ static void rpc_system_storage_tar_extract_process(const PB_Main* request, void*
TarArchive* archive = tar_archive_alloc(rpc_storage->api);
do {
if(!path_contains_only_ascii(request->content.storage_tar_extract_request.out_path)) {
const char *tar_path = request->content.storage_tar_extract_request.tar_path,
*out_path = request->content.storage_tar_extract_request.out_path;
if(!path_contains_only_ascii(out_path)) {
status = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
break;
}
if(!tar_archive_open(
archive,
request->content.storage_tar_extract_request.tar_path,
TAR_OPEN_MODE_READ)) {
TarOpenMode tar_mode = tar_archive_get_mode_for_path(tar_path);
if(!tar_archive_open(archive, tar_path, tar_mode)) {
status = PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER;
break;
}
if(!tar_archive_unpack_to(
archive, request->content.storage_tar_extract_request.out_path, NULL)) {
if(!tar_archive_unpack_to(archive, out_path, NULL)) {
status = PB_CommandStatus_ERROR_STORAGE_INTERNAL;
break;
}

View File

@@ -5,42 +5,22 @@
#include <lib/toolbox/args.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/tar/tar_archive.h>
#include <storage/storage.h>
#include <storage/storage_sd_api.h>
#include <power/power_service/power.h>
#define MAX_NAME_LENGTH 255
static void storage_cli_print_usage(void) {
printf("Usage:\r\n");
printf("storage <cmd> <path> <args>\r\n");
printf("The path must start with /int or /ext\r\n");
printf("Cmd list:\r\n");
printf("\tinfo\t - get FS info\r\n");
printf("\tformat\t - format filesystem\r\n");
printf("\tlist\t - list files and dirs\r\n");
printf("\ttree\t - list files and dirs, recursive\r\n");
printf("\tremove\t - delete the file or directory\r\n");
printf("\tread\t - read text from file and print file size and content to cli\r\n");
printf(
"\tread_chunks\t - read data from file and print file size and content to cli, <args> should contain how many bytes you want to read in block\r\n");
printf("\twrite\t - read text from cli and append it to file, stops by ctrl+c\r\n");
printf(
"\twrite_chunk\t - read data from cli and append it to file, <args> should contain how many bytes you want to write\r\n");
printf("\tcopy\t - copy file to new file, <args> must contain new path\r\n");
printf("\trename\t - move file to new file, <args> must contain new path\r\n");
printf("\tmkdir\t - creates a new directory\r\n");
printf("\tmd5\t - md5 hash of the file\r\n");
printf("\tstat\t - info about file or dir\r\n");
printf("\ttimestamp\t - last modification timestamp\r\n");
};
static void storage_cli_print_usage(void);
static void storage_cli_print_error(FS_Error error) {
printf("Storage error: %s\r\n", storage_error_get_desc(error));
}
static void storage_cli_info(Cli* cli, FuriString* path) {
static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
@@ -88,7 +68,8 @@ static void storage_cli_info(Cli* cli, FuriString* path) {
furi_record_close(RECORD_STORAGE);
};
static void storage_cli_format(Cli* cli, FuriString* path) {
static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(args);
if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) {
storage_cli_print_error(FSE_NOT_IMPLEMENTED);
} else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
@@ -114,8 +95,9 @@ static void storage_cli_format(Cli* cli, FuriString* path) {
}
};
static void storage_cli_list(Cli* cli, FuriString* path) {
static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) {
printf("\t[D] int\r\n");
printf("\t[D] ext\r\n");
@@ -151,12 +133,13 @@ static void storage_cli_list(Cli* cli, FuriString* path) {
}
}
static void storage_cli_tree(Cli* cli, FuriString* path) {
static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(args);
if(furi_string_cmp_str(path, "/") == 0) {
furi_string_set(path, STORAGE_INT_PATH_PREFIX);
storage_cli_tree(cli, path);
storage_cli_tree(cli, path, NULL);
furi_string_set(path, STORAGE_EXT_PATH_PREFIX);
storage_cli_tree(cli, path);
storage_cli_tree(cli, path, NULL);
} else {
Storage* api = furi_record_open(RECORD_STORAGE);
DirWalk* dir_walk = dir_walk_alloc(api);
@@ -192,8 +175,9 @@ static void storage_cli_tree(Cli* cli, FuriString* path) {
}
}
static void storage_cli_read(Cli* cli, FuriString* path) {
static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -223,7 +207,8 @@ static void storage_cli_read(Cli* cli, FuriString* path) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_write(Cli* cli, FuriString* path) {
static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
@@ -353,8 +338,9 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_stat(Cli* cli, FuriString* path) {
static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
if(furi_string_cmp_str(path, "/") == 0) {
@@ -394,8 +380,9 @@ static void storage_cli_stat(Cli* cli, FuriString* path) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_timestamp(Cli* cli, FuriString* path) {
static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
uint32_t timestamp = 0;
@@ -431,8 +418,9 @@ static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_remove(Cli* cli, FuriString* path) {
static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_remove(api, furi_string_get_cstr(path));
@@ -464,8 +452,9 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args)
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_mkdir(Cli* cli, FuriString* path) {
static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path));
@@ -476,8 +465,9 @@ static void storage_cli_mkdir(Cli* cli, FuriString* path) {
furi_record_close(RECORD_STORAGE);
}
static void storage_cli_md5(Cli* cli, FuriString* path) {
static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) {
UNUSED(cli);
UNUSED(args);
Storage* api = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(api);
FuriString* md5 = furi_string_alloc();
@@ -496,6 +486,152 @@ static void storage_cli_md5(Cli* cli, FuriString* path) {
furi_record_close(RECORD_STORAGE);
}
static bool tar_extract_file_callback(const char* name, bool is_directory, void* context) {
UNUSED(context);
printf("\t%s %s\r\n", is_directory ? "D" : "F", name);
return true;
}
static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) {
UNUSED(cli);
FuriString* new_path = furi_string_alloc();
if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
storage_cli_print_usage();
furi_string_free(new_path);
return;
}
Storage* api = furi_record_open(RECORD_STORAGE);
TarArchive* archive = tar_archive_alloc(api);
TarOpenMode tar_mode = tar_archive_get_mode_for_path(furi_string_get_cstr(old_path));
do {
if(!tar_archive_open(archive, furi_string_get_cstr(old_path), tar_mode)) {
printf("Failed to open archive\r\n");
break;
}
uint32_t start_tick = furi_get_tick();
tar_archive_set_file_callback(archive, tar_extract_file_callback, NULL);
printf("Unpacking to %s\r\n", furi_string_get_cstr(new_path));
bool success = tar_archive_unpack_to(archive, furi_string_get_cstr(new_path), NULL);
uint32_t end_tick = furi_get_tick();
printf(
"Decompression %s in %lu ticks \r\n",
success ? "success" : "failed",
end_tick - start_tick);
} while(false);
tar_archive_free(archive);
furi_string_free(new_path);
furi_record_close(RECORD_STORAGE);
}
typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args);
typedef struct {
const char* command;
const char* help;
const StorageCliCommandCallback impl;
} StorageCliCommand;
static const StorageCliCommand storage_cli_commands[] = {
{
"write_chunk",
"read data from cli and append it to file, <args> should contain how many bytes you want to write",
&storage_cli_write_chunk,
},
{
"read_chunks",
"read data from file and print file size and content to cli, <args> should contain how many bytes you want to read in block",
&storage_cli_read_chunks,
},
{
"list",
"list files and dirs",
&storage_cli_list,
},
{
"md5",
"md5 hash of the file",
&storage_cli_md5,
},
{
"stat",
"info about file or dir",
&storage_cli_stat,
},
{
"info",
"get FS info",
&storage_cli_info,
},
{
"tree",
"list files and dirs, recursive",
&storage_cli_tree,
},
{
"read",
"read text from file and print file size and content to cli",
&storage_cli_read,
},
{
"write",
"read text from cli and append it to file, stops by ctrl+c",
&storage_cli_write,
},
{
"copy",
"copy file to new file, <args> must contain new path",
&storage_cli_copy,
},
{
"remove",
"delete the file or directory",
&storage_cli_remove,
},
{
"rename",
"move file to new file, <args> must contain new path",
&storage_cli_rename,
},
{
"mkdir",
"creates a new directory",
&storage_cli_mkdir,
},
{
"timestamp",
"last modification timestamp",
&storage_cli_timestamp,
},
{
"extract",
"extract tar archive to destination",
&storage_cli_extract,
},
{
"format",
"format filesystem",
&storage_cli_format,
},
};
static void storage_cli_print_usage(void) {
printf("Usage:\r\n");
printf("storage <cmd> <path> <args>\r\n");
printf("The path must start with /int or /ext\r\n");
printf("Cmd list:\r\n");
for(size_t i = 0; i < COUNT_OF(storage_cli_commands); ++i) {
const StorageCliCommand* command_descr = &storage_cli_commands[i];
const char* cli_cmd = command_descr->command;
printf(
"\t%s%s - %s\r\n", cli_cmd, strlen(cli_cmd) > 8 ? "\t" : "\t\t", command_descr->help);
}
};
void storage_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
@@ -514,82 +650,18 @@ void storage_cli(Cli* cli, FuriString* args, void* context) {
break;
}
if(furi_string_cmp_str(cmd, "info") == 0) {
storage_cli_info(cli, path);
break;
size_t i = 0;
for(; i < COUNT_OF(storage_cli_commands); ++i) {
const StorageCliCommand* command_descr = &storage_cli_commands[i];
if(furi_string_cmp_str(cmd, command_descr->command) == 0) {
command_descr->impl(cli, path, args);
break;
}
}
if(furi_string_cmp_str(cmd, "format") == 0) {
storage_cli_format(cli, path);
break;
if(i == COUNT_OF(storage_cli_commands)) {
storage_cli_print_usage();
}
if(furi_string_cmp_str(cmd, "list") == 0) {
storage_cli_list(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "tree") == 0) {
storage_cli_tree(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "read") == 0) {
storage_cli_read(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "read_chunks") == 0) {
storage_cli_read_chunks(cli, path, args);
break;
}
if(furi_string_cmp_str(cmd, "write") == 0) {
storage_cli_write(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "write_chunk") == 0) {
storage_cli_write_chunk(cli, path, args);
break;
}
if(furi_string_cmp_str(cmd, "copy") == 0) {
storage_cli_copy(cli, path, args);
break;
}
if(furi_string_cmp_str(cmd, "remove") == 0) {
storage_cli_remove(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "rename") == 0) {
storage_cli_rename(cli, path, args);
break;
}
if(furi_string_cmp_str(cmd, "mkdir") == 0) {
storage_cli_mkdir(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "md5") == 0) {
storage_cli_md5(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "stat") == 0) {
storage_cli_stat(cli, path);
break;
}
if(furi_string_cmp_str(cmd, "timestamp") == 0) {
storage_cli_timestamp(cli, path);
break;
}
storage_cli_print_usage();
} while(false);
furi_string_free(path);

View File

@@ -6,7 +6,7 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname) {
furi_check(storage);
TarArchive* archive = tar_archive_alloc(storage);
bool success = tar_archive_open(archive, dstname, TAR_OPEN_MODE_WRITE) &&
bool success = tar_archive_open(archive, dstname, TarOpenModeWrite) &&
tar_archive_add_dir(archive, STORAGE_INT_PATH_PREFIX, "") &&
tar_archive_finalize(archive);
tar_archive_free(archive);
@@ -18,7 +18,7 @@ FS_Error
furi_check(storage);
TarArchive* archive = tar_archive_alloc(storage);
bool success = tar_archive_open(archive, srcname, TAR_OPEN_MODE_READ) &&
bool success = tar_archive_open(archive, srcname, TarOpenModeRead) &&
tar_archive_unpack_to(archive, STORAGE_INT_PATH_PREFIX, converter);
tar_archive_free(archive);
return success ? FSE_OK : FSE_INTERNAL;

View File

@@ -9,6 +9,8 @@
#include <update_util/lfs_backup.h>
#include <update_util/update_operation.h>
#define TAG "UpdWorker"
static const char* update_task_stage_descr[] = {
[UpdateTaskStageProgress] = "...",
[UpdateTaskStageReadManifest] = "Loading update manifest",
@@ -23,7 +25,9 @@ static const char* update_task_stage_descr[] = {
[UpdateTaskStageOBValidation] = "Validating opt. bytes",
[UpdateTaskStageLfsBackup] = "Backing up LFS",
[UpdateTaskStageLfsRestore] = "Restoring LFS",
[UpdateTaskStageResourcesUpdate] = "Updating resources",
[UpdateTaskStageResourcesFileCleanup] = "Cleaning up files",
[UpdateTaskStageResourcesDirCleanup] = "Cleaning up directories",
[UpdateTaskStageResourcesFileUnpack] = "Extracting resources",
[UpdateTaskStageSplashscreenInstall] = "Installing splashscreen",
[UpdateTaskStageCompleted] = "Restarting...",
[UpdateTaskStageError] = "Error",
@@ -196,7 +200,19 @@ static const struct {
.descr = "LFS I/O error",
},
{
.stage = UpdateTaskStageResourcesUpdate,
.stage = UpdateTaskStageResourcesFileCleanup,
.percent_min = 0,
.percent_max = 100,
.descr = "SD card I/O error",
},
{
.stage = UpdateTaskStageResourcesDirCleanup,
.percent_min = 0,
.percent_max = 100,
.descr = "SD card I/O error",
},
{
.stage = UpdateTaskStageResourcesFileUnpack,
.percent_min = 0,
.percent_max = 100,
.descr = "SD card I/O error",
@@ -230,20 +246,22 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = {
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 35),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 25),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 40),
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 30),
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 5),
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 2),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30),
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 150),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 15),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 33),
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 20),
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5),
[UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255),
[UpdateTaskStageResourcesFileCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 100),
[UpdateTaskStageResourcesDirCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 50),
[UpdateTaskStageResourcesFileUnpack] = STAGE_DEF(UpdateTaskStageGroupResources, 255),
[UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5),
[UpdateTaskStageCompleted] = STAGE_DEF(UpdateTaskStageGroupMisc, 1),
@@ -288,6 +306,7 @@ static void update_task_calc_completed_stages(UpdateTask* update_task) {
void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress) {
if(stage != UpdateTaskStageProgress) {
FURI_LOG_I(TAG, "Stage %d, progress %d", stage, progress);
/* do not override more specific error states */
if((stage >= UpdateTaskStageError) && (update_task->state.stage >= UpdateTaskStageError)) {
return;

View File

@@ -31,7 +31,9 @@ typedef enum {
UpdateTaskStageFlashValidate,
UpdateTaskStageLfsRestore,
UpdateTaskStageResourcesUpdate,
UpdateTaskStageResourcesFileCleanup,
UpdateTaskStageResourcesDirCleanup,
UpdateTaskStageResourcesFileUnpack,
UpdateTaskStageSplashscreenInstall,
UpdateTaskStageCompleted,

View File

@@ -35,36 +35,23 @@ static bool update_task_pre_update(UpdateTask* update_task) {
furi_string_free(backup_file_path);
return success;
}
typedef enum {
UpdateTaskResourcesWeightsFileCleanup = 20,
UpdateTaskResourcesWeightsDirCleanup = 20,
UpdateTaskResourcesWeightsFileUnpack = 60,
} UpdateTaskResourcesWeights;
#define UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT 90
typedef struct {
UpdateTask* update_task;
int32_t total_files, processed_files;
TarArchive* archive;
} TarUnpackProgress;
static bool update_task_resource_unpack_cb(const char* name, bool is_directory, void* context) {
UNUSED(name);
UNUSED(is_directory);
TarUnpackProgress* unpack_progress = context;
unpack_progress->processed_files++;
int32_t progress = 0, total = 0;
tar_archive_get_read_progress(unpack_progress->archive, &progress, &total);
update_task_set_progress(
unpack_progress->update_task,
UpdateTaskStageProgress,
/* For this stage, last progress segment = extraction */
(UpdateTaskResourcesWeightsFileCleanup + UpdateTaskResourcesWeightsDirCleanup) +
(unpack_progress->processed_files * UpdateTaskResourcesWeightsFileUnpack) /
(unpack_progress->total_files + 1));
unpack_progress->update_task, UpdateTaskStageProgress, (progress * 100) / (total + 1));
return true;
}
static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_t n_tar_entries) {
static void update_task_cleanup_resources(UpdateTask* update_task) {
ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage);
do {
FURI_LOG_D(TAG, "Cleaning up old manifest");
@@ -73,20 +60,26 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_
break;
}
const uint32_t n_approx_file_entries =
n_tar_entries * UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT / 100 + 1;
uint32_t n_dir_entries = 1;
ResourceManifestEntry* entry_ptr = NULL;
uint32_t n_processed_entries = 0;
/* Iterate over manifest and calculate entries count */
uint32_t n_file_entries = 1, n_dir_entries = 1;
while((entry_ptr = resource_manifest_reader_next(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeFile) {
n_file_entries++;
} else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
n_dir_entries++;
}
}
resource_manifest_rewind(manifest_reader);
update_task_set_progress(update_task, UpdateTaskStageResourcesFileCleanup, 0);
uint32_t n_processed_file_entries = 0;
while((entry_ptr = resource_manifest_reader_next(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeFile) {
update_task_set_progress(
update_task,
UpdateTaskStageProgress,
/* For this stage, first pass = old manifest's file cleanup */
(n_processed_entries++ * UpdateTaskResourcesWeightsFileCleanup) /
n_approx_file_entries);
(n_processed_file_entries++ * 100) / n_file_entries);
FuriString* file_path = furi_string_alloc();
path_concat(
@@ -108,16 +101,14 @@ static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_
}
}
n_processed_entries = 0;
update_task_set_progress(update_task, UpdateTaskStageResourcesDirCleanup, 0);
uint32_t n_processed_dir_entries = 0;
while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
update_task_set_progress(
update_task,
UpdateTaskStageProgress,
/* For this stage, second 10% of progress = cleanup directories */
UpdateTaskResourcesWeightsFileCleanup +
(n_processed_entries++ * UpdateTaskResourcesWeightsDirCleanup) /
n_dir_entries);
(n_processed_dir_entries++ * 100) / n_dir_entries);
FuriString* folder_path = furi_string_alloc();
@@ -166,26 +157,22 @@ static bool update_task_post_update(UpdateTask* update_task) {
if(update_task->state.groups & UpdateTaskStageGroupResources) {
TarUnpackProgress progress = {
.update_task = update_task,
.total_files = 0,
.processed_files = 0,
.archive = archive,
};
update_task_set_progress(update_task, UpdateTaskStageResourcesUpdate, 0);
path_concat(
furi_string_get_cstr(update_task->update_path),
furi_string_get_cstr(update_task->manifest->resource_bundle),
file_path);
CHECK_RESULT(tar_archive_open(
archive, furi_string_get_cstr(file_path), TarOpenModeReadHeatshrink));
update_task_cleanup_resources(update_task);
update_task_set_progress(update_task, UpdateTaskStageResourcesFileUnpack, 0);
tar_archive_set_file_callback(archive, update_task_resource_unpack_cb, &progress);
CHECK_RESULT(
tar_archive_open(archive, furi_string_get_cstr(file_path), TAR_OPEN_MODE_READ));
progress.total_files = tar_archive_get_entries_count(archive);
if(progress.total_files > 0) {
update_task_cleanup_resources(update_task, progress.total_files);
CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL));
}
CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL));
}
if(update_task->state.groups & UpdateTaskStageGroupSplashscreen) {