/*
    Copyright (C) 1995-1997
        Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld
        email: rainer@mathematik.uni-bielefeld.de

    All rights reserved
*/

#include <rsxnt.h>
#include <excpt.h>

/* signal functions */
LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo);

static int convert_exception(DWORD fault_no)
{
    switch (fault_no) {
        case EXCEPTION_FLT_DENORMAL_OPERAND:
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        case EXCEPTION_FLT_INEXACT_RESULT:
        case EXCEPTION_FLT_INVALID_OPERATION:
        case EXCEPTION_FLT_OVERFLOW:
        case EXCEPTION_FLT_STACK_CHECK:
        case EXCEPTION_FLT_UNDERFLOW:
            return SIGFPE;

        case EXCEPTION_BREAKPOINT:
        case EXCEPTION_SINGLE_STEP:
            return SIGTRAP;

        case EXCEPTION_ACCESS_VIOLATION:
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        case EXCEPTION_IN_PAGE_ERROR:
            return SIGSEGV;

        case EXCEPTION_DATATYPE_MISALIGNMENT:
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
        case EXCEPTION_INT_OVERFLOW:
        case EXCEPTION_PRIV_INSTRUCTION:
        case EXCEPTION_ILLEGAL_INSTRUCTION:
        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        case EXCEPTION_STACK_OVERFLOW:
        case EXCEPTION_INVALID_DISPOSITION:
            return SIGILL;

        default:
            return SIGUSR1;
    }
}

#define SIGDFL_IGNORE       0
#define SIGDFL_TERMINATE    1
#define SIGDFL_CORE         2

static struct {
    char *text;
    char action;
} sigdfl[NSIG] = {
    { "SIGHUP",     SIGDFL_TERMINATE },
    { "SIGINT",     SIGDFL_TERMINATE },
    { "SIGQUIT",    SIGDFL_CORE },
    { "SIGILL",     SIGDFL_CORE },
    { "SIGTRAP",    SIGDFL_CORE },
    { "SIGABRT",    SIGDFL_CORE },
    { "SIGEMT",     SIGDFL_CORE },
    { "SIGFPE",     SIGDFL_CORE },
    { "SIGKILL",    SIGDFL_TERMINATE },
    { "SIGBUS",     SIGDFL_CORE },
    { "SIGSEGV",    SIGDFL_CORE },
    { "SIGSYS",     SIGDFL_CORE },
    { "SIGPIPE",    SIGDFL_TERMINATE },
    { "SIGALRM",    SIGDFL_TERMINATE },
    { "SIGTERM",    SIGDFL_TERMINATE },
    { "SIGUSR1",    SIGDFL_IGNORE },
    { "SIGUSR2",    SIGDFL_IGNORE },
    { "SIGCHLD",    SIGDFL_IGNORE },
    { "SIG19",      SIGDFL_IGNORE },
    { "SIG20",      SIGDFL_IGNORE },
    { "SIGBREAK",   SIGDFL_TERMINATE }
};

static void do_fault(void)
{
    EMXPROCESS *p = _rsxnt_get_process_ptr();
    char buf[80];
    int signal;

    signal = convert_exception(p->regs.faultno);

    wsprintf(buf, "Exception at 0x%08lX\n"
                 "Application got signal %s\n",
                p->regs.eip, sigdfl[signal-1].text);
    MessageBox(NULL, buf,
               "RSXNT", MB_OK|MB_ICONERROR|MB_APPLMODAL|MB_SETFOREGROUND);

    _rsxnt_send_signal(p, signal);
    _rsxnt_do_signal(signal);

    /* if returned */
    MessageBox(NULL,
                  "Returned exception signal handler\n\n"
                  "Press OK to terminate process",
               "RSXNT", MB_OK|MB_ICONERROR|MB_APPLMODAL|MB_SETFOREGROUND);
    ExitProcess(3);
}

static LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo)
{
    PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord;
    PCONTEXT ContextRecord = ExceptionInfo->ContextRecord;
    EMXPROCESS *p = _rsxnt_get_process_ptr();

    /* self-generated by RaiseException */
    if (ExceptionRecord->NumberParameters == 4 &&
        ExceptionRecord->ExceptionInformation[0] == 0x87654321) {
        return (EXCEPTION_CONTINUE_EXECUTION);
    }

    /* fatal errors : run default Win32 handler */
    if (ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE ||
        ExceptionRecord->ExceptionFlags ==EXCEPTION_STACK_OVERFLOW) {

        if (ExceptionRecord->ExceptionFlags ==EXCEPTION_STACK_OVERFLOW)
            MessageBox(NULL, "Fatal Exception in the Application",
               "RSXNT", MB_OK|MB_ICONERROR|MB_APPLMODAL|MB_SETFOREGROUND);
        return (EXCEPTION_EXECUTE_HANDLER);
    }

    /* save old values */
    p->regs.faultno = ExceptionRecord->ExceptionCode;
    p->regs.eip = ContextRecord->Eip;
    p->regs.esp = ContextRecord->Esp;
    p->regs.ebp = ContextRecord->Ebp;
    p->regs.eax = ContextRecord->Eax;
    p->regs.ebx = ContextRecord->Ebx;
    p->regs.ecx = ContextRecord->Ecx;
    p->regs.edx = ContextRecord->Edx;
    p->regs.esi = ContextRecord->Esi;
    p->regs.edi = ContextRecord->Edi;
    p->regs.eflags = ContextRecord->EFlags;

    ContextRecord->Eip = (DWORD) do_fault;

    return (EXCEPTION_CONTINUE_EXECUTION);
}

static BOOL WINAPI controlc_handler(DWORD ctltype)
{
    EMXPROCESS *p = _rsxnt_get_process_ptr();
    int signal;

    switch (ctltype) {
        case CTRL_C_EVENT:
            signal = SIGINT;
            break;
        case CTRL_BREAK_EVENT:
            signal = SIGBREAK;
            break;
        /*
        case CTRL_CLOSE_EVENT:
        case CTRL_LOGOFF_EVENT:
        case CTRL_SHUTDOWN_EVENT:
        */
        default:
            _rsxnt_send_signal(p, SIGHUP);
            _rsxnt_do_signal(SIGHUP);
            return FALSE;
    }

    _rsxnt_send_signal(p, signal);
    if (p->p_status == PS_SLEEP) {  /* wake up sleep() */
        if (p->sigaction[SIGSA(signal)].sa_handler != SIG_IGN)
            PulseEvent(p->hEventSignal);
    }
    else
        _rsxnt_do_signal(signal);
    return TRUE;
}

int _rsxnt_init_signals(EMXPROCESS *p)
{
    if (p->bConsoleApp != FALSE)
        SetConsoleCtrlHandler(controlc_handler, TRUE);

    SetUnhandledExceptionFilter(TopLevelExceptionFilter);

    p->hEventSignal = CreateEvent(NULL, FALSE, FALSE, NULL);

    return 0;
}

/*
** called before return to user process
*/
int _rsxnt_check_signals(void)
{
    EMXPROCESS *proc = _rsxnt_get_process_ptr();
    int i;
    DWORD sigs;

    sigs = ~proc->sig_blocked & proc->sig_raised;
    if (!sigs)
        return 0;

    while (sigs) {
        for (i = 1; i <= NSIG; i++) {
            if (sigs & 1) {
                if (_rsxnt_do_signal(i))
                    break;          /* restart loop, if handler called */
            }
            sigs >>= 1;
        }
        sigs = ~proc->sig_blocked & proc->sig_raised;
    }
    return 0;
}

#ifdef __WATCOMC__
typedef void __cdecl (*SA_HANDLER2) (int);
#else
#define SA_HANDLER2 SA_HANDLER
#endif

#if 0
static void __cdecl RaiseSignal(int signal, DWORD address)
{
    DWORD args[4];

    args[0] = 0x87654321;
    args[1] = 0;
    args[2] = signal;
    args[3] = address;

    RaiseException(0, 0, 4, args);
    return;
}
#endif

/*
** check signals settings , change eip to signal handler
*/
int _rsxnt_do_signal(int signal)
{
    EMXPROCESS *proc = _rsxnt_get_process_ptr();
    SA_HANDLER2 address;
    DWORD mask, sig_blocked;

    address = (SA_HANDLER2) proc->sigaction[SIGSA(signal)].sa_handler;

    mask = SIGMASK(signal);
    proc->sig_raised &= ~mask;          /* clear sig_raised */

    /* for debugger: Raise User Exception */
#if 0    
    RaiseSignal(signal, (DWORD)address);
#endif

    if (address == (SA_HANDLER2) 1L)    /* ignore sig */
        return 0;

    if (address == (SA_HANDLER2) 0L) {
        /* emx ignores SIGCLD, SIGCHLD, SIGUSR */

        if (sigdfl[SIGSA(signal)].action == SIGDFL_IGNORE)
            return 0;

        //  else if (sigdfl[SIGSA(signal)].action == SIGDFL_CORE) {
        //      if (!proc->options.opt_nocore)
        //          write_core_file(proc);
        //  }

        ExitProcess(3);
        return 1;
    }

    /* ok, do user handler */
    sig_blocked = proc->sig_blocked;

    if (proc->sigaction[SIGSA(signal)].sa_flags & SA_SYSV)
        proc->sigaction[SIGSA(signal)].sa_handler = 0L;
    else
        proc->sig_blocked |= mask;   /* set blocked */

    /* BSD block others */
    proc->sig_blocked |= proc->sigaction[SIGSA(signal)].sa_mask;

    /* call signal handler */
    (*address)(signal);

    /* unblock old signal mask, if not emx ack */
    if (!(proc->sigaction[SIGSA(signal)].sa_flags & SA_ACK))
        proc->sig_blocked = sig_blocked & ~SIGMASK(signal);

    return 1;
}

int _rsxnt_send_signal(EMXPROCESS * p, int signal)
{
    if (!p || signal < 0 || signal >= NSIG)
        return _rsxnt_errno(EINVAL);
    if (signal != 0)            /* kill(pid,0): check if pid is ok */
        p->sig_raised |= SIGMASK(signal);
    return 0;
}

