mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Remove exec path check and simplify API
This commit is contained in:
parent
a58638ced2
commit
43ccaf0568
@ -4,7 +4,6 @@
|
||||
#endif
|
||||
|
||||
#include <app/elf_check.h>
|
||||
#include <app/exec.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
#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
|
||||
102
Modules/app-module/include/app/execute.h
Normal file
102
Modules/app-module/include/app/execute.h
Normal file
@ -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 <app/manager.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
@ -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
|
||||
|
||||
80
Modules/app-module/include/app/start.h
Normal file
80
Modules/app-module/include/app/start.h
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/manager.h>
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
25
Modules/app-module/private/app/private/manager_internal.h
Normal file
25
Modules/app-module/private/app/private/manager_internal.h
Normal file
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/manager.h>
|
||||
|
||||
#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
|
||||
@ -1,200 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/exec.h>
|
||||
|
||||
#include <app/private/exec_internal.h>
|
||||
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
|
||||
#include <service/instance.h>
|
||||
#include <service/manager.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/paths.h>
|
||||
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
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<const AppLoaderApi*>(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<char*>(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"
|
||||
55
Modules/app-module/source/execute.cpp
Normal file
55
Modules/app-module/source/execute.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/execute.h>
|
||||
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
#include <app/private/manager_internal.h>
|
||||
|
||||
#include <service/instance.h>
|
||||
#include <service/manager.h>
|
||||
|
||||
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<const AppLoaderApi*>(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"
|
||||
@ -4,6 +4,7 @@
|
||||
#include <app/private/fd_table.h>
|
||||
#include <app/private/fs.h>
|
||||
#include <app/private/ledger.h>
|
||||
#include <app/private/manager_internal.h>
|
||||
#include <app/private/scheduler.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/event.h>
|
||||
#include <app/exec.h>
|
||||
#include <app/execute.h>
|
||||
#include <app/install.h>
|
||||
#include <app/io.h>
|
||||
#include <app/manager.h>
|
||||
@ -8,13 +8,11 @@
|
||||
#include <app/metadata.h>
|
||||
#include <app/paths.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/start.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
#include <app/private/exec_internal.h>
|
||||
|
||||
#include <service/manager.h>
|
||||
|
||||
#include <tactility/concurrent/task_event_group.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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<const AppLoaderApi*>(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) {
|
||||
|
||||
49
Modules/app-module/source/start.cpp
Normal file
49
Modules/app-module/source/start.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/start.h>
|
||||
|
||||
#include <app/private/ledger.h>
|
||||
#include <app/private/manager_internal.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
|
||||
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"
|
||||
@ -1,217 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "doctest.h"
|
||||
|
||||
#include <app/exec.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
|
||||
#include <service/instance.h>
|
||||
#include <service/manager.h>
|
||||
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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<const AppLoaderApi*>(service_instance_get_data(instance));
|
||||
}
|
||||
|
||||
int32_t fake_entry(int, char*[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Creates <tmp>/{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<void*>(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<void*>(fake_entry) };
|
||||
CHECK_FALSE(loader->is_executable(wrong_type));
|
||||
}
|
||||
323
Modules/app-module/tests/source/execute_test.cpp
Normal file
323
Modules/app-module/tests/source/execute_test.cpp
Normal file
@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include "doctest.h"
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/execute.h>
|
||||
#include <app/io.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
#include <service/manager.h>
|
||||
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/freertos/task.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<int32_t>(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<size_t>(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<size_t>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<void*>(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<uint8_t> 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<void*>(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<uint8_t> 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<void*>(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<void*>(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<uint8_t> 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");
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
#include <app/io.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
@ -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<void*>(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<uint8_t> 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);
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/io.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
#include <service/manager.h>
|
||||
|
||||
@ -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<size_t>(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<void*>(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<uint8_t> 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<void*>(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<uint8_t> 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");
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <app/event.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
|
||||
@ -75,7 +76,7 @@ AppInstanceId start_idle_app(const char* id) {
|
||||
AppManifest manifest { id, id, APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(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;
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/elf_check.h>
|
||||
#include <app/exec.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/location.h>
|
||||
|
||||
@ -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__
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/exec.h>
|
||||
#include <app/execute.h>
|
||||
#include <app/loader.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
#include <service/manager.h>
|
||||
@ -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<char*>(path) };
|
||||
return app_is_executable(location);
|
||||
}
|
||||
|
||||
std::atomic<int32_t> g_fixture_result { -1 };
|
||||
std::atomic<bool> 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-<arch>.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()));
|
||||
}
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/module.h>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<const char*> buildArgv(const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
||||
std::vector<const char*> argv { title.c_str(), message.c_str() };
|
||||
for (const auto& label: buttonLabels) {
|
||||
@ -146,7 +147,7 @@ std::vector<const char*> 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<std::string>& buttonLabels) {
|
||||
auto argv = buildArgv(title, message, buttonLabels);
|
||||
uint32_t instanceId = 0;
|
||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast<int>(argv.size()), argv.data(), &instanceId);
|
||||
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), callerAppInstanceId, &instanceId);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/install.h>
|
||||
#include <app/scheduler.h>
|
||||
@ -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 = {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include <app/install.h>
|
||||
#include <app/metadata.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<int>(argv.size()), argv.data(), &instanceId);
|
||||
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), /*parent_instance_id=*/0, &instanceId);
|
||||
}
|
||||
|
||||
extern const ::AppManifest manifest = {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<const ::AppManifest*>(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) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include <app/event.h>
|
||||
#include <app/exec.h>
|
||||
#include <app/execute.h>
|
||||
#include <app/install.h>
|
||||
#include <app/manager.h>
|
||||
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
@ -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<char*>(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<char*>(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);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <app/event.h>
|
||||
#include <app/io.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/paths.h>
|
||||
#include <app/scheduler.h>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<const char*>(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;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
#include <app/stream.h>
|
||||
@ -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 = {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<const char*> buildArgv(const std::string& title, const std::vector<std::string>& items) {
|
||||
std::vector<const char*> argv { title.c_str() };
|
||||
for (const auto& item: items) {
|
||||
@ -147,7 +148,7 @@ std::vector<const char*> buildArgv(const std::string& title, const std::vector<s
|
||||
AppInstanceId start(AppInstanceId callerAppInstanceId, const std::string& title, const std::vector<std::string>& items) {
|
||||
auto argv = buildArgv(title, items);
|
||||
AppInstanceId instanceId = 0;
|
||||
app_manager_start_for_result(manifest.id, callerAppInstanceId, static_cast<int>(argv.size()), argv.data(), &instanceId);
|
||||
app_start_for_result(manifest.id, static_cast<int>(argv.size()), argv.data(), callerAppInstanceId, &instanceId);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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<const ::AppManifest*>(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) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include <app/event.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/scheduler.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <app/install.h>
|
||||
#include <app/manager.h>
|
||||
#include <app/start.h>
|
||||
|
||||
#include <tactility/log.h>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <Tactility/service/webserver/WebServerService.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
|
||||
#include <app/start.h>
|
||||
#include <Tactility/settings/WebServerSettings.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/file/File.h>
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user