Skip to content

Commit

Permalink
ELF, Flipper application: do not crash on "out of memory" (flipperdev…
Browse files Browse the repository at this point in the history
…ices#3664)

* ELF, Flipper application: do not crash on "out of memory"
* loader: better error messages
* typo
* fix position
* Loader: QR code for common errors
* NFC: error message
* Loader: error descriptions
  • Loading branch information
DrZlo13 authored Jun 17, 2024
1 parent 729db7f commit d8ef099
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 121 deletions.
9 changes: 3 additions & 6 deletions applications/main/nfc/nfc_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,13 +464,10 @@ static bool nfc_is_hal_ready(void) {
// No connection to the chip, show an error screen
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Error: NFC Chip Failed", 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
message,
"Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\[email protected]",
0,
0,
AlignLeft,
AlignTop);
message, "Send error photo via\nsupport.flipper.net", 0, 63, AlignLeft, AlignBottom);
dialog_message_set_icon(message, &I_err_09, 128 - 25, 64 - 25);
dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
Expand Down
216 changes: 169 additions & 47 deletions applications/services/loader/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ static const char* loader_find_external_application_by_name(const char* app_name

// API

LoaderStatus
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
furi_check(loader);
furi_check(name);

static LoaderMessageLoaderStatusResult loader_start_internal(
Loader* loader,
const char* name,
const char* args,
FuriString* error_message) {
LoaderMessage message;
LoaderMessageLoaderStatusResult result;

Expand All @@ -44,54 +44,138 @@ LoaderStatus
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);

return result.value;
return result;
}

static void
loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) {
if(status == LoaderStatusErrorUnknownApp &&
typedef struct {
const char* error;
const char* description;
const char* url;
const Icon* icon;
} LoaderError;

static const LoaderError err_app_not_found =
{"App Not Found", "Update firmware or app", "err_01", &I_err_01};
static const LoaderError err_invalid_flie = {"Invalid File", "Update the app", "err_02", &I_err_02};
static const LoaderError err_invalid_manifest =
{"Invalid Manifest", "Update firmware or app", "err_03", &I_err_03};
static const LoaderError err_missing_imports =
{"Missing Imports", "Update firmware", "err_04", &I_err_04};
static const LoaderError err_hw_target_mismatch =
{"HW Target\nMismatch", "App not supported", "err_05", &I_err_05};
static const LoaderError err_outdated_app = {"Outdated App", "Update the app", "err_06", &I_err_06};
static const LoaderError err_outdated_firmware =
{"Outdated\nFirmware", "Update firmware", "err_07", &I_err_07};

static void loader_dialog_prepare_and_show(DialogsApp* dialogs, const LoaderError* err) {
FuriString* header = furi_string_alloc_printf("Error: %s", err->error);
FuriString* text =
furi_string_alloc_printf("%s\nLearn more:\nr.flipper.net/%s", err->description, err->url);
DialogMessage* message = dialog_message_alloc();

dialog_message_set_header(message, furi_string_get_cstr(header), 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(message, furi_string_get_cstr(text), 0, 63, AlignLeft, AlignBottom);
dialog_message_set_icon(message, err->icon, 128 - 25, 64 - 25);
dialog_message_show(dialogs, message);

dialog_message_free(message);
furi_string_free(header);
furi_string_free(text);
}

static void loader_show_gui_error(
LoaderMessageLoaderStatusResult status,
const char* name,
FuriString* error_message) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();

if(status.value == LoaderStatusErrorUnknownApp &&
loader_find_external_application_by_name(name) != NULL) {
// Special case for external apps
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop);
dialog_message_set_buttons(message, NULL, NULL, NULL);
dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22);
dialog_message_set_text(
message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop);
dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
} else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) {
} else if(status.value == LoaderStatusErrorUnknownApp) {
loader_dialog_prepare_and_show(dialogs, &err_app_not_found);
} else if(status.value == LoaderStatusErrorInternal) {
// TODO FL-3522: we have many places where we can emit a double start, ex: desktop, menu
// so i prefer to not show LoaderStatusErrorAppStarted error message for now
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(message, NULL, NULL, NULL);

furi_string_replace(error_message, "/ext/apps/", "");
furi_string_replace(error_message, ", ", "\n");
furi_string_replace(error_message, ": ", "\n");
switch(status.error) {
case LoaderStatusErrorInvalidFile:
loader_dialog_prepare_and_show(dialogs, &err_invalid_flie);
break;
case LoaderStatusErrorInvalidManifest:
loader_dialog_prepare_and_show(dialogs, &err_invalid_manifest);
break;
case LoaderStatusErrorMissingImports:
loader_dialog_prepare_and_show(dialogs, &err_missing_imports);
break;
case LoaderStatusErrorHWMismatch:
loader_dialog_prepare_and_show(dialogs, &err_hw_target_mismatch);
break;
case LoaderStatusErrorOutdatedApp:
loader_dialog_prepare_and_show(dialogs, &err_outdated_app);
break;
case LoaderStatusErrorOutdatedFirmware:
loader_dialog_prepare_and_show(dialogs, &err_outdated_firmware);
break;
case LoaderStatusErrorOutOfMemory:
dialog_message_set_header(
message, "Error: Out of Memory", 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
message,
"Not enough RAM to run the\napp. Please reboot the device",
64,
13,
AlignCenter,
AlignTop);
dialog_message_set_buttons(message, NULL, NULL, "Reboot");
if(dialog_message_show(dialogs, message) == DialogMessageButtonRight) {
furi_hal_power_reset();
}
break;
default:
// Generic error
dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop);

dialog_message_set_text(
message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter);
furi_string_replace(error_message, "/ext/apps/", "");
furi_string_replace(error_message, ", ", "\n");
furi_string_replace(error_message, ": ", "\n");

dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
dialog_message_set_text(
message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter);
dialog_message_show(dialogs, message);
break;
}
}

dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
}

LoaderStatus
loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
furi_check(loader);
furi_check(name);

LoaderMessageLoaderStatusResult result =
loader_start_internal(loader, name, args, error_message);
return result.value;
}

LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) {
furi_check(loader);
furi_check(name);

FuriString* error_message = furi_string_alloc();
LoaderStatus status = loader_start(loader, name, args, error_message);
loader_show_gui_error(status, name, error_message);
LoaderMessageLoaderStatusResult result =
loader_start_internal(loader, name, args, error_message);
loader_show_gui_error(result, name, error_message);
furi_string_free(error_message);
return status;
return result.value;
}

void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) {
Expand Down Expand Up @@ -316,13 +400,46 @@ static LoaderStatus loader_make_success_status(FuriString* error_message) {
return LoaderStatusOk;
}

static LoaderStatus loader_start_external_app(
static LoaderStatusError
loader_status_error_from_preload_status(FlipperApplicationPreloadStatus status) {
switch(status) {
case FlipperApplicationPreloadStatusInvalidFile:
return LoaderStatusErrorInvalidFile;
case FlipperApplicationPreloadStatusNotEnoughMemory:
return LoaderStatusErrorOutOfMemory;
case FlipperApplicationPreloadStatusInvalidManifest:
return LoaderStatusErrorInvalidManifest;
case FlipperApplicationPreloadStatusApiTooOld:
return LoaderStatusErrorOutdatedApp;
case FlipperApplicationPreloadStatusApiTooNew:
return LoaderStatusErrorOutdatedFirmware;
case FlipperApplicationPreloadStatusTargetMismatch:
return LoaderStatusErrorHWMismatch;
default:
return LoaderStatusErrorUnknown;
}
}

static LoaderStatusError
loader_status_error_from_load_status(FlipperApplicationLoadStatus status) {
switch(status) {
case FlipperApplicationLoadStatusMissingImports:
return LoaderStatusErrorMissingImports;
default:
return LoaderStatusErrorUnknown;
}
}

static LoaderMessageLoaderStatusResult loader_start_external_app(
Loader* loader,
Storage* storage,
const char* path,
const char* args,
FuriString* error_message) {
LoaderStatus status = loader_make_success_status(error_message);
LoaderMessageLoaderStatusResult result;
result.value = loader_make_success_status(error_message);
result.error = LoaderStatusErrorUnknown;

LoaderEvent event;
event.type = LoaderEventTypeApplicationBeforeLoad;
furi_pubsub_publish(loader->pubsub, &event);
Expand All @@ -337,24 +454,26 @@ static LoaderStatus loader_start_external_app(
flipper_application_preload(loader->app.fap, path);
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
const char* err_msg = flipper_application_preload_status_to_string(preload_res);
status = loader_make_status_error(
result.value = loader_make_status_error(
LoaderStatusErrorInternal, error_message, "Preload failed, %s: %s", path, err_msg);
result.error = loader_status_error_from_preload_status(preload_res);
break;
}

FlipperApplicationLoadStatus load_status =
flipper_application_map_to_memory(loader->app.fap);
if(load_status != FlipperApplicationLoadStatusSuccess) {
const char* err_msg = flipper_application_load_status_to_string(load_status);
status = loader_make_status_error(
result.value = loader_make_status_error(
LoaderStatusErrorInternal, error_message, "Load failed, %s: %s", path, err_msg);
result.error = loader_status_error_from_load_status(load_status);
break;
}

FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start));

if(flipper_application_is_plugin(loader->app.fap)) {
status = loader_make_status_error(
result.value = loader_make_status_error(
LoaderStatusErrorInternal, error_message, "Plugin %s is not runnable", path);
break;
}
Expand All @@ -376,14 +495,14 @@ static LoaderStatus loader_start_external_app(
loader_start_app_thread(loader, FlipperInternalApplicationFlagDefault);
} while(0);

if(status != LoaderStatusOk) {
if(result.value != LoaderStatusOk) {
flipper_application_free(loader->app.fap);
loader->app.fap = NULL;
event.type = LoaderEventTypeApplicationLoadFailed;
furi_pubsub_publish(loader->pubsub, &event);
}

return status;
return result;
}

// process messages
Expand Down Expand Up @@ -419,23 +538,26 @@ static bool loader_do_is_locked(Loader* loader) {
return loader->app.thread != NULL;
}

static LoaderStatus loader_do_start_by_name(
static LoaderMessageLoaderStatusResult loader_do_start_by_name(
Loader* loader,
const char* name,
const char* args,
FuriString* error_message) {
LoaderStatus status;
LoaderMessageLoaderStatusResult status;
status.value = loader_make_success_status(error_message);
status.error = LoaderStatusErrorUnknown;

do {
// check lock
if(loader_do_is_locked(loader)) {
if(loader->app.thread == (FuriThread*)LOADER_MAGIC_THREAD_VALUE) {
status = loader_make_status_error(
status.value = loader_make_status_error(
LoaderStatusErrorAppStarted, error_message, "Loader is locked");
} else {
const char* current_thread_name =
furi_thread_get_name(furi_thread_get_id(loader->app.thread));

status = loader_make_status_error(
status.value = loader_make_status_error(
LoaderStatusErrorAppStarted,
error_message,
"Loader is locked, please close the \"%s\" first",
Expand All @@ -449,15 +571,15 @@ static LoaderStatus loader_do_start_by_name(
const FlipperInternalApplication* app = loader_find_application_by_name(name);
if(app) {
loader_start_internal_app(loader, app, args);
status = loader_make_success_status(error_message);
status.value = loader_make_success_status(error_message);
break;
}
}

// check Applications
if(strcmp(name, LOADER_APPLICATIONS_NAME) == 0) {
loader_do_applications_show(loader);
status = loader_make_success_status(error_message);
status.value = loader_make_success_status(error_message);
break;
}

Expand All @@ -480,7 +602,7 @@ static LoaderStatus loader_do_start_by_name(
furi_record_close(RECORD_STORAGE);
}

status = loader_make_status_error(
status.value = loader_make_status_error(
LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name);
} while(false);

Expand Down Expand Up @@ -555,13 +677,13 @@ int32_t loader_srv(void* p) {
if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) {
switch(message.type) {
case LoaderMessageTypeStartByName:
message.status_value->value = loader_do_start_by_name(
*(message.status_value) = loader_do_start_by_name(
loader, message.start.name, message.start.args, message.start.error_message);
api_lock_unlock(message.api_lock);
break;
case LoaderMessageTypeStartByNameDetachedWithGuiError: {
FuriString* error_message = furi_string_alloc();
LoaderStatus status = loader_do_start_by_name(
LoaderMessageLoaderStatusResult status = loader_do_start_by_name(
loader, message.start.name, message.start.args, error_message);
loader_show_gui_error(status, message.start.name, error_message);
if(message.start.name) free((void*)message.start.name);
Expand Down
12 changes: 12 additions & 0 deletions applications/services/loader/loader_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@ typedef struct {
FuriString* error_message;
} LoaderMessageStartByName;

typedef enum {
LoaderStatusErrorUnknown,
LoaderStatusErrorInvalidFile,
LoaderStatusErrorInvalidManifest,
LoaderStatusErrorMissingImports,
LoaderStatusErrorHWMismatch,
LoaderStatusErrorOutdatedApp,
LoaderStatusErrorOutOfMemory,
LoaderStatusErrorOutdatedFirmware,
} LoaderStatusError;

typedef struct {
LoaderStatus value;
LoaderStatusError error;
} LoaderMessageLoaderStatusResult;

typedef struct {
Expand Down
Binary file added assets/icons/Loader/err_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_05.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_06.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_07.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/Loader/err_09.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d8ef099

Please sign in to comment.