/* 
 * txUnix.c --
 *
 *	This file contains all of the code for Tx that is different
 *	between Sprite and UNIX, such as code to glue a Tx to a
 *	pseudo-tty.
 *
 * Copyright 1989 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/mx/RCS/txUnix.unix,v 1.8 90/04/17 15:57:50 ouster Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#define Time SpriteTime
#include <bstring.h>
#include <sgtty.h>
#include <errno.h>
#include <fs.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <tcl.h>
#undef Time
#include "mx.h"
#include "mxInt.h"

#include <alloca.h>

/*
 * Linked data structure used to hold data that is waiting to be
 * sent to the application.  This is needed when very large chunks
 * of data are being sent to the application.  Otherwise, there could
 * be a deadlock over space in the pty:  Tx waits for the application
 * to process data in the pty, but the application is waiting for
 * Tx to process its output data.
 */

#define BYTES_PER_CHUNK 20
typedef struct inputChunk {
    char bytes[BYTES_PER_CHUNK];	/* Bytes to send to application. */
    int first, count;			/* Range of bytes still waiting to be
					 * sent. */
    struct inputChunk *nextPtr;		/* Next chunk in list, or NULL for
					 * last chunk. */
} InputChunk;

/*
 * Structures of the following form are created for each pseudo-tty.
 * These are passed to the window-manager and Fs_Dispatch as ClientData
 * and returned here in callbacks.
 */

typedef struct Typescript {
    int ptyID;				/* Descriptor number for our end
					 * of pty (master).  -1 means the
					 * pty is no longer open. */
    char ptyName[30];			/* Name of pty device. */
    InputChunk *firstChunk;		/* First of list of chunks waiting
					 * to be sent to application, or NULL
					 * if nothing waiting.  If non-NULL,
					 * the we're waiting for ptyID to
					 * become writable;  otherwise we don't
					 * care whether or not ptyID is
					 * writable. */
    InputChunk *lastChunk;		/* Last chunk waiting to be sent. */
    MxFileInfo *fileInfoPtr;		/* Structure used to manage display-
					 * related info for typescript. */
    char *command;			/* Used to buffer a command being
					 * received over the terminal output
					 * stream (command could arrive in
					 * pieces, but can't be processed until
					 * it's all here). */
    int commandUsed;			/* Actual number of bytes of command
					 * that are in use presently.  -1 means
					 * no partial command is buffered. */
    int commandSize;			/* Total number of bytes currently
					 * allocated to command buffer;  this
					 * will grow as needed. */
    int pid;				/* The top-level application process.
					 * 0 means there's no application
					 * associated with this typescript. */
    struct Typescript *nextPtr;		/* Next in list of all typescripts,
					 * or NULL for end of list. */
} Typescript;

static Typescript *typescripts = NULL;	/* List of all existing typescripts. */

/*
 * The following global variable is provided so that programs like Tx can
 * tell when there are no longer any Tx windows open.
 */

int tx_TypescriptCount = 0;

/*
 * The following typedefs are exit return codes used to indicate
 * problems that occurred after the application process was forked but
 * before it could exec the application.
 */

#define ERR_RESET_CTY	100
#define ERR_APPL_END	101
#define ERR_SET_FAMILY	102
#define ERR_EXEC	103

/*
 * Termcap information.  Generate a valid termcap entry with something
 * like sprintf(string, tcFormat, cols, lines, tc1, tc2, ...),
 * where lines and cols are screen dimensions.
 */

char *tcFormat = "tx:co#%d:li#%d:%s%s%s%s%s%s%s%s";
char *tc1 = "al=\\Evi in\\n:bs:cd=\\Evi cd\\n:ce=\\Evi ce\\n:";
char *tc2 = "cl=\\Evi cl;selection clear\\n:cm=\\Evi cu %d %d\\n:";
char *tc3 = "dl=\\Evi de\\n:do=^J:ho=\\Evi cu 0 0\\n:pt:";
char *tc4 = "rs=\\Evi le\\n:ta=\\Evi ta\\n:te=\\Evi le;set ti 0\\n:";
char *tc5 = "ti=\\Evi en;set ti 1\\n:up=\\Evi up\\n:";
char *tc6 = "ve=\\Eif [set ti]0 {} else {vi le}\\n:vs=\\Evi en\\n:";
char *tc7 = "xt:nd=\\Ecaret [mark caret forward 1 char]\\n:";
char *tc8 = "";

/*
 * Forward declarations to procedures defined later in this file:
 */

static int	TxChildDeathProc();
static void	TxCloseProc();
static void	TxInputProc();
static void	TxPtyProc();
static void	TxSizeChangeProc();

/*
 *----------------------------------------------------------------------
 *
 * Tx_SetupAndFork --
 *
 *	This is a top-level procedure that makes a window into a Tx
 *	window, associates a terminal-like structure with that
 *	window, and starts up a top-level application process in
 *	the window.  The code in this procedure is UNIX-specific.
 *
 * Results:
 *	The return result is a standard Tcl error code (usually TCL_OK).
 *	Interp->result is modified to hold an error message if an error
 *	occurs (interp->result should already have been initialized to
 *	empty by the caller).
 *
 * Side effects:
 *	A pty is created, a process is forked (if requested) and window
 *	is turned into a Tx window.  Also, this procedure creates a
 *	handler for SIGCHLD signals:  the caller better not have its own
 *	uses for this.
 *
 *----------------------------------------------------------------------
 */

int
Tx_SetupAndFork(interp, display, window, infoPtr, args)
    Tcl_Interp *interp;		/* Used to return error information
				 * in interp->result. */
    Display *display;		/* Display containing window to Tx-ify. */
    Window window;		/* Make this into a Tx window.  Must already
				 * exist on display. */
    Tx_WindowInfo *infoPtr;	/* Information about window;  passed through
				 * to Tx_Make. */
    char **args;		/* Command to execute.  If NULL, then
				 * a shell is created by default. */
{
    register Typescript *tsPtr;
    int letter, digit, result;
    Tx_WindowInfo newInfo;
    MxWindow *mxwPtr;
    int pid;
    static int exitHandlerCreated = 0;

    if (!exitHandlerCreated) {
	exitHandlerCreated = 1;
	signal(SIGCHLD, TxChildDeathProc);
    }

    /*
     * Locate an unused pty.
     */

    tsPtr = (Typescript *) malloc(sizeof(Typescript));
    bzero((char *) tsPtr, sizeof(Typescript));
    tsPtr->nextPtr = typescripts;
    typescripts = tsPtr;

    for (letter = 0; letter < 11; letter++) {
	for (digit = 0; digit < 16; digit++) {
	    sprintf(tsPtr->ptyName, "/dev/pty%c%c", "pqrstuvwxyz"[letter],
		    "0123456789abcdef"[digit]);
	    tsPtr->ptyID = open(tsPtr->ptyName, O_RDWR, 0);
	    if (tsPtr->ptyID >= 0) {
		goto gotPty;
	    }
	}
    }
    gotPty:
    if (tsPtr->ptyID < 0) {
	sprintf(interp->result, "Tx couldn't open a pty for %s",
		"communication with application.\n");
	result = TCL_ERROR;
	goto error;
    }
    Fs_EventHandlerCreate(tsPtr->ptyID, FS_READABLE, TxPtyProc,
	    (ClientData) tsPtr);
    tsPtr->ptyName[5] = 't';
    (void) TxUtmpEntry(1, tsPtr->ptyName, DisplayString(display));
    if (fcntl(tsPtr->ptyID, F_SETFL, FNDELAY) == -1) {
	sprintf(interp->result,
		"Tx couldn't use asynchronous I/O on pty: %s\n",
		strerror(errno));
	result = TCL_ERROR;
	goto error;
    }

    newInfo = *infoPtr;
    if (newInfo.title == NULL) {
	newInfo.title = tsPtr->ptyName;
    }
    result = Tx_Make(interp, display, window, &newInfo, TxInputProc,
	    TxSizeChangeProc, TxCloseProc, (ClientData) tsPtr);
    if (result != TCL_OK) {
	goto error;
    }
    mxwPtr = TxGetMxWindow(display, window);
    tsPtr->fileInfoPtr = mxwPtr->fileInfoPtr;
    tsPtr->command = NULL;
    tsPtr->commandUsed = -1;
    tsPtr->commandSize = 0;
    XStoreName(display, window, newInfo.title);

    if ((args == NULL) || (args[0] == NULL)) {
	static char *defaultArgs[] = {NULL, "-i", NULL};
	char *p;

	args = defaultArgs;
	p = getenv("SHELL");
	if (p != NULL) {
	    defaultArgs[0] = (char *) alloca((unsigned) (strlen(p) + 1));
	    strcpy(defaultArgs[0], p);
	} else {
	    defaultArgs[0] = "csh";
	}
    }
    pid = fork();
    if (pid == 0) {
	int	ttyID, i;
	char	tc[400];
	static struct sgttyb sgttyb = {
	    0, 0, 0177, CKILL, EVENP|ODDP|ECHO|XTABS|CRMOD
	};
	static struct tchars tchars = {
	    CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK
	};
	static struct ltchars ltchars = {
	    CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT
	};
	static int discipline = NTTYDISC;
	static unsigned lmode = LCRTBS|LCRTERA|LCRTKIL|LCTLECH;

	/*
	 * Set up the environment for the application.
	 */

	setenv("TTY", tsPtr->ptyName);
	setenv("TERM", "tx");
	sprintf(tc, tcFormat,
		mxwPtr->width/infoPtr->fontPtr->max_bounds.width,
		mxwPtr->heightLines, tc1, tc2, tc3, tc4, tc5, tc6, tc7, tc8);
	setenv("TERMCAP", tc);
	sprintf(tc, "%d", window);
	setenv("WINDOWID", tc);
	setenv("DISPLAY", DisplayString(display));

	/*
	 * Cancel the current choice of controlling tty, so that when
	 * the pty is opened below it will the controlling tty.  Also
	 * save the current terminal's state, and re-use it for the
	 * pty (with slight modifications so that editors will run more
	 * quickly, etc.).
	 */

	ttyID = open("/dev/tty", O_RDWR, 0);
	if (ttyID >= 0) {
	    if ((ioctl(ttyID, (int) TIOCGETP, (char *) &sgttyb) == -1)
		    || (ioctl(ttyID, (int) TIOCGETC, (char *) &tchars) == -1)
		    || (ioctl(ttyID, (int) TIOCGETD, (char *) &discipline)
			    == -1)
		    || (ioctl(ttyID, (int) TIOCGLTC, (char *) &ltchars)
			    == -1)) {
		ttyError:
		_exit(ERR_RESET_CTY);
	    }
	    if (ioctl(ttyID, (int) TIOCNOTTY, (char *) 0) == -1) {
		goto ttyError;
	    }
	    close(ttyID);
	}
	
	ttyID = open(tsPtr->ptyName, O_RDWR, 0);
	if (ttyID < 0) {
	    _exit(ERR_APPL_END);
	}
	sgttyb.sg_flags &= ~(ALLDELAY | XTABS | CBREAK | RAW);
	sgttyb.sg_flags |= ECHO | CRMOD;
	sgttyb.sg_ispeed = B9600;
	sgttyb.sg_ospeed = B9600;
	if ((ioctl(ttyID, (int) TIOCSETP, (char *) &sgttyb) == -1)
		|| (ioctl(ttyID, (int) TIOCSETC, (char *) &tchars) == -1)
		|| (ioctl(ttyID, (int) TIOCSETD, (char *) &discipline) == -1)
		|| (ioctl(ttyID, (int) TIOCSLTC, (char *) &ltchars) == -1)
		|| (ioctl(ttyID, (int) TIOCLSET, (char *) &lmode) == -1)) {
	    goto ttyError;
	}

	pid = getpid();
	if (setpgrp(0, pid) == -1) {
	    _exit(ERR_SET_FAMILY);
	}

	/*
	 * Need to ignore SIGTTOU signals here.  Otherwise if there's
	 * some program out there that still thinks it's using the pty
	 * (e.g. an Mx run from a previous Tx using the pty), this process
	 * will be signalled.
	 */

	signal(SIGTTOU, SIG_IGN);
	if (ioctl(ttyID, TIOCSPGRP, (char *) &pid) != 0) {
	    _exit(ERR_SET_FAMILY);
	}
	signal(SIGTTOU, SIG_DFL);
	dup2(ttyID, 0);
	dup2(ttyID, 1);
	dup2(ttyID, 2);
	for (i = 3; i <= ttyID; i++) {
	    close(i);
	}

	/*
	 * Run the application.
	 */

	execvp(args[0], args);
	_exit(ERR_EXEC);
    } else if (pid == -1) {
	sprintf(interp->result,  "Tx couldn't fork application: %.100s\n",
		strerror(errno));
	result = TCL_ERROR;
	goto error;
    }
    tsPtr->pid = pid;
    tx_TypescriptCount += 1;
    return TCL_OK;

    error:
    TxCloseProc(tsPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TxCloseProc --
 *
 *	This procedure is invoked by the window portion of Tx when
 *	the last window on a typescript goes away.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	All of our data structures are cleaned up.  The application
 *	process is deleted if it still exists, and the pseudo-device
 *	stuff is all deleted.
 *
 *----------------------------------------------------------------------
 */

static void
TxCloseProc(tsPtr)
    register Typescript *tsPtr;		/* Pointer to our information about
					 * this typescript. */
{
    register Typescript *prevPtr;

    if (typescripts == tsPtr) {
	typescripts = tsPtr->nextPtr;
    } else {
	for (prevPtr = typescripts; prevPtr != NULL;
		prevPtr = prevPtr->nextPtr) {
	    if (prevPtr->nextPtr == tsPtr) {
		prevPtr->nextPtr = tsPtr->nextPtr;
		break;
	    }
	}
    }
    tx_TypescriptCount -= 1;
    if (tsPtr->ptyID >= 0) {
	Fs_EventHandlerDestroy(tsPtr->ptyID);
	close(tsPtr->ptyID);
    }
    if (tsPtr->command != NULL) {
	free(tsPtr->command);
    }
    if (tsPtr->pid != 0) {
	(void) kill(tsPtr->pid, SIGHUP);
    }
    free((char *) tsPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TxInputProc --
 *
 *	This procedure is called by the Tx window widget when input
 *	characters are ready to be passed to the terminal.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Pass off the characters in string to the pty, or buffer them
 *	if the pty is full.
 *
 *----------------------------------------------------------------------
 */

static void
TxInputProc(tsPtr, string)
    register Typescript *tsPtr;		/* Information about typescript
					 * (passed as clientData). */
    char *string;			/* NULL-terminated string of input
					 * characters from window. */
{
    register InputChunk *chunk;
    register char *p;
    int space, limit;

    if (tsPtr->ptyID < 0) {
	return;
    }
    if (tsPtr->firstChunk == NULL) {
	Fs_EventHandlerDestroy(tsPtr->ptyID);
	Fs_EventHandlerCreate(tsPtr->ptyID, FS_READABLE|FS_WRITABLE,
		TxPtyProc, (ClientData) tsPtr);
    }
    chunk = tsPtr->lastChunk;
    while (1) {
	if (chunk == NULL) {
	    space = 0;
	} else {
	    space = BYTES_PER_CHUNK - (chunk->first + chunk->count);
	}
	if (space == 0) {
	    tsPtr->lastChunk = (InputChunk *) malloc(sizeof(InputChunk));
	    if (tsPtr->firstChunk == NULL) {
		tsPtr->firstChunk = tsPtr->lastChunk;
	    } else {
		chunk->nextPtr = tsPtr->lastChunk;
	    }
	    chunk = tsPtr->lastChunk;
	    chunk->first = 0;
	    chunk->count = 0;
	    chunk->nextPtr = 0;
	}
	limit = BYTES_PER_CHUNK - chunk->first;
	for (p = &chunk->bytes[chunk->first + chunk->count];
		(chunk->count < limit);
		string++, p++, chunk->count++) {
	    if (*string == 0) {
		return;
	    }
	    *p = *string;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxPtyProc --
 *
 *	This procedure is invoked by Fs_Dispatch whenever the pty for
 *	a Tx window is readable or writable.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Characters get shuffled.
 *
 *----------------------------------------------------------------------
 */

static void
TxPtyProc(tsPtr, streamID, eventMask)
    Typescript *tsPtr;			/* Information about typescript
					 * (passed as clientData). */
    int streamID;			/* Id for pty file. */
    int eventMask;			/* Combination of FS_READABLE and
					 * FS_WRITABLE, indicating whether
					 * pty is currently readable or
					 * writable. */
{
    /*
     * Handle buffered output to pty.
     */

    if (eventMask & FS_WRITABLE) {
	register InputChunk *chunkPtr;
    
	for (chunkPtr = tsPtr->firstChunk; chunkPtr != NULL; ) {
	    int count;
    
	    count = write(tsPtr->ptyID, &chunkPtr->bytes[chunkPtr->first],
		    chunkPtr->count);
	    if (count == -1) {
		break;
	    }
	    chunkPtr->first += count;
	    chunkPtr->count -= count;
	    if (chunkPtr->count == 0) {
		free((char *) chunkPtr);
		tsPtr->firstChunk = chunkPtr = chunkPtr->nextPtr;
	    }
	}
	if (tsPtr->firstChunk == NULL) {
	    tsPtr->lastChunk = NULL;
	    Fs_EventHandlerDestroy(tsPtr->ptyID);
	    Fs_EventHandlerCreate(tsPtr->ptyID, FS_READABLE, TxPtyProc,
		    (ClientData) tsPtr);
	}
    }

    /*
     * Handle output from the application to the screen.  The tricky
     * thing here is to handle commands embedded in the output stream.
     */

    if (eventMask & FS_READABLE) {
#define OUT_BUFFER_SIZE 2000
	register char *p;
	char *start;
	char buffer[OUT_BUFFER_SIZE+1];
	int count;
    
	count = read(tsPtr->ptyID, buffer, OUT_BUFFER_SIZE);
	if (count <= 0) {

	    /*
	     * Ultrix 3.1 sends EIO if the application closed
	     * the pty.  It also sends EWOULDBLOCK sometimes
	     * if the user types ^S.  This is harmless and should
	     * be ignored.
	     *
	     * If the application closed the pty, issue a message
	     * but leave the window around until the application
	     * exits.
	     */

	    if ((count < 0) && (errno == EWOULDBLOCK)) {
		count = 0;
	    } else {
		Fs_EventHandlerDestroy(tsPtr->ptyID);
		close(tsPtr->ptyID);
		tsPtr->ptyID = -1;
		Tx_Output(tsPtr->fileInfoPtr,
			"\r\n\nApplication closed pty.\r\n");
		Tx_Output(tsPtr->fileInfoPtr,
			"This window is no longer usable.");
		return;
	    }
	}
	buffer[count] = 0;

	/*
	 * Process the new output characters in chunks.  Each chunk is
	 * either a) normal output characters, or b) a Tx command, which
	 * is the characters between ESC and the next newline.  Also,
	 * throw away null characters if they appear in the output.
	 */

	for (p = buffer; *p != 0; ) {
    
	    /*
	     * Find the rest of the current command, if there's a partially-
	     * complete command.
	     */
    
	    if (tsPtr->commandUsed >= 0) {
		while (*p != 0) {
		    MxWindow *mxwPtr;

		    /*
		     * Grow the command buffer if it has overflowed.
		     */

		    if (tsPtr->commandUsed >= tsPtr->commandSize) {
			char *newBuffer;
			int newSize;

			newSize = 2*tsPtr->commandSize;
			if (newSize < 20) {
			    newSize = 20;
			}
			newBuffer = (char *) malloc((unsigned) newSize);
			bcopy(tsPtr->command, newBuffer, tsPtr->commandUsed);
			if (tsPtr->command != NULL) {
			    free(tsPtr->command);
			}
			tsPtr->command = newBuffer;
			tsPtr->commandSize = newSize;
		    }

		    if (*p == '\n') {
			tsPtr->command[tsPtr->commandUsed] = 0;
			mxwPtr = tsPtr->fileInfoPtr->mxwPtr;
			(void) Tx_Command(mxwPtr->display, mxwPtr->w,
				tsPtr->command);
			tsPtr->commandUsed = -1;
			p++;
			break;
		    }
		    tsPtr->command[tsPtr->commandUsed] = *p;
		    tsPtr->commandUsed++;
		    p++;
		}
	    }
    
	    /*
	     * Grab up a chunk of characters, and output them.
	     */
    
	    if (*p == 0) {
		break;
	    }
	    for (start = p; *p != 0; p++) {
		if ((*p == '\33')
			&& !(tsPtr->fileInfoPtr->flags & NO_ESCAPES)) {
		    tsPtr->commandUsed = 0;
		    *p = 0;
		    p++;
		    break;
		}
	    }
	    Tx_Output(tsPtr->fileInfoPtr, start);
	}
	if (count != OUT_BUFFER_SIZE) {
	    return;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TxSizeChangeProc --
 *
 *	This procedure is invoked whenever the size of the main window
 *	for a typescript changes size.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A TIOCSWINSZ IOControl is issued on the pty so that
 *	applications will see the new size.
 *
 *----------------------------------------------------------------------
 */

static void
TxSizeChangeProc(tsPtr, sizePtr)
    Typescript *tsPtr;			/* Information about typescript
					 * (passed as clientData). */
    struct winsize *sizePtr;		/* Pointer to new size information. */
{
    int outputSize, dummy1, dummy2;

    outputSize = 0;
    (void) ioctl(tsPtr->ptyID, TIOCSWINSZ, (char *) sizePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TxChildDeathProc --
 *
 *	This procedure is a signal handler that is invoked when (if)
 *	the child dies.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the child died naturally, then the typescript is closed.
 *	If the child terminated abnormally, a message is printed in
 *	the typescript and the typescript stays around, so the user
 *	can read the message.
 *
 *----------------------------------------------------------------------
 */
static int
TxChildDeathProc()
{
    int	pid;
    Typescript  *tsPtr;
    union wait	status;
    char	msg[100];
    static char *sigNames[] = {
	"undefined (signal 0)",
	"hangup",
	"interrupt",
	"debug",
	"illegal instruction",
	"trace trap",
	"IOT instruction",
	"EMT instruction",
	"floating-point exception",
	"kill",
	"migration",
	"segmentation violation",
	"undefined (signal 12)",
	"broken pipe",
	"alarm clock",
	"software termination",
	"urgent I/O condition",
	"suspended",
	"suspended (TSTP)",
	"continue",
	"child status changed",
	"suspended (tty input)",
	"suspended (tty ouput)",
	"SIGIO signal",
	"CPU time limit exceeded",
	"file size limit exceeded",
	"virtual time alarm",
	"profiling timer alarm",
	"undefined (signal 28)",
	"migrated home",
	"user-defined signal 1",
	"user-defined signal 2",
    };

    /*
     * Get pid of dead child and find the window associated with the
     * child.  If there's no longer a typescript around for this child,
     * then just return.
     */
    pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *) 0);
    if (pid == 0) {
	return 0;
    }
    for (tsPtr = typescripts; tsPtr != NULL; tsPtr = tsPtr->nextPtr) {
	if (tsPtr->pid == pid) {
	    tsPtr->pid = 0;
	    TxUtmpEntry(0, tsPtr->ptyName, "");
	    break;
	}
    }
    if (tsPtr == NULL) {
	return 0;
    }

    /*
     * If the child terminated normally, then destroy all the windows
     * on this typescript.  Otherwise keep the window around and display
     * an error message in it.
     */

    if (WIFEXITED(status)) {
	if (status.w_retcode == 0) {
	    MxWindow *mxwPtr;

	    for (mxwPtr = tsPtr->fileInfoPtr->mxwPtr; mxwPtr != NULL;
		    mxwPtr = mxwPtr->nextPtr) {
		mxwPtr->flags |= DESTROYED;
		XDestroyWindow(mxwPtr->display, mxwPtr->w);
		XFlush(mxwPtr->display);
	    }
	    return 0;
	} else {
	    if (status.w_retcode == ERR_RESET_CTY) {
		sprintf(msg, "Couldn't reset controlling terminal.");
	    } else if (status.w_retcode == ERR_APPL_END) {
		sprintf(msg, "Couldn't open application end of pty.");
	    } else if (status.w_retcode == ERR_SET_FAMILY) {
		sprintf(msg, "Couldn't set process family for terminal.");
	    } else if (status.w_retcode == ERR_EXEC) {
		sprintf(msg, "Couldn't execute application program.");
	    } else {
		sprintf(msg, "Application terminated with status %d.",
			status.w_retcode);
	    }
	}
    } else if (WIFSTOPPED(status)) {
	sprintf(msg, "Application stopped because of \"%s\" signal.",
		sigNames[status.w_stopsig & 037]);
    } else {
	sprintf(msg, "Application killed by \"%s\" signal.",
		sigNames[status.w_termsig & 037]);
    }
    Tx_Output(tsPtr->fileInfoPtr, "\r\n\n");
    Tx_Output(tsPtr->fileInfoPtr, msg);
    Tx_Output(tsPtr->fileInfoPtr, "\r\nThis window is no longer usable.");
    Tx_Update();
    Mx_Update();
    XFlush(tsPtr->fileInfoPtr->mxwPtr->display);
    return 0;
}
