summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2023-08-07 01:01:02 +0200
committerGitHub <noreply@github.com>2023-08-07 01:01:02 +0200
commita38315454add264a41094524e1fcf435acb85fe8 (patch)
treefb6cf7d52f641e35316b4db39a1a424927d77ba5
parentbb5eba4db8895d862038825b84d5b7a94ee5cbb0 (diff)
parent2593270498be82de82f770cb3744d9e5f8d0b0bd (diff)
Merge pull request #157 from jow-/signal-handling
Introduce signal handling
-rw-r--r--include/ucode/types.h8
-rw-r--r--include/ucode/vm.h4
-rw-r--r--lib.c243
-rw-r--r--lib/uloop.c24
-rw-r--r--main.c3
-rw-r--r--tests/custom/03_stdlib/60_gc50
-rw-r--r--types.c1
-rw-r--r--vm.c138
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 */
diff --git a/lib.c b/lib.c
index 9ca2790..b06c02b 100644
--- a/lib.c
+++ b/lib.c
@@ -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);
+ }
}
diff --git a/main.c b/main.c
index 138447a..a51c370 100644
--- a/main.c
+++ b/main.c
@@ -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 --
diff --git a/types.c b/types.c
index d601479..d3aae0a 100644
--- a/types.c
+++ b/types.c
@@ -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++) {
diff --git a/vm.c b/vm.c
index 16bf6c5..d3c26bf 100644
--- a/vm.c
+++ b/vm.c
@@ -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];
+}