/* $XFree86: xc/programs/xterm/vms.c,v 1.1 2000/02/08 17:19:44 dawes 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 from VMS.C * Forced device type to be VT102 since that is * what we are emulating. */ #include #include #include #include #include "ptyx.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); int tt_write(char *tt_write_buf,int size); 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 = &term->screen; 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(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(); } /* * 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(!(term->screen.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((term->screen.max_row != tt_mode.length) || (term->screen.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; }