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