/*
 * $Id: signal.c,v 2.2 2000/05/23 08:01:33 masaki Exp $
 */

#include <mrt.h>
#include <timer.h>

#ifdef NT
#include <signal.h>
#ifndef SIGALRM
#define SIGALRM		SIGTERM                 // NT does not use SIGTERM and SIGALRM is not supported
#endif /* SIGALRM */
#endif	/* NT */	

#if defined(__linux__) && defined(HAVE_LIBPTHREAD)
/* Linux pthread is different from Posix Pthread on asynchronous
   signal handling of SIGALRM that is used to run timers in MRT.
   The signal is delivered to the thread having called alarm().
   The alarm value may be wanted to change from other running threrads, but
   it's not easy to change the alarm value while waiting sigwait() by
   the thread waiting on.  So, with linux pthread, the signal handler 
   just sets a flag as if there is no thread support. All threads
   may be interrupted by the alarm signal. Don't block SIGALRM. */
#endif

void
init_mrt_thread_signals ()
{
#ifdef HAVE_LIBPTHREAD
    sigset_t set;

    sigemptyset (&set);
#ifndef __linux__
    sigaddset (&set, SIGALRM);
#endif /* __linux__ */
    sigaddset (&set, SIGHUP);
    sigaddset (&set, SIGINT);
    sigaddset (&set, SIGPIPE);
    pthread_sigmask (SIG_BLOCK, &set, NULL);
#endif /* HAVE_LIBPTHREAD */
}


/* mrt_process_signal
 * invoke registered handler routines, and exit on sigint
 */
void mrt_process_signal (int sig) {

  /* this may happen during trace(), so don't use trace() again. */
  /* trace (TR_WARN, MRT->trace, "MRT signal (%d) received\n", sig); */
  if (sig == SIGINT) MRT->force_exit_flag = sig;
  /* Should we exit in all cases? */
  signal (sig, mrt_process_signal);
}


/* function where alarm is delivered */
static void_fn_t timer_fire_fn = NULL;

#if !defined(HAVE_LIBPTHREAD) || defined (__linux__)
/* Thread uses sigwait(), instead */

static int alarm_pending = 0;
#ifdef HAVE_LIBPTHREAD
static int thread_id = 0;
#endif /* HAVE_LIBPTHREAD */

/* This is the only function that's invoked by asynchronous signal.
   most functions are not asynchronous signal safe, so don't call
   them during interrupt. Timers MRT uses are second order so that
   they don't require rapid real-time processing */
void 
alarm_interrupt (/* void */)
{
    alarm_pending++;
#ifdef HAVE_LIBPTHREAD
#ifdef NT
	// not sure why but need to "rebind" SIGALRM to alarm_interrupt. Ken 6/21/99
	signal (SIGALRM,  (void *) alarm_interrupt); 
#endif /* NT */
    thread_id = pthread_self ();
#endif
}
#endif /* HAVE_LIBPTHREAD */


/* check to see if alarm is pending */
void
mrt_alarm (void)
{
#if defined(HAVE_LIBPTHREAD) && !defined(__linux__)
    int sig;
    sigset_t set;

    sigemptyset (&set);
    /* other threads block these signals. taken care of here */
    sigaddset (&set, SIGALRM);
    sigaddset (&set, SIGHUP);
    sigaddset (&set, SIGINT);
    sigaddset (&set, SIGPIPE);

    /* wait on alarm */
    if (sigwait (&set, &sig) < 0) {
       trace (TR_TIMER, TIMER_MASTER->trace, "sigwait: %m\n");
    }
    else {
	if (sig == SIGALRM) {
	    if (timer_fire_fn)
	        timer_fire_fn ();
	}
	else if (sig >= 0)
	    mrt_process_signal (sig);
    }
#else
    if (alarm_pending) {
#ifdef HAVE_LIBPTHREAD
       trace (TR_TIMER, TIMER_MASTER->trace, "ALRM caught by thread %d\n", 
	      thread_id);
#endif
	alarm_pending = 0;
	if (timer_fire_fn)
	    timer_fire_fn ();
    }
#ifdef HAVE_LIBPTHREAD
    /* blocking is OK since this thread is idling */
    pause ();
#endif
#endif /* HAVE_LIBPTHREAD */
}

#ifdef notdef
sigset_t
block_signal (int sig)
{
    sigset_t new, old;

    sigemptyset (&new);
    sigaddset (&new, sig);
#ifdef HAVE_LIBPTHREAD
    pthread_sigmask (SIG_BLOCK, &new, &old);
#else
    sigprocmask (SIG_BLOCK, &new, &old);
#endif /* HAVE_LIBPTHREAD */
    return (old);
}


void
recover_signal (sigset_t old)
{
#ifdef HAVE_LIBPTHREAD
    pthread_sigmask (SIG_SETMASK, &old, NULL);
#else
    sigprocmask (SIG_SETMASK, &old, NULL);
#endif /* HAVE_LIBPTHREAD */
}
#endif


void
init_signal (trace_t *tr, void_fn_t fn)
{
#ifndef NT
    sigset_t set;
#endif /* NT */

#if !defined(HAVE_LIBPTHREAD) || defined(__linux__)
#ifdef HAVE_SIGACTION /* POSIX actually */
    {
    	struct sigaction act;

        memset (&act, 0, sizeof (act));
        act.sa_handler = alarm_interrupt;
#ifdef SA_RESTART
        act.sa_flags = SA_RESTART; 
#endif
        sigaction (SIGALRM, &act, NULL);
    }
#else
#ifdef HAVE_SIGSET
    sigset (SIGALRM, alarm_interrupt);
#else
    signal (SIGALRM, alarm_interrupt);
#endif /* HAVE_SIGSET */
#endif /* POSIX signals */
#endif /* HAVE_LIBPTHREAD */

   /* This init routine must be called from the main thread
      that handles alarm interrupts before creations of threads */
#ifndef NT
    sigemptyset (&set);
    sigaddset (&set, SIGALRM);
#endif /* NT */
#if defined(HAVE_LIBPTHREAD) && !defined(__linux__)
    /* sigwait() will be used so that ALARM must be blocked 
       even in the main thread */
    pthread_sigmask (SIG_BLOCK, &set, NULL);
#else
    /* enables alarm interrupts */
#ifndef NT
    pthread_sigmask (SIG_UNBLOCK, &set, NULL);
#endif /* NT */
#endif /* HAVE_LIBPTHREAD */
    timer_fire_fn = fn;
}
