1 /* $XTermId: vms.c,v 1.14 2020/01/18 18:32:45 tom Exp $ */
2 
3 /*  vms.c
4  *
5  * This module contains the VMS version of the routine SPAWN (from the module
6  * MAIN.C) and the routines that do IO to the pseudo terminal.
7  *
8  * Modification History:
9  * Stephan Jansen 1-Mar-1990  Original version
10  * Hal R. Brand   5-Sep-1990  Added code to propagate DECW$DISPLAY
11  * Aaron Leonard 11-Sep-1990  Fix string descriptor lengths
12  * Stephan Jansen 2-Dec-1991  Modify to use new Pseudo terminal drivers
13  *                            (patterned after photo.c by Forrest A. Kenney)
14  * Patrick Mahan  7-Jan-1991  Removed reference to <dvidef.h> from VMS.C
15  *			      Forced device type to be VT102 since that is
16  *			      what we are emulating.
17  */
18 
19 #include <libdef.h>
20 #include <lnmdef.h>
21 
22 #include <stdio.h>
23 #include <string.h>
24 
25 #include "xterm.h"
26 #include "data.h"
27 #include "vms.h"
28 
29 #define PTD$C_SEND_XON		0	/* Pseudo Terminal Driver event      */
30 #define PTD$C_SEND_BELL		1
31 #define PTD$C_SEND_XOFF 	2
32 #define PTD$C_STOP_OUTPUT	3
33 #define PTD$C_RESUME_OUTPUT 	4
34 #define PTD$C_CHAR_CHANGED 	5
35 #define PTD$C_ABORT_OUTPUT 	6
36 #define PTD$C_START_READ 	7
37 #define PTD$C_MIDDLE_READ 	8
38 #define PTD$C_END_READ 		9
39 #define PTD$C_ENABLE_READ 	10
40 #define PTD$C_DISABLE_READ 	11
41 #define PTD$C_MAX_EVENTS 	12
42 
43 #define	BUFFERS		        6
44 #define	PAGE			512
45 
46 typedef struct	tt_buffer
47 {
48 unsigned int	flink;
49 unsigned int	blink;
50 short	int	status;
51 short	int	length;
52 char		data[VMS_TERM_BUFFER_SIZE];
53 } TT_BUF_STRUCT;
54 
55 TT_BUF_STRUCT		*tt_w_buff;
56 struct	q_head		 _align(QUADWORD)	buffer_queue = (0,0);
57 struct	q_head		 _align(QUADWORD)	read_queue = (0,0);
58 
59 static char          tt_name[64];
60 static $DESCRIPTOR   (tt_name_desc, &tt_name);
61 
62 static char          ws_name[64];
63 static $DESCRIPTOR   (ws_name_desc, &ws_name);
64 
65 static struct        tt_char {
66    char        class;
67    char        type;
68    short int   page_width;
69    char        characteristics[3];
70    char        length;
71    int         extended;
72  } tt_mode, tt_chars, orig_tt_chars;
73 
74 struct mem_region
75 {
76   TT_BUF_STRUCT *start;
77   TT_BUF_STRUCT *end;
78 } ret_addr;
79 
80 int read_stopped = False;
81 int write_stopped = False;
82 
83 int tt_width;
84 int tt_length;
85 int tt_changed;
86 int tt_pasting=False;         /* drm */
87 int tt_new_output=False;      /* Cleared by flushlog(), set whenever something new
88    goes to the screen through tt_write */
89 
90 int trnlnm(char *in,int id,char *out);
91 void spawn (void);
92 
93 static void tt_echo_ast(TT_BUF_STRUCT *buff_addr);
94 static void tt_read_ast(TT_BUF_STRUCT *buff_addr);
95 
96 /*
97 static void tt_start_read(void);
98 */
99 void tt_start_read(void);
100 int tt_read(char *buffer);
101 static void send_xon(void);
102 static void send_xoff(void);
103 static void send_bell(void);
104 static void char_change(void);
105 static void freeBuff (TT_BUF_STRUCT *buff_addr);
106 TT_BUF_STRUCT *getBuff(void);
107 static void CloseDown(int exit_status);
108 static void mbx_read_ast(void);
109 static void mbx_read(void);
110 
111 
112 
113 #define DESCRIPTOR(name,string) struct dsc$descriptor_s name = \
114 { strlen(string), DSC$K_DTYPE_T, DSC$K_CLASS_S, string }
115 
trnlnm(char * in,int id,char * out)116 int trnlnm(char *in, int id, char *out)
117 {
118   int status, num, len, attr = LNM$M_CASE_BLIND, foo = id;
119   short outlen;
120   struct itemlist
121     {
122       short buffer_length;
123       short item_code;
124       char  *buffer_addr;
125       int   *return_length;
126     } itmlst[] =
127       {
128 	4  , LNM$_INDEX    , &foo, 0,
129 	255, LNM$_STRING   , out , &outlen,
130 	4  , LNM$_MAX_INDEX, &num, &len,
131 	0  , 0
132 	};
133   DESCRIPTOR(lognam,in);
134   DESCRIPTOR(tabnam,"LNM$DCL_LOGICAL");
135 
136   status = sys$trnlnm(&attr,&tabnam,&lognam,0,itmlst);
137   if(status != SS$_NORMAL) return(-1);   /* error status */
138   out[outlen] = 0;         /* terminate the output string */
139   return(++num);         /* return number of translations */
140 }
141 
142 static int           pty;
143 static int           Xsocket;
144 
spawn(void)145 void spawn (void)
146 {
147   int                  status;
148   static $DESCRIPTOR   (dtime, "0 00:00:00.01");
149   static int           delta[2];
150   register TScreen     *screen = TScreenOf(term);
151   static struct IOSB   iosb;
152   static unsigned int  flags;
153   static unsigned int  uic;
154   static char          imagename[64];
155   static int           privs;
156   static $DESCRIPTOR(device, "FTA0:");
157   static int           type;
158   static int           class;
159   static int           devdepend;
160   static int           mem_size;
161   int                  i;
162 
163   /* if pid and mbx_chan are nonzero then close them in CloseDown() */
164   pid = 0;
165   mbx_chan = 0;
166 
167   status = SYS$EXPREG (BUFFERS, &ret_addr, 0, 0);
168   if(!(status & SS$_NORMAL)) lib$signal(status);
169 
170   tt_w_buff = (char *)ret_addr.end - PAGE + 1;
171 
172   /* use one buffer for writing, the reset go in the free buffer queue */
173   for(i=0; i < BUFFERS-1; i++)
174     {
175       freeBuff((char *)ret_addr.start +i*PAGE);
176     }
177 
178   /* avoid double MapWindow requests, for wm's that care... */
179   XtSetMappedWhenManaged( screen->TekEmu ? XtParent(tekWidget) :
180 			 XtParent(term), False );
181   /* Realize the Tek or VT widget, depending on which mode we're in.
182      If VT mode, this calls VTRealize (the widget's Realize proc) */
183   XtRealizeWidget (screen->TekEmu ? XtParent(tekWidget) :
184 		   XtParent(term));
185 
186   /* get the default device characteristics of the pseudo terminal */
187 
188   itemlist[0].buflen      = 4;
189   itemlist[0].code        = DVI$_DEVTYPE;
190   itemlist[0].buffer      = &type;
191   itemlist[0].return_addr = &tt_name_desc.dsc$w_length;
192 
193   itemlist[1].buflen      = 4;
194   itemlist[1].code        = DVI$_DEVCLASS;
195   itemlist[1].buffer      = &class;
196   itemlist[1].return_addr = &tt_name_desc.dsc$w_length;
197 
198   itemlist[2].buflen      = 4;
199   itemlist[2].code        = DVI$_DEVDEPEND;
200   itemlist[2].buffer      = &devdepend;
201   itemlist[2].return_addr = &tt_name_desc.dsc$w_length;
202 
203   itemlist[3].buflen      = 4;
204   itemlist[3].code        = DVI$_DEVDEPEND2;
205   itemlist[3].buffer      = &tt_chars.extended;
206   itemlist[3].return_addr = &tt_name_desc.dsc$w_length;
207 
208   itemlist[4].buflen      = 0;
209   itemlist[4].code        = 0;
210 
211 
212   status = sys$getdviw(0,0,&device,&itemlist,&iosb,0,0,0);
213   if(!(status & SS$_NORMAL)) lib$signal(status);
214   if(!(iosb.status & SS$_NORMAL)) lib$signal(iosb.status);
215 
216   tt_chars.type        = DT$_VT102; /* XTerm supports VT102 mode */
217   tt_chars.class       = class;
218   tt_chars.page_width  = screen->max_col+1;
219   tt_chars.length      = screen->max_row+1;
220 
221   /* copy the default char's along with the created window size */
222 
223   bcopy(&devdepend, &tt_chars.characteristics, 3);
224 
225   tt_chars.extended |= TT2$M_ANSICRT | TT2$M_AVO | TT2$M_DECCRT;
226 
227 
228   /* create the pseudo terminal with the proper char's */
229   status = ptd$create(&tt_chan,0,&tt_chars,12,0,0,0,&ret_addr);
230   if(!(status & SS$_NORMAL)) lib$signal(status);
231 
232 
233   /* get the device name of the Pseudo Terminal */
234 
235   itemlist[0].buflen      = 64;
236   itemlist[0].code        = DVI$_DEVNAM;
237   itemlist[0].buffer      = &tt_name;
238   itemlist[0].return_addr = &tt_name_desc.dsc$w_length;
239 
240   /* terminate the list */
241   itemlist[1].buflen      = 0;
242   itemlist[1].code        = 0;
243 
244   status = sys$getdviw(0,tt_chan,0,&itemlist,&iosb,0,0,0);
245   if(!(status & SS$_NORMAL)) CloseDown(status);
246   if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);
247 
248   /*
249    * set up AST's for XON, XOFF, BELL and characteristics change.
250    */
251 
252   status = ptd$set_event_notification(tt_chan,&send_xon,0,0,PTD$C_SEND_XON);
253   if(!(status & SS$_NORMAL)) CloseDown(status);
254 
255   status = ptd$set_event_notification(tt_chan,&send_xoff,0,0,PTD$C_SEND_XOFF);
256   if(!(status & SS$_NORMAL)) CloseDown(status);
257 
258   status = ptd$set_event_notification(tt_chan,&send_bell,0,0,PTD$C_SEND_BELL);
259   if(!(status & SS$_NORMAL)) CloseDown(status);
260 
261   status = ptd$set_event_notification(tt_chan,&char_change,0,0,PTD$C_CHAR_CHANGED);
262   if(!(status & SS$_NORMAL)) CloseDown(status);
263 
264   /* create a mailbox for the detached process to detect hangup */
265 
266   status = sys$crembx(0,&mbx_chan,ACC$K_TERMLEN,0,255,0,0);
267   if(!(status & SS$_NORMAL)) CloseDown(status);
268 
269 
270   /*
271    * get the device unit number for created process completion
272    * status to be sent to.
273    */
274 
275   itemlist[0].buflen      = 4;
276   itemlist[0].code        = DVI$_UNIT;
277   itemlist[0].buffer      = &mbxunit;
278   itemlist[0].return_addr = 0;
279 
280   /* terminate the list */
281   itemlist[1].buflen      = 0;
282   itemlist[1].code        = 0;
283 
284   status = sys$getdviw(0,mbx_chan,0,&itemlist,&iosb,0,0,0);
285   if(!(status & SS$_NORMAL)) CloseDown(status);
286   if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);
287 
288 
289   tt_start_read();
290 
291   /*
292    * find the current process's UIC so that it can be used in the
293    * call to sys$creprc
294    */
295   itemlist[0].buflen = 4;
296   itemlist[0].code = JPI$_UIC;
297   itemlist[0].buffer = &uic;
298   itemlist[0].return_addr = 0;
299 
300   /* terminate the list */
301   itemlist[1].buflen      = 0;
302   itemlist[1].code        = 0;
303 
304   status = sys$getjpiw(0,0,0,&itemlist,0,0,0);
305   if(!(status & SS$_NORMAL)) CloseDown(status);
306 
307   /* Complete a descriptor for the WS (DECW$DISPLAY) device */
308 
309   trnlnm("DECW$DISPLAY",0,ws_name);
310   ws_name_desc.dsc$w_length = strlen(ws_name);
311 
312   /* create the process */
313   /*  Set sys$error to be the WS (DECW$DISPLAY) device. LOGINOUT  */
314   /*  has special code for DECWINDOWS that will:                  */
315   /*    1) do a DEFINE/JOB DECW$DISPLAY 'f$trnlnm(sys$error)'     */
316   /*    2) then redefine SYS$ERROR to match SYS$OUTPUT!           */
317   /*  This will propagate DECW$DISPLAY to the XTERM process!!!    */
318   /*  Thanks go to Joel M Snyder who posted this info to INFO-VAX */
319 
320   flags = PRC$M_INTER | PRC$M_NOPASSWORD | PRC$M_DETACH;
321   status = sys$creprc(&pid,&image,&tt_name_desc,&tt_name_desc,
322 		      &ws_name_desc,0,0,0,4,uic,mbxunit,flags);
323   if(!(status & SS$_NORMAL)) CloseDown(status);
324 
325 
326   /* hang a read on the mailbox waiting for completion */
327   mbx_read();
328 
329 
330 /* set time value and schedule a periodic wakeup (every 1/100 of a second)
331  * this is used to prevent the controlling process from using up all the
332  * CPU.  The controlling process will hibernate at strategic points in
333  * the program when it is just waiting for input.
334  */
335 
336   status = sys$bintim(&dtime,&delta);
337   if (!(status & SS$_NORMAL)) CloseDown(status);
338 
339   status = sys$schdwk(0,0,&delta,&delta);
340   if (!(status & SS$_NORMAL)) CloseDown(status);
341 
342 
343   /*
344    * This is rather funky, but it saves me from having to totally
345    * rewrite some parts of the code (namely in_put in module CHARPROC.C)
346    */
347   pty = 1;
348   screen->respond = pty;
349   pty_mask = 1 << pty;
350   Select_mask = pty_mask;
351   X_mask = 1 << Xsocket;
352 
353 }
354 
355 
356 /*
357  * This routine handles completion of write with echo.  It takes the
358  * echo buffer and puts it on the read queue.  It will then be processed
359  * by the routine tt_read.  If the echo buffer is empty, it is put back
360  * on the free buffer queue.
361  */
362 
tt_echo_ast(TT_BUF_STRUCT * buff_addr)363 static void tt_echo_ast(TT_BUF_STRUCT *buff_addr)
364 {
365   int status;
366 
367   if (buff_addr->length != 0)
368     {
369       status = LIB$INSQTI(buff_addr, &read_queue);
370       if((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
371 	{
372 	  CloseDown(status);
373 	}
374     }
375   else
376     {
377       freeBuff(buff_addr);
378     }
379 }
380 
381 
382 /*
383  * This routine writes to the pseudo terminal.  If there is a free
384  * buffer then write with an echo buffer completing asynchronously, else
385  * write synchronously using the buffer reserved for writing.  All errors
386  *  are fatal, except DATAOVERUN and DATALOST,these errors can be ignored.
387 
388  CAREFUL! Whatever calls this must NOT pass more than VMS_TERM_BUFFER_SIZE
389  bytes at a time.  This definition has been moved to VMS.H
390 
391  */
392 
tt_write(const char * tt_write_buf,int size)393 int tt_write(const char *tt_write_buf, int size)
394 {
395   int status;
396   TT_BUF_STRUCT *echoBuff;
397 
398   /* if writing stopped, return 0 until Xon */
399   if(write_stopped) return (0);
400 
401   memmove(&tt_w_buff->data,tt_write_buf,size);
402 
403   echoBuff = getBuff();
404   if (echoBuff != LIB$_QUEWASEMP)
405     {
406       status = PTD$WRITE (tt_chan, &tt_echo_ast, echoBuff,
407 			  &tt_w_buff->status, size,
408 			  &echoBuff->status, VMS_TERM_BUFFER_SIZE);
409     }
410   else
411     {
412       status = PTD$WRITE (tt_chan, 0, 0, &tt_w_buff->status, size, 0, 0);
413     }
414   if (status & SS$_NORMAL)
415     {
416       if ((tt_w_buff->status != SS$_NORMAL) &&
417 	  (tt_w_buff->status != SS$_DATAOVERUN) &&
418 	  (tt_w_buff->status != SS$_DATALOST))
419 	{
420 	  CloseDown(tt_w_buff->status);
421 	}
422     }
423   else
424     {
425       CloseDown(status);
426     }
427 
428   return(size);
429 }
430 
431 
432 /*
433  * This routine is called when a read to the pseudo terminal completes.
434  * Put the newly read buffer onto the read queue.  It will be processed
435  * and freed in the routine tt_read.
436  */
437 
tt_read_ast(TT_BUF_STRUCT * buff_addr)438 static void tt_read_ast(TT_BUF_STRUCT *buff_addr)
439 {
440   int status;
441 
442   if (buff_addr->status & SS$_NORMAL)
443     {
444       status = LIB$INSQTI(buff_addr, &read_queue);
445       if ((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
446 	{
447 	  CloseDown(status);
448 	}
449     }
450   else
451     CloseDown(buff_addr->status);
452 
453   tt_start_read();
454   sys$wake(0,0);
455   return;
456 }
457 
458 
459 /*
460  * If there is a free buffer on the buffer queue then Start a read from
461  * the pseudo terminal, otherwise set a flag, the reading will be restarted
462  * in the routine freeBuff when a buffer is freed.
463  */
464 
tt_start_read(void)465 void tt_start_read(void)
466 {
467   int status;
468   static int size;
469   TT_BUF_STRUCT *buff_addr;
470 
471   buff_addr = getBuff();
472   if (buff_addr != LIB$_QUEWASEMP)
473     {
474       if(!tt_pasting){
475       status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
476 			 &buff_addr->status, VMS_TERM_BUFFER_SIZE);
477       if ((status & SS$_NORMAL) != SS$_NORMAL)
478 	{
479 	  CloseDown(status);
480 	}
481       }
482       }
483   else
484     {
485       read_stopped = True;
486     }
487   return;
488 }
489 
490 
491 /*
492  * Get data from the pseudo terminal.  Return the data from the first item
493  * on the read queue, and put that buffer back onto the free buffer queue.
494  * Return the length or zero if the read queue is empty.
495  *
496  */
497 
tt_read(char * buffer)498 int tt_read(char *buffer)
499 {
500   TT_BUF_STRUCT *read_buff;
501   int status;
502   int len;
503 
504    status = LIB$REMQHI(&read_queue, &read_buff);
505    if(status == LIB$_QUEWASEMP){
506      return(0);
507    }
508    else if (status & SS$_NORMAL)
509      {
510        len = read_buff->length;
511        memmove(buffer,&read_buff->data,len);
512        freeBuff(read_buff);
513        tt_new_output=True; /* DRM something will be written */
514      }
515    else
516      CloseDown(status);
517 
518    return(len);
519 }
520 
521 
522 /*
523  * if xon then it is safe to start writing again.
524  */
525 
send_xon(void)526 static void send_xon(void)
527 {
528   write_stopped = False;
529 }
530 
531 
532 /*
533  * If Xoff then stop writing to the pseudo terminal until you get Xon.
534  */
send_xoff(void)535 static void send_xoff(void)
536 {
537   write_stopped = True;
538 }
539 
540 
541 
542 /*
543  * Beep the terminal to let the user know data will be lost because
544  * of too much data.
545  */
546 
send_bell(void)547 static void send_bell(void)
548 {
549    Bell(term);
550 }
551 
552 /*
553  * if the pseudo terminal's characteristics change, check to see if the
554  * page size changed.  If it did, resize the widget, otherwise, ignore
555  * it!  This routine just gets the new term dimensions and sets a flag
556  * to indicate the term chars have changed.  The widget gets resized in
557  * the routine in_put in the module CHARPROC.C.  You can't resize the
558  * widget in this routine because this is an AST and X is not reenterent.
559  */
560 
char_change(void)561 static void char_change(void)
562 {
563   int status;
564 
565   /*
566    * Don't do anything if in Tek mode
567    */
568 
569   if(!(TScreenOf(term)->TekEmu))
570     {
571       status = sys$qiow(0,tt_chan,IO$_SENSEMODE,0,0,0,&tt_mode,8,0,0,0,0);
572       if(!(status & SS$_NORMAL)) CloseDown(status);
573 
574       if((TScreenOf(term)->max_row != tt_mode.length) ||
575 	 (TScreenOf(term)->max_col != tt_mode.page_width))
576 	{
577 	  tt_length = tt_mode.length;
578 	  tt_width =  tt_mode.page_width;
579 
580 	  tt_changed = True;
581 
582 	}
583     }
584 }
585 
586 
587 /*
588  * Put a free buffer back onto the buffer queue.  If reading was
589  * stopped for lack of free buffers, start reading again.
590  */
591 
freeBuff(TT_BUF_STRUCT * buff_addr)592 static void freeBuff (TT_BUF_STRUCT *buff_addr)
593 {
594   int ast_stat;
595   int status;
596 
597   ast_stat = SYS$SETAST(0);
598   if (!read_stopped)
599     {
600       LIB$INSQHI(buff_addr, &buffer_queue);
601     }
602   else
603     {
604       status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
605 			 &buff_addr->status, VMS_TERM_BUFFER_SIZE);
606       if (status & SS$_NORMAL)
607 	{
608 	  read_stopped = False;
609 	}
610       else
611 	{
612 	  CloseDown(status);
613 	}
614     }
615   if (ast_stat == SS$_WASSET) ast_stat = SYS$SETAST(1);
616 }
617 
618 
619 /*
620  * return a free buffer from the buffer queue.
621  */
622 
getBuff(void)623 TT_BUF_STRUCT *getBuff(void)
624 {
625   int status;
626   TT_BUF_STRUCT *buff_addr;
627 
628   status = LIB$REMQHI(&buffer_queue, &buff_addr);
629   if (status & SS$_NORMAL)
630     {
631       return(buff_addr);
632     }
633   else
634     {
635       return(status);
636     }
637 }
638 
639 
640 /*
641  * Close down and exit.  Kill the detached process (if it still
642  * exists), deassign mailbox channell (if assigned), cancel any
643  * waiting IO to the pseudo terminal and delete it, exit with any
644  * status information.
645  */
646 
CloseDown(int exit_status)647 static void CloseDown(int exit_status)
648 {
649   int status;
650 
651   /* if process has not terminated, do so now! */
652   if(pid != 0)
653     {
654       status = sys$forcex(&pid,0,0);
655       if(!(status & SS$_NORMAL)) lib$signal(status);
656     }
657 
658   /* if mbx_chan is assigned, deassign it */
659   if(mbx_chan != 0)
660     {
661       sys$dassgn(mbx_chan);
662     }
663 
664   /* cancel pseudo terminal IO requests */
665   status = ptd$cancel(tt_chan);
666   if(!(status & SS$_NORMAL)) lib$signal(status);
667 
668   /* delete pseudo terminal */
669   status = ptd$delete(tt_chan);
670   if(!(status & SS$_NORMAL)) lib$signal(status);
671 
672   if(!(exit_status & SS$_NORMAL)) lib$signal(exit_status);
673 
674   exit(1);
675 
676 }
677 
678 
679 /*
680  * This routine gets called when the detached process terminates (for
681  * whatever reason).  The mailbox buffer has final exit status.  Close
682  * down and exit.
683  */
684 
mbx_read_ast(void)685 static void mbx_read_ast(void)
686 {
687   int status;
688 
689   pid = 0;
690 
691   status = mbx_read_iosb.status;
692   if (!(status & SS$_NORMAL)) CloseDown(status);
693 
694   status = (unsigned long int) mbx_buf.acc$l_finalsts;
695   if (!(status & SS$_NORMAL)) CloseDown(status);
696 
697   CloseDown(1);
698 
699 }
700 
701 
702 /*
703  * This routine starts a read on the mailbox associated with the detached
704  * process.  The AST routine gets called when the detached process terminates.
705  */
706 
mbx_read(void)707 static void mbx_read(void)
708 {
709 int status;
710 static int size;
711 
712    size = ACC$K_TERMLEN;
713    status = sys$qio(0,mbx_chan,
714           IO$_READVBLK,
715           &mbx_read_iosb,
716           &mbx_read_ast,
717           0,
718           &mbx_buf,
719           size,0,0,0,0);
720 
721    if (!(status & SS$_NORMAL)) CloseDown(status);
722 
723    return;
724 }
725