/*
Glenn,
 
	Attached is the logger souce code with the DEC copyright notice
removed.  I am not sure exacly how helpful it will be but have fun with it.  I
have toyed with the idea of extending to multiple session but never got around
to it.  It has the following known problems:
 
	1) If the user has privilege to alter the attached terminal but no the 
	   final terminal it will dump them with an fatal error.
 
	2) If the user alters parity again it will dump you out with an error.
 
 
Forrest
*/

/*
*
* ++
* FACILITY:
*
*	PTD
*
* ABSTRACT: 
*		Simple program to demonstrate using a Pseudo Terminal.  This
*	program is based loosely on the popular PHOTO program that is available
*	on the ARPA net.  The program is discussed in Appendix B of the pseudo
*	terminal functional sepcification.
*
*
* AUTHOR: Forrest A. Kenney	01-Nov-1989
*
* Revision history:
*
*	X-XX	XXX000 		Xxxxxxx X. Xxxxxx		xx-Xxx-XXXX
*		Description of change.
*
* --
*
* Link Command File example
*
*	$ !
*	$ ! Command file to link logger program
*	$ !
*	$ link LOGGER/MAP=LOGGER sys$input:/opt
*	!
* 	sys$system:sys.stb/selective
*	sys$share:vaxcrtl/share
*	$ exit
*
* --
*/

 
 
/* Define constants */
 
#define	BELL			0x7
#define BUFFIO_OVERHEAD		48	/* Overhead of TERMINAL buffered I/O  */
#define	CR			0x0D
#define CHAR_BUF_SIZE		492	/* Space in I/O buffer for data       */
#define	FALSE			0
#define	IO_BUFFERS		6	/* Number of one page I/O buffers     */
#define	LF			0x0A
#define	NULL			0
#define	NOWAIT			1	/* No wait flag for LIB$SPAWN 	      */
#define	PAGE			0X1FF	/* Last byte in a page 		      */
#define	PAGE_SIZE		0X200	/* Size of a page in bytes 	      */
#define TRUE			1
#define	XOFF			0x13
#define	XON			0x11
 
#define PTD$C_SEND_XON		0	/* Pseudo Terminal Driver event       */
#define PTD$C_SEND_BELL		1	/* types. When these are in           */
#define PTD$C_SEND_XOFF 	2	/* SYS$LIBRARY:VAXCDEF.TLB they       */
#define PTD$C_STOP_OUTPUT	3	/* should be removed from here.	      */
#define PTD$C_RESUME_OUTPUT 	4
#define PTD$C_CHAR_CHANGED 	5
#define PTD$C_ABORT_OUTPUT 	6
#define PTD$C_START_READ 	7
#define PTD$C_MIDDLE_READ 	8
#define PTD$C_END_READ 		9
#define PTD$C_ENABLE_READ 	10
#define PTD$C_DISABLE_READ 	11
#define PTD$C_MAX_EVENTS 	12

 
 
/* Include various necessary description files */
 
#include	descrip
#include	dvidef
#include	iodef
#include	libdef
#include	rms
#include	ssdef
#include	stdio
#include	ttdef
#include	tt2def

 
 
/* Define several gloabl structures */
 
struct	dev_char			/* Device characteristics block       */
{
unsigned char	class;
unsigned char	type;
short	int	buffer_size;
unsigned int	basic_chars;
unsigned int	extended_chars;
};
 
struct	iosb				/* Standard I/O status block	      */
{
short	int	status;
short	int	byte_cnt;
int		unused;
};
 
struct	sense_iosb			/* IOSB for set and sense requests    */
{
short	int	status;
unsigned char	xmit_speed;
unsigned char	rcv_speed;
unsigned char	cr_fill;
unsigned char	lf_fill;
unsigned char	parity_flags;
unsigned char	unused;
};
 
struct	io_buff				/* I/O block used by logger code      */
{
unsigned int	flink; 			/* forward and backard queue links    */
unsigned int	blink;
short	int	status;			/* IOSB used to terminal requests     */
short	int	byte_cnt;
	int	unused;
short	int	io_status;		/* Status longword used by pseudo     */
short	int	io_byte_cnt;		/* terminal control requests	      */
char		data[CHAR_BUF_SIZE];	/* Data buffer			      */
};
 
struct	q_head				/* Queue head structure		      */
{
int			flink;
int			blink;
};

/* Forward routine references */
 
int			initialization();
int			create_log_file();
int			create_pseudo_terminal();
int			setup_tty();
 
struct	io_buff		*allocate_io_buffer();
 
void			bell_ast();
void			free_io_buffer();
void			ft_echo_ast();
void			ft_read_ast();
void			kbd_read_ast();
void			set_line_ast();
void			subprocess_exit();
void			terminal_output_ast();
void			xoff_ast();
void			xon_ast();

/* Global Variables*/
 
char			*rec_buffer;
char			*char_pos;
 
short	int 		char_count;
short	int		cr_seen = FALSE; 
short	int		exiting	= FALSE; 
short	int		ft_chan;
short	int		have_subprocess = TRUE;
short	int		read_stopped = FALSE;	
short	int		tty_chan;
 
int			exit_status;
int			pid;
int			term_mask[8] = {0, 0, 0, 0, 0, 0, 0, 0};
 
globalref	short int	IOC$GW_MAXBUF;
 
struct	FAB		logger_fab;
struct	RAB		logger_rab;
struct	dev_char	starting_chars;
struct	sense_iosb	starting_iosb;
struct	io_buff		*tty_r_buff;
struct	q_head		 _align(QUADWORD)	buffer_queue = (0,0);
struct	q_head		 _align(QUADWORD)	log_queue = (0,0);
struct	term_descrip
{
short int	size;
short int	unused;
int		*ptr;
} term_block = {32, 0, &term_mask[0]};

/*
**+
** main - Main routine
**
** Functional Description:
**
**	The program intitializes the environment and then hibernates waiting to
** be awakened. When awakened, it checks to see if exiting or if more log data 
** is available.  If more data to log, the data is appended to the current log
** record and checked to see if a log record should be written.  A log record 
** will be written when either maxbuf characters are in the log buffer, or a 
** <CR><LF> character pair are seen.  The algorithm allows an unlimited
** number of <NULL> fill characters to occur between the <CR> and the
** <LF>. If exiting the program closes the log file, deletes the pseudo 
** terminal, resets terminal, and exits.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	char_pos	-	Point to next available character position in
**				rec_buffer
**	char_count	-	Number of characters presently in rec_buffer
**	cr_seen		-	Flag indication if last significant character
**				seen was a <CR>
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	have_subprocess	-	Flag indicating if subprocess is still running
**	IOC$GW_MAXBUF	-	System cell containing value of largest legal
**				buffered I/O
**	log_queue	-	Queue of I/O buffers to be written to log file 
**	logger_fab	-	File Access Block of log file
**	logger_rab	-	Record Access Block of log file
**	pid		-	Process ID of subprocess
**	rec_buffer	-	Pointer to buffer holding characters to be
**				written to the log file
**	starting_chars	-	Terminal characteristics when logger started up
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	buffer_pos	-	Character position in current log buffer
**	got_buf_status	-	Return status from call to remove log buffer
**				from log queue
**	log_buff	-	Pointer to current log buffer being processed
**	set_iosb	-	I/O status block used when resetting terminal
**				to startup characteristics
**	status		-	Return status from various routine calls
**	
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
main()
 
{
 
int			buffer_pos;
int			got_buf_status;
int			status;
 
struct	io_buff		*log_buff;
struct	sense_iosb	set_iosb;
 
status= initialization();
if (status & SS$_NORMAL)
{
   do
   {
      got_buf_status = LIB$REMQHI(&log_queue, &log_buff);
      while (got_buf_status & SS$_NORMAL)
      {
	 for (buffer_pos = 0; buffer_pos < log_buff->io_byte_cnt; buffer_pos++)
	 {
	    if (cr_seen)
	    {
	       if (log_buff->data[buffer_pos] == LF)
	       {
		  logger_rab.rab$w_rsz = char_count;
		  status = SYS$PUT(&logger_rab);
		  if (!(status & SS$_NORMAL)) 
                  {
		     SYS$FORCEX(&pid, 0, 0);
		     LIB$SIGNAL(status);
                  }
		  cr_seen = FALSE;
		  char_count = 0;
		  char_pos = rec_buffer;
	       }
	       else if (log_buff->data[buffer_pos] != NULL)
	       {
		  *char_pos++ = CR;
		  *char_pos++ = log_buff->data[buffer_pos];
		  char_count += 2;
		  cr_seen = FALSE;
	       }
	    }
	    else if (log_buff->data[buffer_pos] != CR)
	    {
	       *char_pos++ = log_buff->data[buffer_pos];
	       char_count += 1;
	    }
	    else
	    {
		cr_seen = TRUE;
	    }
 
	    if (char_count >= (IOC$GW_MAXBUF-BUFFIO_OVERHEAD)) 
	    {
 
	       logger_rab.rab$w_rsz = char_count;
	       status = SYS$PUT(&logger_rab);
	       if (!(status & SS$_NORMAL)) 
               {
		  SYS$FORCEX(&pid, 0, 0);
		  LIB$SIGNAL(status);
               }
	       cr_seen = FALSE;
	       char_count = 0;
	       char_pos = rec_buffer;
	    }
         }
	 free_io_buffer(log_buff);
	 got_buf_status = LIB$REMQHI(&log_queue, &log_buff);
      }
      if (!exiting) SYS$HIBER();
   } while ((!exiting) || (log_queue.flink != 0));
 
   if (char_count != 0)
   {
       logger_rab.rab$w_rsz = char_count;
       status = SYS$PUT(&logger_rab);
      if (!(status & SS$_NORMAL) && (exit_status & SS$_NORMAL)) 
	 exit_status = status;
   }
   status = SYS$CLOSE(&logger_fab);
   if (!(status & SS$_NORMAL) && (exit_status & SS$_NORMAL)) 
      exit_status = status;
   if (have_subprocess) SYS$FORCEX (&pid, 0, 0);
   PTD$CANCEL(ft_chan);
   SYS$CANCEL(tty_chan);
   status = PTD$DELETE(ft_chan);
   if (!(status & SS$_NORMAL) && (exit_status & SS$_NORMAL)) 
      exit_status = status;
   status = SYS$QIOW(0, tty_chan, IO$_SETMODE, &set_iosb, 0, 0, &starting_chars,
		     sizeof(starting_chars), 0, 0, 0, 0);
   if (!(status & SS$_NORMAL) && (exit_status & SS$_NORMAL)) 
      exit_status = status;
   if (!(set_iosb.status & SS$_NORMAL) && (exit_status & SS$_NORMAL))
      exit_status = set_iosb.status;
}
else
{
   exit_status = status;
}
 
LIB$SIGNAL(exit_status);
 
}

/*
**+
** initialization - Set up environment
**
** Functional Description:
**
**	This routine sets the terminal characteristics, creates the pseudo
** terminal, starts up the subprocess, and opens the log file.  If any of these
** steps fails it backs out all steps done up to this points and returns to the
** mainline.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	buffer_queue	-	Queue of free I/O buffer
**	char_pos	-	Point to next available character position in
**				rec_buffer
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	IOC$GW_MAXBUF	-	System cell containing value of largest legal
**				buffered I/O
**	logger_fab	-	File Access Block of log file
**	rec_buffer	-	Pointer to buffer holding characters to be
**				written to the log file
**	starting_chars	-	Terminal characteristics when logger started up
**	starting_iosb	-	IOSB used to hold starting characteristics like
**				terminal speed and parity
**	tty_chan	-	VMS channel pointing to terminal
**	tty_r_buff	-	Pointer to I/O buffer used to read characters
**				from terminal
**
** Local Variables:
**
**	loop		-	Temporary loop counter used will inserting I/O
**				buffers onto queue of free I/O buffers
**	rec_size	-	Local varible used to pass record buffer size
**				to LIB$GET_VM
**	ret_addr	-	Pointer to start and end of pages allocated for
**				I/O buffers
**	status		-	Return status from various routine calls
**	tty_name	-	String descriptor contianing terminal name 
**	
**
** Outputs:
**
**       R0		-	SS$_NORMAL
**				Various error status values
**
** NOTES:
**
**       None
**
**-
*/
 
initialization(void)
 
{
 
$DESCRIPTOR(tty_name,"TT:");
 
int			loop;
int			rec_size;
int			status;
 
struct	mem_region
{
struct	io_buff		*start;
struct	io_buff		*end;
}			ret_addr;
 
rec_size = (int)IOC$GW_MAXBUF;
status = LIB$GET_VM (&rec_size, &rec_buffer);
if (status & SS$_NORMAL)
{
   char_pos = rec_buffer;
   status = SYS$EXPREG (IO_BUFFERS, &ret_addr, 0, 0);
   if (status & SS$_NORMAL)
   {
      tty_r_buff = (char *)ret_addr.end - PAGE;
      for (loop = 0; loop < IO_BUFFERS-1; loop++)
      {
	 free_io_buffer((char *)ret_addr.start + loop*PAGE_SIZE);
      }
      status = SYS$ASSIGN(&tty_name, &tty_chan, 0, 0);
      if (status &SS$_NORMAL)
      {
	 status = SYS$QIOW (0, tty_chan, IO$_SENSEMODE, &starting_iosb, 0, 0,
			    &starting_chars, sizeof(starting_chars), 0, 0, 0, 
			    0);
	 if ((status & SS$_NORMAL) && (starting_iosb.status & SS$_NORMAL))
	 {
	    status = create_pseudo_terminal(&ret_addr);
	    if (status & SS$_NORMAL)
	    {
	        status = create_log_file();
		if (status & SS$_NORMAL)
		{
		   status = setup_tty();
		   if (!(status & SS$_NORMAL))
		   {
		      PTD$DELETE(ft_chan);
		      logger_fab.fab$l_fop = logger_fab.fab$l_fop | FAB$M_DLT;
		      SYS$CLOSE(&logger_fab);
		   }
		}
	        else
		{
		   PTD$DELETE(ft_chan);
		}
	    }
	 }
	 else if (status & SS$_NORMAL)
	 {
	    status = starting_iosb.status;
	 }
      }
   }
}
 
return(status);
 
} 

/*
**+
** setup_tty - Start subprocess and setup terminal 
**
** Functional Description:
**
**
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**	
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	pid		-	Process ID of subprocess
**	starting_chars	-	Terminal characteristics when logger started up
**	term_block	-	Long form to terminal driver read termination
**				mask
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	devnam		-	String descriptor containing name of pseudo
**				terminal
**	flags		-	Flags passed to LIB$SPAWN set to NOWAIT
**	item_list	-	VMS item list used to request device name using
**				SYS$GETDVIW
**	modified_chars	-	Device characteristics used to setup terminal
**				to have attributes we want
**	set_iosb	-	IOSB used to synchronize various requests
**	status		-	Return status from various routine calls
**	tmp_status	-	Return status from various routine calls used
**				on error path so that original error status is
**				preserved
**
** Outputs:
**
**       R0		-	SS$_NORMAL
**				Various error status values
**
** NOTES:
**
**       None
**
**-
*/
int setup_tty(void)
 
{
 
$DESCRIPTOR(devnam,"                ");
 
int			flags	= NOWAIT;
int			status;
int			tmp_status;
 
struct	item
{
short	int		buf_len;
short	int		item;
int			*buf_addr;
int			ret_len;
int			end;
}			item_list = {devnam.dsc$w_length, DVI$_DEVNAM,
				     devnam.dsc$a_pointer, 
				     &devnam.dsc$w_length, 0};
struct	dev_char	modified_chars;
struct	sense_iosb	set_iosb;
 
status = SYS$GETDVIW (0, ft_chan, 0, &item_list, &set_iosb, 0, 0, 0);
if (status & SS$_NORMAL)
{
   status = LIB$SPAWN(0, &devnam, &devnam, &flags, 0, &pid, 0, 0, 
		      &subprocess_exit, 0);
   if (status & SS$_NORMAL)
   {
      modified_chars = starting_chars;
      modified_chars.basic_chars = modified_chars.basic_chars | TT$M_NOECHO;
      modified_chars.extended_chars = modified_chars.extended_chars | 
				      TT2$M_PASTHRU;
      status = SYS$QIOW (0, tty_chan, IO$_SETMODE, &set_iosb, 0, 0,
			 &modified_chars, sizeof(modified_chars), 0, 0, 0, 0);
      if ((status & SS$_NORMAL) && (set_iosb.status & SS$_NORMAL))
      {
	 status = SYS$QIO (0, tty_chan, IO$_READVBLK, &tty_r_buff->status,
			   &kbd_read_ast, 0, &tty_r_buff->data[0],
			   1, 0, &term_block, 0, 0);
	 if (!(status & SS$_NORMAL))
	 {
	    tmp_status = SYS$QIOW (0,tty_chan, IO$_SETMODE, &set_iosb, 0, 0,
				   &starting_chars, sizeof(starting_chars), 0,
				   0, 0, 0);
	    tmp_status = SYS$FORCEX (&pid, 0, 0);
	 }
 
      }
      else if (status & SS$_NORMAL)
      {
	 status = set_iosb.status;
	 tmp_status = SYS$FORCEX (&pid, 0, 0);
      }
   }
}
 
return(status);
 
}

/*
**+
** create_pseudo_terminal - Create and setup pseudo terminal
**
** Functional Description:
**
**	This routine creates the pseudo terminal enables the AST's and starts
** the first read on the pseudo terminal.
**
** Explicit Inputs:
**
**	ret_addr	-	Pointer to start and end of pages allocated for
**				I/O buffers
**
** Implicit Inputs:
**
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	starting_chars	-	Terminal characteristics when logger started up
**
** Local Variables:
**
**	read_buffer	-	Pointer to I/O buffer used to hold data read 
**				from the pseudo terminal
**	status		-	Return status from various routine calls
**
** Outputs:
**
**       R0		-	SS$_NORMAL
**				Various return status codes
**
** NOTES:
**
**       None
**
**-
*/
 
create_pseudo_terminal (ret_addr)
 
struct	mem_region
{
struct	io_buff		*start;
struct	io_buff		*end;
}			*ret_addr;
 
 
{
 
int			status;
 
struct	io_buff		*read_buffer;
 
status = PTD$CREATE(&ft_chan, 0, &starting_chars, sizeof(starting_chars), 0, 0,
		    0, ret_addr);
if (status & SS$_NORMAL)
{
   status = PTD$SET_EVENT_NOTIFICATION (ft_chan, &bell_ast, 0, 0,
					PTD$C_SEND_BELL);
   if (status & SS$_NORMAL)
   {
      status = PTD$SET_EVENT_NOTIFICATION (ft_chan, &xoff_ast, 0, 0,
					   PTD$C_SEND_XOFF);
      if (status & SS$_NORMAL)
      {
	 status = PTD$SET_EVENT_NOTIFICATION (ft_chan, &xon_ast, 0, 0,
					      PTD$C_SEND_XON);
	 if (status &SS$_NORMAL)
	 {
	    status = PTD$SET_EVENT_NOTIFICATION (ft_chan, &set_line_ast, 0, 0,
						 PTD$C_CHAR_CHANGED);
	    if (status & SS$_NORMAL) 
	    {
	       read_buffer = allocate_io_buffer();
	       if ((read_buffer != LIB$_QUEWASEMP) && 
		   (read_buffer != SS$_FORCEDEXIT))
	       {
		  status = PTD$READ (0, ft_chan, &ft_read_ast, read_buffer,
				     &read_buffer->io_status, CHAR_BUF_SIZE);
	       }
	    }
	 }
      }
   }
   if (!(status & SS$_NORMAL)) PTD$DELETE (ft_chan);
}
 
return(status);
 
}

/*
**+
** create_log_file - Initialize FAB, RAB and open log file
**
** Functional Description:
**
**	This routine initialize the File Access Block (FAB) and the Record
** Access Block (RAB).  It opens the file and connects the RAB to the FAB so
** that the file is ready to have records written to it.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**	
** 	logger_fab	-	File Access Block for log file
**	logger_rab	-	Record Access Block for log file
**
** Local Variables:
**
**	status		-	Return status from various routine calls
**
** Outputs:
**
**       R0		-	RMS$_NORMAL successful completion
**				Various RMS errors
**
** NOTES:
**
**       None
**
**-
*/
 
int create_log_file (void)
 
{
 
int			status;
 
logger_fab = cc$rms_fab;
logger_fab.fab$l_fop = FAB$M_SUP | FAB$M_SQO;
logger_fab.fab$b_fac = FAB$M_PUT;
logger_fab.fab$b_shr = FAB$M_SHRGET;
logger_fab.fab$b_org = FAB$C_SEQ;
logger_fab.fab$b_rat = FAB$M_CR;
logger_fab.fab$b_rfm = FAB$C_RFM_DFLT;
logger_fab.fab$l_fna = "SESSION";	
logger_fab.fab$l_dna = ".LOG";
logger_fab.fab$b_fns = 7;
logger_fab.fab$b_dns = 4;
logger_fab.fab$w_mrs = IOC$GW_MAXBUF;
 
status = SYS$CREATE(&logger_fab);
if (status & SS$_NORMAL)
{
   logger_rab = cc$rms_rab;
   logger_rab.rab$l_rbf = rec_buffer;
   logger_rab.rab$l_fab = &logger_fab;
   status = SYS$CONNECT (&logger_rab);
}
 
return (status);
 
}

/*
**+
** kbd_read_ast - Process characters typed at keyboard
**
** Functional Description:
**
**	This routine is called every time data is read from the users terminal. 
** If the program is exiting then the routine will exit without restarting the
** read.   The character read is checked to see if the terminate processing
** character CTRL-\ was entered.  If the terminate processing character was 
** entered the exiting state will be set and a SYS$WAKE will be issued to start
** the mainline code.  Now an attempt will be made to get an I/O buffer to 
** store echoed output in.  If cannot get an I/O buffer a simple PTD$WRITE will
** be used otherwise a PTD$WRITE with echo will be issued.  If the write 
** completed successfully another read read will be issued to the keyboard.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	term_block	-	Long form to terminal driver read termination
**				mask
**	tty_chan	-	VMS channel pointing to terminal
**	tty_r_buff	-	Pointer to I/O buffer used to read characters
**				from terminal
**
** Local Variables:
**
**	echo_buff	-	Pointe to I/O buffer used to receive any
**				characters when data was written to the pseudo
**				terminal
**	status		-	Return status from various routine calls
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void	kbd_read_ast(void)
 
{
 
int			status;
 
struct	io_buff		*echo_buff;
 
if (!exiting)
{
   if (tty_r_buff->status & SS$_NORMAL)
   {
      if (tty_r_buff->data[0] != '\034')
      {
	 echo_buff = allocate_io_buffer();
	 if ((echo_buff != LIB$_QUEWASEMP) && (echo_buff != SS$_FORCEDEXIT))
	 {
	    status = PTD$WRITE (ft_chan, &ft_echo_ast, echo_buff,
				&tty_r_buff->io_status, tty_r_buff->byte_cnt, 
				&echo_buff->io_status, CHAR_BUF_SIZE);
	 }
	 else
	 {
	    status = PTD$WRITE (ft_chan, 0, echo_buff, &tty_r_buff->io_status,
				tty_r_buff->byte_cnt, 0, 0);
	 }
	 if (status & SS$_NORMAL) 
	 {
	     if ((tty_r_buff->io_status & SS$_NORMAL) ||
		 (tty_r_buff->io_status == SS$_DATAOVERUN))
	     {
		status = SYS$QIO (0, tty_chan, IO$_READVBLK,
				  &tty_r_buff->status, &kbd_read_ast, 0,
				  &tty_r_buff->data[0], 1, 0, &term_block, 
				  0, 0);
		if ((status & SS$_NORMAL) != SS$_NORMAL)
		{
		   exiting = TRUE;
		   exit_status = status;
		}
	     }
	     else
	     {
		exiting = TRUE;
		exit_status = tty_r_buff->io_status;
	     }
	 }
	 else
	 {
	    exiting = TRUE;
	    exit_status = status;
	 }
      }
      else
      {
	 exiting = TRUE;
	 exit_status = SS$_NORMAL;
      }
   }
   else
   {
      exiting = TRUE;
      exit_status = tty_r_buff->status;
   }
   if (exiting) SYS$WAKE(0, 0);
}
 
}

/*
**+
** terminal_output_ast - Queue output to logging queue 
**
** Functional Description:
**
**	This routine is called every time an a I/O buffer has been written to
** the terminal.  If the terminal write request completed successfully, it will
** queue  the I/O buffer into the queue of I/O buffers to be logged.  If the I/O
** buffer is the only entry on the queue then it will issue a SYS$WAKE to start
** the mainline.  If multiple entries are already on the queue then it will not
** issue a SYS$WAKE.  This should prevent spurious wake requests from 
** occurring.  If a terminal write error occurred it will set the exit flag and
** wake the mainline.
**
** Explicit Inputs:
**
**	buff_addr	-	Pointer to I/O buffer to be placed on log queue
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**	log_queue	-	Queue of I/O buffers to be written to log file 
**
** Local Variables:
**
**	status		-	Return status from LIB$INSQTI call
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void	terminal_output_ast(buff_addr)
 
struct	io_buff		*buff_addr;
 
{
 
int			status;
 
 
if (buff_addr->status & SS$_NORMAL)
{
   status = LIB$INSQTI(buff_addr, &log_queue);
   if (status = LIB$_ONEENTQUE)
   {
      SYS$WAKE (0, 0);
   }
}
else
{
   exiting = TRUE;
   exit_status = buff_addr->status;
   SYS$WAKE (0, 0);
}
 
}

/*
**+
** ft_read_ast - Output characters read from pseudo terminal
**
** Functional Description:
**
**	This routine is called when a pseudo terminal read request completes. 
** It will write the buffer to the terminal and attempt to start another read 
** from the pseudo terminal.  If the program is not exiting it will write the 
** buffer to the terminal, and it will attempt to start another pseudo terminal
** read.
**
** Explicit Inputs:
**
**	buff_addr	-	Pointer to I/O buffer containing characters
**				read from pseudo terminal
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	exit_status	-	Contains the reason while logging is stopping
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	status		-	Return status from various routine calls
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void	ft_read_ast(buff_addr)
 
struct	io_buff		*buff_addr;
 
{
 
int			status;
 
if (!exiting)
{
   if (buff_addr->io_status & SS$_NORMAL)
   {
      status = SYS$QIO (0, tty_chan, IO$_WRITEVBLK, &buff_addr->status,
		        &terminal_output_ast, buff_addr, &buff_addr->data[0],
			buff_addr->io_byte_cnt, 0, 0, 0, 0);
      if (status & SS$_NORMAL)
      {
         buff_addr = allocate_io_buffer();
         if ((buff_addr != LIB$_QUEWASEMP)  && (buff_addr != SS$_FORCEDEXIT))
         {
	    status = PTD$READ (0, ft_chan, &ft_read_ast, buff_addr,
			       &buff_addr->io_status, CHAR_BUF_SIZE);
	    if ((status & SS$_NORMAL) != SS$_NORMAL)
	    {
	       exiting = TRUE;
	       exit_status = status;
	       SYS$WAKE (0, 0);
            }
         }
         else
         {
	    read_stopped = TRUE;
         }
      }
      else
      {
         exiting = TRUE;
         exit_status = status;
         SYS$WAKE (0, 0);
      }
   }
   else
   {
      exiting = TRUE;
      exit_status = buff_addr->io_status;
      SYS$WAKE (0, 0);
   }
}
 
 
}

/*
**+
** ft_echo_ast - Write any immediately echoed data
**
** Functional Description:
**
**	This routine is called if a write to the pseudo terminal used an ECHO
** buffer.  If any data was echoed it needs to write the output to the 
** terminal. If no data was echoed then the I/O buffer needs to be freed so it
** can be used later. If the program is exiting this routine will just exit and
** not worry about cleaning up.
**
** Explicit Inputs:
**
**	buff_addr	-	Pointer to I/O buffer contianing any characters
**				that resulted from a write to the pseudo
**				terminal
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	status		-	Return status from various routine calls
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void	ft_echo_ast(buff_addr)
 
struct	io_buff		*buff_addr;
 
{
 
int			status;
 
 
if (!exiting)
{
   if (buff_addr->io_byte_cnt != 0)
   {
      status = SYS$QIO (0, tty_chan, IO$_WRITEVBLK, &buff_addr->status,
			&terminal_output_ast, buff_addr, &buff_addr->data[0],
			buff_addr->io_byte_cnt, 0, 0, 0, 0);
      if ((status & SS$_NORMAL) != SS$_NORMAL)
      {
         exiting = TRUE;
         exit_status = status;
         status = SYS$WAKE (0, 0);
      }
   }
   else
   {
      free_io_buffer(buff_addr);
   }
}
 
}

/*
**+
** free_io_buffer - Put I/O buffer on queue of available buffers
**
** Functional Description:
**
**	This routine will place a free I/O buffer on the queue of available I/O
** buffers.  It has the additional responsibility for restarting reads from the
** pseudo terminal if they were stopped.  This routine disables AST delivery 
** while running to synchronize reading and resetting the read stopped flag.  
** This is a big hammer approach but is the easiest way to do it.
**
** Explicit Inputs:
**
**	buff_addr	-	Pointer to I/O buffer to be placed on queue of
**				free I/O buffers
**
** Implicit Inputs:
**
**	buffer_queue	-	Queue of free I/O buffer
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**
** Local Variables:
**
**	ast_stat	-	Return status from SYS$SETAST used to determine
**				if AST delivery should turned back on
**	status		-	Return status from PTD$READ 
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void	free_io_buffer (buff_addr)
 
struct	io_buff		*buff_addr;
 
{
 
int			ast_stat;
int			status;
 
 
if (!exiting)
{
   ast_stat = SYS$SETAST(0);
   if (!read_stopped)
   {
      LIB$INSQHI(buff_addr, &buffer_queue);
   }
   else
   {
      status = PTD$READ (0, ft_chan, &ft_read_ast, buff_addr,
			 &buff_addr->io_status, CHAR_BUF_SIZE);
      if (status & SS$_NORMAL) 
      {
	 read_stopped = FALSE;
      }
      else
      {
         exiting = TRUE;
	 exit_status = status;
         status = SYS$WAKE (0, 0);
      }
   }
   if (ast_stat == SS$_WASSET) ast_stat = SYS$SETAST(1);	  
}
 
}

/*
**+
** allocate_io_buffer - Get a free I/O buffer from queue
**
** Functional Description:
**
**	This routine is used to get a free I/O buffer from the queue of
** available I/O buffers.  If the program is exiting then this routine will 
** return with an error.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**	
**	buffer_queue	-	Queue of free I/O buffer
**	exiting		-	Flag indicating if program is finishing up
**				processing
**
** Local Variables:
**
**	buff_addr	-	Pointer to free I/O buffer if one was available
**	status		-	Return status from LIB$REMQHI
**
** Outputs:
**
**	return_status	-	Address of buffer or
**	  LIB$_QUEWASEMP 	no I/O buffer
**        SS$_FORCEDEXIT 	program exiting
**
** NOTES:
**
**       None
**
**-
*/
 
struct	io_buff	*allocate_io_buffer(void)
{
 
int			status;
 
struct	io_buff		*buff_addr;
 
if (!exiting)
{
   status = LIB$REMQHI(&buffer_queue, &buff_addr);
   if (status & SS$_NORMAL)
   {
      return(buff_addr);
   }
   else
   {
      return(status);
   }
}
else
{
   return(SS$_FORCEDEXIT);
}
 
}

/*
**+
** subprocess_exit - Set up exit status when subprocess exists
**
** Functional Description:
**
**	This routine will be called when the subprocess has completed and
** exited.  This routine will see if program is already exiting.  If not then it
** will indicate that the program is exiting  and wake up the main program.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	exit_status	-	Contains the reason while logging is stopping
**	have_subprocess	-	Flag indicating if subprocess is still running
**
** Local Variables:
**
**	None
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void subprocess_exit(void)
{
 
 
if (!exiting) 
{
   have_subprocess = FALSE;
   exit_status = SS$_NORMAL;
   exiting = TRUE; 
   SYS$WAKE (0, 0);
}
 
}

/*
**+
** xon_ast - Send XON character to terminal
**
** Functional Description:
**
**	This routine is called when the pseudo terminal driver wants to signal
** that it is ready to accept keyboard input. The routine will attempt to send
** an XON character to the terminal.   This is done on a best effort basis - if
** it fails it will not be retried.  This is done by sending XON DC1 to the 
** terminal using SYS$QIO.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	dc1		-	ASCII XON character to be sent to terminal
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void xon_ast(void)
{
 
char			dc1 = XON;
 
if (!exiting)
{
   SYS$QIO (0, tty_chan, IO$_WRITEVBLK, 0, 0, 0, &dc1, 1, 0, 0, 0, 0);
}
 
}

/*
**+
** bell_ast - Send BELL character to terminal
**
** Functional Description:
**
**	This routine is called when the pseudo terminal driver wants to warn the
** user to stop sending keyboard data.  The routine will attempt to ring the
** terminals bell.  This is done on a best effort basis - if it fails it will 
** not be retried.  This is done by send the BELL character to the terminal 
** using SYS$QIO.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	bell		- 	ASCII bell character to be sent to terminal
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void bell_ast(void)
{
 
char			bell = BELL;
 
if (!exiting)
{
   SYS$QIO (0, tty_chan, IO$_WRITEVBLK, 0, 0, 0, &bell, 1, 0, 0, 0, 0);
}
 
}

/*
**+
** xoff_ast - Send XOFF character to terminal
**
** Functional Description:
**
**	This routine is called when the pseudo terminal driver wants to signal
** that it wants to stop accepting  keyboard input. The routine will attempt to
** send an XOFF character to the terminal.   This is done on a best effort 
** basis - if it fails it will not be retried.  This is done by sending XOFF
** <DC3> to the terminal using SYS$QIO.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	dc3		-	ASCII XOFF character to be sent to terminal
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void xoff_ast(void)
{
 
char			dc3 = XOFF;
 
if (!exiting)
{
   SYS$QIO (0, tty_chan, IO$_WRITEVBLK, 0, 0, 0, &dc3, 1, 0, 0, 0, 0);
}
 
}

/*
**+
** set_line_ast - Mirror changes to FT changes onto TT
**
** Functional Description:
**
**  	This routine is called when the pseudo terminal's device characteristics
** changed.  It will read the current pseudo terminal characteristics.  These
** characteristics will be changed to have PASTHRU and NOECHO set.  They will 
** then be applied to the user's input terminal.  Altering the terminal 
** characteristics will be done on a best effort basis - if it fails it will 
** not be retried.
**
** Explicit Inputs:
**
**	None
**
** Implicit Inputs:
**
**	exiting		-	Flag indicating if program is finishing up
**				processing
**	ft_chan		-	VMS channel pointing to control side of pseudo
**				terminal
**	tty_chan	-	VMS channel pointing to terminal
**
** Local Variables:
**
**	device_chars	-	Structure used to hold to new pseudo temrinal
**				charateristics that need to be applied to the
**				actual terminal.  These characteristics will be
**				changed to have TT$M_NOECHO and TT2$M_PASTHRU
**				set.
**	fill		-	Word containing the <CR> and <LF> fill values
**	sense_iosb	-	I/O status block filled in upon completion of
**				sense mode $QIO
**	set_iosb	-	I/O status block filled in upon completions of
**				set mode $QIO
**	speed		-	Terminal transmit and receive speeds
**	status		-	Return status of set and sense mode $QIO
**				operations
**
** Outputs:
**
**       None
**
** NOTES:
**
**       None
**
**-
*/
 
void set_line_ast(void)
{
 
short	int		fill;
short	int		speed;
int			status;
 
struct	dev_char	device_chars;
struct	iosb		set_iosb;
struct	sense_iosb 	sense_iosb;
 
if (!exiting)
{
   status = SYS$QIOW (0, ft_chan, IO$_SENSEMODE, &sense_iosb, 0, 0,
		      &device_chars, sizeof(device_chars), 0, 0, 0, 0);
   if (status & SS$_NORMAL) 
   {
      device_chars.basic_chars = device_chars.basic_chars | TT$M_NOECHO; 
      device_chars.extended_chars = device_chars.extended_chars | TT2$M_PASTHRU;
      speed = sense_iosb.xmit_speed | (sense_iosb.rcv_speed << 8);
      fill = sense_iosb.cr_fill | (sense_iosb.lf_fill << 8);
      status = SYS$QIO (0, tty_chan, IO$_SETMODE, &set_iosb, 0, 0, 
			&device_chars, sizeof(device_chars), speed, fill,
			sense_iosb.parity_flags,0);
   }
}
 
}
