diff options
Diffstat (limited to 'signal.c')
| -rw-r--r-- | signal.c | 1787 |
1 files changed, 1143 insertions, 644 deletions
@@ -3,34 +3,76 @@ signal.c - $Author$ - $Date$ created at: Tue Dec 20 10:13:44 JST 1994 - Copyright (C) 1993-2003 Yukihiro Matsumoto + Copyright (C) 1993-2007 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ -#include "ruby.h" -#include "rubysig.h" +#include "ruby/internal/config.h" + +#include <errno.h> #include <signal.h> #include <stdio.h> -#ifdef __BEOS__ -#undef SIGBUS +#ifdef HAVE_UNISTD_H +# include <unistd.h> #endif -#ifndef NSIG -# ifdef DJGPP -# define NSIG SIGMAX -# else -# define NSIG (_SIGMAX + 1) /* For QNX */ -# endif +#ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +#endif + +#ifdef HAVE_UCONTEXT_H +# include <ucontext.h> +#endif + +#ifdef HAVE_PTHREAD_H +# include <pthread.h> #endif -static struct signals { - char *signm; +#include "debug_counter.h" +#include "eval_intern.h" +#include "internal.h" +#include "internal/error.h" +#include "internal/eval.h" +#include "internal/sanitizers.h" +#include "internal/signal.h" +#include "internal/string.h" +#include "internal/thread.h" +#include "ruby_atomic.h" +#include "vm_core.h" +#include "ractor_core.h" +#include "ruby/internal/attr/nonstring.h" + +#ifdef NEED_RUBY_ATOMIC_OPS +rb_atomic_t +ruby_atomic_exchange(rb_atomic_t *ptr, rb_atomic_t val) +{ + rb_atomic_t old = *ptr; + *ptr = val; + return old; +} + +rb_atomic_t +ruby_atomic_compare_and_swap(rb_atomic_t *ptr, rb_atomic_t cmp, + rb_atomic_t newval) +{ + rb_atomic_t old = *ptr; + if (old == cmp) { + *ptr = newval; + } + return old; +} +#endif + +#define FOREACH_SIGNAL(sig, offset) \ + for (sig = siglist + (offset); sig < siglist + numberof(siglist); ++sig) +enum { LONGEST_SIGNAME = 7 }; /* MIGRATE and RETRACT */ +static const struct signals { + char signm[LONGEST_SIGNAME + 1]; int signo; } siglist [] = { {"EXIT", 0}, @@ -47,12 +89,12 @@ static struct signals { #ifdef SIGTRAP {"TRAP", SIGTRAP}, #endif -#ifdef SIGIOT - {"IOT", SIGIOT}, -#endif #ifdef SIGABRT {"ABRT", SIGABRT}, #endif +#ifdef SIGIOT + {"IOT", SIGIOT}, +#endif #ifdef SIGEMT {"EMT", SIGEMT}, #endif @@ -92,15 +134,9 @@ static struct signals { #ifdef SIGCONT {"CONT", SIGCONT}, #endif -#ifdef SIGCHLD - {"CHLD", SIGCHLD}, -#endif -#ifdef SIGCLD - {"CLD", SIGCLD}, -#else -# ifdef SIGCHLD - {"CLD", SIGCHLD}, -# endif +#ifdef RUBY_SIGCHLD + {"CHLD", RUBY_SIGCHLD }, + {"CLD", RUBY_SIGCHLD }, #endif #ifdef SIGTTIN {"TTIN", SIGTTIN}, @@ -165,862 +201,1297 @@ static struct signals { #ifdef SIGINFO {"INFO", SIGINFO}, #endif - {NULL, 0} }; +static const char signame_prefix[] = "SIG"; +static const int signame_prefix_len = 3; + static int -signm2signo(nm) - const char *nm; +signm2signo(VALUE *sig_ptr, int negative, int exit, int *prefix_ptr) { - struct signals *sigs; + const struct signals *sigs; + VALUE vsig = *sig_ptr; + const char *nm; + long len, nmlen; + int prefix = 0; - for (sigs = siglist; sigs->signm; sigs++) - if (strcmp(sigs->signm, nm) == 0) - return sigs->signo; - return 0; + if (RB_SYMBOL_P(vsig)) { + *sig_ptr = vsig = rb_sym2str(vsig); + } + else if (!RB_TYPE_P(vsig, T_STRING)) { + VALUE str = rb_check_string_type(vsig); + if (NIL_P(str)) { + rb_raise(rb_eArgError, "bad signal type %s", + rb_obj_classname(vsig)); + } + *sig_ptr = vsig = str; + } + + rb_must_asciicompat(vsig); + RSTRING_GETMEM(vsig, nm, len); + if (memchr(nm, '\0', len)) { + rb_raise(rb_eArgError, "signal name with null byte"); + } + + if (len > 0 && nm[0] == '-') { + if (!negative) + rb_raise(rb_eArgError, "negative signal name: % "PRIsVALUE, vsig); + prefix = 1; + } + else { + negative = 0; + } + if (len >= prefix + signame_prefix_len) { + if (memcmp(nm + prefix, signame_prefix, signame_prefix_len) == 0) + prefix += signame_prefix_len; + } + if (len <= (long)prefix) { + goto unsupported; + } + + if (prefix_ptr) *prefix_ptr = prefix; + nmlen = len - prefix; + nm += prefix; + if (nmlen > LONGEST_SIGNAME) goto unsupported; + FOREACH_SIGNAL(sigs, !exit) { + if (memcmp(sigs->signm, nm, nmlen) == 0 && + sigs->signm[nmlen] == '\0') { + return negative ? -sigs->signo : sigs->signo; + } + } + + unsupported: + if (prefix == signame_prefix_len) { + prefix = 0; + } + else if (prefix > signame_prefix_len) { + prefix -= signame_prefix_len; + len -= prefix; + vsig = rb_str_subseq(vsig, prefix, len); + prefix = 0; + } + else { + len -= prefix; + vsig = rb_str_subseq(vsig, prefix, len); + prefix = signame_prefix_len; + } + rb_raise(rb_eArgError, "unsupported signal '%.*s%"PRIsVALUE"'", + prefix, signame_prefix, vsig); + UNREACHABLE_RETURN(0); } -static char* -signo2signm(no) - int no; +static const char* +signo2signm(int no) { - struct signals *sigs; + const struct signals *sigs; - for (sigs = siglist; sigs->signm; sigs++) - if (sigs->signo == no) - return sigs->signm; + FOREACH_SIGNAL(sigs, 0) { + if (sigs->signo == no) + return sigs->signm; + } return 0; } +/* + * call-seq: + * Signal.signame(signo) -> string or nil + * + * Convert signal number to signal name. + * Returns +nil+ if the signo is an invalid signal number. + * + * Signal.trap("INT") { |signo| puts Signal.signame(signo) } + * Process.kill("INT", 0) + * + * <em>produces:</em> + * + * INT + */ +static VALUE +sig_signame(VALUE recv, VALUE signo) +{ + const char *signame = signo2signm(NUM2INT(signo)); + if (!signame) return Qnil; + return rb_str_new_cstr(signame); +} + const char * -ruby_signal_name(no) - int no; +ruby_signal_name(int no) { return signo2signm(no); } +static VALUE +rb_signo2signm(int signo) +{ + const char *const signm = signo2signm(signo); + if (signm) { + return rb_sprintf("SIG%s", signm); + } + else { + return rb_sprintf("SIG%u", signo); + } +} + /* * call-seq: - * SignalException.new(sig) => signal_exception + * SignalException.new(sig_name) -> signal_exception + * SignalException.new(sig_number [, name]) -> signal_exception * - * Construct a new SignalException object. +sig+ should be a known - * signal name, or a signal number. + * Construct a new SignalException object. +sig_name+ should be a known + * signal name. */ static VALUE -esignal_init(argc, argv, self) - int argc; - VALUE *argv; - VALUE self; +esignal_init(int argc, VALUE *argv, VALUE self) { int argnum = 1; VALUE sig = Qnil; int signo; - const char *signm; - char tmpnm[(sizeof(int)*CHAR_BIT)/3+4]; if (argc > 0) { - sig = argv[0]; - if (FIXNUM_P(sig)) argnum = 2; - } - if (argc < 1 || argnum < argc) { - rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", - argc, argnum); + sig = rb_check_to_integer(argv[0], "to_int"); + if (!NIL_P(sig)) argnum = 2; + else sig = argv[0]; } + rb_check_arity(argc, 1, argnum); if (argnum == 2) { - signo = FIX2INT(sig); - if (signo < 0 || signo > NSIG) { - rb_raise(rb_eArgError, "invalid signal number (%d)", signo); - } - if (argc > 1) { - sig = argv[1]; - } - else { - signm = signo2signm(signo); - if (signm) { - snprintf(tmpnm, sizeof(tmpnm), "SIG%s", signm); - } - else { - snprintf(tmpnm, sizeof(tmpnm), "SIG%u", signo); - } - sig = rb_str_new2(signm = tmpnm); - } + signo = NUM2INT(sig); + if (signo < 0 || signo > NSIG) { + rb_raise(rb_eArgError, "invalid signal number (%d)", signo); + } + if (argc > 1) { + sig = argv[1]; + } + else { + sig = rb_signo2signm(signo); + } } else { - signm = SYMBOL_P(sig) ? rb_id2name(SYM2ID(sig)) : StringValuePtr(sig); - if (strncmp(signm, "SIG", 3) == 0) signm += 3; - signo = signm2signo(signm); - if (!signo) { - rb_raise(rb_eArgError, "unsupported name `SIG%s'", signm); - } - if (SYMBOL_P(sig)) { - sig = rb_str_new2(signm); - } + int prefix; + signo = signm2signo(&sig, FALSE, FALSE, &prefix); + if (prefix != signame_prefix_len) { + sig = rb_str_append(rb_str_new_cstr("SIG"), sig); + } } rb_call_super(1, &sig); - rb_iv_set(self, "signo", INT2NUM(signo)); + rb_ivar_set(self, id_signo, INT2NUM(signo)); return self; } +/* + * call-seq: + * signal_exception.signo -> num + * + * Returns a signal number. + */ + +static VALUE +esignal_signo(VALUE self) +{ + return rb_ivar_get(self, id_signo); +} + +/* :nodoc: */ static VALUE -interrupt_init(self, mesg) - VALUE self, mesg; +interrupt_init(int argc, VALUE *argv, VALUE self) { - VALUE argv[2]; + VALUE args[2]; - argv[0] = INT2FIX(SIGINT); - argv[1] = mesg; - return rb_call_super(2, argv); + args[0] = INT2FIX(SIGINT); + args[1] = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil; + return rb_call_super(2, args); } +#if defined(USE_SIGALTSTACK) || defined(_WIN32) +static void reset_sigmask(int sig); +#endif + void -ruby_default_signal(sig) - int sig; +ruby_default_signal(int sig) { -#ifndef MACOS_UNUSE_SIGNAL - extern rb_pid_t getpid _((void)); +#if USE_DEBUG_COUNTER + rb_debug_counter_show_results("killed by signal."); +#endif signal(sig, SIG_DFL); - kill(getpid(), sig); +#if defined(USE_SIGALTSTACK) || defined(_WIN32) + reset_sigmask(sig); #endif + raise(sig); } -/* - * call-seq: - * Process.kill(signal, pid, ...) => fixnum - * - * Sends the given signal to the specified process id(s), or to the - * current process if _pid_ is zero. _signal_ may be an - * integer signal number or a POSIX signal name (either with or without - * a +SIG+ prefix). If _signal_ is negative (or starts - * with a minus sign), kills process groups instead of - * processes. Not all signals are available on all platforms. - * - * pid = fork do - * Signal.trap("HUP") { puts "Ouch!"; exit } - * # ... do some work ... - * end - * # ... - * Process.kill("HUP", pid) - * Process.wait - * - * <em>produces:</em> - * - * Ouch! - */ +static void sighandler(int sig); +static int signal_ignored(int sig); +static void signal_enque(int sig); VALUE -rb_f_kill(argc, argv) - int argc; - VALUE *argv; +rb_f_kill(int argc, const VALUE *argv) { - int negative = 0; +#ifndef HAVE_KILLPG +#define killpg(pg, sig) kill(-(pg), (sig)) +#endif int sig; int i; - char *s; - - rb_secure(2); - if (argc < 2) - rb_raise(rb_eArgError, "wrong number of arguments -- kill(sig, pid...)"); - switch (TYPE(argv[0])) { - case T_FIXNUM: - sig = FIX2INT(argv[0]); - break; - - case T_SYMBOL: - s = rb_id2name(SYM2ID(argv[0])); - if (!s) rb_raise(rb_eArgError, "bad signal"); - goto str_signal; - - case T_STRING: - s = RSTRING(argv[0])->ptr; - if (s[0] == '-') { - negative++; - s++; - } - str_signal: - if (strncmp("SIG", s, 3) == 0) - s += 3; - if((sig = signm2signo(s)) == 0) - rb_raise(rb_eArgError, "unsupported name `SIG%s'", s); - - if (negative) - sig = -sig; - break; + VALUE str; - default: - { - VALUE str; - - str = rb_check_string_type(argv[0]); - if (!NIL_P(str)) { - s = RSTRING(str)->ptr; - goto str_signal; - } - rb_raise(rb_eArgError, "bad signal type %s", - rb_obj_classname(argv[0])); - } - break; + rb_check_arity(argc, 2, UNLIMITED_ARGUMENTS); + + if (FIXNUM_P(argv[0])) { + sig = FIX2INT(argv[0]); } + else { + str = argv[0]; + sig = signm2signo(&str, TRUE, FALSE, NULL); + } + + if (argc <= 1) return INT2FIX(0); if (sig < 0) { - sig = -sig; - for (i=1; i<argc; i++) { - int pid = NUM2INT(argv[i]); -#ifdef HAS_KILLPG - if (killpg(pid, sig) < 0) -#else - if (kill(-pid, sig) < 0) -#endif - rb_sys_fail(0); - } + sig = -sig; + for (i=1; i<argc; i++) { + if (killpg(NUM2PIDT(argv[i]), sig) < 0) + rb_sys_fail(0); + } } else { - for (i=1; i<argc; i++) { - Check_Type(argv[i], T_FIXNUM); - if (kill(FIX2INT(argv[i]), sig) < 0) - rb_sys_fail(0); - } + const rb_pid_t self = (GET_THREAD() == GET_VM()->ractor.main_thread) ? getpid() : -1; + int wakeup = 0; + + for (i=1; i<argc; i++) { + rb_pid_t pid = NUM2PIDT(argv[i]); + + if ((sig != 0) && (self != -1) && (pid == self)) { + int t; + /* + * When target pid is self, many caller assume signal will be + * delivered immediately and synchronously. + */ + switch (sig) { + case SIGSEGV: +#ifdef SIGBUS + case SIGBUS: +#endif +#ifdef SIGKILL + case SIGKILL: +#endif +#ifdef SIGILL + case SIGILL: +#endif +#ifdef SIGFPE + case SIGFPE: +#endif +#ifdef SIGSTOP + case SIGSTOP: +#endif + kill(pid, sig); + break; + default: + t = signal_ignored(sig); + if (t) { + if (t < 0 && kill(pid, sig)) + rb_sys_fail(0); + break; + } + signal_enque(sig); + wakeup = 1; + } + } + else if (kill(pid, sig) < 0) { + rb_sys_fail(0); + } + } + if (wakeup) { + rb_threadptr_check_signal(GET_VM()->ractor.main_thread); + } } + rb_thread_execute_interrupts(rb_thread_current()); + return INT2FIX(i-1); } static struct { - VALUE cmd; - int safe; -} trap_list[NSIG]; -static rb_atomic_t trap_pending_list[NSIG]; -static char rb_trap_accept_nativethreads[NSIG]; -rb_atomic_t rb_trap_pending; -rb_atomic_t rb_trap_immediate; -int rb_prohibit_interrupt = 1; + rb_atomic_t cnt[RUBY_NSIG]; + rb_atomic_t size; +} signal_buff; -void -rb_gc_mark_trap_list() +#define sighandler_t ruby_sighandler_t + +#ifdef USE_SIGALTSTACK +typedef void ruby_sigaction_t(int, siginfo_t*, void*); +#define SIGINFO_ARG , siginfo_t *info, void *ctx +#define SIGINFO_CTX ctx +#else +typedef void ruby_sigaction_t(int); +#define SIGINFO_ARG +#define SIGINFO_CTX 0 +#endif + +#ifdef USE_SIGALTSTACK +/* XXX: BSD_vfprintf() uses >1500B stack and x86-64 need >5KiB stack. */ +#define RUBY_SIGALTSTACK_SIZE (16*1024) + +static int +rb_sigaltstack_size(void) { -#ifndef MACOS_UNUSE_SIGNAL - int i; + int size = RUBY_SIGALTSTACK_SIZE; - for (i=0; i<NSIG; i++) { - if (trap_list[i].cmd) - rb_gc_mark(trap_list[i].cmd); +#ifdef MINSIGSTKSZ + { + int minsigstksz = (int)MINSIGSTKSZ; + if (size < minsigstksz) + size = minsigstksz; + } +#endif +#if defined(HAVE_SYSCONF) && defined(_SC_PAGE_SIZE) + { + int pagesize; + pagesize = (int)sysconf(_SC_PAGE_SIZE); + if (size < pagesize) + size = pagesize; } -#endif /* MACOS_UNUSE_SIGNAL */ +#endif + + return size; } -#ifdef __dietlibc__ -#define sighandler_t sh_t -#endif +static int rb_sigaltstack_size_value = 0; -typedef RETSIGTYPE (*sighandler_t)_((int)); +void * +rb_allocate_sigaltstack(void) +{ + void *altstack; + if (!rb_sigaltstack_size_value) { + rb_sigaltstack_size_value = rb_sigaltstack_size(); + } + altstack = malloc(rb_sigaltstack_size_value); + if (!altstack) rb_memerror(); + return altstack; +} + +/* alternate stack for SIGSEGV */ +void * +rb_register_sigaltstack(void *altstack) +{ + stack_t newSS, oldSS; + + newSS.ss_size = rb_sigaltstack_size_value; + newSS.ss_sp = altstack; + newSS.ss_flags = 0; + + sigaltstack(&newSS, &oldSS); /* ignore error. */ + + return newSS.ss_sp; +} +#endif /* USE_SIGALTSTACK */ #ifdef POSIX_SIGNAL static sighandler_t -ruby_signal(signum, handler) - int signum; - sighandler_t handler; +ruby_signal(int signum, sighandler_t handler) { struct sigaction sigact, old; +#if 0 rb_trap_accept_nativethreads[signum] = 0; +#endif - sigact.sa_handler = handler; sigemptyset(&sigact.sa_mask); +#if defined(USE_SIGALTSTACK) && !defined(__wasm__) + if (handler == SIG_IGN || handler == SIG_DFL) { + sigact.sa_handler = handler; + sigact.sa_flags = 0; + } + else { + sigact.sa_sigaction = (ruby_sigaction_t*)handler; + sigact.sa_flags = SA_SIGINFO; + } +#else + sigact.sa_handler = handler; sigact.sa_flags = 0; -# ifdef SA_NOCLDWAIT - if (signum == SIGCHLD && handler == SIG_IGN) - sigact.sa_flags |= SA_NOCLDWAIT; -# endif - sigaction(signum, &sigact, &old); - return old.sa_handler; +#endif + + switch (signum) { +#if defined(SA_ONSTACK) && defined(USE_SIGALTSTACK) + case SIGSEGV: +#ifdef SIGBUS + case SIGBUS: +#endif + sigact.sa_flags |= SA_ONSTACK; + break; +#endif + } + (void)VALGRIND_MAKE_MEM_DEFINED(&old, sizeof(old)); + if (sigaction(signum, &sigact, &old) < 0) { + return SIG_ERR; + } + if (old.sa_flags & SA_SIGINFO) + handler = (sighandler_t)old.sa_sigaction; + else + handler = old.sa_handler; + ASSUME(handler != SIG_ERR); + return handler; } -void -posix_signal(signum, handler) - int signum; - sighandler_t handler; +sighandler_t +ruby_posix_signal(int signum, sighandler_t handler) +{ + return ruby_signal(signum, handler); +} + +#elif defined _WIN32 +static inline sighandler_t +ruby_signal(int signum, sighandler_t handler) { - ruby_signal(signum, handler); + if (signum == SIGKILL) { + errno = EINVAL; + return SIG_ERR; + } + return signal(signum, handler); } -# ifdef HAVE_NATIVETHREAD +#else /* !POSIX_SIGNAL */ +#define ruby_signal(sig,handler) (/* rb_trap_accept_nativethreads[(sig)] = 0,*/ signal((sig),(handler))) +#if 0 /* def HAVE_NATIVETHREAD */ static sighandler_t -ruby_nativethread_signal(signum, handler) - int signum; - sighandler_t handler; +ruby_nativethread_signal(int signum, sighandler_t handler) { sighandler_t old; - old = ruby_signal(signum, handler); + old = signal(signum, handler); rb_trap_accept_nativethreads[signum] = 1; return old; } +#endif +#endif -void -posix_nativethread_signal(signum, handler) - int signum; - sighandler_t handler; +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) +static rb_nativethread_lock_t sig_check_lock; +#endif + +static int +signal_ignored(int sig) { - ruby_nativethread_signal(signum, handler); + sighandler_t func; +#ifdef POSIX_SIGNAL + struct sigaction old; + (void)VALGRIND_MAKE_MEM_DEFINED(&old, sizeof(old)); + if (sigaction(sig, NULL, &old) < 0) return FALSE; + func = old.sa_handler; +#elif defined SIG_GET + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants + // SIG_GET: Returns the current value of the signal. + func = signal(sig, SIG_GET); +#else + sighandler_t old; + rb_native_mutex_lock(&sig_check_lock); + old = signal(sig, SIG_DFL); + signal(sig, old); + rb_native_mutex_unlock(&sig_check_lock); + func = old; +#endif + if (func == SIG_IGN) return 1; + return func == sighandler ? 0 : -1; } -# endif -#else /* !POSIX_SIGNAL */ -#define ruby_signal(sig,handler) (rb_trap_accept_nativethreads[sig] = 0, signal((sig),(handler))) +static void +signal_enque(int sig) +{ + ATOMIC_INC(signal_buff.cnt[sig]); + ATOMIC_INC(signal_buff.size); +} -# ifdef HAVE_NATIVETHREAD -static sighandler_t -ruby_nativethread_signal(signum, handler) - int signum; - sighandler_t handler; +static void +sighandler(int sig) { - sighandler_t old; + int old_errnum = errno; - old = signal(signum, handler); - rb_trap_accept_nativethreads[signum] = 1; - return old; + signal_enque(sig); + rb_thread_wakeup_timer_thread(sig); + +#if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) + ruby_signal(sig, sighandler); +#endif + + errno = old_errnum; +} + +int +rb_signal_buff_size(void) +{ + return RUBY_ATOMIC_LOAD(signal_buff.size); } -# endif -#endif /* POSIX_SIGNAL */ -static void signal_exec _((int sig)); static void -signal_exec(sig) - int sig; +rb_disable_interrupt(void) { - if (trap_list[sig].cmd == 0) { - switch (sig) { - case SIGINT: - rb_thread_interrupt(); - break; -#ifdef SIGHUP - case SIGHUP: -#endif -#ifdef SIGQUIT - case SIGQUIT: -#endif -#ifdef SIGTERM - case SIGTERM: +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t mask; + sigfillset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); #endif -#ifdef SIGALRM - case SIGALRM: +} + +static void +rb_enable_interrupt(void) +{ +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); #endif -#ifdef SIGUSR1 - case SIGUSR1: +} + +int +rb_get_next_signal(void) +{ + int i, sig = 0; + + if (rb_signal_buff_size() != 0) { + for (i=1; i<RUBY_NSIG; i++) { + if (signal_buff.cnt[i] > 0) { + ATOMIC_DEC(signal_buff.cnt[i]); + ATOMIC_DEC(signal_buff.size); + sig = i; + break; + } + } + } + return sig; +} + +#if defined SIGSEGV || defined SIGBUS || defined SIGILL || defined SIGFPE +static const char *received_signal; +# define clear_received_signal() do { \ + if (GET_VM() != NULL) rb_gc_enable(); \ + received_signal = 0; \ +} while (0) +#else +# define clear_received_signal() ((void)0) +#endif + +#if defined(USE_SIGALTSTACK) || defined(_WIN32) +# if defined __HAIKU__ +# define USE_UCONTEXT_REG 1 +# elif !(defined(HAVE_UCONTEXT_H) && (defined __i386__ || defined __x86_64__ || defined __amd64__)) +# elif defined __linux__ +# define USE_UCONTEXT_REG 1 +# elif defined __APPLE__ +# define USE_UCONTEXT_REG 1 +# elif defined __FreeBSD__ +# define USE_UCONTEXT_REG 1 +# endif +#if defined(HAVE_PTHREAD_SIGMASK) +# define ruby_sigunmask pthread_sigmask +#elif defined(HAVE_SIGPROCMASK) +# define ruby_sigunmask sigprocmask #endif -#ifdef SIGUSR2 - case SIGUSR2: +static void +reset_sigmask(int sig) +{ +#if defined(ruby_sigunmask) + sigset_t mask; #endif - rb_thread_signal_raise(sig); - break; - } - } - else if (trap_list[sig].cmd == Qundef) { - rb_thread_signal_exit(); - } - else { - rb_thread_trap_eval(trap_list[sig].cmd, sig, trap_list[sig].safe); + clear_received_signal(); +#if defined(ruby_sigunmask) + sigemptyset(&mask); + sigaddset(&mask, sig); + if (ruby_sigunmask(SIG_UNBLOCK, &mask, NULL)) { + rb_bug_errno(STRINGIZE(ruby_sigunmask)":unblock", errno); } +#endif } -#if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) +# ifdef USE_UCONTEXT_REG static void -sigsend_to_ruby_thread(int sig) +check_stack_overflow(int sig, const uintptr_t addr, const ucontext_t *ctx) { -# ifdef HAVE_SIGPROCMASK - sigset_t mask, old_mask; + const DEFINE_MCONTEXT_PTR(mctx, ctx); +# if defined __linux__ +# if defined REG_RSP + const greg_t sp = mctx->gregs[REG_RSP]; + const greg_t bp = mctx->gregs[REG_RBP]; +# else + const greg_t sp = mctx->gregs[REG_ESP]; + const greg_t bp = mctx->gregs[REG_EBP]; +# endif +# elif defined __APPLE__ +# include <AvailabilityMacros.h> +# if defined(MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +# define MCTX_SS_REG(reg) __ss.__##reg +# else +# define MCTX_SS_REG(reg) ss.reg +# endif +# if defined(__LP64__) + const uintptr_t sp = mctx->MCTX_SS_REG(rsp); + const uintptr_t bp = mctx->MCTX_SS_REG(rbp); +# else + const uintptr_t sp = mctx->MCTX_SS_REG(esp); + const uintptr_t bp = mctx->MCTX_SS_REG(ebp); +# endif +# elif defined __FreeBSD__ +# if defined(__amd64__) + const __register_t sp = mctx->mc_rsp; + const __register_t bp = mctx->mc_rbp; +# else + const __register_t sp = mctx->mc_esp; + const __register_t bp = mctx->mc_ebp; +# endif +# elif defined __HAIKU__ +# if defined(__amd64__) + const unsigned long sp = mctx->rsp; + const unsigned long bp = mctx->rbp; +# else + const unsigned long sp = mctx->esp; + const unsigned long bp = mctx->ebp; +# endif +# endif + enum {pagesize = 4096}; + const uintptr_t sp_page = (uintptr_t)sp / pagesize; + const uintptr_t bp_page = (uintptr_t)bp / pagesize; + const uintptr_t fault_page = addr / pagesize; + + /* SP in ucontext is not decremented yet when `push` failed, so + * the fault page can be the next. */ + if (sp_page == fault_page || sp_page == fault_page + 1 || + (sp_page <= fault_page && fault_page <= bp_page)) { + rb_execution_context_t *ec = GET_EC(); + ruby_stack_overflow_critical_level crit = rb_stack_overflow_signal; + int uplevel = roomof(pagesize, sizeof(*ec->tag)) / 2; /* XXX: heuristic */ + while ((uintptr_t)ec->tag->buf / pagesize <= fault_page + 1) { + /* drop the last tag if it is close to the fault, + * otherwise it can cause stack overflow again at the same + * place. */ + if (!ec->tag->prev || !--uplevel) { + crit = rb_stack_overflow_fatal; + break; + } + rb_vm_tag_jmpbuf_deinit(&ec->tag->buf); + ec->tag = ec->tag->prev; + } + reset_sigmask(sig); + rb_ec_stack_overflow(ec, crit); + } +} # else - int mask, old_mask; +static void +check_stack_overflow(int sig, const void *addr) +{ + int ruby_stack_overflowed_p(const rb_thread_t *, const void *); + rb_execution_context_t *ec = rb_current_execution_context(false); + if (!ec) return; + rb_thread_t *th = rb_ec_thread_ptr(ec); + if (ruby_stack_overflowed_p(th, addr)) { + reset_sigmask(sig); + rb_ec_stack_overflow(th->ec, 1); + } +} # endif -# ifdef HAVE_SIGPROCMASK - sigfillset(&mask); - sigprocmask(SIG_BLOCK, &mask, &old_mask); +# ifdef _WIN32 +# define CHECK_STACK_OVERFLOW() check_stack_overflow(sig, 0) # else - mask = sigblock(~0); - sigsetmask(mask); +# define FAULT_ADDRESS info->si_addr +# ifdef USE_UCONTEXT_REG +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, (uintptr_t)FAULT_ADDRESS, ctx) +# else +# define CHECK_STACK_OVERFLOW_() check_stack_overflow(sig, FAULT_ADDRESS) +# endif +# define MESSAGE_FAULT_ADDRESS " at %p", FAULT_ADDRESS +# define SIGNAL_FROM_USER_P() ((info)->si_code == SI_USER) +# define CHECK_STACK_OVERFLOW() (SIGNAL_FROM_USER_P() ? (void)0 : CHECK_STACK_OVERFLOW_()) # endif +#else +# define CHECK_STACK_OVERFLOW() (void)0 +#endif +#ifndef MESSAGE_FAULT_ADDRESS +# define MESSAGE_FAULT_ADDRESS +#endif + +#if defined SIGSEGV || defined SIGBUS || defined SIGILL || defined SIGFPE +NOINLINE(static void check_reserved_signal_(const char *name, size_t name_len, int signo)); +/* noinine to reduce stack usage in signal handers */ - ruby_native_thread_kill(sig); +#define check_reserved_signal(name) check_reserved_signal_(name, sizeof(name)-1, sig) + +#ifdef SIGBUS + +static sighandler_t default_sigbus_handler; +NORETURN(static ruby_sigaction_t sigbus); + +static void +sigbus(int sig SIGINFO_ARG) +{ + check_reserved_signal("BUS"); +/* + * Mac OS X makes KERN_PROTECTION_FAILURE when thread touch guard page. + * and it's delivered as SIGBUS instead of SIGSEGV to userland. It's crazy + * wrong IMHO. but anyway we have to care it. Sigh. + */ + /* Seems Linux also delivers SIGBUS. */ +#if defined __APPLE__ || defined __linux__ + CHECK_STACK_OVERFLOW(); +#endif + rb_bug_for_fatal_signal(default_sigbus_handler, sig, SIGINFO_CTX, "Bus Error" MESSAGE_FAULT_ADDRESS); } #endif -static RETSIGTYPE sighandler _((int)); -static RETSIGTYPE -sighandler(sig) - int sig; +#ifdef SIGSEGV + +static sighandler_t default_sigsegv_handler; +NORETURN(static ruby_sigaction_t sigsegv); + +static void +sigsegv(int sig SIGINFO_ARG) { -#ifdef _WIN32 -#define IN_MAIN_CONTEXT(f, a) (rb_w32_main_context(a, f) ? (void)0 : f(a)) -#else -#define IN_MAIN_CONTEXT(f, a) f(a) + check_reserved_signal("SEGV"); + CHECK_STACK_OVERFLOW(); + rb_bug_for_fatal_signal(default_sigsegv_handler, sig, SIGINFO_CTX, "Segmentation fault" MESSAGE_FAULT_ADDRESS); +} #endif - if (sig >= NSIG) { - rb_bug("trap_handler: Bad signal %d", sig); - } +#ifdef SIGABRT -#if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) - if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { - sigsend_to_ruby_thread(sig); - return; - } +static sighandler_t default_sigabrt_handler; +NORETURN(static ruby_sigaction_t sigabrt); + +static void +sigabrt(int sig SIGINFO_ARG) +{ + check_reserved_signal("ABRT"); + CHECK_STACK_OVERFLOW(); + rb_bug_for_fatal_signal(default_sigabrt_handler, sig, SIGINFO_CTX, "Aborted" MESSAGE_FAULT_ADDRESS); +} #endif -#if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) - if (rb_trap_accept_nativethreads[sig]) { - ruby_nativethread_signal(sig, sighandler); - } else { - ruby_signal(sig, sighandler); - } +#ifdef SIGILL + +static sighandler_t default_sigill_handler; +NORETURN(static ruby_sigaction_t sigill); + +static void +sigill(int sig SIGINFO_ARG) +{ + check_reserved_signal("ILL"); +#if defined __APPLE__ || defined __linux__ + CHECK_STACK_OVERFLOW(); +#endif + rb_bug_for_fatal_signal(default_sigill_handler, sig, SIGINFO_CTX, "Illegal instruction" MESSAGE_FAULT_ADDRESS); +} #endif - if (trap_list[sig].cmd == 0 && ATOMIC_TEST(rb_trap_immediate)) { - IN_MAIN_CONTEXT(signal_exec, sig); - ATOMIC_SET(rb_trap_immediate, 1); - } - else { - ATOMIC_INC(rb_trap_pending); - ATOMIC_INC(trap_pending_list[sig]); -#ifdef _WIN32 - rb_w32_interrupted(); +#ifndef __sun +NORETURN(static void ruby_abort(void)); +#endif + +static void +ruby_abort(void) +{ +#ifdef __sun + /* Solaris's abort() is async signal unsafe. Of course, it is not + * POSIX compliant. + */ + raise(SIGABRT); +#else + abort(); #endif - } } -#ifdef SIGBUS -static RETSIGTYPE sigbus _((int)); -static RETSIGTYPE -sigbus(sig) - int sig; +static void +check_reserved_signal_(const char *name, size_t name_len, int signo) { -#if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) - if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { - sigsend_to_ruby_thread(sig); - return; - } + const char *prev = ATOMIC_PTR_EXCHANGE(received_signal, name); + + if (prev) { + ssize_t RB_UNUSED_VAR(err); + static const int stderr_fd = 2; +#define NOZ(name, str) RBIMPL_ATTR_NONSTRING() name[sizeof(str)-1] = str + static const char NOZ(msg1, " received in "); + static const char NOZ(msg2, " handler\n"); + +#ifdef HAVE_WRITEV + struct iovec iov[4]; + int i = 0; +# define W(str, len) \ + iov[i++] = (struct iovec){.iov_base = (void *)(str), .iov_len = (len)} +#else +# define W(str, len) err = write(stderr_fd, (str), (len)) #endif - rb_bug("Bus Error"); -} +#if __has_feature(address_sanitizer) || \ + __has_feature(memory_sanitizer) || \ + defined(HAVE_VALGRIND_MEMCHECK_H) +# define SANITIZING true +#else +# define SANITIZING false #endif -#ifdef SIGSEGV -static RETSIGTYPE sigsegv _((int)); -static RETSIGTYPE -sigsegv(sig) - int sig; -{ -#if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) - if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { - sigsend_to_ruby_thread(sig); - return; +#ifdef SIGABRT +// Avoid infinite loop when already aborting +# define RECURSIVE (signo == SIGABRT) +#else +# define RECURSIVE false +#endif + if (SANITIZING || RECURSIVE) ruby_signal(signo, SIG_DFL); +# undef SANITIZING +# undef RECURSIVE + W(name, name_len); + W(msg1, sizeof(msg1)); + W(prev, strlen(prev)); + W(msg2, sizeof(msg2)); +# undef W +#ifdef HAVE_WRITEV + err = writev(stderr_fd, iov, i); +#endif + ruby_abort(); } -#endif - rb_bug("Segmentation fault"); + if (GET_VM() != NULL) { + rb_gc_disable_no_rest(); + } } #endif -#ifdef SIGPIPE -static RETSIGTYPE sigpipe _((int)); -static RETSIGTYPE -sigpipe(sig) - int sig; +#if defined SIGPIPE || defined SIGSYS +static void +sig_do_nothing(int sig) { - /* do nothing */ } #endif -void -rb_trap_exit() +static int +signal_exec(VALUE cmd, int sig) { -#ifndef MACOS_UNUSE_SIGNAL - if (trap_list[0].cmd) { - VALUE trap_exit = trap_list[0].cmd; + rb_execution_context_t *ec = GET_EC(); + volatile rb_atomic_t old_interrupt_mask = ec->interrupt_mask; + enum ruby_tag_type state; + + /* + * workaround the following race: + * 1. signal_enque queues signal for execution + * 2. user calls trap(sig, "IGNORE"), setting SIG_IGN + * 3. rb_signal_exec runs on queued signal + */ + if (IMMEDIATE_P(cmd)) + return FALSE; + + ec->interrupt_mask |= TRAP_INTERRUPT_MASK; + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + VALUE signum = INT2NUM(sig); + rb_eval_cmd_call_kw(cmd, 1, &signum, RB_NO_KEYWORDS); + } + EC_POP_TAG(); + ec = GET_EC(); + ec->interrupt_mask = old_interrupt_mask; - trap_list[0].cmd = 0; - rb_eval_cmd(trap_exit, rb_ary_new3(1, INT2FIX(0)), trap_list[0].safe); + if (state) { + /* XXX: should be replaced with rb_threadptr_pending_interrupt_enque() */ + EC_JUMP_TAG(ec, state); } -#endif + return TRUE; } void -rb_trap_exec() +rb_vm_trap_exit(rb_vm_t *vm) { -#ifndef MACOS_UNUSE_SIGNAL - int i; + VALUE trap_exit = vm->trap_list.cmd[0]; - for (i=0; i<NSIG; i++) { - if (trap_pending_list[i]) { - trap_pending_list[i] = 0; - signal_exec(i); - } + if (trap_exit) { + vm->trap_list.cmd[0] = 0; + signal_exec(trap_exit, 0); } -#endif /* MACOS_UNUSE_SIGNAL */ - rb_trap_pending = 0; -} - -struct trap_arg { -#ifndef _WIN32 -# ifdef HAVE_SIGPROCMASK - sigset_t mask; -# else - int mask; -# endif -#endif - VALUE sig, cmd; -}; - -# ifdef HAVE_SIGPROCMASK -static sigset_t trap_last_mask; -# else -static int trap_last_mask; -# endif - -static RETSIGTYPE sigexit _((int)); -static RETSIGTYPE -sigexit(sig) - int sig; -{ - rb_thread_signal_exit(); } -static VALUE -trap(arg) - struct trap_arg *arg; +/* returns true if a trap handler was run, false otherwise */ +int +rb_signal_exec(rb_thread_t *th, int sig) { - sighandler_t func, oldfunc; - VALUE command, oldcmd; - int sig = -1; - char *s; - - func = sighandler; - command = arg->cmd; - if (NIL_P(command)) { - func = SIG_IGN; - } - else if (TYPE(command) == T_STRING) { - SafeStringValue(command); /* taint check */ - if (RSTRING(command)->len == 0) { - func = SIG_IGN; - } - else if (RSTRING(command)->len == 7) { - if (strncmp(RSTRING(command)->ptr, "SIG_IGN", 7) == 0) { - func = SIG_IGN; - } - else if (strncmp(RSTRING(command)->ptr, "SIG_DFL", 7) == 0) { - func = SIG_DFL; - } - else if (strncmp(RSTRING(command)->ptr, "DEFAULT", 7) == 0) { - func = SIG_DFL; - } - } - else if (RSTRING(command)->len == 6) { - if (strncmp(RSTRING(command)->ptr, "IGNORE", 6) == 0) { - func = SIG_IGN; - } - } - else if (RSTRING(command)->len == 4) { - if (strncmp(RSTRING(command)->ptr, "EXIT", 4) == 0) { - func = sigexit; - } - } + rb_vm_t *vm = GET_VM(); + VALUE cmd = vm->trap_list.cmd[sig]; + + if (cmd == 0) { + switch (sig) { + case SIGINT: + rb_interrupt(); + break; +#ifdef SIGHUP + case SIGHUP: +#endif +#ifdef SIGQUIT + case SIGQUIT: +#endif +#ifdef SIGTERM + case SIGTERM: +#endif +#ifdef SIGALRM + case SIGALRM: +#endif +#ifdef SIGUSR1 + case SIGUSR1: +#endif +#ifdef SIGUSR2 + case SIGUSR2: +#endif + rb_threadptr_signal_raise(th, sig); + break; + } } - if (func == SIG_IGN || func == SIG_DFL) { - command = 0; + else if (UNDEF_P(cmd)) { + rb_threadptr_signal_exit(th); } - - switch (TYPE(arg->sig)) { - case T_FIXNUM: - sig = FIX2INT(arg->sig); - break; - - case T_SYMBOL: - s = rb_id2name(SYM2ID(arg->sig)); - if (!s) rb_raise(rb_eArgError, "bad signal"); - goto str_signal; - - case T_STRING: - s = RSTRING(arg->sig)->ptr; - - str_signal: - if (strncmp("SIG", s, 3) == 0) - s += 3; - sig = signm2signo(s); - if (sig == 0 && strcmp(s, "EXIT") != 0) - rb_raise(rb_eArgError, "unsupported signal SIG%s", s); + else { + return signal_exec(cmd, sig); } + return FALSE; +} - if (sig < 0 || sig >= NSIG) { - rb_raise(rb_eArgError, "invalid signal number (%d)", sig); - } -#if defined(HAVE_SETITIMER) - if (sig == SIGVTALRM) { - rb_raise(rb_eArgError, "SIGVTALRM reserved for Thread; can't set handler"); - } -#endif - if (func == SIG_DFL) { - switch (sig) { - case SIGINT: +static sighandler_t +default_handler(int sig) +{ + sighandler_t func; + switch (sig) { + case SIGINT: #ifdef SIGHUP - case SIGHUP: + case SIGHUP: #endif #ifdef SIGQUIT - case SIGQUIT: + case SIGQUIT: #endif #ifdef SIGTERM - case SIGTERM: + case SIGTERM: #endif #ifdef SIGALRM - case SIGALRM: + case SIGALRM: #endif #ifdef SIGUSR1 - case SIGUSR1: + case SIGUSR1: #endif #ifdef SIGUSR2 - case SIGUSR2: + case SIGUSR2: #endif - func = sighandler; - break; +#ifdef RUBY_SIGCHLD + case RUBY_SIGCHLD: +#endif + func = sighandler; + break; #ifdef SIGBUS - case SIGBUS: - func = sigbus; - break; + case SIGBUS: + func = (sighandler_t)sigbus; + break; #endif #ifdef SIGSEGV - case SIGSEGV: - func = sigsegv; - break; + case SIGSEGV: + func = (sighandler_t)sigsegv; + break; #endif #ifdef SIGPIPE - case SIGPIPE: - func = sigpipe; - break; + case SIGPIPE: + func = sig_do_nothing; + break; #endif - } +#ifdef SIGSYS + case SIGSYS: + func = sig_do_nothing; + break; +#endif + default: + func = SIG_DFL; + break; + } + + return func; +} + +static sighandler_t +trap_handler(VALUE *cmd, int sig) +{ + sighandler_t func = sighandler; + VALUE command; + + if (NIL_P(*cmd)) { + func = SIG_IGN; } - oldfunc = ruby_signal(sig, func); - oldcmd = trap_list[sig].cmd; - if (!oldcmd) { - if (oldfunc == SIG_IGN) oldcmd = rb_str_new2("IGNORE"); - else if (oldfunc == sighandler) oldcmd = rb_str_new2("DEFAULT"); - else oldcmd = Qnil; + else { + command = rb_check_string_type(*cmd); + if (NIL_P(command) && SYMBOL_P(*cmd)) { + command = rb_sym2str(*cmd); + if (!command) rb_raise(rb_eArgError, "bad handler"); + } + if (!NIL_P(command)) { + const char *cptr; + long len; + StringValue(command); + *cmd = command; + RSTRING_GETMEM(command, cptr, len); + switch (len) { + sig_ign: + func = SIG_IGN; + *cmd = Qtrue; + break; + sig_dfl: + func = default_handler(sig); + *cmd = 0; + break; + case 0: + goto sig_ign; + break; + case 14: + if (memcmp(cptr, "SYSTEM_DEFAULT", 14) == 0) { + func = SIG_DFL; + *cmd = 0; + } + break; + case 7: + if (memcmp(cptr, "SIG_IGN", 7) == 0) { + goto sig_ign; + } + else if (memcmp(cptr, "SIG_DFL", 7) == 0) { + goto sig_dfl; + } + else if (memcmp(cptr, "DEFAULT", 7) == 0) { + goto sig_dfl; + } + break; + case 6: + if (memcmp(cptr, "IGNORE", 6) == 0) { + goto sig_ign; + } + break; + case 4: + if (memcmp(cptr, "EXIT", 4) == 0) { + *cmd = Qundef; + } + break; + } + } } - trap_list[sig].cmd = command; - trap_list[sig].safe = ruby_safe_level; - /* enable at least specified signal. */ -#ifndef _WIN32 -#ifdef HAVE_SIGPROCMASK - sigdelset(&arg->mask, sig); -#else - arg->mask &= ~sigmask(sig); -#endif -#endif - return oldcmd; + return func; +} + +static int +trap_signm(VALUE vsig) +{ + int sig = -1; + + if (FIXNUM_P(vsig)) { + sig = FIX2INT(vsig); + if (sig < 0 || sig >= NSIG) { + rb_raise(rb_eArgError, "invalid signal number (%d)", sig); + } + } + else { + sig = signm2signo(&vsig, FALSE, TRUE, NULL); + } + return sig; } -#ifndef _WIN32 static VALUE -trap_ensure(arg) - struct trap_arg *arg; +trap(int sig, sighandler_t func, VALUE command) { - /* enable interrupt */ -#ifdef HAVE_SIGPROCMASK - sigprocmask(SIG_SETMASK, &arg->mask, NULL); -#else - sigsetmask(arg->mask); -#endif - trap_last_mask = arg->mask; - return 0; + sighandler_t oldfunc; + VALUE oldcmd; + rb_vm_t *vm = GET_VM(); + + /* + * Be careful. ruby_signal() and trap_list.cmd[sig] must be changed + * atomically. In current implementation, we only need to don't call + * RUBY_VM_CHECK_INTS(). + */ + if (sig == 0) { + oldfunc = SIG_ERR; + } + else { + oldfunc = ruby_signal(sig, func); + if (oldfunc == SIG_ERR) rb_sys_fail_str(rb_signo2signm(sig)); + } + oldcmd = vm->trap_list.cmd[sig]; + switch (oldcmd) { + case 0: + case Qtrue: + if (oldfunc == SIG_IGN) oldcmd = rb_str_new2("IGNORE"); + else if (oldfunc == SIG_DFL) oldcmd = rb_str_new2("SYSTEM_DEFAULT"); + else if (oldfunc == sighandler) oldcmd = rb_str_new2("DEFAULT"); + else oldcmd = Qnil; + break; + case Qnil: + break; + case Qundef: + oldcmd = rb_str_new2("EXIT"); + break; + } + + ACCESS_ONCE(VALUE, vm->trap_list.cmd[sig]) = command; + + return oldcmd; } -#endif -void -rb_trap_restore_mask() +static int +reserved_signal_p(int signo) { -#ifndef _WIN32 -# ifdef HAVE_SIGPROCMASK - sigprocmask(SIG_SETMASK, &trap_last_mask, NULL); -# else - sigsetmask(trap_last_mask); -# endif +/* Synchronous signal can't deliver to main thread */ +#ifdef SIGSEGV + if (signo == SIGSEGV) + return 1; +#endif +#ifdef SIGBUS + if (signo == SIGBUS) + return 1; +#endif +#ifdef SIGILL + if (signo == SIGILL) + return 1; +#endif +#ifdef SIGFPE + if (signo == SIGFPE) + return 1; #endif + +/* used ubf internal see thread_pthread.c. */ +#ifdef SIGVTALRM + if (signo == SIGVTALRM) + return 1; +#endif + + return 0; } /* * call-seq: - * Signal.trap( signal, proc ) => obj - * Signal.trap( signal ) {| | block } => obj + * Signal.trap(signal, command) -> obj + * Signal.trap(signal) { ... } -> obj + * + * Specifies the handling of signals. Returns the previous handler for + * the given signal. + * + * Argument +signal+ is a signal name (a string or symbol such + * as +SIGALRM+ or +SIGUSR1+) or an integer signal number. When +signal+ + * is a string or symbol, the leading characters +SIG+ may be omitted. + * + * Argument +command+ or block provided specifies code to be run when the + * signal is raised. * - * Specifies the handling of signals. The first parameter is a signal - * name (a string such as ``SIGALRM'', ``SIGUSR1'', and so on) or a - * signal number. The characters ``SIG'' may be omitted from the - * signal name. The command or block specifies code to be run when the - * signal is raised. If the command is the string ``IGNORE'' or - * ``SIG_IGN'', the signal will be ignored. If the command is - * ``DEFAULT'' or ``SIG_DFL'', the operating system's default handler - * will be invoked. If the command is ``EXIT'', the script will be - * terminated by the signal. Otherwise, the given command or block - * will be run. - * The special signal name ``EXIT'' or signal number zero will be - * invoked just prior to program termination. - * trap returns the previous handler for the given signal. + * Argument +command+ may also be a string or symbol with the following special + * values: + * + * - +IGNORE+, +SIG_IGN+: the signal will be ignored. + * - +DEFAULT+, +SIG_DFL+: Ruby's default handler will be invoked. + * - +EXIT+: the process will be terminated by the signal. + * - +SYSTEM_DEFAULT+: the operating system's default handler will be invoked. + * + * The special signal name +EXIT+ or signal number zero will be + * invoked just prior to program termination: * * Signal.trap(0, proc { puts "Terminating: #{$$}" }) * Signal.trap("CLD") { puts "Child died" } * fork && Process.wait * - * produces: + * Outputs: + * * Terminating: 27461 * Child died * Terminating: 27460 */ static VALUE -sig_trap(argc, argv) - int argc; - VALUE *argv; +sig_trap(int argc, VALUE *argv, VALUE _) { - struct trap_arg arg; + int sig; + sighandler_t func; + VALUE cmd; - rb_secure(2); - if (argc == 0 || argc > 2) { - rb_raise(rb_eArgError, "wrong number of arguments -- trap(sig, cmd)/trap(sig){...}"); + rb_check_arity(argc, 1, 2); + + sig = trap_signm(argv[0]); + if (reserved_signal_p(sig)) { + const char *name = signo2signm(sig); + if (name) + rb_raise(rb_eArgError, "can't trap reserved signal: SIG%s", name); + else + rb_raise(rb_eArgError, "can't trap reserved signal: %d", sig); } - arg.sig = argv[0]; if (argc == 1) { - arg.cmd = rb_block_proc(); + cmd = rb_block_proc(); + func = sighandler; } - else if (argc == 2) { - arg.cmd = argv[1]; + else { + cmd = argv[1]; + func = trap_handler(&cmd, sig); } - if (OBJ_TAINTED(arg.cmd)) { - rb_raise(rb_eSecurityError, "Insecure: tainted signal trap"); + if (rb_obj_is_proc(cmd) && + !rb_ractor_main_p() && !rb_ractor_shareable_p(cmd)) { + cmd = rb_proc_isolate(cmd); } -#ifndef _WIN32 - /* disable interrupt */ -# ifdef HAVE_SIGPROCMASK - sigfillset(&arg.mask); - sigprocmask(SIG_BLOCK, &arg.mask, &arg.mask); -# else - arg.mask = sigblock(~0); -# endif - return rb_ensure(trap, (VALUE)&arg, trap_ensure, (VALUE)&arg); -#else - return trap(&arg); -#endif + return trap(sig, func, cmd); } /* * call-seq: - * Signal.list => a_hash + * Signal.list -> a_hash * * Returns a list of signal names mapped to the corresponding * underlying signal numbers. * - * Signal.list #=> {"ABRT"=>6, "ALRM"=>14, "BUS"=>7, "CHLD"=>17, "CLD"=>17, "CONT"=>18, "FPE"=>8, "HUP"=>1, "ILL"=>4, "INT"=>2, "IO"=>29, "IOT"=>6, "KILL"=>9, "PIPE"=>13, "POLL"=>29, "PROF"=>27, "PWR"=>30, "QUIT"=>3, "SEGV"=>11, "STOP"=>19, "SYS"=>31, "TERM"=>15, "TRAP"=>5, "TSTP"=>20, "TTIN"=>21, "TTOU"=>22, "URG"=>23, "USR1"=>10, "USR2"=>12, "VTALRM"=>26, "WINCH"=>28, "XCPU"=>24, "XFSZ"=>25} + * Signal.list #=> {"EXIT"=>0, "HUP"=>1, "INT"=>2, "QUIT"=>3, "ILL"=>4, "TRAP"=>5, "IOT"=>6, "ABRT"=>6, "FPE"=>8, "KILL"=>9, "BUS"=>7, "SEGV"=>11, "SYS"=>31, "PIPE"=>13, "ALRM"=>14, "TERM"=>15, "URG"=>23, "STOP"=>19, "TSTP"=>20, "CONT"=>18, "CHLD"=>17, "CLD"=>17, "TTIN"=>21, "TTOU"=>22, "IO"=>29, "XCPU"=>24, "XFSZ"=>25, "VTALRM"=>26, "PROF"=>27, "WINCH"=>28, "USR1"=>10, "USR2"=>12, "PWR"=>30, "POLL"=>29} */ static VALUE -sig_list() +sig_list(VALUE _) { VALUE h = rb_hash_new(); - struct signals *sigs; + const struct signals *sigs; - for (sigs = siglist; sigs->signm; sigs++) { - rb_hash_aset(h, rb_str_new2(sigs->signm), INT2FIX(sigs->signo)); + FOREACH_SIGNAL(sigs, 0) { + rb_hash_aset(h, rb_fstring_cstr(sigs->signm), INT2FIX(sigs->signo)); } return h; } -static void -install_sighandler(signum, handler) - int signum; - sighandler_t handler; +#define INSTALL_SIGHANDLER(cond, signame, signum) do { \ + static const char failed[] = "failed to install "signame" handler"; \ + if (!(cond)) break; \ + if (reserved_signal_p(signum)) rb_bug(failed); \ + perror(failed); \ + } while (0) + +static int +install_sighandler_core(int signum, sighandler_t handler, sighandler_t *old_handler) { sighandler_t old; old = ruby_signal(signum, handler); - if (old != SIG_DFL) { - ruby_signal(signum, old); + if (old == SIG_ERR) return -1; + if (old_handler) { + *old_handler = (old == SIG_DFL || old == SIG_IGN) ? 0 : old; } -} - -#if 0 -/* - * If you write a handler which works on any native thread - * (even if the thread is NOT a ruby's one), please enable - * this function and use it to install the handler, instead - * of `install_sighandler()'. - */ -#ifdef HAVE_NATIVETHREAD -static void -install_nativethread_sighandler(signum, handler) - int signum; - sighandler_t handler; -{ - sighandler_t old; - int old_st; - - old_st = rb_trap_accept_nativethreads[signum]; - old = ruby_nativethread_signal(signum, handler); - if (old != SIG_DFL) { - if (old_st) { - ruby_nativethread_signal(signum, old); - } else { + else { + /* signal handler should be inherited during exec. */ + if (old != SIG_DFL) { ruby_signal(signum, old); } } + return 0; } -#endif -#endif -static void -init_sigchld(sig) - int sig; +# define install_sighandler(signum, handler) \ + INSTALL_SIGHANDLER(install_sighandler_core(signum, handler, NULL), #signum, signum) +# define force_install_sighandler(signum, handler, old_handler) \ + INSTALL_SIGHANDLER(install_sighandler_core(signum, handler, old_handler), #signum, signum) + +void +ruby_sig_finalize(void) { sighandler_t oldfunc; -#ifndef _WIN32 -# ifdef HAVE_SIGPROCMASK - sigset_t mask; -# else - int mask; -# endif -#endif - -#ifndef _WIN32 - /* disable interrupt */ -# ifdef HAVE_SIGPROCMASK - sigfillset(&mask); - sigprocmask(SIG_BLOCK, &mask, &mask); -# else - mask = sigblock(~0); -# endif -#endif - oldfunc = ruby_signal(sig, SIG_DFL); - if (oldfunc != SIG_DFL && oldfunc != SIG_IGN) { - ruby_signal(sig, oldfunc); - } else { - trap_list[sig].cmd = 0; + oldfunc = ruby_signal(SIGINT, SIG_IGN); + if (oldfunc == sighandler) { + ruby_signal(SIGINT, SIG_DFL); } - -#ifndef _WIN32 -#ifdef HAVE_SIGPROCMASK - sigdelset(&mask, sig); - sigprocmask(SIG_SETMASK, &mask, NULL); -#else - mask &= ~sigmask(sig); - sigsetmask(mask); -#endif - trap_last_mask = mask; -#endif } +int ruby_enable_coredump = 0; + /* * Many operating systems allow signals to be sent to running * processes. Some signals have a defined effect on the process, while @@ -1049,29 +1520,38 @@ init_sigchld(sig) * # ... * Process.kill("TERM", pid) * - * produces: + * <em>produces:</em> * Debug now: true * Debug now: false - * Terminating... + * Terminating... * * The list of available signal names and their interpretation is * system dependent. Signal delivery semantics may also vary between * systems; in particular signal delivery may not always be reliable. */ void -Init_signal() +Init_signal(void) { -#ifndef MACOS_UNUSE_SIGNAL VALUE mSignal = rb_define_module("Signal"); rb_define_global_function("trap", sig_trap, -1); rb_define_module_function(mSignal, "trap", sig_trap, -1); rb_define_module_function(mSignal, "list", sig_list, 0); + rb_define_module_function(mSignal, "signame", sig_signame, 1); rb_define_method(rb_eSignal, "initialize", esignal_init, -1); - rb_attr(rb_eSignal, rb_intern("signo"), 1, 0, 0); - rb_alias(rb_eSignal, rb_intern("signm"), rb_intern("message")); - rb_define_method(rb_eInterrupt, "initialize", interrupt_init, 1); + rb_define_method(rb_eSignal, "signo", esignal_signo, 0); + rb_alias(rb_eSignal, rb_intern_const("signm"), rb_intern_const("message")); + rb_define_method(rb_eInterrupt, "initialize", interrupt_init, -1); +#if !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); +#endif + + // It should be ready to call rb_signal_exec() + VM_ASSERT(GET_THREAD()->pending_interrupt_queue); + + /* At this time, there is no subthread. Then sigmask guarantee atomics. */ + rb_disable_interrupt(); install_sighandler(SIGINT, sighandler); #ifdef SIGHUP @@ -1093,21 +1573,40 @@ Init_signal() install_sighandler(SIGUSR2, sighandler); #endif + if (!ruby_enable_coredump) { #ifdef SIGBUS - install_sighandler(SIGBUS, sigbus); + force_install_sighandler(SIGBUS, (sighandler_t)sigbus, &default_sigbus_handler); +#endif +#ifdef SIGILL + force_install_sighandler(SIGILL, (sighandler_t)sigill, &default_sigill_handler); #endif #ifdef SIGSEGV - install_sighandler(SIGSEGV, sigsegv); + RB_ALTSTACK_INIT(GET_VM()->main_altstack, rb_allocate_sigaltstack()); + force_install_sighandler(SIGSEGV, (sighandler_t)sigsegv, &default_sigsegv_handler); #endif + +#ifdef SIGABRT + force_install_sighandler(SIGABRT, (sighandler_t)sigabrt, &default_sigabrt_handler); +#endif + } #ifdef SIGPIPE - install_sighandler(SIGPIPE, sigpipe); + install_sighandler(SIGPIPE, sig_do_nothing); +#endif +#ifdef SIGSYS + install_sighandler(SIGSYS, sig_do_nothing); #endif -#if defined(SIGCLD) - init_sigchld(SIGCLD); -#elif defined(SIGCHLD) - init_sigchld(SIGCHLD); +#ifdef RUBY_SIGCHLD + install_sighandler(RUBY_SIGCHLD, sighandler); #endif -#endif /* MACOS_UNUSE_SIGNAL */ + rb_enable_interrupt(); +} + +void +rb_signal_atfork(void) +{ +#if defined(HAVE_WORKING_FORK) && !defined(POSIX_SIGNAL) && !defined(SIG_GET) + rb_native_mutex_initialize(&sig_check_lock); +#endif } |
