/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1996-2020. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */
/*
 * Tty driver that reads one character at the time and provides a
 * smart line for output.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "erl_driver.h"

static int ttysl_init(void);
static ErlDrvData ttysl_start(ErlDrvPort, char*);

#ifdef HAVE_TERMCAP  /* else make an empty driver that cannot be opened */

#ifndef WANT_NONBLOCKING
#define WANT_NONBLOCKING
#endif

#include "sys.h"
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <unistd.h>
#include <termios.h>
#ifdef HAVE_WCWIDTH
#include <wchar.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H)
#define PRIMITIVE_UTF8_CHECK 1
#else
#include <langinfo.h>
#endif

#if defined IOV_MAX
#define MAXIOV IOV_MAX
#elif defined UIO_MAXIOV
#define MAXIOV UIO_MAXIOV
#else
#define MAXIOV 16
#endif

#define TRUE 1
#define FALSE 0

/* Termcap functions. */
int tgetent(char* bp, char *name);
int tgetnum(char* cap);
int tgetflag(char* cap);
char *tgetstr(char* cap, char** buf);
char *tgoto(char* cm, int col, int line);
int tputs(char* cp, int affcnt, int (*outc)(int c));

/* Terminal capabilites in which we are interested. */
static char *capbuf;
static char *up, *down, *left, *right;
static int cols, xn;
static volatile int cols_needs_update = FALSE;

/* The various opcodes. */
#define OP_PUTC 0
#define OP_MOVE 1
#define OP_INSC 2
#define OP_DELC 3
#define OP_BEEP 4
#define OP_PUTC_SYNC 5
/* Control op */
#define CTRL_OP_GET_WINSIZE 100
#define CTRL_OP_GET_UNICODE_STATE 101
#define CTRL_OP_SET_UNICODE_STATE 102

/* We use 1024 as the buf size as that was the default buf size of FILE streams
   on all platforms that I checked. */
#define TTY_BUFFSIZE 1024

static int lbuf_size = BUFSIZ;
static Uint32 *lbuf;		/* The current line buffer */
static int llen;		/* The current line length */
static int lpos;                /* The current "cursor position" in the line buffer */
                                /* NOTE: not the same as column position a char may not take a"
                                 * column to display or it might take many columns
                                 */
/* 
 * Tags used in line buffer to show that these bytes represent special characters,
 * Max unicode is 0x0010ffff, so we have lots of place for meta tags... 
 */
#define CONTROL_TAG 0x10000000U /* Control character, value in first position */
#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */
#define TAG_MASK    0xFF000000U

#define MAXSIZE (1 << 16)

#define COL(_l) ((_l) % cols)
#define LINE(_l) ((_l) / cols)

#define NL '\n'

/* Main interface functions. */
static void ttysl_stop(ErlDrvData);
static void ttysl_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
static void ttysl_to_tty(ErlDrvData, ErlDrvEvent);
static void ttysl_flush_tty(ErlDrvData);
static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
static void ttysl_stop_select(ErlDrvEvent, void*);
static Sint16 get_sint16(char*);

static ErlDrvPort ttysl_port;
static int ttysl_fd;
static int ttysl_terminate = 0;
static int ttysl_send_ok = 0;
static ErlDrvBinary *putcbuf;
static int putcpos;
static int putclen;

/* Functions that work on the line buffer. */
static int start_lbuf(void);
static int stop_lbuf(void);
static int put_chars(byte*,int);
static int move_rel(int);
static int ins_chars(byte *,int);
static int del_chars(int);
static int step_over_chars(int);
static int insert_buf(byte*,int);
static int write_buf(Uint32 *,int);
static int outc(int c);
static int move_cursor(int,int);
static int cp_pos_to_col(int cp_pos);


/* Termcap functions. */
static int start_termcap(void);
static int stop_termcap(void);
static int move_left(int);
static int move_right(int);
static int move_up(int);
static int move_down(int);
static void update_cols(void);

/* Terminal setting functions. */
static int tty_init(int,int,int,int);
static int tty_set(int);
static int tty_reset(int);
static ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int,
				  char *, ErlDrvSizeT, char **, ErlDrvSizeT);
#ifdef ERTS_NOT_USED
static RETSIGTYPE suspend(int);
#endif
static RETSIGTYPE cont(int);
static RETSIGTYPE winch(int);

/*#define LOG_DEBUG*/

#ifdef LOG_DEBUG
FILE *debuglog = NULL;

#define DEBUGLOG(X) 				\
do {						\
    if (debuglog != NULL) {			\
	my_debug_printf X;    			\
    }						\
} while (0)

static void my_debug_printf(char *fmt, ...)
{
    char buffer[1024];
    va_list args;

    va_start(args, fmt);
    erts_vsnprintf(buffer,1024,fmt,args);
    va_end(args);
    erts_fprintf(debuglog,"%s\n",buffer);
    /*erts_printf("Debuglog = %s\n",buffer);*/
}

#else

#define DEBUGLOG(X)

#endif

static int utf8_mode = 0;
static byte utf8buf[4]; /* for incomplete input */
static int utf8buf_size; /* size of incomplete input */

#  define IF_IMPL(x) x
#else
#  define IF_IMPL(x) NULL
#endif /* HAVE_TERMCAP */

/* Define the driver table entry. */
struct erl_drv_entry ttsl_driver_entry = {
    ttysl_init,
    ttysl_start,
    IF_IMPL(ttysl_stop),
    IF_IMPL(ttysl_from_erlang),
    IF_IMPL(ttysl_from_tty),
    IF_IMPL(ttysl_to_tty),
    "tty_sl", /* driver_name */
    NULL, /* finish */
    NULL, /* handle */
    IF_IMPL(ttysl_control),
    NULL, /* timeout */
    NULL, /* outputv */
    NULL, /* ready_async */
    IF_IMPL(ttysl_flush_tty),
    NULL, /* call */
    NULL, /* event */
    ERL_DRV_EXTENDED_MARKER,
    ERL_DRV_EXTENDED_MAJOR_VERSION,
    ERL_DRV_EXTENDED_MINOR_VERSION,
    0, /* ERL_DRV_FLAGs */
    NULL, /* handle2 */
    NULL, /* process_exit */
    IF_IMPL(ttysl_stop_select)
};


static int ttysl_init(void)
{
#ifdef HAVE_TERMCAP
    ttysl_port = (ErlDrvPort)-1;
    ttysl_fd = -1;
    lbuf = NULL;		/* For line buffer handling */
    capbuf = NULL;		/* For termcap handling */
#endif
#ifdef LOG_DEBUG
    {
	char *dl;
	if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) {
	    debuglog = fopen(dl,"w+");
	    if (debuglog != NULL)
		setbuf(debuglog,NULL);
	}
	DEBUGLOG(("ttysl_init: Debuglog = %s(0x%ld)\n",dl,(long) debuglog));
    }
#endif
    return 0;
}

static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
{
#ifndef HAVE_TERMCAP
    return ERL_DRV_ERROR_GENERAL;
#else
    char *s, *t, *l;
    int canon, echo, sig;	/* Terminal characteristics */
    int flag;
    extern int using_oldshell; /* set this to let the rest of erts know */

    DEBUGLOG(("ttysl_start: driver input \"%s\", ttysl_port = %d (-1 expected)", buf, ttysl_port));
    utf8buf_size = 0;
    if (ttysl_port != (ErlDrvPort)-1) {
        DEBUGLOG(("ttysl_start: failure with ttysl_port = %d, not initialized properly?\n", ttysl_port));
        return ERL_DRV_ERROR_GENERAL;
    }

    DEBUGLOG(("ttysl_start: isatty(0) = %d (1 expected), isatty(1) = %d (1 expected)", isatty(0), isatty(1)));
    if (!isatty(0) || !isatty(1)) {
        DEBUGLOG(("ttysl_start: failure in isatty, isatty(0) = %d, isatty(1) = %d", isatty(0), isatty(1)));
	return ERL_DRV_ERROR_GENERAL;
    }

    /* Set the terminal modes to default leave as is. */
    canon = echo = sig = 0;

    /* Parse the input parameters. */
    for (s = strchr(buf, ' '); s; s = t) {
        s++;
        /* Find end of this argument (start of next) and insert NUL. */
        if ((t = strchr(s, ' '))) {
            *t = '\0';
        }
        if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) {
            if (s[1] == 'c') canon = flag;
            if (s[1] == 'e') echo = flag;
            if (s[1] == 's') sig = flag;
        }
        else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0) {
            DEBUGLOG(("ttysl_start: failed to open ttysl_fd, open(%s, O_RDWR, 0)) = %d\n", s, ttysl_fd));
            return ERL_DRV_ERROR_GENERAL;
        }
    }

    if (ttysl_fd < 0)
      ttysl_fd = 0;

    if (tty_init(ttysl_fd, canon, echo, sig) < 0 ||
        tty_set(ttysl_fd) < 0) {
        DEBUGLOG(("ttysl_start: failed init tty or set tty\n"));
	ttysl_port = (ErlDrvPort)-1;
	tty_reset(ttysl_fd);
	return ERL_DRV_ERROR_GENERAL;
    }

    /* Set up smart line and termcap stuff. */
    if (!start_lbuf() || !start_termcap()) {
        DEBUGLOG(("ttysl_start: failed to start_lbuf or start_termcap\n"));
	stop_lbuf();		/* Must free this */
	tty_reset(ttysl_fd);
	return ERL_DRV_ERROR_GENERAL;
    }

    SET_NONBLOCKING(ttysl_fd);

#ifdef PRIMITIVE_UTF8_CHECK
    setlocale(LC_CTYPE, "");  /* Set international environment, 
				 ignore result */
    if (((l = getenv("LC_ALL"))   && *l) ||
	((l = getenv("LC_CTYPE")) && *l) ||
	((l = getenv("LANG"))     && *l)) {
	if (strstr(l, "UTF-8"))
	    utf8_mode = 1;
    }
    
#else
    l = setlocale(LC_CTYPE, "");  /* Set international environment */
    if (l != NULL) {
	utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);
	DEBUGLOG(("ttysl_start: setlocale: %s",l));
    }
#endif
    DEBUGLOG(("ttysl_start: utf8_mode is %s",(utf8_mode) ? "on" : "off"));
    sys_signal(SIGCONT, cont);
    sys_signal(SIGWINCH, winch);

    driver_select(port, (ErlDrvEvent)(UWord)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1);
    ttysl_port = port;

    /* we need to know this when we enter the break handler */
    using_oldshell = 0;

    DEBUGLOG(("ttysl_start: successful start\n"));
    return (ErlDrvData)ttysl_port;	/* Nothing important to return */
#endif /* HAVE_TERMCAP */
}

#ifdef HAVE_TERMCAP

#define DEF_HEIGHT 24
#define DEF_WIDTH 80
static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
{
#ifdef TIOCGWINSZ 
    struct winsize ws;
    if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) {
	*width = (Uint32) ws.ws_col;
	*height = (Uint32) ws.ws_row;
	if (*width <= 0) 
	    *width = DEF_WIDTH;
	if (*height <= 0) 
	    *height = DEF_HEIGHT;
	return;
    }
#endif
    *width = DEF_WIDTH;
    *height = DEF_HEIGHT;
}
    
static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
				  unsigned int command,
				  char *buf, ErlDrvSizeT len,
				  char **rbuf, ErlDrvSizeT rlen)
{
    char resbuff[2*sizeof(Uint32)];
    ErlDrvSizeT res_size;

    command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER;
    switch (command) {
    case CTRL_OP_GET_WINSIZE:
	{
	    Uint32 w,h;
	    ttysl_get_window_size(&w,&h);
	    memcpy(resbuff,&w,sizeof(Uint32));
	    memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32));
	    res_size = 2*sizeof(Uint32);
	}
	break;
    case CTRL_OP_GET_UNICODE_STATE:
	*resbuff = (utf8_mode) ? 1 : 0;
	res_size = 1;
	break;
    case CTRL_OP_SET_UNICODE_STATE:
	if (len > 0) {
	    int m = (int) *buf;
	    *resbuff = (utf8_mode) ? 1 : 0;
	    res_size = 1;
	    utf8_mode = (m) ? 1 : 0;
	} else {
	    return 0;
	}
	break;
    default:
	return -1;
    }
    if (rlen < res_size) {
	*rbuf = driver_alloc(res_size);
    }
    memcpy(*rbuf,resbuff,res_size);
    return res_size;
}


static void ttysl_stop(ErlDrvData ttysl_data)
{
    DEBUGLOG(("ttysl_stop: ttysl_port = %d\n",ttysl_port));
    if (ttysl_port != (ErlDrvPort)-1) {
	stop_lbuf();
	stop_termcap();
	tty_reset(ttysl_fd);
	driver_select(ttysl_port, (ErlDrvEvent)(UWord)ttysl_fd,
                      ERL_DRV_WRITE|ERL_DRV_READ|ERL_DRV_USE, 0);
	sys_signal(SIGCONT, SIG_DFL);
	sys_signal(SIGWINCH, SIG_DFL);
    }
    ttysl_port = (ErlDrvPort)-1;
    ttysl_fd = -1;
    ttysl_terminate = 0;
    /* return TRUE; */
}

static int put_utf8(int ch, byte *target, int sz, int *pos)
{
    Uint x = (Uint) ch;
    if (x < 0x80) {
    if (*pos >= sz) {
	return -1;
    }
	target[(*pos)++] = (byte) x;
    }
    else if (x < 0x800) {
	if (((*pos) + 1) >= sz) {
	    return -1;
	}
	target[(*pos)++] = (((byte) (x >> 6)) | 
			    ((byte) 0xC0));
	target[(*pos)++] = (((byte) (x & 0x3F)) | 
			    ((byte) 0x80));
    } else if (x < 0x10000) {
	if ((x >= 0xD800 && x <= 0xDFFF) ||
	    (x == 0xFFFE) ||
	    (x == 0xFFFF)) { /* Invalid unicode range */
	    return -1;
	}
	if (((*pos) + 2) >= sz) {
	    return -1;
	}

	target[(*pos)++] = (((byte) (x >> 12)) | 
			    ((byte) 0xE0));
	target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F)  | 
			    ((byte) 0x80));
	target[(*pos)++] = (((byte) (x & 0x3F)) | 
			    ((byte) 0x80));
    } else if (x < 0x110000) { /* Standard imposed max */
	if (((*pos) + 3) >= sz) {
	    return -1;
	}
	target[(*pos)++] = (((byte) (x >> 18)) | 
			    ((byte) 0xF0));
	target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F)  | 
			    ((byte) 0x80));
	target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F)  | 
			    ((byte) 0x80));
	target[(*pos)++] = (((byte) (x & 0x3F)) | 
			    ((byte) 0x80));
    } else {
	return -1;
    }
    return 0;
}
    

static int pick_utf8(byte *s, int sz, int *pos) 
{
    int size = sz - (*pos);
    byte *source;
    Uint unipoint;

    if (size > 0) {
	source = s + (*pos);
	if (((*source) & ((byte) 0x80)) == 0) {
	    unipoint = (int) *source;
	    ++(*pos);
	    return (int) unipoint;
	} else if (((*source) & ((byte) 0xE0)) == 0xC0) {
	    if (size < 2) {
		return -2;
	    }
	    if (((source[1] & ((byte) 0xC0)) != 0x80) ||
		((*source) < 0xC2) /* overlong */) {
		return -1;
	    }
	    (*pos) += 2;
	    unipoint = 
		(((Uint) ((*source) & ((byte) 0x1F))) << 6) |
		((Uint) (source[1] & ((byte) 0x3F))); 	
	    return (int) unipoint;
	} else if (((*source) & ((byte) 0xF0)) == 0xE0) {
	    if (size < 3) {
		return -2;
	    }
	    if (((source[1] & ((byte) 0xC0)) != 0x80) ||
		((source[2] & ((byte) 0xC0)) != 0x80) ||
		(((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) {
		return -1;
	    }
	    if ((((*source) & ((byte) 0xF)) == 0xD) && 
		((source[1] & 0x20) != 0)) {
		return -1;
	    }
	    if (((*source) == 0xEF) && (source[1] == 0xBF) &&
		((source[2] == 0xBE) || (source[2] == 0xBF))) {
		return -1;
	    }
	    (*pos) += 3;
	    unipoint = 
		(((Uint) ((*source) & ((byte) 0xF))) << 12) |
		(((Uint) (source[1] & ((byte) 0x3F))) << 6) |
		((Uint) (source[2] & ((byte) 0x3F))); 	 	
	    return (int) unipoint;
	} else if (((*source) & ((byte) 0xF8)) == 0xF0) {
	    if (size < 4) {
		return -2 ;
	    }
	    if (((source[1] & ((byte) 0xC0)) != 0x80) ||
		((source[2] & ((byte) 0xC0)) != 0x80) ||
		((source[3] & ((byte) 0xC0)) != 0x80) ||
		(((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) {
		return -1;
	    }
	    if ((((*source) & ((byte)0x7)) > 0x4U) ||
		((((*source) & ((byte)0x7)) == 0x4U) && 
		 ((source[1] & ((byte)0x3F)) > 0xFU))) {
		return -1;
	    }
	    (*pos) += 4;
	    unipoint = 
		(((Uint) ((*source) & ((byte) 0x7))) << 18) |
		(((Uint) (source[1] & ((byte) 0x3F))) << 12) |
		(((Uint) (source[2] & ((byte) 0x3F))) << 6) |
		((Uint) (source[3] & ((byte) 0x3F))); 	 	
	    return (int) unipoint;
	} else {
	    return -1;
	}
    } else {
	return -1;
    }
}

static int octal_or_hex_positions(Uint c) 
{
    int x = 0;
    Uint ch = c;
    if (!ch) {
	return 1;
    }
    while(ch) {
	++x;
	ch >>= 3;
    }
    if (x <= 3) {
	return 3;
    }
    /* \x{H ...} format when larger than \777 */
    x = 0;
    ch = c;
    while(ch) {
	++x;
	ch >>= 4;
    }
    return x+3;
}

static void octal_or_hex_format(Uint ch, byte *buf, int *pos)
{
    static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9',
				'A','B','C','D','E','F'};
    int num = octal_or_hex_positions(ch);
    if (num != 3) {
	buf[(*pos)++] = 'x';
	buf[(*pos)++] = '{';
	num -= 3;
	while(num--) {
	    buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)];
	}
	buf[(*pos)++] = '}';
    } else {
	while(num--) {
	    buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0');
	}
    }	
}

/*
 * Check that there is enough room in all buffers to copy all pad chars
 * and stiff we need If not, realloc lbuf.
 */
static int check_buf_size(byte *s, int n)
{
    int pos = 0;
    int ch;
    int size = 10;

    DEBUGLOG(("check_buf_size: n = %d",n));
    while(pos < n) {
	/* Indata is always UTF-8 */
	if ((ch = pick_utf8(s,n,&pos)) < 0) {
	    /* XXX temporary allow invalid chars */
	    ch = (int) s[pos];
	    DEBUGLOG(("check_buf_size: Invalid UTF8:%d",ch));
	    ++pos;
	} 
	if (utf8_mode) { /* That is, terminal is UTF8 compliant */
	    if (ch >= 128 || isprint(ch)) {
#ifdef HAVE_WCWIDTH
		int width;
#endif
		DEBUGLOG(("check_buf_size: Printable(UTF-8:%d):%d",pos,ch));
		size++;
#ifdef HAVE_WCWIDTH
		if ((width = wcwidth(ch)) > 1) {
		    size += width - 1;
		}
#endif
	    } else if (ch == '\t') {
		size += 8;
	    } else {
		DEBUGLOG(("check_buf_size: Magic(UTF-8:%d):%d",pos,ch));
		size += 2;
	    }
	} else {
	    if (ch <= 255 && isprint(ch)) {
		DEBUGLOG(("check_buf_size: Printable:%d",ch));
		size++;
	    } else if (ch == '\t') 
		size += 8;
	    else if (ch >= 128) {
		DEBUGLOG(("check_buf_size: Non printable:%d",ch));
		size += (octal_or_hex_positions(ch) + 1);
	    }
	    else {
		DEBUGLOG(("check_buf_size: Magic:%d",ch));
		size += 2;
	    }
	}
    }
		
    if (size + lpos >= lbuf_size) {

	lbuf_size = size + lpos + BUFSIZ;
	if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) {
            DEBUGLOG(("check_buf_size: alloc failure of %d bytes", lbuf_size * sizeof(Uint32)));
	    driver_failure(ttysl_port, -1);
	    return(0);
	}
    }
    DEBUGLOG(("check_buf_size: success\n"));
    return(1);
}


static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, ErlDrvSizeT count)
{
    ErlDrvSizeT sz;

    sz = driver_sizeq(ttysl_port);

    putclen = count > TTY_BUFFSIZE ? TTY_BUFFSIZE : count;
    putcbuf = driver_alloc_binary(putclen);
    putcpos = 0;

    if (lpos > MAXSIZE) 
	put_chars((byte*)"\n", 1);

    DEBUGLOG(("ttysl_from_erlang: OP = %d", buf[0]));

    switch (buf[0]) {
    case OP_PUTC_SYNC:
        /* Using sync means that we have to send an ok to the
           controlling process for each command call. We delay
           sending ok if the driver queue exceeds a certain size.
           We do not set ourselves as a busy port, as this
           could be very bad for user_drv, if it gets blocked on
           the port_command. */
        /* fall through */
    case OP_PUTC:
	DEBUGLOG(("ttysl_from_erlang: OP: Putc(%lu)",(unsigned long) count-1));
	if (check_buf_size((byte*)buf+1, count-1) == 0)
	    return; 
	put_chars((byte*)buf+1, count-1);
	break;
    case OP_MOVE:
	move_rel(get_sint16(buf+1));
	break;
    case OP_INSC:
	if (check_buf_size((byte*)buf+1, count-1) == 0)
	    return;
	ins_chars((byte*)buf+1, count-1);
	break;
    case OP_DELC:
	del_chars(get_sint16(buf+1));
	break;
    case OP_BEEP:
	outc('\007');
	break;
    default:
	/* Unknown op, just ignore. */
	break;
    }

    driver_enq_bin(ttysl_port,putcbuf,0,putcpos);
    driver_free_binary(putcbuf);

    if (sz == 0) {
        for (;;) {
            int written, qlen;
            SysIOVec *iov;

            iov = driver_peekq(ttysl_port,&qlen);
            if (iov)
                written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
            else
                written = 0;
            if (written < 0) {
                if (errno == ERRNO_BLOCK || errno == EINTR) {
                    driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
                                  ERL_DRV_USE|ERL_DRV_WRITE,1);
                    break;
                } else {
                    DEBUGLOG(("ttysl_from_erlang: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
		    driver_failure_posix(ttysl_port, errno);
		    return;
                }
            } else {
                if (driver_deq(ttysl_port, written) == 0)
                    break;
            }
        }
    }

    if (buf[0] == OP_PUTC_SYNC) {
        if (driver_sizeq(ttysl_port) > TTY_BUFFSIZE && !ttysl_terminate) {
            /* We delay sending the ack until the buffer has been consumed */
            ttysl_send_ok = 1;
        } else {
            ErlDrvTermData spec[] = {
                ERL_DRV_PORT, driver_mk_port(ttysl_port),
                ERL_DRV_ATOM, driver_mk_atom("ok"),
                ERL_DRV_TUPLE, 2
            };
            ASSERT(ttysl_send_ok == 0);
            erl_drv_output_term(driver_mk_port(ttysl_port), spec,
                                sizeof(spec) / sizeof(spec[0]));
        }
    }

    return; /* TRUE; */
}

static void ttysl_to_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) {
    for (;;) {
        int written, qlen;
        SysIOVec *iov;
        ErlDrvSizeT sz;

        iov = driver_peekq(ttysl_port,&qlen);
        
        DEBUGLOG(("ttysl_to_tty: qlen = %d", qlen));

        if (iov)
            written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen);
        else
            written = 0;
        if (written < 0) {
            if (errno == EINTR) {
	        continue;
	    } else if (errno != ERRNO_BLOCK){
                DEBUGLOG(("ttysl_to_tty: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno));
	        driver_failure_posix(ttysl_port, errno);
            }
            break;
        } else {
            sz = driver_deq(ttysl_port, written);
            if (sz < TTY_BUFFSIZE && ttysl_send_ok) {
                ErlDrvTermData spec[] = {
                    ERL_DRV_PORT, driver_mk_port(ttysl_port),
                    ERL_DRV_ATOM, driver_mk_atom("ok"),
                    ERL_DRV_TUPLE, 2
                };
                ttysl_send_ok = 0;
                erl_drv_output_term(driver_mk_port(ttysl_port), spec,
                                    sizeof(spec) / sizeof(spec[0]));
            }
            if (sz == 0) {
                driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd,
                              ERL_DRV_WRITE,0);
                if (ttysl_terminate) {
                    /* flush has been called, which means we should terminate
                       when queue is empty. This will not send any exit
                       message */
                    DEBUGLOG(("ttysl_to_tty: ttysl_terminate normal\n"));
                    driver_failure_atom(ttysl_port, "normal");
		}
                break;
            }
        }
    }

    return;
}

static void ttysl_flush_tty(ErlDrvData ttysl_data) {
    DEBUGLOG(("ttysl_flush_tty: .."));
    ttysl_terminate = 1;
    return;
}

static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
{
    byte b[1024];
    ssize_t i;
    int ch = 0, pos = 0;
    int left = 1024;
    byte *p = b;
    byte t[1024];
    int tpos;

    if (utf8buf_size > 0) {
	memcpy(b,utf8buf,utf8buf_size);
	left -= utf8buf_size;
	p += utf8buf_size;
	utf8buf_size = 0;
    }

    DEBUGLOG(("ttysl_from_tty: remainder = %d", left));
    
    if ((i = read((int)(SWord)fd, (char *) p, left)) >= 0) {
	if (p != b) {
	    i += (p - b);
	}
	if (utf8_mode) { /* Hopefully an UTF8 terminal */
	    while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0)
		;
	    if (ch == -2 && i - pos <= 4) {
		/* bytes left to care for */
		utf8buf_size = i -pos;
		memcpy(utf8buf,b+pos,utf8buf_size);
	    } else if (ch == -1) {
		DEBUGLOG(("ttysl_from_tty: Giving up on UTF8 mode, invalid character"));
		utf8_mode = 0;
		goto latin_terminal;
	    }
	    driver_output(ttysl_port, (char *) b, pos);
	} else {
	latin_terminal:
	    tpos = 0;
	    while (pos < i) {
		while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
		    put_utf8((int) b[pos++], t, 1024, &tpos);
		}
		driver_output(ttysl_port, (char *) t, tpos);
		tpos = 0;
	    }
	}
    } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
        DEBUGLOG(("ttysl_from_tty: driver failure in read(%d,..) = %d (errno = %d)\n", (int)(SWord)fd, i, errno));
	driver_failure(ttysl_port, -1);
    }
}

static void ttysl_stop_select(ErlDrvEvent e, void* _)
{
    int fd = (int)(long)e;
    if (fd != 0) {
	close(fd);
    }
}

/* Procedures for putting and getting integers to/from strings. */
static Sint16 get_sint16(char *s)
{
    return ((*s << 8) | ((byte*)s)[1]);
}

static int start_lbuf(void)
{
    if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32))))
      return FALSE;
    llen = 0;
    lpos = 0;
    return TRUE;
}

static int stop_lbuf(void)
{
    if (lbuf) {
	driver_free(lbuf);
	lbuf = NULL;
    }
    return TRUE;
}

/* Put l bytes (in UTF8) from s into the buffer and output them. */
static int put_chars(byte *s, int l)
{
    int n;

    n = insert_buf(s, l);
    if (lpos > llen)
        llen = lpos;
    if (n > 0)
      write_buf(lbuf + lpos - n, n);
    return TRUE;
}

/*
 * Move the current postition forwards or backwards within the current
 * line. We know about padding.
 */
static int move_rel(int n)
{
    int npos;			/* The new position */

    /* Step forwards or backwards over the buffer. */
    npos = step_over_chars(n);

    /* Calculate move, updates pointers and move the cursor. */
    move_cursor(lpos, npos);
    lpos = npos;
    return TRUE;
}

/* Insert characters into the buffer at the current position. */
static int ins_chars(byte *s, int l)
{
    int n, tl;
    Uint32 *tbuf = NULL;    /* Suppress warning about use-before-set */

    /* Move tail of buffer to make space. */
    if ((tl = llen - lpos) > 0) {
	if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL)
	    return FALSE;
	memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32));
    }
    n = insert_buf(s, l);
    if (tl > 0) {
	memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32));
	driver_free(tbuf);
    }
    llen += n;
    write_buf(lbuf + (lpos - n), llen - (lpos - n));
    move_cursor(llen, lpos);
    return TRUE;
}

/*
 * Delete characters in the buffer. Can delete characters before (n < 0)
 * and after (n > 0) the current position. Cursor left at beginning of
 * deleted block.
 */
static int del_chars(int n)
{
    int i, l, r;
    int pos;
    int gcs; /* deleted grapheme characters */

    update_cols();

    /* Step forward or backwards over n logical characters. */
    pos = step_over_chars(n);
    DEBUGLOG(("del_chars: %d from %d %d %d\n", n, lpos, pos, llen));
    if (pos > lpos) {
	l = pos - lpos;		/* Buffer characters to delete */
	r = llen - lpos - l;	/* Characters after deleted */
        gcs = cp_pos_to_col(pos) - cp_pos_to_col(lpos);
	/* Fix up buffer and buffer pointers. */
	if (r > 0)
	    memmove(lbuf + lpos, lbuf + pos, r * sizeof(Uint32));
	llen -= l;
	/* Write out characters after, blank the tail and jump back to lpos. */
	write_buf(lbuf + lpos, r);
	for (i = gcs ; i > 0; --i)
	  outc(' ');
	if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
	{
	   outc(' ');
	   move_left(1);
	}
	move_cursor(llen + gcs, lpos);
    }
    else if (pos < lpos) {
	l = lpos - pos;		/* Buffer characters */
	r = llen - lpos;	/* Characters after deleted */
	gcs = -move_cursor(lpos, lpos-l);	/* Move back */
	/* Fix up buffer and buffer pointers. */
	if (r > 0)
	    memmove(lbuf + pos, lbuf + lpos, r * sizeof(Uint32));
	lpos -= l;
	llen -= l;
	/* Write out characters after, blank the tail and jump back to lpos. */
	write_buf(lbuf + lpos, r);
	for (i = gcs ; i > 0; --i)
          outc(' ');
        if (xn && COL(cp_pos_to_col(llen)+gcs) == 0)
	{
          outc(' ');
          move_left(1);
	}
        move_cursor(llen + gcs, lpos);
    }
    return TRUE;
}

/* Step over n logical characters, check for overflow. */
static int step_over_chars(int n)
{
    Uint32 *c, *beg, *end;

    beg = lbuf;
    end = lbuf + llen;
    c = lbuf + lpos;
    for ( ; n > 0 && c < end; --n) {
	c++;
	while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
	    c++;
    }
    for ( ; n < 0 && c > beg; n++) {
	--c;
	while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
	    --c;
    }
    return c - lbuf;
}

/*
 * Insert n characters into the buffer at lpos.
 * Know about pad characters and treat \n specially.
 */

static int insert_buf(byte *s, int n)
{
    int pos = 0;
    int buffpos = lpos;
    int ch;

    while (pos < n) {
	if ((ch = pick_utf8(s,n,&pos)) < 0) {
	    /* XXX temporary allow invalid chars */
	    ch = (int) s[pos];
	    DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch));
	    ++pos;
	}
	if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) {
	    DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch));
	    lbuf[lpos++] = (Uint32) ch;
	} else if (ch >= 128) { /* not utf8 mode */
	    int nc = octal_or_hex_positions(ch);
	    lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG;
	    while (nc--) {
		lbuf[lpos++] = ESCAPED_TAG;
	    }
	} else if (ch == '\t') {
	    do {
		lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
		ch = 0;
	    } while (lpos % 8);
	} else if (ch == '\e') {
	    DEBUGLOG(("insert_buf: ANSI Escape: \\e"));
	    lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
	} else if (ch == '\n' || ch == '\r') {
	    write_buf(lbuf + buffpos, lpos - buffpos);
                outc('\r');
                if (ch == '\n')
                    outc('\n');
	    if (llen > lpos) {
		memcpy(lbuf, lbuf + lpos, llen - lpos);
	    }
	    llen -= lpos;
	    lpos = buffpos = 0;
	} else {
	    DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch));
	    lbuf[lpos++] = ch | CONTROL_TAG;
	    lbuf[lpos++] = CONTROL_TAG;
	}
    }
    return lpos - buffpos; /* characters "written" into 
			      current buffer (may be less due to newline) */
}
	


/*
 * Write n characters in line buffer starting at s. Be smart about
 * non-printables. Know about pad characters and that \n can never
 * occur normally.
 */

static int write_buf(Uint32 *s, int n)
{
    byte ubuf[4];
    int ubytes = 0, i;
    byte lastput = ' ';

    update_cols();

    while (n > 0) {
	if (!(*s & TAG_MASK) ) {
	    if (utf8_mode) {
		ubytes = 0;
		if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) {
		    for (i = 0; i < ubytes; ++i) {
			outc(ubuf[i]);
		    }
		    lastput = 0; /* Means the last written character was multibyte UTF8 */
		}
	    } else {
		outc((byte) *s);
		lastput = (byte) *s;
	    }
	    --n;
	    ++s;
	} else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
	    outc(lastput = ' ');
	    --n; s++;
	    while (n > 0 && *s == CONTROL_TAG) {
		outc(lastput = ' ');
		--n; s++;
	    }
	} else if (*s == (CONTROL_TAG | ((Uint32) '\e'))) {
	    outc(lastput = '\e');
	    --n;
	    ++s;
	} else if (*s & CONTROL_TAG) {
	    outc('^');
	    outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40)));
	    n -= 2;
	    s += 2;
	} else if (*s & ESCAPED_TAG) {
	    Uint32 ch = *s & ~(TAG_MASK);
	    byte *octbuff;
	    byte octtmp[256];
	    int octbytes;
	    DEBUGLOG(("write_buf: Escaped: %d", ch));
	    octbytes = octal_or_hex_positions(ch);
	    if (octbytes > 256) {
		octbuff = driver_alloc(octbytes);
	    } else {
		octbuff = octtmp;
	    }
	    octbytes = 0;
	    octal_or_hex_format(ch, octbuff, &octbytes);
            DEBUGLOG(("write_buf: octbytes: %d", octbytes));
	    outc('\\');
	    for (i = 0; i < octbytes; ++i) {
		outc(lastput = octbuff[i]);
		DEBUGLOG(("write_buf: outc: %d", (int) lastput));
	    }
	    n -= octbytes+1;
	    s += octbytes+1;
	    if (octbuff != octtmp) {
		driver_free(octbuff);
	    }
	} else {
	    DEBUGLOG(("write_buf: Very unexpected character %d",(int) *s));
	    ++n;
	    --s;
	}
    }
    /* Check landed in first column of new line and have 'xn' bug. */
    n = s - lbuf;
    if (xn && n != 0 && COL(cp_pos_to_col(n)) == 0) {
	if (n >= llen) {
	    outc(' ');
	} else if (lastput == 0) { /* A multibyte UTF8 character */
	    for (i = 0; i < ubytes; ++i) {
		outc(ubuf[i]);
	    }
	} else {
	    outc(lastput);
	}
	move_left(1);
    }
    return TRUE;
}


/* The basic procedure for outputting one character. */
static int outc(int c)
{
    putcbuf->orig_bytes[putcpos++] = c;
    if (putcpos == putclen) {
        driver_enq_bin(ttysl_port,putcbuf,0,putclen);
        driver_free_binary(putcbuf);
        putcpos = 0;
        putclen = TTY_BUFFSIZE;
        putcbuf = driver_alloc_binary(BUFSIZ);
    }
    return 1;
}

static int move_cursor(int from_pos, int to_pos)
{
    int from_col, to_col;
    int dc, dl;
    update_cols();

    from_col = cp_pos_to_col(from_pos);
    to_col = cp_pos_to_col(to_pos);

    dc = COL(to_col) - COL(from_col);
    dl = LINE(to_col) - LINE(from_col);
    DEBUGLOG(("move_cursor: from %d %d to %d %d => %d %d\n",
              from_pos, from_col, to_pos, to_col, dl, dc));
    if (dl > 0)
      move_down(dl);
    else if (dl < 0)
      move_up(-dl);
    if (dc > 0)
      move_right(dc);
    else if (dc < 0)
      move_left(-dc);
    return to_col-from_col;
}

/*
 * Returns the length of an ANSI escape code in a buffer, this function only consider
 * color escape sequences like `\e[33m` or `\e[21;33m`. If a sequence has no valid
 * terminator, the length is equal the number of characters between `\e` and the first
 * invalid character, inclusive.
 */

static int ansi_escape_width(Uint32 *s, int max_length)
{
    int i;
    
    if (*s != (CONTROL_TAG | ((Uint32) '\e'))) {
       return 0;
    } else if (max_length <= 1) {
       return 1;
    } else if (s[1] != '[') {
       return 2;
    }
    
    for (i = 2; i < max_length && (s[i] == ';' || (s[i] >= '0' && s[i] <= '9')); i++);

    return i + 1;
}

static int cp_pos_to_col(int cp_pos)
{
    /*
     * If we don't have any character width information. Assume that
     * code points are one column wide
     */
    int w = 1;
    int col = 0;
    int i = 0;
    int j;

    if (cp_pos > llen) {
        col += cp_pos - llen;
        cp_pos = llen;
    }

    while (i < cp_pos) {
       j = ansi_escape_width(lbuf + i, llen - i);

       if (j > 0) {
           i += j;
       } else {
#ifdef HAVE_WCWIDTH
           w = wcwidth(lbuf[i]);
#endif
           if (w > 0) {
              col += w;
           }
           i++;
       }
    }

    return col;    
}

static int start_termcap(void)
{
    int eres;
    size_t envsz = 1024;
    char *env = NULL;
    char *c;
    int tres;

    DEBUGLOG(("start_termcap: .."));

    capbuf = driver_alloc(1024);
    if (!capbuf)
	goto termcap_false;
    eres = erl_drv_getenv("TERM", capbuf, &envsz);
    if (eres == 0)
	env = capbuf;
    else if (eres < 0) {
        DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d\n", eres));
	goto termcap_false;
    } else /* if (eres > 1) */ {
      char *envbuf = driver_alloc(envsz);
      if (!envbuf)
	  goto termcap_false;
      while (1) {
	  char *newenvbuf;
	  eres = erl_drv_getenv("TERM", envbuf, &envsz);
	  if (eres == 0)
	      break;
	  newenvbuf = driver_realloc(envbuf, envsz);
          if (eres < 0 || !newenvbuf) {
              DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d or realloc buf == %p\n", eres, newenvbuf));
	      env = newenvbuf ? newenvbuf : envbuf;
	      goto termcap_false;
	  }
	  envbuf = newenvbuf;
      }
      env = envbuf;
    }
    if ((tres = tgetent((char*)lbuf, env)) <= 0) {
        DEBUGLOG(("start_termcap: failure in tgetent(..) = %d\n", tres));
        goto termcap_false;
    }
    if (env != capbuf) {
	env = NULL;
	driver_free(env);
    }
    c = capbuf;
    cols = tgetnum("co");
    if (cols <= 0)
	cols = DEF_WIDTH;
    xn = tgetflag("xn");
    up = tgetstr("up", &c);
    if (!(down = tgetstr("do", &c)))
      down = "\n";
    if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c)))
      left = "\b";		/* Can't happen - but does on Solaris 2 */
    right = tgetstr("nd", &c);
    if (up && down && left && right) {
        DEBUGLOG(("start_termcap: successful start\n"));
        return TRUE;
    }
    DEBUGLOG(("start_termcap: failed start\n"));
 termcap_false:
    if (env && env != capbuf)
	driver_free(env);
    if (capbuf)
	driver_free(capbuf);
    capbuf = NULL;
    return FALSE;
}

static int stop_termcap(void)
{
    if (capbuf) driver_free(capbuf);
    capbuf = NULL;
    return TRUE;
}

static int move_left(int n)
{
    while (n-- > 0)
      tputs(left, 1, outc);
    return TRUE;
}

static int move_right(int n)
{
    while (n-- > 0)
      tputs(right, 1, outc);
    return TRUE;
}

static int move_up(int n)
{
    while (n-- > 0)
      tputs(up, 1, outc);
    return TRUE;
}

static int move_down(int n)
{
    while (n-- > 0)
      tputs(down, 1, outc);
    return TRUE;
}


/*
 * Updates cols if terminal has resized (SIGWINCH). Should be called
 * at the start of any function that uses the COL or LINE macros. If
 * the terminal is resized after calling this function but before use
 * of the macros, then we may write to the wrong screen location.
 *
 * We cannot call this from the SIGWINCH handler because it uses
 * ioctl() which is not a safe function as listed in the signal(7)
 * man page.
 */
static void update_cols(void)
{
    Uint32 width, height;
 
    if (cols_needs_update) {
	cols_needs_update = FALSE;
	ttysl_get_window_size(&width, &height);
	cols = width;
    }
}


/*
 * Put a terminal device into non-canonical mode with ECHO off.
 * Before doing so we first save the terminal's current mode,
 * assuming the caller will call the tty_reset() function
 * (also in this file) when it's done with raw mode.
 */

static struct termios tty_smode, tty_rmode;

static int tty_init(int fd, int canon, int echo, int sig) {
    int tres;
    DEBUGLOG(("tty_init: fd = %d, canon = %d, echo = %d, sig = %d", fd, canon, echo, sig));
    if ((tres = tcgetattr(fd, &tty_rmode)) < 0) {
        DEBUGLOG(("tty_init: failure in tcgetattr(%d,..) = %d\n", fd, tres));
        return -1;
    }
    tty_smode = tty_rmode;

    /* Default characteristics for all usage including termcap output. */
    tty_smode.c_iflag &= ~ISTRIP;

    /* Turn canonical (line mode) on off. */
    if (canon > 0) {
	tty_smode.c_iflag |= ICRNL;
	tty_smode.c_lflag |= ICANON;
	tty_smode.c_oflag |= OPOST;
	tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF];
#ifdef VDSUSP
	tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP];
#endif
    }
    if (canon < 0) {
	tty_smode.c_iflag &= ~ICRNL;
	tty_smode.c_lflag &= ~ICANON;
	tty_smode.c_oflag &= ~OPOST;
	/* Must get these really right or funny effects can occur. */
	tty_smode.c_cc[VMIN] = 1;
	tty_smode.c_cc[VTIME] = 0;
#ifdef VDSUSP
	tty_smode.c_cc[VDSUSP] = 0;
#endif
    }

    /* Turn echo on or off. */
    if (echo > 0)
      tty_smode.c_lflag |= ECHO;
    if (echo < 0)
      tty_smode.c_lflag &= ~ECHO;

    /* Set extra characteristics for "RAW" mode, no signals. */
    if (sig > 0) {
	/* Ignore IMAXBEL as not POSIX. */
#ifndef QNX
	tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY);
#else
	tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON);
#endif
	tty_smode.c_lflag |= (ISIG|IEXTEN);
    }
    if (sig < 0) {
	/* Ignore IMAXBEL as not POSIX. */
#ifndef QNX
	tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY);
#else
	tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON);
#endif	
	tty_smode.c_lflag &= ~(ISIG|IEXTEN);
    }
    DEBUGLOG(("tty_init: successful init\n"));
    return 0;
}

/*
 * Set/restore a terminal's mode to whatever it was on the most
 * recent call to the tty_init() function above.
 */

static int tty_set(int fd)
{
    int tres;
    DEBUGF(("tty_set: Setting tty...\n"));

    if ((tres = tcsetattr(fd, TCSANOW, &tty_smode)) < 0) {
        DEBUGLOG(("tty_set: failure in tcgetattr(%d,..) = %d\n", fd, tres));
	return(-1);
    }
    return(0);
}

static int tty_reset(int fd)         /* of terminal device */
{
    int tres;
    DEBUGF(("tty_reset: Resetting tty...\n"));

    if ((tres = tcsetattr(fd, TCSANOW, &tty_rmode)) < 0) {
        DEBUGLOG(("tty_reset: failure in tcsetattr(%d,..) = %d\n", fd, tres));
	return(-1);
    }
    return(0);
}

/* 
 * Signal handler to cope with signals so that we can reset the tty
 * to the orignal settings
 */

#ifdef ERTS_NOT_USED
/* XXX: A mistake that it isn't used, or should it be removed? */

static RETSIGTYPE suspend(int sig)
{
    if (tty_reset(ttysl_fd) < 0) {
        DEBUGLOG(("signal: failure in suspend(%d), can't reset tty %d\n", sig, ttysl_fd));
	fprintf(stderr,"Can't reset tty \n");
	exit(1);
    }

    sys_signal(sig, SIG_DFL);	/* Set signal handler to default */
    sys_sigrelease(sig);	/* Allow 'sig' to come through */
    kill(getpid(), sig);	/* Send ourselves the signal */
    sys_sigblock(sig);		/* Reset to old mask */
    sys_signal(sig, suspend);	/* Reset signal handler */ 

    if (tty_set(ttysl_fd) < 0) {
        DEBUGLOG(("signal: failure in suspend(%d), can't set tty %d\n", sig, ttysl_fd));
	fprintf(stderr,"Can't set tty raw \n");
	exit(1);
    }
}

#endif

static RETSIGTYPE cont(int sig)
{
    if (tty_set(ttysl_fd) < 0) {
        DEBUGLOG(("signal: failure in cont(%d), can't set tty raw %d\n", sig, ttysl_fd));
	fprintf(stderr,"Can't set tty raw\n");
	exit(1);
    }
}

static RETSIGTYPE winch(int sig)
{
    cols_needs_update = TRUE;
}
#endif /* HAVE_TERMCAP */
