diff options
author | Jo-Philipp Wich <jo@mein.io> | 2023-08-07 01:01:02 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-07 01:01:02 +0200 |
commit | a38315454add264a41094524e1fcf435acb85fe8 (patch) | |
tree | fb6cf7d52f641e35316b4db39a1a424927d77ba5 | |
parent | bb5eba4db8895d862038825b84d5b7a94ee5cbb0 (diff) | |
parent | 2593270498be82de82f770cb3744d9e5f8d0b0bd (diff) |
Merge pull request #157 from jow-/signal-handling
Introduce signal handling
-rw-r--r-- | include/ucode/types.h | 8 | ||||
-rw-r--r-- | include/ucode/vm.h | 4 | ||||
-rw-r--r-- | lib.c | 243 | ||||
-rw-r--r-- | lib/uloop.c | 24 | ||||
-rw-r--r-- | main.c | 3 | ||||
-rw-r--r-- | tests/custom/03_stdlib/60_gc | 50 | ||||
-rw-r--r-- | types.c | 1 | ||||
-rw-r--r-- | vm.c | 138 |
8 files changed, 445 insertions, 26 deletions
diff --git a/include/ucode/types.h b/include/ucode/types.h index 9827db5..641c469 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -20,6 +20,7 @@ #include <stdbool.h> #include <stdint.h> #include <regex.h> +#include <signal.h> #include <json-c/json.h> #include "util.h" @@ -230,6 +231,7 @@ typedef struct { bool raw_mode; uc_search_path_t module_search_path; uc_search_path_t force_dynlink_list; + bool setup_signal_handlers; } uc_parse_config_t; extern uc_parse_config_t uc_default_parse_config; @@ -311,6 +313,12 @@ struct uc_vm { uc_stringbuf_t *strbuf; uc_exception_handler_t *exhandler; FILE *output; + struct { + uint64_t raised[((NSIG + 63) & ~63) / 64]; + uc_value_t *handler; + struct sigaction sa; + int sigpipe[2]; + } signal; }; diff --git a/include/ucode/vm.h b/include/ucode/vm.h index 161f1ee..3cb6dc0 100644 --- a/include/ucode/vm.h +++ b/include/ucode/vm.h @@ -156,4 +156,8 @@ uc_vm_raise_exception(uc_vm_t *vm, uc_exception_type_t type, const char *fmt, .. uc_vm_status_t uc_vm_execute(uc_vm_t *vm, uc_program_t *fn, uc_value_t **retval); uc_value_t *uc_vm_invoke(uc_vm_t *vm, const char *fname, size_t nargs, ...); +uc_exception_type_t uc_vm_signal_dispatch(uc_vm_t *vm); +void uc_vm_signal_raise(uc_vm_t *vm, int signo); +int uc_vm_signal_notifyfd(uc_vm_t *vm); + #endif /* UCODE_VM_H */ @@ -5647,6 +5647,248 @@ uc_callfunc(uc_vm_t *vm, size_t nargs) } +static const char *signal_names[] = { +#if defined(SIGINT) + [SIGINT] = "INT", +#endif +#if defined(SIGILL) + [SIGILL] = "ILL", +#endif +#if defined(SIGABRT) + [SIGABRT] = "ABRT", +#endif +#if defined(SIGFPE) + [SIGFPE] = "FPE", +#endif +#if defined(SIGSEGV) + [SIGSEGV] = "SEGV", +#endif +#if defined(SIGTERM) + [SIGTERM] = "TERM", +#endif +#if defined(SIGHUP) + [SIGHUP] = "HUP", +#endif +#if defined(SIGQUIT) + [SIGQUIT] = "QUIT", +#endif +#if defined(SIGTRAP) + [SIGTRAP] = "TRAP", +#endif +#if defined(SIGKILL) + [SIGKILL] = "KILL", +#endif +#if defined(SIGPIPE) + [SIGPIPE] = "PIPE", +#endif +#if defined(SIGALRM) + [SIGALRM] = "ALRM", +#endif +#if defined(SIGSTKFLT) + [SIGSTKFLT] = "STKFLT", +#endif +#if defined(SIGPWR) + [SIGPWR] = "PWR", +#endif +#if defined(SIGBUS) + [SIGBUS] = "BUS", +#endif +#if defined(SIGSYS) + [SIGSYS] = "SYS", +#endif +#if defined(SIGURG) + [SIGURG] = "URG", +#endif +#if defined(SIGSTOP) + [SIGSTOP] = "STOP", +#endif +#if defined(SIGTSTP) + [SIGTSTP] = "TSTP", +#endif +#if defined(SIGCONT) + [SIGCONT] = "CONT", +#endif +#if defined(SIGCHLD) + [SIGCHLD] = "CHLD", +#endif +#if defined(SIGTTIN) + [SIGTTIN] = "TTIN", +#endif +#if defined(SIGTTOU) + [SIGTTOU] = "TTOU", +#endif +#if defined(SIGPOLL) + [SIGPOLL] = "POLL", +#endif +#if defined(SIGXFSZ) + [SIGXFSZ] = "XFSZ", +#endif +#if defined(SIGXCPU) + [SIGXCPU] = "XCPU", +#endif +#if defined(SIGVTALRM) + [SIGVTALRM] = "VTALRM", +#endif +#if defined(SIGPROF) + [SIGPROF] = "PROF", +#endif +#if defined(SIGUSR1) + [SIGUSR1] = "USR1", +#endif +#if defined(SIGUSR2) + [SIGUSR2] = "USR2", +#endif +}; + +/** + * Set or query process signal handler function. + * + * When invoked with two arguments, a signal specification and a signal handler + * value, this function configures a new process signal handler. + * + * When invoked with one argument, a signal specification, this function returns + * the currently configured handler for the given signal. + * + * The signal specification might either be an integer signal number or a string + * value containing a signal name (with or without "SIG" prefix). Signal names + * are treated case-insensitively. + * + * The signal handler might be either a callable function value or one of the + * two special string values `"ignore"` and `"default"`. Passing `"ignore"` will + * mask the given process signal while `"default"` will restore the operating + * systems default behaviour for the given signal. + * + * In case a callable handler function is provided, it is invoked at the + * earliest opportunity after receiving the corresponding signal from the + * operating system. The invoked function will receive a single argument, the + * number of the signal it is invoked for. + * + * Note that within the ucode VM, process signals are not immediately delivered, + * instead the VM keeps track of received signals and delivers them to the ucode + * script environment at the next opportunity, usually before executing the next + * byte code instruction. This means that if a signal is received while + * performing a computationally expensive operation in C mode, such as a complex + * regexp match, the corresponding ucode signal handler will only be invoked + * after that operation concluded and control flow returns to the VM. + * + * Returns the signal handler function or one of the special values `"ignore"` + * or `"default"` corresponding to the given signal specification. + * + * Returns `null` if an invalid signal spec or signal handler was provided. + * + * Returns `null` if changing the signal action failed, e.g. due to insufficient + * permission, or when attempting to ignore a non-ignorable signal. + * + * @function module:core#signal + * + * @param {number|string} signal + * The signal to query/set handler for. + * + * @param {Function|string} [handler] + * The signal handler to install for the given signal. + * + * @returns {Function|string} + * + * @example + * // Ignore signals + * signal('INT', 'ignore'); // "ignore" + * signal('SIGINT', 'ignore'); // "ignore" (equivalent to 'INT') + * signal('sigterm', 'ignore'); // "ignore" (signal names are case insensitive) + * signal(9, 'ignore'); // null (SIGKILL cannot be ignored) + * + * // Restore signal default behavior + * signal('INT', 'default'); // "default" + * signal('foobar', 'default'); // null (unknown signal name) + * signal(-313, 'default'); // null (invalid signal number) + * + * // Set custom handler function + * function intexit(signo) { + * printf("I received signal number %d\n", signo); + * exit(1); + * } + * + * signal('SIGINT', intexit); // returns intexit + * signal('SIGINT') == intexit; // true + */ +static uc_value_t * +uc_signal(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *signame = uc_fn_arg(0); + uc_value_t *sighandler = uc_fn_arg(1); + struct sigaction sa = { 0 }; + char *sigstr; + int sig; + + if (ucv_type(signame) == UC_INTEGER) { + sig = (int)ucv_int64_get(signame); + + if (errno || sig >= (int)ARRAY_SIZE(signal_names) || !signal_names[sig]) + return NULL; + } + else if (ucv_type(signame) == UC_STRING) { + sigstr = ucv_string_get(signame); + + if (!strncasecmp(sigstr, "SIG", 3)) + sigstr += 3; + + for (sig = 0; sig < (int)ARRAY_SIZE(signal_names); sig++) + if (signal_names[sig] && !strcasecmp(signal_names[sig], sigstr)) + break; + + if (sig == (int)ARRAY_SIZE(signal_names)) + return NULL; + } + else { + return NULL; + } + + /* Query current signal handler state */ + if (nargs < 2) { + if (sigaction(sig, NULL, &sa) != 0) + return NULL; + + if (sa.sa_handler == SIG_IGN) + return ucv_string_new("ignore"); + + if (sa.sa_handler == SIG_DFL) + return ucv_string_new("default"); + + return ucv_get(ucv_array_get(vm->signal.handler, sig)); + } + + /* Install new signal handler */ + if (ucv_type(sighandler) == UC_STRING) { + sigstr = ucv_string_get(sighandler); + + sa.sa_flags = SA_ONSTACK | SA_RESTART; + sigemptyset(&sa.sa_mask); + + if (!strcmp(sigstr, "ignore")) + sa.sa_handler = SIG_IGN; + else if (!strcmp(sigstr, "default")) + sa.sa_handler = SIG_DFL; + else + return NULL; + + if (sigaction(sig, &sa, NULL) != 0) + return NULL; + + ucv_array_set(vm->signal.handler, sig, NULL); + } + else if (ucv_is_callable(sighandler)) { + if (sigaction(sig, &vm->signal.sa, NULL) != 0) + return NULL; + + ucv_array_set(vm->signal.handler, sig, ucv_get(sighandler)); + } + else { + return NULL; + } + + return ucv_get(sighandler); +} + + const uc_function_list_t uc_stdlib_functions[] = { { "chr", uc_chr }, { "die", uc_die }, @@ -5718,6 +5960,7 @@ const uc_function_list_t uc_stdlib_functions[] = { { "loadstring", uc_loadstring }, { "loadfile", uc_loadfile }, { "call", uc_callfunc }, + { "signal", uc_signal }, }; diff --git a/lib/uloop.c b/lib/uloop.c index f45f8c3..99cd984 100644 --- a/lib/uloop.c +++ b/lib/uloop.c @@ -1155,8 +1155,22 @@ static void close_pipe(void *ud) } +static struct { + struct uloop_fd ufd; + uc_vm_t *vm; +} signal_handle; + +static void +uc_uloop_signal_cb(struct uloop_fd *ufd, unsigned int events) +{ + if (uc_vm_signal_dispatch(signal_handle.vm) != EXCEPTION_NONE) + uloop_end(); +} + void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { + int signal_fd; + uc_function_list_register(scope, global_fns); #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) @@ -1175,4 +1189,14 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope) object_registry = ucv_array_new(vm); uc_vm_registry_set(vm, "uloop.registry", object_registry); + + signal_fd = uc_vm_signal_notifyfd(vm); + + if (signal_fd != -1 && uloop_init() == 0) { + signal_handle.vm = vm; + signal_handle.ufd.cb = uc_uloop_signal_cb; + signal_handle.ufd.fd = signal_fd; + + uloop_fd_add(&signal_handle.ufd, ULOOP_READ); + } } @@ -502,7 +502,8 @@ main(int argc, char **argv) .strict_declarations = false, .lstrip_blocks = true, .trim_blocks = true, - .raw_mode = true + .raw_mode = true, + .setup_signal_handlers = true }; app = appname(argv[0]); diff --git a/tests/custom/03_stdlib/60_gc b/tests/custom/03_stdlib/60_gc index 44e5d9e..28c0d00 100644 --- a/tests/custom/03_stdlib/60_gc +++ b/tests/custom/03_stdlib/60_gc @@ -45,11 +45,11 @@ Returns an object count if the given operation is `count`. -- End -- -- Expect stdout -- -Count #1: 5 -Count #2: 6 -Count #3: 5 -Count #4: 6 -Count #5: 5 +Count #1: 6 +Count #2: 7 +Count #3: 6 +Count #4: 7 +Count #5: 6 -- End -- @@ -84,24 +84,24 @@ Testing enabling the automatic collector. -- End -- -- Expect stdout -- -Count #1: 6 -Count #2: 12 -Count #3: 12 -Count #4: 12 -Count #5: 12 -Count #6: 12 -Count #7: 12 -Count #8: 12 -Count #9: 12 -Count #10: 12 -Count #11: 12 -Count #12: 22 -Count #13: 32 -Count #14: 42 -Count #15: 52 -Count #16: 62 -Count #17: 72 -Count #18: 82 -Count #19: 92 -Count #20: 102 +Count #1: 7 +Count #2: 14 +Count #3: 14 +Count #4: 14 +Count #5: 14 +Count #6: 14 +Count #7: 14 +Count #8: 14 +Count #9: 14 +Count #10: 14 +Count #11: 14 +Count #12: 24 +Count #13: 34 +Count #14: 44 +Count #15: 54 +Count #16: 64 +Count #17: 74 +Count #18: 84 +Count #19: 94 +Count #20: 104 -- End -- @@ -2279,6 +2279,7 @@ ucv_gc_common(uc_vm_t *vm, bool final) /* mark reachable objects */ ucv_gc_mark(vm->globals); ucv_gc_mark(vm->registry); + ucv_gc_mark(vm->signal.handler); ucv_gc_mark(vm->exception.stacktrace); for (i = 0; i < vm->callframes.count; i++) { @@ -21,6 +21,8 @@ #include <math.h> #include <errno.h> #include <limits.h> +#include <fcntl.h> +#include <unistd.h> #include "ucode/vm.h" #include "ucode/compiler.h" @@ -142,6 +144,69 @@ uc_vm_alloc_global_scope(uc_vm_t *vm) static void uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex); +static uc_vm_t *signal_handler_vm; + +static void +uc_vm_signal_handler(int sig) +{ + assert(signal_handler_vm); + + uc_vm_signal_raise(signal_handler_vm, sig); +} + +#ifdef __APPLE__ +static int pipe2(int pipefd[2], int flags) +{ + if (pipe(pipefd) != 0) + return -1; + + if (flags & O_CLOEXEC) { + if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) { + close(pipefd[0]); + close(pipefd[1]); + + return -1; + } + + flags &= ~O_CLOEXEC; + } + + if (fcntl(pipefd[0], F_SETFL, flags) != 0 || + fcntl(pipefd[1], F_SETFL, flags) != 0) { + close(pipefd[0]); + close(pipefd[1]); + + return -1; + } + + return 0; +} +#endif + +static void +uc_vm_signal_handlers_setup(uc_vm_t *vm) +{ + memset(&vm->signal, 0, sizeof(vm->signal)); + + vm->signal.sigpipe[0] = -1; + vm->signal.sigpipe[1] = -1; + + if (!vm->config->setup_signal_handlers) + return; + + if (pipe2(vm->signal.sigpipe, O_CLOEXEC | O_NONBLOCK) != 0) + return; + + signal_handler_vm = vm; + + vm->signal.handler = ucv_array_new_length(vm, NSIG - 1); + + vm->signal.sa.sa_handler = uc_vm_signal_handler; + vm->signal.sa.sa_flags = SA_RESTART | SA_ONSTACK; + sigemptyset(&vm->signal.sa.sa_mask); +} + void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config) { vm->exception.type = EXCEPTION_NONE; @@ -165,6 +230,8 @@ void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config) uc_vm_exception_handler_set(vm, uc_vm_output_exception); uc_vm_trace_set(vm, 0); + + uc_vm_signal_handlers_setup(vm); } void uc_vm_free(uc_vm_t *vm) @@ -2564,6 +2631,53 @@ uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex) fprintf(stderr, "\n"); } +uc_exception_type_t +uc_vm_signal_dispatch(uc_vm_t *vm) +{ + uc_exception_type_t ex; + uc_value_t *handler; + uint64_t mask; + size_t i, j; + int sig, rv; + + if (!vm->config->setup_signal_handlers) + return EXCEPTION_NONE; + + for (i = 0; i < ARRAY_SIZE(vm->signal.raised); i++) { + if (!vm->signal.raised[i]) + continue; + + do { + rv = read(vm->signal.sigpipe[0], &sig, sizeof(sig)); + } while (rv > 0 || (rv == -1 && errno == EINTR)); + + for (j = 0; j < 64; j++) { + mask = 1ull << j; + + if (vm->signal.raised[i] & mask) { + vm->signal.raised[i] &= ~mask; + + sig = i * 64 + j; + handler = ucv_array_get(vm->signal.handler, sig); + + if (ucv_is_callable(handler)) { + uc_vm_stack_push(vm, ucv_get(handler)); + uc_vm_stack_push(vm, ucv_int64_new(sig)); + + ex = uc_vm_call(vm, false, 1); + + if (ex != EXCEPTION_NONE) + return ex; + + ucv_put(uc_vm_stack_pop(vm)); + } + } + } + } + + return EXCEPTION_NONE; +} + static uc_vm_status_t uc_vm_execute_chunk(uc_vm_t *vm) { @@ -2812,6 +2926,7 @@ uc_vm_execute_chunk(uc_vm_t *vm) break; } +exception: /* previous instruction raised exception */ if (vm->exception.type != EXCEPTION_NONE) { /* VM termination was requested */ @@ -2840,6 +2955,10 @@ uc_vm_execute_chunk(uc_vm_t *vm) chunk = uc_vm_frame_chunk(frame); } } + + /* run handler for signal(s) delivered during previous instruction */ + if (uc_vm_signal_dispatch(vm) != EXCEPTION_NONE) + goto exception; } return STATUS_OK; @@ -3057,3 +3176,22 @@ uc_vm_gc_stop(uc_vm_t *vm) return true; } + +void +uc_vm_signal_raise(uc_vm_t *vm, int signo) +{ + uint8_t signum = signo; + + if (signo <= 0 || signo >= NSIG) + return; + + vm->signal.raised[signo / 64] |= (1ull << (signo % 64)); + + write(vm->signal.sigpipe[1], &signum, sizeof(signum)); +} + +int +uc_vm_signal_notifyfd(uc_vm_t *vm) +{ + return vm->signal.sigpipe[0]; +} |