diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/signal.c |
Fresh start
Diffstat (limited to 'jni/ruby/signal.c')
-rw-r--r-- | jni/ruby/signal.c | 1479 |
1 files changed, 1479 insertions, 0 deletions
diff --git a/jni/ruby/signal.c b/jni/ruby/signal.c new file mode 100644 index 0000000..1c12726 --- /dev/null +++ b/jni/ruby/signal.c @@ -0,0 +1,1479 @@ +/********************************************************************** + + signal.c - + + $Author: naruse $ + created at: Tue Dec 20 10:13:44 JST 1994 + + Copyright (C) 1993-2007 Yukihiro Matsumoto + Copyright (C) 2000 Network Applied Communication Laboratory, Inc. + Copyright (C) 2000 Information-technology Promotion Agency, Japan + +**********************************************************************/ + +#include "internal.h" +#include "vm_core.h" +#include <signal.h> +#include <stdio.h> +#include <errno.h> +#include "ruby_atomic.h" +#include "eval_intern.h" +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif +#ifdef HAVE_UCONTEXT_H +#include <ucontext.h> +#endif + +#ifdef HAVE_VALGRIND_MEMCHECK_H +# include <valgrind/memcheck.h> +# ifndef VALGRIND_MAKE_MEM_DEFINED +# define VALGRIND_MAKE_MEM_DEFINED(p, n) VALGRIND_MAKE_READABLE((p), (n)) +# endif +# ifndef VALGRIND_MAKE_MEM_UNDEFINED +# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) VALGRIND_MAKE_WRITABLE((p), (n)) +# endif +#else +# define VALGRIND_MAKE_MEM_DEFINED(p, n) 0 +# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 +#endif + +#if defined(__native_client__) && defined(NACL_NEWLIB) +# include "nacl/signal.h" +#endif + +#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 + +#if defined(__BEOS__) || defined(__HAIKU__) +#undef SIGBUS +#endif + +#ifndef NSIG +# define NSIG (_SIGMAX + 1) /* For QNX */ +#endif + +static const struct signals { + const char *signm; + int signo; +} siglist [] = { + {"EXIT", 0}, +#ifdef SIGHUP + {"HUP", SIGHUP}, +#endif + {"INT", SIGINT}, +#ifdef SIGQUIT + {"QUIT", SIGQUIT}, +#endif +#ifdef SIGILL + {"ILL", SIGILL}, +#endif +#ifdef SIGTRAP + {"TRAP", SIGTRAP}, +#endif +#ifdef SIGABRT + {"ABRT", SIGABRT}, +#endif +#ifdef SIGIOT + {"IOT", SIGIOT}, +#endif +#ifdef SIGEMT + {"EMT", SIGEMT}, +#endif +#ifdef SIGFPE + {"FPE", SIGFPE}, +#endif +#ifdef SIGKILL + {"KILL", SIGKILL}, +#endif +#ifdef SIGBUS + {"BUS", SIGBUS}, +#endif +#ifdef SIGSEGV + {"SEGV", SIGSEGV}, +#endif +#ifdef SIGSYS + {"SYS", SIGSYS}, +#endif +#ifdef SIGPIPE + {"PIPE", SIGPIPE}, +#endif +#ifdef SIGALRM + {"ALRM", SIGALRM}, +#endif +#ifdef SIGTERM + {"TERM", SIGTERM}, +#endif +#ifdef SIGURG + {"URG", SIGURG}, +#endif +#ifdef SIGSTOP + {"STOP", SIGSTOP}, +#endif +#ifdef SIGTSTP + {"TSTP", SIGTSTP}, +#endif +#ifdef SIGCONT + {"CONT", SIGCONT}, +#endif +#ifdef SIGCHLD + {"CHLD", SIGCHLD}, +#endif +#ifdef SIGCLD + {"CLD", SIGCLD}, +#else +# ifdef SIGCHLD + {"CLD", SIGCHLD}, +# endif +#endif +#ifdef SIGTTIN + {"TTIN", SIGTTIN}, +#endif +#ifdef SIGTTOU + {"TTOU", SIGTTOU}, +#endif +#ifdef SIGIO + {"IO", SIGIO}, +#endif +#ifdef SIGXCPU + {"XCPU", SIGXCPU}, +#endif +#ifdef SIGXFSZ + {"XFSZ", SIGXFSZ}, +#endif +#ifdef SIGVTALRM + {"VTALRM", SIGVTALRM}, +#endif +#ifdef SIGPROF + {"PROF", SIGPROF}, +#endif +#ifdef SIGWINCH + {"WINCH", SIGWINCH}, +#endif +#ifdef SIGUSR1 + {"USR1", SIGUSR1}, +#endif +#ifdef SIGUSR2 + {"USR2", SIGUSR2}, +#endif +#ifdef SIGLOST + {"LOST", SIGLOST}, +#endif +#ifdef SIGMSG + {"MSG", SIGMSG}, +#endif +#ifdef SIGPWR + {"PWR", SIGPWR}, +#endif +#ifdef SIGPOLL + {"POLL", SIGPOLL}, +#endif +#ifdef SIGDANGER + {"DANGER", SIGDANGER}, +#endif +#ifdef SIGMIGRATE + {"MIGRATE", SIGMIGRATE}, +#endif +#ifdef SIGPRE + {"PRE", SIGPRE}, +#endif +#ifdef SIGGRANT + {"GRANT", SIGGRANT}, +#endif +#ifdef SIGRETRACT + {"RETRACT", SIGRETRACT}, +#endif +#ifdef SIGSOUND + {"SOUND", SIGSOUND}, +#endif +#ifdef SIGINFO + {"INFO", SIGINFO}, +#endif + {NULL, 0} +}; + +static const char signame_prefix[3] = "SIG"; + +static int +signm2signo(const char *nm) +{ + const struct signals *sigs; + + for (sigs = siglist; sigs->signm; sigs++) + if (strcmp(sigs->signm, nm) == 0) + return sigs->signo; + return 0; +} + +static const char* +signo2signm(int no) +{ + const struct signals *sigs; + + for (sigs = siglist; sigs->signm; sigs++) + if (sigs->signo == no) + return sigs->signm; + return 0; +} + +/* + * call-seq: + * Signal.signame(signo) -> string + * + * convert signal number to signal name + * + * 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)); + return rb_str_new_cstr(signame); +} + +const char * +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_name) -> signal_exception + * SignalException.new(sig_number [, name]) -> signal_exception + * + * Construct a new SignalException object. +sig_name+ should be a known + * signal name. + */ + +static VALUE +esignal_init(int argc, VALUE *argv, VALUE self) +{ + int argnum = 1; + VALUE sig = Qnil; + int signo; + const char *signm; + + if (argc > 0) { + 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 = 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 { + int len = sizeof(signame_prefix); + if (SYMBOL_P(sig)) sig = rb_sym2str(sig); else StringValue(sig); + signm = RSTRING_PTR(sig); + if (strncmp(signm, signame_prefix, len) == 0) { + signm += len; + len = 0; + } + signo = signm2signo(signm); + if (!signo) { + rb_raise(rb_eArgError, "unsupported name `%.*s%"PRIsVALUE"'", + len, signame_prefix, sig); + } + sig = rb_sprintf("SIG%s", signm); + } + rb_call_super(1, &sig); + rb_iv_set(self, "signo", INT2NUM(signo)); + + return self; +} + +/* + * call-seq: + * signal_exception.signo -> num + * + * Returns a signal number. + */ + +static VALUE +esignal_signo(VALUE self) +{ + return rb_iv_get(self, "signo"); +} + +/* :nodoc: */ +static VALUE +interrupt_init(int argc, VALUE *argv, VALUE self) +{ + VALUE args[2]; + + args[0] = INT2FIX(SIGINT); + rb_scan_args(argc, argv, "01", &args[1]); + return rb_call_super(2, args); +} + +void +ruby_default_signal(int sig) +{ + signal(sig, SIG_DFL); + raise(sig); +} + +static RETSIGTYPE sighandler(int sig); +static int signal_ignored(int sig); +static void signal_enque(int sig); + +/* + * call-seq: + * Process.kill(signal, pid, ...) -> fixnum + * + * Sends the given signal to the specified process id(s) if _pid_ is positive. + * If _pid_ is zero _signal_ is sent to all processes whose group ID is equal + * to the group ID of the process. _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. + * The keys and values of +Signal.list+ are known signal names and numbers, + * respectively. + * + * pid = fork do + * Signal.trap("HUP") { puts "Ouch!"; exit } + * # ... do some work ... + * end + * # ... + * Process.kill("HUP", pid) + * Process.wait + * + * <em>produces:</em> + * + * Ouch! + * + * If _signal_ is an integer but wrong for signal, + * <code>Errno::EINVAL</code> or +RangeError+ will be raised. + * Otherwise unless _signal_ is a +String+ or a +Symbol+, and a known + * signal name, +ArgumentError+ will be raised. + * + * Also, <code>Errno::ESRCH</code> or +RangeError+ for invalid _pid_, + * <code>Errno::EPERM</code> when failed because of no privilege, + * will be raised. In these cases, signals may have been sent to + * preceding processes. + */ + +VALUE +rb_f_kill(int argc, const VALUE *argv) +{ +#ifndef HAVE_KILLPG +#define killpg(pg, sig) kill(-(pg), (sig)) +#endif + int negative = 0; + int sig; + int i; + VALUE str; + const char *s; + + rb_secure(2); + rb_check_arity(argc, 2, UNLIMITED_ARGUMENTS); + + switch (TYPE(argv[0])) { + case T_FIXNUM: + sig = FIX2INT(argv[0]); + break; + + case T_SYMBOL: + str = rb_sym2str(argv[0]); + goto str_signal; + + case T_STRING: + str = argv[0]; + str_signal: + s = RSTRING_PTR(str); + if (s[0] == '-') { + negative++; + s++; + } + if (strncmp(signame_prefix, s, sizeof(signame_prefix)) == 0) + s += 3; + if ((sig = signm2signo(s)) == 0) { + long ofs = s - RSTRING_PTR(str); + if (ofs) str = rb_str_subseq(str, ofs, RSTRING_LEN(str)-ofs); + rb_raise(rb_eArgError, "unsupported name `SIG%"PRIsVALUE"'", str); + } + + if (negative) + sig = -sig; + break; + + default: + str = rb_check_string_type(argv[0]); + if (!NIL_P(str)) { + goto str_signal; + } + rb_raise(rb_eArgError, "bad signal type %s", + rb_obj_classname(argv[0])); + break; + } + + if (argc <= 1) return INT2FIX(0); + + if (sig < 0) { + sig = -sig; + for (i=1; i<argc; i++) { + if (killpg(NUM2PIDT(argv[i]), sig) < 0) + rb_sys_fail(0); + } + } + else { + const rb_pid_t self = (GET_THREAD() == GET_VM()->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 SIGSTOP + case SIGSTOP: +#endif + ruby_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()->main_thread); + } + } + rb_thread_execute_interrupts(rb_thread_current()); + + return INT2FIX(i-1); +} + +static struct { + rb_atomic_t cnt[RUBY_NSIG]; + rb_atomic_t size; +} signal_buff; + +#ifdef __dietlibc__ +#define sighandler_t sh_t +#else +#define sighandler_t ruby_sighandler_t +#endif + +typedef RETSIGTYPE (*sighandler_t)(int); +#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 RETSIGTYPE ruby_sigaction_t(int); +#define SIGINFO_ARG +#define SIGINFO_CTX 0 +#endif + +#ifdef USE_SIGALTSTACK +int +rb_sigaltstack_size(void) +{ + /* XXX: BSD_vfprintf() uses >1500KiB stack and x86-64 need >5KiB stack. */ + int size = 16*1024; + +#ifdef 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 + + return size; +} + +/* alternate stack for SIGSEGV */ +void +rb_register_sigaltstack(rb_thread_t *th) +{ + stack_t newSS, oldSS; + + if (!th->altstack) + rb_bug("rb_register_sigaltstack: th->altstack not initialized\n"); + + newSS.ss_sp = th->altstack; + newSS.ss_size = rb_sigaltstack_size(); + newSS.ss_flags = 0; + + sigaltstack(&newSS, &oldSS); /* ignore error. */ +} +#endif /* USE_SIGALTSTACK */ + +#ifdef POSIX_SIGNAL +static sighandler_t +ruby_signal(int signum, sighandler_t handler) +{ + struct sigaction sigact, old; + +#if 0 + rb_trap_accept_nativethreads[signum] = 0; +#endif + + sigemptyset(&sigact.sa_mask); +#ifdef USE_SIGALTSTACK + 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; +#endif + + switch (signum) { +#ifdef SA_NOCLDWAIT + case SIGCHLD: + if (handler == SIG_IGN) + sigact.sa_flags |= SA_NOCLDWAIT; + break; +#endif +#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) + return (sighandler_t)old.sa_sigaction; + else + return old.sa_handler; +} + +sighandler_t +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) +{ + if (signum == SIGKILL) { + errno = EINVAL; + return SIG_ERR; + } + return signal(signum, handler); +} + +#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(int signum, sighandler_t handler) +{ + sighandler_t old; + + old = signal(signum, handler); + rb_trap_accept_nativethreads[signum] = 1; + return old; +} +#endif +#endif + +static int +signal_ignored(int sig) +{ + 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; +#else + sighandler_t old = signal(sig, SIG_DFL); + signal(sig, old); + func = old; +#endif + if (func == SIG_IGN) return 1; + return func == sighandler ? 0 : -1; +} + +static void +signal_enque(int sig) +{ + ATOMIC_INC(signal_buff.cnt[sig]); + ATOMIC_INC(signal_buff.size); +} + +static RETSIGTYPE +sighandler(int sig) +{ + signal_enque(sig); + rb_thread_wakeup_timer_thread(); +#if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) + ruby_signal(sig, sighandler); +#endif +} + +int +rb_signal_buff_size(void) +{ + return signal_buff.size; +} + +#if HAVE_PTHREAD_H +#include <pthread.h> +#endif + +static void +rb_disable_interrupt(void) +{ +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t mask; + sigfillset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); +#endif +} + +static void +rb_enable_interrupt(void) +{ +#ifdef HAVE_PTHREAD_SIGMASK + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); +#endif +} + +int +rb_get_next_signal(void) +{ + int i, sig = 0; + + if (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() (void)(received_signal = 0) +#else +# define clear_received_signal() ((void)0) +#endif + +#if defined(USE_SIGALTSTACK) || defined(_WIN32) +NORETURN(void ruby_thread_stack_overflow(rb_thread_t *th)); +# if !(defined(HAVE_UCONTEXT_H) && !(defined(__ANDROID_API__)) && (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 +# ifdef USE_UCONTEXT_REG +static void +check_stack_overflow(const uintptr_t addr, const ucontext_t *ctx) +{ + const DEFINE_MCONTEXT_PTR(mctx, ctx); +# if defined __linux__ +# if defined REG_RSP + const greg_t sp = mctx->gregs[REG_RSP]; +# else + const greg_t sp = mctx->gregs[REG_ESP]; +# endif +# elif defined __APPLE__ +# if defined(__LP64__) + const uintptr_t sp = mctx->__ss.__rsp; +# else + const uintptr_t sp = mctx->__ss.__esp; +# endif +# elif defined __FreeBSD__ +# if defined(__amd64__) + const __register_t sp = mctx->mc_rsp; +# else + const __register_t sp = mctx->mc_esp; +# endif +# endif + enum {pagesize = 4096}; + const uintptr_t sp_page = (uintptr_t)sp / 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) { + rb_thread_t *th = ruby_current_thread; + if ((uintptr_t)th->tag->buf / pagesize == sp_page) { + /* drop the last tag if it is close to the fault, + * otherwise it can cause stack overflow again at the same + * place. */ + th->tag = th->tag->prev; + } + clear_received_signal(); + ruby_thread_stack_overflow(th); + } +} +# else +static void +check_stack_overflow(const void *addr) +{ + int ruby_stack_overflowed_p(const rb_thread_t *, const void *); + rb_thread_t *th = ruby_current_thread; + if (ruby_stack_overflowed_p(th, addr)) { + clear_received_signal(); + ruby_thread_stack_overflow(th); + } +} +# endif +# ifdef _WIN32 +# define CHECK_STACK_OVERFLOW() check_stack_overflow(0) +# else +# define FAULT_ADDRESS info->si_addr +# ifdef USE_UCONTEXT_REG +# define CHECK_STACK_OVERFLOW() check_stack_overflow((uintptr_t)FAULT_ADDRESS, ctx) +# else +# define CHECK_STACK_OVERFLOW() check_stack_overflow(FAULT_ADDRESS) +# endif +# define MESSAGE_FAULT_ADDRESS " at %p", FAULT_ADDRESS +# 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)); +/* noinine to reduce stack usage in signal handers */ + +#define check_reserved_signal(name) check_reserved_signal_(name, sizeof(name)-1) + +#ifdef SIGBUS +static RETSIGTYPE +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_context(SIGINFO_CTX, "Bus Error" MESSAGE_FAULT_ADDRESS); +} +#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 SIGSEGV +static RETSIGTYPE +sigsegv(int sig SIGINFO_ARG) +{ + check_reserved_signal("SEGV"); + CHECK_STACK_OVERFLOW(); + rb_bug_context(SIGINFO_CTX, "Segmentation fault" MESSAGE_FAULT_ADDRESS); +} +#endif + +#ifdef SIGILL +static RETSIGTYPE +sigill(int sig SIGINFO_ARG) +{ + check_reserved_signal("ILL"); +#if defined __APPLE__ + CHECK_STACK_OVERFLOW(); +#endif + rb_bug_context(SIGINFO_CTX, "Illegal instruction" MESSAGE_FAULT_ADDRESS); +} +#endif + +static void +check_reserved_signal_(const char *name, size_t name_len) +{ + const char *prev = ATOMIC_PTR_EXCHANGE(received_signal, name); + + if (prev) { + ssize_t RB_UNUSED_VAR(err); +#define NOZ(name, str) 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]; + + iov[0].iov_base = (void *)name; + iov[0].iov_len = name_len; + iov[1].iov_base = (void *)msg1; + iov[1].iov_len = sizeof(msg1); + iov[2].iov_base = (void *)prev; + iov[2].iov_len = strlen(prev); + iov[3].iov_base = (void *)msg2; + iov[3].iov_len = sizeof(msg2); + err = writev(2, iov, 4); +#else + err = write(2, name, name_len); + err = write(2, msg1, sizeof(msg1)); + err = write(2, prev, strlen(prev)); + err = write(2, msg2, sizeof(msg2)); +#endif + ruby_abort(); + } + + ruby_disable_gc = 1; +} +#endif + +static void +signal_exec(VALUE cmd, int safe, int sig) +{ + rb_thread_t *cur_th = GET_THREAD(); + volatile unsigned long old_interrupt_mask = cur_th->interrupt_mask; + int 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; + + cur_th->interrupt_mask |= TRAP_INTERRUPT_MASK; + TH_PUSH_TAG(cur_th); + if ((state = EXEC_TAG()) == 0) { + VALUE signum = INT2NUM(sig); + rb_eval_cmd(cmd, rb_ary_new3(1, signum), safe); + } + TH_POP_TAG(); + cur_th = GET_THREAD(); + cur_th->interrupt_mask = old_interrupt_mask; + + if (state) { + /* XXX: should be replaced with rb_threadptr_pending_interrupt_enque() */ + JUMP_TAG(state); + } +} + +void +rb_trap_exit(void) +{ + rb_vm_t *vm = GET_VM(); + VALUE trap_exit = vm->trap_list[0].cmd; + + if (trap_exit) { + vm->trap_list[0].cmd = 0; + signal_exec(trap_exit, vm->trap_list[0].safe, 0); + } +} + +void +rb_signal_exec(rb_thread_t *th, int sig) +{ + rb_vm_t *vm = GET_VM(); + VALUE cmd = vm->trap_list[sig].cmd; + int safe = vm->trap_list[sig].safe; + + 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; + } + } + else if (cmd == Qundef) { + rb_threadptr_signal_exit(th); + } + else { + signal_exec(cmd, safe, sig); + } +} + +static sighandler_t +default_handler(int sig) +{ + sighandler_t func; + switch (sig) { + case SIGINT: +#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 + func = sighandler; + break; +#ifdef SIGBUS + case SIGBUS: + func = (sighandler_t)sigbus; + break; +#endif +#ifdef SIGSEGV + case SIGSEGV: + func = (sighandler_t)sigsegv; + break; +#endif +#ifdef SIGPIPE + case SIGPIPE: + func = SIG_IGN; + 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; + } + 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)) { + SafeStringValue(command); /* taint check */ + *cmd = command; + switch (RSTRING_LEN(command)) { + case 0: + goto sig_ign; + break; + case 14: + if (strncmp(RSTRING_PTR(command), "SYSTEM_DEFAULT", 14) == 0) { + func = SIG_DFL; + *cmd = 0; + } + break; + case 7: + if (strncmp(RSTRING_PTR(command), "SIG_IGN", 7) == 0) { +sig_ign: + func = SIG_IGN; + *cmd = Qtrue; + } + else if (strncmp(RSTRING_PTR(command), "SIG_DFL", 7) == 0) { +sig_dfl: + func = default_handler(sig); + *cmd = 0; + } + else if (strncmp(RSTRING_PTR(command), "DEFAULT", 7) == 0) { + goto sig_dfl; + } + break; + case 6: + if (strncmp(RSTRING_PTR(command), "IGNORE", 6) == 0) { + goto sig_ign; + } + break; + case 4: + if (strncmp(RSTRING_PTR(command), "EXIT", 4) == 0) { + *cmd = Qundef; + } + break; + } + } + else { + rb_proc_t *proc; + GetProcPtr(*cmd, proc); + (void)proc; + } + } + + return func; +} + +static int +trap_signm(VALUE vsig) +{ + int sig = -1; + const char *s; + + switch (TYPE(vsig)) { + case T_FIXNUM: + sig = FIX2INT(vsig); + if (sig < 0 || sig >= NSIG) { + rb_raise(rb_eArgError, "invalid signal number (%d)", sig); + } + break; + + case T_SYMBOL: + vsig = rb_sym2str(vsig); + s = RSTRING_PTR(vsig); + goto str_signal; + + default: + s = StringValuePtr(vsig); + + str_signal: + if (strncmp(signame_prefix, s, sizeof(signame_prefix)) == 0) + s += 3; + sig = signm2signo(s); + if (sig == 0 && strcmp(s, "EXIT") != 0) { + long ofs = s - RSTRING_PTR(vsig); + if (ofs) vsig = rb_str_subseq(vsig, ofs, RSTRING_LEN(vsig)-ofs); + rb_raise(rb_eArgError, "unsupported signal SIG%"PRIsVALUE"", vsig); + } + } + return sig; +} + +static VALUE +trap(int sig, sighandler_t func, VALUE command) +{ + sighandler_t oldfunc; + VALUE oldcmd; + rb_vm_t *vm = GET_VM(); + + /* + * Be careful. ruby_signal() and trap_list[sig].cmd 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[sig].cmd; + 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; + } + + vm->trap_list[sig].cmd = command; + vm->trap_list[sig].safe = rb_safe_level(); + + return oldcmd; +} + +static int +reserved_signal_p(int signo) +{ +/* 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, command ) -> obj + * Signal.trap( signal ) {| | block } -> obj + * + * 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 Ruby's default handler + * will be invoked. + * If the command is ``EXIT'', the script will be terminated by the signal. + * If the command is ``SYSTEM_DEFAULT'', the operating system's default + * handler will be invoked. + * 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. + * + * Signal.trap(0, proc { puts "Terminating: #{$$}" }) + * Signal.trap("CLD") { puts "Child died" } + * fork && Process.wait + * + * produces: + * Terminating: 27461 + * Child died + * Terminating: 27460 + */ +static VALUE +sig_trap(int argc, VALUE *argv) +{ + int sig; + sighandler_t func; + VALUE cmd; + + rb_secure(2); + 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); + } + + if (argc == 1) { + cmd = rb_block_proc(); + func = sighandler; + } + else { + cmd = argv[1]; + func = trap_handler(&cmd, sig); + } + + if (OBJ_TAINTED(cmd)) { + rb_raise(rb_eSecurityError, "Insecure: tainted signal trap"); + } + + return trap(sig, func, cmd); +} + +/* + * call-seq: + * Signal.list -> a_hash + * + * Returns a list of signal names mapped to the corresponding + * underlying signal numbers. + * + * 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(void) +{ + VALUE h = rb_hash_new(); + const struct signals *sigs; + + for (sigs = siglist; sigs->signm; sigs++) { + rb_hash_aset(h, rb_str_new2(sigs->signm), INT2FIX(sigs->signo)); + } + return h; +} + +static int +install_sighandler(int signum, sighandler_t handler) +{ + sighandler_t old; + + old = ruby_signal(signum, handler); + if (old == SIG_ERR) return -1; + /* signal handler should be inherited during exec. */ + if (old != SIG_DFL) { + ruby_signal(signum, old); + } + return 0; +} +#ifndef __native_client__ +# define install_sighandler(signum, handler) (install_sighandler(signum, handler) ? rb_bug(#signum) : (void)0) +#endif + +#if defined(SIGCLD) || defined(SIGCHLD) +static int +init_sigchld(int sig) +{ + sighandler_t oldfunc; + + oldfunc = ruby_signal(sig, SIG_DFL); + if (oldfunc == SIG_ERR) return -1; + if (oldfunc != SIG_DFL && oldfunc != SIG_IGN) { + ruby_signal(sig, oldfunc); + } + else { + GET_VM()->trap_list[sig].cmd = 0; + } + return 0; +} +# ifndef __native_client__ +# define init_sigchld(signum) (init_sigchld(signum) ? rb_bug(#signum) : (void)0) +# endif +#endif + +void +ruby_sig_finalize(void) +{ + sighandler_t oldfunc; + + oldfunc = ruby_signal(SIGINT, SIG_IGN); + if (oldfunc == sighandler) { + ruby_signal(SIGINT, SIG_DFL); + } +} + + +int ruby_enable_coredump = 0; +#ifndef RUBY_DEBUG_ENV +#define ruby_enable_coredump 0 +#endif + +/* + * Many operating systems allow signals to be sent to running + * processes. Some signals have a defined effect on the process, while + * others may be trapped at the code level and acted upon. For + * example, your process may trap the USR1 signal and use it to toggle + * debugging, and may use TERM to initiate a controlled shutdown. + * + * pid = fork do + * Signal.trap("USR1") do + * $debug = !$debug + * puts "Debug now: #$debug" + * end + * Signal.trap("TERM") do + * puts "Terminating..." + * shutdown() + * end + * # . . . do some work . . . + * end + * + * Process.detach(pid) + * + * # Controlling program: + * Process.kill("USR1", pid) + * # ... + * Process.kill("USR1", pid) + * # ... + * Process.kill("TERM", pid) + * + * produces: + * Debug now: true + * Debug now: false + * 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(void) +{ + 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_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); + + /* At this time, there is no subthread. Then sigmask guarantee atomics. */ + rb_disable_interrupt(); + + install_sighandler(SIGINT, sighandler); +#ifdef SIGHUP + install_sighandler(SIGHUP, sighandler); +#endif +#ifdef SIGQUIT + install_sighandler(SIGQUIT, sighandler); +#endif +#ifdef SIGTERM + install_sighandler(SIGTERM, sighandler); +#endif +#ifdef SIGALRM + install_sighandler(SIGALRM, sighandler); +#endif +#ifdef SIGUSR1 + install_sighandler(SIGUSR1, sighandler); +#endif +#ifdef SIGUSR2 + install_sighandler(SIGUSR2, sighandler); +#endif + + if (!ruby_enable_coredump) { +#ifdef SIGBUS + install_sighandler(SIGBUS, (sighandler_t)sigbus); +#endif +#ifdef SIGILL + install_sighandler(SIGILL, (sighandler_t)sigill); +#endif +#ifdef SIGSEGV +# ifdef USE_SIGALTSTACK + rb_register_sigaltstack(GET_THREAD()); +# endif + install_sighandler(SIGSEGV, (sighandler_t)sigsegv); +#endif + } +#ifdef SIGPIPE + install_sighandler(SIGPIPE, SIG_IGN); +#endif + +#if defined(SIGCLD) + init_sigchld(SIGCLD); +#elif defined(SIGCHLD) + init_sigchld(SIGCHLD); +#endif + + rb_enable_interrupt(); +} |