/***********************************************************************/
/*                                                                     */
/*                           Objective Caml                            */
/*                                                                     */
/*            Xavier Leroy, projet Cristal, INRIA Rocquencourt         */
/*                                                                     */
/*  Copyright 1996 Institut National de Recherche en Informatique et   */
/*  en Automatique.  All rights reserved.  This file is distributed    */
/*  under the terms of the GNU Library General Public License, with    */
/*  the special exception on linking described in file ../LICENSE.     */
/*                                                                     */
/***********************************************************************/

/* $Id: signals.c,v 1.64 2002/05/06 12:02:26 xleroy Exp $ */

#include <signal.h>
#include <stdio.h>
#if (defined(TARGET_sparc) && defined(SYS_solaris))
#include <ucontext.h>
#endif
#include "alloc.h"
#include "callback.h"
#include "memory.h"
#include "minor_gc.h"
#include "misc.h"
#include "mlvalues.h"
#include "fail.h"
#include "signals.h"
#include "stack.h"
#include "sys.h"
#ifdef HAS_STACK_OVERFLOW_DETECTION
#include <sys/resource.h>
#endif

extern void array_bound_error(void), division_by_zero_error(void);

#ifdef _WIN32
typedef void (*sighandler)(int sig);
extern sighandler win32_signal(int sig, sighandler action);
#define signal(sig,act) win32_signal(sig,act)
#endif

#if defined(TARGET_power) && defined(SYS_rhapsody)
/* Confer machdep/ppc/unix_signal.c and mach/ppc/thread_status.h
   in the Darwin sources */
#define CONTEXT_GPR(ctx, regno) \
  (((unsigned long *)((ctx)->sc_regs))[2 + (regno)])
#endif

#if defined(TARGET_power) && defined(SYS_aix)
#ifdef _AIXVERSION_430
#define STRUCT_SIGCONTEXT struct __sigcontext
#define CONTEXT_GPR(ctx, regno) \
  ((ctx)->__sc_jmpbuf.__jmp_context.__gpr[(regno)])
#else
#define STRUCT_SIGCONTEXT struct sigcontext
#define CONTEXT_GPR(ctx, regno) \
  ((ctx)->sc_jmpbuf.jmp_context.gpr[(regno)])
#endif
#endif

#if defined(TARGET_sparc) && defined(SYS_solaris)
#define CONTEXT_GPR(ctx, regno) \
  (ctx)->uc_mcontext.gregs[regno]
#endif

volatile int async_signal_mode = 0;
volatile int pending_signal = 0;
volatile int force_major_slice = 0;
value signal_handlers = 0;
void (*enter_blocking_section_hook)() = NULL;
void (*leave_blocking_section_hook)() = NULL;

static int rev_convert_signal_number(int signo);

/* Execute a signal handler immediately. */

void execute_signal(int signal_number, int in_signal_handler)
{
  value res;
#ifdef POSIX_SIGNALS
  sigset_t sigs;
  /* Block the signal before executing the handler, and record in sigs
     the original signal mask */
  sigemptyset(&sigs);
  sigaddset(&sigs, signal_number);
  sigprocmask(SIG_BLOCK, &sigs, &sigs);
#endif
  res = callback_exn(Field(signal_handlers, signal_number),
                     Val_int(rev_convert_signal_number(signal_number)));
#ifdef POSIX_SIGNALS
  if (! in_signal_handler) {
    /* Restore the original signal mask */
    sigprocmask(SIG_SETMASK, &sigs, NULL);
  } else if (Is_exception_result(res)) {
    /* Restore the original signal mask and unblock the signal itself */
    sigdelset(&sigs, signal_number);
    sigprocmask(SIG_SETMASK, &sigs, NULL);
  }
#endif
  if (Is_exception_result(res)) mlraise(Extract_exception(res));
}

/* This routine is the common entry point for garbage collection
   and signal handling.  It can trigger a callback to Caml code.
   With system threads, this callback can cause a context switch.
   Hence [garbage_collection] must not be called from regular C code
   (e.g. the [alloc] function) because the context of the call
   (e.g. [intern_val]) may not allow context switching.
   Only generated assembly code can call [garbage_collection],
   via the caml_call_gc assembly stubs.  */

void garbage_collection(void)
{
  int sig;

  if (young_ptr < young_start || force_major_slice) minor_collection();
  /* If a signal arrives between the following two instructions,
     it will be lost. */
  sig = pending_signal;
  pending_signal = 0;
  young_limit = young_start;
  if (sig) execute_signal(sig, 0);
}

/* Trigger a garbage collection as soon as possible */

void urge_major_slice (void)
{
  force_major_slice = 1;
  young_limit = young_end;
  /* This is only moderately effective on ports that cache young_limit
     in a register, since modify() is called directly, not through
     caml_c_call, so it may take a while before the register is reloaded
     from young_limit. */
}

void enter_blocking_section(void)
{
  int sig;

  while (1){
    Assert (!async_signal_mode);
    /* If a signal arrives between the next two instructions,
       it will be lost. */
    sig = pending_signal;
    pending_signal = 0;
    young_limit = young_start;
    if (sig) execute_signal(sig, 0);
    async_signal_mode = 1;
    if (!pending_signal) break;
    async_signal_mode = 0;
  }
  if (enter_blocking_section_hook != NULL) enter_blocking_section_hook();
}

void leave_blocking_section(void)
{
  if (leave_blocking_section_hook != NULL) leave_blocking_section_hook();
  Assert(async_signal_mode);
  async_signal_mode = 0;
}

#ifdef POSIX_SIGNALS
static void reraise(int sig, int now)
{
  struct sigaction sa;
  sa.sa_handler = 0;
  sa.sa_flags = 0;
  sigemptyset(&sa.sa_mask);
  sigaction(sig, &sa, 0);
  /* If the signal was sent using kill() (si_code == 0) or will
     not recur then raise it here.  Otherwise return.  The
     offending instruction will be reexecuted and the signal
     will recur.  */
  if (now == 1)
    raise(sig);
  return;
}
#endif

#if defined(TARGET_alpha) || defined(TARGET_mips)
void handle_signal(int sig, int code, struct sigcontext * context)
#elif defined(TARGET_power) && defined(SYS_aix)
void handle_signal(int sig, int code, STRUCT_SIGCONTEXT * context)
#elif defined(TARGET_power) && defined(SYS_elf)
void handle_signal(int sig, struct sigcontext * context)
#elif defined(TARGET_power) && defined(SYS_rhapsody)
void handle_signal(int sig, int code, struct sigcontext * context)
#elif defined(TARGET_sparc) && defined(SYS_solaris)
void handle_signal(int sig, int code, void * context)
#else
void handle_signal(int sig)
#endif
{
#if !defined(POSIX_SIGNALS) && !defined(BSD_SIGNALS)
  signal(sig, handle_signal);
#endif
  if (async_signal_mode) {
    /* We are interrupting a C function blocked on I/O.
       Callback the Caml code immediately. */
    leave_blocking_section();
    execute_signal(sig, 1);
    enter_blocking_section();
  } else {
    /* We can't execute the signal code immediately.
       Instead, we remember the signal and play with the allocation limit
       so that the next allocation will trigger a garbage collection. */
    pending_signal = sig;
    young_limit = young_end;
    /* Some ports cache young_limit in a register.
       Use the signal context to modify that register too, but not if
       we are inside C code (i.e. caml_last_return_address != 0). */
    if (caml_last_return_address == 0) {
#if defined(TARGET_alpha)
      /* Cached in register $14 */
      context->sc_regs[14] = (long) young_limit;
#endif
#if defined(TARGET_mips)
      /* Cached in register $23 */
      context->sc_regs[23] = (int) young_limit;
#endif
#if defined(TARGET_power) && defined(SYS_aix)
      /* Cached in register 30 */
      CONTEXT_GPR(context, 30) = (ulong_t) young_limit;
#endif
#if defined(TARGET_power) && defined(SYS_elf)
      /* Cached in register 30 */
      context->regs->gpr[30] = (unsigned long) young_limit;
#endif
#if defined(TARGET_power) && defined(SYS_rhapsody)
      /* Cached in register 30 */
      CONTEXT_GPR(context, 30) = (unsigned long) young_limit;
#endif
#if defined(TARGET_sparc) && defined(SYS_solaris)
      /* Cached in register l7, which is saved on the stack 7 words
	 after the unbiased stack pointer.  */
      ((long *)(CONTEXT_GPR((ucontext_t *)context, REG_SP) + 2047))[7] = (long)young_limit;
#endif
    }
  }
}

#ifndef SIGABRT
#define SIGABRT -1
#endif
#ifndef SIGALRM
#define SIGALRM -1
#endif
#ifndef SIGFPE
#define SIGFPE -1
#endif
#ifndef SIGHUP
#define SIGHUP -1
#endif
#ifndef SIGILL
#define SIGILL -1
#endif
#ifndef SIGINT
#define SIGINT -1
#endif
#ifndef SIGKILL
#define SIGKILL -1
#endif
#ifndef SIGPIPE
#define SIGPIPE -1
#endif
#ifndef SIGQUIT
#define SIGQUIT -1
#endif
#ifndef SIGSEGV
#define SIGSEGV -1
#endif
#ifndef SIGTERM
#define SIGTERM -1
#endif
#ifndef SIGUSR1
#define SIGUSR1 -1
#endif
#ifndef SIGUSR2
#define SIGUSR2 -1
#endif
#ifndef SIGCHLD
#define SIGCHLD -1
#endif
#ifndef SIGCONT
#define SIGCONT -1
#endif
#ifndef SIGSTOP
#define SIGSTOP -1
#endif
#ifndef SIGTSTP
#define SIGTSTP -1
#endif
#ifndef SIGTTIN
#define SIGTTIN -1
#endif
#ifndef SIGTTOU
#define SIGTTOU -1
#endif
#ifndef SIGVTALRM
#define SIGVTALRM -1
#endif
#ifndef SIGPROF
#define SIGPROF -1
#endif

static int posix_signals[] = {
  SIGABRT, SIGALRM, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE,
  SIGQUIT, SIGSEGV, SIGTERM, SIGUSR1, SIGUSR2, SIGCHLD, SIGCONT,
  SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGVTALRM, SIGPROF
};

int convert_signal_number(int signo)
{
  if (signo < 0 && signo >= -(sizeof(posix_signals) / sizeof(int)))
    return posix_signals[-signo-1];
  else
    return signo;
}

static int rev_convert_signal_number(int signo)
{
  int i;
  for (i = 0; i < sizeof(posix_signals) / sizeof(int); i++)
    if (signo == posix_signals[i]) return -i - 1;
  return signo;
}

#ifndef NSIG
#define NSIG 64
#endif

value install_signal_handler(value signal_number, value action) /* ML */
{
  CAMLparam2 (signal_number, action);
  int sig;
  void (*act)(int signo), (*oldact)(int signo);
#ifdef POSIX_SIGNALS
  struct sigaction sigact, oldsigact;
#endif
  CAMLlocal1 (res);

  sig = convert_signal_number(Int_val(signal_number));
  if (sig < 0 || sig >= NSIG) 
    invalid_argument("Sys.signal: unavailable signal");
  switch(action) {
  case Val_int(0):              /* Signal_default */
    act = SIG_DFL;
    break;
  case Val_int(1):              /* Signal_ignore */
    act = SIG_IGN;
    break;
  default:                      /* Signal_handle */
    act = (void (*)(int)) handle_signal;
    break;
  }
#ifdef POSIX_SIGNALS
  sigact.sa_handler = act;
  sigemptyset(&sigact.sa_mask);
#ifdef SYS_solaris
  sigact.sa_flags = SA_SIGINFO;
#else
  sigact.sa_flags = 0;
#endif
  if (sigaction(sig, &sigact, &oldsigact) == -1) sys_error(NO_ARG);
  oldact = oldsigact.sa_handler;
#else
  oldact = signal(sig, act);
  if (oldact == SIG_ERR) sys_error(NO_ARG);
#endif
  if (oldact == (void (*)(int)) handle_signal) {
    res = alloc_small(1, 0);          /* Signal_handle */
    Field(res, 0) = Field(signal_handlers, sig);
  }
  else if (oldact == SIG_IGN)
    res = Val_int(1);           /* Signal_ignore */
  else
    res = Val_int(0);           /* Signal_default */
  if (Is_block(action)) {
    if (signal_handlers == 0) {
      signal_handlers = alloc(NSIG, 0);
      register_global_root(&signal_handlers);
    }
    modify(&Field(signal_handlers, sig), Field(action, 0));
  }
  CAMLreturn (res);
}

/* Machine- and OS-dependent handling of bound check trap */

#if defined(TARGET_sparc) && defined(SYS_sunos)
static void trap_handler(int sig, int code, 
                         struct sigcontext * context, char * address)
{
  int * sp;
  /* Unblock SIGILL */
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGILL);
  sigprocmask(SIG_UNBLOCK, &mask, NULL);
  if (code != ILL_TRAP_FAULT(5)) {
    fprintf(stderr, "Fatal error: illegal instruction, code 0x%x\n", code);
    exit(100);
  }
  /* Recover young_ptr and caml_exception_pointer from the %l5 and %l6 regs */
  sp = (int *) context->sc_sp;
  caml_exception_pointer = (char *) sp[5];
  young_ptr = (char *) sp[6];
  array_bound_error();
}
#endif

#if defined(TARGET_i386) && defined(SYS_linux_elf)

static void trap_handler(int sig, siginfo_t * info, void * arg)
{
  if (info->si_code != FPE_INTDIV) {
    reraise(SIGFPE, 1);
    return;
  }

  /* No need to change registers; i386 does not cache gc values. */
  division_by_zero_error();
}
#endif

#if defined(TARGET_sparc) && defined(SYS_solaris)

static void trap_handler(int sig, siginfo_t * info, void * arg)
{
  ucontext_t * context;
  long * sp;
  int code = info->si_code;

  switch (sig)
    {
    case SIGILL:
      if (code != ILL_ILLTRP) {
	fprintf(stderr, "Fatal error: illegal instruction, code 0x%x\n",
		code);
	/* Illegal instruction exceptions occur before the instruction
	   completes and will recur on return from the signal handler. */
	reraise(SIGILL, code == 0);
	return;
	/*exit(100);*/
      }
      break;
    case SIGFPE:
      if (code != FPE_INTDIV) {
	/* Floating point exceptions occur after the instruction completes
	   and will not recur on return from the signal handler. */
	reraise(SIGFPE, 1);
	return;
      }
      break;
    }
  /* Recover young_ptr and caml_exception_pointer from the %l5 and %l6 regs. */
  context = (ucontext_t *) arg;
#ifdef __sparcv9
  sp = (long *) (CONTEXT_GPR(context, REG_SP) + 2047);
#else
  sp = (long *) (CONTEXT_GPR(context, REG_SP));
#endif
  caml_exception_pointer = (char *) sp[5];	/* %l5 */
  young_ptr = (char *) sp[6];			/* %l6 */
  switch (sig)
    {
    case SIGILL:
      array_bound_error();
      break;
    case SIGFPE:
      division_by_zero_error();
      break;
    }
}
#endif

#if defined(TARGET_sparc) && (defined(SYS_bsd) || defined(SYS_linux))
static void trap_handler(int sig)
{
  /* TODO: recover registers from context and call array_bound_error */
  fatal_error("Fatal error: out-of-bound access in array or string\n");
}
#endif

#if defined(TARGET_power) && defined(SYS_aix)
static void trap_handler(int sig, int code, STRUCT_SIGCONTEXT * context)
{
  /* Unblock SIGTRAP */
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGTRAP);
  sigprocmask(SIG_UNBLOCK, &mask, NULL);
  /* Recover young_ptr and caml_exception_pointer from registers 31 and 29 */
  caml_exception_pointer = (char *) CONTEXT_GPR(context, 29);
  young_ptr = (char *) CONTEXT_GPR(context, 31);
  array_bound_error();
}
#endif

#if defined(TARGET_power) && defined(SYS_elf)
static void trap_handler(int sig, struct sigcontext * context)
{
  /* Recover young_ptr and caml_exception_pointer from registers 31 and 29 */
  caml_exception_pointer = (char *) context->regs->gpr[29];
  young_ptr = (char *) context->regs->gpr[31];
  array_bound_error();
}
#endif

#if defined(TARGET_power) && defined(SYS_rhapsody)
static void trap_handler(int sig, int code, struct sigcontext * context)
{
  /* Unblock SIGTRAP */
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGTRAP);
  sigprocmask(SIG_UNBLOCK, &mask, NULL);
  /* Recover young_ptr and caml_exception_pointer from registers 31 and 29 */
  caml_exception_pointer = (char *) CONTEXT_GPR(context, 29);
  young_ptr = (char *) CONTEXT_GPR(context, 31);
  array_bound_error();
}
#endif

/* Machine- and OS-dependent handling of stack overflow */

#ifdef HAS_STACK_OVERFLOW_DETECTION

static char * system_stack_top;
static char sig_alt_stack[SIGSTKSZ];

static int is_stack_overflow(char * fault_addr)
{
  struct rlimit limit;
  struct sigaction act;

  /* Sanity checks:
     - faulting address is word-aligned
     - faulting address is within the stack */
  if (((long) fault_addr & (sizeof(long) - 1)) == 0 &&
      getrlimit(RLIMIT_STACK, &limit) == 0 &&
      fault_addr < system_stack_top &&
      fault_addr >= system_stack_top - limit.rlim_cur - 0x2000) {
    /* OK, caller can turn this into a Stack_overflow exception */
    return 1;
  } else {
    /* Otherwise, deactivate our exception handler.  Caller will
       return, causing fatal signal to be generated at point of error. */
    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGSEGV, &act, NULL);
    return 0;
  }
}

#if defined(TARGET_i386) && defined(SYS_linux_elf)
static void segv_handler(int signo, struct sigcontext sc)
{
  if (is_stack_overflow((char *) sc.cr2))
    raise_stack_overflow();
}
#endif

#if defined(TARGET_i386) && !defined(SYS_linux_elf)
static void segv_handler(int signo, siginfo_t * info, void * arg)
{
  if (is_stack_overflow((char *) info->si_addr))
    raise_stack_overflow();
}
#endif

#endif

/* Initialization of signal stuff */

void init_signals(void)
{
  /* Bound-check trap handling */
#if defined(TARGET_sparc) && \
      (defined(SYS_sunos) || defined(SYS_bsd) || defined(SYS_linux))
  {
    struct sigaction act;
    act.sa_handler = (void (*)(int)) trap_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGILL, &act, NULL);
  }
#endif
#if defined(TARGET_sparc) && defined(SYS_solaris)
  {
    struct sigaction act;
    act.sa_sigaction = trap_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO | SA_NODEFER;
    sigaction(SIGILL, &act, NULL);
    sigaction(SIGFPE, &act, NULL);
  }
#endif
#if defined(TARGET_i386) && defined(SYS_linux_elf)
  {
    struct sigaction act;
    act.sa_sigaction = trap_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO | SA_NODEFER;
    sigaction(SIGFPE, &act, NULL);
  }
#endif
#if defined(TARGET_power)
  {
    struct sigaction act;
    act.sa_handler = (void (*)(int)) trap_handler;
    sigemptyset(&act.sa_mask);
#if defined(SYS_rhapsody) || defined(SYS_aix)
    act.sa_flags = 0;
#else
    act.sa_flags = SA_NODEFER;
#endif
    sigaction(SIGTRAP, &act, NULL);
  }
#endif
  /* Stack overflow handling */
#ifdef HAS_STACK_OVERFLOW_DETECTION
  {
    struct sigaltstack stk;
    struct sigaction act;
    stk.ss_sp = sig_alt_stack;
    stk.ss_size = SIGSTKSZ;
    stk.ss_flags = 0;
#if defined(TARGET_i386) && defined(SYS_linux_elf)
    act.sa_handler = (void (*)(int)) segv_handler;
    act.sa_flags = SA_ONSTACK | SA_NODEFER;
#else
    act.sa_sigaction = segv_handler;
    act.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
#endif
    sigemptyset(&act.sa_mask);
    system_stack_top = (char *) &act;
    if (sigaltstack(&stk, NULL) == 0) { sigaction(SIGSEGV, &act, NULL); }
  }
#endif
}
