 /*
  * Khoros: $Id: signal.c,v 1.4 1992/03/20 22:48:18 dkhoros Exp $
  */

#if !defined(lint) && !defined(SABER)
static char rcsid[] = "Khoros: $Id: signal.c,v 1.4 1992/03/20 22:48:18 dkhoros Exp $";
#endif

 /*
  * $Log: signal.c,v $
 * Revision 1.4  1992/03/20  22:48:18  dkhoros
 * VirtualPatch5
 *
  */ 

/*
 *----------------------------------------------------------------------
 *
 * Copyright 1990, University of New Mexico.  All rights reserved.
 * 
 * Permission to copy and modify this software and its documen-
 * tation only for internal use in your organization is hereby
 * granted, provided that this notice is retained thereon and
 * on all copies.  UNM makes no representations as too the sui-
 * tability and operability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 * 
 * UNM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FIT-
 * NESS.  IN NO EVENT SHALL UNM BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY OTHER DAMAGES WHAT-
 * SOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PER-
 * FORMANCE OF THIS SOFTWARE.
 * 
 * No other rights, including for example, the right to redis-
 * tribute this software and its documentation or the right to
 * prepare derivative works, are granted unless specifically
 * provided in a separate license agreement.
 *----------------------------------------------------------------------
 */

#include "unmcopyright.h"	 /* Copyright 1990 by UNM */
#include "forms.h"
#include "form_signal.h"

#define	ERRORFILE "errorfile.XXXXXX"
static  form_signal  *signal_list = NULL;
static vsignal xvf_process_signal();

/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
   >>>>                                                       <<<<
   >>>>             file name: signal.c                       <<<<
   >>>>                                                       <<<<
   >>>>		       Dispatch Routines                      <<<<
   >>>>                                                       <<<<
   >>>>       These routines are used to handle the execution <<<<
   >>>>	 of running processes.				      <<<<
   >>>>                                                       <<<<
   >>>>			xvf_add_signal()		      <<<<
   >>>>			xvf_remove_signal()		      <<<<
   >>>>			xvf_sleep()			      <<<<
   >>>>			xvf_waitpid()			      <<<<
   >>>>                                                       <<<<
   >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  */



/************************************************************
*
*  Routine Name: xvf_add_signal
*  		 xvf_remove_signal
*
*      Purpose:
*
*        Input:
*
*       Output:
*
*    Called By:
*
*   Written By:   Mark Young
*
*  NOTE:  this signal handler is probably a stupid idea, since
*	  a user can override our signal handler, which means
*	  that we can't really provide a reliable service....
*
*
*************************************************************/

xvf_add_signal(sig)

int	sig;
{

	signal(sig, xvf_process_signal);
}


xvf_remove_signal(sig)

int	sig;
{
	signal(sig, SIG_DFL);
}




/************************************************************
*
*  Routine Name:  xvf_add_handler
*
*      Purpose:   
*
*        Input:   
*
*       Output:   
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


form_signal *xvf_add_handler(routine, clientData, fid)

void	(*routine)();
caddr_t clientData;
int	fid;
{
	form_signal *entry;
	void	    xvf_error_callback();


	/*
	 *  create a new entry in which to record the signal information.
	 *  This is where a process records it's pid and status so that when
	 *  the process is done we can call the routine back or display any
	 *  errors that may occur.
	 */
	if (!(entry = (form_signal *) calloc(1,sizeof(form_signal))))
	{
	   xvf_error_wait("Out of memory!", "xvf_add_error", NULL);
	   return(NULL);
	}
	entry->pid    =
	entry->retry  = 0;
	entry->timeid = 0;
	entry->next   = signal_list;
	entry->prev   = NULL;

	if (signal_list != NULL)
	   signal_list->prev = entry;

	signal_list    = entry;
	entry->routine = routine;
	entry->data    = clientData;
	entry->fid     = fid;
	if (fid != -1)
	{
	   entry->inputid = XtAppAddInput(xvf_app_context, fid, 
				(XtPointer) XtInputReadMask, 
				xvf_error_callback, (caddr_t) entry);
	}
	return(entry);
}


/************************************************************
*
*  Routine Name: xvf_remove_handler
*
*      Purpose:
*
*        Input:
*
*       Output:
*
*    Called By:
*
*   Written By:   Mark Young
*
*  NOTE:  this signal handler is probably a stupid idea, since
*	  a user can override our signal handler, which means
*	  that we can't really provide a reliable service....
*
*
*************************************************************/


xvf_remove_handler(pid)

int	pid;
{
	form_signal  *entry;


	if (signal_list == NULL)
	   return;

	entry = signal_list;
	while (entry->pid != pid && entry->next != NULL)
	{
	   entry = entry->next;
	}

	/*
	 *  If we found the process then delete it from the linked list,
	 *  and free memory.
	 */
	if (entry->pid == pid)
	{
	   if (entry->inputid != NULL)
	      XtRemoveInput(entry->inputid);
	   if (entry->timeid != 0)
	      XtRemoveTimeOut(entry->timeid);

	   close(entry->fid);
	   if (entry->error != NULL)
	   {
	      fclose(entry->error);
	      unlink(entry->errorfile);
	   }
	   remove_entry(entry);
	}
}


/************************************************************
*
*  Routine Name: static remove_entry
*
*      Purpose:  Removes the specified entry from the process
*		 list and frees memory.
*
*        Input:  entry  -  the entry to be removed from the
*			   list.
*
*       Output:
*
*   Written By:   Mark Young
*
*************************************************************/


static remove_entry(entry)

form_signal  *entry;
{
	if (entry->next == NULL && entry->prev == NULL)
	{
	   signal_list =  NULL;
	}
	else if (entry->prev == NULL)
	{
	   entry->next->prev = NULL;
	   signal_list = entry->next;
	}
	else if (entry->next == NULL)
	{
	   entry->prev->next = NULL;
	}
	else
	{
	   entry->prev->next = entry->next;
	   entry->next->prev = entry->prev;
	}

	if (entry->errorfile)
	{
	   free(entry->errorfile);
	}
	free(entry);
}



/************************************************************
*
*  Routine Name: vsignal xvf_process_signal(sig, flags, context)
*
*      Purpose:  We got a signal notifying us that a process
*		 signaled us with a certain signal.  Since
*		 we don't know which process signaled us we
*		 must searching through our list to see which
*		 process are done.  When we find such a process
*		 we record the status since the "xvf_error_callback()"
*		 procedure is the one used to call the user's callback
*		 routine.
*
*        Input:  signal  -  the type of signal that called us.
*		 flags   -  more info about the type of signal (not used)
*		 context -  signal context (not used)
*
*       Output:  clears processes out the process table and
*		 initiates the callback sequence.
*
*    Called By:
*
*   Written By:   Mark Young
*
*  NOTE:  this signal handler is probably a stupid idea, since
*	  a user can override our signal handler, which means
*	  that we can't really provide a reliable service....
*
*
*************************************************************/


static vsignal xvf_process_signal(signal, flags, context)

int     signal, flags;
struct  sigcontext *context;
{
	int	     pid;
	form_signal  *entry;
	vstatus	     status;

	/*
	 *  Check to see which processes finished running and dispatch
	 *  "callbacks" to the process that are done.
	 */
	while ((pid = vwait3(&status, WNOHANG, NULL)) > 0)
	{
	   entry = signal_list;
	   while (entry != NULL)
	   {
	      if (entry->pid == pid)
	      {
		 entry->status = status;
		 entry->retry  = -1;
		 break;
	      }
	      else
	         entry = entry->next;
	   }
	}
}
 



/************************************************************
*
*  Routine Name: xvf_dispatch_entry
*
*      Purpose:  Dispatch the entry.
*
*        Input:  entry - 
*
*       Output:  
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


xvf_dispatch_entry(entry)

form_signal *entry;
{
	caddr_t	    data;
	int	    pid, type;
	void	    (*routine)();
	vstatus	    status;
	char	    *errorfile;


	/*
	 *  Need to call the callback routine notifying that the
	 *  process is done.   First we copy the information into
	 *  local variables and advance to the next pointer.  We
	 *  do this just in case the user does something stupid
	 *  like delete the handler while in the callback.
	 *
	 *  Before we call the callback we remove the signal
	 *  handler, call the user's callback and then move onto the
	 *  next entry.
	 */
	data      = entry->data;
	routine   = entry->routine;
	pid       = entry->pid;
	status    = entry->status;
	type	  = (int) routine;


	if (entry->inputid != NULL)
	   XtRemoveInput(entry->inputid);
	if (entry->timeid != 0)
	   XtRemoveTimeOut(entry->timeid);

	if (entry->errorfile != NULL)
	{
	   if (routine == NULL || type == XVF_SYSTEM || type == XVF_POPEN)
	   {
	      (void) xvf_create_online_help(entry->errorfile, NULL);
	      unlink(entry->errorfile);
	   }
	   else
	      errorfile = xvf_strcpy(entry->errorfile);
	}
	else
	   errorfile = NULL;

	remove_entry(entry);
	if (routine != NULL && type != XVF_SYSTEM && type != XVF_POPEN)
	   routine(pid, status, data, errorfile);
}


void dispatch_entry(calldata, id)

XtPointer       calldata;
XtIntervalId    id;
{
	form_signal *entry = (form_signal *) calldata;

	if (entry->retry != -1 && entry->retry < 100)
	{
	   entry->retry++;
	   entry->timeid = XtAppAddTimeOut(xvf_app_context, 100,
				dispatch_entry, entry);
	}
	else
	{
	   entry->timeid = NULL;
	   xvf_dispatch_entry(entry);
	}
}



/************************************************************
*
*  Routine Name: int xvf_check_process
*
*      Purpose:  check to see if a process exists.  In order
*		 to be portable we will use kill instead of
*		 waitpid or wait4.  We send a 0 to the process,
*		 if the kill fails and errno is set to ESRCH we
*		 know that process is finished.
*
*
*        Input:  pid -  integer corresponding to the process
*			we wish to check.
*
*       Output:  Return TRUE if we are able to check the
*		 process.  Otherwise, return FALSE.
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


int  xvf_check_process(pid)

int	pid;
{
	if (kill(pid, 0) == -1)
	{
	   /*
	    *  Failed to send a 0 to the process.  If errno
	    *  is set to EINVAL (note: this should never happen)
	    *  we will give it one last try using SIGUSR1.
	    *
	    *  If errno is set to ESRCH then the process is done,
	    *  otherwise assume the process to be alive.
	    */
	   if (errno == EINVAL)
	   {
	      if (kill(pid, SIGUSR1) == -1)
	         return(FALSE);
	      else
	         return(TRUE);
	   }
	   else if (errno == ESRCH)
	      return(FALSE);
	   else
	      return(TRUE);
	}
	else
	   return(TRUE);
}



/************************************************************
*
*  Routine Name: xvf_sleep
*
*      Purpose:  The purpose of this routine is to sleep
*		 for the desired number of seconds or until
*		 there is input pending from the X server
*		   ie) XtPending().
*
*        Input:  pid -  integer corresponding to the process
*			we wish to check.
*
*       Output:  Return TRUE if we are able to check the
*		 process.  Otherwise, return FALSE.
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


xvf_sleep(time)

float	time;
{
	Boolean ignoreInputs = false, 
	        ignoreEvents = false, 
		ignoreTimers = false,
		block = true;
	unsigned long howlong;

	howlong = time*1000;
	(void) _XtwaitForSomething(ignoreTimers, ignoreInputs, ignoreEvents,
                        block, &howlong, xvf_app_context);
}



/************************************************************
*
*  Routine Name: vstatus xvf_waitpid(pid, stop_button)
*
*      Purpose:  The purpose of this routine is to wait until
*		 a certain process is done while still processing
*		 certain X Events.
*
*        Input:  pid -  integer corresponding to the process
*			we wish to check.
*
*		 stop_button -  The widget in which we should
*			abort this process.  If the user button
*			presses the stop button then we return
*		        -1 so that the user can kill the process
*			and cleanup.  The "stop_button" can be
*			passed as NULL which will prevent the user
*			from stop the process until done.
*
*       Output:  returns 0 upon suceesful completion of process
*		 pid, or -1 if the user aborted the process.
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


int xvf_waitpid(pid, status, stop_button)

int	pid;
vstatus *status;
Widget	stop_button;
{
	form_signal *entry;


	/*
	 *  Check to see if the process is done.  If not then sleep for
	 *  1 second, then check to see if the user depressed the stop
	 *  button.  If not then repeat the process until the process
	 *  is done.
	 */
	do
	{
	    xvf_sleep(1.0);
	    if (xvf_clean_event_list(stop_button) == False)
	       return(-1);
	} while (xvf_check_process(pid));

	/*
	 *  Race thru the entry list looking for our pid, if it's in the
	 *  list then return the status, otherwise return NULL.
	 */
	entry = signal_list;
	while (entry != NULL)
	{
	   if (entry->pid == pid)
	   {
	       if (status != NULL)
		  *status = entry->status;

	       xvf_dispatch_entry(entry);
	       return(pid);
	   }
	   entry = entry->next;
	}
	return(-1);
}



/************************************************************
*
*  Routine Name:  void xvf_error_callback(data, fid, id)
*
*      Purpose:   
*
*        Input:   
*
*       Output:   
*
*    Called By:
*
*   Written By:   Mark Young
*
*
*************************************************************/


void xvf_error_callback(data, fid, id)

caddr_t   data;
int       *fid;
XtInputId *id;
{
	form_signal *entry = (form_signal *) data;
	int         nbytes;
	char        buffer[512];


	if ((nbytes = read(*fid, buffer, 511)) <= 0)
	{
	   close(*fid);
	   XtRemoveInput(*id);
	   if (entry->error != NULL)
	      fclose(entry->error);

	   entry->inputid = NULL;
	   entry->timeid = XtAppAddTimeOut(xvf_app_context, 0, dispatch_entry,
					entry);
	}
	else if (nbytes > 0)
	{
	   buffer[nbytes] = '\0';
	   if (entry->errorfile == NULL)
	   {
	      entry->errorfile = vtempnam(ERRORFILE);
	      entry->error = fopen(entry->errorfile, "w");
	   }

	   if (entry->error != NULL)
	      fprintf(entry->error, "%s", buffer);
	   else
	      fprintf(stderr, "%s", buffer);
	}
}
