1 /*
2 * %CopyrightBegin%
3 *
4 * Copyright Ericsson AB 1996-2020. All Rights Reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * %CopyrightEnd%
19 */
20 /*
21 * Tty driver that reads one character at the time and provides a
22 * smart line for output.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "erl_driver.h"
30
31 static int ttysl_init(void);
32 static ErlDrvData ttysl_start(ErlDrvPort, char*);
33
34 #ifdef HAVE_TERMCAP /* else make an empty driver that cannot be opened */
35
36 #ifndef WANT_NONBLOCKING
37 #define WANT_NONBLOCKING
38 #endif
39
40 #include "sys.h"
41 #include <ctype.h>
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <signal.h>
46 #include <fcntl.h>
47 #include <limits.h>
48 #include <locale.h>
49 #include <unistd.h>
50 #include <termios.h>
51 #ifdef HAVE_WCWIDTH
52 #include <wchar.h>
53 #endif
54 #ifdef HAVE_FCNTL_H
55 #include <fcntl.h>
56 #endif
57 #ifdef HAVE_SYS_IOCTL_H
58 #include <sys/ioctl.h>
59 #endif
60 #if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H)
61 #define PRIMITIVE_UTF8_CHECK 1
62 #else
63 #include <langinfo.h>
64 #endif
65
66 #if defined IOV_MAX
67 #define MAXIOV IOV_MAX
68 #elif defined UIO_MAXIOV
69 #define MAXIOV UIO_MAXIOV
70 #else
71 #define MAXIOV 16
72 #endif
73
74 #define TRUE 1
75 #define FALSE 0
76
77 /* Termcap functions. */
78 int tgetent(char* bp, char *name);
79 int tgetnum(char* cap);
80 int tgetflag(char* cap);
81 char *tgetstr(char* cap, char** buf);
82 char *tgoto(char* cm, int col, int line);
83 int tputs(char* cp, int affcnt, int (*outc)(int c));
84
85 /* Terminal capabilites in which we are interested. */
86 static char *capbuf;
87 static char *up, *down, *left, *right;
88 static int cols, xn;
89 static volatile int cols_needs_update = FALSE;
90
91 /* The various opcodes. */
92 #define OP_PUTC 0
93 #define OP_MOVE 1
94 #define OP_INSC 2
95 #define OP_DELC 3
96 #define OP_BEEP 4
97 #define OP_PUTC_SYNC 5
98 /* Control op */
99 #define CTRL_OP_GET_WINSIZE 100
100 #define CTRL_OP_GET_UNICODE_STATE 101
101 #define CTRL_OP_SET_UNICODE_STATE 102
102
103 /* We use 1024 as the buf size as that was the default buf size of FILE streams
104 on all platforms that I checked. */
105 #define TTY_BUFFSIZE 1024
106
107 static int lbuf_size = BUFSIZ;
108 static Uint32 *lbuf; /* The current line buffer */
109 static int llen; /* The current line length */
110 static int lpos; /* The current "cursor position" in the line buffer */
111 /* NOTE: not the same as column position a char may not take a"
112 * column to display or it might take many columns
113 */
114 /*
115 * Tags used in line buffer to show that these bytes represent special characters,
116 * Max unicode is 0x0010ffff, so we have lots of place for meta tags...
117 */
118 #define CONTROL_TAG 0x10000000U /* Control character, value in first position */
119 #define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */
120 #define TAG_MASK 0xFF000000U
121
122 #define MAXSIZE (1 << 16)
123
124 #define COL(_l) ((_l) % cols)
125 #define LINE(_l) ((_l) / cols)
126
127 #define NL '\n'
128
129 /* Main interface functions. */
130 static void ttysl_stop(ErlDrvData);
131 static void ttysl_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
132 static void ttysl_to_tty(ErlDrvData, ErlDrvEvent);
133 static void ttysl_flush_tty(ErlDrvData);
134 static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
135 static void ttysl_stop_select(ErlDrvEvent, void*);
136 static Sint16 get_sint16(char*);
137
138 static ErlDrvPort ttysl_port;
139 static int ttysl_fd;
140 static int ttysl_terminate = 0;
141 static int ttysl_send_ok = 0;
142 static ErlDrvBinary *putcbuf;
143 static int putcpos;
144 static int putclen;
145
146 /* Functions that work on the line buffer. */
147 static int start_lbuf(void);
148 static int stop_lbuf(void);
149 static int put_chars(byte*,int);
150 static int move_rel(int);
151 static int ins_chars(byte *,int);
152 static int del_chars(int);
153 static int step_over_chars(int);
154 static int insert_buf(byte*,int);
155 static int write_buf(Uint32 *,int);
156 static int outc(int c);
157 static int move_cursor(int,int);
158 static int cp_pos_to_col(int cp_pos);
159
160
161 /* Termcap functions. */
162 static int start_termcap(void);
163 static int stop_termcap(void);
164 static int move_left(int);
165 static int move_right(int);
166 static int move_up(int);
167 static int move_down(int);
168 static void update_cols(void);
169
170 /* Terminal setting functions. */
171 static int tty_init(int,int,int,int);
172 static int tty_set(int);
173 static int tty_reset(int);
174 static ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int,
175 char *, ErlDrvSizeT, char **, ErlDrvSizeT);
176 #ifdef ERTS_NOT_USED
177 static RETSIGTYPE suspend(int);
178 #endif
179 static RETSIGTYPE cont(int);
180 static RETSIGTYPE winch(int);
181
182 /*#define LOG_DEBUG*/
183
184 #ifdef LOG_DEBUG
185 FILE *debuglog = NULL;
186
187 #define DEBUGLOG(X) \
188 do { \
189 if (debuglog != NULL) { \
190 my_debug_printf X; \
191 } \
192 } while (0)
193
my_debug_printf(char * fmt,...)194 static void my_debug_printf(char *fmt, ...)
195 {
196 char buffer[1024];
197 va_list args;
198
199 va_start(args, fmt);
200 erts_vsnprintf(buffer,1024,fmt,args);
201 va_end(args);
202 erts_fprintf(debuglog,"%s\n",buffer);
203 /*erts_printf("Debuglog = %s\n",buffer);*/
204 }
205
206 #else
207
208 #define DEBUGLOG(X)
209
210 #endif
211
212 static int utf8_mode = 0;
213 static byte utf8buf[4]; /* for incomplete input */
214 static int utf8buf_size; /* size of incomplete input */
215
216 # define IF_IMPL(x) x
217 #else
218 # define IF_IMPL(x) NULL
219 #endif /* HAVE_TERMCAP */
220
221 /* Define the driver table entry. */
222 struct erl_drv_entry ttsl_driver_entry = {
223 ttysl_init,
224 ttysl_start,
225 IF_IMPL(ttysl_stop),
226 IF_IMPL(ttysl_from_erlang),
227 IF_IMPL(ttysl_from_tty),
228 IF_IMPL(ttysl_to_tty),
229 "tty_sl", /* driver_name */
230 NULL, /* finish */
231 NULL, /* handle */
232 IF_IMPL(ttysl_control),
233 NULL, /* timeout */
234 NULL, /* outputv */
235 NULL, /* ready_async */
236 IF_IMPL(ttysl_flush_tty),
237 NULL, /* call */
238 NULL, /* event */
239 ERL_DRV_EXTENDED_MARKER,
240 ERL_DRV_EXTENDED_MAJOR_VERSION,
241 ERL_DRV_EXTENDED_MINOR_VERSION,
242 0, /* ERL_DRV_FLAGs */
243 NULL, /* handle2 */
244 NULL, /* process_exit */
245 IF_IMPL(ttysl_stop_select)
246 };
247
248
ttysl_init(void)249 static int ttysl_init(void)
250 {
251 #ifdef HAVE_TERMCAP
252 ttysl_port = (ErlDrvPort)-1;
253 ttysl_fd = -1;
254 lbuf = NULL; /* For line buffer handling */
255 capbuf = NULL; /* For termcap handling */
256 #endif
257 #ifdef LOG_DEBUG
258 {
259 char *dl;
260 if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) {
261 debuglog = fopen(dl,"w+");
262 if (debuglog != NULL)
263 setbuf(debuglog,NULL);
264 }
265 DEBUGLOG(("ttysl_init: Debuglog = %s(0x%ld)\n",dl,(long) debuglog));
266 }
267 #endif
268 return 0;
269 }
270
ttysl_start(ErlDrvPort port,char * buf)271 static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
272 {
273 #ifndef HAVE_TERMCAP
274 return ERL_DRV_ERROR_GENERAL;
275 #else
276 char *s, *t, *l;
277 int canon, echo, sig; /* Terminal characteristics */
278 int flag;
279 extern int using_oldshell; /* set this to let the rest of erts know */
280
281 DEBUGLOG(("ttysl_start: driver input \"%s\", ttysl_port = %d (-1 expected)", buf, ttysl_port));
282 utf8buf_size = 0;
283 if (ttysl_port != (ErlDrvPort)-1) {
284 DEBUGLOG(("ttysl_start: failure with ttysl_port = %d, not initialized properly?\n", ttysl_port));
285 return ERL_DRV_ERROR_GENERAL;
286 }
287
288 DEBUGLOG(("ttysl_start: isatty(0) = %d (1 expected), isatty(1) = %d (1 expected)", isatty(0), isatty(1)));
289 if (!isatty(0) || !isatty(1)) {
290 DEBUGLOG(("ttysl_start: failure in isatty, isatty(0) = %d, isatty(1) = %d", isatty(0), isatty(1)));
291 return ERL_DRV_ERROR_GENERAL;
292 }
293
294 /* Set the terminal modes to default leave as is. */
295 canon = echo = sig = 0;
296
297 /* Parse the input parameters. */
298 for (s = strchr(buf, ' '); s; s = t) {
299 s++;
300 /* Find end of this argument (start of next) and insert NUL. */
301 if ((t = strchr(s, ' '))) {
302 *t = '\0';
303 }
304 if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) {
305 if (s[1] == 'c') canon = flag;
306 if (s[1] == 'e') echo = flag;
307 if (s[1] == 's') sig = flag;
308 }
309 else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0) {
310 DEBUGLOG(("ttysl_start: failed to open ttysl_fd, open(%s, O_RDWR, 0)) = %d\n", s, ttysl_fd));
311 return ERL_DRV_ERROR_GENERAL;
312 }
313 }
314
315 if (ttysl_fd < 0)
316 ttysl_fd = 0;
317
318 if (tty_init(ttysl_fd, canon, echo, sig) < 0 ||
319 tty_set(ttysl_fd) < 0) {
320 DEBUGLOG(("ttysl_start: failed init tty or set tty\n"));
321 ttysl_port = (ErlDrvPort)-1;
322 tty_reset(ttysl_fd);
323 return ERL_DRV_ERROR_GENERAL;
324 }
325
326 /* Set up smart line and termcap stuff. */
327 if (!start_lbuf() || !start_termcap()) {
328 DEBUGLOG(("ttysl_start: failed to start_lbuf or start_termcap\n"));
329 stop_lbuf(); /* Must free this */
330 tty_reset(ttysl_fd);
331 return ERL_DRV_ERROR_GENERAL;
332 }
333
334 SET_NONBLOCKING(ttysl_fd);
335
336 #ifdef PRIMITIVE_UTF8_CHECK
337 setlocale(LC_CTYPE, ""); /* Set international environment,
338 ignore result */
339 if (((l = getenv("LC_ALL")) && *l) ||
340 ((l = getenv("LC_CTYPE")) && *l) ||
341 ((l = getenv("LANG")) && *l)) {
342 if (strstr(l, "UTF-8"))
343 utf8_mode = 1;
344 }
345
346 #else
347 l = setlocale(LC_CTYPE, ""); /* Set international environment */
348 if (l != NULL) {
349 utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);
350 DEBUGLOG(("ttysl_start: setlocale: %s",l));
351 }
352 #endif
353 DEBUGLOG(("ttysl_start: utf8_mode is %s",(utf8_mode) ? "on" : "off"));
354 sys_signal(SIGCONT, cont);
355 sys_signal(SIGWINCH, winch);
356
357 driver_select(port, (ErlDrvEvent)(UWord)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1);
358 ttysl_port = port;
359
360 /* we need to know this when we enter the break handler */
361 using_oldshell = 0;
362
363 DEBUGLOG(("ttysl_start: successful start\n"));
364 return (ErlDrvData)ttysl_port; /* Nothing important to return */
365 #endif /* HAVE_TERMCAP */
366 }
367
368 #ifdef HAVE_TERMCAP
369
370 #define DEF_HEIGHT 24
371 #define DEF_WIDTH 80
ttysl_get_window_size(Uint32 * width,Uint32 * height)372 static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
373 {
374 #ifdef TIOCGWINSZ
375 struct winsize ws;
376 if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) {
377 *width = (Uint32) ws.ws_col;
378 *height = (Uint32) ws.ws_row;
379 if (*width <= 0)
380 *width = DEF_WIDTH;
381 if (*height <= 0)
382 *height = DEF_HEIGHT;
383 return;
384 }
385 #endif
386 *width = DEF_WIDTH;
387 *height = DEF_HEIGHT;
388 }
389
ttysl_control(ErlDrvData drv_data,unsigned int command,char * buf,ErlDrvSizeT len,char ** rbuf,ErlDrvSizeT rlen)390 static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
391 unsigned int command,
392 char *buf, ErlDrvSizeT len,
393 char **rbuf, ErlDrvSizeT rlen)
394 {
395 char resbuff[2*sizeof(Uint32)];
396 ErlDrvSizeT res_size;
397
398 command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
399 switch (command) {
400 case CTRL_OP_GET_WINSIZE:
401 {
402 Uint32 w,h;
403 ttysl_get_window_size(&w,&h);
404 memcpy(resbuff,&w,sizeof(Uint32));
405 memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32));
406 res_size = 2*sizeof(Uint32);
407 }
408 break;
409 case CTRL_OP_GET_UNICODE_STATE:
410 *resbuff = (utf8_mode) ? 1 : 0;
411 res_size = 1;
412 break;
413 case CTRL_OP_SET_UNICODE_STATE:
414 if (len > 0) {
415 int m = (int) *buf;
416 *resbuff = (utf8_mode) ? 1 : 0;
417 res_size = 1;
418 utf8_mode = (m) ? 1 : 0;
419 } else {
420 return 0;
421 }
422 break;
423 default:
424 return -1;
425 }
426 if (rlen < res_size) {
427 *rbuf = driver_alloc(res_size);
428 }
429 memcpy(*rbuf,resbuff,res_size);
430 return res_size;
431 }
432
433
ttysl_stop(ErlDrvData ttysl_data)434 static void ttysl_stop(ErlDrvData ttysl_data)
435 {
436 DEBUGLOG(("ttysl_stop: ttysl_port = %d\n",ttysl_port));
437 if (ttysl_port != (ErlDrvPort)-1) {
438 stop_lbuf();
439 stop_termcap();
440 tty_reset(ttysl_fd);
441 driver_select(ttysl_port, (ErlDrvEvent)(UWord)ttysl_fd,
442 ERL_DRV_WRITE|ERL_DRV_READ|ERL_DRV_USE, 0);
443 sys_signal(SIGCONT, SIG_DFL);
444 sys_signal(SIGWINCH, SIG_DFL);
445 }
446 ttysl_port = (ErlDrvPort)-1;
447 ttysl_fd = -1;
448 ttysl_terminate = 0;
449 /* return TRUE; */
450 }
451
put_utf8(int ch,byte * target,int sz,int * pos)452 static int put_utf8(int ch, byte *target, int sz, int *pos)
453 {
454 Uint x = (Uint) ch;
455 if (x < 0x80) {
456 if (*pos >= sz) {
457 return -1;
458 }
459 target[(*pos)++] = (byte) x;
460 }
461 else if (x < 0x800) {
462 if (((*pos) + 1) >= sz) {
463 return -1;
464 }
465 target[(*pos)++] = (((byte) (x >> 6)) |
466 ((byte) 0xC0));
467 target[(*pos)++] = (((byte) (x & 0x3F)) |
468 ((byte) 0x80));
469 } else if (x < 0x10000) {
470 if ((x >= 0xD800 && x <= 0xDFFF) ||
471 (x == 0xFFFE) ||
472 (x == 0xFFFF)) { /* Invalid unicode range */
473 return -1;
474 }
475 if (((*pos) + 2) >= sz) {
476 return -1;
477 }
478
479 target[(*pos)++] = (((byte) (x >> 12)) |
480 ((byte) 0xE0));
481 target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
482 ((byte) 0x80));
483 target[(*pos)++] = (((byte) (x & 0x3F)) |
484 ((byte) 0x80));
485 } else if (x < 0x110000) { /* Standard imposed max */
486 if (((*pos) + 3) >= sz) {
487 return -1;
488 }
489 target[(*pos)++] = (((byte) (x >> 18)) |
490 ((byte) 0xF0));
491 target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) |
492 ((byte) 0x80));
493 target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
494 ((byte) 0x80));
495 target[(*pos)++] = (((byte) (x & 0x3F)) |
496 ((byte) 0x80));
497 } else {
498 return -1;
499 }
500 return 0;
501 }
502
503
pick_utf8(byte * s,int sz,int * pos)504 static int pick_utf8(byte *s, int sz, int *pos)
505 {
506 int size = sz - (*pos);
507 byte *source;
508 Uint unipoint;
509
510 if (size > 0) {
511 source = s + (*pos);
512 if (((*source) & ((byte) 0x80)) == 0) {
513 unipoint = (int) *source;
514 ++(*pos);
515 return (int) unipoint;
516 } else if (((*source) & ((byte) 0xE0)) == 0xC0) {
517 if (size < 2) {
518 return -2;
519 }
520 if (((source[1] & ((byte) 0xC0)) != 0x80) ||
521 ((*source) < 0xC2) /* overlong */) {
522 return -1;
523 }
524 (*pos) += 2;
525 unipoint =
526 (((Uint) ((*source) & ((byte) 0x1F))) << 6) |
527 ((Uint) (source[1] & ((byte) 0x3F)));
528 return (int) unipoint;
529 } else if (((*source) & ((byte) 0xF0)) == 0xE0) {
530 if (size < 3) {
531 return -2;
532 }
533 if (((source[1] & ((byte) 0xC0)) != 0x80) ||
534 ((source[2] & ((byte) 0xC0)) != 0x80) ||
535 (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) {
536 return -1;
537 }
538 if ((((*source) & ((byte) 0xF)) == 0xD) &&
539 ((source[1] & 0x20) != 0)) {
540 return -1;
541 }
542 if (((*source) == 0xEF) && (source[1] == 0xBF) &&
543 ((source[2] == 0xBE) || (source[2] == 0xBF))) {
544 return -1;
545 }
546 (*pos) += 3;
547 unipoint =
548 (((Uint) ((*source) & ((byte) 0xF))) << 12) |
549 (((Uint) (source[1] & ((byte) 0x3F))) << 6) |
550 ((Uint) (source[2] & ((byte) 0x3F)));
551 return (int) unipoint;
552 } else if (((*source) & ((byte) 0xF8)) == 0xF0) {
553 if (size < 4) {
554 return -2 ;
555 }
556 if (((source[1] & ((byte) 0xC0)) != 0x80) ||
557 ((source[2] & ((byte) 0xC0)) != 0x80) ||
558 ((source[3] & ((byte) 0xC0)) != 0x80) ||
559 (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) {
560 return -1;
561 }
562 if ((((*source) & ((byte)0x7)) > 0x4U) ||
563 ((((*source) & ((byte)0x7)) == 0x4U) &&
564 ((source[1] & ((byte)0x3F)) > 0xFU))) {
565 return -1;
566 }
567 (*pos) += 4;
568 unipoint =
569 (((Uint) ((*source) & ((byte) 0x7))) << 18) |
570 (((Uint) (source[1] & ((byte) 0x3F))) << 12) |
571 (((Uint) (source[2] & ((byte) 0x3F))) << 6) |
572 ((Uint) (source[3] & ((byte) 0x3F)));
573 return (int) unipoint;
574 } else {
575 return -1;
576 }
577 } else {
578 return -1;
579 }
580 }
581
octal_or_hex_positions(Uint c)582 static int octal_or_hex_positions(Uint c)
583 {
584 int x = 0;
585 Uint ch = c;
586 if (!ch) {
587 return 1;
588 }
589 while(ch) {
590 ++x;
591 ch >>= 3;
592 }
593 if (x <= 3) {
594 return 3;
595 }
596 /* \x{H ...} format when larger than \777 */
597 x = 0;
598 ch = c;
599 while(ch) {
600 ++x;
601 ch >>= 4;
602 }
603 return x+3;
604 }
605
octal_or_hex_format(Uint ch,byte * buf,int * pos)606 static void octal_or_hex_format(Uint ch, byte *buf, int *pos)
607 {
608 static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9',
609 'A','B','C','D','E','F'};
610 int num = octal_or_hex_positions(ch);
611 if (num != 3) {
612 buf[(*pos)++] = 'x';
613 buf[(*pos)++] = '{';
614 num -= 3;
615 while(num--) {
616 buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)];
617 }
618 buf[(*pos)++] = '}';
619 } else {
620 while(num--) {
621 buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0');
622 }
623 }
624 }
625
626 /*
627 * Check that there is enough room in all buffers to copy all pad chars
628 * and stiff we need If not, realloc lbuf.
629 */
check_buf_size(byte * s,int n)630 static int check_buf_size(byte *s, int n)
631 {
632 int pos = 0;
633 int ch;
634 int size = 10;
635
636 DEBUGLOG(("check_buf_size: n = %d",n));
637 while(pos < n) {
638 /* Indata is always UTF-8 */
639 if ((ch = pick_utf8(s,n,&pos)) < 0) {
640 /* XXX temporary allow invalid chars */
641 ch = (int) s[pos];
642 DEBUGLOG(("check_buf_size: Invalid UTF8:%d",ch));
643 ++pos;
644 }
645 if (utf8_mode) { /* That is, terminal is UTF8 compliant */
646 if (ch >= 128 || isprint(ch)) {
647 #ifdef HAVE_WCWIDTH
648 int width;
649 #endif
650 DEBUGLOG(("check_buf_size: Printable(UTF-8:%d):%d",pos,ch));
651 size++;
652 #ifdef HAVE_WCWIDTH
653 if ((width = wcwidth(ch)) > 1) {
654 size += width - 1;
655 }
656 #endif
657 } else if (ch == '\t') {
658 size += 8;
659 } else {
660 DEBUGLOG(("check_buf_size: Magic(UTF-8:%d):%d",pos,ch));
661 size += 2;
662 }
663 } else {
664 if (ch <= 255 && isprint(ch)) {
665 DEBUGLOG(("check_buf_size: Printable:%d",ch));
666 size++;
667 } else if (ch == '\t')
668 size += 8;
669 else if (ch >= 128) {
670 DEBUGLOG(("check_buf_size: Non printable:%d",ch));
671 size += (octal_or_hex_positions(ch) + 1);
672 }
673 else {
674 DEBUGLOG(("check_buf_size: Magic:%d",ch));
675 size += 2;
676 }
677 }
678 }
679
680 if (size + lpos >= lbuf_size) {
681
682 lbuf_size = size + lpos + BUFSIZ;
683 if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) {
684 DEBUGLOG(("check_buf_size: alloc failure of %d bytes", lbuf_size * sizeof(Uint32)));
685 driver_failure(ttysl_port, -1);
686 return(0);
687 }
688 }
689 DEBUGLOG(("check_buf_size: success\n"));
690 return(1);
691 }
692
693
ttysl_from_erlang(ErlDrvData ttysl_data,char * buf,ErlDrvSizeT count)694 static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, ErlDrvSizeT count)
695 {
696 ErlDrvSizeT sz;
697
698 sz = driver_sizeq(ttysl_port);
699
700 putclen = count > TTY_BUFFSIZE ? TTY_BUFFSIZE : count;
701 putcbuf = driver_alloc_binary(putclen);
702 putcpos = 0;
703
704 if (lpos > MAXSIZE)
705 put_chars((byte*)"\n", 1);
706
707 DEBUGLOG(("ttysl_from_erlang: OP = %d", buf[0]));
708
709 switch (buf[0]) {
710 case OP_PUTC_SYNC:
711 /* Using sync means that we have to send an ok to the
712 controlling process for each command call. We delay
713 sending ok if the driver queue exceeds a certain size.
714 We do not set ourselves as a busy port, as this
715 could be very bad for user_drv, if it gets blocked on
716 the port_command. */
717 /* fall through */
718 case OP_PUTC:
719 DEBUGLOG(("ttysl_from_erlang: OP: Putc(%lu)",(unsigned long) count-1));
720 if (check_buf_size((byte*)buf+1, count-1) == 0)
721 return;
722 put_chars((byte*)buf+1, count-1);
723 break;
724 case OP_MOVE:
725 move_rel(get_sint16(buf+1));
726 break;
727 case OP_INSC:
728 if (check_buf_size((byte*)buf+1, count-1) == 0)
729 return;
730 ins_chars((byte*)buf+1, count-1);
731 break;
732 case OP_DELC:
733 del_chars(get_sint16(buf+1));
734 break;
735 case OP_BEEP:
736 outc('\007');
737 break;
738 default:
739 /* Unknown op, just ignore. */
740 break;
741 }
742
743 driver_enq_bin(ttysl_port,putcbuf,0,putcpos);
744 driver_free_binary(putcbuf);
745
746 if (sz == 0) {
747 for (;;) {
748 int written, qlen;
749 SysIOVec *iov;
750
751 iov = driver_peekq(ttysl_port,&qlen);
752 if (iov)
753 written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
754 else
755 written = 0;
756 if (written < 0) {
757 if (errno == ERRNO_BLOCK || errno == EINTR) {
758 driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
759 ERL_DRV_USE|ERL_DRV_WRITE,1);
760 break;
761 } else {
762 DEBUGLOG(("ttysl_from_erlang: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
763 driver_failure_posix(ttysl_port, errno);
764 return;
765 }
766 } else {
767 if (driver_deq(ttysl_port, written) == 0)
768 break;
769 }
770 }
771 }
772
773 if (buf[0] == OP_PUTC_SYNC) {
774 if (driver_sizeq(ttysl_port) > TTY_BUFFSIZE && !ttysl_terminate) {
775 /* We delay sending the ack until the buffer has been consumed */
776 ttysl_send_ok = 1;
777 } else {
778 ErlDrvTermData spec[] = {
779 ERL_DRV_PORT, driver_mk_port(ttysl_port),
780 ERL_DRV_ATOM, driver_mk_atom("ok"),
781 ERL_DRV_TUPLE, 2
782 };
783 ASSERT(ttysl_send_ok == 0);
784 erl_drv_output_term(driver_mk_port(ttysl_port), spec,
785 sizeof(spec) / sizeof(spec[0]));
786 }
787 }
788
789 return; /* TRUE; */
790 }
791
ttysl_to_tty(ErlDrvData ttysl_data,ErlDrvEvent fd)792 static void ttysl_to_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) {
793 for (;;) {
794 int written, qlen;
795 SysIOVec *iov;
796 ErlDrvSizeT sz;
797
798 iov = driver_peekq(ttysl_port,&qlen);
799
800 DEBUGLOG(("ttysl_to_tty: qlen = %d", qlen));
801
802 if (iov)
803 written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
804 else
805 written = 0;
806 if (written < 0) {
807 if (errno == EINTR) {
808 continue;
809 } else if (errno != ERRNO_BLOCK){
810 DEBUGLOG(("ttysl_to_tty: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
811 driver_failure_posix(ttysl_port, errno);
812 }
813 break;
814 } else {
815 sz = driver_deq(ttysl_port, written);
816 if (sz < TTY_BUFFSIZE && ttysl_send_ok) {
817 ErlDrvTermData spec[] = {
818 ERL_DRV_PORT, driver_mk_port(ttysl_port),
819 ERL_DRV_ATOM, driver_mk_atom("ok"),
820 ERL_DRV_TUPLE, 2
821 };
822 ttysl_send_ok = 0;
823 erl_drv_output_term(driver_mk_port(ttysl_port), spec,
824 sizeof(spec) / sizeof(spec[0]));
825 }
826 if (sz == 0) {
827 driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
828 ERL_DRV_WRITE,0);
829 if (ttysl_terminate) {
830 /* flush has been called, which means we should terminate
831 when queue is empty. This will not send any exit
832 message */
833 DEBUGLOG(("ttysl_to_tty: ttysl_terminate normal\n"));
834 driver_failure_atom(ttysl_port, "normal");
835 }
836 break;
837 }
838 }
839 }
840
841 return;
842 }
843
ttysl_flush_tty(ErlDrvData ttysl_data)844 static void ttysl_flush_tty(ErlDrvData ttysl_data) {
845 DEBUGLOG(("ttysl_flush_tty: .."));
846 ttysl_terminate = 1;
847 return;
848 }
849
ttysl_from_tty(ErlDrvData ttysl_data,ErlDrvEvent fd)850 static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
851 {
852 byte b[1024];
853 ssize_t i;
854 int ch = 0, pos = 0;
855 int left = 1024;
856 byte *p = b;
857 byte t[1024];
858 int tpos;
859
860 if (utf8buf_size > 0) {
861 memcpy(b,utf8buf,utf8buf_size);
862 left -= utf8buf_size;
863 p += utf8buf_size;
864 utf8buf_size = 0;
865 }
866
867 DEBUGLOG(("ttysl_from_tty: remainder = %d", left));
868
869 if ((i = read((int)(SWord)fd, (char *) p, left)) >= 0) {
870 if (p != b) {
871 i += (p - b);
872 }
873 if (utf8_mode) { /* Hopefully an UTF8 terminal */
874 while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0)
875 ;
876 if (ch == -2 && i - pos <= 4) {
877 /* bytes left to care for */
878 utf8buf_size = i -pos;
879 memcpy(utf8buf,b+pos,utf8buf_size);
880 } else if (ch == -1) {
881 DEBUGLOG(("ttysl_from_tty: Giving up on UTF8 mode, invalid character"));
882 utf8_mode = 0;
883 goto latin_terminal;
884 }
885 driver_output(ttysl_port, (char *) b, pos);
886 } else {
887 latin_terminal:
888 tpos = 0;
889 while (pos < i) {
890 while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
891 put_utf8((int) b[pos++], t, 1024, &tpos);
892 }
893 driver_output(ttysl_port, (char *) t, tpos);
894 tpos = 0;
895 }
896 }
897 } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
898 DEBUGLOG(("ttysl_from_tty: driver failure in read(%d,..) = %d (errno = %d)\n", (int)(SWord)fd, i, errno));
899 driver_failure(ttysl_port, -1);
900 }
901 }
902
ttysl_stop_select(ErlDrvEvent e,void * _)903 static void ttysl_stop_select(ErlDrvEvent e, void* _)
904 {
905 int fd = (int)(long)e;
906 if (fd != 0) {
907 close(fd);
908 }
909 }
910
911 /* Procedures for putting and getting integers to/from strings. */
get_sint16(char * s)912 static Sint16 get_sint16(char *s)
913 {
914 return ((*s << 8) | ((byte*)s)[1]);
915 }
916
start_lbuf(void)917 static int start_lbuf(void)
918 {
919 if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32))))
920 return FALSE;
921 llen = 0;
922 lpos = 0;
923 return TRUE;
924 }
925
stop_lbuf(void)926 static int stop_lbuf(void)
927 {
928 if (lbuf) {
929 driver_free(lbuf);
930 lbuf = NULL;
931 }
932 return TRUE;
933 }
934
935 /* Put l bytes (in UTF8) from s into the buffer and output them. */
put_chars(byte * s,int l)936 static int put_chars(byte *s, int l)
937 {
938 int n;
939
940 n = insert_buf(s, l);
941 if (lpos > llen)
942 llen = lpos;
943 if (n > 0)
944 write_buf(lbuf + lpos - n, n);
945 return TRUE;
946 }
947
948 /*
949 * Move the current postition forwards or backwards within the current
950 * line. We know about padding.
951 */
move_rel(int n)952 static int move_rel(int n)
953 {
954 int npos; /* The new position */
955
956 /* Step forwards or backwards over the buffer. */
957 npos = step_over_chars(n);
958
959 /* Calculate move, updates pointers and move the cursor. */
960 move_cursor(lpos, npos);
961 lpos = npos;
962 return TRUE;
963 }
964
965 /* Insert characters into the buffer at the current position. */
ins_chars(byte * s,int l)966 static int ins_chars(byte *s, int l)
967 {
968 int n, tl;
969 Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */
970
971 /* Move tail of buffer to make space. */
972 if ((tl = llen - lpos) > 0) {
973 if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL)
974 return FALSE;
975 memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32));
976 }
977 n = insert_buf(s, l);
978 if (tl > 0) {
979 memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32));
980 driver_free(tbuf);
981 }
982 llen += n;
983 write_buf(lbuf + (lpos - n), llen - (lpos - n));
984 move_cursor(llen, lpos);
985 return TRUE;
986 }
987
988 /*
989 * Delete characters in the buffer. Can delete characters before (n < 0)
990 * and after (n > 0) the current position. Cursor left at beginning of
991 * deleted block.
992 */
del_chars(int n)993 static int del_chars(int n)
994 {
995 int i, l, r;
996 int pos;
997 int gcs; /* deleted grapheme characters */
998
999 update_cols();
1000
1001 /* Step forward or backwards over n logical characters. */
1002 pos = step_over_chars(n);
1003 DEBUGLOG(("del_chars: %d from %d %d %d\n", n, lpos, pos, llen));
1004 if (pos > lpos) {
1005 l = pos - lpos; /* Buffer characters to delete */
1006 r = llen - lpos - l; /* Characters after deleted */
1007 gcs = cp_pos_to_col(pos) - cp_pos_to_col(lpos);
1008 /* Fix up buffer and buffer pointers. */
1009 if (r > 0)
1010 memmove(lbuf + lpos, lbuf + pos, r * sizeof(Uint32));
1011 llen -= l;
1012 /* Write out characters after, blank the tail and jump back to lpos. */
1013 write_buf(lbuf + lpos, r);
1014 for (i = gcs ; i > 0; --i)
1015 outc(' ');
1016 if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
1017 {
1018 outc(' ');
1019 move_left(1);
1020 }
1021 move_cursor(llen + gcs, lpos);
1022 }
1023 else if (pos < lpos) {
1024 l = lpos - pos; /* Buffer characters */
1025 r = llen - lpos; /* Characters after deleted */
1026 gcs = -move_cursor(lpos, lpos-l); /* Move back */
1027 /* Fix up buffer and buffer pointers. */
1028 if (r > 0)
1029 memmove(lbuf + pos, lbuf + lpos, r * sizeof(Uint32));
1030 lpos -= l;
1031 llen -= l;
1032 /* Write out characters after, blank the tail and jump back to lpos. */
1033 write_buf(lbuf + lpos, r);
1034 for (i = gcs ; i > 0; --i)
1035 outc(' ');
1036 if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
1037 {
1038 outc(' ');
1039 move_left(1);
1040 }
1041 move_cursor(llen + gcs, lpos);
1042 }
1043 return TRUE;
1044 }
1045
1046 /* Step over n logical characters, check for overflow. */
step_over_chars(int n)1047 static int step_over_chars(int n)
1048 {
1049 Uint32 *c, *beg, *end;
1050
1051 beg = lbuf;
1052 end = lbuf + llen;
1053 c = lbuf + lpos;
1054 for ( ; n > 0 && c < end; --n) {
1055 c++;
1056 while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
1057 c++;
1058 }
1059 for ( ; n < 0 && c > beg; n++) {
1060 --c;
1061 while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
1062 --c;
1063 }
1064 return c - lbuf;
1065 }
1066
1067 /*
1068 * Insert n characters into the buffer at lpos.
1069 * Know about pad characters and treat \n specially.
1070 */
1071
insert_buf(byte * s,int n)1072 static int insert_buf(byte *s, int n)
1073 {
1074 int pos = 0;
1075 int buffpos = lpos;
1076 int ch;
1077
1078 while (pos < n) {
1079 if ((ch = pick_utf8(s,n,&pos)) < 0) {
1080 /* XXX temporary allow invalid chars */
1081 ch = (int) s[pos];
1082 DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch));
1083 ++pos;
1084 }
1085 if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) {
1086 DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch));
1087 lbuf[lpos++] = (Uint32) ch;
1088 } else if (ch >= 128) { /* not utf8 mode */
1089 int nc = octal_or_hex_positions(ch);
1090 lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG;
1091 while (nc--) {
1092 lbuf[lpos++] = ESCAPED_TAG;
1093 }
1094 } else if (ch == '\t') {
1095 do {
1096 lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
1097 ch = 0;
1098 } while (lpos % 8);
1099 } else if (ch == '\e') {
1100 DEBUGLOG(("insert_buf: ANSI Escape: \\e"));
1101 lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
1102 } else if (ch == '\n' || ch == '\r') {
1103 write_buf(lbuf + buffpos, lpos - buffpos);
1104 outc('\r');
1105 if (ch == '\n')
1106 outc('\n');
1107 if (llen > lpos) {
1108 memmove(lbuf, lbuf + lpos, llen - lpos);
1109 }
1110 llen -= lpos;
1111 lpos = buffpos = 0;
1112 } else {
1113 DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch));
1114 lbuf[lpos++] = ch | CONTROL_TAG;
1115 lbuf[lpos++] = CONTROL_TAG;
1116 }
1117 }
1118 return lpos - buffpos; /* characters "written" into
1119 current buffer (may be less due to newline) */
1120 }
1121
1122
1123
1124 /*
1125 * Write n characters in line buffer starting at s. Be smart about
1126 * non-printables. Know about pad characters and that \n can never
1127 * occur normally.
1128 */
1129
write_buf(Uint32 * s,int n)1130 static int write_buf(Uint32 *s, int n)
1131 {
1132 byte ubuf[4];
1133 int ubytes = 0, i;
1134 byte lastput = ' ';
1135
1136 update_cols();
1137
1138 while (n > 0) {
1139 if (!(*s & TAG_MASK) ) {
1140 if (utf8_mode) {
1141 ubytes = 0;
1142 if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) {
1143 for (i = 0; i < ubytes; ++i) {
1144 outc(ubuf[i]);
1145 }
1146 lastput = 0; /* Means the last written character was multibyte UTF8 */
1147 }
1148 } else {
1149 outc((byte) *s);
1150 lastput = (byte) *s;
1151 }
1152 --n;
1153 ++s;
1154 } else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
1155 outc(lastput = ' ');
1156 --n; s++;
1157 while (n > 0 && *s == CONTROL_TAG) {
1158 outc(lastput = ' ');
1159 --n; s++;
1160 }
1161 } else if (*s == (CONTROL_TAG | ((Uint32) '\e'))) {
1162 outc(lastput = '\e');
1163 --n;
1164 ++s;
1165 } else if (*s & CONTROL_TAG) {
1166 outc('^');
1167 outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40)));
1168 n -= 2;
1169 s += 2;
1170 } else if (*s & ESCAPED_TAG) {
1171 Uint32 ch = *s & ~(TAG_MASK);
1172 byte *octbuff;
1173 byte octtmp[256];
1174 int octbytes;
1175 DEBUGLOG(("write_buf: Escaped: %d", ch));
1176 octbytes = octal_or_hex_positions(ch);
1177 if (octbytes > 256) {
1178 octbuff = driver_alloc(octbytes);
1179 } else {
1180 octbuff = octtmp;
1181 }
1182 octbytes = 0;
1183 octal_or_hex_format(ch, octbuff, &octbytes);
1184 DEBUGLOG(("write_buf: octbytes: %d", octbytes));
1185 outc('\\');
1186 for (i = 0; i < octbytes; ++i) {
1187 outc(lastput = octbuff[i]);
1188 DEBUGLOG(("write_buf: outc: %d", (int) lastput));
1189 }
1190 n -= octbytes+1;
1191 s += octbytes+1;
1192 if (octbuff != octtmp) {
1193 driver_free(octbuff);
1194 }
1195 } else {
1196 DEBUGLOG(("write_buf: Very unexpected character %d",(int) *s));
1197 ++n;
1198 --s;
1199 }
1200 }
1201 /* Check landed in first column of new line and have 'xn' bug. */
1202 n = s - lbuf;
1203 if (xn && n != 0 && COL(cp_pos_to_col(n)) == 0) {
1204 if (n >= llen) {
1205 outc(' ');
1206 } else if (lastput == 0) { /* A multibyte UTF8 character */
1207 for (i = 0; i < ubytes; ++i) {
1208 outc(ubuf[i]);
1209 }
1210 } else {
1211 outc(lastput);
1212 }
1213 move_left(1);
1214 }
1215 return TRUE;
1216 }
1217
1218
1219 /* The basic procedure for outputting one character. */
outc(int c)1220 static int outc(int c)
1221 {
1222 putcbuf->orig_bytes[putcpos++] = c;
1223 if (putcpos == putclen) {
1224 driver_enq_bin(ttysl_port,putcbuf,0,putclen);
1225 driver_free_binary(putcbuf);
1226 putcpos = 0;
1227 putclen = TTY_BUFFSIZE;
1228 putcbuf = driver_alloc_binary(BUFSIZ);
1229 }
1230 return 1;
1231 }
1232
move_cursor(int from_pos,int to_pos)1233 static int move_cursor(int from_pos, int to_pos)
1234 {
1235 int from_col, to_col;
1236 int dc, dl;
1237 update_cols();
1238
1239 from_col = cp_pos_to_col(from_pos);
1240 to_col = cp_pos_to_col(to_pos);
1241
1242 dc = COL(to_col) - COL(from_col);
1243 dl = LINE(to_col) - LINE(from_col);
1244 DEBUGLOG(("move_cursor: from %d %d to %d %d => %d %d\n",
1245 from_pos, from_col, to_pos, to_col, dl, dc));
1246 if (dl > 0)
1247 move_down(dl);
1248 else if (dl < 0)
1249 move_up(-dl);
1250 if (dc > 0)
1251 move_right(dc);
1252 else if (dc < 0)
1253 move_left(-dc);
1254 return to_col-from_col;
1255 }
1256
1257 /*
1258 * Returns the length of an ANSI escape code in a buffer, this function only consider
1259 * color escape sequences like `\e[33m` or `\e[21;33m`. If a sequence has no valid
1260 * terminator, the length is equal the number of characters between `\e` and the first
1261 * invalid character, inclusive.
1262 */
1263
ansi_escape_width(Uint32 * s,int max_length)1264 static int ansi_escape_width(Uint32 *s, int max_length)
1265 {
1266 int i;
1267
1268 if (*s != (CONTROL_TAG | ((Uint32) '\e'))) {
1269 return 0;
1270 } else if (max_length <= 1) {
1271 return 1;
1272 } else if (s[1] != '[') {
1273 return 2;
1274 }
1275
1276 for (i = 2; i < max_length && (s[i] == ';' || (s[i] >= '0' && s[i] <= '9')); i++);
1277
1278 return i + 1;
1279 }
1280
cp_pos_to_col(int cp_pos)1281 static int cp_pos_to_col(int cp_pos)
1282 {
1283 /*
1284 * If we don't have any character width information. Assume that
1285 * code points are one column wide
1286 */
1287 int w = 1;
1288 int col = 0;
1289 int i = 0;
1290 int j;
1291
1292 if (cp_pos > llen) {
1293 col += cp_pos - llen;
1294 cp_pos = llen;
1295 }
1296
1297 while (i < cp_pos) {
1298 j = ansi_escape_width(lbuf + i, llen - i);
1299
1300 if (j > 0) {
1301 i += j;
1302 } else {
1303 #ifdef HAVE_WCWIDTH
1304 w = wcwidth(lbuf[i]);
1305 #endif
1306 if (w > 0) {
1307 col += w;
1308 }
1309 i++;
1310 }
1311 }
1312
1313 return col;
1314 }
1315
start_termcap(void)1316 static int start_termcap(void)
1317 {
1318 int eres;
1319 size_t envsz = 1024;
1320 char *env = NULL;
1321 char *c;
1322 int tres;
1323
1324 DEBUGLOG(("start_termcap: .."));
1325
1326 capbuf = driver_alloc(1024);
1327 if (!capbuf)
1328 goto false;
1329 eres = erl_drv_getenv("TERM", capbuf, &envsz);
1330 if (eres == 0)
1331 env = capbuf;
1332 else if (eres < 0) {
1333 DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d\n", eres));
1334 goto false;
1335 } else /* if (eres > 1) */ {
1336 char *envbuf = driver_alloc(envsz);
1337 if (!envbuf)
1338 goto false;
1339 while (1) {
1340 char *newenvbuf;
1341 eres = erl_drv_getenv("TERM", envbuf, &envsz);
1342 if (eres == 0)
1343 break;
1344 newenvbuf = driver_realloc(envbuf, envsz);
1345 if (eres < 0 || !newenvbuf) {
1346 DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d or realloc buf == %p\n", eres, newenvbuf));
1347 env = newenvbuf ? newenvbuf : envbuf;
1348 goto false;
1349 }
1350 envbuf = newenvbuf;
1351 }
1352 env = envbuf;
1353 }
1354 if ((tres = tgetent((char*)lbuf, env)) <= 0) {
1355 DEBUGLOG(("start_termcap: failure in tgetent(..) = %d\n", tres));
1356 goto false;
1357 }
1358 if (env != capbuf) {
1359 env = NULL;
1360 driver_free(env);
1361 }
1362 c = capbuf;
1363 cols = tgetnum("co");
1364 if (cols <= 0)
1365 cols = DEF_WIDTH;
1366 xn = tgetflag("xn");
1367 up = tgetstr("up", &c);
1368 if (!(down = tgetstr("do", &c)))
1369 down = "\n";
1370 if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c)))
1371 left = "\b"; /* Can't happen - but does on Solaris 2 */
1372 right = tgetstr("nd", &c);
1373 if (up && down && left && right) {
1374 DEBUGLOG(("start_termcap: successful start\n"));
1375 return TRUE;
1376 }
1377 DEBUGLOG(("start_termcap: failed start\n"));
1378 false:
1379 if (env && env != capbuf)
1380 driver_free(env);
1381 if (capbuf)
1382 driver_free(capbuf);
1383 capbuf = NULL;
1384 return FALSE;
1385 }
1386
stop_termcap(void)1387 static int stop_termcap(void)
1388 {
1389 if (capbuf) driver_free(capbuf);
1390 capbuf = NULL;
1391 return TRUE;
1392 }
1393
move_left(int n)1394 static int move_left(int n)
1395 {
1396 while (n-- > 0)
1397 tputs(left, 1, outc);
1398 return TRUE;
1399 }
1400
move_right(int n)1401 static int move_right(int n)
1402 {
1403 while (n-- > 0)
1404 tputs(right, 1, outc);
1405 return TRUE;
1406 }
1407
move_up(int n)1408 static int move_up(int n)
1409 {
1410 while (n-- > 0)
1411 tputs(up, 1, outc);
1412 return TRUE;
1413 }
1414
move_down(int n)1415 static int move_down(int n)
1416 {
1417 while (n-- > 0)
1418 tputs(down, 1, outc);
1419 return TRUE;
1420 }
1421
1422
1423 /*
1424 * Updates cols if terminal has resized (SIGWINCH). Should be called
1425 * at the start of any function that uses the COL or LINE macros. If
1426 * the terminal is resized after calling this function but before use
1427 * of the macros, then we may write to the wrong screen location.
1428 *
1429 * We cannot call this from the SIGWINCH handler because it uses
1430 * ioctl() which is not a safe function as listed in the signal(7)
1431 * man page.
1432 */
update_cols(void)1433 static void update_cols(void)
1434 {
1435 Uint32 width, height;
1436
1437 if (cols_needs_update) {
1438 cols_needs_update = FALSE;
1439 ttysl_get_window_size(&width, &height);
1440 cols = width;
1441 }
1442 }
1443
1444
1445 /*
1446 * Put a terminal device into non-canonical mode with ECHO off.
1447 * Before doing so we first save the terminal's current mode,
1448 * assuming the caller will call the tty_reset() function
1449 * (also in this file) when it's done with raw mode.
1450 */
1451
1452 static struct termios tty_smode, tty_rmode;
1453
tty_init(int fd,int canon,int echo,int sig)1454 static int tty_init(int fd, int canon, int echo, int sig) {
1455 int tres;
1456 DEBUGLOG(("tty_init: fd = %d, canon = %d, echo = %d, sig = %d", fd, canon, echo, sig));
1457 if ((tres = tcgetattr(fd, &tty_rmode)) < 0) {
1458 DEBUGLOG(("tty_init: failure in tcgetattr(%d,..) = %d\n", fd, tres));
1459 return -1;
1460 }
1461 tty_smode = tty_rmode;
1462
1463 /* Default characteristics for all usage including termcap output. */
1464 tty_smode.c_iflag &= ~ISTRIP;
1465
1466 /* Turn canonical (line mode) on off. */
1467 if (canon > 0) {
1468 tty_smode.c_iflag |= ICRNL;
1469 tty_smode.c_lflag |= ICANON;
1470 tty_smode.c_oflag |= OPOST;
1471 tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF];
1472 #ifdef VDSUSP
1473 tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP];
1474 #endif
1475 }
1476 if (canon < 0) {
1477 tty_smode.c_iflag &= ~ICRNL;
1478 tty_smode.c_lflag &= ~ICANON;
1479 tty_smode.c_oflag &= ~OPOST;
1480 /* Must get these really right or funny effects can occur. */
1481 tty_smode.c_cc[VMIN] = 1;
1482 tty_smode.c_cc[VTIME] = 0;
1483 #ifdef VDSUSP
1484 tty_smode.c_cc[VDSUSP] = 0;
1485 #endif
1486 }
1487
1488 /* Turn echo on or off. */
1489 if (echo > 0)
1490 tty_smode.c_lflag |= ECHO;
1491 if (echo < 0)
1492 tty_smode.c_lflag &= ~ECHO;
1493
1494 /* Set extra characteristics for "RAW" mode, no signals. */
1495 if (sig > 0) {
1496 /* Ignore IMAXBEL as not POSIX. */
1497 #ifndef QNX
1498 tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY);
1499 #else
1500 tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON);
1501 #endif
1502 tty_smode.c_lflag |= (ISIG|IEXTEN);
1503 }
1504 if (sig < 0) {
1505 /* Ignore IMAXBEL as not POSIX. */
1506 #ifndef QNX
1507 tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY);
1508 #else
1509 tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON);
1510 #endif
1511 tty_smode.c_lflag &= ~(ISIG|IEXTEN);
1512 }
1513 DEBUGLOG(("tty_init: successful init\n"));
1514 return 0;
1515 }
1516
1517 /*
1518 * Set/restore a terminal's mode to whatever it was on the most
1519 * recent call to the tty_init() function above.
1520 */
1521
tty_set(int fd)1522 static int tty_set(int fd)
1523 {
1524 int tres;
1525 DEBUGF(("tty_set: Setting tty...\n"));
1526
1527 if ((tres = tcsetattr(fd, TCSANOW, &tty_smode)) < 0) {
1528 DEBUGLOG(("tty_set: failure in tcgetattr(%d,..) = %d\n", fd, tres));
1529 return(-1);
1530 }
1531 return(0);
1532 }
1533
tty_reset(int fd)1534 static int tty_reset(int fd) /* of terminal device */
1535 {
1536 int tres;
1537 DEBUGF(("tty_reset: Resetting tty...\n"));
1538
1539 if ((tres = tcsetattr(fd, TCSANOW, &tty_rmode)) < 0) {
1540 DEBUGLOG(("tty_reset: failure in tcsetattr(%d,..) = %d\n", fd, tres));
1541 return(-1);
1542 }
1543 return(0);
1544 }
1545
1546 /*
1547 * Signal handler to cope with signals so that we can reset the tty
1548 * to the orignal settings
1549 */
1550
1551 #ifdef ERTS_NOT_USED
1552 /* XXX: A mistake that it isn't used, or should it be removed? */
1553
suspend(int sig)1554 static RETSIGTYPE suspend(int sig)
1555 {
1556 if (tty_reset(ttysl_fd) < 0) {
1557 DEBUGLOG(("signal: failure in suspend(%d), can't reset tty %d\n", sig, ttysl_fd));
1558 fprintf(stderr,"Can't reset tty \n");
1559 exit(1);
1560 }
1561
1562 sys_signal(sig, SIG_DFL); /* Set signal handler to default */
1563 sys_sigrelease(sig); /* Allow 'sig' to come through */
1564 kill(getpid(), sig); /* Send ourselves the signal */
1565 sys_sigblock(sig); /* Reset to old mask */
1566 sys_signal(sig, suspend); /* Reset signal handler */
1567
1568 if (tty_set(ttysl_fd) < 0) {
1569 DEBUGLOG(("signal: failure in suspend(%d), can't set tty %d\n", sig, ttysl_fd));
1570 fprintf(stderr,"Can't set tty raw \n");
1571 exit(1);
1572 }
1573 }
1574
1575 #endif
1576
cont(int sig)1577 static RETSIGTYPE cont(int sig)
1578 {
1579 if (tty_set(ttysl_fd) < 0) {
1580 DEBUGLOG(("signal: failure in cont(%d), can't set tty raw %d\n", sig, ttysl_fd));
1581 fprintf(stderr,"Can't set tty raw\n");
1582 exit(1);
1583 }
1584 }
1585
winch(int sig)1586 static RETSIGTYPE winch(int sig)
1587 {
1588 cols_needs_update = TRUE;
1589 }
1590 #endif /* HAVE_TERMCAP */
1591