mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
490 lines
14 KiB
C++
490 lines
14 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// libc wraps that route an app instance's calls to its own fds, cwd and exit(). App-instance behavior
|
|
// itself lives in app-module (app/libc.h). POSIX can't use -Wl,--wrap= like ESP32: --wrap doesn't reach
|
|
// a dlopen()ed app's own printf/write calls, so these wraps are installed under their real names instead
|
|
// - dyld interpose on Apple (stdio_wrap_apple.cpp), plain strong definitions elsewhere (stdio_wrap_elf.cpp).
|
|
// app-module's io.cpp falls through to the __real_read/__real_write/__real_close defined here.
|
|
#include <app_posix/stdio_wrap.h>
|
|
|
|
#include <app/io.h>
|
|
#include <app/libc.h>
|
|
#include <app/scheduler.h>
|
|
#include <app/signal.h>
|
|
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
|
|
// region syscall wraps
|
|
|
|
// dlsym(RTLD_NEXT, ...) avoids recursing into the overrides installed under the real names.
|
|
#include <dlfcn.h>
|
|
#include <memory>
|
|
#include <unistd.h>
|
|
|
|
extern "C" {
|
|
|
|
ssize_t __real_read(int fd, void* buffer, size_t size) {
|
|
static auto real = reinterpret_cast<ssize_t (*)(int, void*, size_t)>(dlsym(RTLD_NEXT, "read"));
|
|
return real(fd, buffer, size);
|
|
}
|
|
|
|
ssize_t __real_write(int fd, const void* buffer, size_t size) {
|
|
static auto real = reinterpret_cast<ssize_t (*)(int, const void*, size_t)>(dlsym(RTLD_NEXT, "write"));
|
|
return real(fd, buffer, size);
|
|
}
|
|
|
|
int __real_close(int fd) {
|
|
static auto real = reinterpret_cast<int (*)(int)>(dlsym(RTLD_NEXT, "close"));
|
|
return real(fd);
|
|
}
|
|
|
|
int __real_ioctl(int fd, unsigned long request, void* arg) {
|
|
static auto real = reinterpret_cast<int (*)(int, unsigned long, void*)>(dlsym(RTLD_NEXT, "ioctl"));
|
|
return real(fd, request, arg);
|
|
}
|
|
|
|
char* __real_getcwd(char* buf, size_t size) {
|
|
static auto real = reinterpret_cast<char* (*)(char*, size_t)>(dlsym(RTLD_NEXT, "getcwd"));
|
|
return real(buf, size);
|
|
}
|
|
|
|
int __real_chdir(const char* path) {
|
|
static auto real = reinterpret_cast<int (*)(const char*)>(dlsym(RTLD_NEXT, "chdir"));
|
|
return real(path);
|
|
}
|
|
|
|
int __real_fstat(int fd, struct stat* st) {
|
|
static auto real = reinterpret_cast<int (*)(int, struct stat*)>(dlsym(RTLD_NEXT, "fstat"));
|
|
return real(fd, st);
|
|
}
|
|
|
|
int __real_poll(struct pollfd* fds, nfds_t nfds, int timeout) {
|
|
static auto real = reinterpret_cast<int (*)(struct pollfd*, nfds_t, int)>(dlsym(RTLD_NEXT, "poll"));
|
|
return real(fds, nfds, timeout);
|
|
}
|
|
|
|
int __real_tcgetattr(int fd, struct termios* p) {
|
|
static auto real = reinterpret_cast<int (*)(int, struct termios*)>(dlsym(RTLD_NEXT, "tcgetattr"));
|
|
return real(fd, p);
|
|
}
|
|
|
|
int __real_tcsetattr(int fd, int optional_actions, const struct termios* p) {
|
|
static auto real = reinterpret_cast<int (*)(int, int, const struct termios*)>(dlsym(RTLD_NEXT, "tcsetattr"));
|
|
return real(fd, optional_actions, p);
|
|
}
|
|
|
|
AppLibcSignalHandler __real_signal(int sig, AppLibcSignalHandler handler) {
|
|
static auto real = reinterpret_cast<AppLibcSignalHandler (*)(int, AppLibcSignalHandler)>(dlsym(RTLD_NEXT, "signal"));
|
|
return real(sig, handler);
|
|
}
|
|
|
|
int __real_kill(pid_t pid, int sig) {
|
|
static auto real = reinterpret_cast<int (*)(pid_t, int)>(dlsym(RTLD_NEXT, "kill"));
|
|
return real(pid, sig);
|
|
}
|
|
|
|
pid_t __real_getpid() {
|
|
static auto real = reinterpret_cast<pid_t (*)()>(dlsym(RTLD_NEXT, "getpid"));
|
|
return real();
|
|
}
|
|
|
|
pid_t __real_getppid() {
|
|
static auto real = reinterpret_cast<pid_t (*)()>(dlsym(RTLD_NEXT, "getppid"));
|
|
return real();
|
|
}
|
|
|
|
int __real_usleep(useconds_t usec) {
|
|
static auto real = reinterpret_cast<int (*)(useconds_t)>(dlsym(RTLD_NEXT, "usleep"));
|
|
return real(usec);
|
|
}
|
|
|
|
unsigned int __real_sleep(unsigned int seconds) {
|
|
static auto real = reinterpret_cast<unsigned int (*)(unsigned int)>(dlsym(RTLD_NEXT, "sleep"));
|
|
return real(seconds);
|
|
}
|
|
|
|
[[noreturn]] void __real_exit(int status) {
|
|
static auto real = reinterpret_cast<void (*)(int)>(dlsym(RTLD_NEXT, "exit"));
|
|
real(status);
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
// Pending signals are delivered at the entry of these wraps, and again when a call was interrupted
|
|
// by one, since the app holds no lock of the system itself there.
|
|
|
|
ssize_t __wrap_read(int fd, void* buffer, size_t size) {
|
|
app_signal_deliver_pending();
|
|
const ssize_t result = app_io_read(fd, buffer, size);
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ssize_t __wrap_write(int fd, const void* buffer, size_t size) {
|
|
app_signal_deliver_pending();
|
|
const ssize_t result = app_io_write(fd, buffer, size);
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int __wrap_close(int fd) {
|
|
return app_io_close(fd);
|
|
}
|
|
|
|
int __wrap_ioctl(int fd, unsigned long request, ...) {
|
|
va_list args;
|
|
va_start(args, request);
|
|
void* arg = va_arg(args, void*);
|
|
va_end(args);
|
|
|
|
int result;
|
|
if (app_libc_try_window_size(fd, request, arg, &result)) {
|
|
return result;
|
|
}
|
|
return __real_ioctl(fd, request, arg);
|
|
}
|
|
|
|
char* __wrap_getcwd(char* buf, size_t size) {
|
|
char* result;
|
|
if (app_libc_try_getcwd(buf, size, &result)) {
|
|
return result;
|
|
}
|
|
return __real_getcwd(buf, size);
|
|
}
|
|
|
|
int __wrap_chdir(const char* path) {
|
|
int result;
|
|
if (app_libc_try_chdir(path, &result)) {
|
|
return result;
|
|
}
|
|
return __real_chdir(path);
|
|
}
|
|
|
|
int __wrap_fstat(int fd, struct stat* st) {
|
|
int result;
|
|
if (app_libc_try_fstat(fd, st, &result)) {
|
|
return result;
|
|
}
|
|
return __real_fstat(fd, st);
|
|
}
|
|
|
|
int __wrap_poll(struct pollfd* fds, nfds_t nfds, int timeout) {
|
|
app_signal_deliver_pending();
|
|
int result;
|
|
if (app_libc_try_poll(fds, nfds, timeout, __real_poll, &result)) {
|
|
if (result < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
return result;
|
|
}
|
|
return __real_poll(fds, nfds, timeout);
|
|
}
|
|
|
|
int __wrap_tcgetattr(int fd, struct termios* p) {
|
|
int result;
|
|
if (app_libc_try_tcgetattr(fd, p, &result)) {
|
|
return result;
|
|
}
|
|
return __real_tcgetattr(fd, p);
|
|
}
|
|
|
|
int __wrap_tcsetattr(int fd, int optional_actions, const struct termios* p) {
|
|
int result;
|
|
if (app_libc_try_tcsetattr(fd, p, &result)) {
|
|
return result;
|
|
}
|
|
return __real_tcsetattr(fd, optional_actions, p);
|
|
}
|
|
|
|
// Called by an app, the real signal() would install a process-wide handler pointing into the app's binary
|
|
AppLibcSignalHandler __wrap_signal(int sig, AppLibcSignalHandler handler) {
|
|
AppLibcSignalHandler previous;
|
|
if (app_libc_try_signal(sig, handler, &previous)) {
|
|
return previous;
|
|
}
|
|
return __real_signal(sig, handler);
|
|
}
|
|
|
|
// Called by an app, the real kill() would signal the whole simulator (e.g. SIGSTOP)
|
|
int __wrap_kill(pid_t pid, int sig) {
|
|
int result;
|
|
if (app_libc_try_kill(pid, sig, &result)) {
|
|
// A signal sent to the app itself is delivered before kill() returns
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
return __real_kill(pid, sig);
|
|
}
|
|
|
|
pid_t __wrap_getpid() {
|
|
int result;
|
|
if (app_libc_try_getpid(&result)) {
|
|
return result;
|
|
}
|
|
return __real_getpid();
|
|
}
|
|
|
|
pid_t __wrap_getppid() {
|
|
int result;
|
|
if (app_libc_try_getppid(&result)) {
|
|
return result;
|
|
}
|
|
return __real_getppid();
|
|
}
|
|
|
|
int __wrap_usleep(useconds_t usec) {
|
|
app_signal_deliver_pending();
|
|
int result;
|
|
if (app_libc_try_usleep(usec, &result)) {
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
return __real_usleep(usec);
|
|
}
|
|
|
|
unsigned int __wrap_sleep(unsigned int seconds) {
|
|
app_signal_deliver_pending();
|
|
unsigned int result;
|
|
if (app_libc_try_sleep(seconds, &result)) {
|
|
app_signal_deliver_pending();
|
|
return result;
|
|
}
|
|
return __real_sleep(seconds);
|
|
}
|
|
|
|
// Called by an app, the real exit() would end the whole simulator
|
|
void __wrap_exit(int status) {
|
|
app_scheduler_exit_current(status);
|
|
__real_exit(status);
|
|
}
|
|
|
|
}
|
|
|
|
// endregion
|
|
|
|
// region stdio wraps
|
|
//
|
|
// libc's printf/fprintf/etc call an internal, non-exported write() alias that the read/write/close
|
|
// wraps above can't reach, so these redirect calls to printf/fprintf/etc directly. POSIX-only:
|
|
// newlib's stdio already goes through the wrappable syscall stubs.
|
|
//
|
|
// Only the process' original stdin/stdout/stderr are routed to the app's fds; every other stream
|
|
// (real file I/O, including fwrite() to a file) goes straight to libc. fread() is not wrapped.
|
|
// putc/getc are macros, not real calls, so wrapping those symbols wouldn't reliably intercept them.
|
|
|
|
extern "C" {
|
|
|
|
int __real_vfprintf(FILE* stream, const char* format, va_list args) {
|
|
static auto real = reinterpret_cast<int (*)(FILE*, const char*, va_list)>(dlsym(RTLD_NEXT, "vfprintf"));
|
|
return real(stream, format, args);
|
|
}
|
|
|
|
int __real_fputs(const char* s, FILE* stream) {
|
|
static auto real = reinterpret_cast<int (*)(const char*, FILE*)>(dlsym(RTLD_NEXT, "fputs"));
|
|
return real(s, stream);
|
|
}
|
|
|
|
int __real_fputc(int c, FILE* stream) {
|
|
static auto real = reinterpret_cast<int (*)(int, FILE*)>(dlsym(RTLD_NEXT, "fputc"));
|
|
return real(c, stream);
|
|
}
|
|
|
|
size_t __real_fwrite(const void* data, size_t size, size_t count, FILE* stream) {
|
|
static auto real = reinterpret_cast<size_t (*)(const void*, size_t, size_t, FILE*)>(dlsym(RTLD_NEXT, "fwrite"));
|
|
return real(data, size, count, stream);
|
|
}
|
|
|
|
int __real_fgetc(FILE* stream) {
|
|
static auto real = reinterpret_cast<int (*)(FILE*)>(dlsym(RTLD_NEXT, "fgetc"));
|
|
return real(stream);
|
|
}
|
|
|
|
char* __real_fgets(char* buffer, int size, FILE* stream) {
|
|
static auto real = reinterpret_cast<char* (*)(char*, int, FILE*)>(dlsym(RTLD_NEXT, "fgets"));
|
|
return real(buffer, size, stream);
|
|
}
|
|
|
|
}
|
|
|
|
namespace {
|
|
|
|
/** @return the number of bytes written, less than `size` if the fd stopped accepting data */
|
|
size_t writeAllTo(int fd, const void* data, size_t size) {
|
|
app_signal_deliver_pending();
|
|
const auto* bytes = static_cast<const char*>(data);
|
|
size_t remaining = size;
|
|
while (remaining > 0) {
|
|
ssize_t written = app_io_write(fd, bytes, remaining);
|
|
if (written <= 0) {
|
|
if (written < 0 && errno == EINTR) {
|
|
app_signal_deliver_pending();
|
|
}
|
|
break;
|
|
}
|
|
bytes += written;
|
|
remaining -= static_cast<size_t>(written);
|
|
}
|
|
return size - remaining;
|
|
}
|
|
|
|
int formatTo(int fd, const char* format, va_list args) {
|
|
char stackBuffer[256];
|
|
va_list argsForStack;
|
|
va_copy(argsForStack, args);
|
|
int needed = vsnprintf(stackBuffer, sizeof(stackBuffer), format, argsForStack);
|
|
va_end(argsForStack);
|
|
if (needed < 0) {
|
|
return needed;
|
|
}
|
|
if (static_cast<size_t>(needed) < sizeof(stackBuffer)) {
|
|
writeAllTo(fd, stackBuffer, static_cast<size_t>(needed));
|
|
return needed;
|
|
}
|
|
auto heapBuffer = std::make_unique<char[]>(static_cast<size_t>(needed) + 1);
|
|
va_list argsForHeap;
|
|
va_copy(argsForHeap, args);
|
|
vsnprintf(heapBuffer.get(), static_cast<size_t>(needed) + 1, format, argsForHeap);
|
|
va_end(argsForHeap);
|
|
writeAllTo(fd, heapBuffer.get(), static_cast<size_t>(needed));
|
|
return needed;
|
|
}
|
|
|
|
int readOneFromStdin(char& out) {
|
|
return static_cast<int>(__wrap_read(STDIN_FILENO, &out, 1));
|
|
}
|
|
|
|
// The process' own streams, captured before anything can reassign stdin/stdout/stderr. A caller
|
|
// that points stdout at a file (e.g. the shell's redirection) must get real file I/O, so only these
|
|
// original streams are routed to the app's fds.
|
|
FILE* const originalStdin = stdin;
|
|
FILE* const originalStdout = stdout;
|
|
FILE* const originalStderr = stderr;
|
|
|
|
int targetFdOf(FILE* stream) {
|
|
if (stream == originalStdout) return STDOUT_FILENO;
|
|
if (stream == originalStderr) return STDERR_FILENO;
|
|
return -1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
int __wrap_vprintf(const char* format, va_list args) {
|
|
return __wrap_vfprintf(stdout, format, args);
|
|
}
|
|
|
|
int __wrap_printf(const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
int result = __wrap_vfprintf(stdout, format, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
int __wrap_vfprintf(FILE* stream, const char* format, va_list args) {
|
|
int fd = targetFdOf(stream);
|
|
if (fd >= 0) {
|
|
return formatTo(fd, format, args);
|
|
}
|
|
return __real_vfprintf(stream, format, args);
|
|
}
|
|
|
|
int __wrap_fprintf(FILE* stream, const char* format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
int fd = targetFdOf(stream);
|
|
int result = (fd >= 0) ? formatTo(fd, format, args) : __real_vfprintf(stream, format, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
int __wrap_puts(const char* s) {
|
|
if (targetFdOf(stdout) < 0) {
|
|
return __real_fputs(s, stdout) < 0 ? EOF : __real_fputc('\n', stdout);
|
|
}
|
|
writeAllTo(STDOUT_FILENO, s, strlen(s));
|
|
writeAllTo(STDOUT_FILENO, "\n", 1);
|
|
return 0;
|
|
}
|
|
|
|
int __wrap_fputs(const char* s, FILE* stream) {
|
|
int fd = targetFdOf(stream);
|
|
if (fd >= 0) {
|
|
writeAllTo(fd, s, strlen(s));
|
|
return 0;
|
|
}
|
|
return __real_fputs(s, stream);
|
|
}
|
|
|
|
int __wrap_putchar(int c) {
|
|
return __wrap_fputc(c, stdout);
|
|
}
|
|
|
|
int __wrap_fputc(int c, FILE* stream) {
|
|
int fd = targetFdOf(stream);
|
|
if (fd >= 0) {
|
|
auto ch = static_cast<char>(c);
|
|
writeAllTo(fd, &ch, 1);
|
|
return c;
|
|
}
|
|
return __real_fputc(c, stream);
|
|
}
|
|
|
|
size_t __wrap_fwrite(const void* data, size_t size, size_t count, FILE* stream) {
|
|
int fd = targetFdOf(stream);
|
|
if (fd < 0) {
|
|
return __real_fwrite(data, size, count, stream);
|
|
}
|
|
if (size == 0 || count == 0) {
|
|
return 0;
|
|
}
|
|
return writeAllTo(fd, data, size * count) / size;
|
|
}
|
|
|
|
int __wrap_fgetc(FILE* stream) {
|
|
if (stream != originalStdin) {
|
|
return __real_fgetc(stream);
|
|
}
|
|
char c;
|
|
return readOneFromStdin(c) == 1 ? static_cast<unsigned char>(c) : EOF;
|
|
}
|
|
|
|
int __wrap_getchar() {
|
|
return __wrap_fgetc(stdin);
|
|
}
|
|
|
|
char* __wrap_fgets(char* buffer, int size, FILE* stream) {
|
|
if (stream != originalStdin) {
|
|
return __real_fgets(buffer, size, stream);
|
|
}
|
|
if (size <= 0) {
|
|
return nullptr;
|
|
}
|
|
int i = 0;
|
|
for (; i < size - 1; ++i) {
|
|
char c;
|
|
if (readOneFromStdin(c) != 1) {
|
|
break;
|
|
}
|
|
buffer[i] = c;
|
|
if (c == '\n') {
|
|
++i;
|
|
break;
|
|
}
|
|
}
|
|
if (i == 0) {
|
|
return nullptr;
|
|
}
|
|
buffer[i] = '\0';
|
|
return buffer;
|
|
}
|
|
|
|
}
|
|
|
|
// endregion
|