 /*
  * Khoros: $Id: system.c,v 1.5 1992/03/20 22:48:22 dkhoros Exp $
  */

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

 /*
  * $Log: system.c,v $
 * Revision 1.5  1992/03/20  22:48:22  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"

/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
   >>>>                                                       <<<<
   >>>>             file name: system.c                       <<<<
   >>>>                                                       <<<<
   >>>>		       Dispatch Routines                      <<<<
   >>>>                                                       <<<<
   >>>>       These routines are used to handle the execution <<<<
   >>>>	 of running processes.				      <<<<
   >>>>                                                       <<<<
   >>>>			xvf_fork()			      <<<<
   >>>>			xvf_system()			      <<<<
   >>>>			xvf_expression()		      <<<<
   >>>>			xvf_popen()			      <<<<
   >>>>			xvf_pclose()			      <<<<
   >>>>                                                       <<<<
   >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  */




/************************************************************
*
*  Routine Name:  xvf_fork
*
*      Purpose:   forks a child.  The child executes a
*		  routine based on the command, while the
*		  parent continues doing cantata programming
*		  stuff.
*
*        Input:   command - string containing the command to be executed
*
*       Output:   
*
*    Called By:   application programmer
*
*   Written By:   Mark Young
*
*
*************************************************************/


int xvf_fork(command, routine, clientData)

char	*command;
void	(*routine)();
caddr_t clientData;
{
	form_signal  *entry;
	int	     pid, errors[2], *error;


	/*
	 *  Add a callback for the error file, enabling us to display
	 *  any error messages after the process has terminated.  If we
	 *  not able to stat the process then we encountered an error.
	 */
	if (XVF_ECHO_CMD == true)
	   printf("%s\n", command);

	if (pipe(errors) == -1)
	{
	   error = NULL;
	   entry = xvf_add_handler(routine, clientData, -1);
	}
	else
	{
	   error = errors;
	   entry = xvf_add_handler(routine, clientData, errors[0]);
	}

#ifdef VFORK
	entry->pid = vfork();     /* create child to process in background */
#else
	entry->pid = fork();     /* create child to process in background */
#endif
	switch (entry->pid)
	{
	   case -1:
		if (errno == EAGAIN)
		{
		   (void) fprintf(stderr, "Could not fork process,  no more \
processes available.  Unix errno = EAGAIN. See UNIX man page describing fork.");
		}
		else if (errno == ENOMEM)
		{
		   (void) fprintf(stderr,"Could not fork process, not enough \
core.  Unix errno = ENOMEM. See UNIX man page describing fork.");
		}
		else
		{
		   (void) fprintf(stderr,"Could not fork process.  Unix errno =\
  %d. See UNIX man page describing fork and errno.", errno);
		}
		entry->pid = 0;
		break;

	   case 0:

		/*
		 *  this is the child.  We should never return after
		 *  calling 
		 */
		if (error != NULL)
		{
		   dup2(error[1], 2);
		   close(error[1]);
		}
		execute_routine(command);
		_exit(1);
		break; 

	   default:
		if (error != NULL)
	           close(error[1]);
#ifdef DEBUG
	        printf("inside parent pid = %d.\n", entry->pid);
#endif
	}
	if (entry->pid == 0)
	{
	   xvf_remove_handler(entry->pid);
	   pid = 0;
	}
	else
	   pid = entry->pid;

	return(pid);
}



/************************************************************
*
*  Routine Name:  xvf_system(command)
*
*      Purpose:   The child executes the routine in foreground based on the
*		  command passed in.  Call xvf_fork to execute
*		  the routine then wait until the process
*		  finishes before returning.
*
*        Input:   command - string containing the command to be executed
*
*       Output:
*
*    Called By:   application programmer
*
*   Written By:   Mark Young
*
*
*************************************************************/


int xvf_system(command)

char	*command;
{
	int	pid, status, xvf_fork(), xvf_waitpid();

	if ((pid = xvf_fork(command, XVF_SYSTEM, NULL)) != 0)
	{
	   if (xvf_waitpid(pid, (vstatus *) &status, NULL) != -1)
	      return(status);
	}
	return(-1);
}



/************************************************************
*
*  Routine Name:  xvf_expression(form, command)
*
*      Purpose:   The child executes the routine in foreground based on the
*		  command passed in.  This is done by using xvf_popen() then
*		  reading the output, which is then passed to the parser.
*
*        Input:   form    - from which we derive the expression id.
*		  command - string containing the command to be executed
*
*       Output:
*
*    Called By:   application programmer
*
*   Written By:   Mark Young
*
*
*************************************************************/


xvf_expression(form, command)

xvf_form *form;
char	 *command;
{
	long	id;
	float	value;
	FILE	*file, *xvf_popen();
	char	temp[512], error[512];


	if (XVF_EXPRESSION == false)
	{
	   (void) sprintf(temp,"Warning!  Expressions not currently enabled, \
but the following command expects to update the expression parser.\n\n%s\n\nDo \
you wish to continue anyway?", command);
	   if (xvf_warn_wait(temp, "xvf_expression", "Yes", "No") == 1)
	      xvf_system(command);

	   return;
	}

	if (!(file = xvf_popen(command, "r")))
	{
	   sprintf(temp, "Error! Cannot execute the following command\n\n\
'%s'\n\nNo update to any variables will be made.", command);
	   xvf_error_wait(temp, "xvf_expression", NULL);
	   return;
	}

	id = (long) form;
	while (fgets(temp, 512, file) != NULL)
	{
	   if (!xve_eval_float(id, temp, &value, error))
	   {
	      fprintf(stderr,"\n%s\n", error);
	   }
	   else if (XVF_ECHO_CMD == true)
	   {
	      fprintf(stderr,"%s",temp);
	   }
	}
	(void) xvf_pclose(file);
}



/************************************************************
*
*  Routine Name:  xvf_popen
*
*      Purpose:   process open.
*
*        Input:   command - string containing the command to be executed
*		  mode	  - mode for process (read or write).
*
*       Output:   
*
*    Called By:   application programmer
*
*   Written By:   Mark Young
*
*
*************************************************************/

static form_signal **popen_table = NULL;


FILE *xvf_popen(command, mode)

char	*command, *mode;
{
	FILE	     *file;
	form_signal  *entry;
	int	     child, parent, num, pipes[2], errors[2], *error;


	/*
	 *  Do some error checking to make sure that we have a valid command
	 *  and mode to work with.
	 */
	if (mode == NULL || command == NULL)
	   return(NULL);

	if (mode[0] != 'r' && mode[0] != 'w')
	   return(NULL);

	/*
	 *  Make sure that the popen_table array is not NULL
	 */
	if (popen_table == NULL)
	{

	   /*
	    *  Should use getdtablesize() but this function is not defined
	    *  on an HP.
	    num = getdtablesize();
	    */
	   num = 100;
	   if ((popen_table = (form_signal **) calloc(num, sizeof(form_signal *)
			* num)) == NULL)
	   {
	      return(NULL);
	   }
	}

	/*
	 *  Echo the command
	 */
	if (XVF_ECHO_CMD == true)
	   printf("%s\n", command);

	/*
	 *  Now that we have a valid mode, we need to create a pipe in order
	 *  for the calling routine to either read or write to the child
	 *  process.
	 */
	if (pipe(pipes) == -1)
	   return(NULL);

	if (mode[0] == 'r')
	{
	   parent = pipes[0];
	   child  = pipes[1];
	}
	else
	{
	   parent = pipes[1];
	   child  = pipes[0];
	}
	file = fdopen(parent, mode);


	/*
	 *  Add a callback for the error file, enabling us to display
	 *  any error messages after the process has terminated.  If we
	 *  not able to stat the process then we encountered an error.
	 */
	if (pipe(errors) == -1)
	{
	   error = NULL;
	   entry = xvf_add_handler(NULL, NULL, -1);
	}
	else
	{
	   error = errors;
	   entry = xvf_add_handler(NULL, NULL, errors[0]);
	}
#ifdef VFORK
	entry->pid = vfork();     /* create child to process in background */
#else
	entry->pid = fork();     /* create child to process in background */
#endif
	switch (entry->pid)
	{
	   case -1:
		if (errno == EAGAIN)
		{
		   (void) fprintf(stderr, "Could not fork process,  no more \
processes available.  Unix errno = EAGAIN. See UNIX man page describing fork.");
		}
		else if (errno == ENOMEM)
		{
		   (void) fprintf(stderr,"Could not fork process, not enough \
core.  Unix errno = ENOMEM. See UNIX man page describing fork.");
		}
		else
		{
		   (void) fprintf(stderr,"Could not fork process.  Unix errno =\
  %d. See UNIX man page describing fork and errno.", errno);
		}
		entry->pid = 0;
		break;

	   case 0:

		/*
		 *  this is the child.  We should never return after
		 *  calling 
		 */
		if (mode[0] == 'r' && child != 0)
		{
		   dup2(child, 1);
		   close(child);
		}
		else if (mode[0] == 'w' && child != 1)
		{
		   dup2(child, 0);
		   close(child);
		}

		/*
		 *  If an error pipe exists, then link in stderr.
		 */
		if (error != NULL)
		{
		   dup2(error[1], 2);
		   close(error[1]);
		}

		execute_routine(command);
		_exit(1);
		break; 

	   default:
	        close(child);
		if (error != NULL)
		   close(error[1]);

		popen_table[parent] = entry;
#ifdef DEBUG
	        printf("inside parent pid = %d.\n", entry->pid);
#endif
	}

	if (entry->pid == 0 || file == NULL)
	{
	   xvf_remove_handler(entry->pid);
	   return(NULL);
	}
	return(file);
}



/************************************************************
*
*  Routine Name:  xvf_pclose
*
*      Purpose:   process close.
*
*        Input:   file - the file descriptor
*
*
*    Called By:   application programmer
*
*   Written By:   Mark Young
*
*
*************************************************************/


int xvf_pclose(file)

FILE	*file;
{
	form_signal *entry;
	int	    status, xvf_waitpid();


	if (file == NULL)
	   return(NULL);

	entry = popen_table[fileno(file)];
	popen_table[fileno(file)] = NULL;

	if (entry == NULL)
	   return(NULL);

	fclose(file);
	if (xvf_waitpid(entry->pid, (vstatus *) &status, NULL) != -1)
	   return(status);

	return(-1);
}




/************************************************************
*
*  Routine Name:  execute_routine(command)
*
*      Purpose:   This is the child that executes a routine
*		  based on the form, while the parent continues
*		  doing xvips programming stuff.
*
*        Input:   command - command to be executed
*
*       Output:   none
*
*    Called By:   xvf_fork() and xvf_system()
*
*   Written By:   Stephanie Hallett and Mark Young
*
*
*************************************************************/



execute_routine(command)

char	*command;
{
	int	i;
	char	end_char;
	char	*args[MaxDBSize], *temp[MaxDBSize], routine[5*MaxLength];


	/*
	 *  Copy the command in case we fail so that we can print it to stderr.
	 */
	strcpy(routine, command);


	/*
	 *  Quick check to see if a we need to use csh to execute this job
	 */
	i = 0;
	if (strchr(command,'|') || strchr(command,';') || strchr(command,'>') ||
	    strchr(command,'<'))
	{
	   /*
	    *  Kludge in order to remove the quotes (") that form2command put
	    *  around string UIS lines.
	    */
	   vreplace_char(command, '"', ' ', command);
	   args[i++] = "/bin/csh";
	   args[i++] = "-cf";
	   args[i++] = command;
	}
	else
	{
	   while (*command != '\0' && i < MaxDBSize)
	   {
	      while (isspace(*command) && *command != '\0')
	         command++;

	      if (*command != '\0')
	      {
	         if (*command == '\'' || *command == '"')
	         {
		    end_char = *command++;
		    args[i] = command;
		    while ((end_char != *command) && (*command != '\0'))
		    {
		       if (*command == '\\' && *(command+1) != '\0')
		          command += 2;
		       else
		          command++;
		    }

		    if (*command != '\0')
		       *command = ' ';
	         }
		 else
		    args[i] = command;

	         while (!isspace(*command) && *command != '\0')
	            command++;

	         if (*command != '\0')
	         {
	            *command = '\0';
	            command++;
	         }

	         /*
	          *  Perform quick check to see if this job is to be executed
		  *  locally or remotely.  If it's gonna be executed remotely
		  *  then simply exec "kexec" to execute the job.
		  */
		 if (i == 0 && strchr(args[0],'@') != NULL)
		 {
		    temp[0] = "kexec"; temp[1] = "-c"; temp[2] = routine;
		    temp[3] = NULL;
		    execvp(temp[0], temp);

		    /*
		     *  Opps...  must have failed, so we'll continue and
		     *  see if kexecvp() execute the job.
		     */
		 }
		 i++;
	      }
	   }
	}
	args[i] = NULL;
	kexecvp(args[0], args);

	fprintf(stderr,"execute_routine:  failed to execute the following ");
	fprintf(stderr,"command:\n\n '%s'\n\n", routine);
	_exit(1);
}
