xenocara/app/xterm/vms.c

725 lines
18 KiB
C

/* $XTermId: vms.c,v 1.13 2010/10/11 08:05:35 tom Exp $ */
/* vms.c
*
* This module contains the VMS version of the routine SPAWN (from the module
* MAIN.C) and the routines that do IO to the pseudo terminal.
*
* Modification History:
* Stephan Jansen 1-Mar-1990 Original version
* Hal R. Brand 5-Sep-1990 Added code to propagate DECW$DISPLAY
* Aaron Leonard 11-Sep-1990 Fix string descriptor lengths
* Stephan Jansen 2-Dec-1991 Modify to use new Pseudo terminal drivers
* (patterned after photo.c by Forrest A. Kenney)
* Patrick Mahan 7-Jan-1991 Removed reference to <dvidef.h> from VMS.C
* Forced device type to be VT102 since that is
* what we are emulating.
*/
#include <libdef.h>
#include <lnmdef.h>
#include <stdio.h>
#include <string.h>
#include "xterm.h"
#include "data.h"
#include "vms.h"
#define PTD$C_SEND_XON 0 /* Pseudo Terminal Driver event */
#define PTD$C_SEND_BELL 1
#define PTD$C_SEND_XOFF 2
#define PTD$C_STOP_OUTPUT 3
#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
#define BUFFERS 6
#define PAGE 512
typedef struct tt_buffer
{
unsigned int flink;
unsigned int blink;
short int status;
short int length;
char data[VMS_TERM_BUFFER_SIZE];
} TT_BUF_STRUCT;
TT_BUF_STRUCT *tt_w_buff;
struct q_head _align(QUADWORD) buffer_queue = (0,0);
struct q_head _align(QUADWORD) read_queue = (0,0);
static char tt_name[64];
static $DESCRIPTOR (tt_name_desc, &tt_name);
static char ws_name[64];
static $DESCRIPTOR (ws_name_desc, &ws_name);
static struct tt_char {
char class;
char type;
short int page_width;
char characteristics[3];
char length;
int extended;
} tt_mode, tt_chars, orig_tt_chars;
struct mem_region
{
TT_BUF_STRUCT *start;
TT_BUF_STRUCT *end;
} ret_addr;
int read_stopped = False;
int write_stopped = False;
int tt_width;
int tt_length;
int tt_changed;
int tt_pasting=False; /* drm */
int tt_new_output=False; /* Cleared by flushlog(), set whenever something new
goes to the screen through tt_write */
int trnlnm(char *in,int id,char *out);
void spawn (void);
static void tt_echo_ast(TT_BUF_STRUCT *buff_addr);
static void tt_read_ast(TT_BUF_STRUCT *buff_addr);
/*
static void tt_start_read(void);
*/
void tt_start_read(void);
int tt_read(char *buffer);
static void send_xon(void);
static void send_xoff(void);
static void send_bell(void);
static void char_change(void);
static void freeBuff (TT_BUF_STRUCT *buff_addr);
TT_BUF_STRUCT *getBuff(void);
static void CloseDown(int exit_status);
static void mbx_read_ast(void);
static void mbx_read(void);
#define DESCRIPTOR(name,string) struct dsc$descriptor_s name = \
{ strlen(string), DSC$K_DTYPE_T, DSC$K_CLASS_S, string }
int trnlnm(char *in, int id, char *out)
{
int status, num, len, attr = LNM$M_CASE_BLIND, foo = id;
short outlen;
struct itemlist
{
short buffer_length;
short item_code;
char *buffer_addr;
int *return_length;
} itmlst[] =
{
4 , LNM$_INDEX , &foo, 0,
255, LNM$_STRING , out , &outlen,
4 , LNM$_MAX_INDEX, &num, &len,
0 , 0
};
DESCRIPTOR(lognam,in);
DESCRIPTOR(tabnam,"LNM$DCL_LOGICAL");
status = sys$trnlnm(&attr,&tabnam,&lognam,0,itmlst);
if(status != SS$_NORMAL) return(-1); /* error status */
out[outlen] = 0; /* terminate the output string */
return(++num); /* return number of translations */
}
static int pty;
static int Xsocket;
void spawn (void)
{
int status;
static $DESCRIPTOR (dtime, "0 00:00:00.01");
static int delta[2];
register TScreen *screen = TScreenOf(term);
static struct IOSB iosb;
static unsigned int flags;
static unsigned int uic;
static char imagename[64];
static int privs;
static $DESCRIPTOR(device, "FTA0:");
static int type;
static int class;
static int devdepend;
static int mem_size;
int i;
/* if pid and mbx_chan are nonzero then close them in CloseDown() */
pid = 0;
mbx_chan = 0;
status = SYS$EXPREG (BUFFERS, &ret_addr, 0, 0);
if(!(status & SS$_NORMAL)) lib$signal(status);
tt_w_buff = (char *)ret_addr.end - PAGE + 1;
/* use one buffer for writing, the reset go in the free buffer queue */
for(i=0; i < BUFFERS-1; i++)
{
freeBuff((char *)ret_addr.start +i*PAGE);
}
/* avoid double MapWindow requests, for wm's that care... */
XtSetMappedWhenManaged( screen->TekEmu ? XtParent(tekWidget) :
XtParent(term), False );
/* Realize the Tek or VT widget, depending on which mode we're in.
If VT mode, this calls VTRealize (the widget's Realize proc) */
XtRealizeWidget (screen->TekEmu ? XtParent(tekWidget) :
XtParent(term));
/* get the default device characteristics of the pseudo terminal */
itemlist[0].buflen = 4;
itemlist[0].code = DVI$_DEVTYPE;
itemlist[0].buffer = &type;
itemlist[0].return_addr = &tt_name_desc.dsc$w_length;
itemlist[1].buflen = 4;
itemlist[1].code = DVI$_DEVCLASS;
itemlist[1].buffer = &class;
itemlist[1].return_addr = &tt_name_desc.dsc$w_length;
itemlist[2].buflen = 4;
itemlist[2].code = DVI$_DEVDEPEND;
itemlist[2].buffer = &devdepend;
itemlist[2].return_addr = &tt_name_desc.dsc$w_length;
itemlist[3].buflen = 4;
itemlist[3].code = DVI$_DEVDEPEND2;
itemlist[3].buffer = &tt_chars.extended;
itemlist[3].return_addr = &tt_name_desc.dsc$w_length;
itemlist[4].buflen = 0;
itemlist[4].code = 0;
status = sys$getdviw(0,0,&device,&itemlist,&iosb,0,0,0);
if(!(status & SS$_NORMAL)) lib$signal(status);
if(!(iosb.status & SS$_NORMAL)) lib$signal(iosb.status);
tt_chars.type = DT$_VT102; /* XTerm supports VT102 mode */
tt_chars.class = class;
tt_chars.page_width = screen->max_col+1;
tt_chars.length = screen->max_row+1;
/* copy the default char's along with the created window size */
bcopy(&devdepend, &tt_chars.characteristics, 3);
tt_chars.extended |= TT2$M_ANSICRT | TT2$M_AVO | TT2$M_DECCRT;
/* create the pseudo terminal with the proper char's */
status = ptd$create(&tt_chan,0,&tt_chars,12,0,0,0,&ret_addr);
if(!(status & SS$_NORMAL)) lib$signal(status);
/* get the device name of the Pseudo Terminal */
itemlist[0].buflen = 64;
itemlist[0].code = DVI$_DEVNAM;
itemlist[0].buffer = &tt_name;
itemlist[0].return_addr = &tt_name_desc.dsc$w_length;
/* terminate the list */
itemlist[1].buflen = 0;
itemlist[1].code = 0;
status = sys$getdviw(0,tt_chan,0,&itemlist,&iosb,0,0,0);
if(!(status & SS$_NORMAL)) CloseDown(status);
if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);
/*
* set up AST's for XON, XOFF, BELL and characteristics change.
*/
status = ptd$set_event_notification(tt_chan,&send_xon,0,0,PTD$C_SEND_XON);
if(!(status & SS$_NORMAL)) CloseDown(status);
status = ptd$set_event_notification(tt_chan,&send_xoff,0,0,PTD$C_SEND_XOFF);
if(!(status & SS$_NORMAL)) CloseDown(status);
status = ptd$set_event_notification(tt_chan,&send_bell,0,0,PTD$C_SEND_BELL);
if(!(status & SS$_NORMAL)) CloseDown(status);
status = ptd$set_event_notification(tt_chan,&char_change,0,0,PTD$C_CHAR_CHANGED);
if(!(status & SS$_NORMAL)) CloseDown(status);
/* create a mailbox for the detached process to detect hangup */
status = sys$crembx(0,&mbx_chan,ACC$K_TERMLEN,0,255,0,0);
if(!(status & SS$_NORMAL)) CloseDown(status);
/*
* get the device unit number for created process completion
* status to be sent to.
*/
itemlist[0].buflen = 4;
itemlist[0].code = DVI$_UNIT;
itemlist[0].buffer = &mbxunit;
itemlist[0].return_addr = 0;
/* terminate the list */
itemlist[1].buflen = 0;
itemlist[1].code = 0;
status = sys$getdviw(0,mbx_chan,0,&itemlist,&iosb,0,0,0);
if(!(status & SS$_NORMAL)) CloseDown(status);
if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);
tt_start_read();
/*
* find the current process's UIC so that it can be used in the
* call to sys$creprc
*/
itemlist[0].buflen = 4;
itemlist[0].code = JPI$_UIC;
itemlist[0].buffer = &uic;
itemlist[0].return_addr = 0;
/* terminate the list */
itemlist[1].buflen = 0;
itemlist[1].code = 0;
status = sys$getjpiw(0,0,0,&itemlist,0,0,0);
if(!(status & SS$_NORMAL)) CloseDown(status);
/* Complete a descriptor for the WS (DECW$DISPLAY) device */
trnlnm("DECW$DISPLAY",0,ws_name);
ws_name_desc.dsc$w_length = strlen(ws_name);
/* create the process */
/* Set sys$error to be the WS (DECW$DISPLAY) device. LOGINOUT */
/* has special code for DECWINDOWS that will: */
/* 1) do a DEFINE/JOB DECW$DISPLAY 'f$trnlnm(sys$error)' */
/* 2) then redefine SYS$ERROR to match SYS$OUTPUT! */
/* This will propogate DECW$DISPLAY to the XTERM process!!! */
/* Thanks go to Joel M Snyder who posted this info to INFO-VAX */
flags = PRC$M_INTER | PRC$M_NOPASSWORD | PRC$M_DETACH;
status = sys$creprc(&pid,&image,&tt_name_desc,&tt_name_desc,
&ws_name_desc,0,0,0,4,uic,mbxunit,flags);
if(!(status & SS$_NORMAL)) CloseDown(status);
/* hang a read on the mailbox waiting for completion */
mbx_read();
/* set time value and schedule a periodic wakeup (every 1/100 of a second)
* this is used to prevent the controlling process from using up all the
* CPU. The controlling process will hibernate at strategic points in
* the program when it is just waiting for input.
*/
status = sys$bintim(&dtime,&delta);
if (!(status & SS$_NORMAL)) CloseDown(status);
status = sys$schdwk(0,0,&delta,&delta);
if (!(status & SS$_NORMAL)) CloseDown(status);
/*
* This is rather funky, but it saves me from having to totally
* rewrite some parts of the code (namely in_put in module CHARPROC.C)
*/
pty = 1;
screen->respond = pty;
pty_mask = 1 << pty;
Select_mask = pty_mask;
X_mask = 1 << Xsocket;
}
/*
* This routine handles completion of write with echo. It takes the
* echo buffer and puts it on the read queue. It will then be processed
* by the routine tt_read. If the echo buffer is empty, it is put back
* on the free buffer queue.
*/
static void tt_echo_ast(TT_BUF_STRUCT *buff_addr)
{
int status;
if (buff_addr->length != 0)
{
status = LIB$INSQTI(buff_addr, &read_queue);
if((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
{
CloseDown(status);
}
}
else
{
freeBuff(buff_addr);
}
}
/*
* This routine writes to the pseudo terminal. If there is a free
* buffer then write with an echo buffer completing asyncronously, else
* write syncronously using the buffer reserved for writing. All errors
* are fatal, except DATAOVERUN and DATALOST,these errors can be ignored.
CAREFUL! Whatever calls this must NOT pass more than VMS_TERM_BUFFER_SIZE
bytes at a time. This definition has been moved to VMS.H
*/
int tt_write(const char *tt_write_buf, int size)
{
int status;
TT_BUF_STRUCT *echoBuff;
/* if writing stopped, return 0 until Xon */
if(write_stopped) return (0);
memmove(&tt_w_buff->data,tt_write_buf,size);
echoBuff = getBuff();
if (echoBuff != LIB$_QUEWASEMP)
{
status = PTD$WRITE (tt_chan, &tt_echo_ast, echoBuff,
&tt_w_buff->status, size,
&echoBuff->status, VMS_TERM_BUFFER_SIZE);
}
else
{
status = PTD$WRITE (tt_chan, 0, 0, &tt_w_buff->status, size, 0, 0);
}
if (status & SS$_NORMAL)
{
if ((tt_w_buff->status != SS$_NORMAL) &&
(tt_w_buff->status != SS$_DATAOVERUN) &&
(tt_w_buff->status != SS$_DATALOST))
{
CloseDown(tt_w_buff->status);
}
}
else
{
CloseDown(status);
}
return(size);
}
/*
* This routine is called when a read to the pseudo terminal completes.
* Put the newly read buffer onto the read queue. It will be processed
* and freed in the routine tt_read.
*/
static void tt_read_ast(TT_BUF_STRUCT *buff_addr)
{
int status;
if (buff_addr->status & SS$_NORMAL)
{
status = LIB$INSQTI(buff_addr, &read_queue);
if ((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
{
CloseDown(status);
}
}
else
CloseDown(buff_addr->status);
tt_start_read();
sys$wake(0,0);
return;
}
/*
* If there is a free buffer on the buffer queue then Start a read from
* the pseudo terminal, otherwise set a flag, the reading will be restarted
* in the routine freeBuff when a buffer is freed.
*/
void tt_start_read(void)
{
int status;
static int size;
TT_BUF_STRUCT *buff_addr;
buff_addr = getBuff();
if (buff_addr != LIB$_QUEWASEMP)
{
if(!tt_pasting){
status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
&buff_addr->status, VMS_TERM_BUFFER_SIZE);
if ((status & SS$_NORMAL) != SS$_NORMAL)
{
CloseDown(status);
}
}
}
else
{
read_stopped = True;
}
return;
}
/*
* Get data from the pseudo terminal. Return the data from the first item
* on the read queue, and put that buffer back onto the free buffer queue.
* Return the length or zero if the read queue is empty.
*
*/
int tt_read(char *buffer)
{
TT_BUF_STRUCT *read_buff;
int status;
int len;
status = LIB$REMQHI(&read_queue, &read_buff);
if(status == LIB$_QUEWASEMP){
return(0);
}
else if (status & SS$_NORMAL)
{
len = read_buff->length;
memmove(buffer,&read_buff->data,len);
freeBuff(read_buff);
tt_new_output=True; /* DRM something will be written */
}
else
CloseDown(status);
return(len);
}
/*
* if xon then it is safe to start writing again.
*/
static void send_xon(void)
{
write_stopped = False;
}
/*
* If Xoff then stop writing to the pseudo terminal until you get Xon.
*/
static void send_xoff(void)
{
write_stopped = True;
}
/*
* Beep the terminal to let the user know data will be lost because
* of too much data.
*/
static void send_bell(void)
{
Bell(term);
}
/*
* if the pseudo terminal's characteristics change, check to see if the
* page size changed. If it did, resize the widget, otherwise, ignore
* it! This routine just gets the new term dimensions and sets a flag
* to indicate the term chars have changed. The widget gets resized in
* the routine in_put in the module CHARPROC.C. You cant resize the
* widget in this routine because this is an AST and X is not reenterent.
*/
static void char_change(void)
{
int status;
/*
* Dont do anything if in Tek mode
*/
if(!(TScreenOf(term)->TekEmu))
{
status = sys$qiow(0,tt_chan,IO$_SENSEMODE,0,0,0,&tt_mode,8,0,0,0,0);
if(!(status & SS$_NORMAL)) CloseDown(status);
if((TScreenOf(term)->max_row != tt_mode.length) ||
(TScreenOf(term)->max_col != tt_mode.page_width))
{
tt_length = tt_mode.length;
tt_width = tt_mode.page_width;
tt_changed = True;
}
}
}
/*
* Put a free buffer back onto the buffer queue. If reading was
* stopped for lack of free buffers, start reading again.
*/
static void freeBuff (TT_BUF_STRUCT *buff_addr)
{
int ast_stat;
int status;
ast_stat = SYS$SETAST(0);
if (!read_stopped)
{
LIB$INSQHI(buff_addr, &buffer_queue);
}
else
{
status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
&buff_addr->status, VMS_TERM_BUFFER_SIZE);
if (status & SS$_NORMAL)
{
read_stopped = False;
}
else
{
CloseDown(status);
}
}
if (ast_stat == SS$_WASSET) ast_stat = SYS$SETAST(1);
}
/*
* return a free buffer from the buffer queue.
*/
TT_BUF_STRUCT *getBuff(void)
{
int status;
TT_BUF_STRUCT *buff_addr;
status = LIB$REMQHI(&buffer_queue, &buff_addr);
if (status & SS$_NORMAL)
{
return(buff_addr);
}
else
{
return(status);
}
}
/*
* Close down and exit. Kill the detached process (if it still
* exists), deassign mailbox channell (if assigned), cancel any
* waiting IO to the pseudo terminal and delete it, exit with any
* status information.
*/
static void CloseDown(int exit_status)
{
int status;
/* if process has not terminated, do so now! */
if(pid != 0)
{
status = sys$forcex(&pid,0,0);
if(!(status & SS$_NORMAL)) lib$signal(status);
}
/* if mbx_chan is assigned, deassign it */
if(mbx_chan != 0)
{
sys$dassgn(mbx_chan);
}
/* cancel pseudo terminal IO requests */
status = ptd$cancel(tt_chan);
if(!(status & SS$_NORMAL)) lib$signal(status);
/* delete pseudo terminal */
status = ptd$delete(tt_chan);
if(!(status & SS$_NORMAL)) lib$signal(status);
if(!(exit_status & SS$_NORMAL)) lib$signal(exit_status);
exit(1);
}
/*
* This routine gets called when the detached process terminates (for
* whatever reason). The mailbox buffer has final exit status. Close
* down and exit.
*/
static void mbx_read_ast(void)
{
int status;
pid = 0;
status = mbx_read_iosb.status;
if (!(status & SS$_NORMAL)) CloseDown(status);
status = (unsigned long int) mbx_buf.acc$l_finalsts;
if (!(status & SS$_NORMAL)) CloseDown(status);
CloseDown(1);
}
/*
* This routine starts a read on the mailbox associated with the detached
* process. The AST routine gets called when the detached process terminates.
*/
static void mbx_read(void)
{
int status;
static int size;
size = ACC$K_TERMLEN;
status = sys$qio(0,mbx_chan,
IO$_READVBLK,
&mbx_read_iosb,
&mbx_read_ast,
0,
&mbx_buf,
size,0,0,0,0);
if (!(status & SS$_NORMAL)) CloseDown(status);
return;
}