/*****************************************************************************
 *                                                                           *
 * DH_SIG.C                                                                  *
 *                                                                           *
 * Freely redistributable and modifiable.  Use at your own risk.             *
 *                                                                           *
 * Copyright 1994 The Downhill Project                                       *
 *                                                                           *
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include "psignal.h"
extern int UserBreak;

/* Define stuff ************************************************************ */
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif

/* Signal groupings ======================================================== */
#define SIGNAL_DEFAULT_EXIT         case SIGABRT:               \
				    case SIGFPE:                \
				    case SIGILL:                \
				    case SIGINT:                \
				    case SIGSEGV:               \
				    case SIGTERM:

#define IS_SIGNAL(a)        ( ((a) > 0 ) &&  ((a) < NSIG))


/* Struct stuff **************************************************************/
struct downhill_Signal_Struct
{
    void     (*signal_Handler)(int);
    int      signal_Count;
    int      signal_Extra;
    sigset_t signal_Mask;
    int      signal_Flags;
};


/* Static stuff **************************************************************/
static struct downhill_Signal_Struct* downhill_Signal_Info = NULL;
static sigset_t                       downhill_Sigset_Mask = 0;
static HANDLE                            IGotASignal;

/* Function stuff ************************************************************/


/* Handle a signal ========================================================= */
void raise(int signal_Number)
{
    if (!IS_SIGNAL(signal_Number)) {
	errno = EINVAL;
	return;
    }
    /* Check to see if we're masking this signal */
    if (sigismember(&downhill_Sigset_Mask,signal_Number))
    {
	downhill_Signal_Info[signal_Number].signal_Count++;
    }
    else
    {
	/* Do a signal's action */
	if (downhill_Signal_Info[signal_Number].signal_Handler ==
	    SIG_DFL)
	{
	    switch (signal_Number)
	    {
		SIGNAL_DEFAULT_EXIT
		    exit(3);
	    default:
		break;
	    }
	}
	else if (
	    (downhill_Signal_Info[signal_Number].signal_Handler ==
	     SIG_IGN)
	    )
	{/* IGNORE */}
	else
	{
	    void (*signal_HandlerOld)(int);
	    /* Do we reset the handler? */
	    signal_HandlerOld =
		downhill_Signal_Info[signal_Number].signal_Handler;
	    if (downhill_Signal_Info[signal_Number].signal_Flags&
		SA_RESETHAND)
	    {
		downhill_Signal_Info[signal_Number].
		    signal_Handler = SIG_DFL;
	    }
	    /* Do the action */
	    if ((signal_Number == SIGCHLD) &&
		(downhill_Signal_Info[signal_Number].signal_Flags&
		 SA_NOCLDSTOP))
	    {
		/* Ignore SIGCHLD */
	    }
	    else
	    {
		sigset_t sigset_MaskOriginal =
		    downhill_Sigset_Mask;
		sigset_t sigset_MaskNew = downhill_Signal_Info[
		    signal_Number].signal_Mask;

		/* Set the new signal mask */
		sigaddset(&sigset_MaskNew,signal_Number);
		sigprocmask(SIG_BLOCK,&sigset_MaskNew,NULL);

		/* Execute the handler */
		signal_HandlerOld(signal_Number);

		/* Restore the signal mask */
		sigprocmask(SIG_SETMASK,&sigset_MaskOriginal,
			    NULL);

	    }
	}
	PulseEvent(IGotASignal);
    }
}

/* Init the signal re-direction ============================================ */

/* Hardware interrupt handler.  */
static BOOL CALLBACK hwIntrHandler (DWORD type)
{
    int ret;
    switch (type) {
    case CTRL_C_EVENT :
    case CTRL_BREAK_EVENT :
	/*
	  Why SIGBREAK? SIGINT is used internally by R and the signal
	  handler for SIGINT ends with a 'longjmp'. But, under Windows,
	  hardware interrupt handler runs in a different thread. So,
	  longjmp fails (tipically it will crash R). So, we raise a SIGBREAK
	  to record that the user want to stop. This is then
	  processed at due time. Drawback of this approach: if R is lost
	  inside a C or Fortran routine we don't break it. I (g.m.) have
	  tried to raise a signal in the appropriate thread using
	  SuspendThread/GetThreadContext/setting Eip to the signal handler/
	  SetThreadContext/ResumeThread but I had success only under NT.
	*/
	raise(SIGBREAK);
	/* Seems that SIGBREAK is not working under 1,4,0, so do it via
	   a semaphore, as RGui does */
	UserBreak = 1;
	ret = TRUE;
	break;
    default:
	ret = FALSE;
    }
    return ret ;
}

static int downhill_Signal_Init(void)
{
    /* Skip this if we've already done it */
    if (downhill_Signal_Info == NULL)
    {
	if (!(downhill_Signal_Info =
	      calloc(sizeof(struct downhill_Signal_Struct),NSIG)) ||
	    !(IGotASignal=CreateEvent(NULL,FALSE,FALSE,NULL)) ||
	    !SetConsoleCtrlHandler (hwIntrHandler, TRUE))

	{
	    if (downhill_Signal_Info) free(downhill_Signal_Info);
	    if (IGotASignal) CloseHandle(IGotASignal);
	    errno = ENOMEM;
	    return FALSE;
	}
    }
    return TRUE;
}

/* Set a signal action ===================================================== */
int sigaction(int signal_Number,struct sigaction* sigaction_Info,
     struct sigaction* sigaction_InfoOld)
{
    /* Make sure we're init'd */
    if (!downhill_Signal_Init())
    {
	return -1;
    }

    /* Set the signal */
    if (IS_SIGNAL(signal_Number)) {
	if (sigaction_InfoOld != NULL)
	{
	    sigaction_InfoOld->sa_handler =
		downhill_Signal_Info[signal_Number].
		signal_Handler;
	    sigaction_InfoOld->sa_mask =
		downhill_Signal_Info[signal_Number].
		signal_Mask;
	    sigaction_InfoOld->sa_flags =
		downhill_Signal_Info[signal_Number].
		signal_Flags;
	}
	if (sigaction_Info != NULL)
	{
	    downhill_Signal_Info[signal_Number].
		signal_Handler = sigaction_Info->sa_handler;
	    downhill_Signal_Info[signal_Number].
		signal_Count = 0;
	    downhill_Signal_Info[signal_Number].
		signal_Extra = 0;
	    downhill_Signal_Info[signal_Number].
		signal_Mask = sigaction_Info->sa_mask;
	    downhill_Signal_Info[signal_Number].
		signal_Flags = sigaction_Info->sa_flags;
	}
    }
    else  {
	errno = EINVAL;
	return -1;
    }

    return 0;
}

/* Set the action of a signal ============================================== */
sighandler_t signal(int signal_Number, sighandler_t signal_Handler)
{
    sighandler_t signal_HandlerOld;

    /* Make sure we're init'd */
    if (!IS_SIGNAL(signal_Number) || !downhill_Signal_Init() )
    {
	return SIG_ERR;
    }
    signal_HandlerOld =
	downhill_Signal_Info[signal_Number].signal_Handler;
    downhill_Signal_Info[signal_Number].signal_Handler =
	signal_Handler;
    downhill_Signal_Info[signal_Number].signal_Count = 0;
    downhill_Signal_Info[signal_Number].signal_Extra = 0;
    downhill_Signal_Info[signal_Number].signal_Mask = 0;
    downhill_Signal_Info[signal_Number].signal_Flags = 0;
    return signal_HandlerOld;
}

/* Add a signal to a set =================================================== */
int sigaddset(sigset_t* sigset_Info,int signal_Number)
{
    if (IS_SIGNAL(signal_Number)) {
	(*sigset_Info) |= (1 << (signal_Number - 1));
	return 0;
    }
    else {
	errno = EINVAL;
	return -1;
    }
    return 0;
}

/* Remove a signal from a set ============================================== */
int sigdelset(sigset_t* sigset_Info,int signal_Number)
{
    if (IS_SIGNAL(signal_Number)) {
	*sigset_Info &= ~(1<< (signal_Number - 1));
	return 0;
    }
    else {
	errno = EINVAL;
	return -1;
    }
    return 0;
}

/* Empty a set ============================================================= */
int sigemptyset(sigset_t* sigset_Info)
{
    *sigset_Info = 0;
    return 0;
}

/* Fill a set ============================================================== */
int sigfillset(sigset_t* sigset_Info)
{
    *sigset_Info = (sigset_t)-1;
    return 0;
}

/* Checks if a signal is in a set ========================================== */
int sigismember(sigset_t* sigset_Info,int signal_Number)
{
    if (IS_SIGNAL(signal_Number)) {
	if ( *sigset_Info & (1 << (signal_Number-1)))
	    return 1;
	else
	    return 0;
    }
    errno = EINVAL;
    return -1;
}

/* Returns the signals pending ============================================= */
int sigpending(sigset_t* sigset_Info)
{
    int signal_Index;
    /* Make sure we're init'd */
    if (!downhill_Signal_Init())
    {
	return -1;
    }
    /* Check all the pending signals */
    sigemptyset(sigset_Info);
    for (signal_Index = 1;signal_Index < NSIG;signal_Index++)
    {
	if (downhill_Signal_Info[signal_Index].signal_Count > 0)
	{
	    sigaddset(sigset_Info,signal_Index);
	}
    }

    return 0;
}

/* Change the blocked signals ============================================== */
int sigprocmask(int mask_Function,sigset_t* sigset_Info,
     sigset_t* sigset_InfoOld)
{
    int      signal_Index;
    sigset_t sigset_MaskOld = downhill_Sigset_Mask;

    /* Make sure we're init'd */
    if (!downhill_Signal_Init())
    {
	return -1;
    }

    /* Return the current value */
    if (sigset_InfoOld != NULL)
    {
	*sigset_InfoOld = sigset_MaskOld;
    }

    /* Set the new mask */
    if (sigset_Info) {
	switch (mask_Function)
	{
	case SIG_BLOCK:
	    downhill_Sigset_Mask |= (*sigset_Info);
	    break;
	case SIG_UNBLOCK:
	    downhill_Sigset_Mask &= ~(*sigset_Info);
	    break;
	case SIG_SETMASK:
	    downhill_Sigset_Mask = *sigset_Info;
	    break;
	default:
	    errno = EINVAL;
	    return -1;
	}
    }

    /* And release any signals that were pending */
    for (signal_Index = 1;signal_Index < NSIG;signal_Index++)
    {
	if ((!sigismember(&downhill_Sigset_Mask,signal_Index)) &&
	    (sigismember(&sigset_MaskOld,signal_Index)) &&
	    (downhill_Signal_Info[signal_Index].signal_Count > 0))
	{
	    downhill_Signal_Info[signal_Index].signal_Count = 0;
	    raise(signal_Index);
	}
    }

    return 0;
}

/* Set signal mask ========================================================= */
int sigsetmask(int signal_MaskNew)
{
    int signal_MaskOld = downhill_Sigset_Mask;

    if (sigprocmask(SIG_SETMASK, &signal_MaskNew, NULL) == -1)
	return (int)-1;

    return signal_MaskOld;
}

/* Add signals to mask ===================================================== */
int sigblock(int signal_MaskNew)
{
    /* Block a specific group of signals */
    return sigsetmask(downhill_Sigset_Mask|signal_MaskNew);
}


/* Hold a signal =========================================================== */
int sighold(int signal_Number)
{
    /* Block a specific signal */
    if (sigblock(sigmask(signal_Number)) == -1)
    {
	return -1;
    }

    return 0;
}

/* Release a signal ======================================================== */
int sigrelse(int signal_Number)
{
    /* Release a specific signal */
    if (sigsetmask(downhill_Sigset_Mask&(~sigmask(signal_Number))) == -1)
    {
	return -1;
    }

    return 0;
}



/* Pause until a signal ==================================================== */
int pause(void)
{
    /* Wait for a signal */
    WaitForSingleObject(IGotASignal,INFINITE);
    /* And return if we were interrupted */
    errno = EINTR;
    return -1;
}


/* Suspend the process until a signal ====================================== */
int sigsuspend(sigset_t* sigset_Info)
{
    sigset_t sigset_MaskOriginal = downhill_Sigset_Mask;

    /* Set the new mask */
    sigprocmask(SIG_SETMASK,sigset_Info,NULL);

    /* Wait for the signal */
    pause();

    /* Reset the old mask */
    sigprocmask(SIG_SETMASK,&sigset_MaskOriginal,NULL);

    return -1;
}
