Ken Van Hoeylandt f8172446fd
App loading improvements and more (#675)
- 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.
2026-10-06 07:45:58 +02:00

193 lines
5.7 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include <app/libc.h>
#include <tactility/paths.h>
#include <reent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <utime.h>
#include <cerrno>
// Wraps of the path-based VFS calls (-Wl,--wrap=, see the top-level CMakeLists.txt):
// - ESP-IDF's VFS has no cwd: an app instance's relative paths are resolved against its own (see app/libc.h).
// - ESP-IDF's VFS dereferences a NULL path. These fail with EFAULT instead, like other POSIX systems do.
// opendir() gets the same treatment in its own wrap (root_dir.cpp).
namespace {
/**
* @return the path to pass on to the VFS: resolved for an app instance, as-is otherwise.
* NULL when the resolved path doesn't fit (errno is set to ENAMETOOLONG).
*/
const char* resolve(const char* path, char (&buffer)[FILE_MAX_PATH_STRING_LENGTH]) {
const char* resolved;
return app_libc_try_resolve_path(path, buffer, sizeof(buffer), &resolved) ? resolved : path;
}
} // namespace
extern "C" {
int __real__open_r(struct _reent* r, const char* path, int flags, int mode);
int __real__stat_r(struct _reent* r, const char* path, struct stat* st);
int __real__link_r(struct _reent* r, const char* n1, const char* n2);
int __real__unlink_r(struct _reent* r, const char* path);
int __real__rename_r(struct _reent* r, const char* src, const char* dst);
int __real_truncate(const char* path, off_t length);
int __real_access(const char* path, int amode);
int __real_utime(const char* path, const struct utimbuf* times);
int __real_rmdir(const char* name);
int __real_mkdir(const char* path, mode_t mode);
// open() and fopen() both go through _open_r
int __wrap__open_r(struct _reent* r, const char* path, int flags, int mode) {
if (path == nullptr) {
r->_errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
r->_errno = ENAMETOOLONG;
return -1;
}
return __real__open_r(r, resolved, flags, mode);
}
int __wrap__stat_r(struct _reent* r, const char* path, struct stat* st) {
if (path == nullptr) {
r->_errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
r->_errno = ENAMETOOLONG;
return -1;
}
return __real__stat_r(r, resolved, st);
}
int __wrap__link_r(struct _reent* r, const char* n1, const char* n2) {
if (n1 == nullptr || n2 == nullptr) {
r->_errno = EFAULT;
return -1;
}
char buffer1[FILE_MAX_PATH_STRING_LENGTH];
char buffer2[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved1 = resolve(n1, buffer1);
const char* resolved2 = resolve(n2, buffer2);
if (resolved1 == nullptr || resolved2 == nullptr) {
r->_errno = ENAMETOOLONG;
return -1;
}
return __real__link_r(r, resolved1, resolved2);
}
// unlink() and remove() both go through _unlink_r
int __wrap__unlink_r(struct _reent* r, const char* path) {
if (path == nullptr) {
r->_errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
r->_errno = ENAMETOOLONG;
return -1;
}
return __real__unlink_r(r, resolved);
}
int __wrap__rename_r(struct _reent* r, const char* src, const char* dst) {
if (src == nullptr || dst == nullptr) {
r->_errno = EFAULT;
return -1;
}
char src_buffer[FILE_MAX_PATH_STRING_LENGTH];
char dst_buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved_src = resolve(src, src_buffer);
const char* resolved_dst = resolve(dst, dst_buffer);
if (resolved_src == nullptr || resolved_dst == nullptr) {
r->_errno = ENAMETOOLONG;
return -1;
}
return __real__rename_r(r, resolved_src, resolved_dst);
}
int __wrap_truncate(const char* path, off_t length) {
if (path == nullptr) {
errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
return -1;
}
return __real_truncate(resolved, length);
}
int __wrap_access(const char* path, int amode) {
if (path == nullptr) {
errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
return -1;
}
return __real_access(resolved, amode);
}
int __wrap_utime(const char* path, const struct utimbuf* times) {
if (path == nullptr) {
errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
return -1;
}
return __real_utime(resolved, times);
}
int __wrap_rmdir(const char* name) {
if (name == nullptr) {
errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(name, buffer);
if (resolved == nullptr) {
return -1;
}
return __real_rmdir(resolved);
}
// mkdir() on an existing FATFS mount root (e.g. "/sdcard") fails without setting EEXIST,
// which breaks the common "mkdir() then accept EEXIST" pattern. Any existing path reports EEXIST.
int __wrap_mkdir(const char* path, mode_t mode) {
if (path == nullptr) {
errno = EFAULT;
return -1;
}
char buffer[FILE_MAX_PATH_STRING_LENGTH];
const char* resolved = resolve(path, buffer);
if (resolved == nullptr) {
return -1;
}
struct stat info;
if (stat(resolved, &info) == 0) {
errno = EEXIST;
return -1;
}
return __real_mkdir(resolved, mode);
}
}