/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989, 1990, 1991
 *
 *    Please See COPYLEFT notice.
 *
 **************************************************************/
# include	"list.h"
#include        <signal.h>

char    *shname = NULL;                 /* Saved shell name             */
int	background = FALSE;

typedef	struct PROC {
	int	p_pid;		/* Process ID */
	char	*p_macro;	/* Completion macro to call. */
	} PROC;
Head_p	hd_procs;
int	child_sig = FALSE;	/* Set to TRUE when SIGCLD goes off. */

void
proc_add(pid, macro)
int	pid;
char	*macro;
{	char	*strdup();
	void	child_handler();
	
	PROC *pp = (PROC *) chk_alloc(sizeof (PROC));
	pp->p_pid = pid;
	pp->p_macro = macro ? strdup(macro) : NULL;
	if (hd_procs == NULL) {
		hd_procs = ll_init();
# if	defined(SIGCLD)
		signal(SIGCLD, child_handler);
# endif
# if	defined(SIGPIPE)
		signal(SIGPIPE, child_handler);
# endif
# if	defined(HAVE_SIGINTERRUPT)
		siginterrupt(SIGCLD, TRUE);
# endif
		}
	ll_append(hd_procs, (char *) pp);
}
void
child_handler()
{
	child_sig = TRUE;
}
/**********************************************************************/
/*   Function  to  wait  for  a  process to die, and return the exit  */
/*   status  of  the process. If pid is -1 then wait for any process  */
/*   to die, otherwise wait for a specific process to die.	      */
/*   								      */
/*   This  code  to  do  with  using the wait3()/wait4() stuff but I  */
/*   haven't  any  idea  how  to  do  this portably and still run on  */
/*   older Unix versions. Have to suffer this for now.		      */
/**********************************************************************/
int
proc_wait(pid)
int	pid;
{	int	dead_proc;
	PROC	*pp;
	List_p	lp;
	int	status;
	char	*cp, *cp1;
	BUFFER	*bp;
		
	while (1) {
		while (1) {
# if	defined(HAVE_SIGINTERRUPT)
			siginterrupt(SIGINT, TRUE);
# endif
			dead_proc = waitpid(pid, &status, pid < 0 ? WNOHANG : 0);
# if	defined(SIGCLD)
       			signal(SIGCLD, child_handler);
# endif
			if (dead_proc < 0 && pid < 0) {
				child_sig = FALSE;
				return status;
				}
			if (dead_proc >= 0)
				break;
			}
			
		/***********************************************/
		/*   We  got  a  process  dying.  So find the  */
		/*   buffer  which  this process was attached  */
		/*   to  and  save  the  status.  We  may get  */
		/*   called  here  even  if  WE  didnt create  */
		/*   the  process,  so  we are allowed not to  */
		/*   find   the  process.  (e.g.  a  system()  */
		/*   call or getcwd().			       */
		/***********************************************/
		for (bp = bheadp; bp; bp = bp->b_bufp)
			if (bp->b_display && 
			    bp->b_display->d_pid == dead_proc) {
				bp->b_wstat = status;
				break;
				}
				
		/***********************************************/
		/*   Check    for   a   callback   macro   on  */
		/*   completion of the process.		       */
		/***********************************************/
		for (lp = ll_first(hd_procs); lp; lp = ll_next(lp)) {
			pp = (PROC *) ll_elem(lp);
			if (pp->p_pid == dead_proc) {
				if (pp->p_macro) {
					char *tmpstr = chk_alloc(strlen(pp->p_macro) + 16);
					cp = tmpstr;
					for (cp1 = pp->p_macro; *cp1 && !isspace(*cp1); )
						*cp++ = *cp1++;
					*cp++ = ' ';
					sprintf(cp, " %d ", status);
					cp += strlen(cp);
					strcpy(cp, cp1);
					str_exec(tmpstr);
					chk_free(tmpstr);
					}
				chk_free((void *) pp);
				ll_delete(lp);
				break;
				}
			}
		if (lp == NULL || pid < 0 || pid == dead_proc)
			break;
		}
	child_sig = FALSE;
	if (!background)
		update();
	return status;
}
char *
get_shell()
{	char	*ggetenv();
	if (shname == NULL) {
		shname = ggetenv("SHELL");
# if defined(MSDOS)
		if (shname == NULL)
			shname = ggetenv("COMSPEC");
# endif
		if (shname == NULL)
			shname = ggetenv("shell");
		if (shname == NULL)
			shname = "/bin/csh";     
		}
	return shname;
}

/**********************************************************************/
/*   Function  called  if we are about to spawn a subshell or we are  */
/*   about to handle a SIGTSTP					      */
/**********************************************************************/
void
get_ready_to_stop(repaint)
int	repaint;
{
	exit_graphics_mode();
	ttcolor(FG(col_table.c_normal));
	ttmove(nrow-1, 0);
	if (repaint) {
		tteeol();
		ttclose();
		tttidy();
		}
}

/**********************************************************************/
/*   Function  called  to  repaint  screen and set up tty mode after  */
/*   doing a shell escape or handling JOB control		      */
/**********************************************************************/
void
resume_after_stopping(repaint)
int	repaint;
{

	ttopen(repaint);
	if (repaint) {
		screen_garbled = TRUE;
		}
	else
		screen_garbled = FALSE;
	/***********************************************/
	/*   Stop  autosave  going  off  if  we  were  */
	/*   away for too long.			       */
	/***********************************************/
	clear_timers();
}
void
do_shell()
{
	char	*command = get_str(1);
	extern char     *strrchr();
	register void    (*oisig)();
	register void   (*oqsig)();
	int	pid;
	int	status = 0;
	int	repaint = argv[2].l_flags != F_INT || argv[2].l_int == 0;
	char	*macro_name = get_str(3);

	get_shell();

	get_ready_to_stop(repaint);
	oisig = signal(SIGINT,  SIG_IGN); 
# if defined(SIGQUIT)
	oqsig = signal(SIGQUIT,  SIG_IGN); 
# endif
	background = TRUE;
# if defined(MSDOS)
	system("cmd");
# else
# if defined(VMS)
	system(argv[1].l_flags != F_NULL ? command : (char *) NULL);
# else
	if ((pid = fork()) == 0) {
		register char *name = strrchr(shname, '/');
		if (name)
			name++;
		else
			name = "sh";
# if	defined(SIGCLD)
		(void) signal(SIGCLD,  SIG_DFL);
# endif
		(void) signal(SIGINT,  SIG_DFL);
		if (argv[1].l_flags != F_NULL) 
			execl(shname, name, "-c", command, (char *) NULL);
		else
			execl(shname, name, "-i", (char *) NULL);
		printf("Couldn't exec(%s, %s, %s, %s)\n", shname, name, command ? "-c" : "-i", command ? command : "(char *) NULL");
		exit(1);
		}
	proc_add(pid, *macro_name ? macro_name : (char *) NULL);
	if (*macro_name == NULL)
		status = proc_wait(pid);
# endif
# endif
	signal(SIGINT,  oisig);
# if defined(SIGQUIT)
	signal(SIGQUIT,  oqsig); 
# endif
	background = FALSE;
	resume_after_stopping(repaint);
	line_col(TRUE);
	acc_assign_int((long) status);
}
