/*
*
* ***************************************************************************
**									    *
**  COPYRIGHT (c) 1989, 1992 BY    					    *
**  DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS.		    *
**  ALL RIGHTS RESERVED.						    *
** 									    *
**  THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED   *
**  ONLY IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE   *
**  INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER   *
**  COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY   *
**  OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE IS  HEREBY   *
**  TRANSFERRED.							    *
** 									    *
**  THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE   *
**  AND  SHOULD  NOT  BE  CONSTRUED AS  A COMMITMENT BY DIGITAL EQUIPMENT   *
**  CORPORATION.							    *
** 									    *
**  DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS   *
**  SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.		    *
** 									    *
**									    *
*****************************************************************************
*
* ++
* 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-02	FAK001 		Forrest A. Kenney 		28-Apr-1992
*		Make single common source code for EVMS and VMS.  Remove
*		reference IOC$GW_MAXBUF and use $GETSYI instead.  The removes
*		the need to link against SYS.STB and removes tie to system
*		version numbers.
*
* --
*
* Compile and link instructions:
*
*	For VAX/VMS: 
*
*		$ CC LOGGER
*		$ !
*		$ ! Command file to link logger program
*		$ !
*		$ link LOGGER/MAP=LOGGER, sys$input:/opt
*		sys$share:vaxcrtl/share
*		$ exit
*
*	For Alpha/VMS
*		$ CC/STANDARD=VAXC LOGGER
*		$ !
*		$ ! Command file to link logger program
*		$ !
*		$ link LOGGER/MAP=LOGGER
*
* --
*/



/* Define constants */

#define	BELL			0x7
#define BUFFIO_OVERHEAD		78	/* 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	ASCII_NULL		'\0'	/* ASCII null character               */
#define	NOWAIT			1	/* No wait flag for LIB$SPAWN 	      */
#define	REQUEST_LENGTH		0X1FF	/* For traditional VAX/VMS this is    */
                                        /* the last byte in a page            */
#define	BUFFER_SIZE		0X200	/* Size of a I/O BUFFER in bytes. For */
                                        /* traditional VAX/VMS this is 1 page.*/
#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>
#pragma	nomember_alignment
#include	<rms>
#pragma	member_alignment
#include	<ssdef>
#include	<stdio>
#include	<syidef>
#include	<ttdef>
#include	<tt2def>

#include <lib$routines.h>
#include <starlet.h>




/* 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	item				/* VMS item list item		      */
{
short	int	buf_len;
short	int	item_code;
int		*buff_addr;
int		*ret_addr;
};

struct	io_buff				/* I/O block used by logger code      */
{
int		*flink; 	      	/* forward and backard queue links    */
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};
int			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
**	maxbuf		-	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] != ASCII_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 >= (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
**	maxbuf		-	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
**
**	syi_item1	-	$GETSYI item list to read system MAXBUF
**				parameter
**	
**
** Outputs:
**
**       R0		-	SS$_NORMAL
**				Various error status values
**
** NOTES:
**
**       None
**
**-
*/

typedef struct
{
struct	io_buff		*start;
struct	io_buff		*end;
} mem_region;

initialization(void)

{

$DESCRIPTOR(tty_name,"TT:");

int			loop;
int			rec_size;
int			status;

mem_region		ret_addr;

struct	
{
struct	item		item1;
int			end;
}			syi_item1 = {4, SYI$_MAXBUF, &maxbuf, 0, 0};

struct	iosb		iosb;

status = SYS$GETSYIW(0, 0, 0, &syi_item1, &iosb, 0, 0);

rec_size = 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 = (struct io_buff *)((char *)ret_addr.end - REQUEST_LENGTH);
      for (loop = 0; loop < IO_BUFFERS-1; loop++)
      {
	 free_io_buffer((char *)ret_addr.start + loop*BUFFER_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)

{

char			devnam_string[16];
$DESCRIPTOR(devnam,devnam_string);

int			flags	= NOWAIT;
int			status;
int			tmp_status;

struct
{
short	int		buf_len;
short	int		item;
char			*buf_addr;
unsigned short 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( mem_region *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 (((int)read_buffer != LIB$_QUEWASEMP) && 
		   ((int)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 = 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 (((int)echo_buff != LIB$_QUEWASEMP) && 
             ((int)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 (((int)buff_addr != LIB$_QUEWASEMP) && 
             ((int)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((struct io_buff *)status);
   }
}
else
{
   return((struct io_buff *)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);
   }
}

}
