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