/*
 *
 * s i g n a l . c			-- Signal handling
 *
 * Copyright © 1993-1999 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
 * 
 *
 * Permission to use, copy, modify, distribute,and license this
 * software and its documentation for any purpose is hereby granted,
 * provided that existing copyright notices are retained in all
 * copies and that this notice is included verbatim in any
 * distributions.  No written agreement, license, or royalty fee is
 * required for any of the authorized uses.
 * This software is provided ``AS IS'' without express or implied
 * warranty.
 *
 *           Author: Erick Gallesio [eg@unice.fr]
 *    Creation date: 10-Oct-1995 07:55
 * Last file update:  3-Sep-1999 20:22 (eg)
 *
 */

#include "stk.h"
#include <signal.h>

#ifdef HAVE_SIGACTION
   static sigset_t global_sigset;
#  ifdef SA_RESTART
#    define SA_FLAGS SA_RESTART
#  else 
#    define SA_FLAGS 0
#  endif
#endif

int STk_control_C = 0;

static SCM signals[MAX_SIGNAL];

/* Convention: signals[i] can be 
 * 			- #f if signal is ignored
 *			- #t if signal is set to default
 *			- a list of handlers
 */

static void execute_signal_handlers(int sig, SCM handlers)
{
  SCM arg = LIST1(STk_makeinteger(sig));

  for ( ; CONSP(handlers); handlers = CDR(handlers)) {
    if (Apply(CAR(handlers), arg) == Sym_break) break;
  } 
}


#ifdef SIGSEGV
static void handle_sigsegv_signal(void)
{
  SCM l = signals[SIGSEGV];
  
  if (l == NIL) {
    /* User has not redefined SIGSEGV action */
#ifdef HAVE_SIGACTION
    /* See comment in procedure above */
    sigset_t set;
    
    sigemptyset(&set);
    sigaddset(&set, SIGSEGV);
    sigprocmask(SIG_UNBLOCK, &set, NULL);
#endif
    /*  SEGV occurs generally when there is an error in the
     * interpreter. We cannot do a lot of things here, excepted
     * signalling this fact and go back to toplevel
     */
    Err("Segmentation violation: Returning to toplevel", NIL);
  }
  else 
    execute_signal_handlers(SIGSEGV, l);
}
#endif


void STk_handle_sigint_signal(void)
{
  SCM l = signals[SIGINT];
  
  if (l == NIL) {
    /* User has not redefined ^C action */
#ifdef HAVE_SIGACTION
    /* Empty the signal mask before calling a longjmp and don't let the
     *  system (at least Linux) a chance to restore the mask 
     */
    sigset_t set;
    
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_UNBLOCK, &set, NULL);
#endif
    longjmp(STk_err_handler->j, JMP_INTERRUPT);
  }
  else
    execute_signal_handlers(SIGINT, l);
}

/*===========================================================================*\
 *
 *				 STk_handle_signal
 *
\*===========================================================================*/

void STk_handle_signal(int sig)
{
#ifndef  HAVE_SIGACTION
  if (sig < MAX_SYSTEM_SIG)
    signal(sig, STk_handle_signal);
#endif

  if (sig == SIGINT) {			/* SIGINT */
    Puts("*** Interrupt ***\n", STk_curr_eport); Flush(STk_curr_eport);
    STk_control_C = 1; STk_eval_flag = 1;
  }
#ifdef SIGSEGV
  else if (sig == SIGSEGV) {		/* SIGSEGV */
    handle_sigsegv_signal();
  }
#endif
  else
    execute_signal_handlers(sig, signals[sig]);
}


static SCM set_handler(long s, SCM proc)
{
  void (*handler)();

  if (BOOLEANP(proc))
    /* Special case #f means ignore and #t means default */
    handler = (proc == Truth) ? SIG_DFL: SIG_IGN;
  else
    /* Use our handler */
    handler = STk_handle_signal;
     
  if (s < MAX_SYSTEM_SIG)
#ifdef HAVE_SIGACTION
    {
      struct sigaction sigact;
      
      sigfillset(&(sigact.sa_mask));
      /* FIXME: => handler non interruptible mais est-ce un problème en pratique? */
      /* sigdelset(&(sigact.sa_mask), SIGINT);  */
      sigact.sa_handler = handler;
      sigact.sa_flags   = SA_FLAGS;
      sigaction(s, &sigact, NULL);
    }
#else
    signal(s, handler);
#endif
  return signals[s] = proc;
}


PRIMITIVE STk_set_signal_handler(SCM sig, SCM proc)
{
  long s = STk_integer_value(sig);

  ENTER_PRIMITIVE("set-signal-handler!");
  
  if (s == LONG_MIN || s < 0 || s >= MAX_SIGNAL) Serror("bad signal number", sig);

  if (BOOLEANP(proc))
    return set_handler(s, proc);
  else {
    if (STk_procedurep(proc) == Ntruth) Serror("bad procedure", proc);
    return set_handler(s, LIST1(proc));
  }
}

  
PRIMITIVE STk_add_signal_handler(SCM sig, SCM proc)
{
  long s = STk_integer_value(sig);

  ENTER_PRIMITIVE("add-signal-handler!");

  if (s == LONG_MIN || s < 0 || s >= MAX_SIGNAL) Serror("bad signal number", sig);

  if (STk_procedurep(proc) == Ntruth) Serror("bad procedure", proc);

  if (BOOLEANP(signals[s]))
    /* We add a handler on a defaulted or ignored signal */
    return  set_handler(s, LIST1(proc));
  else
    /* Just add the new signal in front of the list */
    return signals[s] = Cons(proc,signals[s]);
}

PRIMITIVE STk_get_signal_handlers(SCM sig)
{
  if (sig == UNBOUND) {				/* Return all handlers */
    SCM z;
  
    z = STk_makevect(MAX_SIGNAL, NULL);
    memcpy(VECT(z), signals, MAX_SIGNAL * sizeof(SCM));
    return z; 
  }
  else {					/* Return handlers of sig only */
    long s = STk_integer_value(sig);

    if (s == LONG_MIN || s < 0 || s >= MAX_SIGNAL)
      STk_err("get-signal-handlers: bad signal number", sig);
    return signals[s];
  }
}


PRIMITIVE STk_send_signal(SCM sig)
{
  long s = STk_integer_value(sig);
  
  ENTER_PRIMITIVE("send-signal");

  if (s < 0 || s >= MAX_SIGNAL) Serror("bad signal number", sig);
  
  STk_handle_signal(s);
  return UNDEFINED;
}


void STk_mark_signal_table(void)
{
  int i;

  for (i = 0; i < MAX_SIGNAL; i++) 
    STk_gc_mark(signals[i]);
}


void STk_ignore_signals(void)	/* Block all signals */
{
#ifdef HAVE_SIGACTION
  sigset_t new;

  sigfillset(&new);
  sigprocmask(SIG_BLOCK, &new, &global_sigset);
#endif
}

void STk_allow_signals(void)  /* Restore signals as  before ignore_signals */
{
#ifdef HAVE_SIGACTION
  sigprocmask(SIG_SETMASK, &global_sigset, NULL);
#endif
}


void STk_signal_GC(void)
{
  SCM old = signals[SIGHADGC];
  
  STk_ignore_signals();
  /* Ignore this signal while executing the handler */
  signals[SIGHADGC] = Ntruth;
  execute_signal_handlers(SIGHADGC, old);
  signals[SIGHADGC] = old;
  STk_allow_signals();
}
 
/*******************************************************************************/


void STk_init_signal(void)
{
  int i;


  for (i = 0; i < MAX_SIGNAL; i++) {
    set_handler(i, (
#ifdef SIGTSTP
      (i==SIGTSTP) || /* ^Z is set to SIG_DFLT to allow program supension */
#endif
#ifdef SIGABRT
      (i== SIGABRT) || /* Really abort when receiving  ABORT (loops otherwise)*/
#endif
#ifdef SIGQUIT
      (i == SIGQUIT) || /* ditto for QUIT */
#endif
      0) ? Truth : NIL);
  }
  /* 
   * POSIX.1 signals 
   */
#ifdef SIGABRT
  STk_define_scheme_variable("SIGABRT", STk_makeinteger(SIGABRT));
#endif
#ifdef SIGALRM
  STk_define_scheme_variable("SIGALRM", STk_makeinteger(SIGALRM));
#endif
#ifdef SIGFPE
  STk_define_scheme_variable("SIGFPE", STk_makeinteger(SIGFPE));
#endif
#ifdef SIGHUP
  STk_define_scheme_variable("SIGHUP", STk_makeinteger(SIGHUP));
#endif
#ifdef SIGILL
  STk_define_scheme_variable("SIGILL", STk_makeinteger(SIGILL));
#endif
#ifdef SIGINT
  STk_define_scheme_variable("SIGINT", STk_makeinteger(SIGINT));
#endif
#ifdef SIGKILL
  STk_define_scheme_variable("SIGKILL", STk_makeinteger(SIGKILL));
#endif
#ifdef SIGPIPE
  STk_define_scheme_variable("SIGPIPE", STk_makeinteger(SIGPIPE));
#endif
#ifdef SIGQUIT
  STk_define_scheme_variable("SIGQUIT", STk_makeinteger(SIGQUIT));
#endif
#ifdef SIGSEGV
  STk_define_scheme_variable("SIGSEGV", STk_makeinteger(SIGSEGV));
#endif
#ifdef SIGTERM
  STk_define_scheme_variable("SIGTERM", STk_makeinteger(SIGTERM));
#endif
#ifdef SIGUSR1
  STk_define_scheme_variable("SIGUSR1", STk_makeinteger(SIGUSR1));
#endif
#ifdef SIGUSR2
  STk_define_scheme_variable("SIGUSR2", STk_makeinteger(SIGUSR2));
#endif

  /* 
   * Following signals exist only on system which support Job Control 
   */
#ifdef SIGCHLD
  STk_define_scheme_variable("SIGCHLD", STk_makeinteger(SIGCHLD));
#endif
#ifdef SIGCONT
  STk_define_scheme_variable("SIGCONT", STk_makeinteger(SIGCONT));
#endif
#ifdef SIGSTOP
  STk_define_scheme_variable("SIGSTOP", STk_makeinteger(SIGSTOP));
#endif
#ifdef SIGTSTP
  STk_define_scheme_variable("SIGTSTP", STk_makeinteger(SIGTSTP));
#endif
#ifdef SIGTTIN
  STk_define_scheme_variable("SIGTTIN", STk_makeinteger(SIGTTIN));
#endif
#ifdef SIGTTOU
  STk_define_scheme_variable("SIGTTOU", STk_makeinteger(SIGTTOU));
#endif

  /*
   * Non POSIX signals stolen on Sun and Linux
   */

#ifdef SIGTRAP
  STk_define_scheme_variable("SIGTRAP", STk_makeinteger(SIGTRAP));
#endif
#ifdef SIGIOT
  STk_define_scheme_variable("SIGIOT", STk_makeinteger(SIGIOT));
#endif
#ifdef SIGEMT
  STk_define_scheme_variable("SIGEMT", STk_makeinteger(SIGEMT));
#endif
#ifdef SIGBUS
  STk_define_scheme_variable("SIGBUS", STk_makeinteger(SIGBUS));
#endif
#ifdef SIGSYS
  STk_define_scheme_variable("SIGSYS", STk_makeinteger(SIGSYS));
#endif
#ifdef SIGURG
  STk_define_scheme_variable("SIGURG", STk_makeinteger(SIGURG));
#endif
#ifdef SIGCLD
  STk_define_scheme_variable("SIGCLD", STk_makeinteger(SIGCLD));
#endif
#ifdef SIGIO
  STk_define_scheme_variable("SIGIO", STk_makeinteger(SIGIO));
#endif
#ifdef SIGPOLL
  STk_define_scheme_variable("SIGPOLL", STk_makeinteger(SIGPOLL));
#endif
#ifdef SIGXCPU
  STk_define_scheme_variable("SIGXCPU", STk_makeinteger(SIGXCPU));
#endif
#ifdef SIGXFSZ
  STk_define_scheme_variable("SIGXFSZ", STk_makeinteger(SIGXFSZ));
#endif
#ifdef SIGVTALRM
  STk_define_scheme_variable("SIGVTALRM", STk_makeinteger(SIGVTALRM));
#endif
#ifdef SIGPROF
  STk_define_scheme_variable("SIGPROF", STk_makeinteger(SIGPROF));
#endif
#ifdef SIGWINCH
  STk_define_scheme_variable("SIGWINCH", STk_makeinteger(SIGWINCH));
#endif
#ifdef SIGLOST
  STk_define_scheme_variable("SIGLOST", STk_makeinteger(SIGLOST));
#endif

  /* Add GC signal */
  STk_define_scheme_variable("SIGHADGC", STk_makeinteger(SIGHADGC));
}
