mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 10:55:06 +00:00
- External apps can opt into automatic cleanup when they exit, including tracked tasks, threads, files, directories, and memory. - App-relative file paths resolve against the app’s working directory, with `.` and `..` normalized. - The shell identifies ELF binaries that cannot be executed instead of treating them as scripts. - Shared icon fonts now offer distinct default and large sizes, with updated icons across the interface. - ESP32 apps can load additional ELF formats when address mirroring is enabled. - Terminal colors render clearly on monochrome displays, and toolbar buttons have cleaner styling. - GPS settings safely refresh their device list when their window is hidden or its widgets are destroyed.
514 lines
17 KiB
C++
514 lines
17 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Symbols resolved for ELF apps ahead of every other module's (see app_esp32_symbol_resolver()).
|
|
// Firmware code calling the same functions never gets here.
|
|
// Pending signals are delivered here, at the entry and again when a call was interrupted by one,
|
|
// since the app holds no lock of the system itself there.
|
|
// Allocations are counted per app instance (see app/memory.h).
|
|
// Memory, files and tasks are tracked for apps that request cleanup (see app/resources.h).
|
|
#include <app/libc.h>
|
|
#include <app/memory.h>
|
|
#include <app/resources.h>
|
|
#include <app/signal.h>
|
|
|
|
#include <esp_heap_caps.h>
|
|
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/idf_additions.h>
|
|
#include <freertos/task.h>
|
|
|
|
#include <tactility/module.h>
|
|
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sys/poll.h>
|
|
#include <unistd.h>
|
|
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <new>
|
|
#include <unordered_map>
|
|
|
|
namespace {
|
|
|
|
ssize_t app_read(int fd, void* buffer, size_t size) {
|
|
app_signal_deliver_pending();
|
|
const ssize_t result = read(fd, buffer, size);
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ssize_t app_write(int fd, const void* buffer, size_t size) {
|
|
app_signal_deliver_pending();
|
|
const ssize_t result = write(fd, buffer, size);
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int app_poll(struct pollfd* fds, nfds_t nfds, int timeout) {
|
|
app_signal_deliver_pending();
|
|
const int result = poll(fds, nfds, timeout);
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int app_usleep(useconds_t usec) {
|
|
app_signal_deliver_pending();
|
|
int result;
|
|
if (!app_libc_try_usleep(usec, &result)) {
|
|
return usleep(usec);
|
|
}
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
|
|
unsigned int app_sleep(unsigned int seconds) {
|
|
app_signal_deliver_pending();
|
|
unsigned int result;
|
|
if (!app_libc_try_sleep(seconds, &result)) {
|
|
return sleep(seconds);
|
|
}
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
|
|
int app_kill(pid_t pid, int sig) {
|
|
const int result = kill(pid, sig);
|
|
// A signal sent to the app itself is delivered before kill() returns
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
|
|
pid_t app_getpid() {
|
|
int result;
|
|
return app_libc_try_getpid(&result) ? result : 0;
|
|
}
|
|
|
|
pid_t app_getppid() {
|
|
int result;
|
|
return app_libc_try_getppid(&result) ? result : 0;
|
|
}
|
|
|
|
void* record_alloc(void* ptr) {
|
|
if (ptr != nullptr) {
|
|
app_memory_record_alloc(heap_caps_get_allocated_size(ptr));
|
|
app_resources_track_alloc(ptr);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
void record_free(void* ptr) {
|
|
if (ptr != nullptr) {
|
|
app_memory_record_free(heap_caps_get_allocated_size(ptr));
|
|
app_resources_untrack_alloc(ptr);
|
|
}
|
|
}
|
|
|
|
void* app_malloc(size_t size) {
|
|
return record_alloc(malloc(size));
|
|
}
|
|
|
|
void* app_calloc(size_t count, size_t size) {
|
|
return record_alloc(calloc(count, size));
|
|
}
|
|
|
|
void* app_realloc(void* ptr, size_t size) {
|
|
const size_t old_size = (ptr != nullptr) ? heap_caps_get_allocated_size(ptr) : 0;
|
|
// Untracked before ptr may be freed
|
|
app_resources_untrack_alloc(ptr);
|
|
void* result = realloc(ptr, size);
|
|
// A failed realloc() leaves the old block allocated
|
|
if (result != nullptr || size == 0) {
|
|
if (ptr != nullptr) {
|
|
app_memory_record_free(old_size);
|
|
}
|
|
record_alloc(result);
|
|
} else if (ptr != nullptr) {
|
|
app_resources_track_alloc(ptr);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void app_free(void* ptr) {
|
|
record_free(ptr);
|
|
free(ptr);
|
|
}
|
|
|
|
char* app_strdup(const char* s) {
|
|
return static_cast<char*>(record_alloc(strdup(s)));
|
|
}
|
|
|
|
char* app_strndup(const char* s, size_t n) {
|
|
return static_cast<char*>(record_alloc(strndup(s, n)));
|
|
}
|
|
|
|
void* app_operator_new(size_t size) {
|
|
return record_alloc(::operator new(size));
|
|
}
|
|
|
|
void* app_operator_new_array(size_t size) {
|
|
return record_alloc(::operator new[](size));
|
|
}
|
|
|
|
void app_operator_delete(void* ptr) {
|
|
record_free(ptr);
|
|
::operator delete(ptr);
|
|
}
|
|
|
|
void app_operator_delete_array(void* ptr) {
|
|
record_free(ptr);
|
|
::operator delete[](ptr);
|
|
}
|
|
|
|
void app_operator_delete_sized(void* ptr, size_t) {
|
|
app_operator_delete(ptr);
|
|
}
|
|
|
|
void app_operator_delete_array_sized(void* ptr, size_t) {
|
|
app_operator_delete_array(ptr);
|
|
}
|
|
|
|
// region Files
|
|
|
|
int app_open(const char* path, int flags, ...) {
|
|
va_list args;
|
|
va_start(args, flags);
|
|
const mode_t mode = (flags & O_CREAT) ? static_cast<mode_t>(va_arg(args, int)) : 0;
|
|
va_end(args);
|
|
const int fd = open(path, flags, mode);
|
|
if (fd >= 0) {
|
|
app_resources_track_fd(fd);
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
int app_close(int fd) {
|
|
app_resources_untrack_fd(fd);
|
|
return close(fd);
|
|
}
|
|
|
|
FILE* app_fopen(const char* path, const char* mode) {
|
|
FILE* file = fopen(path, mode);
|
|
if (file != nullptr) {
|
|
app_resources_track_file(file);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
FILE* app_fdopen(int fd, const char* mode) {
|
|
FILE* file = fdopen(fd, mode);
|
|
if (file != nullptr) {
|
|
// Closed through the FILE from now on
|
|
app_resources_untrack_fd(fd);
|
|
app_resources_track_file(file);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
int app_fclose(FILE* file) {
|
|
app_resources_untrack_file(file);
|
|
return fclose(file);
|
|
}
|
|
|
|
DIR* app_opendir(const char* path) {
|
|
DIR* dir = opendir(path);
|
|
if (dir != nullptr) {
|
|
app_resources_track_dir(dir);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
int app_closedir(DIR* dir) {
|
|
app_resources_untrack_dir(dir);
|
|
return closedir(dir);
|
|
}
|
|
|
|
// endregion
|
|
|
|
// region FreeRTOS tasks
|
|
|
|
struct TaskStart {
|
|
TaskFunction_t function;
|
|
void* parameter;
|
|
AppInstanceId app_instance_id;
|
|
};
|
|
|
|
// Also tracked as an allocation of the app, so it's freed when the task is deleted before it ever ran
|
|
TaskStart* task_start_create(TaskFunction_t function, void* parameter) {
|
|
auto* start = static_cast<TaskStart*>(malloc(sizeof(TaskStart)));
|
|
if (start != nullptr) {
|
|
*start = { function, parameter, app_resources_current_app() };
|
|
app_resources_track_alloc(start);
|
|
}
|
|
return start;
|
|
}
|
|
|
|
void task_trampoline(void* context) {
|
|
auto* start = static_cast<TaskStart*>(context);
|
|
// Only fails when app_resources_release() is about to delete this task
|
|
if (!app_resources_enter_task(start->app_instance_id)) {
|
|
vTaskSuspend(nullptr);
|
|
}
|
|
const TaskStart copy = *start;
|
|
app_resources_untrack_alloc(start);
|
|
free(start);
|
|
copy.function(copy.parameter);
|
|
}
|
|
|
|
void delete_task(void* handle) {
|
|
vTaskDelete(static_cast<TaskHandle_t>(handle));
|
|
}
|
|
|
|
void delete_task_with_caps(void* handle) {
|
|
vTaskDeleteWithCaps(static_cast<TaskHandle_t>(handle));
|
|
}
|
|
|
|
struct TaskCreate {
|
|
TaskStart* start;
|
|
const char* name;
|
|
uint32_t stack_depth;
|
|
UBaseType_t priority;
|
|
BaseType_t core;
|
|
// xTaskCreateStaticPinnedToCore() only
|
|
StackType_t* stack_buffer;
|
|
StaticTask_t* task_buffer;
|
|
// xTaskCreatePinnedToCoreWithCaps() only
|
|
bool with_caps;
|
|
UBaseType_t memory_caps;
|
|
TaskHandle_t created;
|
|
};
|
|
|
|
bool create_task(void* context, void** out_handle) {
|
|
auto* create = static_cast<TaskCreate*>(context);
|
|
TaskHandle_t handle = nullptr;
|
|
if (create->stack_buffer != nullptr) {
|
|
handle = xTaskCreateStaticPinnedToCore(task_trampoline, create->name, create->stack_depth, create->start, create->priority, create->stack_buffer, create->task_buffer, create->core);
|
|
} else if (create->with_caps) {
|
|
if (xTaskCreatePinnedToCoreWithCaps(task_trampoline, create->name, create->stack_depth, create->start, create->priority, &handle, create->core, create->memory_caps) != pdPASS) {
|
|
handle = nullptr;
|
|
}
|
|
} else if (xTaskCreatePinnedToCore(task_trampoline, create->name, create->stack_depth, create->start, create->priority, &handle, create->core) != pdPASS) {
|
|
handle = nullptr;
|
|
}
|
|
create->created = handle;
|
|
*out_handle = handle;
|
|
return handle != nullptr;
|
|
}
|
|
|
|
/** @return the created task, or nullptr */
|
|
TaskHandle_t create_tracked_task(TaskFunction_t function, void* parameter, TaskCreate create) {
|
|
create.start = task_start_create(function, parameter);
|
|
if (create.start == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (!app_resources_create_task(create_task, &create, create.with_caps ? delete_task_with_caps : delete_task, nullptr)) {
|
|
app_resources_untrack_alloc(create.start);
|
|
free(create.start);
|
|
return nullptr;
|
|
}
|
|
return create.created;
|
|
}
|
|
|
|
BaseType_t app_xTaskCreatePinnedToCore(TaskFunction_t function, const char* name, uint32_t stack_depth, void* parameter, UBaseType_t priority, TaskHandle_t* out_handle, BaseType_t core) {
|
|
if (app_resources_current_app() == 0) {
|
|
return xTaskCreatePinnedToCore(function, name, stack_depth, parameter, priority, out_handle, core);
|
|
}
|
|
TaskHandle_t handle = create_tracked_task(function, parameter, { nullptr, name, stack_depth, priority, core, nullptr, nullptr, false, 0, nullptr });
|
|
if (out_handle != nullptr) {
|
|
*out_handle = handle;
|
|
}
|
|
return handle != nullptr ? pdPASS : errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
|
|
}
|
|
|
|
BaseType_t app_xTaskCreate(TaskFunction_t function, const char* name, configSTACK_DEPTH_TYPE stack_depth, void* parameter, UBaseType_t priority, TaskHandle_t* out_handle) {
|
|
return app_xTaskCreatePinnedToCore(function, name, stack_depth, parameter, priority, out_handle, tskNO_AFFINITY);
|
|
}
|
|
|
|
TaskHandle_t app_xTaskCreateStaticPinnedToCore(TaskFunction_t function, const char* name, uint32_t stack_depth, void* parameter, UBaseType_t priority, StackType_t* stack_buffer, StaticTask_t* task_buffer, BaseType_t core) {
|
|
if (app_resources_current_app() == 0) {
|
|
return xTaskCreateStaticPinnedToCore(function, name, stack_depth, parameter, priority, stack_buffer, task_buffer, core);
|
|
}
|
|
return create_tracked_task(function, parameter, { nullptr, name, stack_depth, priority, core, stack_buffer, task_buffer, false, 0, nullptr });
|
|
}
|
|
|
|
TaskHandle_t app_xTaskCreateStatic(TaskFunction_t function, const char* name, uint32_t stack_depth, void* parameter, UBaseType_t priority, StackType_t* stack_buffer, StaticTask_t* task_buffer) {
|
|
return app_xTaskCreateStaticPinnedToCore(function, name, stack_depth, parameter, priority, stack_buffer, task_buffer, tskNO_AFFINITY);
|
|
}
|
|
|
|
BaseType_t app_xTaskCreatePinnedToCoreWithCaps(TaskFunction_t function, const char* name, configSTACK_DEPTH_TYPE stack_depth, void* parameter, UBaseType_t priority, TaskHandle_t* out_handle, BaseType_t core, UBaseType_t memory_caps) {
|
|
if (app_resources_current_app() == 0) {
|
|
return xTaskCreatePinnedToCoreWithCaps(function, name, stack_depth, parameter, priority, out_handle, core, memory_caps);
|
|
}
|
|
TaskHandle_t handle = create_tracked_task(function, parameter, { nullptr, name, stack_depth, priority, core, nullptr, nullptr, true, memory_caps, nullptr });
|
|
if (out_handle != nullptr) {
|
|
*out_handle = handle;
|
|
}
|
|
return handle != nullptr ? pdPASS : errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
|
|
}
|
|
|
|
BaseType_t app_xTaskCreateWithCaps(TaskFunction_t function, const char* name, configSTACK_DEPTH_TYPE stack_depth, void* parameter, UBaseType_t priority, TaskHandle_t* out_handle, UBaseType_t memory_caps) {
|
|
return app_xTaskCreatePinnedToCoreWithCaps(function, name, stack_depth, parameter, priority, out_handle, tskNO_AFFINITY, memory_caps);
|
|
}
|
|
|
|
void app_vTaskDelete(TaskHandle_t handle) {
|
|
app_resources_untrack_task(handle != nullptr ? handle : xTaskGetCurrentTaskHandle());
|
|
vTaskDelete(handle);
|
|
}
|
|
|
|
void app_vTaskDeleteWithCaps(TaskHandle_t handle) {
|
|
app_resources_untrack_task(handle != nullptr ? handle : xTaskGetCurrentTaskHandle());
|
|
vTaskDeleteWithCaps(handle);
|
|
}
|
|
|
|
// endregion
|
|
|
|
// region pthreads
|
|
|
|
struct ThreadStart {
|
|
void* (*function)(void*);
|
|
void* argument;
|
|
AppInstanceId app_instance_id;
|
|
};
|
|
|
|
// A pthread is tracked by its pthread_t, but deleted through the FreeRTOS task that runs it
|
|
std::mutex thread_tasks_mutex;
|
|
std::unordered_map<pthread_t, TaskHandle_t> thread_tasks;
|
|
|
|
void* thread_key(pthread_t thread) {
|
|
return reinterpret_cast<void*>(static_cast<uintptr_t>(thread));
|
|
}
|
|
|
|
void thread_ended() {
|
|
const pthread_t self = pthread_self();
|
|
app_resources_untrack_task(thread_key(self));
|
|
std::lock_guard lock(thread_tasks_mutex);
|
|
thread_tasks.erase(self);
|
|
}
|
|
|
|
void* thread_trampoline(void* context) {
|
|
auto* start = static_cast<ThreadStart*>(context);
|
|
if (!app_resources_enter_task(start->app_instance_id)) {
|
|
return nullptr;
|
|
}
|
|
const ThreadStart copy = *start;
|
|
app_resources_untrack_alloc(start);
|
|
free(start);
|
|
{
|
|
std::lock_guard lock(thread_tasks_mutex);
|
|
thread_tasks[pthread_self()] = xTaskGetCurrentTaskHandle();
|
|
}
|
|
void* result = copy.function(copy.argument);
|
|
thread_ended();
|
|
return result;
|
|
}
|
|
|
|
// ESP-IDF's own bookkeeping of the thread leaks
|
|
void delete_thread(void* handle) {
|
|
std::lock_guard lock(thread_tasks_mutex);
|
|
auto iterator = thread_tasks.find(static_cast<pthread_t>(reinterpret_cast<uintptr_t>(handle)));
|
|
if (iterator != thread_tasks.end()) {
|
|
vTaskDelete(iterator->second);
|
|
thread_tasks.erase(iterator);
|
|
}
|
|
}
|
|
|
|
struct ThreadCreate {
|
|
pthread_t* thread;
|
|
const pthread_attr_t* attributes;
|
|
ThreadStart* start;
|
|
};
|
|
|
|
bool create_thread(void* context, void** out_handle) {
|
|
const auto* create = static_cast<const ThreadCreate*>(context);
|
|
if (pthread_create(create->thread, create->attributes, thread_trampoline, create->start) != 0) {
|
|
return false;
|
|
}
|
|
*out_handle = thread_key(*create->thread);
|
|
return true;
|
|
}
|
|
|
|
int app_pthread_create(pthread_t* thread, const pthread_attr_t* attributes, void* (*function)(void*), void* argument) {
|
|
if (app_resources_current_app() == 0) {
|
|
return pthread_create(thread, attributes, function, argument);
|
|
}
|
|
auto* start = static_cast<ThreadStart*>(malloc(sizeof(ThreadStart)));
|
|
if (start == nullptr) {
|
|
return ENOMEM;
|
|
}
|
|
*start = { function, argument, app_resources_current_app() };
|
|
app_resources_track_alloc(start);
|
|
ThreadCreate create { thread, attributes, start };
|
|
if (!app_resources_create_task(create_thread, &create, delete_thread, nullptr)) {
|
|
app_resources_untrack_alloc(start);
|
|
free(start);
|
|
return EAGAIN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void app_pthread_exit(void* result) {
|
|
thread_ended();
|
|
pthread_exit(result);
|
|
}
|
|
|
|
// endregion
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
extern const ModuleSymbol app_esp32_symbols[] = {
|
|
{ "read", reinterpret_cast<void*>(app_read) },
|
|
{ "write", reinterpret_cast<void*>(app_write) },
|
|
{ "poll", reinterpret_cast<void*>(app_poll) },
|
|
{ "usleep", reinterpret_cast<void*>(app_usleep) },
|
|
{ "sleep", reinterpret_cast<void*>(app_sleep) },
|
|
{ "kill", reinterpret_cast<void*>(app_kill) },
|
|
{ "getpid", reinterpret_cast<void*>(app_getpid) },
|
|
{ "getppid", reinterpret_cast<void*>(app_getppid) },
|
|
{ "malloc", reinterpret_cast<void*>(app_malloc) },
|
|
{ "calloc", reinterpret_cast<void*>(app_calloc) },
|
|
{ "realloc", reinterpret_cast<void*>(app_realloc) },
|
|
{ "free", reinterpret_cast<void*>(app_free) },
|
|
{ "strdup", reinterpret_cast<void*>(app_strdup) },
|
|
{ "strndup", reinterpret_cast<void*>(app_strndup) },
|
|
{ "_Znwj", reinterpret_cast<void*>(app_operator_new) }, // operator new(unsigned int)
|
|
{ "_Znaj", reinterpret_cast<void*>(app_operator_new_array) }, // operator new[](unsigned int)
|
|
{ "_ZdlPv", reinterpret_cast<void*>(app_operator_delete) }, // operator delete(void*)
|
|
{ "_ZdaPv", reinterpret_cast<void*>(app_operator_delete_array) }, // operator delete[](void*)
|
|
{ "_ZdlPvj", reinterpret_cast<void*>(app_operator_delete_sized) }, // operator delete(void*, unsigned int)
|
|
{ "_ZdaPvj", reinterpret_cast<void*>(app_operator_delete_array_sized) }, // operator delete[](void*, unsigned int)
|
|
{ "open", reinterpret_cast<void*>(app_open) },
|
|
{ "close", reinterpret_cast<void*>(app_close) },
|
|
{ "fopen", reinterpret_cast<void*>(app_fopen) },
|
|
{ "fdopen", reinterpret_cast<void*>(app_fdopen) },
|
|
{ "fclose", reinterpret_cast<void*>(app_fclose) },
|
|
{ "opendir", reinterpret_cast<void*>(app_opendir) },
|
|
{ "closedir", reinterpret_cast<void*>(app_closedir) },
|
|
{ "xTaskCreate", reinterpret_cast<void*>(app_xTaskCreate) },
|
|
{ "xTaskCreatePinnedToCore", reinterpret_cast<void*>(app_xTaskCreatePinnedToCore) },
|
|
{ "xTaskCreateStatic", reinterpret_cast<void*>(app_xTaskCreateStatic) },
|
|
{ "xTaskCreateStaticPinnedToCore", reinterpret_cast<void*>(app_xTaskCreateStaticPinnedToCore) },
|
|
{ "xTaskCreateWithCaps", reinterpret_cast<void*>(app_xTaskCreateWithCaps) },
|
|
{ "xTaskCreatePinnedToCoreWithCaps", reinterpret_cast<void*>(app_xTaskCreatePinnedToCoreWithCaps) },
|
|
{ "vTaskDelete", reinterpret_cast<void*>(app_vTaskDelete) },
|
|
{ "vTaskDeleteWithCaps", reinterpret_cast<void*>(app_vTaskDeleteWithCaps) },
|
|
{ "pthread_create", reinterpret_cast<void*>(app_pthread_create) },
|
|
{ "pthread_exit", reinterpret_cast<void*>(app_pthread_exit) },
|
|
MODULE_SYMBOL_TERMINATOR
|
|
};
|
|
|
|
}
|