/*
 * etm.c - Exception and Termination Manager
 *
 * 10 Jul 90	Paul DuBois	dubois@primate.wisc.edu
 *
 * 10 Jul 90 V1.0.
 * - Created.
 * 26 Sep 90 V1.01.
 * - Allow alternate error message printer.  This is
 * useful, e.g., for servers that want to use syslog instead of
 * stderr, or programs that want to send messages to multiple
 * destinations.
 * 04 Oct 90 V1.02.
 * - Allow exit status code to be modified.  Mail servers
 * that panic otherwise exit with status 1, which causes sendmail
 * to return an extra error messsage to the person sending the
 * mail.  Making the exit status 0 fixes this.
 * 27 Jun 91 V1.03.
 * - Created etm.h, other minor fixes.
 * 23 Jul 91 V1.04.
 * - ETMMsg() added.
 * 17 Sep 91 V1.05.
 * - ETMShutdownDeregister() added.  More complete typecasting.
 * 02 Jun 92 V1.06.
 * - Yet more typing.  Some function renaming, and a few new ones added.
 * 07 Jul 92
 * - Added ETMSetAbort(), which can be used to cause ETMPanic() to dump
 * core dump after it prints its message.  This can sometimes be useful
 * for debugging.
 * 10 Apr 97 V1.09
 * - Move fflush(stdout)/fflush(stderr) into PrintMsg() so that it's
 * done uniformly.
 */


/*
 * This isn't quite right, since some ANSI systems are missing
 * stdarg.h.
 */

# if defined(__STDC__) || defined(THINK_C)
# define	STDARGS
# endif /* __STDC__, THINK_C */

/*
	Include stdarg.h or varargs.h if present, with the former
	taking precedence.
*/

# include	<stdio.h>
# include	<signal.h>
# include	<sys/types.h>
# ifdef	STDARGS
#   include	<stdarg.h>
# else /* !STDARGS */
#   ifdef	VARARGS
#     include	<varargs.h>
#   endif /* VARARGS */
# endif /* STDARGS */

# include	"etm.internal.h"
# include	"etm.h"


# define	bufSiz		1024

# if	!HASVOIDTYPE
# define	void	int
# endif

/* Determinal signal-handling routine return type */

# if	SIGNALRETURNSINT
# define	SIGRET	int
# else
# define	SIGRET	void
# endif

/*
	This horrible mess makes ETM (try to) use the proper underlying types
	for memory allocation that are appropriate for the system on which it's
	compiled.
*/

# if defined(__STDC__) || defined(SYSV)
#  if defined(__STDC__)
#   include	<stdlib.h>
#  else /* SYSV */
#   include	<malloc.h>
#  endif /* __STDC__ vs. SYSV */
#  define	allocPtrType	void
#  define	allocSizeType	size_t
# else	/* !SYSV */
#  define	allocPtrType	char
#  define	allocSizeType	unsigned
#  ifndef THINK_C
extern allocPtrType	*malloc ();
#  endif /* THINK_C */
# endif /* __STDC, SYSV */

# define	mymalloc(x)	malloc ((allocSizeType) (x))
# define	myfree(x)	free ((allocPtrType *) (x))



/* Private routines */

static ETMProcRetType	Catch ();
static SIGRET	CatchManager ();
static ETMProcRetType	PrintMsg ();


typedef	struct ETMShutdownProc	ETMShutdownProc;
typedef	struct ETMSigCatcher	ETMSigCatcher;


struct ETMShutdownProc
{
	ETMProcPtr	sdProc;
	ETMShutdownProc	*nextShutdown;
};


struct ETMSigCatcher
{
	int		signo;
	ETMProcPtr	sigProc;
	ETMSigCatcher	*nextCatcher;
};


static ETMShutdownProc	*sdProcList = (ETMShutdownProc *) NULL;
static ETMSigCatcher	*catcherList = (ETMSigCatcher *) NULL;

static int	exitStatus = 1;
static int	exitAbort = 0;


/*
	Initialize signal catchers for signals that normally cause an exit,
	sets up the application's cleanup routine.
*/

ETMProcRetType ETMInit (p)
ETMProcPtr	p;
{
	/* register application-specific cleanup routine */

	ETMAddShutdownProc (p);

	/* register catchers for several common signals */

# ifdef	SIGHUP
	ETMSetSignalProc (SIGHUP, Catch);
# endif
# ifdef	SIGINT
	ETMSetSignalProc (SIGINT, Catch);
# endif
# ifdef	SIGQUIT
	ETMSetSignalProc (SIGQUIT, Catch);
# endif
# ifdef	SIGILL
	ETMSetSignalProc (SIGILL, Catch);
# endif
# ifdef	SIGBUS
	ETMSetSignalProc (SIGBUS, Catch);
# endif
# ifdef	SIGSEGV
	ETMSetSignalProc (SIGSEGV, Catch);
# endif
# ifdef	SIGSYS
	ETMSetSignalProc (SIGSYS, Catch);
# endif
# ifdef	SIGTERM
	ETMSetSignalProc (SIGTERM, Catch);
# endif

	/* less likely, perhaps, but worth catching... */

# ifdef	SIGFPE
	ETMSetSignalProc (SIGFPE, Catch);
# endif
# ifdef	SIGPIPE
	ETMSetSignalProc (SIGPIPE, Catch);
# endif
}


/*
	Invoke all the shutdown routines.  Called either by application for
	normal shutdown, or by ETMPanic() when an exceptional condition occurs.
*/

ETMProcRetType ETMEnd ()
{
ETMShutdownProc	*sd;

	for (sd = sdProcList;
		sd != (ETMShutdownProc *) NULL;
		sd = sd->nextShutdown)
	{
		if (sd->sdProc != (ETMProcPtr) NULL)
			(*(sd->sdProc)) ();
	}
}


/*
	Register a shutdown routine.  These are placed in a list in
	reverse order because shutdown is in reverse order of initialization.
*/

ETMProcRetType ETMAddShutdownProc (p)
ETMProcPtr	p;
{
ETMShutdownProc	*sd;

	if (p == (ETMProcPtr) NULL)
		return;
	if ((sd = (ETMShutdownProc *) mymalloc (sizeof (ETMShutdownProc)))
						== (ETMShutdownProc *) NULL)
		ETMPanic ("ETMAddShutdownProc: out of memory");
	sd->sdProc = p;
	sd->nextShutdown = sdProcList;
	sdProcList = sd;
}


/*
	Deregister a shutdown routine.  This can be used when a routine
	only needs to be called if a panic occurs in a certain piece of
	code, but not after that code finishes.
	The algoritm is stupid because all it does it NULL out the proc
	pointer; it should really take the record out of the list.
*/

ETMProcRetType ETMRemoveShutdownProc (p)
ETMProcPtr	p;
{
ETMShutdownProc	*sd = (ETMShutdownProc *) NULL, *sd2;

	if (sdProcList == (ETMShutdownProc *) NULL)	/* shouldn't happen */
		return;
	if (p == sdProcList->sdProc)	/* first in list */
	{
		sd = sdProcList;
		sdProcList = sdProcList->nextShutdown;
	}
	else
	{
		sd2 = sdProcList;
		while ((sd = sd2->nextShutdown) != (ETMShutdownProc *) NULL)
		{
			if (p == sd->sdProc)
			{
				sd2->nextShutdown = sd->nextShutdown;
				break;
			}
			sd2 = sd;
		}
	}

	if (sd != (ETMShutdownProc *) NULL)	/* found it */
		myfree (sd);
}


/*
	Register a signal-catching routine.  This can be either for a new
	(currently not caught) signal, or to reset the catcher for a signal
	that's already being caught.

	An attempt is made to enhance portability by specifying that all
	handlers return ETMProcRetType.  A single proxy routine,
	CatchManager(), which is declared properly according to the signal
	semantics of the compilation environment, actually catches all
	signals and determines which routine to invoke based on which
	signal occurred.

	This insulates programs from the vagaries of the signal-handling-proc
	return type mess.  I hope.

	p == ETMSigDefault :	restore default signal action
	p == ETMSigIgnore :	ignore signal
*/

ETMProcRetType ETMSetSignalProc (signo, p)
int	signo;
ETMProcPtr	p;
{
ETMSigCatcher	*cp;

	for (cp = catcherList; cp != (ETMSigCatcher *) NULL; cp = cp->nextCatcher)
	{
		if (cp->signo == signo)	/* resetting existing catcher */
		{
			if (p == ETMSigIgnore)
			{
				(void) signal (signo, SIG_IGN);
				p = (ETMProcPtr) NULL;	/* to disable catcher struct */
			}
			else if (p == ETMSigDefault)
			{
				(void) signal (signo, SIG_DFL);
				p = (ETMProcPtr) NULL;	/* to disable catcher struct */
			}
			cp->sigProc = p;
			return;
		}
	}

	/* catching a signal that's not currently being caught */

	if (p == ETMSigIgnore)
	{
		(void) signal (signo, SIG_IGN);
		return;
	}
	else if (p == ETMSigDefault)
	{
		(void) signal (signo, SIG_DFL);
		return;
	}

	if ((cp = (ETMSigCatcher *) mymalloc (sizeof (ETMSigCatcher))) == (ETMSigCatcher *) NULL)
		ETMPanic ("ETMSetSignalProc: out of memory");
	cp->signo = signo;
	cp->sigProc = p;
	cp->nextCatcher = catcherList;
	catcherList = cp;
	(void) signal (signo, CatchManager);
}


ETMProcPtr ETMGetSignalProc (signo)
{
ETMSigCatcher	*cp;

	for (cp = catcherList; cp != (ETMSigCatcher *) NULL; cp = cp->nextCatcher)
	{
		if (cp->signo == signo)	/* found it */
			return (cp->sigProc);
	}

	/* not found, ignored, or default */

	return ((ETMProcPtr) NULL);
}


/*
	All signals come through here.  CatchManager() determines which
	catch routine should be invoked based on the signal number.
*/

static SIGRET CatchManager (signo)
int	signo;
{
ETMSigCatcher	*cp;

	for (cp = catcherList; cp != (ETMSigCatcher *) NULL; cp = cp->nextCatcher)
	{
		/* if catcher wasn't disabled and it's right signal... */
		if (cp->sigProc != (ETMProcPtr) NULL && cp->signo == signo)
		{
			(*(cp->sigProc)) (signo);
			break;
		}
	}
	if (cp == (ETMSigCatcher *) NULL)
		ETMPanic ("caught signal %d; program terminated", signo);
}


/*
	Default signal catching routine.  Triggers a panic, which causes
	ETMEnd() to be executed.
*/

static ETMProcRetType Catch (signo)
int	signo;
{
	ETMPanic ("caught signal %d; program terminated", signo);
}


/*
	ETMPanic - print message and die with status 1.  Takes variable
	argument lists.  Uses stdargs/varargs on those systems that have it.

	Try to avoid getting into a panic loop, whereby one of the shutdown
	routines calls panic.  In that case just print a message and exit
	immediately: that is probably a bug in the application, not ETM.

	Flushes stdout before stderr so that error output appears after
	any pending application output.
*/


static int	loop = 0;
static char	*loopMsg =
	"\r\nETMPanic loop; one of your shutdown routines panics!\r\n";


# ifdef	STDARGS

ETMProcRetType
ETMPanic (char *fmt, ...)
{
va_list	args;
char	buf[bufSiz];

	if (loop++)
	{
		PrintMsg (loopMsg);
		exit (exitStatus);
	}
	ETMEnd ();
	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);
	strcat (buf, "\n");
	PrintMsg (buf);
	if (exitAbort)
	{
		/* deregister catcher for abort() signal to avoid panic loop */
		ETMSetSignalProc (abortSignal, ETMSigDefault);
		abort ();
	}
	exit (exitStatus);
}

# else	/* !STDARGS */

# ifdef	VARARGS

ETMProcRetType
ETMPanic (va_alist)
va_dcl
{
va_list	args;
char	*fmt, buf[bufSiz];

	if (loop++)
	{
		PrintMsg (loopMsg);
		exit (exitStatus);
	}
	ETMEnd ();
	va_start (args);
	fmt = va_arg (args, char *);
	vsprintf (buf, fmt, args);
	va_end (args);
	strcat (buf, "\n");
	PrintMsg (buf);
	if (exitAbort)
	{
		/* deregister catcher for abort() signal to avoid panic loop */
		ETMSetSignalProc (abortSignal, ETMSigDefault);
		abort ();
	}
	exit (exitStatus);
}

# else	/* !VARARGS */

ETMProcRetType
ETMPanic (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char	*fmt;
char	*a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
{
char	buf[bufSiz];

	if (loop++)
	{
		PrintMsg (loopMsg);
		exit (exitStatus);
	}
	ETMEnd ();
	sprintf (buf, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
	strcat (buf, "\n");
	PrintMsg (buf);
	if (exitAbort)
	{
		/* deregister catcher for abort() signal to avoid panic loop */
		ETMSetSignalProc (abortSignal, ETMSigDefault);
		abort ();
	}
	exit (exitStatus);
}

# endif	/* VARARGS */

# endif /* STDARGS */


/*
 * ETMMsg - print message and return.  Takes variable argument
 * lists.  Uses stdargs/varargs on those systems that have it.
 */


# ifdef	STDARGS

ETMProcRetType
ETMMsg (char *fmt, ...)
{
va_list	args;
char	buf[bufSiz];

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);
	strcat (buf, "\n");
	PrintMsg (buf);
}

# else	/* !STDARGS */

# ifdef	VARARGS

ETMProcRetType
ETMMsg (va_alist)
va_dcl
{
va_list	args;
char	*fmt, buf[bufSiz];

	va_start (args);
	fmt = va_arg (args, char *);
	vsprintf (buf, fmt, args);
	va_end (args);
	strcat (buf, "\n");
	PrintMsg (buf);
}

# else	/* !VARARGS */

ETMProcRetType
ETMMsg (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char	*fmt;
char	*a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
{
char	buf[bufSiz];

	sprintf (buf, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
	strcat (buf, "\n");
	PrintMsg (buf);
}

# endif /* VARARGS */

# endif /* STDARGS */


static ETMProcPtr	printProc = (ETMProcPtr) NULL;


ETMProcRetType
ETMSetPrintProc (p)
ETMProcPtr	p;
{
	printProc = p;
}


ETMProcPtr
ETMGetPrintProc ()
{
	return ((ETMProcPtr) printProc);
}


static char	*pLoopMsg = "\r\nETM print routine recursion detected!\r\n";

static ETMProcRetType
PrintMsg (s)
char	*s;
{
static int	loop = 0;

	fflush (stdout);
	fflush (stderr);
	if (loop++)
	{
		fprintf (stderr, "%s", pLoopMsg);
		fflush (stderr);
		exit (exitStatus);
	}
	if (printProc == (ETMProcPtr) NULL)	/* default is stderr */
		fprintf (stderr, "%s", s);
	else
		(*printProc) (s);
	fflush (stderr);
	--loop;
}


ETMProcRetType
ETMSetExitStatus (status)
int	status;
{
	exitStatus = status;
}


int
ETMGetExitStatus ()
{
	return (exitStatus);
}


ETMProcRetType
ETMSetAbort (abortVal)
int	abortVal;
{
	exitAbort = abortVal;
}


int
ETMGetAbort ()
{
	return (exitAbort);
}
