/*
 * This module provides the dummy routines invoked by the exception handling
 * macros in exception_emulate.h.  This module only applies to very non-POSIX
 * pthreads implementations (.e.g. linux) that don't provide these functions,
 * do not try to compile this module on VMS or TRU64 unix.
 *
 * The macros in exception_emulate.h provide the following mapping:
 *
 *   TRY {			      {   pte_scope_t blk;
 *					  blk.state = pte_try_begin(&blk);
 *     [user's code]			  setjmp(blk.env); 
 *					  if (!blk.state==0) {
 *                                           { user code }
 *   } CATCH(exc_SIGPIPE_e) {	      } else if ( pte_match_exception(blk,
 *						&exc_SIGPIPE_e) ) {
 *					     { user code }
 *__/} CATCH_ALL { 		      } else { blk.raised = 0;     \__
 *  \} FINALLY {		      } if (1==(blk.matched=1)) {  / 
 *					     { user code }
 *   } ENDTRY			      } pte_try_finish(&blk);
 *				      }
 *
 * 
 * The call to pte_try_begin sets up a signal handler to receive all maskable
 * signals.  When it catches a signal it does a long jump to the point
 * saved in the scope block which will search for a matching handler.
 *
 * Author: David Jones
 * Date:   5-MAR-2000
 */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>

#include "pthread_1c_np.h"
#define PTE_OVERRIDE 1
#include "exception_emulate.h"		/* verify prototypes. */

pthread_key_t handler_list;
static pthread_once_t pte_once = PTHREAD_ONCE_INIT;
/*
 * Define exception structures for the OS signals.
 */
EXCEPTION pthread_exit_e = { 2, 9999997, "thread exit" };
EXCEPTION pthread_exc_SIGABRT_e;
EXCEPTION pthread_exc_SIGBUS_e;
EXCEPTION pthread_exc_SIGFPE_e;
EXCEPTION pthread_exc_SIGILL_e;
EXCEPTION pthread_exc_SIGPIPE_e;
EXCEPTION pthread_exc_SIGSEGV_e;
EXCEPTION pthread_exc_SIGTRAP_e;

struct action_item {
    int signum;
    struct sigaction old;
    struct sigaction sa;
};
static void pte_signal_handler ( int );
static struct action_item action_table[50];
EXCEPTION pthread_exc_SIGSYS_e;
EXCEPTION pthread_exc_SIGIOT_e;
/*
 * Define table mapping system signal values to exception structures.
 */
static struct {
    int signum;
    EXCEPTION *exception;
    char *description;
} siglist[] = { 
  { SIGABRT, &pthread_exc_SIGABRT_e, "Abort" },
  { SIGFPE, &pthread_exc_SIGFPE_e, "Floating Point exception" },
  { SIGBUS, &pthread_exc_SIGBUS_e, "Bus error" },
#ifdef SIGEMT
  { SIGEMT, &pthread_exc_SIGEMT_e, "EMT error" },
#endif
  { SIGILL, &pthread_exc_SIGILL_e, "Illegal" },
  { SIGIOT, &pthread_exc_SIGIOT_e, "I/O trap" },
  { SIGPIPE, &pthread_exc_SIGPIPE_e, "Broken pipe" },
  { SIGSEGV, &pthread_exc_SIGSEGV_e, "Segment violation" },
#ifdef SIGSYS
  { SIGSYS, &pthread_exc_SIGSYS_e, "SYS error" },
#endif
  { SIGTRAP, &pthread_exc_SIGTRAP_e, "system trap" },
  { 0, (EXCEPTION *) 0 }
};

static void pte_raise_exception ( pte_scope_t *scope, EXCEPTION *code )
{
    if ( !scope ) return;
    scope->state = code->value;
    if ( scope->state == 0 ) scope->state = 1;
    
    scope->matched = 0;
    scope->raised = 1;
    scope->cur = code;
    longjmp ( scope->env, code->value );
}

static void pte_signal_handler ( int sig_id )
{
    pte_scope_t *exc;
    int i;

    GET_SPECIFIC ( handler_list, exc );
    if ( !exc ) {
        /*
         * No TRY block scope.
         */
        raise ( SIGKILL );
    }
    /*
     * find exception block for this code.
     */
    for ( i = 0; siglist[i].signum; i++ ) if ( siglist[i].signum == sig_id ) {
        /*
	 * Signal number found, raise exception.
         */
         pte_raise_exception ( exc, siglist[i].exception );
	 return;
    }
    /*
     * If no match, use generic exception.
     */
    pte_raise_exception ( exc, &pthread_exc_SIGSYS_e );
    return;
}
/*****************************************************************************/
/*
 * One-time setup, define context key and signal handler.
 */
static void pte_rundown ( pte_scope_t *blk );	/* forward reference */
static void pte_setup(void)
{
    int status, i;
    /*
     * Init context key used to track exception socpes.
     */
    status = CREATE_KEY ( &handler_list, (pthread_destructor_t) pte_rundown );
    if ( status != 0 ) return;
    SET_KEY_NAME ( pte_handler, "OSU exception emulation" );
    /*
     * Initialize the known exceptions.
     */
    for ( i = 0; siglist[i].signum; i++ ) {
       action_table[i].signum = siglist[i].signum;
       action_table[i].sa.sa_handler = pte_signal_handler;
       sigemptyset ( &action_table[i].sa.sa_mask );
       action_table[i].sa.sa_flags = 0;
       status = sigaction ( action_table[i].signum, &action_table[i].sa,
			    &action_table[i].old );
       siglist[i].exception->type = 1;
       siglist[i].exception->value = siglist[i].signum;
       siglist[i].exception->description = siglist[i].description;
    }
    action_table[i].signum = 0;
#ifndef SIGSYS
    pthread_exc_SIGSYS_e.type = 2;
    pthread_exc_SIGSYS_e.value = 1025;
    pthread_exc_SIGSYS_e.description = "Unknown system error";
#endif
}
/*
 * Define destructor routine for the handler_list context key.  If this
 * function gets called it means a thread exitted while inside a TRY block.
 */
static void pte_rundown ( pte_scope_t *blk )
{
    /*
     * Reset the context key to the handler list and raise exit exception.
     */
    pthread_setspecific ( handler_list, (void *) blk );
    pte_raise_exception ( blk, &pthread_exit_e );
}
/*
 * Define macros.  States:
 *     state     matched    raised
 *       0         N/A        N/A	! normal execution
 *      !0          0          1        ! Exception raised, looking for match
 *      !0          1          0        ! pte_match_exeception b
ranch taken
 *      !0          0          0        ! catch_all branch taken.
 *      !0          1          1        ! finally branch taken. (reraise).
 */
void pte_try_begin ( pte_scope_t *blk )
{
    /*
     * Push scope block onto 'stack' of scopes with the address of top
     * stored in handler_list.
     */
    pthread_once ( &pte_once, pte_setup );
    GET_SPECIFIC ( handler_list, blk->prev );
    blk->state = 0;			/* state clean */
    blk->matched = 0;
    blk->raised = 0;
    pthread_setspecific ( handler_list, (void *) blk );
    /*
     * Set up signal handler if this is block at bottom of stack (prev
     * pointer is null).
     */
    if ( !blk->prev ) {
    }
    return;
}
void pte_try_finish ( pte_scope_t *blk )
{
    pte_scope_t *top;
    /*
     * Verify the scope we are finishing was that last scope entered.
     */
    GET_SPECIFIC ( handler_list, top );
    if ( top != blk ) {
	fprintf(stderr, "BUGCHECK-- ENDTRY does not match\n");
	return;
    }
    /*
     * Pop the block off the top of stack.
     */
    pthread_setspecific ( handler_list, (void *) blk->prev );
    if ( !blk->prev ) {
	/*
	 * This was the outermost try block, tear down the signal handler.
         */
    }
    /*
     * See if there is an unhandled condition pending.
     */
    if ( blk->state != 0 ) {
	/*
	 * An abnormal condition is present.
	 */
	if ( blk->raised ) {
	    /*
	     * We got here by jumping back the start of the TRY block
	     * and failing all the tests, i.e. the raised signal wasn't
	     * caught.
	     */
	    if ( blk->matched ) {
		/* We executed a FINALLY block */
	    }
	    /*
	     * Propagate up.
	     */
	    if ( !blk->prev ) pthread_exit(&blk->state);
	} else {
	    /*
	     * The signal raised was 'caught', either by a CATCH
	     * (blk->matched==1) or CATCH_ALL (blk->matched==0);
	     */
	}
    }
}

void pte_reraise ( pte_scope_t *blk )
{
    /*
     */
    if ( !blk->state || blk->raised ) {
	/* bugcheck, no pending condition */
	return;
    }
    /*
     * The actions taken on a reraise are the same as if a FINALLY clause
     * on an uncaught exception had executed.
     */
    blk->raised = 1;
    blk->matched = 1;
    
    pte_try_finish ( blk );
}
int pte_match_exception ( pte_scope_t *blk, EXCEPTION *candidate )
{
    if ( blk->state == 0 ) {
	/*
	 * We should never see this state since it would have been caught
         * by the opening 'if' in the TRY macro.
	 */
	fprintf(stderr,"BUGCHECK, invalid call to CATCH macro\n");
	return 0;
    }
    if ( blk->state == candidate->value ) {
	if ( blk->cur ) if ( candidate->type != blk->cur->type ) return 0;
	blk->matched = 1;
	blk->raised = 0;		/* this CATCH caught it */
	return 1;
    }
    return 0;
}
/*
 * Initialize a user-defined exception object.
 */
void pte_init_exception ( EXCEPTION *obj )
{
    obj->type = 3;
    obj->value = 999999;
    obj->description = "User-declared exception";
}
