ecl/src/c/unixint.d
2012-05-19 10:07:48 +02:00

1479 lines
42 KiB
C

/* -*- mode: c; c-basic-offset: 8 -*- */
/*
unixint.c -- Unix interrupt interface.
*/
/*
Copyright (c) 1984, Taiichi Yuasa and Masami Hagiya.
Copyright (c) 1990, Giuseppe Attardi.
Copyright (c) 2001, Juan Jose Garcia Ripoll.
ECL is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
See file '../Copyright' for full details.
*/
/**********************************************************************
* HOW WE HANDLE SIGNALS AND EXCEPTIONS
*
* (the following should be correlated with the manual)
*
* POSIX contemplates the notion of "signals", which are events that
* cause a process or a thread to be interrupted. Windows uses the
* term "exception", which includes also a more general kind of
* errors.
*
* In both cases the consequence is that a thread or process may be
* interrupted at any time, either by causes which are intrinsic to
* them (synchronous signals), such as floating point exceptions, or
* extrinsic (asynchronous signals), such as the process being aborted
* by the user.
*
* Of course, those interruptions are not always welcome. When the
* interrupt is delivered and a handler is invoked, the thread or even
* the whole program may be in an inconsistent state. For instance the
* thread may have acquired a lock, or it may be in the process of
* filling the fields of a structure. Understanding this POSIX
* restricts severely what functions can be called from a signal
* handler, thereby limiting its usefulness.
*
* There is a simple solution, which ECL uses, and which is to mark
* sections of code which are interruptible, and in which it is safe
* for the handler to run arbitrary code, protect anything else. In
* principle this "marking" can be done using POSIX functions such as
* pthread_sigmask() or sigprocmask().
*
* However in practice this is slow, as it involves at least a
* function call, resolving thread-local variables, etc, etc, and it
* will not work in Windows. Furthermore, sometimes we want signals to
* be detected but not to be immediately processed. For instance, when
* reading from the terminal we want to be able to interrupt the
* process, but we can not execute the code from the handler, since
* read() may leave the input stream in an inconsistent, or even
* locked state.
*
* Our approach is slightly different: we install our own signal
* hander which reads a single, thread-local variable stored in the
* ecl_process_env()->disable_interrupts. If the variable marks that
* signals should be postponed, then the information about the signal
* is queued. Otherwise the appropriate code is executed: for instance
* invoking the debugger, jumping to a condition handler, quitting,
* etc.
*/
#ifdef __sun__
/* For SA_SIGINFO in Solaris. We could have used _XOPEN_SOURCE=600, but
* this requires C99 and the default GCC for Solaris (3.4.3) does not
* support this C standard. */
# define __EXTENSIONS__
#endif
#include <errno.h>
#include <string.h>
#include <stdio.h>
/* To get APCProc calls */
#define _WIN32_WINNT 0x400
#include <signal.h>
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <windows.h>
#endif
#if !defined(_MSC_VER)
# include <unistd.h>
#endif
#include <ecl/ecl.h>
#ifdef ECL_USE_MPROTECT
# ifndef SA_SIGINFO
# error "We cannot use the mmap code without siginfo"
# endif
# include <sys/mman.h>
#endif
#include <ecl/internal.h>
#include <ecl/ecl-inl.h>
#include <ecl/impl/math_fenv.h>
static struct {
int code;
char *name;
cl_object handler;
} known_signals[] = {
#ifdef SIGHUP
{ SIGHUP, "+SIGHUP+", Cnil},
#endif
#ifdef SIGINT
{ SIGINT, "+SIGINT+", @'si::terminal-interrupt'},
#endif
#ifdef SIGQUIT
{ SIGQUIT, "+SIGQUIT+", Cnil},
#endif
#ifdef SIGILL
{ SIGILL, "+SIGILL+", @'ext::illegal-instruction'},
#endif
#ifdef SIGTRAP
{ SIGTRAP, "+SIGTRAP+", Cnil},
#endif
#ifdef SIGABRT
{ SIGABRT, "+SIGABRT+", Cnil},
#endif
#ifdef SIGEMT
{ SIGEMT, "+SIGEMT+", Cnil},
#endif
#ifdef SIGFPE
{ SIGFPE, "+SIGFPE+", Cnil},
#endif
#ifdef SIGKILL
{ SIGKILL, "+SIGKILL+", Cnil},
#endif
#ifdef SIGBUS
{ SIGBUS, "+SIGBUS+", @'ext::segmentation-violation'},
#endif
#ifdef SIGSEGV
{ SIGSEGV, "+SIGSEGV+", @'ext::segmentation-violation'},
#endif
#ifdef SIGSYS
{ SIGSYS, "+SIGSYS+", Cnil},
#endif
#ifdef SIGPIPE
{ SIGPIPE, "+SIGPIPE+", Cnil},
#endif
#ifdef SIGALRM
{ SIGALRM, "+SIGALRM+", Cnil},
#endif
#ifdef SIGTERM
{ SIGTERM, "+SIGTERM+", Cnil},
#endif
#ifdef SIGURG
{ SIGURG, "+SIGURG+", Cnil},
#endif
#ifdef SIGSTOP
{ SIGSTOP, "+SIGSTOP+", Cnil},
#endif
#ifdef SIGTSTP
{ SIGTSTP, "+SIGTSTP+", Cnil},
#endif
#ifdef SIGCONT
{ SIGCONT, "+SIGCONT+", Cnil},
#endif
#ifdef SIGCHLD
{ SIGCHLD, "+SIGCHLD+", @'si::wait-for-all-processes'},
#endif
#ifdef SIGTTIN
{ SIGTTIN, "+SIGTTIN+", Cnil},
#endif
#ifdef SIGTTOU
{ SIGTTOU, "+SIGTTOU+", Cnil},
#endif
#ifdef SIGIO
{ SIGIO, "+SIGIO+", Cnil},
#endif
#ifdef SIGXCPU
{ SIGXCPU, "+SIGXCPU+", Cnil},
#endif
#ifdef SIGXFSZ
{ SIGXFSZ, "+SIGXFSZ+", Cnil},
#endif
#ifdef SIGVTALRM
{ SIGVTALRM, "+SIGVTALRM+", Cnil},
#endif
#ifdef SIGPROF
{ SIGPROF, "+SIGPROF+", Cnil},
#endif
#ifdef SIGWINCH
{ SIGWINCH, "+SIGWINCH+", Cnil},
#endif
#ifdef SIGINFO
{ SIGINFO, "+SIGINFO+", Cnil},
#endif
#ifdef SIGUSR1
{ SIGUSR1, "+SIGUSR1+", Cnil},
#endif
#ifdef SIGUSR2
{ SIGUSR2, "+SIGUSR2+", Cnil},
#endif
#ifdef SIGTHR
{ SIGTHR, "+SIGTHR+", Cnil},
#endif
{ -1, "", Cnil }
};
#ifdef HAVE_SIGPROCMASK
static sigset_t main_thread_sigmask;
# define handler_fn_prototype(name, sig, info, aux) name(sig, info, aux)
# define call_handler(name, sig, info, aux) name(sig, info, aux)
# define reinstall_signal(x,y)
# define copy_siginfo(x,y) memcpy(x, y, sizeof(struct sigaction))
static void
mysignal(int code, void *handler)
{
struct sigaction action;
sigaction(code, NULL, &action);
if (handler == SIG_IGN || handler == SIG_DFL) {
action.sa_handler = handler;
} else {
#ifdef SA_SIGINFO
/* void (*handler)(int, siginfo_t *, void*) */
action.sa_sigaction = handler;
action.sa_flags = SA_SIGINFO;
#else
/* void (*handler)(int) */
action.sa_handler = handler;
action.sa_flags = 0;
#endif
sigfillset(&action.sa_mask);
}
sigaction(code, &action, NULL);
}
#else /* HAVE_SIGPROCMASK */
# define handler_fn_prototype(name, sig, info, aux) name(sig)
# define call_handler(name, sig, info, aux) name(sig)
# define mysignal(x,y) signal(x,y)
# define reinstall_signal(x,y) signal(x,y)
# define copy_siginfo(x,y)
#endif
static bool
zombie_process(cl_env_ptr the_env)
{
#ifdef ECL_THREADS
if (the_env == NULL) {
return 1;
} else {
/* When we are exiting a thread, we simply ignore all signals. */
cl_object process = the_env->own_process;
return (process->process.phase == ECL_PROCESS_INACTIVE);
}
#else
return !the_env;
#endif
}
static ECL_INLINE bool
interrupts_disabled_by_C(cl_env_ptr the_env)
{
return the_env->disable_interrupts;
}
static ECL_INLINE bool
interrupts_disabled_by_lisp(cl_env_ptr the_env)
{
return !ecl_option_values[ECL_OPT_BOOTED] ||
Null(ECL_SYM_VAL(the_env, @'ext::*interrupts-enabled*'));
}
static void early_signal_error() ecl_attr_noreturn;
static void
early_signal_error()
{
ecl_internal_error("Got signal before environment was installed"
" on our thread");
}
static void illegal_signal_code(cl_object code) ecl_attr_noreturn;
static void
illegal_signal_code(cl_object code)
{
FEerror("Unknown signal code: ~D", 1, code);
}
/* On platforms in which mprotect() works, we block all write access
* to the environment for a cheap check of pending interrupts. On
* other platforms we change the value of disable_interrupts to 3, so
* that we detect changes. */
static ECL_INLINE void
set_guard_page(cl_env_ptr the_env)
{
#if defined(ECL_USE_MPROTECT)
if (mprotect(the_env, sizeof(*the_env), PROT_READ) < 0) {
ecl_internal_error("Unable to mprotect environment.");
}
#elif defined(ECL_USE_GUARD_PAGE)
if (!VirtualProtect(the_env, sizeof(*the_env), PAGE_GUARD, NULL)) {
ecl_internal_error("Unable to mprotect environment.");
}
#endif
}
static cl_object pop_signal(cl_env_ptr env);
#define unblock_signal(env, sig)
#ifdef HAVE_SIGPROCMASK
# undef unblock_signal
static void
unblock_signal(cl_env_ptr the_env, int signal)
{
/*
* We do not really "unblock" the signal, but rather restore
* ECL's default sigmask.
*/
# ifdef ECL_THREADS
pthread_sigmask(SIG_SETMASK, the_env->default_sigmask, NULL);
# else
sigprocmask(SIG_SETMASK, the_env->default_sigmask, NULL);
# endif
}
#endif
ecl_def_ct_base_string(str_ignore_signal,"Ignore signal",13,static,const);
static void
handle_signal_now(cl_object signal_code, cl_object process)
{
switch (type_of(signal_code)) {
case t_fixnum:
cl_cerror(4, str_ignore_signal, @'ext::unix-signal-received',
@':code', signal_code);
break;
case t_symbol:
/*
* When we bind a handler to a signal, it may either
* be a function, a symbol denoting a function or
* a symbol denoting a condition.
*/
if (cl_find_class(2, signal_code, Cnil) != Cnil)
cl_cerror(2, str_ignore_signal, signal_code);
#ifdef ECL_THREADS
else if (!Null(process))
_ecl_funcall3(signal_code, @':process', process);
#endif
else
_ecl_funcall1(signal_code);
break;
case t_cfun:
case t_cfunfixed:
case t_cclosure:
case t_bytecodes:
case t_bclosure:
_ecl_funcall1(signal_code);
default:
break;
}
}
cl_object
si_handle_signal(cl_object signal_code, cl_object process)
{
handle_signal_now(signal_code, process);
@(return)
}
static void
handle_all_queued(cl_env_ptr env)
{
while (env->pending_interrupt != Cnil) {
handle_signal_now(pop_signal(env), env->own_process);
}
}
static void
queue_signal(cl_env_ptr env, cl_object code, int allocate)
{
ECL_WITH_SPINLOCK_BEGIN(env, &env->signal_queue_spinlock) {
cl_object record;
if (allocate) {
record = ecl_list1(Cnil);
} else {
record = ECL_CONS_CAR(env->signal_queue);
if (record != Cnil) {
env->signal_queue = ECL_CONS_CDR(record);
}
}
if (record != Cnil) {
ECL_RPLACD(record, env->pending_interrupt);
ECL_RPLACA(record, code);
env->pending_interrupt = record;
}
} ECL_WITH_SPINLOCK_END;
}
static cl_object
pop_signal(cl_env_ptr env)
{
cl_object record, value;
if (env->pending_interrupt == Cnil) {
return Cnil;
}
ECL_WITH_SPINLOCK_BEGIN(env, &env->signal_queue_spinlock) {
record = env->pending_interrupt;
value = ECL_CONS_CAR(record);
env->pending_interrupt = ECL_CONS_CDR(record);
/* Save some conses for future use, to avoid allocating */
if (ECL_SYMBOLP(value) || ECL_FIXNUMP(value)) {
ECL_RPLACD(record, env->signal_queue);
env->signal_queue = record;
}
} ECL_WITH_SPINLOCK_END;
return value;
}
static void
handle_or_queue(cl_env_ptr the_env, cl_object signal_code, int code)
{
if (Null(signal_code) || signal_code == NULL)
return;
/*
* If interrupts are disabled by lisp we are not so eager on
* detecting when the interrupts become enabled again. We
* queue the signal and are done with that.
*/
if (interrupts_disabled_by_lisp(the_env)) {
queue_signal(the_env, signal_code, 0);
}
/*
* If interrupts are disabled by C, and we have not pushed a
* pending signal, save this signal and return.
*/
else if (interrupts_disabled_by_C(the_env)) {
the_env->disable_interrupts = 3;
queue_signal(the_env, signal_code, 0);
set_guard_page(the_env);
}
/*
* If interrupts are enabled, that means we are in a safe area
* and may execute arbitrary lisp code. We can thus call the
* appropriate handlers.
*/
else {
if (code) unblock_signal(the_env, code);
si_trap_fpe(@'last', Ct); /* Clear FPE exception flag */
handle_signal_now(signal_code, the_env->own_process);
}
}
static void
handler_fn_prototype(non_evil_signal_handler, int sig, siginfo_t *siginfo, void *data)
{
int old_errno = errno;
cl_env_ptr the_env;
cl_object signal_object;
reinstall_signal(sig, non_evil_signal_handler);
/* The lisp environment might not be installed. */
the_env = ecl_process_env();
unlikely_if (zombie_process(the_env))
return;
signal_object = ecl_gethash_safe(MAKE_FIXNUM(sig),
cl_core.known_signals,
Cnil);
handle_or_queue(the_env, signal_object, sig);
errno = old_errno;
}
static void
handler_fn_prototype(evil_signal_handler, int sig, siginfo_t *siginfo, void *data)
{
int old_errno = errno;
cl_env_ptr the_env;
cl_object signal_object;
reinstall_signal(sig, evil_signal_handler);
/* The lisp environment might not be installed. */
the_env = ecl_process_env();
unlikely_if (zombie_process(the_env))
return;
signal_object = ecl_gethash_safe(MAKE_FIXNUM(sig),
cl_core.known_signals,
Cnil);
handle_signal_now(signal_object, the_env->own_process);
errno = old_errno;
}
#if defined(ECL_THREADS) && !defined(ECL_MS_WINDOWS_HOST)
typedef struct {
cl_object process;
int signo;
} signal_thread_message;
static cl_object signal_thread_process = Cnil;
static signal_thread_message signal_thread_msg;
static cl_object signal_thread_spinlock = Cnil;
static int signal_thread_pipe[2] = {-1,-1};
static void
handler_fn_prototype(deferred_signal_handler, int sig, siginfo_t *siginfo, void *data)
{
int old_errno = errno;
cl_env_ptr the_env;
signal_thread_message msg;
reinstall_signal(sig, deferred_signal_handler);
/* The lisp environment might not be installed. */
the_env = ecl_process_env();
unlikely_if (zombie_process(the_env))
return;
msg.signo = sig;
msg.process = the_env->own_process;
if (msg.process == signal_thread_process) {
/* The signal handling thread may also receive signals. In
* this case we do not use the pipe, but just copy the message
* Note that read() will abort the thread will get notified. */
signal_thread_msg = msg;
} else if (signal_thread_pipe[1] > 0) {
ecl_get_spinlock(the_env, &signal_thread_spinlock);
write(signal_thread_pipe[1], &msg, sizeof(msg));
ecl_giveup_spinlock(&signal_thread_spinlock);
} else {
/* Nothing to do. There is no way to handle this signal because
* the responsible thread is not running */
}
errno = old_errno;
}
static cl_object
asynchronous_signal_servicing_thread()
{
const cl_env_ptr the_env = ecl_process_env();
int interrupt_signal;
/*
* We block all signals except the usual interrupt thread.
*/
{
sigset_t handled_set;
sigfillset(&handled_set);
if (ecl_option_values[ECL_OPT_TRAP_INTERRUPT_SIGNAL]) {
interrupt_signal =
ecl_option_values[ECL_OPT_THREAD_INTERRUPT_SIGNAL];
sigdelset(&handled_set, interrupt_signal);
}
pthread_sigmask(SIG_BLOCK, &handled_set, NULL);
}
/*
* We create the object for communication. We need a lock to prevent other
* threads from writing before the pipe is created.
*/
ecl_get_spinlock(the_env, &signal_thread_spinlock);
pipe(signal_thread_pipe);
ecl_giveup_spinlock(&signal_thread_spinlock);
signal_thread_msg.process = Cnil;
for (;;) {
cl_object signal_code;
signal_thread_msg.process = Cnil;
if (read(signal_thread_pipe[0], &signal_thread_msg,
sizeof(signal_thread_msg)) < 0)
{
if (errno != EINTR ||
signal_thread_msg.process != the_env->own_process)
break;
}
if (signal_thread_msg.signo == interrupt_signal &&
signal_thread_msg.process == the_env->own_process) {
break;
}
#ifdef SIGCHLD
if (signal_thread_msg.signo == SIGCHLD) {
si_wait_for_all_processes();
continue;
}
#endif
signal_code = ecl_gethash_safe(MAKE_FIXNUM(signal_thread_msg.signo),
cl_core.known_signals,
Cnil);
if (!Null(signal_code)) {
mp_process_run_function(4, @'si::handle-signal',
@'si::handle-signal',
signal_code,
signal_thread_msg.process);
}
}
close(signal_thread_pipe[0]);
close(signal_thread_pipe[1]);
ecl_return0(the_env);
}
#endif /* ECL_THREADS && !ECL_MS_WINDOWS_HOST */
#if defined(ECL_THREADS) && !defined(ECL_MS_WINDOWS_HOST)
static void
handler_fn_prototype(process_interrupt_handler, int sig, siginfo_t *siginfo, void *data)
{
int old_errno = errno;
cl_env_ptr the_env;
reinstall_signal(sig, process_interrupt_handler);
/* The lisp environment might not be installed. */
the_env = ecl_process_env();
if (zombie_process(the_env))
return;
if (!Null(the_env->pending_interrupt)) {
if (interrupts_disabled_by_C(the_env)) {
set_guard_page(the_env);
} else if (!interrupts_disabled_by_lisp(the_env)) {
unblock_signal(the_env, sig);
handle_all_queued(the_env);
}
}
errno = old_errno;
}
#endif /* ECL_THREADS && !ECL_MS_WINDOWS_HOST */
static void
handler_fn_prototype(fpe_signal_handler, int sig, siginfo_t *info, void *data)
{
cl_object condition;
int code, old_errno = errno;
cl_env_ptr the_env;
reinstall_signal(sig, fpe_signal_handler);
/* The lisp environment might not be installed. */
unlikely_if (!ecl_option_values[ECL_OPT_BOOTED]) {
early_signal_error();
}
the_env = ecl_process_env();
unlikely_if (zombie_process(the_env))
return;
condition = @'arithmetic-error';
code = 0;
#ifdef _MSC_VER
switch (_fpecode) {
case _FPE_INVALID:
condition = @'floating-point-invalid-operation';
code = FE_INVALID;
break;
case _FPE_OVERFLOW:
condition = @'floating-point-overflow';
code = FE_OVERFLOW;
break;
case _FPE_UNDERFLOW:
condition = @'floating-point-underflow';
code = FE_UNDERFLOW;
break;
case _FPE_ZERODIVIDE:
condition = @'division-by-zero';
code = FE_DIVBYZERO;
break;
}
#else /* !_MSC_VER */
# if defined(HAVE_FENV_H) & !defined(ECL_AVOID_FENV_H)
code = fetestexcept(FE_ALL_EXCEPT);
if (code & FE_DIVBYZERO) {
condition = @'division-by-zero';
code = FE_DIVBYZERO;
} else if (code & FE_INVALID) {
condition = @'floating-point-invalid-operation';
code = FE_INVALID;
} else if (code & FE_OVERFLOW) {
condition = @'floating-point-overflow';
code = FE_OVERFLOW;
} else if (code & FE_UNDERFLOW) {
condition = @'floating-point-underflow';
code = FE_UNDERFLOW;
} else if (code & FE_INEXACT) {
condition = @'floating-point-inexact';
code = FE_INEXACT;
}
feclearexcept(FE_ALL_EXCEPT);
# endif
#endif /* !_MSC_VER */
#ifdef SA_SIGINFO
if (info) {
if (info->si_code == FPE_INTDIV || info->si_code == FPE_FLTDIV) {
condition = @'division-by-zero';
code = FE_DIVBYZERO;
} else if (info->si_code == FPE_FLTOVF) {
condition = @'floating-point-overflow';
code = FE_OVERFLOW;
} else if (info->si_code == FPE_FLTUND) {
condition = @'floating-point-underflow';
code = FE_UNDERFLOW;
} else if (info->si_code == FPE_FLTRES) {
condition = @'floating-point-inexact';
code = FE_INEXACT;
} else if (info->si_code == FPE_FLTINV) {
condition = @'floating-point-invalid-operation';
code = FE_INVALID;
}
}
#endif /* SA_SIGINFO */
/*
if (code && !(code & the_env->trap_fpe_bits))
condition = Cnil;
*/
si_trap_fpe(@'last', Ct); /* Clear FPE exception flag */
unblock_signal(the_env, code);
handle_signal_now(condition, the_env->own_process);
/* We will not reach past this point. */
}
static void
handler_fn_prototype(sigsegv_handler, int sig, siginfo_t *info, void *aux)
{
int old_errno = errno;
static const char *stack_overflow_msg =
"\n;;;\n;;; Stack overflow.\n"
";;; Jumping to the outermost toplevel prompt\n"
";;;\n\n";
static const char *segv_msg =
"\n;;;\n"
";;; Detected access to protected memory, "
"also kwown as 'bus or segmentation fault'.\n"
";;; Jumping to the outermost toplevel prompt\n"
";;;\n\n";
cl_env_ptr the_env;
reinstall_signal(sig, sigsegv_handler);
/* The lisp environment might not be installed. */
unlikely_if (!ecl_option_values[ECL_OPT_BOOTED]) {
early_signal_error();
}
the_env = ecl_process_env();
unlikely_if (zombie_process(the_env))
return;
#if defined(SA_SIGINFO)
# if defined(ECL_USE_MPROTECT)
/* We access the environment when it was protected. That
* means there was a pending signal. */
if (((char*)the_env <= (char*)info->si_addr) &&
((char*)info->si_addr <= (char*)(the_env+1)))
{
mprotect(the_env, sizeof(*the_env), PROT_READ | PROT_WRITE);
the_env->disable_interrupts = 0;
unblock_signal(the_env, sig);
handle_all_queued(the_env);
return;
}
# endif /* ECL_USE_MPROTECT */
# ifdef ECL_DOWN_STACK
if (sig == SIGSEGV &&
(char*)info->si_addr > the_env->cs_barrier &&
(char*)info->si_addr <= the_env->cs_org) {
unblock_signal(the_env, sig);
ecl_unrecoverable_error(the_env, stack_overflow_msg);
return;
}
# else
if (sig == SIGSEGV &&
(char*)info->si_addr < the_env->cs_barrier &&
(char*)info->si_addr >= the_env->cs_org) {
unblock_signal(the_env, sig);
ecl_unrecoverable_error(the_env, stack_overflow_msg);
return;
}
# endif /* ECL_DOWN_STACK */
/* Do not attempt an error handler if we nest two serious
* errors in the same thread */
if (the_env->fault_address == info->si_addr) {
the_env->fault_address = info->si_addr;
unblock_signal(the_env, sig);
ecl_unrecoverable_error(the_env, segv_msg);
} else {
the_env->fault_address = info->si_addr;
handle_or_queue(the_env, @'ext::segmentation-violation', sig);
}
#else
/*
* We cannot distinguish between a stack overflow and a simple
* access violation. Thus we assume the worst case and jump to
* the outermost handler.
*/
unblock_signal(the_env, sig);
ecl_unrecoverable_error(the_env, segv_msg);
#endif /* SA_SIGINFO */
errno = old_errno;
}
cl_object
si_check_pending_interrupts(void)
{
handle_all_queued(ecl_process_env());
@(return)
}
void
ecl_check_pending_interrupts(cl_env_ptr env)
{
handle_all_queued(env);
}
static cl_object
do_catch_signal(int code, cl_object action, cl_object process)
{
cl_object code_fixnum = MAKE_FIXNUM(code);
if (action == Cnil || action == @':ignore') {
mysignal(code, SIG_IGN);
return Ct;
} else if (action == @':default') {
mysignal(code, SIG_DFL);
return Ct;
} else if (action == @':mask' || action == @':unmask') {
#ifdef HAVE_SIGPROCMASK
# ifdef ECL_THREADS
/* When a process object is supplied, the changes take care
* on the process structure and will only take effect when
* the process is enabled. */
if (type_of(process) == t_process) {
cl_env_ptr env = process->process.env;
sigset_t *handled_set = (sigset_t *)env->default_sigmask;
if (action == @':mask') {
sigaddset(handled_set, code);
} else {
sigdelset(handled_set, code);
}
return Ct;
} else {
sigset_t handled_set;
pthread_sigmask(SIG_SETMASK, NULL, &handled_set);
if (action == @':mask') {
sigaddset(&handled_set, code);
} else {
sigdelset(&handled_set, code);
}
pthread_sigmask(SIG_SETMASK, &handled_set, NULL);
return Ct;
}
# else
{
sigset_t handled_set;
sigprocmask(SIG_SETMASK, NULL, &handled_set);
if (action == @':mask') {
sigaddset(&handled_set, code);
} else {
sigdelset(&handled_set, code);
}
sigprocmask(SIG_SETMASK, &handled_set, NULL);
return Ct;
}
# endif /* !ECL_THREADS */
#else /* !HAVE_SIGPROCMASK */
return Cnil;
#endif /* !HAVE_SIGPROCMASK */
} else if (action == Ct || action == @':catch') {
if (code == SIGSEGV) {
mysignal(code, sigsegv_handler);
}
#ifdef SIGBUS
else if (code == SIGBUS) {
mysignal(code, sigsegv_handler);
}
#endif
#ifdef SIGILL
else if (code == SIGILL) {
mysignal(SIGILL, evil_signal_handler);
}
#endif
#if defined(SIGCHLD) && defined(ECL_THREADS)
else if (code == SIGCHLD &&
ecl_option_values[ECL_OPT_SIGNAL_HANDLING_THREAD])
{
/* Do nothing. This is taken care of in
* the asynchronous signal handler. */
}
#endif
else {
mysignal(code, non_evil_signal_handler);
}
return Ct;
} else {
FEerror("Unknown 2nd argument to EXT:CATCH-SIGNAL: ~A", 1,
action);
}
}
cl_object
si_get_signal_handler(cl_object code)
{
cl_object handler = ecl_gethash_safe(code, cl_core.known_signals, OBJNULL);
unlikely_if (handler == OBJNULL) {
illegal_signal_code(code);
}
@(return handler)
}
cl_object
si_set_signal_handler(cl_object code, cl_object handler)
{
cl_object action = ecl_gethash_safe(code, cl_core.known_signals, OBJNULL);
unlikely_if (action == OBJNULL) {
illegal_signal_code(code);
}
ecl_sethash(code, cl_core.known_signals, handler);
si_catch_signal(2, code, Ct);
@(return handler)
}
@(defun ext::catch-signal (code flag &key process)
@
{
int code_int;
unlikely_if (ecl_gethash_safe(code, cl_core.known_signals, OBJNULL) == OBJNULL) {
illegal_signal_code(code);
}
code_int = fix(code);
#ifdef GBC_BOEHM
# ifdef SIGSEGV
unlikely_if ((code == MAKE_FIXNUM(SIGSEGV)) &&
ecl_option_values[ECL_OPT_INCREMENTAL_GC])
FEerror("It is not allowed to change the behavior of SIGSEGV.",
0);
# endif
# ifdef SIGBUS
unlikely_if (code_int == SIGBUS)
FEerror("It is not allowed to change the behavior of SIGBUS.",
0);
# endif
#endif
#if defined(ECL_THREADS) && !defined(ECL_MS_WINDOWS_HOST)
unlikely_if (code_int == ecl_option_values[ECL_OPT_THREAD_INTERRUPT_SIGNAL]) {
FEerror("It is not allowed to change the behavior of signal ~D", 1,
code);
}
#endif
#ifdef SIGFPE
unlikely_if (code_int == SIGFPE) {
FEerror("The signal handler for SIGPFE cannot be uninstalled. Use SI:TRAP-FPE instead.", 0);
}
#endif
@(return do_catch_signal(code_int, flag, process));
}
@)
#ifdef ECL_THREADS
# ifdef ECL_WINDOWS_THREADS
static VOID CALLBACK
wakeup_function(ULONG_PTR foo)
{
cl_env_ptr env = ecl_process_env();
volatile i = env->nvalues;
env->nvalues = i;
}
static VOID CALLBACK
wakeup_noop(ULONG_PTR foo)
{
}
# endif
static bool
do_interrupt_thread(cl_object process)
{
# ifdef ECL_WINDOWS_THREADS
# ifndef ECL_USE_GUARD_PAGE
# error "Cannot implement ecl_interrupt_process without guard pages"
# endif
HANDLE thread = (HANDLE)process->process.thread;
CONTEXT context;
void *trap_address = process->process.env;
DWORD guard = PAGE_GUARD | PAGE_READWRITE;
int ok = 1;
if (SuspendThread(thread) == (DWORD)-1) {
FEwin32_error("Unable to suspend thread ~A", 1,
process);
ok = 0;
goto EXIT;
}
process->process.interrupt = Ct;
if (!VirtualProtect(process->process.env,
sizeof(struct cl_env_struct),
guard,
&guard))
{
FEwin32_error("Unable to protect memory from thread ~A",
1, process);
ok = 0;
}
RESUME:
if (!QueueUserAPC(wakeup_function, thread, 0)) {
FEwin32_error("Unable to queue APC call to thread ~A",
1, process);
ok = 0;
}
if (ResumeThread(thread) == (DWORD)-1) {
FEwin32_error("Unable to resume thread ~A", 1,
process);
ok = 0;
goto EXIT;
}
EXIT:
return ok;
# else
int signal = ecl_option_values[ECL_OPT_THREAD_INTERRUPT_SIGNAL];
if (pthread_kill(process->process.thread, signal)) {
FElibc_error("Unable to interrupt process ~A", 1,
process);
}
return 1;
# endif
}
void
ecl_interrupt_process(cl_object process, cl_object function)
{
/*
* We first ensure that the process is active and running
* and past the initialization phase, where it has set up
* the environment. Then:
* - In Windows it sets up a trap in the stack, so that the
* uncaught exception handler can catch it and process it.
* - In POSIX systems it sends a user level interrupt to
* the thread, which then decides how to act.
*
* If FUNCTION is NIL, we just intend to wake up the process
* from some call to ecl_musleep() Queue the interrupt for any
* process stage that can potentially receive a signal */
if (!Null(function) &&
(process->process.phase >= ECL_PROCESS_BOOTING))
{
function = si_coerce_to_function(function);
queue_signal(process->process.env, function, 1);
}
/* ... but only deliver if the process is still alive */
if (process->process.phase == ECL_PROCESS_ACTIVE)
do_interrupt_thread(process);
}
void
ecl_wakeup_process(cl_object process)
{
# ifdef ECL_WINDOWS_THREADS
HANDLE thread = (HANDLE)process->process.thread;
if (!QueueUserAPC(wakeup_noop, thread, 0)) {
FEwin32_error("Unable to queue APC call to thread ~A",
1, process);
}
# else
do_interrupt_thread(process);
# endif
}
#endif /* ECL_THREADS */
#ifdef ECL_WINDOWS_THREADS
static LPTOP_LEVEL_EXCEPTION_FILTER old_W32_exception_filter = NULL;
LONG WINAPI
_ecl_w32_exception_filter(struct _EXCEPTION_POINTERS* ep)
{
LONG excpt_result;
cl_env_ptr the_env = ecl_process_env();
excpt_result = EXCEPTION_CONTINUE_EXECUTION;
switch (ep->ExceptionRecord->ExceptionCode)
{
/* Access to guard page */
case STATUS_GUARD_PAGE_VIOLATION: {
cl_object process = the_env->own_process;
if (!Null(process->process.interrupt)) {
cl_object signal = pop_signal(the_env);
process->process.interrupt = Cnil;
while (signal != Cnil && signal) {
handle_signal_now(signal, the_env->own_process);
signal = pop_signal(the_env);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
}
/* Catch all arithmetic exceptions */
case EXCEPTION_INT_DIVIDE_BY_ZERO:
handle_signal_now(@'division-by-zero', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_INT_OVERFLOW:
handle_signal_now(@'arithmetic-error', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
handle_signal_now(@'floating-point-overflow', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_OVERFLOW:
handle_signal_now(@'floating-point-overflow', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_UNDERFLOW:
handle_signal_now(@'floating-point-underflow', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_INEXACT_RESULT:
handle_signal_now(@'floating-point-inexact', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_DENORMAL_OPERAND:
case EXCEPTION_FLT_INVALID_OPERATION:
handle_signal_now(@'floating-point-invalid-operation', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_FLT_STACK_CHECK:
handle_signal_now(@'arithmetic-error', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
/* Catch segmentation fault */
case EXCEPTION_ACCESS_VIOLATION:
handle_signal_now(@'ext::segmentation-violation', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
/* Catch illegal instruction */
case EXCEPTION_ILLEGAL_INSTRUCTION:
handle_signal_now(@'ext::illegal-instruction', the_env->own_process);
return EXCEPTION_CONTINUE_EXECUTION;
/* Do not catch anything else */
default:
excpt_result = EXCEPTION_CONTINUE_SEARCH;
break;
}
if (old_W32_exception_filter)
return old_W32_exception_filter(ep);
return excpt_result;
}
static cl_object
W32_handle_in_new_thread(cl_object signal_code)
{
int outside_ecl = ecl_import_current_thread(@'si::handle-signal', Cnil);
mp_process_run_function(4, @'si::handle-signal',
@'si::handle-signal',
signal_code, Cnil);
if (outside_ecl) ecl_release_current_thread();
}
BOOL WINAPI W32_console_ctrl_handler(DWORD type)
{
switch (type)
{
/* Catch CTRL-C */
case CTRL_C_EVENT: {
cl_object function = SYM_FUN(@'si::terminal-interrupt');
if (function)
W32_handle_in_new_thread(function);
return TRUE;
}
}
return FALSE;
}
#endif /* ECL_WINDOWS_THREADS */
#if 0
static cl_object
asynchronous_signal_servicing_thread()
{
const cl_env_ptr the_env = ecl_process_env();
sigset_t handled_set;
cl_object signal_code;
int signo;
int interrupt_signal = 0;
if (ecl_option_values[ECL_OPT_TRAP_INTERRUPT_SIGNAL]) {
interrupt_signal = ecl_option_values[ECL_OPT_THREAD_INTERRUPT_SIGNAL];
}
/*
* We wait here for all signals that are blocked in all other
* threads. It would be desirable to be able to wait for _all_
* signals, but this can not be done for SIGFPE, SIGSEGV, etc.
*/
pthread_sigmask(SIG_SETMASK, NULL, &handled_set);
/*
* Under OS X we also have to explicitely add the signal we
* use to communicate process interrupts. For some unknown
* reason those signals may get lost.
*/
if (interrupt_signal) {
sigaddset(&handled_set, interrupt_signal);
pthread_sigmask(SIG_SETMASK, &handled_set, NULL);
}
CL_CATCH_ALL_BEGIN(the_env) {
for (;;) {
/* Waiting may fail! */
int status = sigwait(&handled_set, &signo);
if (status == 0) {
#if 0
if (signo == interrupt_signal) {
/* If we get this signal it may be because
* of two reasons. One is that it is just
* an awake message. Then the queue is empty
* and we continue ... */
signal_code = pop_signal(the_env);
if (Null(signal_code))
continue;
/* ... the other one is that we are being
* interrupted, but this only happens when
* we quit */
break;
}
#else
if (signo == interrupt_signal) {
break;
}
#endif
#ifdef SIGCHLD
if (signo == SIGCHLD) {
si_wait_for_all_processes();
continue;
}
#endif
signal_code = ecl_gethash_safe(MAKE_FIXNUM(signo),
cl_core.known_signals,
Cnil);
if (!Null(signal_code)) {
mp_process_run_function(3, @'si::handle-signal',
@'si::handle-signal',
signal_code);
}
}
}
} CL_CATCH_ALL_END;
ecl_return0(the_env);
}
#endif
cl_object
si_trap_fpe(cl_object condition, cl_object flag)
{
cl_env_ptr the_env = ecl_process_env();
#ifndef FE_ALL_EXCEPT
# define FE_ALL_EXCEPT FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID
#endif
const int all = FE_ALL_EXCEPT;
int bits = 0;
if (condition == @'last') {
bits = the_env->trap_fpe_bits;
} else {
if (condition == Ct)
bits = FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID;
else if (condition == @'division-by-zero')
bits = FE_DIVBYZERO;
else if (condition == @'floating-point-overflow')
bits = FE_OVERFLOW;
else if (condition == @'floating-point-underflow')
bits = FE_UNDERFLOW;
else if (condition == @'floating-point-invalid-operation')
bits = FE_INVALID;
else if (condition == @'floating-point-inexact')
bits = FE_INEXACT;
else if (FIXNUMP(condition))
bits = fix(condition) & all;
if (flag == Cnil) {
bits = the_env->trap_fpe_bits & ~bits;
} else {
bits = the_env->trap_fpe_bits | bits;
}
}
#if !defined(ECL_AVOID_FPE_H)
# ifdef HAVE_FENV_H
feclearexcept(all);
# endif
# if defined(ECL_MS_WINDOWS_HOST)
_fpreset();
# endif
# ifdef HAVE_FEENABLEEXCEPT
fedisableexcept(all & ~bits);
feenableexcept(all & bits);
# endif
#endif
the_env->trap_fpe_bits = bits;
@(return MAKE_FIXNUM(bits))
}
/*
* In this code we decide whether to install a process-wide signal
* handler for each of the asynchronous signals (SIGINT, SIGTERM,
* SIGCHLD...) or we block the signal and let the background thread
* detect and process them.
*/
static void
install_asynchronous_signal_handlers()
{
#if defined(ECL_MS_WINDOWS_HOST)
# define async_handler(signal,handler,mask)
#else
# if defined(ECL_THREADS) && defined(HAVE_SIGPROCMASK)
# define async_handler(signal,handler,mask) { \
if (ecl_option_values[ECL_OPT_SIGNAL_HANDLING_THREAD]) { \
mysignal(signal, deferred_signal_handler); \
} else { \
mysignal(signal,handler); \
}}
# else
# define async_handler(signal,handler,mask) \
mysignal(signal,handler)
# endif
#endif
#ifdef HAVE_SIGPROCMASK
sigset_t *sigmask = cl_core.default_sigmask = &main_thread_sigmask;
cl_core.default_sigmask_bytes = sizeof(sigset_t);
# ifdef ECL_THREADS
pthread_sigmask(SIG_SETMASK, NULL, sigmask);
# else
sigprocmask(SIG_SETMASK, NULL, sigmask);
# endif
#endif
#ifdef SIGINT
if (ecl_option_values[ECL_OPT_TRAP_SIGINT]) {
async_handler(SIGINT, non_evil_signal_handler, sigmask);
}
#endif
#ifdef SIGCHLD
if (ecl_option_values[ECL_OPT_TRAP_SIGCHLD]) {
/* We have to set the process signal handler explicitly,
* because on many platforms the default is SIG_IGN. */
mysignal(SIGCHLD, non_evil_signal_handler);
async_handler(SIGCHLD, non_evil_signal_handler, sigmask);
}
#endif
#ifdef HAVE_SIGPROCMASK
# if defined(ECL_THREADS)
pthread_sigmask(SIG_SETMASK, sigmask, NULL);
# else
sigprocmask(SIG_SETMASK, sigmask, NULL);
# endif
#endif
#ifdef ECL_WINDOWS_THREADS
old_W32_exception_filter =
SetUnhandledExceptionFilter(_ecl_w32_exception_filter);
if (ecl_option_values[ECL_OPT_TRAP_SIGINT]) {
SetConsoleCtrlHandler(W32_console_ctrl_handler, TRUE);
}
#endif
#undef async_handler
}
/*
* In POSIX systems we may set up a background thread that detects
* synchronous signals and spawns a new thread to handle each of them.
*/
static void
install_signal_handling_thread()
{
#if defined(ECL_THREADS) && defined(HAVE_SIGPROCMASK)
ecl_process_env()->default_sigmask = &main_thread_sigmask;
if (ecl_option_values[ECL_OPT_SIGNAL_HANDLING_THREAD]) {
cl_object fun =
ecl_make_cfun((cl_objectfn_fixed)
asynchronous_signal_servicing_thread,
@'si::signal-servicing',
Cnil,
0);
cl_object process =
signal_thread_process =
mp_process_run_function_wait(2,
@'si::signal-servicing',
fun);
if (Null(process)) {
ecl_internal_error("Unable to create signal "
"servicing thread");
}
}
#endif
}
/*
* This routine sets up handlers for all exceptions, such as access to
* restricted regions of memory. They have to be set up before we call
* init_GC().
*/
static void
install_synchronous_signal_handlers()
{
#ifdef SIGBUS
if (ecl_option_values[ECL_OPT_TRAP_SIGBUS]) {
do_catch_signal(SIGBUS, Ct, Cnil);
}
#endif
#ifdef SIGSEGV
if (ecl_option_values[ECL_OPT_TRAP_SIGSEGV]) {
do_catch_signal(SIGSEGV, Ct, Cnil);
}
#endif
#ifdef SIGPIPE
if (ecl_option_values[ECL_OPT_TRAP_SIGPIPE]) {
do_catch_signal(SIGPIPE, Ct, Cnil);
}
#endif
#ifdef SIGILL
if (ecl_option_values[ECL_OPT_TRAP_SIGILL]) {
do_catch_signal(SIGILL, Ct, Cnil);
}
#endif
/* In order to implement MP:INTERRUPT-PROCESS, MP:PROCESS-KILL
* and the like, we use signals. This sets up a synchronous
* signal handler for that particular signal.
*/
#ifdef SIGRTMIN
# define DEFAULT_THREAD_INTERRUPT_SIGNAL SIGRTMIN + 2
#else
# define DEFAULT_THREAD_INTERRUPT_SIGNAL SIGUSR1
#endif
#if defined(ECL_THREADS) && !defined(ECL_MS_WINDOWS_HOST)
if (ecl_option_values[ECL_OPT_TRAP_INTERRUPT_SIGNAL]) {
int signal = ecl_option_values[ECL_OPT_THREAD_INTERRUPT_SIGNAL];
if (signal == 0) {
signal = DEFAULT_THREAD_INTERRUPT_SIGNAL;
ecl_set_option(ECL_OPT_THREAD_INTERRUPT_SIGNAL,
signal);
}
mysignal(signal, process_interrupt_handler);
#ifdef HAVE_SIGROCMASK
sigdelset(ecl_process_env()->default_sigmask, signal);
pthread_sigmask(SIG_SETMASK, ecl_process_env()->default_sigmask, NULL);
#endif
}
#endif
}
/*
* This routine sets up handlers for floating point exceptions. We
* cannot do it earlier because it requires the memory allocator to
* be set up.
*/
static void
install_fpe_signal_handlers()
{
#ifdef SIGFPE
if (ecl_option_values[ECL_OPT_TRAP_SIGFPE]) {
mysignal(SIGFPE, fpe_signal_handler);
si_trap_fpe(Ct, Ct);
# ifdef ECL_IEEE_FP
/* By default deactivate errors and accept
* denormals in floating point computations */
si_trap_fpe(@'floating-point-invalid-operation', Cnil);
si_trap_fpe(@'division-by-zero', Cnil);
si_trap_fpe(@'floating-point-overflow', Cnil);
# endif
}
#endif
}
/*
* Create one Common Lisp constant for each signal that we know,
* such as +SIGINT+ for SIGINT, etc.
*/
static void
add_one_signal(cl_object hash_table, int signal, cl_object name, cl_object handler)
{
cl_object code = MAKE_FIXNUM(signal);
cl_export2(name, cl_core.ext_package);
si_Xmake_constant(name, code);
ecl_sethash(code, hash_table, handler);
}
static void
create_signal_code_constants()
{
cl_object hash =
cl_core.known_signals =
cl__make_hash_table(@'eql', MAKE_FIXNUM(128),
cl_core.rehash_size,
cl_core.rehash_threshold);
int i;
for (i = 0; known_signals[i].code >= 0; i++) {
add_one_signal(hash, known_signals[i].code,
_ecl_intern(known_signals[i].name,
cl_core.ext_package),
known_signals[i].handler);
}
#ifdef SIGRTMIN
for (i = SIGRTMIN; i <= SIGRTMAX; i++) {
int intern_flag[1];
char buffer[64];
cl_object name;
sprintf(buffer, "+SIGRT%d+", i-SIGRTMIN);
name = ecl_intern(make_base_string_copy(buffer),
cl_core.ext_package,
intern_flag);
add_one_signal(hash, i, name, Cnil);
}
add_one_signal(hash, SIGRTMIN,
_ecl_intern("+SIGRTMIN+", cl_core.ext_package),
Cnil);
add_one_signal(hash, SIGRTMAX,
_ecl_intern("+SIGRTMAX+", cl_core.ext_package),
Cnil);
#endif
}
void
init_unixint(int pass)
{
if (pass == 0) {
install_asynchronous_signal_handlers();
install_synchronous_signal_handlers();
} else {
create_signal_code_constants();
install_fpe_signal_handlers();
install_signal_handling_thread();
ECL_SET(@'ext::*interrupts-enabled*', Ct);
ecl_process_env()->disable_interrupts = 0;
}
}