/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
 */

#include <string.h>

#define import_spp
#define import_libc
#define import_stdio
#define import_xwhen
#include <iraf.h>

#include "config.h"
#include "clmodes.h"
#include "mem.h"
#include "opcodes.h"
#include "operand.h"
#include "param.h"
#include "task.h"
#include "errs.h"
#include "grammar.h"
#include "proto.h"


/*
 * EXEC -- Functions that prepare tasks for running, the actual runtime
 * interpreter, and functions involved in wrapping up when a task dies.
 */

extern int cldebug;
extern int cltrace;

#define	SZ_STARTUPMSG	4000	/* cmd sent to subprocess to run task	*/
#define	BINDIR		"bin$"	/* where installed executables go	*/	

extern	FILE *yyin;		/* yyparse's input			*/
extern	int alldone;		/* set when oneof pops firstask		*/
extern	int yeof;		/* parser saw EOF			*/
extern	int gologout;		/* user typed logout()			*/
extern	int loggingout;		/* in the process of logging out	*/
extern	int currentline;	/* current script line being executed	*/
extern	int in_iferr;		/* currently in an iferr block		*/
		    
extern  ErrCom errcom;		/* execution error recovery struct	*/

	
long int run_level 	= 0;
int 	do_error  	= YES;
char	*onerr_handler  = NULL;


/* RUN -- Run the code beginning at pc until we run an EXEC instruction of
 *   something other than a builtin command or END instruction.
 *   The EXEC instruction means that a new task is being started and we should
 *   return to the parser in the main "parse/run" loop in main.  If, however,
 *   the exec was for a builtin (or procedure, someday) then no parsing is to
 *   be done and we just continue on with the current code.
 * Note that execing the bye builtin is not a special case since it does a
 *   restor() which resets the pc to the instruction immediately following the
 *   exec IN THE PARENT task and we continue on with it.
 * Increment pc after each "fetch" cycle and before the "exec" cycle.
 *   If any if the instructions fail, they will call error(). this will do
 *   a longjmp(errenv,1), causing setjmp to return (in main) and an
 *   immediate retreat to the most recent terminaltask with unwind().
 */
void 
run (void)
{
	register struct codeentry *cp;
	register int opcode;
	char *task;

	if (cltrace) {
	    cp = coderef(pc);
            currentline = cp->c_scriptln;
	    eprintf ("\t----- task %s - line %d (%s) -----\n",
		currentask->t_ltp->lt_lname, 
		currentline, op2str(cp->c_opcode));
	}
	if (cltrace > 1) eprintf ("start of run()\t\t\n");

	do {
	    cp = coderef (pc);
	    opcode = cp->c_opcode;

  	    if (currentask->t_flags &  T_SCRIPT)
	        currentline = cp->c_scriptln;

	    currentask->t_callln = currentline;

	    if (cltrace)
		d_instr (stderr, "\t", pc);
	    if (cldebug)
		eprintf("run: pc=%d, op=%s\n", pc, op2str(opcode), currentline);

	    if (coderef(pc)->c_opcode == CALL) {
		task = (char *)&(coderef(pc)->c_args);

	        if (cltrace && run_level > 0)
		    eprintf ("%d: CALL %s\n", run_level, task);

		if (strncmp ("error", task, 5) != 0)
	            errcom.linenum = currentline;

		run_level++;

	    } else if (coderef(pc)->c_opcode == END)
		run_level--;

	    pc += cp->c_length;
	    (*opcodetbl[opcode]) (&cp->c_args);

	} until ((opcode == EXEC && !(newtask->t_flags & T_BUILTIN)) ||
		  opcode == END || alldone);

	if (cltrace > 1) eprintf ("end of run()\t\t\n");
}


/* CALLNEWTASK -- Called from CALL instruction to push and setup a new task
 *   structure.  If find a known ltask with given name create a new task on
 *   control stack, set up newtask and defaults for the pseudofiles.
 *   Pseudofiles may be effected by other instructions before it gets to exec.
 * Make sure we have a pfile list; either try to read it if task is 
 *   supposed to have a real one or manufacture the beginnings of one if it
 *   isn't and set PF_FAKE.  New task runs with a copy of the pfile if it
 *   wasn't fake.  Guard against making more than one copy.  Also, don't dup
 *   the cl's params to maintain the meaning of "firstask".  Things like mode,
 *   logfile and abbreviations should be global and permanent.
 * Special case for package names essentially runs a cl but with a new curpack,
 *   the only real semantic intent of "running" a package.
 *   This lets a package name given as a command appear to change the current
 *   package and yet remain interactive.  Since it really is a new task, state
 *   saving and restoring on error will work right and we also achieve an
 *   ability to have multiple package defn's in a script ltask.
 *   Any parameter references will refer to the cl's also.
 */
void
callnewtask (char *name)
{
	/* x1 and x2 are just place holders to call breakout().
	 */
	char	*x1, *pk, *t, *x2;	
	struct	ltask *ltp;
	int	flags, ltflags;

	if (cldebug)
	    eprintf ("callnewtask: name=%s, currentask=%x\n", name, currentask);

	currentline = coderef(pc)->c_scriptln;

	/* Save current dictionary and stack pointers. They get restored when
	 * the new task dies normally and the current task is to continue.
	 * Save pc when get to the EXEC instruction so it continues from there.
	 */
	currentask->t_topos = topos;	/* save these two just in case 	*/
	currentask->t_basos = basos;	/* something is left on the stk	*/
	currentask->t_topcs = topcs;	/* save before adding newtask	*/
	currentask->t_topd  = topd;	/* save before adding pfile	*/
	currentask->t_curpack = curpack;/* save in case changing to a new one*/
	c_envmark (&currentask->t_envp);/* save env stack pointer	*/
	currentask->t_pno = 0;		/* set only if task defines pkg	*/
	currentask->t_callln = currentline;

	newtask = pushtask();
	flags = 0;

	/* Search for the command to run.  A leading '$' signifies that
	 * execution is to be time but is not part of the name.  Set ltp
	 * and newtask->t_pfp depending on whether we are running a task or
	 * a package.
	 */
	if (*name == '$') {
	    flags |= T_TIMEIT;
	    name++;
	}

	breakout (name, &x1, &pk, &t, &x2);
	ltp = cmdsrch (pk, t);

	if (ltp->lt_flags & LT_CL) {
	    /* Change curpack if LT_PACCL. (cmdsrch() set lt_pkp).  Just
	     * changing packages; use cl's ltask and pfile.  Push a new cl()
	     * on the control stack, with the T_PKGCL and T_CL flags set.
	     */
	    if (ltp->lt_flags & LT_PACCL) {
		flags |= T_PKGCL;
		curpack = ltp->lt_pkp;
	    } else if (ltp->lt_flags & LT_CLEOF)
		flags |= T_CLEOF;

	    ltp = firstask->t_ltp;
	    newtask->t_pfp = firstask->t_pfp;

	    /* Initialize the lexical analyzer (necessary to recognize BOL).
	     */
	    lexinit();

	} else {
	    if (ltp->lt_flags & LT_PFILE) {
		register struct pfile *pfp;

		/* This task has a real pfile. read in if not already in
		 * core.  Copy if not already one and not just cl.
		 */
		newtask->t_pfp = NULL;
		if ((pfp = pfilefind (ltp)) == NULL)
		    pfp = pfileload (ltp);
		if (!(pfp->pf_flags & PF_COPY) && ltp != firstask->t_ltp)
		    pfp = pfilecopy (pfp);
		newtask->t_pfp = pfp;

		/* Also load any pset files associated with the main pfile.
		 * These are linked into a list with the main pfile at the
		 * head of the list, pointed to by the task descriptor.
		 */
		if (pfp->pf_flags & PF_PSETREF) {
		    register struct param *pp;
		    struct  operand o;
		    char    *pset;

		    for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
			if (!(pp->p_type & PT_PSET))
			    continue;
			o = pp->p_valo;
			if (opundef(&o) || *(pset = o.o_val.v_s) == EOS)
			    pset = pp->p_name;
			pfp = pfp->pf_npset = pfilecopy (pfilesrch (pset));
			pfp->pf_psetp = pp;
		    }
		}

	    } else {
		/* This task does not have a real pfile so start a fake one.
		 */
		newtask->t_pfp = newpfile (ltp);
		newtask->t_pfp->pf_flags = PF_FAKE;
	    }
	}

	newtask->t_pfp->pf_n = 0;	/* init number of command line args */
	newtask->t_ltp = ltp;
	newtask->t_pid = -1;		/* gets set if do a real exec	*/
	newtask->t_stdin = currentask->t_stdin;	/* inherit files	*/
	newtask->t_stdout = currentask->t_stdout;
	newtask->t_stderr = currentask->t_stderr;
	newtask->t_stdgraph = currentask->t_stdgraph;
	newtask->t_stdimage = currentask->t_stdimage;
	newtask->t_stdplot = currentask->t_stdplot;

	/* Init i/o redirection for a foreign task.
	 */
	newtask->ft_in = newtask->ft_out = newtask->ft_err = NULL;

	/* Set up flags describing the kind of task we are about to run. the
	 * absence of any of these flags will imply a genuine executable task.
	 * the flags in t_flags are more of a convenience than anything since
	 * later tests could use the same tests used here.
	 */
	ltflags = ltp->lt_flags;

	if (ltflags & LT_PSET) {
	    flags = (T_SCRIPT|T_PSET);
	} else if (ltflags & LT_SCRIPT) {
	    newtask->t_scriptln = 0;
	    flags = T_SCRIPT;
	} else if (ltflags & LT_FOREIGN) {
	    flags = T_BUILTIN | T_FOREIGN;	/* a type of builtin */
	} else if (ltflags & LT_BUILTIN) {
	    flags = T_BUILTIN;
	} else if (ltflags & LT_CL) {
	    /* Or, not assign: preserve T_PKGCL and T_CLEOF flags if set. */
	    flags |= T_CL;
	}

	if (ltflags & LT_STDINB)
	    flags |= T_STDINB;
	if (ltflags & LT_STDOUTB)
	    flags |= T_STDOUTB;

	newtask->t_flags = flags;
}


/* EXECNEWTASK -- Called from the EXEC instruction after all param and stdio
 * processing for the new task is complete.  Here we actually run the new task,
 * either directly in the case of a builtin function, or as a new case for
 * main()'s loop.  Do not set newtask to NULL so that run() can tell what it
 * exec'd.
 */
void
execnewtask (void)
{
	/* VMS C V2.1 cannot handle this (see below).
	 * register struct pfile *pfp;
	 */
        struct  codeentry *cep;
	static	struct pfile *pfp;

	struct	param *pp;

	if (newtask == NULL)
	    /* if this ever happens, i don't want to know about it. */
	    return;

	currentask->t_pc = pc;		/* instruction after EXEC	*/
        cep = coderef (pc);
        currentline = cep->c_scriptln;


	if (cldebug)
	    eprintf ("execnewtask: pc = %d  (line %d)\n", pc, currentline);

	if (newtask->t_flags & T_BUILTIN) {
	    /* set yyin in case a builtin reads someday; none do now.
	     * unlink newtask's fake param file and reset top of dictionary
	     *   to what it was before the fake param file was added; it is
	     *   still there, however, for the builtin to use. this is done
	     *   since some builtins (eg task) want to add things that are
	     *   to stay on the dictionary and the tools all start at topd.
	     * the return is back to run(); it will continue since it will
	     *   see that newtask was just a builtin.
	     * note that we do not reset pf_n, as with other fake pfiles,
	     *   as this is the way builtins get their number of arguments
	     *   (it's faster than building them a $nargs).
	     */
	    yyin = newtask->t_in = currentask->t_in;	/* inherit pipe */
	    newtask->t_out = currentask->t_out;
	    newtask->t_modep = currentask->t_modep;	/* inherit mode */

	    /* VMS C 2.1 Optimizer cannot handle this.
	     * parhead = dereference (reference (pfile, parhead)->pf_npf);
	     */
	    pfp = reference (pfile, parhead);
	    parhead = dereference (pfp->pf_npf);

	    topd = currentask->t_topd;
	    currentask = newtask;
	    newtask->t_flags |= T_RUNNING;

	    if (cldebug)
		eprintf ("execnewtask: calling new task@@%x\n", newtask);
	    if (cltrace)
		eprintf ("\t----- exec %s %s : line %d -----\n",
		    (newtask->t_flags & T_FOREIGN) ? "foreign" : "builtin",
		    newtask->t_ltp->lt_lname, coderef(pc)->c_scriptln);

	    (*newtask->t_ltp->lt_f)();
	    oneof();		/* proceed as though this task saw eof	  */
	    return;
	}

	pfp = newtask->t_pfp;

	/* If the new task is a cl, we are not running in background and
	 * its t_in is stdin, it is interactive.  Note that when a package
	 * is loaded by a script task rather than interactively by the user,
	 * the t_in of the cl() in the package script task will be reading
	 * from the calling script task rather than from the original stdin
	 * (the user terminal), hence is not interactive.  If this task is
	 * flagged interactive, taskunwind() may elect to restart it on an
	 * error so save present state for restor().
	 */
	if (newtask->t_flags & T_CL) {
	    if (cldebug)
		eprintf ("execnewtask: new task is the CL\n");
	    if (cltrace)
		eprintf ("\t----- exec cl -----\n");

	    /* Call set_clio to set the command input and output streams
	     * t_in and t_out for a cl() or package_name() command.
	     */
	    set_clio (newtask);

	    /* This code is a temporary patch to allow packages to be 
	     * loaded from within scripts regardless of whether there 
	     * are enclosing brackets.  If a CL statement is executed
	     * within a script which is itself called within another
	     * script, then we will do an implicit keep before the CL.
	     */
	    if (topcs + 2*TASKSIZ <= STACKSIZ)
		if ((strcmp (newtask->t_ltp->lt_lname, "cl") == 0) ||
 		    (strcmp (newtask->t_ltp->lt_lname, "clbye") == 0))
		    if ((currentask->t_flags &  T_SCRIPT) &&
		        (prevtask->t_flags & T_SCRIPT))
			keep(prevtask);

	    /* If newtask is cleof(), close the input stream of the current
	     * task (the task whose input contained the cleof), and reopen
	     * as the null file.
	     */
	    if (newtask->t_flags & T_CLEOF) {
		if (currentask->t_in != stdin)
		    fclose (currentask->t_in);
		if (currentask != firstask)
		    currentask->t_in = fopen ("dev$null", "r");
	    }

	    if (!(firstask->t_flags & T_BATCH) &&
		 (newtask->t_in == stdin) && (newtask->t_out == stdout)) {
		newtask->t_flags |= T_INTERACTIVE;
		newtask->t_topd = topd;
		newtask->t_topos = topos;
		newtask->t_topcs = topcs;
		newtask->t_curpack = curpack;
	    }
	}

	/* Standardize the pfile.
	 * Set (or create if necessary) `$nargs', number of command line args,
	 *   based on pf_n which is set for each command line argument by
	 *   posargset, et al.
	 * If this ltask had no paramfile and we built one up from the
	 *   command line, then we need to add a `mode' param.  If it did have
	 *   a paramfile, then pfileload has already added it for us.
	 *   Point t_modep to the mode param for newtask.
	 */
	pp = paramfind (pfp, "$nargs", 0, YES);
	if (pp == NULL || (XINT)pp == ERR) {
	    char nabuf[FAKEPARAMLEN];
	    sprintf (nabuf, "$nargs,i,h,%d\n", pfp->pf_n);
	    pp = addparam (pfp, nabuf, NULL);
	    pp->p_mode |= M_FAKE;		/* never flush out $nargs */
	} else
	    pp->p_val.v_i = pfp->pf_n;

	if (pfp->pf_flags & PF_FAKE) {
	    newtask->t_modep = addparam (pfp, "mode,s,h,q\n", NULL);
	    /* pf_n will be used by paramsrch() to count positional arg
	     * matches; see it and param.h.
	     */
	    pfp->pf_n = 0;
	} else {
	    newtask->t_modep = paramfind (pfp, "mode", 0, YES);
	}

	if (newtask->t_modep == NULL)
	    cl_error (E_IERR, "no mode param for task `%s'",
		newtask->t_ltp->lt_lname);

	/* If task is being run in menu mode, call up eparam so that the user
	 * can edit/inspect the parameters.  If eparam is exited with ctrl/c
	 * do not run the task or update the pfile.  The parameter editor
	 * will make a copy of the task's pfile(s), edit it, and if necessary
	 * update the incore version created earlier by callnewtask().
	 */
	if ((taskmode(newtask) & M_MENU) || (newtask->t_flags & T_PSET)) {
	    if (epset (newtask->t_ltp->lt_lname) == ERR) {
		if (newtask->t_flags & T_PSET)
		    cl_error (E_UERR, "parameter file not updated");
		else
		    cl_error (E_UERR, "menu mode task execution aborted");
	    }
	}

	/* Set up bascode so new task has a good place to start building
	 * code.  See how the pc is set up before each call to the parser in
	 * main() loop.
	 */
	newtask->t_bascode = topos + 1;

	/* Set up io paths.  If the new task is cl(), it's command input
	 * and output streams are connected to those of the task which
	 * called currentask.  If the currentask is the firstask, there
	 * was no caller (no prevtask), so we must watch out for that.
	 * In the case of a script, commands are read from the script.
	 * In the case of a process, commands are read from the process.
	 */
	if (newtask->t_flags & T_PSET) {
	    newtask->t_in = fopen ("dev$null", "r");
	    newtask->t_out = newtask->t_stdout;

	} else if (newtask->t_flags & T_SCRIPT) {
	    if (cltrace)
		eprintf ("\t----- exec script %s (%s) - line %d -----\n",
		    newtask->t_ltp->lt_lname, newtask->t_ltp->lt_pname,
		    coderef(pc)->c_scriptln);

	    newtask->t_in = fopen (newtask->t_ltp->lt_pname, "r");
	    if (newtask->t_in == NULL)
		cl_error (E_UERR|E_P, "can not open script file `%s'",
		    newtask->t_ltp->lt_pname);
	    newtask->t_out = newtask->t_stdout;

	} else if (newtask->t_flags & T_CL) {
	    /* The command streams t_in and t_out have already been
	     * set up above by set_clio() in the test for T_INTERACTIVE.
	     */
	    /* Do nothing */

	} else {
	    char    startup_msg[SZ_STARTUPMSG+1];
	    int     timeit;

	    /* Connect to an executable process.
	     */
	    mk_startupmsg (newtask, startup_msg, SZ_STARTUPMSG);
	    timeit = (newtask->t_flags & T_TIMEIT) != 0;
	    if (cltrace)
		eprintf ("\t----- exec external task %s - line %d -----\n",
		    newtask->t_ltp->lt_lname, coderef(pc)->c_scriptln);
	    newtask->t_pid = pr_connect (
		findexe (newtask->t_ltp->lt_pkp, newtask->t_ltp->lt_pname),
		startup_msg,
		&newtask->t_in, &newtask->t_out,
		newtask->t_stdin, newtask->t_stdout, newtask->t_stderr,
		newtask->t_stdgraph, newtask->t_stdimage, newtask->t_stdplot,
		timeit);
	}

	yyin = newtask->t_in;	/* set the input for the parser	*/

	/* Tell parser what to expect.
	 */
	parse_state = PARSE_FREE;
	if (newtask->t_flags & T_SCRIPT) {
	    proc_script = (newtask->t_flags & T_PSET) ? NO : procscript(yyin);

	    if (proc_script) {
		parse_state = PARSE_BODY;
		/* Skip to the BEGIN statement */
		newtask->t_scriptln = skip_to (yyin, "begin");
		if (newtask->t_scriptln == ERR)
		    cl_error (E_UERR, "No BEGIN statement in procedure script");

		/* Reset pointer here.
		 */
		proc_script = NO;
	    }
	}

	/* Log a start message for script and executable tasks.
	 */
	if (keeplog() && log_trace())
	    if (newtask->t_flags & T_SCRIPT || newtask->t_pid != -1) {
	    	char  logmsg[SZ_LINE];
	    	sprintf (logmsg, "Start (%s)", newtask->t_ltp->lt_pname);
	    	putlog (newtask, logmsg);
	    }

	newtask->t_flags |= T_RUNNING;
	currentask = newtask;	/* continue as new the new task; at last. */

	if (cldebug)
	    eprintf ("Returning from execnewtask.yyin, ct_in, nt_in:%d %d %d\n",
		yyin, currentask->t_in, newtask->t_in);
}


/* MK_STARTUPMSG -- Format the command to be sent to the interpreter in the
 * IRAF Main in the child to execute the indicated logical task.  The format
 * of this command is
 *
 *	taskname redir_args paramset_args
 *
 * where "redir_args" are used to either inform the task that a stream has
 * been redirected by the CL (file "$") or to actually redirect a stream,
 * and where "paramset_args" are assignments of the form "param=value".
 * For example, "4 > $" tells the task that its standard output (4 = integer
 * value of STDOUT) has been redirected.  Only parameters with static values,
 * i.e., with predefined values that are not expected to change during task
 * execution (no queries) may be passed on the command line.
 */
void
mk_startupmsg (
    struct task *tp,			/* task being executed		*/
    char *cmd,				/* receives formatted command	*/
    int maxch				/* max chars out		*/
)
{
	register char	*ip, *op, *cp;
	struct	pfile *pfp;
	struct	operand o;
	struct	param *pp;
	char	redir[20];

	/* Start with the task name.
	 * Task names which begin with an underscore are used to implement
	 * "invisible" commands which are not intended to be part of the
	 * user interface.  The distinction between these and regular
	 * commands is restricted to the CL, hence the leading underscore
	 * is stripped from the task name sent to the process.
	 */
	ip = tp->t_ltp->lt_lname;
	while (*ip == CH_INVIS)
	    ip++;
	strcpy (cmd, ip);

	/* Add redirection information.  We can omit the pseudofile stream
	 * codes for the standard input and output as the iraf main will
	 * assume those streams if no stream code is given, though we must
	 * be explicit for stderr and the graphics streams.
	 */
	if (tp->t_flags & (T_MYIN|T_MYOUT|T_MYERR)) {
	    if (tp->t_flags & T_MYIN)
		strcat (cmd, " < $");
	    if (tp->t_flags & T_MYOUT)
		strcat (cmd, " > $");
	    if (tp->t_flags & T_MYERR) {
		sprintf (redir, " %d> $", STDERR);
		strcat (cmd, redir);
	    }
	}
	if (tp->t_flags & (T_MYSTDGRAPH|T_MYSTDIMAGE|T_MYSTDPLOT)) {
	    if (tp->t_flags & T_MYSTDGRAPH) {
		sprintf (redir, " %d> $", STDGRAPH);
		strcat (cmd, redir);
	    }
	    if (tp->t_flags & T_MYSTDIMAGE) {
		sprintf (redir, " %d> $", STDIMAGE);
		strcat (cmd, redir);
	    }
	    if (tp->t_flags & T_MYSTDPLOT) {
		sprintf (redir, " %d> $", STDPLOT);
		strcat (cmd, redir);
	    }
	}

	for (cp=cmd;  *cp;  cp++)
	    --maxch;

	/* Add parameter assignments for all non list-structured parameters
	 * whose access would not cause a query, i.e., those parameters which
	 * already have a legal value and which are either hidden or were set
	 * on the command line.  Passing the values of these parameters on the
	 * command line speeds task startup by reducing the number of parameter
	 * requests that must be processed by handshaking over the IPC.
	 */
	for (pfp = tp->t_pfp;  pfp;  pfp = pfp->pf_npset) {
	    for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
		o = pp->p_valo;

		/* Do not cache parameters which have an undefined value or
		 * for which the value is indirect to another parameter.
		 * Also, array parameters can not be cached currently.
		 */
		if (o.o_type & OT_UNDEF)
		    continue;
		if ((o.o_type & OT_BASIC) == OT_STRING &&
		    (o.o_val.v_s[0] == PF_INDIRECT))
		    continue;

		if (pp->p_type & PT_ARRAY)
		    continue;

		if (!(pp->p_type & PT_LIST) && !(effmode(pp) & M_QUERY)) {
		    char   buf[SZ_LINE+1];
		    char   val[SZ_LINE+1];

		    /* First format the param=value string in buf.
		     */

		    /* Start with "param=" if main pfile, or "pset.param=" if
		     * pset-param pfile.
		     */
		    if (pfp->pf_psetp != NULL) {
			ip = pfp->pf_psetp->p_name;
			for (op=buf;  (*op = *ip++);  op++)
			    ;
			*op++ = '.';
		    } else
			op = buf;

		    for (ip=pp->p_name;  (*op = *ip++);  op++)
			;
		    *op++ = '=';

		    /* Add "value".  If the parameter is string valued enclose
		     * the string in quotes and convert any newlines into \n.
		     */
		    sprop (val, &pp->p_valo);
		    if ((pp->p_type & OT_BASIC) == OT_STRING)
			*op++ = '"';

		    for (ip=val;  (*op = *ip++);  op++)
			if (*op == '\n') {
			    *op++ = '\\';
			    *op = 'n';
			} else if (*op == '"') {
			    *op++ = '\\';
			    *op = '"';
			}

		    if ((pp->p_type & OT_BASIC) == OT_STRING)
			*op++ = '"';

		    *op = EOS;

		    /* Now check to see if there is room in the output buffer.
		     * If not we can just quit, as the task will automatically
		     * query for any parameters not set on the command line.
		     * If there is room break the current line by appending \\n
		     * (an escaped newline) and append the new line.
		     */
		    maxch -= (strlen(buf) + 2);
		    if (maxch <= 0)
			break;

		    *cp++ = '\\';
		    *cp++ = '\n';

		    for (ip=buf;  (*cp = *ip++);  cp++)
			;
		}
	    }
	}

	/* Terminate the command line by appending an unescaped newline.
	 */
	*cp++ = '\n';
	*cp = EOS;

	if (cldebug)
	    eprintf ("CALL %s", cmd);
}


/* FINDEXE -- Search a set of standard places for an executable file to be
 * run.  Currently, we check first in the logical directory BIN for the
 * "installed" version of the executable, and if that is not found, use
 * the pathname given, which is the pathname specified in the TASK declaration.
 */
char *
findexe (
    struct package *pkg,	/* package in which task resides */
    char *pkg_path	/* pathname of exe file given in TASK statement	*/
)	
{
	static	char bin_path[SZ_PATHNAME+1], loc_path[SZ_PATHNAME+1];
	char	root[SZ_FNAME+1], root_path[SZ_PATHNAME+1];
	char	bindir[SZ_FNAME+1], *ip = NULL, *arch = NULL;
	char	bin_root[SZ_PATHNAME+1];


	memset (root, 0, SZ_FNAME);
	memset (bindir, 0, SZ_FNAME);
	memset (bin_path, 0, SZ_PATHNAME);
	memset (loc_path, 0, SZ_PATHNAME);
	memset (bin_root, 0, SZ_PATHNAME);
	memset (root_path, 0, SZ_PATHNAME);

	c_fnroot (pkg_path, root, SZ_FNAME);
	c_fpathname ((pkg ? pkg->pk_bin : BINDIR), root_path, SZ_PATHNAME);
	sprintf (bin_path, "%s%s.e", pkg ? pkg->pk_bin : BINDIR, root);
	sprintf (loc_path, "./%s.e", root);
	arch = envget ("arch");


	if (c_access (bin_path, 0, 0) == YES) {
	    return (bin_path);
	} else if (arch != NULL) {
	    /*  The binary wasn't found in the expected bin directory, but
	     *  on certain platforms look for alternate binaries that may
	     *  work.  This supports backward compatability with older
	     *  packages that may not have been upgraded to architecture
	     *  conventions in this release but which may contain usable
	     *  binaries (e.g. 32-bit 'linux' binaries on 64-bit systems
	     *  or older 'redhat' binaries where the core arch is 'linux').
	     */
	    memset (bin_root, 0, SZ_PATHNAME);
	    strcpy (bin_root, root_path);
	    if ((ip = strstr (bin_root, arch)))
		*ip = '\0';
	    else {
		int len = strlen (bin_root);
		if (bin_root[len-1] == '/')
		    bin_root[len-1] = '\0';
	    }

	    if (strcmp (arch, ".linux64") == 0) {
		/*  On 64-bit Linux systems we can use either of the
		 *  available 32-bit binaries if needed.  In v2.15 and
		 *  later, 'linux' is the preferred arch but look for
		 *  'redhat' in case it's a package that hasn't been
		 *  updated.
		 */
		sprintf (bin_path, "%s.linux/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
	    	    return (bin_path);

		sprintf (bin_path, "%s.redhat/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
	    	    return (bin_path);

	    } else if (strcmp (arch, ".linux") == 0) {
		/*  On 32-bit Linux systems, check for older 'redhat' binaries.
		 */
		sprintf (bin_path, "%s.redhat/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
	    	    return (bin_path);

	    } else if (strcmp (arch, ".macos64") == 0) {
		/*  On 64-bit Mac systems, check for older 'macintel' binaries.
		 */
		sprintf (bin_path, "%s.macintel/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
		    return (bin_path);

	    } else if (strcmp (arch, ".macintel") == 0) {
		/*  On 64-bit Mac systems, check for older 32-bin binaries.
		 */
		sprintf (bin_path, "%s.macosx/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
	    	    return (bin_path);

	    } else if (strcmp (arch, ".macosx") == 0) {
		/*  On 32-bit Mac systems, check for older 'macintel' binaries.
		 */
		sprintf (bin_path, "%s.macintel/%s.e", bin_root, root);
		if (c_access (bin_path, 0, 0) == YES)
	    	    return (bin_path);
	    }
	}

	if (c_access (pkg_path, 0, 0) == YES)
	    return (pkg_path);
	else
	    return (loc_path);
}


/* SET_CLIO -- Set the command input and output for the new cl().  If the
 * standard input or output has been redirected, use that, otherwise inherit
 * the t_in, t_out of the task preceeding the most recent non-CL task that has
 * the same t_in as the current task (this is not obvious, but permits packages
 * to be called or loaded within scripts).  In the case of a cl() type task
 * used to change packages, change the current package and push a cl() on the
 * control stack but continue reading from the current command stream.
 */
void
set_clio (register struct task *newtask)
{
	register struct task *tp;

	if ((newtask->t_stdin == currentask->t_stdin) &&
	    (currentask->t_in != stdin)) {
	    newtask->t_in = NULL;

	    if (newtask->t_flags & T_PKGCL) {		/* package()	*/
		newtask->t_in = currentask->t_in;
		tp = currentask;
	    } else {					/* cl()		*/
		for (tp=currentask;  tp != firstask; tp = next_task(tp))
		    if (!(tp->t_flags & T_CL) &&
			 (tp->t_in == currentask->t_in)) {
			tp = next_task(tp);
			newtask->t_in = tp->t_in;
			break;
		    }
	    }
	    if (newtask->t_in == NULL)
		cl_error (E_IERR, "Cannot find t_in for cl()");

	} else {					/* pk|cl <	*/
	    tp = NULL;
	    newtask->t_in = newtask->t_stdin;
	}

	if ((newtask->t_stdout == stdout) && (tp != NULL))
	    newtask->t_out = tp->t_out;
	else
	    newtask->t_out = newtask->t_stdout;		/* pk|cl >	*/
}


/* PPFIND -- Search the list of loaded psets for a task for the named
 * parameter.  If a taskname is given, search only the pset with that
 * taskname, else search all the psets associated with the running task.
 * This is called by the routines in opcodes.c to perform command line
 * assignments to parameters.
 */
struct param *
ppfind (
    struct pfile *pfp,		/* first pfile in chain		*/
    char *tn,			/* psetname (taskname) or null	*/
    char *pn,			/* parameter name		*/
    int pos,			/* for paramfind		*/
    int abbrev			/* for paramfind		*/
)
{
	struct	param *pp, *m_pp;
	struct	pfile *m_pfp = NULL;
	int	nchars;

	if (tn != NULL && *tn != EOS) {
	    /* Locate the named pset and search it. */
	    for (nchars=strlen(tn), m_pp=NULL;  pfp;  pfp = pfp->pf_npset) {
		if ((pp = pfp->pf_psetp)) {
		    if (strncmp (pp->p_name, tn, nchars) == 0) {
			if (strlen (pp->p_name) == nchars)
			    return (paramfind (pfp, pn, pos, abbrev));
			else if (m_pp)
			    return ((struct param *)ERR);
			else {
			    m_pp = pp;
			    m_pfp = pfp;
			}
		    }
		}
	    }

	    /* Unique abbreviation for pset was given. */
	    if (m_pp)
		return (paramfind (m_pfp, pn, pos, abbrev));
	    else
		return (NULL);

	} else {
	    /* Search all psets. */
	    for (;  pfp;  pfp = pfp->pf_npset)
		if ((pp = paramfind (pfp, pn, pos, abbrev)) != NULL)
		    return (pp);
	    return (NULL);
	}
}


/* PSETRELOAD -- Called when a pset parameter is assigned into by a command
 * line argument.  The previous value of the pset param will already have
 * been used by callnewtask() to load a pset.  We must replace the old pset
 * by the new one.
 */
void
psetreload (
    struct pfile *main_pfp,		/* main task pfile	*/
    struct param *psetp			/* pset param		*/
)
{
	struct	pfile *o_pfp, *n_pfp, *prev_pfp;
	struct	pfile *next_pfp = NULL;

	if (cldebug)
	    eprintf ("psetreload, pset %s\n", psetp->p_name);

	/* Locate the old pfile in the list of psets off the main task pfile.
	 */
	prev_pfp = main_pfp;
	for (o_pfp=prev_pfp->pf_npset;  o_pfp;  o_pfp = o_pfp->pf_npset)
	    if (o_pfp->pf_psetp == psetp)
		break;
	    else
		prev_pfp = o_pfp;
	
	if (o_pfp == NULL)
	    cl_error (E_IERR, "in psetreload: cannot find npset");
	else
	    next_pfp = o_pfp->pf_npset;

	/* Unlink the old pfile and its copy.  This must be done before loading
	 * the new pfile, else pfilesrch will simply reference the old pfile.
	 */
	pfileunlink (o_pfp->pf_oldpfp);
	pfileunlink (o_pfp);

	/* Load the new pfile.  */
	n_pfp = pfilecopy (pfilesrch (psetp->p_name));

	/* Link it into the pset list */
	prev_pfp->pf_npset = n_pfp;
	n_pfp->pf_npset = next_pfp;
	n_pfp->pf_psetp = o_pfp->pf_psetp;
}


/* IOFINISH -- Flush out and wrap up all pending io for given task.
 *   Called when the task is dying and it wants to close all files it opened.
 *   This includes a pipe if it used one, a file if it was a script and io
 *   redirections as indicated by the T_MYXXX flags.  The T_MYXXX flags are
 *   set only when the redirections were done for this task, ie, they were
 *   not simply inherited.
 * Just as a fail-safe measure, always check that a real stdio file is
 *   not being closed.
 * Don't call error() because in trying to restor to an interactive task
 *   it might call us again and cause an inf. loop.
 */
void
iofinish (register struct task *tp)
{
	register FILE *fp;
	int	flags;

	flags = tp->t_flags;

	/* Make sure we do not close files more than once.
	 */
	if (flags & T_RUNNING)
	    tp->t_flags &= ~T_RUNNING;
	else
	    return;

	if (cldebug)
	    eprintf ("flushing io for task `%s'\n", tp->t_ltp->lt_lname);

	if (flags & T_MYIN) {
	    fp = tp->t_stdin;
	    if (fp != stdin)
		fclose (fp);
	}
	if (flags & T_MYOUT) {
	    fflush (fp = tp->t_stdout);
	    if (fp != stdout)
		fclose (fp);
	}
	if (flags & T_MYERR) {
	    fflush (fp = tp->t_stderr);
	    if (fp != stderr)
		fclose (fp);
	}

	/* Close any redirected graphics output streams.
	 */
	if (flags & (T_MYSTDGRAPH|T_MYSTDIMAGE|T_MYSTDPLOT)) {
	    if (flags & T_MYSTDGRAPH)
		if (tp->t_stdgraph != tp->t_stdimage &&
		    tp->t_stdgraph != tp->t_stdplot)
		    fclose (tp->t_stdgraph);
	    if (flags & T_MYSTDIMAGE)
		if (tp->t_stdimage != tp->t_stdplot)
		    fclose (tp->t_stdimage);
	    if (flags & T_MYSTDPLOT)
		fclose (tp->t_stdplot);
	}

	/* If task i/o is redirected to a subprocess send the done message.
	 */
	if (flags & T_IPCIO)
	    fputs (IPCDONEMSG, tp->t_out);
	fflush (tp->t_out);

	/* Close files only for script task, not for a cl, a builtin, or
	 * a process.  Do call disconnect if the task lives in a process.
	 */
	if (flags & T_SCRIPT) {
	    fp = tp->t_in;
	    if (fp != stdin)
		fclose (fp);
	} else if (flags & (T_CL|T_BUILTIN)) {
	    ;
	} else if (tp->t_pid != -1)
	    pr_disconnect (tp->t_pid);

	/* Log a stop message for script and executable tasks.
	 */
	if (keeplog() && log_trace())
	    if (tp->t_flags & T_SCRIPT || tp->t_pid != -1)
	    	putlog (tp, "Stop");
}


/* RESTOR -- Restor all global variables for the given task and insure the
 *   integrity of the dictionary and control stack.
 * Go through the dictionary and properly disgard any packages, ltasks,
 *   pfiles, environments and params that may be above the new topd.
 * Write out any pfiles that are not just working copies that have been
 *   updated before discarding them.
 * Don't call error() because in trying to restor to an interactive task
 *   it might call us again and cause an inf. loop.  Instead, issue fatal error
 *   which will kill the cl for good.  This seems reasonable since we might
 *   as well die if we can't  restor back to an interactive state.
 * N.B. we assume that a pfile's params will either all lie above or all
 *   below tp->t_topd.  If this can ever happen, must add a further check
 *   of each pfile below topd and lob off any params above topd.
 *   The way posargset, et al, and call/execnewtask are now, we are safe.
 */
void
restor (struct task *tp)
{
	memel *topdp;
	register struct ltask *ltp;
	register struct package *pkp;
	register struct param *pp;
	register struct pfile *pfp;
	struct	param *last_pp;
	int	n;

	if (cldebug) {
	    eprintf ("restoring task `%s', tp: %d\n", tp->t_ltp->lt_lname,tp);
	    eprintf ("  topd %d/%d\n", topd, tp->t_topd);
	}

	topd = tp->t_topd;
	pc = tp->t_pc;
	topos = tp->t_topos;
	basos = tp->t_basos;
	topcs = tp->t_topcs;
	curpack = tp->t_curpack;

	yyin = tp->t_in;
	parse_state = PARSE_FREE;

	topdp = daddr (topd);

	/* Set pachead to first package below new topd.  Then lob off any ltasks
	 *   all remaining packages might have above topd.  It is sufficient to 
	 *   stop the ltask checks for a given package once find an ltask
	 *   below topd since the dictionary always grows upward.
 	 *   (Recall that since new ltasks are always added at the top of the
	 *   dictionary, and pkp->pk_ltp always points to the most recently
	 *   added ltask, then the thread moves to lower and lower addrs.)
	 *   Thus, work downward and throw out all ltasks until find one below
	 *   the new topd.
	 */
	for (pkp = reference (package, pachead);  pkp;  pkp = pkp->pk_npk)
	    if ((memel)pkp < (memel)topdp) {
		pachead = dereference (pkp);
		break;
	    }
	if (pkp == NULL)
	    cl_error (E_FERR, "package list broken");

	for (;  pkp;  pkp = pkp->pk_npk) {
	    for (ltp = pkp->pk_ltp; ltp; ltp = ltp->lt_nlt)
		if ((memel)ltp < (memel)topdp) {
		    pkp->pk_ltp = ltp;
		    break;
		}
	    if ((memel)pkp->pk_ltp >= (memel)topdp)
		/* All ltasks in this package were above topd */
		pkp->pk_ltp = NULL;
	}

	/* Similarly for pfiles and their params; however, since new params
	 *   are always added at the top of the dictionary and linked in at the
	 *   END of the list (at pfp->pf_lastpp), the thread off pfp->pf_pp
	 *   moves to higher and higher addrs.  Thus, we work our way up and
	 *   throw out all params above the new topd.  Also, close off any open
	 *   list files from discarded params along the way, if any.
	 * Also, see if any of the params were P_SET and set PF_UPDATE.
	 *   This avoids having to set PF_UPDATE for each assignment when the
	 *   is not always easily found.
	 * N.B. hope mode param that some t_modep is using is never disgarded..
	 * Also, guard against writing out pfiles in background.
	 */
	for (pfp = reference (pfile, parhead);  pfp;  pfp = pfp->pf_npf) {
	    /* Lob off any pfiles above new topd.  Go through their
	     * params, updating if necessary and closing any lists.
	     */
	    if ((memel)pfp < (memel)topdp) {
		parhead = dereference (pfp);
		break;
	    }

	    for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
		/* Close if list file and enable flushing if P_SET.
		 */
		if (pp->p_type & PT_LIST)
		    closelist (pp);
		if (pp->p_flags & P_SET)
		    pfp->pf_flags |= PF_UPDATE;
	    }
	    if (((pfp->pf_flags & (PF_UPDATE|PF_COPY)) == PF_UPDATE) &&
		!(firstask->t_flags & T_BATCH))
		    pfileupdate (pfp);
	}

	/* Discard any recently added parameters above topd, where the pfile
	 * itself is below topd.  This happens when a new parameter is added
	 * to an existing incore pfile, e.g., in a declaration.
	 */
	for (;  pfp;  pfp = pfp->pf_npf) {
	    if ((memel)(pfp->pf_lastpp) < (memel)topdp)
		continue;		/* quick check */
	    last_pp = NULL;
	    n = 0;

	    for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
		if ((memel)pp >= (memel)topdp) {
		    if (cldebug)
			fprintf (stderr, "chop pfile for task %s at param %s\n",
			    pfp->pf_ltp->lt_lname, last_pp->p_name);
		    if (last_pp)
			last_pp->p_np = NULL;
		    pfp->pf_lastpp = last_pp;
		    pfp->pf_n = n;
		    break;
		} else {
		    last_pp = pp;
		    n++;
		}
	    }
	}

	/* Delete any SET environment statements processed since this task
	 * was spawned.  If any redefs are uncovered the original values are
	 * reset in all connected subprocesses.
	 */
	if (tp->t_envp)
	    c_prenvfree (0, tp->t_envp);

	/* If the task being restored defined a package, dump all processes
	 * in the process cache spawned since the package was loaded.
	 */
	if (tp->t_pno)
	    pr_prunecache (tp->t_pno);
}


/* ONEOF -- "on eof" (not "one of"):
 * The current task has issued eof, either directly or via the "bye" command.
 *   Flush out all pending io, copy working pfile back to original if have one,
 *   pop a state back to the previous state and restore its environment.
 *   Avoid calling effecmode() if called from a builtin task since builtins
 *   do not have the "mode" parameter.
 *
 * If currentask is the first cl or we are batch, then we are truely done.
 *   Return true to the caller (EXECUTE), causing a return to the main.
 */
void
oneof (void)
{
	register struct pfile *pfp;
	register struct package *pkp;
	register struct param *pp;
	static	int	nerrs = 0;
	int	flags;
	extern  ErrCom	errcom;
	extern  int 	err_clear;

	if (cldebug)
	    eprintf ("received `%s' from `%s'\n", yeof ? "eof" : "bye",
		currentask == firstask ? "root" : currentask->t_ltp->lt_lname);

	if (!(firstask->t_flags & T_BATCH))
	    if (currentask == firstask && !gologout && !loggingout &&
		isatty (fileno (stdin)) && nerrs++ < 8)
		cl_error (E_UERR, "use `logout' to log out of the CL");

	flags = currentask->t_flags;

	/* Clear the error struct to indicate a successful run().  If 
	 * the err_clear flag is not set ....
	 */
	if (err_clear) {
	    if ((do_error == YES || (do_error == NO && !errcom.errflag))) {
		errcom.errcode = OK;
		strcpy (errcom.errmsg, "");
		strcpy (errcom.task, currentask->t_ltp->lt_lname);
		/*strcpy (errcom.task, "");*/

        	pp = paramfind (firstask->t_pfp, "$errno", 0, YES);
        	pp->p_val.v_i = errcom.errcode;
        	pp = paramfind (firstask->t_pfp, "$errmsg", 0, YES);
        	pp->p_val.v_s = errcom.errmsg;
        	pp = paramfind (firstask->t_pfp, "$errtask", 0, YES);
        	pp->p_val.v_s = errcom.task;
	    }
	}


	if (!(flags & (T_BUILTIN|T_CL|T_SCRIPT|T_BATCH)))
	    fflush (currentask->t_out);
	iofinish (currentask);

	/* Copy back the main pfile and any pset-param files.  If the task
	 * which has terminated is a package script task, fix up the pfile
	 * pointer in the package descriptor to point to the updated pset.
	 */
	if (currentask->t_ltp->lt_flags & LT_PFILE) {
	    pfcopyback (pfp = currentask->t_pfp);
	    if (currentask->t_ltp->lt_flags & LT_DEFPCK)
		if ( (pkp = pacfind(currentask->t_ltp->lt_lname)) )
		    if (pkp->pk_pfp == pfp)
			pkp->pk_pfp = pfp->pf_oldpfp;
	    for (pfp = pfp->pf_npset;  pfp != NULL;  pfp = pfp->pf_npset)
		pfcopyback (pfp);
	}

	if (currentask == firstask)
	    alldone = 1;
	else {
	    currentask = poptask();
	    if (currentask->t_flags & T_BATCH)
		alldone = 1;
	}

	restor (currentask);		/* restore environment 		*/
}


/* PRINTCALL -- Print the calling sequence for a task.  Called by killtask()
 * to print stack trace.
 */
void
printcall (FILE *fp, struct task *tp)
{
	register struct param *pp;
	int	 notfirst = 0;
	extern   int unwind_level;


	/*  See whether we print a stack trace at all, and if so, whether
	 *  we want the full trace. 
	 */
	if (err_trace == NO || (err_full == NO && unwind_level > 0))
	    return;

	/* Print the offending line.
	 */
 	print_call_line (fp, tp->t_callln, tp->t_ltp->lt_pname,
	    tp->t_ltp->lt_flags);

	fprintf (fp, "     called as: `%s (", tp->t_ltp->lt_lname);
	for (pp = tp->t_pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
	    if (pp->p_flags & P_CLSET) {
		if (notfirst)
		    fprintf (fp, ", ");
		notfirst++;
		if (!(tp->t_pfp->pf_flags & PF_FAKE) && !(pp->p_mode & M_FAKE))
		    fprintf (fp, "%s=", pp->p_name);

		/* Use only low level routines to print the parameter value to
		 * avoid error recursion.  In particular, parameter indirection
		 * is not resolved.
		 */
		if (!(pp->p_valo.o_type & OT_UNDEF))
		    fprop (fp, &pp->p_valo);
		else
		    fprintf (fp, "UNDEF");
	    }
	}

	fprintf (fp, ")'\n");
}


/* PRINT_CALL_LINE -- Print the line of the file being called.
 */
void 
print_call_line (FILE *out, int line, char *fname, int flags)
{
    	register int i, len;
	register FILE *fp;
	char 	buf[SZ_LINE+1];
	extern  int unwind_level;


	len = strlen (fname);
	if (strncmp (".cl", &fname[len-3], 3) == 0) {

	    fp = fopen (fname, "r");		/* open the script 	*/
	    for (i=0; i < line; i++) {		/* skip to line		*/
	        if (fgets (buf, SZ_LINE, fp) == (char *)NULL) {
	    	    fclose (fp);
		    return;
		}
	    }

	    /* Skip leading whitespace and string trailing newline. */
	    len = strlen (buf);
	    buf[len-1] = '\0';
	    for (i=0; buf[i] == ' ' || buf[i] == '\t'; i++) 
	        ;

	    fprintf (out, "  \"%.68s", &buf[i]);
	    if (strlen (&buf[i]) > 68)
	        fprintf (out, " ...");

	    if (flags & LT_INVIS)
	        fprintf (out, "\"\n     line %d: %s (hidden task)\n",
		    line, fname);
	    else
	        fprintf (out, "\"\n     line %d: %s\n", line, fname);

	    fclose (fp);
	}

	unwind_level++;
}


/* KILLTASK -- Abort the currently executing task.  Only call this when a task
 *  is to be killed spontaneously, as from interrupt, not when it is just dying
 *   due to a "bye" or eof.
 * Close all pipes and pseudofiles, being careful not to close any that
 *   are real stdio files.
 * Note that our function is to kill an external task, not the process in which
 *   it resides.  The process is left running in the cache in case it is needed
 *   again.
 */
void
killtask (register struct task *tp)
{
	char	buf[128];

	/* Print stack trace, with arguments.
	if (!(tp->t_ltp->lt_flags&LT_INVIS) && !(firstask->t_flags&T_BATCH) &&
	    !(strcmp (tp->t_ltp->lt_lname, "error") == 0))
	 */
	if (!(firstask->t_flags&T_BATCH))
	    printcall (currentask->t_stderr, tp);

	/* If task is running in a subprocess, interrupt it and read the ERROR
	 * message.  Not certain there isn't some case where this could cause
	 * deadlock, but it does not seem so.  Interrupts are disabled during
	 * process startup.  If task issues ERROR then it is popped before
	 * we are called, without issuing the signal.
	 */
	if (tp->t_pid != -1) {
	    fflush (tp->t_out);
	    c_prsignal (tp->t_pid, X_INT);
	    fgets (buf, 128, tp->t_in);
	}

	iofinish (tp);
}
