From 43ccaf056862f5a71ec163c6feb17bdd083b5ef4 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 4 Sep 2026 00:15:13 +0200 Subject: [PATCH] Remove exec path check and simplify API --- .../source/app_esp32_loader_service.cpp | 6 +- Modules/app-module/include/app/exec.h | 44 --- Modules/app-module/include/app/execute.h | 102 ++++++ Modules/app-module/include/app/manager.h | 127 +------ Modules/app-module/include/app/start.h | 80 +++++ Modules/app-module/include/app/stream.h | 2 +- .../private/app/private/exec_internal.h | 16 - .../app-module/private/app/private/ledger.h | 12 +- .../private/app/private/manager_internal.h | 25 ++ Modules/app-module/source/exec.cpp | 200 ----------- Modules/app-module/source/execute.cpp | 55 +++ Modules/app-module/source/manager.cpp | 72 +--- Modules/app-module/source/module.cpp | 29 +- Modules/app-module/source/scheduler.cpp | 4 +- Modules/app-module/source/start.cpp | 49 +++ Modules/app-module/tests/source/exec_test.cpp | 217 ------------ .../app-module/tests/source/execute_test.cpp | 323 ++++++++++++++++++ Modules/app-module/tests/source/io_test.cpp | 15 +- .../app-module/tests/source/manager_test.cpp | 246 ++----------- .../app-module/tests/source/stream_test.cpp | 3 +- .../source/app_posix_loader_service.cpp | 7 +- .../tests/source/loader_test.cpp | 46 +-- .../app/fileselection/FileSelection.h | 2 +- .../Tactility/app/wifimanage/WifiManage.h | 2 +- Tactility/Source/Tactility.cpp | 3 +- .../Source/app/alertdialog/AlertDialog.cpp | 5 +- .../Source/app/appdetails/AppDetails.cpp | 3 +- .../app/apphubdetails/AppHubDetailsApp.cpp | 3 +- Tactility/Source/app/applist/AppList.cpp | 3 +- Tactility/Source/app/boot/Boot.cpp | 5 +- Tactility/Source/app/btmanage/BtManage.cpp | 3 +- .../app/btpeersettings/BtPeerSettings.cpp | 3 +- .../app/crashdiagnostics/CrashDiagnostics.cpp | 3 +- .../Source/app/development/Development.cpp | 3 +- Tactility/Source/app/files/View.cpp | 18 +- .../app/fileselection/FileSelection.cpp | 3 +- .../Source/app/gpssettings/GpsSettings.cpp | 3 +- .../Source/app/i2cscanner/I2cScanner.cpp | 3 +- .../Source/app/imageviewer/ImageViewer.cpp | 3 +- .../Source/app/inputdialog/InputDialog.cpp | 3 +- Tactility/Source/app/launcher/Launcher.cpp | 9 +- Tactility/Source/app/notes/Notes.cpp | 3 +- .../app/selectiondialog/SelectionDialog.cpp | 5 +- Tactility/Source/app/settings/Settings.cpp | 3 +- Tactility/Source/app/setup/Setup.cpp | 3 +- .../app/timedatesettings/TimeDateSettings.cpp | 3 +- Tactility/Source/app/timezone/TimeZone.cpp | 3 +- .../app/touchcalibration/TouchCalibration.cpp | 3 +- .../app/wifiapsettings/WifiApSettings.cpp | 3 +- .../Source/app/wificonnect/WifiConnect.cpp | 3 +- .../Source/app/wifimanage/WifiManage.cpp | 3 +- .../development/DevelopmentService.cpp | 3 +- .../service/webserver/WebServerService.cpp | 4 +- 53 files changed, 812 insertions(+), 987 deletions(-) delete mode 100644 Modules/app-module/include/app/exec.h create mode 100644 Modules/app-module/include/app/execute.h create mode 100644 Modules/app-module/include/app/start.h delete mode 100644 Modules/app-module/private/app/private/exec_internal.h create mode 100644 Modules/app-module/private/app/private/manager_internal.h delete mode 100644 Modules/app-module/source/exec.cpp create mode 100644 Modules/app-module/source/execute.cpp create mode 100644 Modules/app-module/source/start.cpp delete mode 100644 Modules/app-module/tests/source/exec_test.cpp create mode 100644 Modules/app-module/tests/source/execute_test.cpp diff --git a/Modules/app-esp32-module/source/app_esp32_loader_service.cpp b/Modules/app-esp32-module/source/app_esp32_loader_service.cpp index 2c32a9af3..e8d4e7093 100644 --- a/Modules/app-esp32-module/source/app_esp32_loader_service.cpp +++ b/Modules/app-esp32-module/source/app_esp32_loader_service.cpp @@ -4,7 +4,6 @@ #endif #include -#include #include #include @@ -88,11 +87,10 @@ constexpr ElfRequirements EXECUTABLE_REQUIREMENTS = { }; // Validates an already-resolved binary path (see resolve_elf_path()) before it's handed to -// esp_elf_relocate(), which performs no header validation of its own: the extension and -// directory checks are cheap string comparisons, so the file is only opened as a last resort. +// esp_elf_relocate(), which performs no header validation of its own: the extension check is a +// cheap string comparison, so the file is only opened as a last resort. bool is_executable_file(const std::string& resolved_path) { return resolved_path.ends_with(".elf") - && app_exec_path_allowed(resolved_path.c_str()) && elf_check_file(resolved_path.c_str(), &EXECUTABLE_REQUIREMENTS); } diff --git a/Modules/app-module/include/app/exec.h b/Modules/app-module/include/app/exec.h deleted file mode 100644 index 8732ca56e..000000000 --- a/Modules/app-module/include/app/exec.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Largest number of directories app_exec_path_add() can hold at once. */ -#define APP_EXEC_MAX_PATHS 8 - -/** - * Registers @a path as a directory whose contents may be executed. Applies recursively to - * subdirectories. Registering the same path twice is a no-op. - * @retval ERROR_OUT_OF_MEMORY the registry already holds APP_EXEC_MAX_PATHS entries - * @retval ERROR_NONE on success (including when @a path was already registered) - */ -error_t app_exec_path_add(const char* path); - -/** - * Unregisters a directory previously passed to app_exec_path_add(). - * @retval ERROR_NOT_FOUND @a path was not registered - * @retval ERROR_NONE on success - */ -error_t app_exec_path_remove(const char* path); - -/** - * True when @a file_path lives under a directory registered with app_exec_path_add(), at any - * depth. Rejects any path containing a ".." segment rather than normalising it. - */ -bool app_exec_path_allowed(const char* file_path); - -/** - * Asks the registered APP_LOADER_PATH_SERVICE_ID loader (see app/loader.h) whether @a path is - * runnable on this target, without loading it. - * @return false if @a path can't be run, or if no path loader is registered - */ -bool app_exec_is_executable_path(const char* path); - -#ifdef __cplusplus -} -#endif diff --git a/Modules/app-module/include/app/execute.h b/Modules/app-module/include/app/execute.h new file mode 100644 index 000000000..f876e2442 --- /dev/null +++ b/Modules/app-module/include/app/execute.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +/** + * This file contains functions to start and run apps. + * It differs from start.h by running apps directly from the specified location, + * instead of having to register them first via an AppManifest and the app manager. + */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Starts an app directly from @a location, without it having to be pre-registered via + * app_manager_add() first. Independent of the id-based app_start*() family + * (app/manager.h): performs no checks of its own beyond what AppLoaderApi::load() itself + * rejects. Not manifest lookup, and not whether @a location should be allowed to run (see + * app_is_executable() below): callers that need a check must run it themselves before calling + * this. + * @param[in] stack stack allocation config for the app's task; all-zero uses the scheduler's + * default depth/capability, same as an AppManifest that leaves AppManifest::stack zeroed + * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type + * @retval ERROR_NONE on success + */ +error_t app_execute( + struct AppLocation location, + struct AppStackConfig stack, + int argc, + const char* const argv[], + AppInstanceId* out_app_instance_id +); + +/** + * Same as app_execute(), but as a modal child of @a parent_instance_id - see + * app_start_for_result()'s own doc for the result-delivery contract. Still no manifest + * and no check of its own beyond what AppLoaderApi::load() itself rejects. + * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type + * @retval ERROR_NONE on success + */ +error_t app_execute_for_result( + struct AppLocation location, + struct AppStackConfig stack, + int argc, + const char* const argv[], + AppInstanceId parent_instance_id, + AppInstanceId* out_app_instance_id +); + +/** + * Same as app_execute(), but installs @a bindings into the new instance's fd table before its + * task begins executing - see app_start_with_streams()'s own doc for stream ownership. + * @param[in] bindings see app_start_with_streams() + * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type + * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range + * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim + * @retval ERROR_NONE on success + */ +error_t app_execute_with_streams( + struct AppLocation location, + struct AppStackConfig stack, + int argc, + const char* const argv[], + const struct AppStreamBinding* bindings, + size_t binding_count, + AppInstanceId* out_app_instance_id +); + +/** + * Combines app_execute_for_result() and app_execute_with_streams(). + * @param[in] bindings see app_start_with_streams() + * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type + * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range + * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim + * @retval ERROR_NONE on success + */ +error_t app_execute_for_result_with_streams( + struct AppLocation location, + struct AppStackConfig stack, + int argc, + const char* const argv[], + const struct AppStreamBinding* binding, + size_t binding_count, + AppInstanceId parent_instance_id, + AppInstanceId* out_app_instance_id +); + +/** + * Reports whether @a location is runnable on this target - the extension and header a loader + * requires (e.g. an ELF's class/data/type/machine), not whether it lives anywhere in particular: + * any executable app is runnable from any path. Cheap enough to call while listing a directory. + * @return false if @a location can't be run, or if no AppLoaderApi is registered for its type + */ +bool app_is_executable(struct AppLocation location); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/app-module/include/app/manager.h b/Modules/app-module/include/app/manager.h index b87169445..3b92bb162 100644 --- a/Modules/app-module/include/app/manager.h +++ b/Modules/app-module/include/app/manager.h @@ -44,51 +44,8 @@ error_t app_manager_find_manifest(const char* id, struct AppManifest* out_manife typedef void (*AppManifestVisitorFn)(const struct AppManifest* manifest, void* context); void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context); -/** - * Starts a new instance of the app registered under @a id. Every app instance gets its own - * dedicated task for its entire lifetime - starting an app never asks any other app to give up - * its task, and multiple instances (of the same or different apps) can be Active at once. - * @param[in] id the manifest id to start - * @param[out] out_app_instance_id the id of the new app instance - * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered - * @retval ERROR_NONE on success - */ -error_t app_manager_start(const char* id, AppInstanceId* out_app_instance_id); - -/** - * Same as app_manager_start(), but also passes @a argc/@a argv to the new instance's own main - * function (see app/loader.h's AppMainFn) - modelled on a C program's main(argc, argv). For - * regular (non-modal) navigations that need to pass data to the target app (e.g. "show details - * for this app id") without expecting a result back. - * @param[in] argv @a argc strings; app-module makes its own deep copy before returning, so - * @a argv and the strings it points to may be freed/go out of scope immediately after this call - * returns (e.g. safe to pass a stack-local array of a caller's own std::string::c_str()s). - */ -error_t app_manager_start_with_parameters(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id); - -/** - * Starts @a id as a modal child of @a parent_instance_id, for the purpose of receiving a - * result. The parent keeps running (window_manager's own multi-window stack handles burying its - * window while the child is shown). - * - * When the child's task exits, an APP_EVENT_RESULT is delivered to @a parent_instance_id - - * result is whatever the child's AppMainFn/AppLoaderApi::run() returned - unless - * @a parent_instance_id is 0, in which case no result is delivered (fire-and-forget, for - * callers with no app_instance_id of their own). The parent is then responsible for calling - * app_manager_stop() on the child's instance id to fully reap it. Children that need to hand - * back more than an int32_t (e.g. picked text, a path) expose their own "get last result" - * getter for the parent to call after receiving the event - see e.g. - * tt::app::inputdialog::getLastText(). - * @param[in] argv @a argc strings; app-module makes its own deep copy before returning (same as - * app_manager_start_with_parameters()), so @a argv and the strings it points to may be - * freed/go out of scope immediately after this call returns. - * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered - * @retval ERROR_NONE on success - */ -error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id); - -/** One fd-to-stream binding for app_manager_start_with_streams(). Every field is passed through - * to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */ +/** One fd-to-stream binding for app_start_with_streams() (app/start.h). Every field is passed + * through to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */ struct AppStreamBinding { int producer_fd; struct AppStream* stream; @@ -97,84 +54,6 @@ struct AppStreamBinding { struct TaskEventGroup* event_group; }; -/** - * Same as app_manager_start(), but installs @a bindings into the new instance's fd table before - * its task begins executing (e.g. a child's stdio, piped through parent-owned AppStreams; see - * app/stream.h). Writes the new instance's id into each bound stream's producer_id itself, since - * the caller cannot know it in advance. - * @param[in] bindings @a binding_count entries; each stream and buffer must stay alive (see - * app_stream_subscribe()) until unsubscribed or the child exits. - * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered - * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range - * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim - * @retval ERROR_NONE on success - */ -error_t app_manager_start_with_streams(const char* id, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); - -/** - * Combines app_manager_start_for_result() and app_manager_start_with_streams(): starts @a id as - * a modal child of @a parent_instance_id (see app_manager_start_for_result()'s own doc for the - * result-delivery contract) with @a bindings installed into its fd table before its task begins - * executing (see app_manager_start_with_streams()'s own doc for stream ownership). For a child - * that needs to hand back more than an int32_t (e.g. a path) via its own stdout instead of the - * "get last result" getter pattern (see app_manager_start_for_result()) - see e.g. - * tt::app::fileselection::startForExistingFile(). - * @param[in] argv see app_manager_start_for_result(). - * @param[in] bindings see app_manager_start_with_streams(). - * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered - * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range - * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim - * @retval ERROR_NONE on success - */ -error_t app_manager_start_for_result_with_streams(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); - -/** - * Starts an app directly from @a location, without it having to be pre-registered via - * app_manager_add() first. Independent of the id-based app_manager_start*() family: performs no - * checks of its own beyond what AppLoaderApi::load() itself rejects. Not manifest lookup, and - * not whether @a location should be allowed to run (see app_exec_is_executable_path(), - * app/exec.h, for that): callers that need a check must run it themselves before calling this. - * @param[in] stack stack allocation config for the app's task; all-zero uses the scheduler's - * default depth/capability, same as an AppManifest that leaves AppManifest::stack zeroed - * @param[in] argv see app_manager_start_with_parameters() - * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type - * @retval ERROR_NONE on success - */ -error_t app_manager_start_location(struct AppLocation location, struct AppStackConfig stack, int argc, const char* const argv[], AppInstanceId* out_app_instance_id); - -/** - * Same as app_manager_start_location(), but as a modal child of @a parent_instance_id - see - * app_manager_start_for_result()'s own doc for the result-delivery contract. Still no manifest - * and no check of its own beyond what AppLoaderApi::load() itself rejects. - * @param[in] argv see app_manager_start_with_parameters() - * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type - * @retval ERROR_NONE on success - */ -error_t app_manager_start_location_for_result(struct AppLocation location, struct AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id); - -/** - * Same as app_manager_start_location(), but installs @a bindings into the new instance's fd - * table before its task begins executing - see app_manager_start_with_streams()'s own doc for - * stream ownership. - * @param[in] bindings see app_manager_start_with_streams() - * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type - * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range - * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim - * @retval ERROR_NONE on success - */ -error_t app_manager_start_location_with_streams(struct AppLocation location, struct AppStackConfig stack, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); - -/** - * Combines app_manager_start_location_for_result() and app_manager_start_location_with_streams(). - * @param[in] argv see app_manager_start_with_parameters() - * @param[in] bindings see app_manager_start_with_streams() - * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type - * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range - * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim - * @retval ERROR_NONE on success - */ -error_t app_manager_start_location_for_result_with_streams(struct AppLocation location, struct AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); - /** * Stop an app instance permanently. Emits APP_EVENT_CLOSE and bound-waits for its task to exit * if it was running. @@ -190,7 +69,7 @@ AppInstanceState app_manager_get_state(AppInstanceId app_instance_id); /** * @param[out] out_app_instance_id set to the instance id of the topmost currently-Active app - * the most recently started of whichever instances are Active (a modal child launched via - * app_manager_start_for_result() stays Active alongside its parent while shown, so this + * app_start_for_result() (app/start.h) stays Active alongside its parent while shown, so this * correctly picks the child, not the parent, while a dialog is up). * @retval ERROR_NOT_FOUND no app is Active * @retval ERROR_NONE on success diff --git a/Modules/app-module/include/app/start.h b/Modules/app-module/include/app/start.h new file mode 100644 index 000000000..3531c2665 --- /dev/null +++ b/Modules/app-module/include/app/start.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +/** + * This file contains functions to start and run apps that were registerd to the app manager. + * It differs from execute.h which runs executables from a specific path. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Same as app_start(), but also passes @a argc/@a argv to the new instance's own main + * function (see app/loader.h's AppMainFn) - modelled on a C program's main(argc, argv). For + * regular (non-modal) navigations that need to pass data to the target app (e.g. "show details + * for this app id") without expecting a result back. + * @param[in] argv @a argc strings; app-module makes its own deep copy before returning, so + * @a argv and the strings it points to may be freed/go out of scope immediately after this call + * returns (e.g. safe to pass a stack-local array of a caller's own std::string::c_str()s). + */ +error_t app_start(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id); + +/** + * Starts @a id as a modal child of @a parent_instance_id, for the purpose of receiving a + * result. The parent keeps running (window_manager's own multi-window stack handles burying its + * window while the child is shown). + * + * When the child's task exits, an APP_EVENT_RESULT is delivered to @a parent_instance_id - + * result is whatever the child's AppMainFn/AppLoaderApi::run() returned - unless + * @a parent_instance_id is 0, in which case no result is delivered (fire-and-forget, for + * callers with no app_instance_id of their own). The parent is then responsible for calling + * app_manager_stop() on the child's instance id to fully reap it. Children that need to hand + * back more than an int32_t (e.g. picked text, a path) expose their own "get last result" + * getter for the parent to call after receiving the event - see e.g. + * tt::app::inputdialog::getLastText(). + * @param[in] argv @a argc strings; app-module makes its own deep copy before returning (same as + * app_start()), so @a argv and the strings it points to may be + * freed/go out of scope immediately after this call returns. + * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered + * @retval ERROR_NONE on success + */ +error_t app_start_for_result(const char* id, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id); + +/** + * Same as app_start(), but installs @a bindings into the new instance's fd table before + * its task begins executing (e.g. a child's stdio, piped through parent-owned AppStreams; see + * app/stream.h). Writes the new instance's id into each bound stream's producer_id itself, since + * the caller cannot know it in advance. + * @param[in] bindings @a binding_count entries; each stream and buffer must stay alive (see + * app_stream_subscribe()) until unsubscribed or the child exits. + * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered + * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range + * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim + * @retval ERROR_NONE on success + */ +error_t app_start_with_streams(const char* id, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); + +/** + * Combines app_start_for_result() and app_start_with_streams(): starts @a id as + * a modal child of @a parent_instance_id (see app_start_for_result()'s own doc for the + * result-delivery contract) with @a bindings installed into its fd table before its task begins + * executing (see app_start_with_streams()'s own doc for stream ownership). For a child + * that needs to hand back more than an int32_t (e.g. a path) via its own stdout instead of the + * "get last result" getter pattern (see app_start_for_result()) - see e.g. + * tt::app::fileselection::startForExistingFile(). + * @param[in] argv see app_start_for_result(). + * @param[in] bindings see app_start_with_streams(). + * @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered + * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range + * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim + * @retval ERROR_NONE on success + */ +error_t app_start_for_result_with_streams(const char* id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/app-module/include/app/stream.h b/Modules/app-module/include/app/stream.h index bf5127f00..0295f9405 100644 --- a/Modules/app-module/include/app/stream.h +++ b/Modules/app-module/include/app/stream.h @@ -37,7 +37,7 @@ struct AppStream { AppInstanceId producer_id; TaskHandle_t producer_task; /** fd this stream is installed at in producer_id's fd table; set by - * app_stream_subscribe()/app_manager_start_with_streams(), used by + * app_stream_subscribe()/app_start_with_streams(), used by * app_stream_unsubscribe() to find it again. */ int producer_fd; diff --git a/Modules/app-module/private/app/private/exec_internal.h b/Modules/app-module/private/app/private/exec_internal.h deleted file mode 100644 index f4f879c26..000000000 --- a/Modules/app-module/private/app/private/exec_internal.h +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Registers app-module's default executable directories (data/bin, system/bin, the app install - * root) with app_exec_path_add(). Called once from app-module's own Module::start(). - */ -void app_exec_register_default_paths(void); - -#ifdef __cplusplus -} -#endif diff --git a/Modules/app-module/private/app/private/ledger.h b/Modules/app-module/private/app/private/ledger.h index 712924fcc..85e770027 100644 --- a/Modules/app-module/private/app/private/ledger.h +++ b/Modules/app-module/private/app/private/ledger.h @@ -38,22 +38,22 @@ struct AppCompletionSignal { /** A registered/running app instance, as tracked internally by app-module. */ struct AppInstanceRecord { uint32_t id; - /** NULL for an instance started via app_manager_start_location() (no manifest involved). */ + /** NULL for an instance started via app_execute() (app/execute.h; no manifest involved). */ const AppManifest* manifest; AppInstanceState state; /** The FreeRTOS task currently executing AppLoaderApi::run() for this instance; NULL when not running. */ TaskHandle_t task; - /** 0 for a top-level launch (app_manager_start()). Non-zero for a modal child launched via - * app_manager_start_for_result() - the instance that receives this child's APP_EVENT_RESULT. */ + /** 0 for a top-level launch (app_start()). Non-zero for a modal child launched via + * app_start_for_result() - the instance that receives this child's APP_EVENT_RESULT. */ uint32_t parent_id = 0; /** This instance's completion signal - see AppCompletionSignal. Set once by * app_scheduler_start(), never reassigned. */ AppCompletionSignal* completion = nullptr; - /** This instance's fd table. Constructed by start_internal() before insertion into - * AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */ + /** This instance's fd table. Constructed by app_manager_start_internal() before insertion + * into AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */ AppFdTable fd_table {}; }; @@ -73,7 +73,7 @@ inline AppLedger& app_ledger() { } /** - * Frees a deep-copied argv previously built by app_manager_start_with_parameters()/app_manager_start_for_result(): + * Frees a deep-copied argv previously built by app_start()/app_start_for_result(): * each individually heap-allocated string, then the array itself. Safe to call with count == 0 values == nullptr (no-op). */ inline void app_ledger_free_arguments(int count, char** values) { diff --git a/Modules/app-module/private/app/private/manager_internal.h b/Modules/app-module/private/app/private/manager_internal.h new file mode 100644 index 000000000..19dcb9f0a --- /dev/null +++ b/Modules/app-module/private/app/private/manager_internal.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Shared core behind every app_start*() (app/manager.h) and app_execute*() + * (app/execute.h) entry point: deep-copies @a argv, allocates an instance id, installs + * @a bindings into the new instance's fd table before app_scheduler_start() is called, then + * starts it. @a manifest may be NULL for a location-based start with no manifest at all. + * @retval ERROR_INVALID_ARGUMENT @a binding_count is nonzero but @a bindings is NULL + * @retval ERROR_NOT_FOUND no AppLoaderApi is registered for @a location.type + * @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range + * @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim + * @retval ERROR_NONE on success + */ +error_t app_manager_start_internal(const struct AppManifest* manifest, struct AppLocation location, struct AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/app-module/source/exec.cpp b/Modules/app-module/source/exec.cpp deleted file mode 100644 index 24f994437..000000000 --- a/Modules/app-module/source/exec.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -constexpr auto* TAG = "app_exec"; - -namespace { - -struct ExecPathRegistry { - std::string paths[APP_EXEC_MAX_PATHS]; - size_t count = 0; - Mutex mutex {}; - - ExecPathRegistry() { mutex_construct(&mutex); } -}; - -ExecPathRegistry& registry() { - static ExecPathRegistry instance; - return instance; -} - -// POSIX works with relative paths. The rest of the OS uses anchors relative paths. -// Resolving through getcwd() keeps every path callers pass in comparable, regardless of which convention they used. -// A path already starting with '/' passes through unchanged, so this is a no-op on ESP32. -std::string make_absolute(const std::string& path) { - if (path.empty() || path.front() == '/') { - return path; - } - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd)) == nullptr) { - return path; // best effort: falls back to today's (broken but not worse) behavior - } - return std::string(cwd) + "/" + path; -} - -// Resolves to absolute (see make_absolute()) and strips a trailing '/' so "/data/bin/" and -// "/data/bin" register/compare identically. -std::string normalize(const char* path) { - std::string result = make_absolute(path); - while (result.size() > 1 && result.back() == '/') { - result.pop_back(); - } - return result; -} - -bool has_dotdot_segment(const std::string& path) { - size_t start = 0; - while (start <= path.size()) { - size_t slash = path.find('/', start); - size_t length = (slash == std::string::npos ? path.size() : slash) - start; - if (path.compare(start, length, "..") == 0) { - return true; - } - if (slash == std::string::npos) { - break; - } - start = slash + 1; - } - return false; -} - -// Resolves @a absolute_path through every symlink on its way (POSIX realpath()), so a symlink -// sitting inside an allowed directory can't point at a target outside it. is_under_directory() -// only ever sees where a path physically ends up, never the lexical path a caller wrote. Falls -// back to @a absolute_path unresolved if realpath() fails (e.g. nothing exists there yet): a -// nonexistent path can't be dlopen()ed either way, so comparing it unresolved is still safe. -std::string resolve_physical(const std::string& absolute_path) { - char resolved[PATH_MAX]; - if (realpath(absolute_path.c_str(), resolved) == nullptr) { - return absolute_path; - } - return std::string(resolved); -} - -// True when @a file_path lives strictly under @a dir (a direct or nested child). Never equal to -// @a dir itself: a bare directory is never itself an executable candidate. -bool is_under_directory(const std::string& file_path, const std::string& dir) { - if (file_path.size() <= dir.size() || file_path.compare(0, dir.size(), dir) != 0) { - return false; - } - return file_path[dir.size()] == '/'; -} - -const AppLoaderApi* find_path_loader_api() { - ServiceInstance* instance = service_manager_find_instance(APP_LOADER_PATH_SERVICE_ID); - if (instance == nullptr) { - return nullptr; - } - return static_cast(service_instance_get_data(instance)); -} - -} // namespace - -extern "C" { - -error_t app_exec_path_add(const char* path) { - auto normalized = normalize(path); - - auto& reg = registry(); - mutex_lock(®.mutex); - - for (size_t i = 0; i < reg.count; i++) { - if (reg.paths[i] == normalized) { - mutex_unlock(®.mutex); - return ERROR_NONE; - } - } - - if (reg.count >= APP_EXEC_MAX_PATHS) { - mutex_unlock(®.mutex); - LOG_E(TAG, "Registry full, can't add %s", path); - return ERROR_OUT_OF_MEMORY; - } - - reg.paths[reg.count++] = normalized; - mutex_unlock(®.mutex); - return ERROR_NONE; -} - -error_t app_exec_path_remove(const char* path) { - auto normalized = normalize(path); - - auto& reg = registry(); - mutex_lock(®.mutex); - - for (size_t i = 0; i < reg.count; i++) { - if (reg.paths[i] == normalized) { - reg.paths[i] = reg.paths[reg.count - 1]; - reg.count--; - mutex_unlock(®.mutex); - return ERROR_NONE; - } - } - - mutex_unlock(®.mutex); - return ERROR_NOT_FOUND; -} - -bool app_exec_path_allowed(const char* file_path) { - std::string path = make_absolute(file_path); - if (has_dotdot_segment(path)) { - return false; - } - std::string resolved_path = resolve_physical(path); - - auto& reg = registry(); - mutex_lock(®.mutex); - bool allowed = false; - for (size_t i = 0; i < reg.count; i++) { - // Resolved at check time, not at app_exec_path_add() time: the registered directory - // itself may not exist yet when it's registered (e.g. before the first app_install()). - std::string resolved_dir = resolve_physical(reg.paths[i]); - if (is_under_directory(resolved_path, resolved_dir)) { - allowed = true; - break; - } - } - mutex_unlock(®.mutex); - return allowed; -} - -bool app_exec_is_executable_path(const char* path) { - const AppLoaderApi* loader = find_path_loader_api(); - if (loader == nullptr || loader->is_executable == nullptr) { - return false; - } - - AppLocation location { APP_LOCATION_PATH, const_cast(path) }; - return loader->is_executable(location); -} - -void app_exec_register_default_paths() { - char data_root[192]; - if (paths_get_data_path(data_root, sizeof(data_root)) == ERROR_NONE) { - app_exec_path_add((std::string(data_root) + "/bin").c_str()); - app_exec_path_add((std::string(data_root) + "/app").c_str()); - } else { - LOG_W(TAG, "Failed to resolve data path, not registering its executable directories"); - } -} - -} // extern "C" diff --git a/Modules/app-module/source/execute.cpp b/Modules/app-module/source/execute.cpp new file mode 100644 index 000000000..59a0a4ab2 --- /dev/null +++ b/Modules/app-module/source/execute.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include + +#include +#include + +namespace { + +// Same lookup as scheduler.cpp's own (private) find_loader_api() - duplicated rather than +// shared since it's a handful of lines and neither file depends on the other. +const char* loader_service_id_for(AppLocationType type) { + return (type == APP_LOCATION_MEMORY) ? APP_LOADER_MEMORY_SERVICE_ID : APP_LOADER_PATH_SERVICE_ID; +} + +const AppLoaderApi* find_loader_api(AppLocationType type) { + ServiceInstance* instance = service_manager_find_instance(loader_service_id_for(type)); + if (instance == nullptr) { + return nullptr; + } + return static_cast(service_instance_get_data(instance)); +} + +} // namespace + +extern "C" { + +error_t app_execute(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { + return app_manager_start_internal(nullptr, location, stack, 0, argc, argv, nullptr, 0, out_app_instance_id); +} + +error_t app_execute_for_result(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) { + return app_manager_start_internal(nullptr, location, stack, parent_instance_id, argc, argv, nullptr, 0, out_app_instance_id); +} + +error_t app_execute_with_streams(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { + return app_manager_start_internal(nullptr, location, stack, 0, argc, argv, bindings, binding_count, out_app_instance_id); +} + +error_t app_execute_for_result_with_streams(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) { + return app_manager_start_internal(nullptr, location, stack, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id); +} + +bool app_is_executable(AppLocation location) { + const AppLoaderApi* loader = find_loader_api(location.type); + if (loader == nullptr || loader->is_executable == nullptr) { + return false; + } + return loader->is_executable(location); +} + +} // extern "C" diff --git a/Modules/app-module/source/manager.cpp b/Modules/app-module/source/manager.cpp index ed1931f84..6b3d2c46e 100644 --- a/Modules/app-module/source/manager.cpp +++ b/Modules/app-module/source/manager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -88,14 +89,13 @@ char** copy_arguments(int argc, const char* const argv[]) { return copy; } -// Takes ownership of argv (already a deep copy, or NULL/argc==0) regardless of outcome - -// app_scheduler_start() frees it on any failure path, and the spawned task frees it once its -// run() returns. @a bindings (@a binding_count entries, may be NULL/0) are subscribed into the -// new instance's fd table before app_scheduler_start() is called, so they're in place before its -// task begins executing (see app_manager_start_with_streams()). @a manifest may be NULL, for a -// location-based start with no manifest at all (see app_manager_start_location()); it's stored -// on the ledger record as-is and never dereferenced here beyond the log line below. -error_t start_internal(const AppManifest* manifest, AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, char* argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { +} // namespace + +error_t app_manager_start_internal(const AppManifest* manifest, AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv_in[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { + // Owns its own deep copy from here on: app_scheduler_start() takes ownership of it regardless + // of outcome, and the spawned task frees it once its run() returns. + char** argv = copy_arguments(argc, argv_in); + if (binding_count != 0 && bindings == nullptr) { app_ledger_free_arguments(argc, argv); return ERROR_INVALID_ARGUMENT; @@ -154,62 +154,6 @@ error_t start_internal(const AppManifest* manifest, AppLocation location, AppSta return ERROR_NONE; } -// Looks @a id up in the manifest registry, then delegates to start_internal(). The only path -// that requires a registered manifest; app_manager_start_location() bypasses this entirely. -error_t start_internal_by_id(const char* id, AppInstanceId parent_instance_id, int argc, char* argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { - auto& ledger = app_ledger(); - - mutex_lock(&ledger.mutex); - auto manifest_iterator = ledger.manifests.find(id); - if (manifest_iterator == ledger.manifests.end()) { - mutex_unlock(&ledger.mutex); - app_ledger_free_arguments(argc, argv); - return ERROR_NOT_FOUND; - } - const AppManifest* manifest = manifest_iterator->second; - mutex_unlock(&ledger.mutex); - - return start_internal(manifest, manifest->location, manifest->stack, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id); -} - -} // namespace - -error_t app_manager_start(const char* id, AppInstanceId* out_app_instance_id) { - return start_internal_by_id(id, 0, 0, nullptr, nullptr, 0, out_app_instance_id); -} - -error_t app_manager_start_with_parameters(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { - return start_internal_by_id(id, 0, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id); -} - -error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { - return start_internal_by_id(id, parent_instance_id, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id); -} - -error_t app_manager_start_with_streams(const char* id, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { - return start_internal_by_id(id, 0, 0, nullptr, bindings, binding_count, out_app_instance_id); -} - -error_t app_manager_start_for_result_with_streams(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { - return start_internal_by_id(id, parent_instance_id, argc, copy_arguments(argc, argv), bindings, binding_count, out_app_instance_id); -} - -error_t app_manager_start_location(AppLocation location, AppStackConfig stack, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { - return start_internal(nullptr, location, stack, 0, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id); -} - -error_t app_manager_start_location_for_result(AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { - return start_internal(nullptr, location, stack, parent_instance_id, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id); -} - -error_t app_manager_start_location_with_streams(AppLocation location, AppStackConfig stack, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { - return start_internal(nullptr, location, stack, 0, 0, nullptr, bindings, binding_count, out_app_instance_id); -} - -error_t app_manager_start_location_for_result_with_streams(AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { - return start_internal(nullptr, location, stack, parent_instance_id, argc, copy_arguments(argc, argv), bindings, binding_count, out_app_instance_id); -} - error_t app_manager_stop(AppInstanceId app_instance_id) { return app_scheduler_stop(app_instance_id, pdMS_TO_TICKS(2000)); } diff --git a/Modules/app-module/source/module.cpp b/Modules/app-module/source/module.cpp index 8f010c38b..fcdf041a3 100644 --- a/Modules/app-module/source/module.cpp +++ b/Modules/app-module/source/module.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include #include #include #include @@ -8,13 +8,11 @@ #include #include #include +#include #include -#include - #include -#include #include #include @@ -28,6 +26,12 @@ static const ModuleSymbol SYMBOLS[] = { DEFINE_MODULE_SYMBOL(app_event_subscribe_with_app_id), DEFINE_MODULE_SYMBOL(app_event_unsubscribe), DEFINE_MODULE_SYMBOL(app_event_poll), + // app/execute + DEFINE_MODULE_SYMBOL(app_execute), + DEFINE_MODULE_SYMBOL(app_execute_for_result), + DEFINE_MODULE_SYMBOL(app_execute_with_streams), + DEFINE_MODULE_SYMBOL(app_execute_for_result_with_streams), + DEFINE_MODULE_SYMBOL(app_is_executable), // app/install DEFINE_MODULE_SYMBOL(app_get_install_path), DEFINE_MODULE_SYMBOL(app_install), @@ -37,11 +41,6 @@ static const ModuleSymbol SYMBOLS[] = { DEFINE_MODULE_SYMBOL(app_io_write), DEFINE_MODULE_SYMBOL(app_io_close), // app/manager - DEFINE_MODULE_SYMBOL(app_manager_start), - DEFINE_MODULE_SYMBOL(app_manager_start_with_parameters), - DEFINE_MODULE_SYMBOL(app_manager_start_for_result), - DEFINE_MODULE_SYMBOL(app_manager_start_with_streams), - DEFINE_MODULE_SYMBOL(app_manager_start_for_result_with_streams), DEFINE_MODULE_SYMBOL(app_manager_stop), DEFINE_MODULE_SYMBOL(app_manager_get_state), DEFINE_MODULE_SYMBOL(app_manager_find_manifest), @@ -53,6 +52,11 @@ static const ModuleSymbol SYMBOLS[] = { DEFINE_MODULE_SYMBOL(app_manager_install_path_add), DEFINE_MODULE_SYMBOL(app_manager_install_path_scan), DEFINE_MODULE_SYMBOL(app_manager_install_path_uninstall), + // app/start + DEFINE_MODULE_SYMBOL(app_start), + DEFINE_MODULE_SYMBOL(app_start_for_result), + DEFINE_MODULE_SYMBOL(app_start_with_streams), + DEFINE_MODULE_SYMBOL(app_start_for_result_with_streams), // app/manifest DEFINE_MODULE_SYMBOL(app_id_is_valid), // app/metadata @@ -76,12 +80,7 @@ static const ModuleSymbol SYMBOLS[] = { }; static error_t start() { - error_t error = service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true); - if (error != ERROR_NONE) { - return error; - } - app_exec_register_default_paths(); - return ERROR_NONE; + return service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true); } static error_t stop() { diff --git a/Modules/app-module/source/scheduler.cpp b/Modules/app-module/source/scheduler.cpp index 6bbc3fc7c..5b7707951 100644 --- a/Modules/app-module/source/scheduler.cpp +++ b/Modules/app-module/source/scheduler.cpp @@ -101,7 +101,7 @@ void set_task(AppInstanceId app_instance_id, TaskHandle_t task) { auto iterator = ledger.instances.find(app_instance_id); if (iterator != ledger.instances.end()) { iterator->second.task = task; - // Streams bound before this instance's task existed (app_manager_start_with_streams()) + // Streams bound before this instance's task existed (app_start_with_streams()) // only got producer_task filled in as NULL at subscribe time. Backfill it now. AppFdTable& fd_table = iterator->second.fd_table; for (auto& slot : fd_table.slots) { @@ -168,7 +168,7 @@ const AppLoaderApi* find_loader_api(AppLocationType type) { return static_cast(service_instance_get_data(instance)); } -// If this instance was launched via app_manager_start_for_result(), delivers @a result (its +// If this instance was launched via app_start_for_result(), delivers @a result (its // own AppMainFn/AppLoaderApi::run() return value) to its parent. No-op for a top-level instance // (parent_id == 0). void deliver_result_to_parent_if_any(AppInstanceId app_instance_id, int32_t result) { diff --git a/Modules/app-module/source/start.cpp b/Modules/app-module/source/start.cpp new file mode 100644 index 000000000..f9da33028 --- /dev/null +++ b/Modules/app-module/source/start.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include + +#include + +namespace { + +// Looks @a id up in the manifest registry, then delegates to app_manager_start_internal(). The +// only path that requires a registered manifest; app_execute*() (app/execute.h) bypasses this +// entirely. +error_t start_internal_by_id(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { + auto& ledger = app_ledger(); + + mutex_lock(&ledger.mutex); + auto manifest_iterator = ledger.manifests.find(id); + if (manifest_iterator == ledger.manifests.end()) { + mutex_unlock(&ledger.mutex); + return ERROR_NOT_FOUND; + } + const AppManifest* manifest = manifest_iterator->second; + mutex_unlock(&ledger.mutex); + + return app_manager_start_internal(manifest, manifest->location, manifest->stack, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id); +} + +} // namespace + +extern "C" { + +error_t app_start(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) { + return start_internal_by_id(id, 0, argc, argv, nullptr, 0, out_app_instance_id); +} + +error_t app_start_for_result(const char* id, int argc, const char* const argv[], AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) { + return start_internal_by_id(id, parent_instance_id, argc, argv, nullptr, 0, out_app_instance_id); +} + +error_t app_start_with_streams(const char* id, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) { + return start_internal_by_id(id, 0, 0, nullptr, bindings, binding_count, out_app_instance_id); +} + +error_t app_start_for_result_with_streams(const char* id, int argc, const char* const argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId parent_instance_id, AppInstanceId* out_app_instance_id) { + return start_internal_by_id(id, parent_instance_id, argc, argv, bindings, binding_count, out_app_instance_id); +} + +} // extern "C" diff --git a/Modules/app-module/tests/source/exec_test.cpp b/Modules/app-module/tests/source/exec_test.cpp deleted file mode 100644 index 618ffdfc7..000000000 --- a/Modules/app-module/tests/source/exec_test.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "doctest.h" - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -extern ServiceManifest app_internal_loader_service_manifest; - -namespace { - -const AppLoaderApi* memory_loader_api() { - if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) { - service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true); - } - auto* instance = service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID); - REQUIRE(instance != nullptr); - return static_cast(service_instance_get_data(instance)); -} - -int32_t fake_entry(int, char*[]) { - return 0; -} - -// Creates /{bin,outside} plus a real file at outside/target.so, for the symlink-escape -// tests below. Caller removes everything via remove_symlink_fixture() once done. -struct SymlinkFixture { - std::string root; - std::string bin_dir; - std::string outside_dir; - std::string outside_target; - - bool create() { - char temp_template[] = "/tmp/exec-test-symlink-XXXXXX"; - char* temp_dir = mkdtemp(temp_template); - if (temp_dir == nullptr) { - return false; - } - root = temp_dir; - bin_dir = root + "/bin"; - outside_dir = root + "/outside"; - outside_target = outside_dir + "/target.so"; - - if (mkdir(bin_dir.c_str(), 0755) != 0 || mkdir(outside_dir.c_str(), 0755) != 0) { - return false; - } - FILE* file = fopen(outside_target.c_str(), "w"); - if (file == nullptr) { - return false; - } - fclose(file); - return true; - } -}; - -void remove_symlink_fixture(const SymlinkFixture& fixture) { - unlink((fixture.bin_dir + "/escape.so").c_str()); - unlink((fixture.bin_dir + "/inside.so").c_str()); - unlink((fixture.bin_dir + "/real.so").c_str()); - unlink(fixture.outside_target.c_str()); - rmdir(fixture.bin_dir.c_str()); - rmdir(fixture.outside_dir.c_str()); - rmdir(fixture.root.c_str()); -} - -} // namespace - -TEST_CASE("app_exec_path_add/app_exec_path_allowed accept nested children of a registered directory") { - REQUIRE_EQ(app_exec_path_add("/exec-test/bin"), ERROR_NONE); - - CHECK(app_exec_path_allowed("/exec-test/bin/foo")); - CHECK(app_exec_path_allowed("/exec-test/bin/sub/foo")); - CHECK_FALSE(app_exec_path_allowed("/exec-test/binaries/foo")); - CHECK_FALSE(app_exec_path_allowed("/exec-test/bin")); - - app_exec_path_remove("/exec-test/bin"); -} - -TEST_CASE("app_exec_path_allowed rejects a '..' segment even under a registered directory") { - REQUIRE_EQ(app_exec_path_add("/exec-test/bin2"), ERROR_NONE); - - CHECK_FALSE(app_exec_path_allowed("/exec-test/bin2/../etc/passwd")); - - app_exec_path_remove("/exec-test/bin2"); -} - -TEST_CASE("app_exec_path_add is a no-op for a path already registered") { - REQUIRE_EQ(app_exec_path_add("/exec-test/dup"), ERROR_NONE); - REQUIRE_EQ(app_exec_path_add("/exec-test/dup"), ERROR_NONE); - - CHECK(app_exec_path_allowed("/exec-test/dup/foo")); - - app_exec_path_remove("/exec-test/dup"); -} - -TEST_CASE("app_exec_path_add does not register same item twice") { - REQUIRE_EQ(app_exec_path_add("/exec-test/dup"), ERROR_NONE); - REQUIRE_EQ(app_exec_path_add("/exec-test/dup"), ERROR_NONE); - app_exec_path_remove("/exec-test/dup"); - CHECK(!app_exec_path_allowed("/exec-test/dup/foo")); -} - -TEST_CASE("app_exec_path_remove reports ERROR_NOT_FOUND for a path that was never registered") { - CHECK_EQ(app_exec_path_remove("/exec-test/never-registered"), ERROR_NOT_FOUND); -} - -TEST_CASE("app_exec_path_remove makes a previously allowed path disallowed again") { - REQUIRE_EQ(app_exec_path_add("/exec-test/removable"), ERROR_NONE); - REQUIRE(app_exec_path_allowed("/exec-test/removable/foo")); - - REQUIRE_EQ(app_exec_path_remove("/exec-test/removable"), ERROR_NONE); - CHECK_FALSE(app_exec_path_allowed("/exec-test/removable/foo")); -} - -TEST_CASE("app_exec_path_add resolves a relative path to absolute, so it matches an absolute incoming path") { - char cwd[PATH_MAX]; - REQUIRE(getcwd(cwd, sizeof(cwd)) != nullptr); - - REQUIRE_EQ(app_exec_path_add("exec-test-relative/bin"), ERROR_NONE); - - std::string absolute_child = std::string(cwd) + "/exec-test-relative/bin/foo"; - CHECK(app_exec_path_allowed(absolute_child.c_str())); - - app_exec_path_remove("exec-test-relative/bin"); - CHECK_FALSE(app_exec_path_allowed(absolute_child.c_str())); -} - -TEST_CASE("app_exec_path_allowed resolves a relative incoming path to absolute, so it matches an absolute registered directory") { - char cwd[PATH_MAX]; - REQUIRE(getcwd(cwd, sizeof(cwd)) != nullptr); - - std::string absolute_dir = std::string(cwd) + "/exec-test-relative2/bin"; - REQUIRE_EQ(app_exec_path_add(absolute_dir.c_str()), ERROR_NONE); - - CHECK(app_exec_path_allowed("exec-test-relative2/bin/foo")); - - app_exec_path_remove(absolute_dir.c_str()); -} - -TEST_CASE("app_exec_path_allowed rejects a symlink inside a registered directory that resolves outside it") { - SymlinkFixture fixture; - REQUIRE(fixture.create()); - - std::string escape_link = fixture.bin_dir + "/escape.so"; - REQUIRE_EQ(symlink(fixture.outside_target.c_str(), escape_link.c_str()), 0); - - REQUIRE_EQ(app_exec_path_add(fixture.bin_dir.c_str()), ERROR_NONE); - - // Lexically "escape.so" is inside bin_dir, but it physically resolves to outside_dir. Must - // be rejected, or the posix loader would dlopen() a binary the caller never allowed. - CHECK_FALSE(app_exec_path_allowed(escape_link.c_str())); - - app_exec_path_remove(fixture.bin_dir.c_str()); - remove_symlink_fixture(fixture); -} - -TEST_CASE("app_exec_path_allowed accepts a real file, and a symlink that stays inside, a registered directory") { - SymlinkFixture fixture; - REQUIRE(fixture.create()); - - std::string real_file = fixture.bin_dir + "/real.so"; - FILE* file = fopen(real_file.c_str(), "w"); - REQUIRE(file != nullptr); - fclose(file); - - std::string inside_link = fixture.bin_dir + "/inside.so"; - REQUIRE_EQ(symlink(real_file.c_str(), inside_link.c_str()), 0); - - REQUIRE_EQ(app_exec_path_add(fixture.bin_dir.c_str()), ERROR_NONE); - - CHECK(app_exec_path_allowed(real_file.c_str())); - CHECK(app_exec_path_allowed(inside_link.c_str())); - - app_exec_path_remove(fixture.bin_dir.c_str()); - remove_symlink_fixture(fixture); -} - -TEST_CASE("app_exec_path_add returns ERROR_OUT_OF_MEMORY once the registry is full") { - char path[32]; - int added = 0; - for (added = 0; added < APP_EXEC_MAX_PATHS + 1; added++) { - snprintf(path, sizeof(path), "/exec-test/overflow%d", added); - if (app_exec_path_add(path) != ERROR_NONE) { - break; - } - } - CHECK_LT(added, APP_EXEC_MAX_PATHS + 1); - - for (int i = 0; i < added; i++) { - snprintf(path, sizeof(path), "/exec-test/overflow%d", i); - app_exec_path_remove(path); - } -} - -TEST_CASE("memory loader's is_executable() reports a non-null AppLocation as executable, a null one as not") { - const AppLoaderApi* loader = memory_loader_api(); - REQUIRE(loader->is_executable != nullptr); - - AppLocation runnable { APP_LOCATION_MEMORY, reinterpret_cast(fake_entry) }; - CHECK(loader->is_executable(runnable)); - - AppLocation empty { APP_LOCATION_MEMORY, nullptr }; - CHECK_FALSE(loader->is_executable(empty)); - - AppLocation wrong_type { APP_LOCATION_PATH, reinterpret_cast(fake_entry) }; - CHECK_FALSE(loader->is_executable(wrong_type)); -} diff --git a/Modules/app-module/tests/source/execute_test.cpp b/Modules/app-module/tests/source/execute_test.cpp new file mode 100644 index 000000000..11a4734a3 --- /dev/null +++ b/Modules/app-module/tests/source/execute_test.cpp @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "doctest.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +extern ServiceManifest app_internal_loader_service_manifest; + +namespace { + +// See manager_test.cpp's own copy of this helper for why this checks the registry directly +// rather than a per-translation-unit static bool. +void ensure_memory_loader_registered() { + if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) { + service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true); + } +} + +bool wait_for_state(uint32_t instance_id, AppInstanceState target, uint32_t timeout_ms) { + uint32_t waited = 0; + while (waited < timeout_ms) { + if (app_manager_get_state(instance_id) == target) { + return true; + } + delay_millis(10); + waited += 10; + } + return app_manager_get_state(instance_id) == target; +} + +AppInstanceId topmost_instance_id() { + AppInstanceId id = 0; + return app_manager_get_topmost_instance_id(&id) == ERROR_NONE ? id : 0; +} + +// Every APP_LOCATION_PATH-based test below would need its own fake path loader, registered +// under the same singleton APP_LOADER_PATH_SERVICE_ID that manager_test.cpp's own fake loader +// already claims - two independent-but-competing registrations in the same test binary is a real +// race (whichever file's static registrar runs first wins process-wide; the other file then runs +// silently against a loader it didn't write). Using APP_LOCATION_MEMORY + the real, already-safe +// app_internal_loader_service_manifest (registered via the find-instance-check idiom above, safe +// under this exact contention already used elsewhere in this test suite) sidesteps the problem +// entirely: no second competing registration exists. + +// Subscribes until APP_EVENT_CLOSE like a real app instance; if launched with a single argv +// entry, returns it parsed as int immediately instead (the app_execute_for_result() shortcut, +// mirroring a modal dialog's result). +int32_t location_app_main(int argc, char* argv[]) { + if (argc == 1) { + return static_cast(strtol(argv[0], nullptr, 10)); + } + + TaskEventGroup event_group {}; + task_event_group_construct(&event_group); + + AppEventSubscription sub {}; + app_event_subscribe(&sub, &event_group); + + while (true) { + if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) { + break; // safety net so a bug here can't hang the test suite + } + bool done = false; + AppEvent event {}; + while (app_event_poll(&sub, &event) == ERROR_NONE) { + if (event.type == APP_EVENT_CLOSE) { + done = true; + break; + } + } + if (done) { + break; + } + } + + app_event_unsubscribe(&sub); + task_event_group_destruct(&event_group); + return 0; +} + +// Writes a fixed string to its own stdout then returns 7 as its result, for the +// app_execute_with_streams()/_for_result_with_streams() tests: proves a binding installed +// before the task starts actually reaches the app's own app_io_write() calls. +int32_t stream_writer_app_main(int, char*[]) { + const char message[] = "loc"; + size_t sent = 0; + while (sent < sizeof(message) - 1) { + ssize_t written = app_io_write(STDOUT_FILENO, message + sent, sizeof(message) - 1 - sent); + if (written < 0) { + break; + } + sent += static_cast(written); + } + return 7; +} + +// Writes argv[0] to its own stdout, for the app_execute_with_streams() argv-delivery test. +int32_t argv_echo_app_main(int argc, char* argv[]) { + if (argc < 1) { + return -1; + } + const char* message = argv[0]; + size_t length = strlen(message); + size_t sent = 0; + while (sent < length) { + ssize_t written = app_io_write(STDOUT_FILENO, message + sent, length - sent); + if (written < 0) { + break; + } + sent += static_cast(written); + } + return 0; +} + +} // namespace + +TEST_CASE("app_execute runs a location with no manifest at all, and reports no topmost app id for it") { + ensure_memory_loader_registered(); + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) }; + uint32_t instance_id = 0; + REQUIRE_EQ(app_execute(location, AppStackConfig {}, 0, nullptr, &instance_id), ERROR_NONE); + CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); + + CHECK_EQ(topmost_instance_id(), instance_id); + char buffer[64]; + // No manifest to report an id from - same NOT_FOUND a caller already sees for "nothing active". + CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NOT_FOUND); + + CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); + CHECK_EQ(app_manager_get_state(instance_id), APP_INSTANCE_STATE_STOPPED); +} + +TEST_CASE("app_execute doesn't disturb app_manager_get_topmost_app_id for a normal manifest-backed app started afterward") { + ensure_memory_loader_registered(); + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) }; + uint32_t unregistered_id = 0; + REQUIRE_EQ(app_execute(location, AppStackConfig {}, 0, nullptr, &unregistered_id), ERROR_NONE); + CHECK(wait_for_state(unregistered_id, APP_INSTANCE_STATE_ACTIVE, 1000)); + + AppManifest manifest { "test.app.execute.after", "After", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) } }; + REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); + uint32_t registered_id = 0; + REQUIRE_EQ(app_start("test.app.execute.after", 0, nullptr, ®istered_id), ERROR_NONE); + CHECK(wait_for_state(registered_id, APP_INSTANCE_STATE_ACTIVE, 1000)); + + char buffer[64]; + CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NONE); + CHECK_EQ(std::string(buffer), "test.app.execute.after"); + + app_manager_stop(unregistered_id); + app_manager_stop(registered_id); + app_manager_remove("test.app.execute.after"); +} + +TEST_CASE("app_execute_for_result delivers APP_EVENT_RESULT to the parent, with no manifest for the child either") { + ensure_memory_loader_registered(); + + AppManifest parent_manifest { "test.app.execute.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) } }; + REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE); + + uint32_t parent_id = 0; + REQUIRE_EQ(app_start("test.app.execute.parent", 0, nullptr, &parent_id), ERROR_NONE); + CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); + + TaskEventGroup parent_event_group {}; + task_event_group_construct(&parent_event_group); + + AppEventSubscription parent_sub {}; + REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE); + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) }; + const char* argv[] = { "42" }; // location_app_main's single-arg shortcut - returns 42 immediately + uint32_t child_id = 0; + REQUIRE_EQ(app_execute_for_result(location, AppStackConfig {}, 1, argv, parent_id, &child_id), ERROR_NONE); + + REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE); + AppEvent event {}; + REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE); + CHECK_EQ(event.type, APP_EVENT_RESULT); + CHECK_EQ(event.result.launch_id, child_id); + CHECK_EQ(event.result.result, 42); + + app_event_unsubscribe(&parent_sub); + task_event_group_destruct(&parent_event_group); + app_manager_stop(child_id); + app_manager_stop(parent_id); + app_manager_remove("test.app.execute.parent"); +} + +TEST_CASE("app_execute_with_streams pipes a manifest-less child's app_io_write() calls into a parent-owned AppStream") { + ensure_memory_loader_registered(); + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(stream_writer_app_main) }; + + TaskEventGroup event_group {}; + task_event_group_construct(&event_group); + + uint8_t storage[64]; + AppStream child_stdout {}; + AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; + + AppInstanceId child_id = 0; + REQUIRE_EQ(app_execute_with_streams(location, AppStackConfig {}, 0, nullptr, &binding, 1, &child_id), ERROR_NONE); + + std::vector received; + while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { + uint8_t chunk[16]; + size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk)); + if (n == 0) { + break; // EOF + } + received.insert(received.end(), chunk, chunk + n); + } + + REQUIRE_EQ(received.size(), 3u); + CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0); + + REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000)); + app_stream_unsubscribe(&child_stdout); + task_event_group_destruct(&event_group); +} + +TEST_CASE("app_execute_with_streams passes argv through to the started app") { + ensure_memory_loader_registered(); + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(argv_echo_app_main) }; + + TaskEventGroup event_group {}; + task_event_group_construct(&event_group); + + uint8_t storage[64]; + AppStream child_stdout {}; + AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; + + const char* argv[] = { "hello" }; + AppInstanceId child_id = 0; + REQUIRE_EQ(app_execute_with_streams(location, AppStackConfig {}, 1, argv, &binding, 1, &child_id), ERROR_NONE); + + std::vector received; + while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { + uint8_t chunk[16]; + size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk)); + if (n == 0) { + break; // EOF + } + received.insert(received.end(), chunk, chunk + n); + } + + REQUIRE_EQ(received.size(), 5u); + CHECK_EQ(std::memcmp(received.data(), "hello", 5), 0); + + REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000)); + app_stream_unsubscribe(&child_stdout); + task_event_group_destruct(&event_group); +} + +TEST_CASE("app_execute_for_result_with_streams delivers both the stream data and the APP_EVENT_RESULT") { + ensure_memory_loader_registered(); + + AppManifest parent_manifest { "test.app.execute.parent_streams", "Parent", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast(location_app_main) } }; + REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE); + + uint32_t parent_id = 0; + REQUIRE_EQ(app_start("test.app.execute.parent_streams", 0, nullptr, &parent_id), ERROR_NONE); + CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); + + TaskEventGroup parent_event_group {}; + task_event_group_construct(&parent_event_group); + AppEventSubscription parent_sub {}; + REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE); + + uint8_t storage[64]; + AppStream child_stdout {}; + AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &parent_event_group }; + + AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(stream_writer_app_main) }; + uint32_t child_id = 0; + REQUIRE_EQ(app_execute_for_result_with_streams(location, AppStackConfig {}, 0, nullptr, &binding, 1, parent_id, &child_id), ERROR_NONE); + + std::vector received; + while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { + uint8_t chunk[16]; + size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk)); + if (n == 0) { + break; // EOF + } + received.insert(received.end(), chunk, chunk + n); + } + REQUIRE_EQ(received.size(), 3u); + CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0); + + REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE); + AppEvent event {}; + REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE); + CHECK_EQ(event.type, APP_EVENT_RESULT); + CHECK_EQ(event.result.launch_id, child_id); + CHECK_EQ(event.result.result, 7); + + app_stream_unsubscribe(&child_stdout); + app_event_unsubscribe(&parent_sub); + task_event_group_destruct(&parent_event_group); + app_manager_stop(child_id); + app_manager_stop(parent_id); + app_manager_remove("test.app.execute.parent_streams"); +} diff --git a/Modules/app-module/tests/source/io_test.cpp b/Modules/app-module/tests/source/io_test.cpp index aa7b37de2..400bd8d43 100644 --- a/Modules/app-module/tests/source/io_test.cpp +++ b/Modules/app-module/tests/source/io_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -135,7 +136,7 @@ TEST_CASE("an app's stdio fds default to the null device: write succeeds and dis REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); AppInstanceId instance_id = 0; - REQUIRE_EQ(app_manager_start("test.io.unbound", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.io.unbound", 0, nullptr, &instance_id), ERROR_NONE); REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000)); CHECK_EQ(g_stdio_write_result.load(std::memory_order_acquire), 1); @@ -144,7 +145,7 @@ TEST_CASE("an app's stdio fds default to the null device: write succeeds and dis app_manager_remove("test.io.unbound"); } -TEST_CASE("app_manager_start_with_streams pipes a child's app_io_write() calls into a parent-owned AppStream, EOF at exit") { +TEST_CASE("app_start_with_streams pipes a child's app_io_write() calls into a parent-owned AppStream, EOF at exit") { ensure_memory_loader_registered(); AppManifest manifest { "test.io.writer", "Writer", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast(stdout_writer_app_main) } }; @@ -158,7 +159,7 @@ TEST_CASE("app_manager_start_with_streams pipes a child's app_io_write() calls i AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; AppInstanceId child_id = 0; - REQUIRE_EQ(app_manager_start_with_streams("test.io.writer", &binding, 1, &child_id), ERROR_NONE); + REQUIRE_EQ(app_start_with_streams("test.io.writer", &binding, 1, &child_id), ERROR_NONE); std::vector received; while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { @@ -195,7 +196,7 @@ TEST_CASE("a write blocked on a full stream wakes with an error once the consume AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; AppInstanceId child_id = 0; - REQUIRE_EQ(app_manager_start_with_streams("test.io.blocked", &binding, 1, &child_id), ERROR_NONE); + REQUIRE_EQ(app_start_with_streams("test.io.blocked", &binding, 1, &child_id), ERROR_NONE); // Never drained: the child fills the 4-byte buffer and blocks awaiting space for the rest. delay_millis(200); @@ -231,7 +232,7 @@ TEST_CASE("app_stream_unsubscribe is safe to call while a write is actively bloc AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; AppInstanceId child_id = 0; - REQUIRE_EQ(app_manager_start_with_streams("test.io.unsub_race", &binding, 1, &child_id), ERROR_NONE); + REQUIRE_EQ(app_start_with_streams("test.io.unsub_race", &binding, 1, &child_id), ERROR_NONE); // Give the child time to fill the 4-byte buffer and block inside app_io_write(), already // dispatched through app_fd_table_get_and_retain() and currently waiting in @@ -262,7 +263,7 @@ TEST_CASE("app_io_read/write/close pass through a real file fd app-module never REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); AppInstanceId instance_id = 0; - REQUIRE_EQ(app_manager_start("test.io.real_file", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.io.real_file", 0, nullptr, &instance_id), ERROR_NONE); REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000)); CHECK_EQ(g_real_file_write_result.load(std::memory_order_acquire), 2); @@ -283,7 +284,7 @@ TEST_CASE("closing an already-closed app fd reports EBADF instead of falling thr REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); AppInstanceId instance_id = 0; - REQUIRE_EQ(app_manager_start("test.io.double_close", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.io.double_close", 0, nullptr, &instance_id), ERROR_NONE); REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000)); CHECK_EQ(g_double_close_first_result.load(std::memory_order_acquire), 0); diff --git a/Modules/app-module/tests/source/manager_test.cpp b/Modules/app-module/tests/source/manager_test.cpp index 5dfd8a8dd..28f92fd30 100644 --- a/Modules/app-module/tests/source/manager_test.cpp +++ b/Modules/app-module/tests/source/manager_test.cpp @@ -1,11 +1,10 @@ #include "doctest.h" #include -#include #include #include +#include #include -#include #include @@ -55,7 +54,7 @@ void stash_received_arguments(int argc, char* argv[]) { // A minimal stand-in for a real app's main(): subscribes to its own app_event stream and exits // as soon as it's asked to close - exactly the contract every app instance (with its own // dedicated task for its whole lifetime) is expected to follow. If launched with a single -// parameter (app_manager_start_for_result()), acts as a modal dialog instead: returns the +// parameter (app_start_for_result()), acts as a modal dialog instead: returns the // requested result (argv[0], parsed as an int) immediately (the app's own return value IS the // delivered APP_EVENT_RESULT.result - see app_scheduler.cpp's thread_main()). int32_t fake_run(void*, uint32_t /*app_instance_id*/, int argc, char* argv[]) { @@ -120,11 +119,13 @@ ServiceManifest fake_loader_manifest = { .on_stop = nullptr, }; +// Checks the registry directly rather than a per-translation-unit static bool, matching +// ensure_memory_loader_registered() below - this is the only file that registers a fake path +// loader, but a second one would silently win the race otherwise (see execute_test.cpp, which +// deliberately avoids needing one at all for exactly this reason). void ensure_fake_loader_registered() { - static bool registered = false; - if (!registered) { - CHECK_EQ(service_manager_add(&fake_loader_manifest, /*auto_start=*/true), ERROR_NONE); - registered = true; + if (service_manager_find_instance(APP_LOADER_PATH_SERVICE_ID) == nullptr) { + service_manager_add(&fake_loader_manifest, /*auto_start=*/true); } } @@ -175,32 +176,16 @@ bool wait_for_arguments_stashed(uint32_t timeout_ms) { return arguments_stashed.load(std::memory_order_acquire); } -// Writes a fixed string to its own stdout then returns 7 as its result, for the -// app_manager_start_location_with_streams()/_for_result_with_streams() tests: proves a binding -// installed before the task starts actually reaches the app's own app_io_write() calls. -int32_t stream_writer_app_main(int, char*[]) { - const char message[] = "loc"; - size_t sent = 0; - while (sent < sizeof(message) - 1) { - ssize_t written = app_io_write(STDOUT_FILENO, message + sent, sizeof(message) - 1 - sent); - if (written < 0) { - break; - } - sent += static_cast(written); - } - return 7; -} - } // namespace -TEST_CASE("app_manager_start activates an app instance, app_manager_stop terminates it") { +TEST_CASE("app_start activates an app instance, app_manager_stop terminates it") { ensure_fake_loader_registered(); AppManifest manifest { "test.app.a", "Test App A", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t instance_id = 0; - REQUIRE_EQ(app_manager_start("test.app.a", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.a", 0, nullptr, &instance_id), ERROR_NONE); CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); @@ -209,7 +194,7 @@ TEST_CASE("app_manager_start activates an app instance, app_manager_stop termina app_manager_remove("test.app.a"); } -TEST_CASE("app_manager_start never touches another already-running app - every instance gets its own task") { +TEST_CASE("app_start never touches another already-running app - every instance gets its own task") { ensure_fake_loader_registered(); AppManifest manifest_b { "test.app.b", "Test App B", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; @@ -218,11 +203,11 @@ TEST_CASE("app_manager_start never touches another already-running app - every i REQUIRE_EQ(app_manager_add(&manifest_c), ERROR_NONE); uint32_t id_b = 0; - REQUIRE_EQ(app_manager_start("test.app.b", &id_b), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.b", 0, nullptr, &id_b), ERROR_NONE); CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000)); uint32_t id_c = 0; - REQUIRE_EQ(app_manager_start("test.app.c", &id_c), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.c", 0, nullptr, &id_c), ERROR_NONE); CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000)); // b is untouched by c starting - both stay Active at once, each with its own task. @@ -234,18 +219,18 @@ TEST_CASE("app_manager_start never touches another already-running app - every i app_manager_remove("test.app.c"); } -TEST_CASE("app_manager_start always creates a fresh instance, even for the same manifest id twice") { +TEST_CASE("app_start always creates a fresh instance, even for the same manifest id twice") { ensure_fake_loader_registered(); AppManifest manifest { "test.app.twice", "Test App Twice", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t id_first = 0; - REQUIRE_EQ(app_manager_start("test.app.twice", &id_first), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.twice", 0, nullptr, &id_first), ERROR_NONE); CHECK(wait_for_state(id_first, APP_INSTANCE_STATE_ACTIVE, 1000)); uint32_t id_second = 0; - REQUIRE_EQ(app_manager_start("test.app.twice", &id_second), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.twice", 0, nullptr, &id_second), ERROR_NONE); CHECK(wait_for_state(id_second, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_NE(id_first, id_second); @@ -260,7 +245,7 @@ TEST_CASE("app_manager_get_state returns STOPPED for an unknown instance id") { CHECK_EQ(app_manager_get_state(999999), APP_INSTANCE_STATE_STOPPED); } -TEST_CASE("app_manager_start_with_parameters deep-copies argv before the app instance receives it") { +TEST_CASE("app_start_with_parameters deep-copies argv before the app instance receives it") { ensure_fake_loader_registered(); AppManifest manifest { "test.app.args", "Test App Args", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; @@ -274,7 +259,7 @@ TEST_CASE("app_manager_start_with_parameters deep-copies argv before the app ins std::string ssid = "MyNetwork"; std::string password = "hunter2"; const char* argv[] = { ssid.c_str(), password.c_str() }; - REQUIRE_EQ(app_manager_start_with_parameters("test.app.args", 2, argv, &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.args", 2, argv, &instance_id), ERROR_NONE); } CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); REQUIRE(wait_for_arguments_stashed(1000)); @@ -319,12 +304,12 @@ TEST_CASE("app_manager_for_each_manifest visits every registered manifest, inclu CHECK(std::ranges::find(seen_ids, "test.app.foreach.x") == seen_ids.end()); } -TEST_CASE("app_manager_start fails for an unregistered manifest id") { +TEST_CASE("app_start fails for an unregistered manifest id") { uint32_t instance_id = 0; - CHECK_EQ(app_manager_start("test.app.nonexistent", &instance_id), ERROR_NOT_FOUND); + CHECK_EQ(app_start("test.app.nonexistent", 0, nullptr, &instance_id), ERROR_NOT_FOUND); } -TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function pointer, through the real internal loader") { +TEST_CASE("app_start runs an APP_LOCATION_MEMORY app via its function pointer, through the real internal loader") { ensure_memory_loader_registered(); AppManifest manifest { @@ -336,7 +321,7 @@ TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function po REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t instance_id = 0; - REQUIRE_EQ(app_manager_start("test.app.memory", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.memory", 0, nullptr, &instance_id), ERROR_NONE); CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); @@ -345,7 +330,7 @@ TEST_CASE("app_manager_start runs an APP_LOCATION_MEMORY app via its function po app_manager_remove("test.app.memory"); } -TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent, which stays Active throughout") { +TEST_CASE("app_start_for_result delivers APP_EVENT_RESULT to the parent, which stays Active throughout") { ensure_fake_loader_registered(); AppManifest parent_manifest { "test.app.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; @@ -354,7 +339,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent, REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE); uint32_t parent_id = 0; - REQUIRE_EQ(app_manager_start("test.app.parent", &parent_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.parent", 0, nullptr, &parent_id), ERROR_NONE); CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); TaskEventGroup parent_event_group {}; @@ -365,7 +350,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent, const char* argv[] = { "42" }; uint32_t child_id = 0; - REQUIRE_EQ(app_manager_start_for_result("test.app.child", parent_id, 1, argv, &child_id), ERROR_NONE); + REQUIRE_EQ(app_start_for_result("test.app.child", 1, argv, parent_id, &child_id), ERROR_NONE); // Launching a modal child never touches the parent's own task/state. CHECK_EQ(app_manager_get_state(parent_id), APP_INSTANCE_STATE_ACTIVE); @@ -385,7 +370,7 @@ TEST_CASE("app_manager_start_for_result delivers APP_EVENT_RESULT to the parent, app_manager_remove("test.app.child"); } -TEST_CASE("app_manager_start_for_result delivers the child's own return value as the result") { +TEST_CASE("app_start_for_result delivers the child's own return value as the result") { ensure_fake_loader_registered(); AppManifest parent_manifest { "test.app.parent2", "Parent2", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; @@ -394,7 +379,7 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as REQUIRE_EQ(app_manager_add(&child_manifest), ERROR_NONE); uint32_t parent_id = 0; - REQUIRE_EQ(app_manager_start("test.app.parent2", &parent_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.parent2", 0, nullptr, &parent_id), ERROR_NONE); CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); TaskEventGroup parent_event_group {}; @@ -406,7 +391,7 @@ TEST_CASE("app_manager_start_for_result delivers the child's own return value as uint32_t child_id = 0; // No parameters - fake_run falls through to its normal CLOSE loop instead of acting as a // dialog. - REQUIRE_EQ(app_manager_start_for_result("test.app.child2", parent_id, 0, nullptr, &child_id), ERROR_NONE); + REQUIRE_EQ(app_start_for_result("test.app.child2", 0, nullptr, parent_id, &child_id), ERROR_NONE); CHECK(wait_for_state(child_id, APP_INSTANCE_STATE_ACTIVE, 1000)); app_manager_stop(child_id); // force-close @@ -436,14 +421,14 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is REQUIRE_EQ(app_manager_add(&manifest_b), ERROR_NONE); uint32_t id_a = 0; - REQUIRE_EQ(app_manager_start("test.app.top_a", &id_a), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.top_a", 0, nullptr, &id_a), ERROR_NONE); CHECK(wait_for_state(id_a, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(topmost_instance_id(), id_a); // a stays Active - b just has a higher (more recently allocated) instance id, so it becomes // topmost without a superseding/saving. uint32_t id_b = 0; - REQUIRE_EQ(app_manager_start("test.app.top_b", &id_b), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.top_b", 0, nullptr, &id_b), ERROR_NONE); CHECK(wait_for_state(id_b, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(topmost_instance_id(), id_b); @@ -456,7 +441,7 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is // its persistent CLOSE loop branch instead of instantly resolving like a real dialog would - // needed here so there's a reliable window to observe it as topmost. uint32_t id_c = 0; - REQUIRE_EQ(app_manager_start_for_result("test.app.top_a", id_b, 0, nullptr, &id_c), ERROR_NONE); + REQUIRE_EQ(app_start_for_result("test.app.top_a", 0, nullptr, id_b, &id_c), ERROR_NONE); CHECK(wait_for_state(id_c, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(topmost_instance_id(), id_c); @@ -469,7 +454,7 @@ TEST_CASE("app_manager_get_topmost_instance_id returns NOT_FOUND when nothing is app_manager_remove("test.app.top_b"); } -TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") { +TEST_CASE("app_start honors a custom AppManifest::stack.depth") { ensure_fake_loader_registered(); AppManifest manifest { "test.app.stack.custom", "Stack Custom", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; @@ -477,7 +462,7 @@ TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") { REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t instance_id = 0; - REQUIRE_EQ(app_manager_start("test.app.stack.custom", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.stack.custom", 0, nullptr, &instance_id), ERROR_NONE); CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); @@ -486,7 +471,7 @@ TEST_CASE("app_manager_start honors a custom AppManifest::stack.depth") { app_manager_remove("test.app.stack.custom"); } -TEST_CASE("app_manager_start still works when AppManifest::stack is left at its zero-value default") { +TEST_CASE("app_start still works when AppManifest::stack is left at its zero-value default") { ensure_fake_loader_registered(); // stack.depth == 0 - app_scheduler_start() must fall back to its own default stack depth @@ -496,7 +481,7 @@ TEST_CASE("app_manager_start still works when AppManifest::stack is left at its REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t instance_id = 0; - REQUIRE_EQ(app_manager_start("test.app.stack.default", &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.stack.default", 0, nullptr, &instance_id), ERROR_NONE); CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); @@ -516,7 +501,7 @@ TEST_CASE("app_manager_get_topmost_app_id returns BUFFER_OVERFLOW for a too-smal REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); uint32_t id = 0; - REQUIRE_EQ(app_manager_start("test.app.top_overflow", &id), ERROR_NONE); + REQUIRE_EQ(app_start("test.app.top_overflow", 0, nullptr, &id), ERROR_NONE); CHECK(wait_for_state(id, APP_INSTANCE_STATE_ACTIVE, 1000)); // "test.app.top_overflow" doesn't fit in a 4-byte buffer. @@ -525,162 +510,3 @@ TEST_CASE("app_manager_get_topmost_app_id returns BUFFER_OVERFLOW for a too-smal app_manager_stop(id); app_manager_remove("test.app.top_overflow"); } - -TEST_CASE("app_manager_start_location runs a location with no manifest at all, and reports no topmost app id for it") { - ensure_fake_loader_registered(); - - AppLocation location { APP_LOCATION_PATH, nullptr }; - uint32_t instance_id = 0; - REQUIRE_EQ(app_manager_start_location(location, AppStackConfig {}, 0, nullptr, &instance_id), ERROR_NONE); - CHECK(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); - - CHECK_EQ(topmost_instance_id(), instance_id); - char buffer[64]; - // No manifest to report an id from - same NOT_FOUND a caller already sees for "nothing active". - CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NOT_FOUND); - - CHECK_EQ(app_manager_stop(instance_id), ERROR_NONE); - CHECK_EQ(app_manager_get_state(instance_id), APP_INSTANCE_STATE_STOPPED); -} - -TEST_CASE("app_manager_start_location doesn't disturb app_manager_get_topmost_app_id for a normal manifest-backed app started afterward") { - ensure_fake_loader_registered(); - - AppLocation location { APP_LOCATION_PATH, nullptr }; - uint32_t unregistered_id = 0; - REQUIRE_EQ(app_manager_start_location(location, AppStackConfig {}, 0, nullptr, &unregistered_id), ERROR_NONE); - CHECK(wait_for_state(unregistered_id, APP_INSTANCE_STATE_ACTIVE, 1000)); - - AppManifest manifest { "test.app.location.after", "After", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; - REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); - uint32_t registered_id = 0; - REQUIRE_EQ(app_manager_start("test.app.location.after", ®istered_id), ERROR_NONE); - CHECK(wait_for_state(registered_id, APP_INSTANCE_STATE_ACTIVE, 1000)); - - char buffer[64]; - CHECK_EQ(app_manager_get_topmost_app_id(buffer, sizeof(buffer)), ERROR_NONE); - CHECK_EQ(std::string(buffer), "test.app.location.after"); - - app_manager_stop(unregistered_id); - app_manager_stop(registered_id); - app_manager_remove("test.app.location.after"); -} - -TEST_CASE("app_manager_start_location_for_result delivers APP_EVENT_RESULT to the parent, with no manifest for the child either") { - ensure_fake_loader_registered(); - - AppManifest parent_manifest { "test.app.location.parent", "Parent", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; - REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE); - - uint32_t parent_id = 0; - REQUIRE_EQ(app_manager_start("test.app.location.parent", &parent_id), ERROR_NONE); - CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); - - TaskEventGroup parent_event_group {}; - task_event_group_construct(&parent_event_group); - - AppEventSubscription parent_sub {}; - REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE); - - AppLocation location { APP_LOCATION_PATH, nullptr }; - const char* argv[] = { "42" }; // fake_run's single-arg shortcut - returns 42 immediately - uint32_t child_id = 0; - REQUIRE_EQ(app_manager_start_location_for_result(location, AppStackConfig {}, parent_id, 1, argv, &child_id), ERROR_NONE); - - REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE); - AppEvent event {}; - REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE); - CHECK_EQ(event.type, APP_EVENT_RESULT); - CHECK_EQ(event.result.launch_id, child_id); - CHECK_EQ(event.result.result, 42); - - app_event_unsubscribe(&parent_sub); - task_event_group_destruct(&parent_event_group); - app_manager_stop(child_id); - app_manager_stop(parent_id); - app_manager_remove("test.app.location.parent"); -} - -TEST_CASE("app_manager_start_location_with_streams pipes a manifest-less child's app_io_write() calls into a parent-owned AppStream") { - ensure_memory_loader_registered(); - - AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(stream_writer_app_main) }; - - TaskEventGroup event_group {}; - task_event_group_construct(&event_group); - - uint8_t storage[64]; - AppStream child_stdout {}; - AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group }; - - AppInstanceId child_id = 0; - REQUIRE_EQ(app_manager_start_location_with_streams(location, AppStackConfig {}, &binding, 1, &child_id), ERROR_NONE); - - std::vector received; - while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { - uint8_t chunk[16]; - size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk)); - if (n == 0) { - break; // EOF - } - received.insert(received.end(), chunk, chunk + n); - } - - REQUIRE_EQ(received.size(), 3u); - CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0); - - REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000)); - app_stream_unsubscribe(&child_stdout); - task_event_group_destruct(&event_group); -} - -TEST_CASE("app_manager_start_location_for_result_with_streams delivers both the stream data and the APP_EVENT_RESULT") { - ensure_fake_loader_registered(); - ensure_memory_loader_registered(); - - AppManifest parent_manifest { "test.app.location.parent_streams", "Parent", APP_CATEGORY_USER, { APP_LOCATION_PATH, nullptr } }; - REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE); - - uint32_t parent_id = 0; - REQUIRE_EQ(app_manager_start("test.app.location.parent_streams", &parent_id), ERROR_NONE); - CHECK(wait_for_state(parent_id, APP_INSTANCE_STATE_ACTIVE, 1000)); - - TaskEventGroup parent_event_group {}; - task_event_group_construct(&parent_event_group); - AppEventSubscription parent_sub {}; - REQUIRE_EQ(app_event_subscribe_with_app_id(&parent_sub, &parent_event_group, parent_id), ERROR_NONE); - - uint8_t storage[64]; - AppStream child_stdout {}; - AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &parent_event_group }; - - AppLocation location { APP_LOCATION_MEMORY, reinterpret_cast(stream_writer_app_main) }; - uint32_t child_id = 0; - REQUIRE_EQ(app_manager_start_location_for_result_with_streams(location, AppStackConfig {}, parent_id, 0, nullptr, &binding, 1, &child_id), ERROR_NONE); - - std::vector received; - while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) { - uint8_t chunk[16]; - size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk)); - if (n == 0) { - break; // EOF - } - received.insert(received.end(), chunk, chunk + n); - } - REQUIRE_EQ(received.size(), 3u); - CHECK_EQ(std::memcmp(received.data(), "loc", 3), 0); - - REQUIRE_EQ(task_event_group_wait(&parent_event_group, parent_sub.bit, false, nullptr, pdMS_TO_TICKS(2000)), ERROR_NONE); - AppEvent event {}; - REQUIRE_EQ(app_event_poll(&parent_sub, &event), ERROR_NONE); - CHECK_EQ(event.type, APP_EVENT_RESULT); - CHECK_EQ(event.result.launch_id, child_id); - CHECK_EQ(event.result.result, 7); - - app_stream_unsubscribe(&child_stdout); - app_event_unsubscribe(&parent_sub); - task_event_group_destruct(&parent_event_group); - app_manager_stop(child_id); - app_manager_stop(parent_id); - app_manager_remove("test.app.location.parent_streams"); -} diff --git a/Modules/app-module/tests/source/stream_test.cpp b/Modules/app-module/tests/source/stream_test.cpp index f11dc1ed6..9d2cd4e0d 100644 --- a/Modules/app-module/tests/source/stream_test.cpp +++ b/Modules/app-module/tests/source/stream_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -75,7 +76,7 @@ AppInstanceId start_idle_app(const char* id) { AppManifest manifest { id, id, APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast(idle_app_main) } }; REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE); AppInstanceId instance_id = 0; - REQUIRE_EQ(app_manager_start(id, &instance_id), ERROR_NONE); + REQUIRE_EQ(app_start(id, 0, nullptr, &instance_id), ERROR_NONE); REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000)); return instance_id; } diff --git a/Modules/app-posix-module/source/app_posix_loader_service.cpp b/Modules/app-posix-module/source/app_posix_loader_service.cpp index 404eccc18..0d66cd78d 100644 --- a/Modules/app-posix-module/source/app_posix_loader_service.cpp +++ b/Modules/app-posix-module/source/app_posix_loader_service.cpp @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include #include #include @@ -45,10 +44,10 @@ constexpr ElfRequirements EXECUTABLE_REQUIREMENTS = { #endif // Validates an already-resolved binary path (see resolve_app_path()) before it's handed to -// dlopen(): the extension and directory checks are cheap string comparisons, so the file is only -// opened as a last resort. +// dlopen(): the extension check is a cheap string comparison, so the file is only opened as a +// last resort. bool is_executable_file(const std::string& resolved_path) { - if (!resolved_path.ends_with(".so") || !app_exec_path_allowed(resolved_path.c_str())) { + if (!resolved_path.ends_with(".so")) { return false; } #ifdef __APPLE__ diff --git a/Modules/app-posix-module/tests/source/loader_test.cpp b/Modules/app-posix-module/tests/source/loader_test.cpp index d377c7387..9ab8953a9 100644 --- a/Modules/app-posix-module/tests/source/loader_test.cpp +++ b/Modules/app-posix-module/tests/source/loader_test.cpp @@ -2,9 +2,10 @@ #include "doctest.h" #include -#include +#include #include #include +#include #include #include @@ -50,6 +51,11 @@ bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ return app_manager_get_state(id) == target; } +bool is_executable_path(const char* path) { + AppLocation location { APP_LOCATION_PATH, const_cast(path) }; + return app_is_executable(location); +} + std::atomic g_fixture_result { -1 }; std::atomic g_fixture_result_received { false }; @@ -71,7 +77,7 @@ int32_t parent_app_main(int, char*[]) { app_manager_add(&fixture_manifest); AppInstanceId fixture_id = 0; - app_manager_start_for_result("test.posix.fixture", self_id, 0, nullptr, &fixture_id); + app_start_for_result("test.posix.fixture", 0, nullptr, self_id, &fixture_id); while (true) { if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) { @@ -102,7 +108,6 @@ int32_t parent_app_main(int, char*[]) { TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its main(), which resolves a real Tactility symbol against the host") { ensure_path_loader_registered(); ensure_memory_loader_registered(); - REQUIRE_EQ(app_exec_path_add(FIXTURE_DIR.c_str()), ERROR_NONE); g_fixture_result.store(-1, std::memory_order_relaxed); g_fixture_result_received.store(false, std::memory_order_relaxed); @@ -110,7 +115,7 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its REQUIRE_EQ(app_manager_add(&parent_manifest), ERROR_NONE); AppInstanceId parent_id = 0; - REQUIRE_EQ(app_manager_start("test.posix.parent", &parent_id), ERROR_NONE); + REQUIRE_EQ(app_start("test.posix.parent", 0, nullptr, &parent_id), ERROR_NONE); REQUIRE(wait_for_state(parent_id, APP_INSTANCE_STATE_STOPPED, 3000)); CHECK(g_fixture_result_received.load(std::memory_order_acquire)); @@ -122,44 +127,27 @@ TEST_CASE("app-posix-module's loader-path service dlopen()s a .so and calls its app_manager_remove("test.posix.parent"); } -TEST_CASE("app_exec_is_executable_path() accepts a real .so in a registered directory") { +TEST_CASE("app_is_executable() accepts a real .so") { ensure_path_loader_registered(); - REQUIRE_EQ(app_exec_path_add(FIXTURE_DIR.c_str()), ERROR_NONE); - CHECK(app_exec_is_executable_path(FIXTURE_APP_PATH)); + CHECK(is_executable_path(FIXTURE_APP_PATH)); } -TEST_CASE("app_exec_is_executable_path() rejects the same .so when its directory isn't registered") { +TEST_CASE("app_is_executable() rejects a .so-named file with no ELF header") { ensure_path_loader_registered(); - app_exec_path_remove(FIXTURE_DIR.c_str()); - CHECK_FALSE(app_exec_is_executable_path(FIXTURE_APP_PATH)); - - // Restore for any later test case that relies on it being registered. - app_exec_path_add(FIXTURE_DIR.c_str()); + CHECK_FALSE(is_executable_path(FIXTURE_NON_ELF_PATH)); } -TEST_CASE("app_exec_is_executable_path() rejects a .so-named file with no ELF header") { +TEST_CASE("app_is_executable() rejects a nonexistent path") { ensure_path_loader_registered(); - auto non_elf_dir = directory_of(FIXTURE_NON_ELF_PATH); - REQUIRE_EQ(app_exec_path_add(non_elf_dir.c_str()), ERROR_NONE); - CHECK_FALSE(app_exec_is_executable_path(FIXTURE_NON_ELF_PATH)); - - app_exec_path_remove(non_elf_dir.c_str()); + CHECK_FALSE(is_executable_path((FIXTURE_DIR + "/does-not-exist.so").c_str())); } -TEST_CASE("app_exec_is_executable_path() rejects a nonexistent path in a registered directory") { +TEST_CASE("app_is_executable() rejects an install-directory-shaped path missing its per-arch .so") { ensure_path_loader_registered(); - REQUIRE_EQ(app_exec_path_add(FIXTURE_DIR.c_str()), ERROR_NONE); - - CHECK_FALSE(app_exec_is_executable_path((FIXTURE_DIR + "/does-not-exist.so").c_str())); -} - -TEST_CASE("app_exec_is_executable_path() rejects an install-directory-shaped path missing its per-arch .so") { - ensure_path_loader_registered(); - REQUIRE_EQ(app_exec_path_add(FIXTURE_DIR.c_str()), ERROR_NONE); // FIXTURE_DIR itself has no elf/posix-.so under it, so resolution fails. - CHECK_FALSE(app_exec_is_executable_path(FIXTURE_DIR.c_str())); + CHECK_FALSE(is_executable_path(FIXTURE_DIR.c_str())); } diff --git a/Tactility/Include/Tactility/app/fileselection/FileSelection.h b/Tactility/Include/Tactility/app/fileselection/FileSelection.h index 2111b3604..ae962d235 100644 --- a/Tactility/Include/Tactility/app/fileselection/FileSelection.h +++ b/Tactility/Include/Tactility/app/fileselection/FileSelection.h @@ -10,7 +10,7 @@ namespace tt::app::fileselection { /** * Show a file selection dialog that allows the user to select an existing file, as a modal - * child of @a callerAppInstanceId (see app_manager_start_for_result_with_streams()). Result + * child of @a callerAppInstanceId (see app_start_for_result_with_streams()). Result * (0 = Ok, 1 = Cancelled) is delivered back via APP_EVENT_RESULT once this app's thread exits. * On result == 0, read the picked path with app_stream_read(&stream, ...) then * app_stream_unsubscribe(&stream); on any other result, just app_stream_unsubscribe(&stream). diff --git a/Tactility/Include/Tactility/app/wifimanage/WifiManage.h b/Tactility/Include/Tactility/app/wifimanage/WifiManage.h index 2c187bb1d..bbdafa586 100644 --- a/Tactility/Include/Tactility/app/wifimanage/WifiManage.h +++ b/Tactility/Include/Tactility/app/wifimanage/WifiManage.h @@ -5,7 +5,7 @@ namespace tt::app::wifimanage { /** - * Starts as a modal child of @a callerAppInstanceId (see app_manager_start_for_result()) - an + * Starts as a modal child of @a callerAppInstanceId (see app_start_for_result()) - an * APP_EVENT_RESULT is delivered back once the user closes this screen (default Cancelled/no * bundle if never explicitly set - callers that just want a "the wifi step is done" signal, like * Setup, can ignore the actual result value). diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index d2e30b1f8..cd9f7dec1 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -571,7 +572,7 @@ void run(Module* const dtsModules[], const DtsDevice dtsDevices[]) { // It's a new-model (app-module + window-manager) app now, replacing the old app::start(). app_manager_add(&app::boot::manifest); uint32_t boot_instance_id = 0; - app_manager_start(app::boot::manifest.id, &boot_instance_id); + app_start(app::boot::manifest.id, 0, nullptr, &boot_instance_id); LOG_I(TAG, "Main dispatcher ready"); while (true) { diff --git a/Tactility/Source/app/alertdialog/AlertDialog.cpp b/Tactility/Source/app/alertdialog/AlertDialog.cpp index 0ff79276e..4d7b9aa09 100644 --- a/Tactility/Source/app/alertdialog/AlertDialog.cpp +++ b/Tactility/Source/app/alertdialog/AlertDialog.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -132,7 +133,7 @@ int32_t appMain(int argc, char* argv[]) { namespace { -// Builds argv = [title, message, buttonLabels...] for app_manager_start_for_result(). +// Builds argv = [title, message, buttonLabels...] for app_start_for_result(). std::vector buildArgv(const std::string& title, const std::string& message, const std::vector& buttonLabels) { std::vector argv { title.c_str(), message.c_str() }; for (const auto& label: buttonLabels) { @@ -146,7 +147,7 @@ std::vector buildArgv(const std::string& title, const std::string& uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::vector& buttonLabels) { auto argv = buildArgv(title, message, buttonLabels); uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast(argv.size()), argv.data(), &instanceId); + app_start_for_result(manifest.id, static_cast(argv.size()), argv.data(), callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/appdetails/AppDetails.cpp b/Tactility/Source/app/appdetails/AppDetails.cpp index d716511b6..4afd45d21 100644 --- a/Tactility/Source/app/appdetails/AppDetails.cpp +++ b/Tactility/Source/app/appdetails/AppDetails.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -158,7 +159,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& appId) { const char* argv[] = { appId.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId); + app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/apphubdetails/AppHubDetailsApp.cpp b/Tactility/Source/app/apphubdetails/AppHubDetailsApp.cpp index bf21cb6c8..4d879d7de 100644 --- a/Tactility/Source/app/apphubdetails/AppHubDetailsApp.cpp +++ b/Tactility/Source/app/apphubdetails/AppHubDetailsApp.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -351,7 +352,7 @@ void start(const apphub::AppHubEntry& entry) { argv.push_back(platform.c_str()); } uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, /*parent_instance_id=*/0, static_cast(argv.size()), argv.data(), &instanceId); + app_start_for_result(manifest.id, static_cast(argv.size()), argv.data(), /*parent_instance_id=*/0, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/applist/AppList.cpp b/Tactility/Source/app/applist/AppList.cpp index c7b0effd8..6ef4e6367 100644 --- a/Tactility/Source/app/applist/AppList.cpp +++ b/Tactility/Source/app/applist/AppList.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -28,7 +29,7 @@ void onAppPressed(lv_event_t* e) { // Fire-and-forget top-level navigation, same as Launcher's own app-launch buttons. const auto* manifest = static_cast(lv_event_get_user_data(e)); uint32_t instanceId = 0; - app_manager_start(manifest->id, &instanceId); + app_start(manifest->id, 0, nullptr, &instanceId); } void onBackPressed(lv_event_t* event) { diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index aa1b24f33..983302dd0 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -251,7 +252,7 @@ void startNextApp() { auto launcher_app_id = getLauncherAppId(); uint32_t launcher_instance_id = 0; - app_manager_start(launcher_app_id.c_str(), &launcher_instance_id); + app_start(launcher_app_id.c_str(), 0, nullptr, &launcher_instance_id); } void runBootSequence(TickType_t startTime) { @@ -318,7 +319,7 @@ int32_t appMain(int argc, char* argv[]) { runBootSequence(start_time); - // Waits until app_manager_start(launcher) (or a permanent stop) tells us to give up - + // Waits until app_start(launcher) (or a permanent stop) tells us to give up - // startNextApp() above is what triggers that, via app-module's "save the previously active // app" policy, unless sdCardMissing halted before it. while (true) { diff --git a/Tactility/Source/app/btmanage/BtManage.cpp b/Tactility/Source/app/btmanage/BtManage.cpp index 112aac051..8b1a327a4 100644 --- a/Tactility/Source/app/btmanage/BtManage.cpp +++ b/Tactility/Source/app/btmanage/BtManage.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -236,7 +237,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start() { uint32_t instanceId = 0; - app_manager_start(manifest.id, &instanceId); + app_start(manifest.id, 0, nullptr, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/btpeersettings/BtPeerSettings.cpp b/Tactility/Source/app/btpeersettings/BtPeerSettings.cpp index d67fbec2a..c339332cd 100644 --- a/Tactility/Source/app/btpeersettings/BtPeerSettings.cpp +++ b/Tactility/Source/app/btpeersettings/BtPeerSettings.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -262,7 +263,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& addrHex) { const char* argv[] = { addrHex.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId); + app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp index e20ec93ab..fdcd7ab59 100644 --- a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp +++ b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -272,7 +273,7 @@ int32_t appMain(int argc, char* argv[]) { void start() { uint32_t instanceId = 0; - app_manager_start(manifest.id, &instanceId); + app_start(manifest.id, 0, nullptr, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/development/Development.cpp b/Tactility/Source/app/development/Development.cpp index b4fae6797..f27c24871 100644 --- a/Tactility/Source/app/development/Development.cpp +++ b/Tactility/Source/app/development/Development.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -184,7 +185,7 @@ int32_t appMain(int argc, char* argv[]) { if (lvgl_is_running()) { lvgl_lock(); // Widgets only exist while this window is topmost - skip otherwise. Another app - // (started non-modally, e.g. via app_manager_start()) can bury this window without + // (started non-modally, e.g. via app_start()) can bury this window without // stopping this instance or notifying it; window_manager deletes a buried window's // widgets, so touching ctx->statusLabel here would use-after-free it. if (window_manager_get_state(window) == WINDOW_STATE_GRANTED) { diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index 1c23e78cf..fbf6d8871 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -1,7 +1,6 @@ #include -#include +#include #include -#include #include #include @@ -113,6 +112,11 @@ static void onRunPressedCallback(lv_event_t* event) { // region File helpers +static bool isExecutablePath(const std::string& path) { + AppLocation location { APP_LOCATION_PATH, const_cast(path.c_str()) }; + return app_is_executable(location); +} + static bool copyFileContents(const std::string& src, const std::string& dst) { FILE* in = fopen(src.c_str(), "rb"); if (in == nullptr) { @@ -199,7 +203,7 @@ void View::viewFile(const std::string& path, const std::string& filename) { // Remove forward slash, because we need a relative path notes::start(file_path.substr(1)); } - } else if (app_exec_is_executable_path(file_path.c_str())) { + } else if (isExecutablePath(file_path)) { runFile(file_path); } else { LOG_W(TAG, "Opening files of this type is not supported"); @@ -211,7 +215,7 @@ void View::viewFile(const std::string& path, const std::string& filename) { void View::runFile(const std::string& file_path) { LOG_I(TAG, "Running %s", file_path.c_str()); - if (!app_exec_is_executable_path(file_path.c_str())) { + if (!isExecutablePath(file_path)) { LOG_W(TAG, "Not executable: %s", file_path.c_str()); alertdialog::start(appInstanceId, "Run failed", "Could not run \"" + file::getLastPathSegment(file_path) + "\"."); return; @@ -219,7 +223,7 @@ void View::runFile(const std::string& file_path) { AppLocation location { APP_LOCATION_PATH, const_cast(file_path.c_str()) }; AppInstanceId instance_id = 0; - if (app_manager_start_location(location, AppStackConfig {}, 0, nullptr, &instance_id) != ERROR_NONE) { + if (app_execute(location, AppStackConfig {}, 0, nullptr, &instance_id) != ERROR_NONE) { LOG_W(TAG, "Failed to run %s", file_path.c_str()); alertdialog::start(appInstanceId, "Run failed", "Could not run \"" + file::getLastPathSegment(file_path) + "\"."); } @@ -313,7 +317,7 @@ void View::createDirEntryWidget(lv_obj_t* list, dirent& dir_entry) { symbol = LV_SYMBOL_IMAGE; } else if (dir_entry.d_type == file::TT_DT_LNK) { symbol = LV_SYMBOL_LOOP; - } else if (app_exec_is_executable_path(file::getChildPath(state->getCurrentPath(), dir_entry.d_name).c_str())) { + } else if (isExecutablePath(file::getChildPath(state->getCurrentPath(), dir_entry.d_name))) { symbol = LV_SYMBOL_PLAY; } else { symbol = LV_SYMBOL_FILE; @@ -420,7 +424,7 @@ void View::showActionsForDirectory() { showActions(); } void View::showActionsForFile() { lv_obj_clean(action_list); - if (app_exec_is_executable_path(state->getSelectedChildPath().c_str())) { + if (isExecutablePath(state->getSelectedChildPath())) { auto* run_button = lv_list_add_button(action_list, LV_SYMBOL_PLAY, "Run"); lv_obj_add_event_cb(run_button, onRunPressedCallback, LV_EVENT_SHORT_CLICKED, this); } diff --git a/Tactility/Source/app/fileselection/FileSelection.cpp b/Tactility/Source/app/fileselection/FileSelection.cpp index dd28b5236..3e98e2c2f 100644 --- a/Tactility/Source/app/fileselection/FileSelection.cpp +++ b/Tactility/Source/app/fileselection/FileSelection.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -106,7 +107,7 @@ uint32_t startWithMode(const char* modeArg, uint32_t callerAppInstanceId, AppStr .event_group = eventGroup, }; uint32_t instanceId = 0; - app_manager_start_for_result_with_streams(manifest.id, callerAppInstanceId, 1, argv, &binding, 1, &instanceId); + app_start_for_result_with_streams(manifest.id, 1, argv, &binding, 1, callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/gpssettings/GpsSettings.cpp b/Tactility/Source/app/gpssettings/GpsSettings.cpp index d24624cc5..f3798cd2d 100644 --- a/Tactility/Source/app/gpssettings/GpsSettings.cpp +++ b/Tactility/Source/app/gpssettings/GpsSettings.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -73,7 +74,7 @@ void onAddGpsPressed(lv_event_t* event) { // this app; rebuildDeviceList() runs fresh whenever this app is resumed regardless). (void)ctx; uint32_t instanceId = 0; - app_manager_start(addgps::manifest.id, &instanceId); + app_start(addgps::manifest.id, 0, nullptr, &instanceId); } void onDeviceButtonPressed(lv_event_t* event) { diff --git a/Tactility/Source/app/i2cscanner/I2cScanner.cpp b/Tactility/Source/app/i2cscanner/I2cScanner.cpp index 0f49507ea..87fc23db9 100644 --- a/Tactility/Source/app/i2cscanner/I2cScanner.cpp +++ b/Tactility/Source/app/i2cscanner/I2cScanner.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -430,7 +431,7 @@ extern const ::AppManifest manifest = { uint32_t start() { uint32_t instanceId = 0; - app_manager_start(manifest.id, &instanceId); + app_start(manifest.id, 0, nullptr, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/imageviewer/ImageViewer.cpp b/Tactility/Source/app/imageviewer/ImageViewer.cpp index a26bbbd03..ac64c298a 100644 --- a/Tactility/Source/app/imageviewer/ImageViewer.cpp +++ b/Tactility/Source/app/imageviewer/ImageViewer.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -123,7 +124,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& file) { const char* argv[] = { file.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId); + app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/inputdialog/InputDialog.cpp b/Tactility/Source/app/inputdialog/InputDialog.cpp index 4e3e57248..a6028a030 100644 --- a/Tactility/Source/app/inputdialog/InputDialog.cpp +++ b/Tactility/Source/app/inputdialog/InputDialog.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -145,7 +146,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start(uint32_t callerAppInstanceId, const std::string& title, const std::string& message, const std::string& prefilled) { const char* argv[] = { title.c_str(), message.c_str(), prefilled.c_str() }; uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, 3, argv, &instanceId); + app_start_for_result(manifest.id, 3, argv, callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index f899a5e42..63a9d9865 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -45,7 +46,7 @@ int32_t computeButtonMargin(int32_t available_span, int32_t total_button_size) { void onAppPressed(lv_event_t* e) { auto* appId = static_cast(lv_event_get_user_data(e)); uint32_t instance_id = 0; - app_manager_start(appId, &instance_id); + app_start(appId, 0, nullptr, &instance_id); } lv_obj_t* createAppButton(lv_obj_t* parent, UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { @@ -214,7 +215,7 @@ void runAutoStart() { ) { LOG_I(TAG, "Starting %s", CONFIG_TT_AUTO_START_APP_ID); uint32_t app_launch_id; - app_manager_start(CONFIG_TT_AUTO_START_APP_ID, &app_launch_id); + app_start(CONFIG_TT_AUTO_START_APP_ID, 0, nullptr, &app_launch_id); } else if ( // Auto-start due to user configuration settings::loadBootSettings(boot_properties) && @@ -223,7 +224,7 @@ void runAutoStart() { ) { LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str()); uint32_t app_launch_id; - app_manager_start(boot_properties.autoStartAppId.c_str(), &app_launch_id); + app_start(boot_properties.autoStartAppId.c_str(), 0, nullptr, &app_launch_id); } else { // No auto-start, consider running system setup if (!setup::isCompleted()) { @@ -282,7 +283,7 @@ extern const ::AppManifest manifest = { // used by the old, unconverted CrashDiagnostics app to return to the launcher after a crash). uint32_t start() { uint32_t instance_id = 0; - app_manager_start(manifest.id, &instance_id); + app_start(manifest.id, 0, nullptr, &instance_id); return instance_id; } diff --git a/Tactility/Source/app/notes/Notes.cpp b/Tactility/Source/app/notes/Notes.cpp index 381d618aa..24c1fc657 100644 --- a/Tactility/Source/app/notes/Notes.cpp +++ b/Tactility/Source/app/notes/Notes.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -268,7 +269,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& filePath) { const char* argv[] = { filePath.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId); + app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp index 96442721e..5f9927b1b 100644 --- a/Tactility/Source/app/selectiondialog/SelectionDialog.cpp +++ b/Tactility/Source/app/selectiondialog/SelectionDialog.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -133,7 +134,7 @@ int32_t appMain(int argc, char* argv[]) { namespace { -// Builds argv = [title, items...] for app_manager_start_for_result(). +// Builds argv = [title, items...] for app_start_for_result(). std::vector buildArgv(const std::string& title, const std::vector& items) { std::vector argv { title.c_str() }; for (const auto& item: items) { @@ -147,7 +148,7 @@ std::vector buildArgv(const std::string& title, const std::vector& items) { auto argv = buildArgv(title, items); AppInstanceId instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast(argv.size()), argv.data(), &instanceId); + app_start_for_result(manifest.id, static_cast(argv.size()), argv.data(), callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/settings/Settings.cpp b/Tactility/Source/app/settings/Settings.cpp index 8b05f4ccc..f623618f0 100644 --- a/Tactility/Source/app/settings/Settings.cpp +++ b/Tactility/Source/app/settings/Settings.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -28,7 +29,7 @@ void onAppPressed(lv_event_t* e) { // Fire-and-forget top-level navigation, same as AppList's own app-launch buttons. const auto* manifest = static_cast(lv_event_get_user_data(e)); uint32_t instanceId = 0; - app_manager_start(manifest->id, &instanceId); + app_start(manifest->id, 0, nullptr, &instanceId); } void onBackPressed(lv_event_t* event) { diff --git a/Tactility/Source/app/setup/Setup.cpp b/Tactility/Source/app/setup/Setup.cpp index b06e2831b..42047b9b2 100644 --- a/Tactility/Source/app/setup/Setup.cpp +++ b/Tactility/Source/app/setup/Setup.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -269,7 +270,7 @@ int32_t appMain(int argc, char* argv[]) { void start() { uint32_t instanceId = 0; - app_manager_start(manifest.id, &instanceId); + app_start(manifest.id, 0, nullptr, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index 8a5da22d1..c52c486b5 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -206,7 +207,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start() { uint32_t instanceId = 0; - app_manager_start(manifest.id, &instanceId); + app_start(manifest.id, 0, nullptr, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index 9ef2f9f37..f10850667 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -273,7 +274,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start(uint32_t callerAppInstanceId, bool saveTimeZone) { const char* argv[] = { saveTimeZone ? "1" : "0" }; uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, 1, argv, &instanceId); + app_start_for_result(manifest.id, 1, argv, callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp index 96867584d..7db51338f 100644 --- a/Tactility/Source/app/touchcalibration/TouchCalibration.cpp +++ b/Tactility/Source/app/touchcalibration/TouchCalibration.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -285,7 +286,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start(uint32_t callerAppInstanceId) { uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, 0, nullptr, &instanceId); + app_start_for_result(manifest.id, 0, nullptr, callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp index cf4b697f7..581b91066 100644 --- a/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp +++ b/Tactility/Source/app/wifiapsettings/WifiApSettings.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -317,7 +318,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& ssid) { const char* argv[] = { ssid.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 1, argv, &instanceId); + app_start(manifest.id, 1, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index 08ccf333f..3cc324605 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -377,7 +378,7 @@ int32_t appMain(int argc, char* argv[]) { void start(const std::string& ssid, const std::string& password) { const char* argv[] = { ssid.c_str(), password.c_str() }; uint32_t instanceId = 0; - app_manager_start_with_parameters(manifest.id, 2, argv, &instanceId); + app_start(manifest.id, 2, argv, &instanceId); } extern const ::AppManifest manifest = { diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 935f8b400..fdc97442b 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -240,7 +241,7 @@ int32_t appMain(int argc, char* argv[]) { uint32_t start(uint32_t callerAppInstanceId) { uint32_t instanceId = 0; - app_manager_start_for_result(manifest.id, callerAppInstanceId, 0, nullptr, &instanceId); + app_start_for_result(manifest.id, 0, nullptr, callerAppInstanceId, &instanceId); return instanceId; } diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 833fcf714..232c0687e 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -113,7 +114,7 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) { } } - app_manager_start(id_key_pos->second.c_str(), &instance_id); + app_start(id_key_pos->second.c_str(), 0, nullptr, &instance_id); LOG_I(TAG, "[200] /app/run %s", id_key_pos->second.c_str()); httpd_resp_send(request, nullptr, 0); diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index e3af7ca3d..529d0e404 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -2,6 +2,8 @@ #include #include + +#include #include #include #include @@ -1270,7 +1272,7 @@ esp_err_t WebServerService::handleApiAppsRun(httpd_req_t* request) { // Every app instance gets its own task now, so there's no "stop the existing one first" - // this just starts a fresh instance alongside whatever's already running. AppInstanceId instance_id = 0; - app_manager_start(appId.c_str(), &instance_id); + app_start(appId.c_str(), 0, nullptr, &instance_id); LOG_I(TAG, "[200] /api/apps/run %s", appId.c_str()); httpd_resp_sendstr(request, "ok");