From:	CRDGW2::CRDGW2::MRGATE::"SMTP::KCGL1.ENG.OHIO-STATE.EDU::JONESD" 30-APR-1991 10:03:14.18
To:	ARISIA::EVERHART
CC:	
Subj:	Re: Request

From:	JONESD@kcgl1.eng.ohio-state.edu@SMTP@CRDGW2
To:	EVERHART@ARISIA@MRGATE

Received:  by crdgw1.ge.com (5.57/GE 1.97)
	 id AA23922; Tue, 30 Apr 91 09:09:15 EDT
Date: Tue, 30 Apr 91 09:10 EST
From: Dave Jones <JONESD@kcgl1.eng.ohio-state.edu>
Subject: Re: Request
To: EVERHART@ARISIA.DECNET
Message-Id: <81AC5798C51F200159@kcgl1.eng.ohio-state.edu>
X-Envelope-To: EVERHART@ARISIA.dnet.ge.com
X-Vms-To: IN%"EVERHART@ARISIA.dnet.ge.com"
X-Vms-Cc: JONESD

Sorry for the slow response, your message got overlooked by mistake.

>>Dave
-----------------------------------------------------------------------------
$!                                                    19-APR-1991 10:15:36.85
$!
$! Invoke this file as a command procedure to upack the UPFE source files
$! and build the image.
$!
$ ON WARNGING THEN GOTO CLEANUP
$ GOSUB UNLOAD_FILES
$ FORTRAN/NODEBUG UPFE_MAIN.FOR+UPFE_PTERM/OBJECT=UPFE_OBJ.OBJ
$ LINK/EXE=UPFE.EXE/NOTRACE UPFE_OBJ.OBJ
$!
$ TYPE SYS$INPUT
The executable file UPFE.EXE is now built.  Place this file in a directory
where the full file specification is less than 64 characters, then setup
a DCL symbol to invoke this executable as a foreign command 
(e.g. UPFE == "$USER$DISK:[UTILITIES]UPFE").

The argument to UPFE is either the command to execute or a '$image' string in
the manner of a foreign command.

Examples:

	UPFE RUN SIMPLE_IO
	UPFE $SYSGEN
	UPFE MCR DISKQUOTA

$ prog_template = f$parse("UPFE.EXE;",,,,"NO_CONCEAL")
$ IF f$length(prog_template) .GT. 63 THEN WRITE SYS$OUTPUT -
	"Warning!  Filename is more than 63 chars: ", prog_template
$!
$ CLEANUP:
$    DELETE = "DELETE"
$    IF F$SEARCH("UPFE_OBJ.OBJ;") .NE. "" THEN DELETE/LOG UPFE_OBJ.OBJ;*
$ EXIT
$ UNLOAD_FILES:
$ CREATE UPFE_MAIN.FOR
	PROGRAM UNIX_PROGRAM_FRONT_END
C+
C	This program provides a utility for 'rationalizing' standard terminal
C	I/O for	unix programs ported to VMS.  Since unix doesn't have a
C	read-with-prompt concept, prompts are simply writes to stdout followed
C	by a read from stdin.  As a result, the VMS user sees recalled lines
C	go to the next line without a prompt.
C
C	This program filters input/output and converts output characters
C	preceeding a read into a prompt for that read.  In addition, an SMG
C	virtual keyboard is used to supply up to 20 lines of command recall.
C
C	User invokes this utility as a foreign command and passes the
C	command used to invoke the unix program as it argument.
C
C	Internally, this utility creates a sub-process to handle the PTY
C	and drive a mailbox-based command procedure that this process
C	invokes.
C-
	IMPLICIT NONE
	INTEGER STATUS, LIB$GETJPI, LIB$GET_FOREIGN, LENGTH, IMAGE_COUNT
	INTEGER PID, MASTER_PID, MBX, SYS$CREMBX, SYS$SETPRN, IMLEN
	INTEGER SYS$ENQ, SYS$DEQ, SYS$SYNCH, LKSB(6), LOCK_AST, UPFE_SPAWN
	INTEGER UPFE_SEND_INFO, UPFE_GET_INFO
	CHARACTER MBX_NAME*13, PROC_NAME*15, IMAGE_NAME*64, LINE*300
	CHARACTER TERMINAL*64
	INCLUDE '($JPIDEF)'
	INCLUDE '($LCKDEF)'
	INCLUDE '($LNMDEF)'
	EXTERNAL UPFE_LOCK_AST
C
C	Determine whether we are running due to user command (master process)
C	or whether we are the sub-process created to control the
C	psuedo-terminal.  If we assume that the master process is using a
C	CLI (true if using upfe command), we can distinguish the cases by 
C	checking the image count.  The sub-process's will be zero and the
C	master process will be greater than 0 (LOGINOUT is always the first
C	image run).
C
	PID = 0
	STATUS = LIB$GETJPI ( JPI$_IMAGECOUNT, PID, , IMAGE_COUNT )
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	IF ( IMAGE_COUNT .EQ. 0 ) THEN
C
C	    We are the pty process, get PID of guy who created us.
C
	    STATUS = LIB$GETJPI ( JPI$_OWNER, , , MASTER_PID )
	    IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	    LOCK_AST = %LOC ( UPFE_LOCK_AST )
	ELSE
C
C	    We are the master process.  Set pid and prompt for command
C	    if not supplied (give user oportunity to abort before we
C	    expend additional energies).
C
	    MASTER_PID = PID
	    STATUS = LIB$GET_FOREIGN ( LINE, 'command: ', LENGTH )
	    IF ( .NOT. STATUS ) CALL EXIT
	    LOCK_AST = 0
	END IF
C
C	Create mailbox and queue exclusive mode lock using name based upon 
C	master PID.  
C
	CALL SYS$FAOL ( 'UPFE_!8XL', , MBX_NAME, MASTER_PID )
	STATUS = SYS$CREMBX ( , MBX, %VAL(300), %VAL(360),
	1	%VAL('FF00'X), , MBX_NAME )
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
C
	STATUS = SYS$ENQ ( %VAL(8), %VAL(LCK$K_NLMODE), LKSB, ,MBX_NAME, , 
	1	, , , , )
	IF ( STATUS ) CALL SYS$SYNCH ( %VAL(8), LKSB )
	STATUS = LKSB(1)
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	STATUS = SYS$ENQ ( %VAL(8), %VAL(LCK$K_EXMODE), LKSB, 
	1	%VAL(LCK$M_CONVERT), , , %VAL(LOCK_AST), LKSB, , , )
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
C
	IF ( PID .EQ. MASTER_PID ) THEN
C
C	    Wait for the lock
C
	    CALL SYS$SYNCH ( %VAL(8), LKSB )
C
C	    Create the sub-process and pass it info.
C
	    STATUS = LIB$GETJPI ( JPI$_IMAGNAME, , , , IMAGE_NAME, IMLEN )
	    IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	    STATUS = UPFE_SPAWN ( IMAGE_NAME(:IMLEN), PID )
	    IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
C
	    STATUS = UPFE_SEND_INFO ( MBX, LINE, LENGTH )
C
C	    IF spawn worked, exit with do_command to invoke mailbox.
C
	    IF ( STATUS ) CALL LIB$DO_COMMAND ( '@'//MBX_NAME )
	    CALL EXIT ( STATUS )
C
	ELSE
C
C	    Set the process name.
C
	    STATUS = SYS$SETPRN ( MBX_NAME )
C
C	    Branch to mainline code for sub-process.
C
	    CALL UPFE_CHILD ( MBX, LKSB(2) )
C
	END IF
	END
C-----------------------------------------------------------------------------
C
	SUBROUTINE UPFE_LOCK_AST ( LKSB )
C+
C	Completion AST for conversion of sub-process's lock conversion
C	request.  If the status is other that SS$_CANCEL, assume master
C	process exitted the image before anticipated and kill this process.
C-
	IMPLICIT NONE
	INTEGER LKSB(6)
	INCLUDE '($SSDEF)'
	IF ( LKSB(1) .NE. SS$_CANCEL ) CALL EXIT ( LKSB(1) )
	END
C-----------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_SPAWN ( IMAGE, PID )
C+
C	Spawn the sub-process that controls the PTY.
C
C	Input:
C	    IMAGE
C	Output:
C	    PID		Pid of created process or zero.
C-
	IMPLICIT NONE
	INTEGER PID
	CHARACTER*(*) IMAGE
	INTEGER SYS$CREPRC, SYS$CREMBX, PRIVS(2), MBX_UNIT, SYS$TRNLNM
	INTEGER ITEM_LIST(4), START, TRN_LEN
	STRUCTURE /PROCESS_CONTROL/
	    INTEGER AST, PID, MBX, IOSB(2)
	    CHARACTER MESSAGE*128
	END STRUCTURE
	RECORD /PROCESS_CONTROL/ PCB
	CHARACTER TRANSLATION*256
	EXTERNAL UPFE_TERM_MBX_AST
	INCLUDE '($JPIDEF)'
	INCLUDE '($LNMDEF)'
	INCLUDE '($DVIDEF)'
C
C	Create mailbox to receive termination messages.
C
	PID = 0
	UPFE_SPAWN = SYS$CREMBX ( , PCB.MBX, %VAL(128),, %VAL(128),
	1	%VAL('FF00'X), , )
	IF ( .NOT. UPFE_SPAWN ) RETURN
	CALL LIB$GETDVI ( DVI$_UNIT, PCB.MBX, , MBX_UNIT )
C
C	Issue read on mailbox.
C
	PCB.AST = %LOC ( UPFE_TERM_MBX_AST )
	PCB.IOSB(1) = 0
	CALL SYS$DCLAST ( UPFE_TERM_MBX_AST, PCB, )
C
C	Translate SYS$COMMAND for use as the sub-processes INPUT/OUTPUT.
C
	ITEM_LIST(1) = LNM$_STRING * '10000'X + 255
	ITEM_LIST(2) = %LOC ( TRANSLATION )
	ITEM_LIST(3) = %LOC ( TRN_LEN )
	ITEM_LIST(4) = 0
C
	UPFE_SPAWN = SYS$TRNLNM 
	1	(  , 'LNM$FILE_DEV', 'SYS$COMMAND', , ITEM_LIST )
	IF ( .NOT. UPFE_SPAWN ) RETURN
	START = 1
	IF ( TRANSLATION(1:2) .EQ. CHAR(27)//CHAR(0) ) START = 5
C
C	Create the sub-process.
C
	CALL LIB$GETJPI ( JPI$_PROCPRIV, , , PRIVS )
	UPFE_SPAWN = SYS$CREPRC ( PCB.PID, IMAGE, 
	1	TRANSLATION(START:TRN_LEN), TRANSLATION(START:TRN_LEN), ,
	2	PRIVS, , , %VAL(4), , %VAL(MBX_UNIT), )
C
	IF ( UPFE_SPAWN ) PID = PCB.PID
C
	END	
C-----------------------------------------------------------------------------
C
	SUBROUTINE UPFE_TERM_MBX_AST ( PCB )
C+
C	AST thread to read termination mailboxes checking for termination
C	of the sub-process.  If detected, force exit of image.
C-
	IMPLICIT NONE
	STRUCTURE /PROCESS_CONTROL/
	    INTEGER AST, PID, MBX, IOSB(2)
	    CHARACTER MESSAGE*128
	END STRUCTURE
	RECORD /PROCESS_CONTROL/ PCB
	INTEGER STATUS, SYS$QIO
	INCLUDE '($IODEF)'
	INCLUDE '($MSGDEF)'
C
	IF ( PCB.IOSB(1) ) THEN
C
C	   See if message is DELPROC and send by the right process.
C
	    IF ( PCB.IOSB(2) .EQ. PCB.PID ) THEN
		CALL LIB$PUT_OUTPUT ( 'Child process died' )
		CALL EXIT
	    END IF
	END IF
C
C	Issue another read.
C
	STATUS = SYS$QIO ( , %VAL(PCB.MBX), %VAL(IO$_READVBLK), PCB.IOSB,
	1	%VAL(PCB.AST), PCB, %REF(PCB.MESSAGE), %VAL(128), , , , )
C
	END
C-----------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_SEND_INFO ( MBX, LINE, LENGTH )
C+
C	Send control info to child process via mailbox.
C-
	IMPLICIT NONE
	INTEGER MBX, LENGTH
	CHARACTER LINE*(*)
	INTEGER SYS$QIOW, SYS$TRNLNM, ITEM_LIST(4), TRN_LEN, START
	INCLUDE '($IODEF)'
	INCLUDE '($LNMDEF)'
	INTEGER*2 IOSB(4)
	CHARACTER TRANSLATION*256
C
C	Translate sys$input and sys$output and send translations to
C	the sub-process.
C
	ITEM_LIST(1) = LNM$_STRING * '10000'X + 255
	ITEM_LIST(2) = %LOC ( TRANSLATION )
	ITEM_LIST(3) = %LOC ( TRN_LEN )
	ITEM_LIST(4) = 0
C
	UPFE_SEND_INFO = SYS$TRNLNM 
	1	( , 'LNM$FILE_DEV', 'SYS$INPUT', , ITEM_LIST )
	IF ( .NOT. UPFE_SEND_INFO ) RETURN
	START = 1
	IF ( TRANSLATION(1:2) .EQ. CHAR(27)//CHAR(0) ) START = 5
	UPFE_SEND_INFO = SYS$QIOW ( %VAL(8), %VAL(MBX), 
	1	%VAL(IO$_WRITEVBLK), IOSB, , ,
	2	%REF(TRANSLATION(START:)), %VAL(TRN_LEN-START+1), , , , )
C
	UPFE_SEND_INFO = SYS$TRNLNM 
	1	( , 'LNM$FILE_DEV', 'SYS$OUTPUT', , ITEM_LIST )
	IF ( .NOT. UPFE_SEND_INFO ) RETURN
	START = 1
	IF ( TRANSLATION(1:2) .EQ. CHAR(27)//CHAR(0) ) START = 5
	UPFE_SEND_INFO = SYS$QIOW ( %VAL(8), %VAL(MBX), 
	1	%VAL(IO$_WRITEVBLK), IOSB, , ,
	2	%REF(TRANSLATION(START:)), %VAL(TRN_LEN-START+1), , , , )
C
C	Send the orignal command line
C
	UPFE_SEND_INFO = SYS$QIOW ( %VAL(8), %VAL(MBX), 
	1	%VAL(IO$_WRITEVBLK), IOSB, , ,
	2	%REF(LINE), %VAL(LENGTH), , , , )
C
C	Read a status line back to synchronize lock dequeue.
C
	UPFE_SEND_INFO = SYS$QIOW ( %VAL(8), %VAL(MBX),
	1	%VAL(IO$_READVBLK), IOSB, , ,
	2	%REF(TRANSLATION), %VAL(LEN(TRANSLATION)), , , , )
C
	END
C===========================================================================
C
	INTEGER FUNCTION UPFE_CHILD ( MBX, LOCK )
C+
C	Top level routine for sub-process.
C	Input:
C	    MBX		Integer.  Channel assigned to mailbox.
C	    LOCK	Integer.  Lock ID of deadman lock.
C-
	IMPLICIT NONE
	INTEGER LOCK, MBX
	INTEGER SYS$QIOW, SYS$DEQ, IN_LEN, OUT_LEN, CMD_LEN, LENGTH
	INTEGER EXIT_BLOCK(-2:5), SYS$DCLEXH, BUFADR, UPFE_SEND_COMMAND
	INTEGER*2 IOSB(4)
	INCLUDE '($IODEF)'
	INCLUDE '($LCKDEF)'
	CHARACTER INPUT*300, OUTPUT*300, COMMAND*300
	CHARACTER TERMINAL*64
	EXTERNAL UPFE_CHILD_EXIT
C
C	Read the info passed by the master.
C
	UPFE_CHILD = SYS$QIOW ( , %VAL(MBX), %VAL(IO$_READVBLK), IOSB,
	1	, , %REF(INPUT), %VAL(LEN(INPUT)), , , , )
	IF ( UPFE_CHILD ) UPFE_CHILD = ZEXT ( IOSB(1) )
	IF ( .NOT. UPFE_CHILD ) RETURN
	IN_LEN = ZEXT(IOSB(2))
CC	TYPE*,'Input device: ', INPUT(:IN_LEN), IN_LEN
C
	UPFE_CHILD = SYS$QIOW ( , %VAL(MBX), %VAL(IO$_READVBLK), IOSB,
	1	, , %REF(OUTPUT), %VAL(LEN(OUTPUT)), , , , )
	IF ( UPFE_CHILD ) UPFE_CHILD = ZEXT ( IOSB(1) )
	IF ( .NOT. UPFE_CHILD ) RETURN
	OUT_LEN = ZEXT(IOSB(2))
C
	UPFE_CHILD = SYS$QIOW ( , %VAL(MBX), %VAL(IO$_READVBLK), IOSB,
	1	, , %REF(COMMAND), %VAL(LEN(COMMAND)), , , , )
	IF ( UPFE_CHILD ) UPFE_CHILD = ZEXT ( IOSB(1) )
	IF ( .NOT. UPFE_CHILD ) RETURN
	CMD_LEN = ZEXT(IOSB(2))
C
C	Cancel the pending lock request and write status.
C
	UPFE_CHILD = SYS$DEQ ( %VAL(LOCK), , , %VAL(LCK$M_CANCEL) )
	IF ( .NOT. UPFE_CHILD ) RETURN
C	  
	UPFE_CHILD = SYS$QIOW ( , %VAL(MBX), %VAL(IO$_WRITEVBLK), IOSB,
	1	, , 1, %VAL(4), , , , )
	IF ( UPFE_CHILD ) UPFE_CHILD = ZEXT ( IOSB(1) )
	IF ( .NOT. UPFE_CHILD ) RETURN
C
C	Setup an exit handler to send EOF to mailbox.
C
	EXIT_BLOCK(-1) = %LOC ( UPFE_CHILD_EXIT )
	EXIT_BLOCK(0) = 2
	EXIT_BLOCK(1) = %LOC ( EXIT_BLOCK(5) )
	EXIT_BLOCK(2) = %LOC ( MBX )
	UPFE_CHILD = SYS$DCLEXH ( EXIT_BLOCK )
C
C	Create a PTY.
C
	CALL UPFE_CREATE_PTY 
	1	( INPUT(:IN_LEN), OUTPUT(:OUT_LEN), TERMINAL, BUFADR )
C
C	Send commands to mailbox as a command procedure.  We need to
C	reset SYS$INPUT and SYS$INPUT to the PTY, then issue the command.
C
C	Commands sent:
C		$DEFINE/USER/TRANS=TERM SYS$OUTPUT FTAnnn
C		$DEFINE/USER/TRANS=TERM SYS$ERROR NL:
C		$DEFINE/USER/TRANS=TERM SYS$INPUT  FTAnnn
C		$OPEN/READ UPFE_PSEUDO_TERM FTAnnn 
C		$ON WARNING THEN CONTINUE
C		$ON CONTROL_Y THEN CONTINUE
C		${user command}
C		$CLOSE UPFE_PSEUDO_TERM
C
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$SAVE_VER = ''F$VERIFY(0)', 0 )
	IF ( .NOT. UPFE_CHILD ) RETURN
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$DEFINE/USER/TRANS=TERMINAL SYS$OUTPUT !AS', TERMINAL )
	IF ( .NOT. UPFE_CHILD ) RETURN
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$DEFINE/USER/TRANS=TERMINAL SYS$ERROR NL:', TERMINAL )
	IF ( .NOT. UPFE_CHILD ) RETURN
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$DEFINE/USER/TRANS=TERMINAL SYS$INPUT !AS', TERMINAL )
	IF ( .NOT. UPFE_CHILD ) RETURN
C
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX,  .FALSE., 
	1	'$OPEN/READ UPFE_PSEUDO_TERM !AS', TERMINAL )
	IF ( .NOT. UPFE_CHILD ) RETURN
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$ON WARNING THEN CONTINUE', 0 )
	IF ( .NOT. UPFE_CHILD ) RETURN
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$ON CONTROL_Y THEN CONTINUE', 0 )
	IF ( .NOT. UPFE_CHILD ) RETURN
C
C	IF user specified command begining with a dollar sign, make at
C	temporary symbol and modify command string.
C
	IF ( COMMAND(1:1) .EQ. '$' ) THEN
	    UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$ UPFE_CMD := !AS', COMMAND(:MAX(1,MIN(CMD_LEN,285))) )
	    IF ( .NOT. UPFE_CHILD ) RETURN
	    CALL STR$TRIM ( COMMAND, 'UPFE_CMD', CMD_LEN )
	END IF
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .FALSE.,
	1	'$!AS', COMMAND(:MAX(1,MIN(CMD_LEN,299))) )
	IF ( .NOT. UPFE_CHILD ) RETURN
C
C	The final command we do ansynchronously so that we don't have
C	to wait on it.
C
	UPFE_CHILD = UPFE_SEND_COMMAND ( MBX, .TRUE.,
	1	'$CLOSE UPFE_PSEUDO_TERM/ERR=L''F$VERIFY(SAVE_VER)', 0 )
	IF ( .NOT. UPFE_CHILD ) RETURN
C
C	process the I/O generated by the command to the psuedo-terminal.
C
	CALL UPFE_PTY_IO ( %VAL(BUFADR) )
C
	END
C------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_SEND_COMMAND (MBX, NOWAIT, STRING, FAO_ARG)
C+
C	Format data line and write to mailbox.
C	process to exit.
C-
	IMPLICIT NONE
	INTEGER MBX, NOWAIT, FAO_ARG(100), CMD_LEN, SYS$FAO, SYS$QIOW, FUNC
	INTEGER*2 IOSB(4)
	CHARACTER STRING*(*), COMMAND*300
	INCLUDE '($IODEF)'
C
	UPFE_SEND_COMMAND = SYS$FAO (STRING, CMD_LEN, COMMAND, FAO_ARG)
	IF ( UPFE_SEND_COMMAND .AND. CMD_LEN .GT. 0 ) THEN
C
C	    Trim string and write it.
C
	    CALL STR$TRIM ( COMMAND, COMMAND(:MAX(1,CMD_LEN)), CMD_LEN )
	    CMD_LEN = MIN ( CMD_LEN, 255 )
	    FUNC = IO$_WRITEVBLK
	    IF ( NOWAIT ) FUNC = FUNC + IO$M_NOW
	    UPFE_SEND_COMMAND = SYS$QIOW ( %VAL(8), %VAL(MBX),
	1	%VAL(FUNC), IOSB, , ,
	2	%REF(COMMAND), %VAL(CMD_LEN), , , , )
C
	    IF ( UPFE_SEND_COMMAND ) UPFE_SEND_COMMAND = ZEXT(IOSB(1))
	END IF
	END
C
C------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_CHILD_EXIT ( EXIT_STATUS, MBX )
C+
C	Perform cleanup operation on mailbox, send EOF to force
C	process to exit.
C-
	IMPLICIT NONE
	INTEGER EXIT_STATUS, MBX, SYS$QIOW, IOSB(2)
	INCLUDE '($IODEF)'
C
	UPFE_CHILD_EXIT = SYS$QIOW ( , %VAL(MBX), 
	1	%VAL(IO$_WRITEOF+IO$M_NOW), IOSB, , , , , , , , )
	CALL SYS$DASSGN ( %VAL(MBX) )
	END

$!
$ CREATE UPFE_PTERM.FOR
C
	SUBROUTINE UPFE_CREATE_PTY ( INPUT, OUTPUT, TERMINAL, BUFADR )
C+
C	Initialize psuedo terminal and its control structures.
C
C	Input;
C	    INPUT
C	Output:
C	    TERMINAL	Device name of created PTY.
C-
	IMPLICIT NONE
	CHARACTER*(*) INPUT, OUTPUT, TERMINAL
	INTEGER STATUS, SYS$ASSIGN, PTD$CREATE, UNIT, LENGTH, BUFADR
	INTEGER TT_CHAR(5), IO_BUFADR(2), ITEM_LIST(4), SYS$QIOW
	INTEGER SMG$CREATE_VIRTUAL_KEYBOARD, PTD$SET_EVENT_NOTIFICATION 
	INTEGER EXIT_BLOCK(-2:5)
	INTEGER*2 IOSB(4)
	INCLUDE '($DVIDEF)'
	INCLUDE '($IODEF)'
	INCLUDE '($PTDDEF)'
	EXTERNAL PTD$CANCEL, UPFE_QUEUE_EVENT, UPFE_PTY_RUNDOWN
	EXTERNAL UPFE_CONTROL_C_AST
C
	INTEGER PTY, TTY, KEYBOARD, KEYTABLE
	LOGICAL IS_TERMINAL
	COMMON /UPFE_PTY/ PTY, TTY, IS_TERMINAL, KEYBOARD, KEYTABLE
C
C	Assign channel to INPUT device and get characteristics.
C
C
	STATUS = SYS$ASSIGN ( INPUT, TTY, , )
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	CALL LIB$GETDVI ( DVI$_TRM, TTY, , IS_TERMINAL )
	IF ( IS_TERMINAL ) THEN
C
C	    Perform sense mode to get current characteristics.
C
	    STATUS = SYS$QIOW ( , %VAL(TTY), %VAL(IO$_SENSEMODE), IOSB,
	1	, , TT_CHAR, %VAL(20), , , , )
C
C	    Setup control Y ast to force exit.  Set control to be ignored.
C
CC	    STATUS = SYS$QIOW ( , %VAL(TTY), 
CC 	1	%VAL(IO$_SETMODE+IO$M_CTRLCAST), IOSB, , ,
CC	2	UPFE_CONTROL_C_AST, UPFE_CONTROL_C_AST, , , , )
C
CC	    STATUS = SYS$QIOW ( , %VAL(TTY), 
CC	1	%VAL(IO$_SETMODE+IO$M_CTRLYAST), IOSB, , ,
CC	2	UPFE_CONTROL_C_AST, , , , , )
	END IF
C
C	Create the PTY.
C
	CALL LIB$GET_VM_PAGE ( 6, IO_BUFADR )
	BUFADR = IO_BUFADR(1)
	IO_BUFADR(2) = IO_BUFADR(1) + 512*5
	STATUS = PTD$CREATE
	1	(PTY, , TT_CHAR, %VAL(20), UPFE_QUEUE_EVENT, 6, , IO_BUFADR)
	IF ( .NOT. STATUS ) TYPE*,'Status of PTY create:', STATUS
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
	CALL LIB$GETDVI ( DVI$_UNIT, PTY, , UNIT )
	TERMINAL = ' '
	CALL LIB$GETDVI ( DVI$_DEVNAM, PTY, , , TERMINAL )
C
C	Set up exit handler to cleanly run down the PTY (works around bug in
C	FTDRIVER).
C
	EXIT_BLOCK(-1) = %LOC ( UPFE_PTY_RUNDOWN )
	EXIT_BLOCK(0) = 2
	EXIT_BLOCK(1) = %LOC(EXIT_BLOCK(5))	! reason
	EXIT_BLOCK(2) = %LOC(PTY)
	CALL SYS$DCLEXH ( EXIT_BLOCK )
C
C	Set ASTs to go off when reads start.  Event codes;
C	    0	PTD$READ completed
C	    1	START_READ notification
C	    2	MIDDLE_READ notification
C	    3   END_READ notification
C	    4	Characteristics change notification
C	    5   XON notification
C	    6   PTY idle notification
C
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	UPFE_QUEUE_EVENT, 1, , %VAL(PTD$C_START_READ) )
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	UPFE_QUEUE_EVENT, 2, , %VAL(PTD$C_MIDDLE_READ) )
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	UPFE_QUEUE_EVENT, 3, , %VAL(PTD$C_END_READ) )
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	UPFE_QUEUE_EVENT, 4, , %VAL(PTD$C_CHAR_CHANGED) )
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	UPFE_QUEUE_EVENT, 5, , %VAL(PTD$C_SEND_XON) )
	STATUS = PTD$SET_EVENT_NOTIFICATION ( %VAL(PTY),
	1	, , , %VAL(PTD$C_ENABLE_READ) )
C
C	Creat virtual keyboard and keytable.
C
	CALL SMG$CREATE_KEY_TABLE ( KEYTABLE )
	STATUS = SMG$CREATE_VIRTUAL_KEYBOARD ( KEYBOARD, INPUT )
	IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
C
C
	END
C---------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_QUEUE_EVENT ( CODE )
C+
C	Queue/dequeue events signifying AST calls.  IF CODE >= 0, save the
C	code number in a FIFO.  If CODE < 0, return the next code in the list
C	or an error status (-1, queue empty).  If an error status is returned,
C	the next queue event will also issue a wake request to the process.
C
C	This routine must always be called as an AST or with ASTs disabled.
C
C	Input:
C	    CODE value.
C
C-
	IMPLICIT NONE
	INTEGER CODE
	PARAMETER QUEUE_SIZE = 63
	INTEGER PENDING(0:QUEUE_SIZE), HEAD/0/, TAIL/0/
	LOGICAL AWAITING_EVENT/.FALSE./
	SAVE PENDING, HEAD, TAIL, AWAITING_EVENT
C
	IF ( CODE .GE. 0 ) THEN
C
C	   Append code to tail.  Wake if awating_event and queue is empty
C
	    IF ( AWAITING_EVENT .AND. HEAD .EQ. TAIL ) THEN
		CALL SYS$WAKE ( , )
		AWAITING_EVENT = .FALSE.
	    END IF
	    PENDING(TAIL) = CODE
	    TAIL = IAND(QUEUE_SIZE,TAIL+1)
	    IF ( TAIL .EQ. HEAD ) HEAD = IAND(QUEUE_SIZE,HEAD+1)	! queue full
	    UPFE_QUEUE_EVENT = 1
	    RETURN
C
	ELSE IF ( HEAD .EQ. TAIL ) THEN
C
C	    User wanted an event returned but queue is empty.
C
	    AWAITING_EVENT = .TRUE.
	    UPFE_QUEUE_EVENT = -1
	ELSE
C
C	    Simplest case, remove from head of queue.
C
	    UPFE_QUEUE_EVENT = PENDING(HEAD)
	    HEAD = IAND(QUEUE_SIZE,HEAD+1)
	END IF
C
	END
C---------------------------------------------------------------------------
C
	SUBROUTINE UPFE_PTY_IO ( PTDBUF )
C+
C	Relay/filter I/O between the terminal and IO.
C-
	IMPLICIT NONE
	STRUCTURE /PTDBUF/
	    INTEGER*2 STATUS, LENGTH
	    CHARACTER*508 DATA
	END STRUCTURE
	RECORD /PTDBUF/ PTDBUF(6)
	INTEGER STATUS, EF, PTD$READ, PTD$WRITE, LINE_LEN, CR_PENDING, OCTET
	INTEGER I, EVENT, UPFE_QUEUE_EVENT, PTD$DELETE, PROMPT_LEN, J, GSTATE
	INTEGER SMG$READ_COMPOSED_LINE, LIB$GETDVI, UNIT, READ_STATE, SPTR
	INTEGER TT_CHAR(5), NEW_CHAR(5), SYS$QIOW, NEW_STATE, SILO(0:65)
	LOGICAL READ_ACTIVE
	EXTERNAL UPFE_QUEUE_EVENT
	INTEGER*2 IOSB(4)
	CHARACTER LINE*1024, PROMPT*1024
C
	INTEGER PTY, TTY, KEYBOARD, KEYTABLE
	LOGICAL IS_TERMINAL
	COMMON /UPFE_PTY/ PTY, TTY, IS_TERMINAL, KEYBOARD, KEYTABLE
	INCLUDE '($DVIDEF)'
	INCLUDE '($IODEF)'
	INCLUDE '($PTDDEF)'
	INCLUDE '($SSDEF)'
	INCLUDE '($TTDEF)'
	INCLUDE '($TT2DEF)'
C
C	Initialize
C
	EF = 11
	READ_STATE = 0
	GSTATE = 0
	PTDBUF(1).STATUS = 44		! initialize buffer for read.
	PTDBUF(1).LENGTH = 0
	SILO(0) = -1
	SPTR = -1
	CALL SYS$DCLAST ( UPFE_QUEUE_EVENT, 0, )
	LINE_LEN = 0
	CR_PENDING = .FALSE.
	READ_ACTIVE = .FALSE.		! call to PTD$READ is not pending
	IF ( IS_TERMINAL ) THEN
	    STATUS = SYS$QIOW ( , %VAL(TTY), %VAL(IO$_SENSEMODE), IOSB,
	1	, , TT_CHAR, %VAL(20), , , , )
	    IF ( IAND(TT_CHAR(3), TT2$M_APP_KEYPAD) .EQ. 0 ) 
	1	CALL SMG$SET_KEYPAD_MODE ( KEYBOARD, 0 )
	END IF
C
C	Main loop, Get event.
C
	DO WHILE ( EF .EQ. 11 )
C
100	    SPTR = SPTR + 1
	    EVENT = SILO(SPTR)
CC	TYPE*,'Event', EVENT, PTDBUF(1).STATUS, PTDBUF(1).LENGTH,
CC	1	ICHAR(PTDBUF(1).DATA), SPTR
	    IF ( EVENT .LT. 0 ) THEN
C
C		No more events in silo, wait for a wake and reload it.
C		Note that startup position zero is reserved and the first
C		event read goes into position 1.
C
		I = 0
		IF ( SILO(I) .GE. 0 ) CALL SYS$HIBER()
		SILO(I) = 0
		CALL SYS$SETAST(%VAL(0))
		DO WHILE ( SILO(I) .GE. 0 )
		    I = I + 1
		    SILO(I) = UPFE_QUEUE_EVENT ( -1 )
		    IF ( I .GT. 64 ) STOP 'Event queue inconsistent'
		END DO
		CALL SYS$SETAST(%VAL(1))
		SPTR = 0
C
	    ELSE IF ( EVENT .EQ. 0 ) THEN
C
C		A call to PTD$READ completed, display what was read.
C
		IF ( PTDBUF(1).STATUS ) THEN
C
C		    Call graphics intercept routine to pick out the graphics
C		    commands.  The buffer will be fixed up if needed.
C
		    CALL UPFE_GRAPHICS_INTERCEPT ( GSTATE, PTDBUF(1) )
C
C		    Transfer buffer contents to output lines.  Break up char stream
C		    into records.
C
		    DO I = 1, PTDBUF(1).LENGTH
		        OCTET = ICHAR ( PTDBUF(1).DATA(I:I) )
		        IF ( OCTET .EQ. 13 ) THEN 	! ascii CR
			    CR_PENDING = .TRUE.
			    IF ( LINE_LEN .EQ. 0 ) CR_PENDING  = .FALSE.
C
		        ELSE IF ( CR_PENDING .AND. OCTET .EQ. 10 ) THEN
C
C			    Previous char was <CR> and this is <LF>, ignore it.
C
			    CR_PENDING = .FALSE.
C
		        ELSE
C
C			    Add character to output line.
C
		            LINE_LEN = LINE_LEN + 1
		            LINE(LINE_LEN:LINE_LEN) = CHAR(OCTET)
			    CR_PENDING = .FALSE.
			    IF ( LINE_LEN .GE. LEN(LINE) ) CR_PENDING = .TRUE.
		        END IF
C
C		        Flush output buffer if <CR> detected.
C
		        IF ( CR_PENDING ) THEN
			    CALL SYS$QIOW ( , %VAL(TTY), %VAL(IO$_WRITEVBLK),
	1			IOSB, , , %REF(LINE), %VAL(LINE_LEN),
	2			, %VAL('01000000'X), , )
			    LINE_LEN = 0
		        END IF
		    END DO
C
	        ELSE IF ( PTDBUF(1).STATUS .NE. SS$_ABORT .AND.
	1		  PTDBUF(1).STATUS .NE. SS$_CANCEL ) THEN
C
C		    Error is not one of the 'expected' cases.
C
		    TYPE*,'Error in PTD$READ:',PTDBUF(1).STATUS,PTDBUF(1).LENGTH
	        END IF
C
C		Check read state and either start reading next buffer or
C		get user input.
C
		IF ( READ_STATE .EQ. 0 ) THEN
C
C	            Initialize buffer for next read and queue it.
C
	            PTDBUF(1).STATUS = 0
	            PTDBUF(1).LENGTH = 0
		    STATUS = PTD$READ (%VAL(EF), %VAL(PTY), 
	1		UPFE_QUEUE_EVENT, 0, PTDBUF(1), %VAL(500) )
CC	TYPE*,'Started new PTD$READ...', status
		    IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
		    READ_ACTIVE = .TRUE.
		ELSE
		    READ_ACTIVE = .FALSE.
C
C		    read input line.
C
150		    PROMPT_LEN = LINE_LEN
		    IF ( PROMPT_LEN .GT. 0 ) THEN
		        PROMPT = LINE(:PROMPT_LEN)
		        STATUS = SMG$READ_COMPOSED_LINE ( KEYBOARD, KEYTABLE,
	1		PTDBUF(2).DATA, PROMPT(:PROMPT_LEN), PTDBUF(2).LENGTH )
		    ELSE
		        STATUS = SMG$READ_COMPOSED_LINE ( KEYBOARD, KEYTABLE,
	1		    PTDBUF(2).DATA, 'upfe> ', PTDBUF(2).LENGTH )
		    END IF
C
C		    Append either <CR> or <EOF> to buffer
C
		    IF ( STATUS ) THEN
		        LINE_LEN = PTDBUF(2).LENGTH + 1
		        PTDBUF(2).DATA(LINE_LEN:LINE_LEN) = CHAR(13)
		    ELSE
			LINE_LEN = 1
		        PTDBUF(2).DATA(LINE_LEN:LINE_LEN) = CHAR(26)
		    END IF
C
C		    Send data line.
C
		    DO WHILE ( READ_STATE .EQ. 1 .AND. LINE_LEN .GT. 0 )
		        PTDBUF(2).STATUS = 0
		        PTDBUF(2).LENGTH = 0
		        PTDBUF(3).STATUS = 0
		        PTDBUF(3).LENGTH = 0
		        STATUS = PTD$WRITE ( %VAL(PTY), , , PTDBUF(2),
	1		    %VAL(LINE_LEN), PTDBUF(3), %VAL(500) )
C
			J = PTDBUF(2).LENGTH
			LINE_LEN = LINE_LEN - J
			DO I = 1, LINE_LEN
			    J = J + 1
			    PTDBUF(2).DATA(I:I) = PTDBUF(2).DATA(J:J)
			END DO
C
		        IF ( PTDBUF(2).STATUS .EQ. SS$_DATAOVERUN ) THEN
C
C			    Typeahead buffer is full, stall until driver is
C			    ready.
C
			    IF ( LINE_LEN .GT. 0 ) READ_STATE = 2
		        END IF
		    END DO
C
		END IF
	    ELSE IF ( EVENT .EQ. 1 ) THEN 
C
C		Read started, take what is left over in the line buffer
C		as a prompt.
C
		READ_STATE = 1
	        CALL PTD$CANCEL ( %VAL(PTY) )
C
	    ELSE IF ( EVENT .EQ. 2 ) THEN 
C
C		Read finished writing prompt.
C
		READ_STATE = 1
C
	    ELSE IF ( EVENT .EQ. 3 ) THEN 
C
C		Read finish, get more data from client.
C
		READ_STATE = 0
		IF ( .NOT. READ_ACTIVE ) THEN
	            PTDBUF(1).STATUS = 0
	            PTDBUF(1).LENGTH = 0
		    STATUS = PTD$READ (%VAL(EF), %VAL(PTY), 
	1		UPFE_QUEUE_EVENT, 0, PTDBUF(1), %VAL(500) )
		    IF ( .NOT. STATUS ) CALL EXIT ( STATUS )
		    READ_ACTIVE = .TRUE.
		END IF
C
	    ELSE IF ( EVENT .EQ. 4 ) THEN 
C
C		Change in terminal characteristics, flush line buffer.
C		Anything that goes changing things is probably trouble.
C
		CALL SYS$QIOW ( , %VAL(TTY), %VAL(IO$_WRITEVBLK),
	1			IOSB, , , %REF(LINE), %VAL(LINE_LEN),
	2			, , , )
		LINE_LEN = 0
	        STATUS = SYS$QIOW ( , %VAL(PTY), %VAL(IO$_SENSEMODE), IOSB,
	1		, , NEW_CHAR, %VAL(20), , , , )
		NEW_CHAR(2) = IEOR(TT_CHAR(2),NEW_CHAR(2))
		NEW_CHAR(3) = IEOR(TT_CHAR(3),NEW_CHAR(3))
		IF ( IAND(NEW_CHAR(3),TT2$M_APP_KEYPAD) .NE. 0 ) THEN
		    TT_CHAR(3) = IEOR ( TT_CHAR(3), TT2$M_APP_KEYPAD )
	    	    NEW_STATE = (IAND(TT_CHAR(3),TT2$M_APP_KEYPAD) .NE. 0)
		    CALL SMG$SET_KEYPAD_MODE ( KEYBOARD, NEW_STATE )
		    NEW_CHAR(3) = IEOR ( NEW_CHAR(3), TT2$M_APP_KEYPAD )
		END IF
		IF ( NEW_CHAR(2) .NE. 0 .OR. NEW_CHAR(3) .NE. 0 ) THEN
C
C		    Allow a selected set of characteristics to be modified.
C
		    IF ( IAND(NEW_CHAR(2),TT$M_NOBRDCST) .NE. 0 )
	1		TT_CHAR(2) = IEOR ( TT_CHAR(2), TT$M_NOBRDCST )
		    IF ( IAND(NEW_CHAR(2),TT$M_LOWER) .NE. 0 )
	1		TT_CHAR(2) = IEOR ( TT_CHAR(2), TT$M_LOWER )
		    IF ( IAND(NEW_CHAR(2),TT$M_NOECHO) .NE. 0 )
	1		TT_CHAR(2) = IEOR ( TT_CHAR(2), TT$M_NOECHO )
		    IF ( IAND(NEW_CHAR(2),TT$M_WRAP) .NE. 0 )
	1		TT_CHAR(2) = IEOR ( TT_CHAR(2), TT$M_WRAP )
C
		    STATUS = SYS$QIOW ( , %VAL(TTY), %VAL(IO$_SETMODE),
	1			IOSB, , , TT_CHAR, %VAL(12), , , , )
		END IF
C
	    ELSE IF ( EVENT .EQ. 5 ) THEN
C
C		The driver sent an XON, continue sending data.
C
		DO WHILE ( READ_STATE .EQ. 2 .AND. LINE_LEN .GT. 0 )
		    PTDBUF(2).STATUS = 0
		    PTDBUF(2).LENGTH = 0
		    PTDBUF(3).STATUS = 0
		    PTDBUF(3).LENGTH = 0
		    STATUS = PTD$WRITE ( %VAL(PTY), , , PTDBUF(2),
	1		    %VAL(LINE_LEN), PTDBUF(3), %VAL(500) )
C
		    J = PTDBUF(2).LENGTH
		    LINE_LEN = LINE_LEN - J
		    DO I = 1, LINE_LEN
			J = J + 1
			PTDBUF(2).DATA(I:I) = PTDBUF(2).DATA(J:J)
		    END DO
C
		    IF ( PTDBUF(2).STATUS .EQ. SS$_DATAOVERUN ) THEN
C
C			Typeahead buffer is full, stall until driver is
C			ready.
C
			READ_STATE = 3
		    END IF
		END DO
C
C		If we got another XOFF, go back to stalled state.
C
		IF ( READ_STATE .EQ. 3 ) READ_STATE = 2
C
	    ELSE IF ( EVENT .EQ. 6 ) THEN
C
C		User deassigned channel, cancel any reads  exit program.
C
		CALL PTD$CANCEL(%VAL(PTY))
		CALL SYS$SYNCH ( %VAL(EF), PTDBUF(1) )
		CALL EXIT ( STATUS )
	    END IF
	END DO
C
	END
C---------------------------------------------------------------------------
C
	INTEGER FUNCTION UPFE_PTY_RUNDOWN ( REASON, PTY )
C+
C	Exit handler for  PTY.
C	Rundown the psuedo terminal control connection.  There is currently
C	a bug in the driver that forces us to take extra action during the
C	rundown to prevent attrition of the process's BYTLM
C
C	Input:
C	    PTY		Controller channel for terminal assigned by PTD$CREATE.
C			We assume that there is no pending activity.
C
C-
	IMPLICIT NONE
	INTEGER REASON, PTY, LIB$GETDVI, STATUS, PTD$DELETE, UNIT
	INTEGER*2 IOSB(4)
	CHARACTER DEVICE*64
	INCLUDE '($DVIDEF)'
	INCLUDE '($IODEF)'
C
C	Get the device name
C
CC	UPFE_PTY_RUNDOWN = LIB$GETDVI ( DVI$_DEVNAM, PTY, , , DEVICE )
CC	IF ( .NOT. UPFE_PTY_RUNDOWN ) GOTO 400
C
C	Delete the PTY and wait for the driver to detect it and properly 
C	cleanup the resources.  If this process dies before the device UCB is 
C	deleted, the job doesn't get the byte limit back.  
C
	UPFE_PTY_RUNDOWN = PTD$DELETE ( %VAL(PTY) )
	IF ( .NOT. UPFE_PTY_RUNDOWN ) GOTO 400
C
CC	CALL LIB$WAIT ( 0.5 )
CC	DO WHILE ( LIB$GETDVI ( DVI$_UNIT, , DEVICE, UNIT ) )
CC	    CALL LIB$WAIT ( 0.5 )
CC	END DO
C
400	CONTINUE
	UPFE_PTY_RUNDOWN = 1
C
	END
C------------------------------------------------------------------------
C
	SUBROUTINE UPFE_CONTROL_C_AST ( AST )
C+
C	AST to handle control C's hit by use so that control-Y ast is
C	not fired.   IF AST argument is non-null, issue another ^C AST,
C	otherwise force an exit
C-
	IMPLICIT NONE
	EXTERNAL AST
	INTEGER STATUS, SYS$QIOW, IOSB(2), SYS$FORCEX
C
	INTEGER PTY, TTY, KEYBOARD, KEYTABLE, AST
	LOGICAL IS_TERMINAL 
	COMMON /UPFE_PTY/ PTY, TTY, IS_TERMINAL, KEYBOARD, KEYTABLE
	INCLUDE '($IODEF)'
	IF ( %LOC(AST) .EQ. 0 ) THEN
	    STATUS = SYS$FORCEX ( , , %VAL(1) )
	ELSE
	     STATUS = SYS$QIOW ( , %VAL(TTY), 
	1	%VAL(IO$_SETMODE+IO$M_CTRLCAST), IOSB, , ,
	2	AST, AST, , , , )
	END IF
	END
C
	SUBROUTINE SHOW_QUO ( TEXT )
	CHARACTER*(*) TEXT
	INCLUDE '($JPIDEF)'
	INTEGER BYTLM
	CALL LIB$GETJPI ( JPI$_BYTLM, , , BYTLM )
	TYPE*,TEXT, BYTLM
	END
C-----------------------------------------------------------------
C
	SUBROUTINE UPFE_GRAPHICS_INTERCEPT ( GSTATE, BUFFER )
C+
C	Dummy routine stub.  The mission of this routine is to monitor
C	the output stream and intercept and emulate graphics commands.
C-
	INTEGER GSTATE, BUFFER(128)
	END
$!
$ RETURN
