From:	MERC::"uunet!theory.pppl.gov!karney" 28-APR-1992 20:38:44.18
To:	galaxy::gleeve
CC:	
Subj:	Re: Boss 4.1

I sent you the new boss.  Here is intermediate version 3.1
boss.c-31

/* BOSS interactive job controller.
Copyright (c) 1987, 1988 by Charles Karney.  All rights reserved.

Written by
     Charles Karney
     Plasma Physics Laboratory      E-mail:  Karney@Princeton.EDU
     Princeton University           Phone:   +1 609 243 2607
     Princeton, NJ 08543-0451       FAX:     +1 609 243 2160

BOSS 3.0 features added by Glenn Everhart (EVERHART@ARISIA.dnet.ge.com).

Based on the PHOTO program of Asbed Bedrossian (USC, asbed@oberon.usc.edu).

It utilizes the Pseudo TTY package of Dale Moore (CMU,
Dale.Moore@PS1.CS.CMU.EDU) and Kevin Carosso (Network Research Co.,
kvc@nrc.com, kvc@ymir.bitnet).

The UW terminal emulator was written by John Bruner (LLNL,
jbd@mordor.s1.gov).  He also defined the UW protocol.

DESCRIPTION:

BOSS lets you create up to 8 processes on a VAX/VMS system.  Each process
is identified by a single letter (A thru Z).  At most one of these
processes is `current'.  What you type is sent to that process, and output
from that process is sent to your terminal.  A process which is not current
can run but cannot do output.  (Output is saved until you make that process
current.)  You usually switch between processes by typing control-\
followed by the identifying letter.

You can run any program under BOSS.  For example, you might
    run Emacs or EVE                   in process E
    SET HOST to another machine        in process H
    do a FORTRAN compilation           in process F
    execute DCL commands               in process D
    talk to your colleague using PHONE in process P
and so on.

As an experimental addition, BOSS can also be run with the UW multiple-
window terminal emulator for the Macintosh or Meshugena-term on Amiga.
In this case you interact with the various processes through their own
windows.

INSTALLATION:

Compile and link with
    $ @boss_build
Install with
    $ @boss_install             ! This goes in the system startup file

In order to run BOSS, you will need pseudo TTYs installed on your system.
Contact me if you need a copy of this software. (The DECWindows drivers
in VMS 5.3 work just fine.)

BOSS can be operated without installing it with privileges (but you will
still need the pseudo TTYs installed).  If BOSS doesn't with PHY_IO
privilege, then it won't be able to set the terminal type of the processes
under BOSS (SHOW TERM will list the device type as UNKNOWN); the terminal
type can be set with SET TERM.  If BOSS doesn't have OPER privilege, then
it won't be able to rebroadcast broadcast messages that it receives;
instead these messages will be sent directly to the terminal.

REVISION HISTORY:

Version 1.0.  August 22, 1987
Version 1.1.  August 27, 1987
    Add BOSS$ID, BOSS$SWITCH, BOSS$STUFF.
Version 1.2.  September 30, 1987
    Stop PHY_IO being inherited by subprocesses.
Version 1.3.  March 17, 1988
  C-b buffer output
  C-p proceed (output comes through regardless)
  C-o suppress output (but save the last write)
  C-s hang output
Version 1.4.  March 23, 1988
  Make BOSS$SWITCH switching set output flag to s (for synchronism)
  Default output flag is b
Version 1.5.  April 5, 1988
  Fix exceeded quota problem that occurs when buflen > sysgen's maxbuf.
Version 1.6.  April 7, 1988
  Fix failure to detect failure of LIB$SPAWN (e.g., when over quota).
  Add C-t to make next process created as a top-level process.
Version 1.7.  June 5, 1988
  Use separate command C-\ C-n a to create a process.
  C-\ C-t a does this at top-level.
  Cleaned up input routine.
Version 1.8.  June 7, 1988
  Accept command line args:
    /COMMAND_CHARACTER=char - set control character
    /START_PROCESS=list - start up specified jobs
    /STUFF_STRING=list - and stuff their input buffers
    /OUTPUT_FLAGS=list - and set output flags accordingly
    /BEGIN_PROMPT=string - part of prompt appearing before id letter
    /END_PROMPT=string - part of prompt appearing after id letter
    /DEFAULT_OUTPUT_FLAG=char - set default output flag
    /SWITCH_CREATE - switching to nonexistent process creates it
Version 1.9.  June 8, 1988
  Trap broadcasts to BOSS and send them on to the current process.
Version 2.0.  June 9, 1988
  Add /DELETE_CHAR=char to specify a character to be interchanged with DEL.
Version 2.1.  June 10, 1988
  Support selected terminal types via BOSS$TERM logical name.
Version 2.2.  June 26, 1988
  Convert for new pseudo TTY drivers.
  Change TPA to TWA; ignore SS$_DATAOVERUN; use GETDVI to get unit number
Version 2.3.  June 30, 1988
  C-s (to stop output) hangs process when June 1988 PTY (TWA) drivers are used
  with VMS 4.7.  Make BOSS try both TWA and TPA, so it works with both new
  and old PTY drivers.
Version 2.4.  July 4, 1988
  BOSS/FLOW_CONTROL to permit flow-control at BOSS level rather than
  subprocess level.  To make this work, need to change C-s command to C-w.
Version 2.5.  July 22, 1988
  Implement UW Protocol 1.  BOSS/UW.  Add /PROCESS_DEFAULT.  Add VT52 support.
  C-k command added to kill current process.
Version 2.6.  September 26, 1988
  Write out stuffed strings one character at a time to prevent part of the
  string being lost.  (A better solution would be to handle the DATA_OVERRUN
  condition properly by waiting for an XON AST.)
  Fixed bug where the y/n response to process deletion and exiting was
  interpreted as a process switch command if the current process completed
  in the meantime.
  Add /AUTO_STUFF_STRING
Version 2.7.  November 4, 1988
  Ignore error status 0.
  Fix C-k so it doesn't try to kill top-level process.
Version 2.7b 8/22/1990 Glenn Everhart
  Added control-I toggle for buffering. Allows buffering of console
  output even from active output so one can switch to another window
  and get back the buffered text. Switching processes resets. Will
  arrange so that some "scrolled off" text can be saved, though the
  buffering is switched off on process switches. Another toggle will
  be added to allow buffer display to be a one-shot without losing old
  contents.
Version 2.7c 8/23/90 Glenn Everhart
  Add C-L toggle to open or close output to BOSSx.LOG for current
  process. Make it apply per process. Shutdown will close the file
  where orderly.
Version 2.7d 8/30/90 Glenn Everhart
  Add C-G to grab 1 to 9 lines out of one process' buffer (assuming
  data is being buffered) and shove it into the next process where
  one starts it, similar to boss_stuff operation. Kludgey but possibly
  useful.
Version 2.8.  June 23, 1989
  Set /noescape on terminal running BOSS to avoid "partial escape" error
Version 2.9.  August 1, 1989
  Fix error in C-\ C-h documentation.
Version 3.0.  May 31, 1991
  Merge C. Karney mods for V2.9 into Glenn Everhart version 2.7d+
Version 3.1.  December 12, 1991 Henk Davids <hdavids@mswe.dnet.ms.philips.nl>
  Source processed by "indent -ncdb -di0 -i4 -nfc1 -br -ce -psl -ip -cli1".
  Added poor mans record and playback facilities.
  Qualifiers:
   /record[=filespec]     default: boss.record
   /playback[=filespec]   default: boss.record
   /gear=<f>              <f> is speedup factor for playback; default 1.0.
                          if <f> is "I[nfinite]" we will skip delays.
   /lock                  ignore keyboard input during playback
  The record file will contain the keystrokes from the physical terminal,
  interspersed with delay specifications of the form \<n><newline>,
  where <n> is the delay in 100 millisecond units.
  Added C-x command to request an exit after all processes have terminated.
  Added /quit_on_idle to perform the same function from the command line.
  Added /log to set the default action to logging when creating a process,
   and optionally supply a prefix for the log-filename (def:"BOSS").
  Do not close the logfile when logging is stopped, but at quit time.
  Use the process name for the log filename iso a number.
Still to do:
  Make /FLOW_CONTROL work with Emacs by checking the device characteristics
  of the pseudo TTY.  (Not sure how best to do this: Could do this with
  setmode ast, or else check terminal setting, or else let user set a
  per-process flag.)
  C-a append to log file?
  Add support for VT420 terminals, where the display buffer can be larger
  than the screen.
  Add some minimal 'expect' support in playback mode. Somebody care to
  port Don Libes' 'expect' package to VMS?
  Add support for the FTDRIVER.
*/

#define VERSION		"3.1"

#include DESCRIP
#include IODEF
#include TTDEF
#include TT2DEF
#include JPIDEF
#include LNMDEF
#include PRVDEF
#include PSLDEF
#include SSDEF
#include STSDEF
#include TIME
#include DVIDEF
#include PERROR
#include FILE
#include ERRNO

#include STDIO
#include STDLIB
#include CLIMSGDEF

#define  TTCHRLEN       12
#define  TPDEVLEN       15
#define  MBSIZ          40
#define  TTMBSIZ       256
#define  MAXSIZ         80
#define  TTMAXSIZ      256
#define  IMAGELEN       80
#define  LINESZ        512
#define  BUFSIZE      4096	/* Size of output buffers */
#define  MAXBUF       1200	/* Should be less than SYSGEN MAXBUF */
				/* 1200 is in fact the minimum setting */
#define  NPROCMAX        8	/* Number of process allowed */
#define  NALPHMAX       26	/* Number of possible names */

#define  bad(j)         !((j) & 1)
#define  check(a)       if (bad(st = (a))) \
                           {if (st != 0) LIB$SIGNAL(st);} else {}

#define NORMAL 0
#define SWITCH 0
#define PENDING 1
#define CREATE 2
#define TOP 3
#define END 4
#define KILL 5
#define CUTP 6

#define BRK$C_DEVICE      1
#define BRK$C_USER16      47

/*
 *      uw protocol
 *
 * Copyright 1985,1986 by John D. Bruner.  All rights reserved.  Permission to
 * copy this program is given provided that the copy is not sold and that
 * this copyright notice is included.
 */

#define P1_IAC          0001	/* interpret as command */
#define P1_DIR          0100	/* command direction: */
#define P1_DIR_HTOM     0000	/* from host to Mac */
#define P1_DIR_MTOH     0100	/* from Mac to host */
#define P1_FN           0070	/* function code: */
#define P1_FN_NEWW      0000	/* new window */
#define P1_FN_KILLW     0010	/* kill (delete) window */
#define P1_FN_ISELW     0020	/* select window for input */
#define P1_FN_OSELW     0030	/* select window for output */
#define P1_FN_META      0050	/* add meta to next data char */
#define P1_FN_CTLCH     0060	/* low 3 bits specify char */
#define P1_FN_MAINT     0070	/* maintenance functions */
#define P1_WINDOW       0007	/* window number mask */
#define P1_CC           0007	/* control character specifier: */
#define P1_CC_IAC       1	/* IAC */
#define P1_CC_XON       2	/* XON */
#define P1_CC_XOFF      3	/* XOFF */
#define P1_MF           0007	/* maintenance functions: */
#define P1_MF_ENTRY     0	/* beginning execution */
#define P1_MF_ASKPCL    2	/* request protocol negotiation */
#define P1_MF_CANPCL    3	/* suggest protocol */
#define P1_MF_SETPCL    4	/* set current protocol */
#define P1_MF_EXIT      7	/* execution terminating */
#define P1_NWINDOW      7	/* maximum number of windows */

#define UW_NORMAL 0
#define UW_PENDING 1
#define UW_CANPCL 2
#define UW_SETPCL 3
#define XON 021
#define XOFF 023

/* Definitions for record/playback modes */
/* To start a delay specifier in the record file: */
#define DELAY_ESC '\\'
/* Less than this many secs, and we will consider keystrokes as consecutive: */
#define DELAY_THR 0.3
/* End definitions for record/playback modes */

struct CHARBLK {
    unsigned char class, ttype;
    unsigned short pgwid;
    unsigned ttchr:24;
    unsigned char pglen;
    unsigned int xchar;
};

struct IOSBBLK {
    unsigned short stats, tmoff, tmntr, tmsiz;
};

typedef struct DVIBLK {
    unsigned short len, code;
    char *buffp;
    long *lenp;
    long terminate;
}      DVIBLK;

int
    py_chn[NPROCMAX], py_mb_chn[NPROCMAX], tt_chn, tt_mb_chn, pid[NPROCMAX],
    st, cur, kill_proc, count[NPROCMAX], buflen[NPROCMAX], procno[NALPHMAX],
    priv[2], privs[2], def_stuff_len, bufmod[NPROCMAX], bufmod2[NPROCMAX],
    logmod[NPROCMAX], cutpas[NPROCMAX], kprc, no_phy_io, no_oper, brkthru,
    ctlchar, init, nproc, nalph, recording, record_fd, playback, playback_fd,
    quit_in_progress = FALSE, keyboard_locked = FALSE, synchr_quit = FALSE,
    auto_log = FALSE, log_file_prefix_len;

float gear = 1.0;		/* for playback */

int logfd[NPROCMAX];

static char
    *clr, *bos, *ceol, *ceoln, *retval, ctlchar_str[4], prompt_begin[30],
     prompt_end[30], switch_create, flow_control, delete_char, stuff_buf[256],
     def_stuff_buf[256], buf[256], image[IMAGELEN], finaltp[NPROCMAX][TPDEVLEN], py_mb[NPROCMAX][MBSIZ],
     tt_mb[TTMBSIZ], tpline[NPROCMAX][LINESZ], buffer[NPROCMAX][BUFSIZE], term_buf[MAXBUF], blocked[NPROCMAX],
     mode[NPROCMAX], pmode[NPROCMAX], defmode, defproc, name[NPROCMAX],
     py_post[NPROCMAX], proc_type[NPROCMAX], enable_hangup[NPROCMAX], input_state = NORMAL,
     oboss_id = 0, super_ac_mode = PSL$C_SUPER, uw = 0, uw_state = UW_NORMAL,
     uw_meta = 0, first_name, last_name, mac_command = 0, record_file[256],
     playback_file[256], log_file_prefix[80];
static unsigned char input_char;

extern int close();
extern int creat();
extern int write();

extern int BOSS_CLD();

struct CHARBLK tt_chr, tt_sav_chr;
struct IOSBBLK tiosb, tiosbmb, piosb[NPROCMAX], miosb[NPROCMAX];

static short trnlnm_string_len;
static char trnlnm_string[BUFSIZE + 151];
/* Make this string quite long so we can use it for cut/paste */
/* n.b. cut/paste a bit kludgey at the moment */

struct ITEM_LST {
    unsigned short len, code;
    char *addr;
    short *retlen;
}        trnlnm_item = {
    80, LNM$_STRING, &trnlnm_string, &trnlnm_string_len
};

/*---- timer functions ---*/

/* static timer memory */
static double last_timestamp = 0.0;

static double
time_so_far()
{
    timeb_t tms;

    ftime(&tms);		/* get time in millisec units */
    /* resolution is 10 msecs */
    return ((((double) tms.time * 1000.0) + (double) tms.millitm) / 1000.0);
}

void
startclock()
{
    last_timestamp = time_so_far();
}

double
elapsedtime()
{
    return (time_so_far() - last_timestamp);
}

void 
msleep(millisecs)		/* Hibernate n milliseconds */
    int millisecs;
{
    int i, sleep;
    extern int sys$schdwk();	/* scheduled wake-up */
    extern int sys$hiber();	/* hibernate */
    extern int sys$canwak();
    extern int lib$mult_delta_time();
    int delta[2] = {-10000, -1};/* delta 1 millisec in quadword */

    if (millisecs <= 0)
	return;
    /* convert millisecs value to delta time in system quad time format */
    i = lib$mult_delta_time(&millisecs, delta);

    check(sys$schdwk(0, 0, delta, 0));
    check(sys$hiber());		/* sleep */
    check(sys$canwak(0, 0));	/* cancel schdwk request */
    return;
}

quit()
{				/* This is done upon exiting, by exit handler */
    int i, j;
    char id[2];

    $DESCRIPTOR(d_boss_id, "BOSS$ID");
    $DESCRIPTOR(d_id, id);

    if (uw && !mac_command)
	uw_fun(P1_FN_MAINT | P1_MF_EXIT, 0, 0);

    if (oboss_id != 0) {	/* Restore BOSS$ID */
	id[0] = oboss_id;
	j = LIB$SET_LOGICAL(&d_boss_id, &d_id, 0, 0, 0);
    }
    for (i = 0; i < nproc; i++) {
	if (name[i]) {
	    j = SYS$DELMBX(py_mb_chn[i]);
	    if (bad(j))
		printf("[SYS$DELMBX pseudo-mbx deletion failed]\n");
	    /* Last chance close all log files */
	    if (logfd[i] != 0)
		j = close(logfd[i]);
	}
    }
    quit_in_progress = TRUE;
    j = SYS$CANCEL(tt_chn);	/* Cancel outstanding input request physical
				 * terminal */
    j = SYS$QIOW(0, tt_chn, IO$_SETMODE, 0, 0, 0, &tt_sav_chr, TTCHRLEN, 0, 0, 0, 0);
    if (bad(j))
	printf("[SYS$QIO /setmode/ failed]\n");
    if (recording) {
	j = close(record_fd);
    }
    printf("\nEnd BOSS\n");
}

mb_srv(n)			/* AST for mailbox message on top-level */
				/* process completion */
    int n;
{
    if (proc_type[n] == TOP) {
	if (enable_hangup[n])
	    comp_srv(n);
	else {
	    enable_hangup[n] = 1;
	    check(SYS$QIO(0, py_mb_chn[n], IO$_READVBLK, &miosb[n], &mb_srv, n,
			  &py_mb[n], MBSIZ, 0, 0, 0, 0));
	}
    }
}

comp_srv(n)			/* AST for completion of processes */
    int n;
{
    int j;

    j = SYS$DELMBX(py_mb_chn[n]);
    if (name[n])
	procno[name[n] - first_name] = -1;
    name[n] = '\0';
    if (uw && !mac_command)
	uw_fun(P1_FN_KILLW | ((n + 1) & P1_WINDOW), 0, 0);
    if (kill_proc == n)
	kill_proc = -1;
    if (logfd[n] != 0)
	j = close(logfd[n]);
    if (cur == n)
	cur = -1;
    if (synchr_quit && !count_processes()) {
	/* No more processes, and synchr quit requested: exit */
	exit(SS$_NORMAL);
    }
}

/*
    Output routines for pseudo terminal
*/
/* Note: as coded, these routines try to use the XON-AST mechanism to
	 prevent overflowing the PY with data in playback mode.
	 However, although the basic mechanism seems to work on the
	 PY side:
	 	start output
	 	hibernate
		in the IO AST, check the status:
			success, then wake
			data overrun, then schedule XON AST
		in the XON AST, restart output if needed
		else wake.
	we still will get data overrun errors, but now in the read
	from TW in the subprocess.
	What am I doing wrong here?
*/

/* Request codes */
#define SET_XON_AST	1
#define SET_XOFF_AST	2
#define SET_LINE_AST	3

/* Static buffers */
/* Since only one PY is used at any time for output, we only need 1 copy */
static unsigned char py_out_buf[80]; /* only 1 char used currently */
static unsigned char *py_out_buf_ptr;
static int py_out_len = 0;
static struct IOSBBLK py_out_iosb;

/*
    AST for py XON notifcation
*/
void
py_out_xon()
{
    void py_out_srv();
    if (py_out_len) {
	check(SYS$QIO(0, py_chn[cur], IO$_WRITEVBLK, &py_out_iosb,
		      py_out_srv, 0,
		      py_out_buf_ptr, py_out_len, 0, 0, 0, 0));
    } else {
	check(sys$wake(0, 0));
    }
}

/*
    AST for py output completion
*/
void
py_out_srv()
{
    if (py_out_iosb.stats != SS$_DATAOVERUN) {
	check(py_out_iosb.stats);
	py_out_len = 0;
	check(sys$wake(0, 0));
    } else {
	/* update output len and ptr */
	py_out_len -= py_out_iosb.tmoff;
	py_out_buf_ptr += py_out_iosb.tmoff;
	/* schedule an XON AST */
	check(SYS$QIOW(0, py_chn[cur], IO$_SETMODE, 0,
		       py_out_xon, 0, 0, 0, 0, SET_XON_AST, 0, 0));
    }
}

/*
    Send data to current pseudo terminal.
    If we are not on_AST_level let's try to be careful not to overflow
    the pseudo terminal.
    Otherwise, just use the old method (QIOW) for now.
*/
void
to_pty(on_AST_level, character)
    int on_AST_level;
    unsigned char character;
{
    py_out_len = 1;
    py_out_buf_ptr = &py_out_buf[0];
    *py_out_buf_ptr = character;
    if (on_AST_level) {
	check(SYS$QIOW(0, py_chn[cur], IO$_WRITEVBLK, &py_out_iosb, 0, 0,
		       py_out_buf_ptr, py_out_len, 0, 0, 0, 0));
	if (py_out_iosb.stats != SS$_DATAOVERUN)
	    check(py_out_iosb.stats);
    } else {
	py_out_iosb.tmoff = 0;
	check(SYS$QIO(0, py_chn[cur], IO$_WRITEVBLK, &py_out_iosb,
		      py_out_srv, 0,
		      py_out_buf_ptr, py_out_len, 0, 0, 0, 0));
	check(sys$hiber());
    }
}    

int 
low_lib_spawn(n, pty_io, pid, name)
 /* Spawns subprocess to speak to pseudo terminal */
    char *pty_io, name;
    int n, *pid;
{
    int flg = 1, len, val, i;
    char proc[20], prompt[50], id[2];
    $DESCRIPTOR(d_pty_io, pty_io);	/* PTY name + number */
    $DESCRIPTOR(d_proc, proc);		/* Process name */
    $DESCRIPTOR(d_prompt, prompt);	/* Prompt */
    $DESCRIPTOR(d_boss_id, "BOSS$ID");
    $DESCRIPTOR(d_id, id);	/* The id */
    d_pty_io.dsc$w_length = strlen(pty_io);
    strcpy(proc, getenv("TT"));
    len = strlen(proc);
    if (proc[len - 1] == ':')
	proc[--len] = '\0';
    strcat(proc, "_A");
    len = strlen(proc);
    proc[len - 1] = name;
    d_proc.dsc$w_length = len;
    if (proc[0] == '_') {
	d_proc.dsc$w_length--;
	d_proc.dsc$a_pointer++;
    }
    sprintf(prompt, "%s%c%s", prompt_begin, name, prompt_end);
    d_prompt.dsc$w_length = strlen(prompt);
    id[0] = name;
    check(LIB$SET_LOGICAL(&d_boss_id, &d_id, 0, 0, 0));
    val = LIB$SPAWN(0, &d_pty_io, &d_pty_io, &flg, &d_proc, pid, 0, 0,
		    &comp_srv, n, &d_prompt, 0);
    if (!bad(val))
	for (i = 0; i < def_stuff_len; i++) {
	    check(SYS$QIOW(0, py_chn[n], IO$_WRITEVBLK, &tiosb, 0, 0,
			   def_stuff_buf + i, 1, 0, 0, 0, 0));
	    if (tiosb.stats != SS$_DATAOVERUN)
		check(tiosb.stats);
	}
    check(LIB$DELETE_LOGICAL(&d_boss_id, 0));
    return (val);
}

py_srv(n)			/* AST reads on pseudo terminal */
    int n;
{
    int j, kkkk;
    char *tpptr;
    py_post[n] = 0;
    check(piosb[n].stats);	/* Check status */
    count[n] = piosb[n].tmoff + piosb[n].tmsiz;	/* How much was read */
    if (n >= 0 && logmod[n] != 0) {
	if (logfd[n] != 0) {
	    tpptr = &tpline[n];
	    /* write text to active logfile if one exists */
	    j = write(logfd[n], tpptr, count[n]);
	}
    }
    if (n == cur || mode[n] == 'p') {
	if (bufmod[n] != 0) {	/* Switch buffering separately */
	    if (count[n] + buflen[n] < BUFSIZE) {
		for (j = 0; j < count[n]; j++)
		    buffer[n][buflen[n]++] = tpline[n][j];
	    } else {
		/* copy buffer down and add in new text */
		/* First copy text down enough to hold the new line */
		kkkk = buflen[n] - count[n];
		if (kkkk <= 0)
		    kkkk = 1;
		for (j = 0; j < kkkk; j++)
		    buffer[n][j] = buffer[n][j + count[n]];
		buflen[n] = buflen[n] - count[n];
		/* Now add the new text after the line */
		for (j = 0; j < count[n]; j++)
		    buffer[n][buflen[n]++] = tpline[n][j];
	    }
	}			/* bufmod */
	term_out(n);		/* Write the stuff to the terminal */
    } else if (mode[n] == 'w') {
	blocked[n] = 1;
    } else if (mode[n] == 'o') {
	blocked[n] = 1;
	check(SYS$QIO(0, py_chn[n], IO$_READVBLK, &piosb[n], &py_srv, n,
		      &tpline[n], LINESZ, 0, 0, 0, 0));	/* Queue next AST */
	py_post[n] = 1;
    } else if (mode[n] == 'b') {
	if (count[n] + buflen[n] < BUFSIZE) {
	    for (j = 0; j < count[n]; j++)
		buffer[n][buflen[n]++] = tpline[n][j];
	    check(SYS$QIO(0, py_chn[n], IO$_READVBLK, &piosb[n], &py_srv, n,
			  &tpline[n], LINESZ, 0, 0, 0, 0));	/* Queue next AST */
	    py_post[n] = 1;
	    blocked[n] = 0;
	} else {
	    py_post[n] = 0;
	    blocked[n] = 1;
	}
    }
}

to_term(buf, len, chan)
    char *buf;
    int len, chan;
{
    int i, j;
    char ochar;

    if (!uw) {
	j = 0;
	while (j < len) {
	    check(SYS$QIOW(0, tt_chn, IO$_WRITEVBLK, &tiosb,
	    		   0, 0, &buf[j],
			   (len - j < MAXBUF) ? len - j : MAXBUF,
			   0, 0, 0, 0));
	    j += MAXBUF;
	}
    } else if (chan >= 0) {
	j = 0;
	i = 0;
	term_buf[i++] = P1_IAC;
	term_buf[i++] = P1_DIR_HTOM | P1_FN_OSELW | ((chan + 1) & P1_WINDOW);
	while (j < len) {
	    ochar = buf[j++];
	    if (ochar & 0200) {
		term_buf[i++] = P1_IAC;
		term_buf[i++] = P1_DIR_HTOM | P1_FN_META;
		ochar = ochar & 0177;
	    }
	    switch (ochar) {
		case P1_IAC:
		    term_buf[i++] = P1_IAC;
		    term_buf[i++] = P1_DIR_HTOM | P1_FN_CTLCH | P1_CC_IAC;
		    break;
		case XON:
		    term_buf[i++] = P1_IAC;
		    term_buf[i++] = P1_DIR_HTOM | P1_FN_CTLCH | P1_CC_XON;
		    break;
		case XOFF:
		    term_buf[i++] = P1_IAC;
		    term_buf[i++] = P1_DIR_HTOM | P1_FN_CTLCH | P1_CC_XOFF;
		    break;
		default:
		    term_buf[i++] = ochar;
		    break;
	    }
	    if (i > MAXBUF - 4) {	/* If no room for another meta-xon */
		check(SYS$QIOW(0, tt_chn, IO$_WRITEVBLK, &tiosb,
			       0, 0, term_buf, i, 0, 0, 0, 0));
		i = 2;		/* Leave OSELW command in first 2 bytes */
	    }
	}
	if (i > 2) {		/* Spit out rest of buffer */
	    check(SYS$QIOW(0, tt_chn, IO$_WRITEVBLK, &tiosb,
	    		   0, 0, term_buf, i, 0, 0, 0, 0));
	    i = 0;
	}
    }
}

term_out(n)
    int n;
{
    int j, k, kk, kkk, kkkk;
    char nname;
    $DESCRIPTOR(d_boss_switch, "BOSS$SWITCH");
    $DESCRIPTOR(d_boss_stuff, "BOSS$STUFF");
    $DESCRIPTOR(d_lnm_job, "LNM$JOB");
    if (buflen[n] > 0) {
	/* If buffering current stuff and in a print mode, print only new
	 * stuff */
	if (bufmod[n] == 0) {
	    to_term(buffer[n], buflen[n], n);
	    if (bufmod2[n] == 0)
		buflen[n] = 0;
	    else
		bufmod[n] = 1;
	} else
	    to_term(tpline[n], count[n], n);
	if (blocked[n])
	    to_term(tpline[n], count[n], n);
    } else
	to_term(tpline[n], count[n], n);

    /* Process boss_switch and boss_stuff logicals if present */

    j = SYS$TRNLNM(0, &d_lnm_job, &d_boss_switch, &super_ac_mode, &trnlnm_item);
    if (!bad(j) && trnlnm_string_len == 1) {
	j = LIB$DELETE_LOGICAL(&d_boss_switch, &d_lnm_job);
	nname = toupper(trnlnm_string[0]);
	j = SYS$TRNLNM(0, &d_lnm_job, &d_boss_stuff, &super_ac_mode, &trnlnm_item);
	if (!bad(j)) {
	    j = LIB$DELETE_LOGICAL(&d_boss_stuff, &d_lnm_job);
	    trnlnm_string[trnlnm_string_len] = '\0';
	} else {
	    trnlnm_string[0] = '\0';
	}
	if (nname >= first_name && nname <= last_name) {
	    mode[n] = 'w';
	    mov_to(nname, 0, trnlnm_string, defproc);
	}
    }
    if (py_post[n] == 0) {
	check(SYS$QIO(0, py_chn[n], IO$_READVBLK, &piosb[n], &py_srv, n,
		      &tpline[n], LINESZ, 0, 0, 0, 0));	/* Queue next AST */
	py_post[n] = 1;
    }
    blocked[n] = 0;
}

int 
count_processes()
{
    int j, i = 0;

    for (j = 0; j < nproc; j++)
	if (name[j] > 0)
	    i++;
    return (i);
}

diag()
{
    char bufa[8];
    int j;

    if (count_processes()) {
	sprintf(buf, "%s[Processes:", bos);
	for (j = 0; j < nproc; j++) {
	    if (name[j] > 0) {
		if (j == cur)
		    sprintf(bufa, " %c%c*", name[j], mode[j]);
		else if (blocked[j])
		    sprintf(bufa, " %c%c+", name[j], mode[j]);
		else if (buflen[j] > 0)
		    sprintf(bufa, " %c%c-", name[j], mode[j]);
		else
		    sprintf(bufa, " %c%c", name[j], mode[j]);
		strcat(buf, bufa);
	    }
	}
	strcat(buf, "] ");
	strcat(buf, ceoln);
    } else
	sprintf(buf, "%s[No processes] %s", bos, ceoln);
    term_msg(buf);
}

int 
next_slot()
{
    int j = 0;
    while ((j < nproc) && name[j])
	j++;
    return (j == nproc) ? -1 : j;
}

term_msg(msg)
    char *msg;
{
    to_term(msg, strlen(msg), cur);
}

char *
get_image(pid)			/* Get the image name for a process */
    int pid;
{
    int j, item;
    short len;
    char *ptr, *ptra;
    $DESCRIPTOR(d_image, image);
    ptr = &image;
    if (pid == 0)
	strcpy(image, "<TOP>");
    else {
	item = JPI$_IMAGNAME;
	j = LIB$GETJPI(&item, &pid, 0, 0, &d_image, &len);
	if (bad(j))
	    strcpy(image, "<UNKNOWN>");
	else {
	    image[len] = '\0';
	    if (len == 0) {
		item = JPI$_CLINAME;
		j = LIB$GETJPI(&item, &pid, 0, 0, &d_image, &len);
		if (bad(j))
		    strcpy(image, "<UNKNOWN>");
		else
		    image[len] = '\0';
	    } else {
		if ((ptr = strrchr(image, ']')))
		    ptr++;
		else
		    ptr = &image;
		if (ptra = strchr(ptr, '.'))
		    *ptra = '\0';
	    }
	}
    }
    return (ptr);
}

int 
mov_to(nname, clear, string, proc_mode)	/* Switch to process */
    char nname, *string;		/* string is stuffed into input */
    int clear, proc_mode;               /* proc_mode says whether to create process */
{
    int ncur, len, i, j;
    char *prefix;
    prefix = clear ? clr : bos;
    len = strlen(string);
    if ((cur >= 0) && (name[cur] == nname)) {	/* Redundant move */
	bufmod[cur] = 0;
	mode[cur] = pmode[cur];
	if (len == 0 & !uw) {
	    sprintf(buf, "%s[Already in process %c%c, %s]%s",
		    prefix, nname, mode[cur], get_image(pid[cur]), ceoln);
	    term_msg(buf);
	}
	j = 1;
    } else if ((ncur = procno[nname - first_name]) >= 0) {	/* Existing proc */
	cur = ncur;
	mode[cur] = pmode[cur];
	bufmod[cur] = 0;
	if (!uw) {
	    sprintf(buf, "%s[Switch to process %c%c, %s]%s",
		  prefix, name[cur], mode[cur], get_image(pid[cur]), ceoln);
	    term_msg(buf);
	}
	if (blocked[cur] || buflen[cur] > 0)
	    term_out(cur);
	j = 1;
    } else if (proc_mode == SWITCH) {
	sprintf(buf,
	  "%s[Process %c nonexistent\007 (type %s C-%c %c to create it)]%s",
		bos, nname, ctlchar_str, defproc == TOP ? 't' : 'n',
		clear ? nname : tolower(nname), ceoln);
	term_msg(buf);
	len = 0;
	j = 0;
    } else if ((ncur = (uw ? (nname - first_name) : next_slot())) < 0) {
	if (cur >= 0)
	    sprintf(buf, "%s[No process slots left--still in %c%c]%s",
		    bos, name[cur], mode[cur], ceoln);
	else
	    sprintf(buf, "%s[No process slots left]%s", bos, ceoln);
	term_msg(buf);
	len = 0;
	j = 0;
    } else {
	if (!uw) {
	    if (proc_mode == CREATE)
		sprintf(buf, "%s[Starting subprocess %c...%s", prefix, nname, ceol);
	    else if (proc_mode == TOP)
		sprintf(buf, "%s[Starting top-level process %c...%s", prefix, nname, ceol);
	    term_msg(buf);
	}
	j = fire_up(ncur, nname, proc_mode);
	if (bad(j)) {
	    if (!uw) {
		if (cur >= 0)
		    sprintf(buf, "failed!!\007--still in %c%c]%s",
			    name[cur], mode[cur], ceoln);
		else
		    sprintf(buf, "failed!!\007]%s", ceoln);
	    } else {
		sprintf(buf, "%s[Couldn't start start process %c]%s",
			prefix, nname, ceoln);
		uw_fun(P1_FN_KILLW | ((ncur + 1) & P1_WINDOW), 0, 0);
	    }
	    term_msg(buf);
	    len = 0;
	} else {
	    if (!uw) {
		sprintf(buf, "done; now in process %c%c, %s]%s\r",
			nname, mode[ncur], get_image(pid[ncur]), ceol);
		term_msg(buf);
	    }
	    cur = ncur;
	    if (blocked[cur])
		term_out(cur);
	}
    }
    /* if (j && uw && !mac_command) uw_fun(P1_FN_ISELW | ((cur + 1) &
     * P1_WINDOW), 0, 0); */
    for (i = 0; i < len; i++) {
	check(SYS$QIOW(0, py_chn[cur], IO$_WRITEVBLK, &tiosb, 0, 0,
		       string + i, 1, 0, 0, 0, 0));
	if (tiosb.stats != SS$_DATAOVERUN)
	    check(tiosb.stats);
    }
    return (j);
}

print_help()
{
    if (ctlchar >= 0 && ctlchar < 040)
	sprintf(buf, "%sBOSS commands are preceded by %s (control-%c).  \
The commands are:%s", bos, ctlchar_str, tolower(ctlchar + 0100), ceoln);
    else
	sprintf(buf, "%sBOSS commands are preceded by %s.  \
The commands are:%s", bos, ctlchar_str, ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-h     This message%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-z     Quit immediately%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-x     Quit when no more processes active%s", ceoln);
    term_msg(buf);
    sprintf(buf,
	 "\r    %c       Switch to process %c (similarly for %c thru %c)%s",
	    tolower(first_name), first_name,
	    tolower(first_name), tolower(last_name), ceoln);
    term_msg(buf);
    if (!uw) {
	sprintf(buf, "\r    %c       Clear screen and switch to process %c%s",
		first_name, first_name, ceoln);
	term_msg(buf);
    }
    sprintf(buf, "\r    C-n %c   Create new process %c as a subprocess%s",
	    tolower(first_name), first_name, ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-t %c   Create process %c at top level%s",
	    tolower(first_name), first_name, ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-k     Kill current subprocess%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    ?       List processes (* means current, \
+/- means waiting for output)%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-b     Buffer output for this process%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-o     Discard output for this process%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-p     Print output from this process%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-i     Toggle buffering of active output%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-j     Toggle multishot buffering%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-l     Toggle logging to BOSSn.LOG%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-g     Get n lines of cur proc -> next%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    C-w     Stop output from this process%s", ceoln);
    term_msg(buf);
    sprintf(buf, "\r    %-3s     Send command character to current process%s",
	    ctlchar_str, ceoln);
    term_msg(buf);
    sprintf(buf, "\rType HELP BOSS for more information.%s", ceoln);
    term_msg(buf);
}

void
start_logging(cur)
    int cur;
{
    char lognam[80];

    logmod[cur] = 1;
    if (logfd[cur] == 0) {
	/* no open logfile yet, compose name and open */
	strcpy(lognam, log_file_prefix);
	lognam[log_file_prefix_len] = name[cur];
	strcpy(&lognam[log_file_prefix_len + 1], ".LOG");
	logfd[cur] = creat(lognam, 0, "mbf=3");
    }
}

/*
   Read on real terminal server
   This routine will either be called at AST level for physical terminal
   reads (on_AST_level true), or from main level in case of playback (false).
*/

tt_srv(on_AST_level)
    int on_AST_level;
{
    int i;
    int k, kk, kkk, kkkk;
    int n;
    char nname, nmode, *desc, dismiss, function, arg;
    char lognam[80];
    char *lnptr;

    if (quit_in_progress)
	return;			/* ignore AST from sys$cancel */

    check(tiosb.stats);

    dismiss = 0;

    if (playback && on_AST_level) {
	/* User interrupts a playback session */
	if (keyboard_locked) {
	    /* ignoring keyboard */
	    dismiss = 1;
	} else {
	    playback = 0;
	    /* todo: main() may be in a delay sleep - wake up? */
	}
    }
    /* recording first */
    if (recording && !dismiss) {
	double delay = elapsedtime();
	startclock();
	if (delay > DELAY_THR) {
	    /* write a delay entry if more than DELAY_THR secs between
	     * keystrokes */
	    delay *= 10.0;	/* convert to 100 millisecs units */
	    sprintf(buf, "%c%d\n", DELAY_ESC, (int) delay);
	    write(record_fd, buf, strlen(buf));
	}
	if (input_char == DELAY_ESC)
	    write(record_fd, &input_char, 1);
	write(record_fd, &input_char, 1);
    }
    /* do UW decoding at first */
    if (uw) {
	mac_command = 1;
	switch (uw_state) {
	    case UW_NORMAL:
		if (input_char == P1_IAC) {
		    uw_state = UW_PENDING;
		    dismiss = 1;
		}
		break;
	    case UW_PENDING:
		uw_state = UW_NORMAL;
		dismiss = 1;
		if ((input_char & P1_DIR) == P1_DIR_MTOH) {
		    function = input_char & P1_FN;
		    arg = input_char & P1_WINDOW;
		    switch (function) {
			case P1_FN_NEWW:
			    mov_to(first_name + (arg - 1), 0, "", defproc);
			    break;
			case P1_FN_KILLW:
			    if (name[arg - 1] > 0 && proc_type[arg - 1] != TOP)
				st = SYS$DELPRC(&pid[arg - 1], 0);
			    break;
			case P1_FN_ISELW:
			    mov_to(first_name + (arg - 1), 0, "", defproc);
			    break;
			case P1_FN_OSELW:	/* shouldn't ever occur */
			    break;
			case P1_FN_META:
			    uw_meta = 1;
			    break;
			case P1_FN_CTLCH:
			    dismiss = 0;
			    switch (arg) {
				case P1_CC_IAC:
				    input_char = P1_IAC;
				    break;
				case P1_CC_XON:
				    input_char = XON;
				    break;
				case P1_CC_XOFF:
				    input_char = XOFF;
				    break;
				default:
				    dismiss = 1;
				    break;
			    }
			    break;
			case P1_FN_MAINT:
			    switch (arg) {
				case P1_MF_ENTRY: /* Shouldn't get this */
				    break;
				case P1_MF_ASKPCL:
				    uw_fun(P1_FN_MAINT | P1_MF_CANPCL, 040, 1);
				    break;
				case P1_MF_CANPCL: /* Shouldn't get this */
				    uw_state = UW_CANPCL;
				    break;
				case P1_MF_SETPCL:
				    uw_state = UW_SETPCL;
				    break;
				case P1_MF_EXIT:
				    exit(SS$_NORMAL);
				    break;
			    }
			    break;
			case UW_CANPCL:
			    if (input_char == 040)
				uw_fun(P1_MF_SETPCL, 040, 1);
			    else
				uw_fun(P1_MF_CANPCL, 040, 1);
			    uw_state = UW_NORMAL;
			    break;
			case UW_SETPCL:
			    if (input_char != 040) {
				sprintf(buf,
					"%s[Don't understand this protocol: %d]%s",
					bos, input_char, ceoln);
			    }
			    uw_state = UW_NORMAL;
			    break;
		    }
		}
	}
	if (uw_meta && !dismiss) {
	    uw_meta = 0;
	    input_char = 0200 | input_char;
	}
	mac_command = 0;
    }
    if (!dismiss) {
	if (input_char == 0177)
	    input_char = delete_char;
	else if (input_char == delete_char)
	    input_char = 0177;
	if (input_state == NORMAL && cur < 0)
	    input_state = PENDING;
	switch (input_state) {
	    case NORMAL:
		if (input_char == ctlchar)
		    input_state = PENDING;
		else {
		    to_pty(on_AST_level, input_char);
		}
		break;
	    case PENDING:
		if (input_char == ctlchar) {
		    if (cur >= 0) {
			to_pty(on_AST_level, input_char);
			input_state = NORMAL;
		    }
		    break;
		}
		switch (input_char) {
		    case '\016':	/* C-n */
			input_state = CREATE;
			break;
		    case '\024':	/* C-t */
			input_state = TOP;
			break;
		    case '\030':	/* C-x */
			synchr_quit = TRUE;
			sprintf(buf,
				"%s[Boss: will exit when processes terminate]%s",
				bos, ceoln);
			term_msg(buf);
			input_state = NORMAL;
			break;
		    case '\032':	/* C-z */
			if (count_processes()) {
			    diag();
			    sprintf(buf,
			    	    "[Do you really want to quit (y or n)?]%s\007",
				    ceol);
			    term_msg(buf);
			    input_state = END;
			} else
			    exit(SS$_NORMAL);
			break;
		    case '\007':	/* C-g */
			if (cur < 0) {
			    term_msg("\007");
			    input_state = NORMAL;
			} else {
			    sprintf(buf,
			    "%s[Enter # lines to move (1-9):%s", bos, ceoln);
			    term_msg(buf);
			    kprc = cur;
			    input_state = CUTP;
			}
			break;
		    case '\013':	/* C-k */
			if (cur < 0) {
			    term_msg("\007");
			    input_state = NORMAL;
			} else if (proc_type[cur] == TOP) {
			    sprintf(buf, "%s[Can't kill top-level process %c]%s\007",
				    bos, name[cur], ceoln);
			    term_msg(buf);
			    input_state = NORMAL;
			} else {
			    sprintf(buf,
				    "%s[Do you really want to kill process %c (y or n)?]%s\007",
				    bos, name[cur], ceol);
			    term_msg(buf);
			    kill_proc = cur;
			    input_state = KILL;
			}
			break;
		    case '\010':	/* C-h */
			print_help();
			input_state = NORMAL;
			break;
		    case '?':
			diag();
			input_state = NORMAL;
			break;
		    case '\002':	/* C-b */
		    case '\017':	/* C-o */
		    case '\020':	/* C-p */
		    case '\027':	/* C-w */
			if (input_char == '\002')
			    desc = "Buffer";
			else if (input_char == '\017')
			    desc = "Discard";
			else if (input_char == '\020')
			    desc = "Print";
			else if (input_char == '\027')
			    desc = "Stop";
			nmode = input_char + 0140;
			if (cur < 0) {
			    defmode = nmode;
			    sprintf(buf,
			    	    "%s[%s output by default]%s",
				    bos, desc, ceoln);
			} else {
			    mode[cur] = nmode;
			    pmode[cur] = nmode;
			    sprintf(buf,
			    	    "%s[%s output from process %c]%s",
				    bos, desc, name[cur], ceoln);
			}
			term_msg(buf);
			input_state = NORMAL;
			break;
		    case '\011':	/* C-i */
			if (cur >= 0) {
			    kkk = bufmod[cur];
			    if (kkk == 0) {
				desc = "Actv Buffered";
				bufmod[cur] = 1;
			    }
			    if (kkk != 0) {
				desc = "Actv Unbuffered";
				bufmod[cur] = 0;
			    }
			    /* Hack to keep current mode */
			    nmode = 'b';
			}
			if (cur < 0) {
			    defmode = nmode;
			} else {
			    sprintf(buf,
			    	    "%s[%s output from process %c]%s",
				    bos, desc, name[cur], ceoln);
			}
			term_msg(buf);
			input_state = NORMAL;
			break;
		    case '\012':	/* C-j */
			if (cur >= 0) {
			    kkk = bufmod2[cur];
			    if (kkk == 0) {
				desc = "Multshot Buffered";
				bufmod2[cur] = 1;
			    }
			    if (kkk != 0) {
				desc = "Multshot Unbuffered";
				bufmod2[cur] = 0;
			    }
			    /* Hack to keep current mode */
			    nmode = 'b';
			}
			if (cur < 0) {
			    defmode = nmode;
			} else {
			    sprintf(buf, "%s[%s output from process %c]%s",
			    	    bos, desc, name[cur], ceoln);
			}
			term_msg(buf);
			input_state = NORMAL;
			break;
		    case '\014':	/* C-l */
			if (cur >= 0) {
			    kkk = logmod[cur];
			    if (kkk == 0) {
				desc = "Logging enabled";
				start_logging(cur);
			    }
			    if (kkk != 0) {
				desc = "Logging disabled";
				logmod[cur] = 0;
				/* do not close logfile yet, might get enable
				 * later */
			    }
			    /* Hack to keep current mode */
			    nmode = 'b';
			}
			if (cur < 0) {
			    defmode = nmode;
			} else {
			    sprintf(buf, "%s[%s output from process %c]%s",
			    	    bos, desc, name[cur], ceoln);
			}
			term_msg(buf);
			input_state = NORMAL;
			break;
		    case '\177':	/* C-? */
			input_state = NORMAL;
			break;
		    default:
			nname = toupper(input_char);
			if (nname >= first_name && nname <= last_name) {
			    /* fill in any "cut/paste" text being brought in
			     * from other proc */
			    n = cur;
			    /* Zero text string if not being used */
			    trnlnm_string[0] = '\0';
			    if (n >= 0 && cutpas[n] > 0) {
				trnlnm_string_len = 0;
				/* Paste last n lines (up to max in buffer)
				 * back */
				/* We are assured the dest. process number is
				 * legal here */
				k = 0;
				for (kkkk = 0; kkkk < buflen[n]; kkkk++) {
				    if (buffer[n][kkkk] == '\n') {
					k++;
				    }
				}
				/* Count CR's (newlines) in buffer, then go
				 * back by n of them and copy from there to
				 * the end of buffer into the string that
				 * would be used for boss_stuff work.
				 * This avoids another mechanism. */
				kkk = k - cutpas[n];
				if (kkk < 0)
				    kkk = 0;
				/* reset save amount for next time */
				cutpas[n] = 0;
				kk = buflen[n] + 100;
				k = 0;
				for (kkkk = 0; kkkk < buflen[n]; kkkk++) {
				    if (buffer[n][kkkk] == '\n') {
					k++;
					if (k == kkk)
					    kk = kkkk;
				    }
				}
				/* kk is now index into start of cut/paste
				 * area, buflen[n] = end */
				/* guard against empty buffer */
				trnlnm_string_len = 0;
				if (kk < buflen[n]) {
				    for (kkkk = kk; kkkk < buflen[n]; kkkk++) {
					trnlnm_string[trnlnm_string_len++] =
					    buffer[n][kkkk];
				    }
				}
				trnlnm_string[trnlnm_string_len] = '\0';
			    }
			    /* end cut/paste */

			    mov_to(nname, uw ? 0 : input_char < 'a',
				   trnlnm_string,
				   switch_create ? defproc : SWITCH);
			} else
			    term_msg("\007");
			input_state = NORMAL;
			break;
		}
		break;
	    case CREATE:
	    case TOP:
		switch (input_char) {
		    case '\016':	/* C-n */
			input_state = CREATE;
			break;
		    case '\024':	/* C-t */
			input_state = TOP;
			break;
		    case '\010':	/* C-h */
			print_help();
			input_state = NORMAL;
			break;
		    case '?':
			diag();
			input_state = NORMAL;
			break;
		    case '\177':	/* C-? */
			input_state = NORMAL;
			break;
		    default:
			nname = toupper(input_char);
			if (nname >= first_name && nname <= last_name) {
			    /* fill in any "cut/paste" text being brought in
			     * from other proc */
			    n = cur;
			    /* Zero text string if not being used */
			    trnlnm_string[0] = '\0';
			    if (n >= 0 && cutpas[n] > 0) {
				trnlnm_string_len = 0;
				/* Paste last n lines (up to max in buffer)
				 * back. We are assured the dest. process
				 * number is legal here */
				k = 0;
				for (kkkk = 0; kkkk < buflen[n]; kkkk++) {
				    if (buffer[n][kkkk] == '\n') {
					k++;
				    }
				}
				/* Count LF's (newlines) in buffer, then go
				 * back by n of them and copy from there to
				 * the end of buffer into the string that
				 * would be used for boss_stuff work.
				 * This avoids another mechanism. */
				kkk = k - cutpas[n];
				if (kkk < 0)
				    kkk = 0;
				/* reset save amount for next time */
				cutpas[n] = 0;
				k = 0;
				kk = buflen[n] + 100;
				for (kkkk = 0; kkkk < buflen[n]; kkkk++) {
				    if (buffer[n][kkkk] == '\n') {
					k++;
					if (k == kkk)
					    kk = kkkk;
				    }
				}
				/* kk is now index into start of cut/paste
				 * area, buflen[n] = end */
				/* guard against empty buffer */
				trnlnm_string_len = 0;
				if (kk < buflen[n]) {
				    for (kkkk = kk; kkkk < buflen[n]; kkkk++) {
					trnlnm_string[trnlnm_string_len++] =
					    buffer[n][kkkk];
				    }
				}
				trnlnm_string[trnlnm_string_len] = '\0';
			    }
			    /* end cut/paste */
			    mov_to(nname, uw ? 0 : input_char < 'a',
				   trnlnm_string, input_state);
			} else
			    term_msg("\007");
			input_state = NORMAL;
			break;
		}
		break;
	    case END:
		if (toupper(input_char) == 'Y') {
		    term_msg(" Yes\r");
		    exit(SS$_NORMAL);
		} else {
		    term_msg(" No\r\n");
		}
		input_state = NORMAL;
		break;
	    case CUTP:
		/* kprc has proc. number to cut from */
		kkk = input_char;
		/* Make sure char is 1 to 9, else discard and ignore */
		/* Add small bit to allow multiple digits of count */
		if (kkk > 48 && kkk < 58) {
		    kk = cutpas[kprc] * 10;
		    cutpas[kprc] = kk + kkk - 48;
		    input_state = CUTP;
		} else {
		    input_state = NORMAL;
		}
		break;
	    case KILL:
		if (toupper(input_char) == 'Y') {
		    if (kill_proc >= 0 && name[kill_proc] > 0) {
			if (proc_type[kill_proc] == TOP) {
			    sprintf(buf,
			    	"%s[Can't kill top-level process %c]%s\007",
				bos, name[kill_proc], ceoln);
			} else {
			    st = SYS$DELPRC(&pid[kill_proc], 0);
			    if (bad(st))
				sprintf(buf, "%s[Couldn't kill process %c]%s",
					bos, name[kill_proc], ceoln);
			    else
				sprintf(buf, "%s[Killed process %c]%s",
					bos, name[kill_proc], ceoln);
			}
		    } else
			sprintf(buf, "%s[Process already killed]%s",
				bos, ceoln);
		    term_msg(buf);
		} else {
		    term_msg(" No\r\n");
		}
		input_state = NORMAL;
		kill_proc = -1;
		break;
	}
    }
    /* re-post read AST on real term */
    /* but only if really on_AST_level */
    if (on_AST_level) {
	check(SYS$QIO(0, tt_chn, IO$_READVBLK, &tiosb, &tt_srv, TRUE,
		      &input_char, 1, 0, 0, 0, 0));
    }
}

uw_fun(code, arg, count)
    char code, arg;
    int count;
{
    term_buf[0] = P1_IAC;
    term_buf[1] = P1_DIR_HTOM | code;
    if (count > 0)
	term_buf[2] = arg;
    check(SYS$QIOW(0, tt_chn, IO$_WRITEVBLK, &tiosb, 0, 0,
    		   term_buf, 2 + count, 0, 0, 0, 0));
}

get_tt_info()
{
    $DESCRIPTOR(d_tt, "SYS$COMMAND");
    /* Get a channel & mailbox of terminal */
    check(LIB$ASN_WTH_MBX(&d_tt, &TTMBSIZ, &TTMAXSIZ, &tt_chn, &tt_mb_chn));
    /* Get the terminal characteristics. */
    check(SYS$QIOW(0, tt_chn, IO$_SENSEMODE, 0, 0, 0,
    		   &tt_chr, TTCHRLEN, 0, 0, 0, 0));
    tt_sav_chr = tt_chr;
    tt_chr.ttchr |= TT$M_NOECHO;/* term will be Noecho */
    tt_chr.ttchr &= ~TT$M_ESCAPE;	/* no escape */
    tt_chr.ttchr &= ~TT$M_HOSTSYNC;	/* no host sync */
    if (flow_control)
	tt_chr.ttchr |= TT$M_TTSYNC;	/* do sync at BOSS level */
    else
	tt_chr.ttchr &= ~TT$M_TTSYNC;	/* do sync at subprocess level */
    tt_chr.xchar |= TT2$M_PASTHRU;	/* it will be PASTRHU */
    if (brkthru) {
	tt_chr.ttchr |= TT$M_MBXDSABL;	/* no hangup messages */
	tt_chr.ttchr |= TT$M_NOBRDCST;	/* disable direct broadcast */
	tt_chr.xchar |= TT2$M_BRDCSTMBX;	/* send them to mailbox
						 * instead */
    }
    check(SYS$QIOW(0, tt_chn, IO$_SETMODE, 0, 0, 0,
    		   &tt_chr, TTCHRLEN, 0, 0, 0, 0));
}

fix_a_tp(n)			/* Set up a Pseudo term */
    int n;
{
    int dev_depend, tp_chn;
    struct CHARBLK tw_chr;
    struct IOSBBLK iosb;
    struct DVIBLK dvi_stuff = {4, DVI$_DEVDEPEND, &dev_depend, 0, 0};

    $DESCRIPTOR(d_pynam, "PYA0:");	/* Template. */
    $DESCRIPTOR(d_finaltp, &finaltp[n]);
    /* Assign a mailbox to PYA */
    check(LIB$ASN_WTH_MBX(&d_pynam, &MBSIZ, &MAXSIZ, &py_chn[n],
    			  &py_mb_chn[n]));
    /* Use $GETDVI to get the device dependent characteristics, which
     * contains the associated terminal device's unit number. */
    check(SYS$GETDVI(0, py_chn[n], 0, &dvi_stuff, &iosb, 0, 0, 0));
    check(iosb.stats);
    tw_chr = tt_sav_chr;
    tw_chr.xchar |= TT2$M_HANGUP;
    sprintf(&finaltp[n], "TWA%d:", dev_depend);
    d_finaltp.dsc$w_length = strlen(&finaltp[n]);
    /* Get a channel on this TWA */
    if (bad(SYS$ASSIGN(&d_finaltp, &tp_chn, 0, 0))) {
	sprintf(&finaltp[n], "TPA%d:", dev_depend);	/* TWA doesn't work;
							 * try TPA */
	d_finaltp.dsc$w_length = strlen(&finaltp[n]);
	check(SYS$ASSIGN(&d_finaltp, &tp_chn, 0, 0));
    }
    if (no_phy_io)
	check(SYS$SETPRV(1, &priv, 0, 0));
    /* Make it look like a terminal */
    if (bad(SYS$QIOW(0, tp_chn, IO$_SETCHAR, 0, 0, 0,	/* This needs PHY_IO
							 * priv */
		     &tw_chr, TTCHRLEN, 0, 0, 0, 0)))
	check(SYS$QIOW(0, tp_chn, IO$_SETMODE, 0, 0, 0,
		       &tw_chr, TTCHRLEN, 0, 0, 0, 0));
    if (no_phy_io)
	check(SYS$SETPRV(0, &priv, 0, 0));
    check(SYS$DASSGN(tp_chn));	/* We don't need it. only the mailbox */
    /* in fact keeping it kills us. */
}

broadcast_handler()
{				/* handle broadcasts to BOSS */
    int j, len;

    $DESCRIPTOR(d_tt_mb, tt_mb);
    $DESCRIPTOR(d_finaltp, finaltp[cur]);

    check(tiosbmb.stats);	/* Check status */
    len = ((0377 & tt_mb[21]) << 8) + (0377 & tt_mb[20]); /* message length */
    if (cur < 0) {
	term_msg("\r\n");
	to_term(&(tt_mb[22]), len, 1);
	term_msg("\r");
    } else {
	d_tt_mb.dsc$w_length = len;
	d_tt_mb.dsc$a_pointer = &(tt_mb[22]);
	if (no_oper)
	    check(SYS$SETPRV(1, &privs, 0, 0));
	check(SYS$BRKTHRU(0, &d_tt_mb, &d_finaltp,
			  BRK$C_DEVICE, 0, 32, 0, BRK$C_USER16, 0, 0, 0));
	if (no_oper)
	    check(SYS$SETPRV(0, &privs, 0, 0));
    }
    check(SYS$QIO(0, tt_mb_chn, IO$_READVBLK, &tiosbmb, &broadcast_handler, 0,
		  &tt_mb, TTMBSIZ, 0, 0, 0, 0));
}

post_term_reads()		/* Read AST on real term */
{
    if (brkthru) {
	check(SYS$QIO(0, tt_mb_chn, IO$_READVBLK, &tiosbmb,
		      &broadcast_handler, 0, &tt_mb, TTMBSIZ, 0, 0, 0, 0));
    } else {
	check(SYS$QIO(0, tt_mb_chn, IO$_READVBLK, &tiosbmb, 0, 0,
		      &tt_mb, TTMBSIZ, 0, 0, 0, 0));
    }
    check(SYS$QIO(0, tt_chn, IO$_READVBLK, &tiosb, &tt_srv, TRUE,
		  &input_char, 1, 0, 0, 0, 0));
}

post_pty_reads(n)		/* Post read AST on Pseudo-term */
    int n;
{
    char cr = '\r';

    if (init)
	return (0);
    py_post[n] = 1;
    check(SYS$QIO(0, py_mb_chn[n], IO$_READVBLK, &miosb[n], &mb_srv, n,
		  &py_mb[n], MBSIZ, 0, 0, 0, 0));
    check(SYS$QIO(0, py_chn[n], IO$_READVBLK, &piosb[n], &py_srv, n,
		  &tpline[n], LINESZ, 0, 0, 0, 0));
    if (proc_type[n] == TOP) {
	check(SYS$QIOW(0, py_chn[n], IO$_WRITEVBLK, &tiosb, 0, 0,
		       &cr, 1, 0, 0, 0, 0));
	if (tiosb.stats != SS$_DATAOVERUN)
	    check(tiosb.stats);
    }
}

int
fire_up(n, nname, proc_mode)	/* Fire up subprocess n */
    int n, proc_mode;
    char nname;
{
    int val;
    name[n] = nname;
    procno[nname - first_name] = n;
    count[n] = 0;		/* Initialize buffer count */
    blocked[n] = 0;		/* It starts unblocked */
    bufmod[n] = 0;		/* Starts w/o buffering all */
    bufmod2[n] = 0;
    logmod[n] = 0;		/* no logging initially */
    logfd[n] = 0;		/* no fd either */
    py_post[n] = 0;
    mode[n] = defmode;
    pmode[n] = defmode;
    buflen[n] = 0;
    proc_type[n] = proc_mode;
    enable_hangup[n] = 0;
    pid[n] = 0;
    fix_a_tp(n);		/* Set a pseudo terminal by TT info */
    check(SYS$CANCEL(py_chn[n]));   /* Don't need this Half of pseudo-ter */
    val = (proc_type[n] == TOP) ? 1 :
	low_lib_spawn(n, &finaltp[n], &pid[n], name[n]); /* Spawn a subprocess */
    if (!bad(val)) {
	if (auto_log) start_logging(n);	/* if requested by /LOG */
	post_pty_reads(n);	/* Set up AST */
	if (uw && !mac_command)
	    uw_fun(P1_FN_NEWW | ((n + 1) & P1_WINDOW), 0, 0);
    } else
	comp_srv(n);		/* Mark the process as non-existent */
    return (val);
}

initialize()
{				/* Initialize everything */
    int j, item;
    $DESCRIPTOR(d_boss_id, "BOSS$ID");
    $DESCRIPTOR(d_lnm_process, "LNM$PROCESS");

    nalph = last_name - first_name + 1;
    for (j = 0; j < nproc; j++)
	name[j] = '\0';		/* Initialize variables */
    for (j = 0; j < nalph; j++)
	procno[j] = -1;
    /* Save old value of BOSS$ID */
    j = SYS$TRNLNM(0, &d_lnm_process, &d_boss_id,
    		   &super_ac_mode, &trnlnm_item);
    if (!bad(j) && trnlnm_string_len == 1) {
	oboss_id = trnlnm_string[0];
	j = LIB$DELETE_LOGICAL(&d_boss_id, 0);
    }
    item = JPI$_PROCPRIV;	/* Check whether have PHY_IO & OPER */
    check(LIB$GETJPI(&item, 0, 0, &priv, 0, 0));
    no_phy_io = !(priv[0] & PRV$M_PHY_IO);
    no_oper = !(priv[0] & PRV$M_OPER);
    item = JPI$_IMAGPRIV;	/* Check whether we can do BRKTHRU */
    check(LIB$GETJPI(&item, 0, 0, &priv, 0, 0));
    brkthru = ((priv[0] & PRV$M_OPER) || !no_oper);
    priv[0] = PRV$M_PHY_IO;
    priv[1] = 0;
    privs[0] = PRV$M_OPER;
    privs[1] = 0;
    if (no_phy_io)
	check(SYS$SETPRV(0, &priv, 0, 0));
    if (no_oper)
	check(SYS$SETPRV(0, &privs, 0, 0));
    get_tt_info();		/* Initialize terminal */
    if (uw)
	uw_fun(P1_FN_MAINT | P1_MF_ENTRY, 0, 0);
    start_up();			/* Start up processes */
    if (recording) {
	record_fd = creat(record_file, 0, "mbf=3");
	if (record_fd < 0) {
	    perror(record_file);
	    exit(0);
	}
	startclock();
    }
    if (playback) {
	playback_fd = open(playback_file, O_RDONLY, 0, "mbf=3");
	if (playback_fd < 0) {
	    perror(playback_file);
	    exit(0);
	}
    }
    post_term_reads();
}

/* Next two routines taken from FILE program by Joe Meadows Jr. */

long int 
cli_present(s)
    char *s;
{
    static struct dsc$descriptor s_desc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};

    s_desc.dsc$w_length = strlen(s);
    s_desc.dsc$a_pointer = s;
    return (cli$present(&s_desc));
}

long int 
cli_get_value(s1, s2)
    char *s1, **s2;
{
    static struct dsc$descriptor s1_desc =
    	{0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
    static struct dsc$descriptor s2_desc =
    	{0, DSC$K_DTYPE_T, DSC$K_CLASS_D, 0};
    static char null = '\0';
    static struct dsc$descriptor null_desc =
    	{1, DSC$K_DTYPE_T, DSC$K_CLASS_S, &null};
    long int status;

    s1_desc.dsc$w_length = strlen(s1);
    s1_desc.dsc$a_pointer = s1;

    status = cli$get_value(&s1_desc, &s2_desc);

    if (status & 1) {
	str$append(&s2_desc, &null_desc);
	*s2 = s2_desc.dsc$a_pointer;
    } else
	*s2 = 0;
    return (status);
}

process_command_line()
{
    long int status, boss_len;
    short int length;
    $DESCRIPTOR(d_line, buf);
    $DESCRIPTOR(d_cldline, buf);
    $DESCRIPTOR(d_boss_term, "BOSS$TERM");
    $DESCRIPTOR(d_lnm_file_dev, "LNM$FILE_DEV");

    strcpy(buf, "BOSS ");
    boss_len = strlen(buf);
    d_line.dsc$w_length = d_line.dsc$w_length - boss_len;
    d_line.dsc$a_pointer = d_line.dsc$a_pointer + boss_len;
    check(lib$get_foreign(&d_line, 0, &length, 0));
    buf[length + boss_len] = '\0';
    d_cldline.dsc$w_length = length + boss_len;
    status = cli$dcl_parse(&d_cldline, BOSS_CLD, 0, 0, 0);
    if (bad(status))
	exit(STS$K_ERROR + STS$M_INHIB_MSG);

    status = cli_present("UW");
    uw = !bad(status);

    if (!uw) {
	nproc = NPROCMAX;
	defmode = 'b';
	first_name = 'A';
	last_name = 'Z';
    } else {
	nproc = P1_NWINDOW;
	defmode = 'p';
	first_name = '1';
	last_name = '7';
    }

    status = cli_get_value("COMMAND_CHARACTER", &retval);
    if (bad(status)) {
	if (!uw)
	    ctlchar = 034;	/* C-\ */
	else
	    ctlchar = -1;
    } else
	sscanf(retval, "%d", &ctlchar);
    if (ctlchar < (uw ? -1 : 0) || ctlchar > 0177 || ctlchar == 032) {
	printf("[Illegal command character (%d); using C-\\ instead]\n",
	       ctlchar);
	ctlchar = 034;		/* C-\ */
	/* disallow C-z as a command character */
    }
    if (ctlchar >= 0 && ctlchar < 040)
	sprintf(ctlchar_str, "C-%c", tolower(ctlchar + 0100));
    else if (ctlchar == 040)
	strcpy(ctlchar_str, "SPC");
    else if (ctlchar == 0177)
	strcpy(ctlchar_str, "DEL");
    else if (ctlchar == -1)
	strcpy(ctlchar_str, "---");
    else
	sprintf(ctlchar_str, "%c", ctlchar);

    status = cli_get_value("BEGIN_PROMPT", &retval);
    if (bad(status))
	strcpy(prompt_begin, "");
    else
	strcpy(prompt_begin, retval);

    status = cli_get_value("END_PROMPT", &retval);
    if (bad(status))
	strcpy(prompt_end, "");
    else
	strcpy(prompt_end, retval);

    status = cli_get_value("DEFAULT_OUTPUT_FLAG", &retval);
    if (!bad(status))
	defmode = tolower(retval[0]);

    status = cli_get_value("PROCESS_DEFAULT", &retval);
    if (bad(status))
	defproc = CREATE;
    else
	defproc = ((retval[0] == 'T') ? TOP : CREATE);

    status = cli_present("SWITCH_CREATE");
    switch_create = !bad(status);

    status = cli_present("FLOW_CONTROL");
    flow_control = !bad(status) || uw;

    status = cli_get_value("DELETE_CHARACTER", &retval);
    if (bad(status))
	delete_char = 0177;
    else
	sscanf(retval, "%d", &delete_char);
    if (delete_char < 0 || delete_char > 0177) {
	printf("[Illegal delete character (%d); using DEL instead]\n",
	       delete_char);
	delete_char = 0177;
    }
    status = cli_get_value("AUTO_STUFF_STRING", &retval);
    if (bad(status))
	def_stuff_len = 0;
    else {
	strcpy(def_stuff_buf, retval);
	if (strlen(retval) > 0)
	    strcat(def_stuff_buf, "\015");
	def_stuff_len = strlen(def_stuff_buf);
    }

    status = cli_present("RECORD");
    recording = !bad(status);
    if (recording) {
	status = cli_get_value("RECORD", &retval);
	if (bad(status)) {
	    strcpy(record_file, "BOSS.RECORD");
	} else {
	    strcpy(record_file, retval);
	}
    }
    status = cli_present("PLAYBACK");
    playback = !bad(status);
    if (playback) {
	status = cli_get_value("PLAYBACK", &retval);
	if (bad(status)) {
	    strcpy(playback_file, "BOSS.RECORD");
	} else {
	    strcpy(playback_file, retval);
	}
    }
    status = cli_present("GEAR");
    if (!bad(status)) {
	status = cli_get_value("GEAR", &retval);
	if (!bad(status)) {
	    char *cp = retval;
	    while (*cp) {
		*cp = tolower(*cp);
		cp++;
	    }
	    if (!strncmp(retval, "infinite", strlen(retval))) {
		printf("[Boss: no delays in playback]\n");
		gear = -1.0;
	    } else {
		gear = atof(retval);
		if (gear <= 0.0) {
		    printf("[Boss: bad /GEAR value, assuming 1.0]\n");
		    gear = 1.0;
		}
	    }
	}
    }
    status = cli_present("LOCK");
    keyboard_locked = !bad(status);

    status = cli_present("LOG");
    auto_log = !bad(status);
    if (auto_log) {
	status = cli_get_value("LOG", &retval);
	if (bad(status)) {
	    strcpy(log_file_prefix, "BOSS");
	    log_file_prefix_len = 4;
	} else {
	    /* watch out for overflow, need room for "x.log" */
	    log_file_prefix_len = strlen(retval);
	    if (log_file_prefix_len >= sizeof(log_file_prefix) - 5) {
		printf("[Boss: log file prefix too long, ignored]\n");
		strcpy(log_file_prefix, "BOSS");
		log_file_prefix_len = 4;
	    } else {
		strcpy(log_file_prefix, retval);
	    }
	}
    }

    status = cli_present("QUIT_ON_IDLE");
    synchr_quit = !bad(status);

    status = SYS$TRNLNM(0, &d_lnm_file_dev, &d_boss_term, 0, &trnlnm_item);
    if (bad(status))
	strcpy(trnlnm_string, "VT100");
    else
	trnlnm_string[trnlnm_string_len] = '\0';
    if (strcmp(trnlnm_string, "VT100") == 0) {	/* VT100 */
	clr = "\033[r\033[4l\033[H\033[2J";	/* Clear screen reset scroll */
	bos = "\033[r\033[4l\033[99;1H\n";	/* Go to bottom of screen */
	ceol = "\033[K";	/* Clear to end-of-line */
	ceoln = "\033[K\r\n";	/* Clear to end-of-line and newline */
    } else if (strcmp(trnlnm_string, "VT52") == 0) {	/* VT52 */
	clr = "\033H\033J";
	bos = "\033Y7 \n";
	ceol = "\033K";
	ceoln = "\033K\r\n";
    } else if (strcmp(trnlnm_string, "ADM3A") == 0) {	/* ADM3A */
	clr = "\032";
	bos = "\033=7 \n";
	ceol = "   \010\010\010";
	ceoln = "   \r\n";
    } else {			/* UNKNOWN */
	clr = "\r\n";
	bos = "\r\n";
	ceol = "";
	ceoln = "\r\n";
    }
}

int 
start_up()
{
    long int status, j, n;
    char nname[30], output_flags[30], stuff_flag, odefmode;

    cur = -1;
    if (!uw)
	input_state = PENDING;
    init = 1;

    status = cli_present("START_PROCESS");
    if (!bad(status)) {
	stuff_flag = 1;
	j = 0;
	while (j < 30 && !bad(cli_get_value("START_PROCESS", &retval))) {
	    if (strlen(retval) != 1)
		break;
	    nname[j] = toupper(retval[0]);
	    if (nname[j] < first_name || nname[j] > last_name)
		break;
	    j++;
	}
	n = j;
	j = 0;
	while (j < n && !bad(cli_get_value("OUTPUT_FLAGS", &retval))) {
	    output_flags[j] = tolower(retval[0]);
	    j++;
	}
	while (j < n) {
	    output_flags[j] = defmode;
	    j++;
	}
	for (j = 0; j < n; j++) {
	    if (stuff_flag) {
		status = cli_get_value("STUFF_STRING", &retval);
		if (bad(status)) {
		    stuff_flag = 0;
		    strcpy(stuff_buf, "");
		} else {
		    strcpy(stuff_buf, retval);
		    if (strlen(retval) > 0)
			strcat(stuff_buf, "\015");
		}
	    }
	    odefmode = defmode;
	    defmode = output_flags[j];
	    status = mov_to(nname[j], 0, stuff_buf, defproc);
	    defmode = odefmode;
	    if (bad(status))
		break;
	    input_state = NORMAL;
	}
    }
    init = 0;
    for (j = 0; j < nproc; j++)
	if (name[j] > 0)
	    post_pty_reads(j);
}

main()
{
    int exit_handler[4] = {0, quit, 0, &st};

    process_command_line();
    check(SYS$DCLEXH(&exit_handler));	/* Define Exit handler (quit) */
    if (ctlchar >= 0 && ctlchar < 040)
	printf("Begin BOSS %s\nType control-%c control-h for information\n",
	       VERSION, tolower(ctlchar + 0100));
    else if (ctlchar < 0)
	printf("Begin BOSS %s\n", VERSION);
    else
	printf("Begin BOSS %s\nType %s control-h for information\n",
	       VERSION, ctlchar_str);
    initialize();
    if (playback) {
	/* playback mode: feed data from record file to tt_srv() */
	/* keep checking playback - tt_srv ASTs may reset it! */
	int i, j;
	char buf[80];
	char *endbufp = &buf[80];

	tiosb.stats = SS$_NORMAL;	/* fool tt_srv */
	while (1 == 1) {
	    if (!playback)
		break;
	    i = read(playback_fd, &input_char, 1);
	    if (!i)
		break;
	    if (input_char != DELAY_ESC) {
		/* normal char, process */
		tt_srv(FALSE);
	    } else {
		/* the escape char: read more into buf */
		i = read(playback_fd, &buf, 1);
		if (!i)
		    break;
		if (buf[0] == DELAY_ESC) {
		    /* double escape: feed one */
		    input_char = DELAY_ESC;
		    tt_srv(FALSE);
		} else if (buf[0] != '\n') {
		    /* read a delay spec */
		    int delay;
		    char *bufp = &buf[1];
		    while ((i = read(playback_fd, bufp, 1)) && (*bufp != '\n')) {
			bufp++;
			if (bufp == endbufp) {	/* overflow */
			    i = 0;
			    break;
			}
		    }
		    if (!i)
			break;	/* eof or overflow: error */
		    *bufp = '\0';
		    if (gear >= 0.0) {		/* ignore delay if gear<0 */
			delay = atoi(buf);	/* get in 0.1 sec units */
		        delay = (int) (delay * 100.0 / gear);
			msleep(delay);
		    }
		}
	    }
	}			/* end while */
	close(playback_fd);
	/* playback ended, switch to interactive mode if no quit yet */
	playback = 0;
	sprintf(buf, "%s[Boss: playback ended]%s", bos, ceoln);
	term_msg(buf);
	/* AST on real terminal already outstanding */
	/* Unlock keyboard, fall through into interactive mode */
	keyboard_locked = FALSE;
    }				/* end if (playback) */
    /* Normal mode: driven by ASTs to tt_srv() */
    sys$hiber();
}
