root/usr/src/cmd/mdb/common/mdb/mdb_termio.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2012 by Delphix. All rights reserved.
 * Copyright 2017 Joyent, Inc.
 */

/*
 * Terminal I/O Backend
 *
 * Terminal editing backend for standard input.  The terminal i/o backend is
 * actually built on top of two other i/o backends: one for raw input and
 * another for raw output (presumably stdin and stdout).  When IOP_READ is
 * invoked, the terminal backend enters a read-loop in which it can perform
 * command-line editing and access a history buffer.  Once a newline is read,
 * the entire buffered command-line is returned to the caller.  The termio
 * code makes use of a command buffer (see mdb_cmdbuf.c) to maintain and
 * manipulate the state of a command line, and store it for re-use in a
 * history list.  The termio code manipulates the terminal to keep it in
 * sync with the contents of the command buffer, and moves the cursor in
 * response to editing commands.
 *
 * The terminal backend is also responsible for maintaining and manipulating
 * the settings (see stty(1) and termio(4I)) associated with the terminal.
 * The debugger makes use of four distinct sets of terminal attributes:
 *
 * (1) the settings used by the debugger's parent process (tio_ptios),
 * (2) the settings used by a controlled child process (tio_ctios),
 * (3) the settings used for reading and command-line editing (tio_rtios), and
 * (4) the settings used when mdb dcmds are executing (tio_dtios).
 *
 * The parent settings (1) are read from the terminal during initialization.
 * These settings are restored before the debugger exits or when it is stopped
 * by SIGTSTP.  The child settings (2) are initially a copy of (1), but are
 * then restored prior to continuing execution of a victim process.  The new
 * settings (3) and (4) are both derived from (1).  The raw settings (3) used
 * for reading from the terminal allow the terminal code to respond instantly
 * to keypresses and perform all the necessary handling.  The dcmd settings (4)
 * are essentially the same as (1), except that we make sure ISIG is enabled
 * so that we will receive asynchronous SIGINT notification from the terminal
 * driver if the user types the interrupt character (typically ^C).
 */

#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>

#include <mdb/mdb_types.h>
#include <mdb/mdb_cmdbuf.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_signal.h>
#include <mdb/mdb_callb.h>
#include <mdb/mdb_stdlib.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb_tab.h>
#include <mdb/mdb.h>

#ifdef ERR
#undef ERR
#endif

#include <curses.h>

#define KEY_ESC (0x01b)                 /* Escape key code */
#define KEY_DEL (0x07f)                 /* ASCII DEL key code */

/*
 * These macros support the use of various ranges within the "tio_keymap"
 * member of "termio_data_t" objects.  This array maps from an input byte, or
 * special control code, to the appropriate terminal handling callback.  The
 * array has KEY_MAX (0x1ff) entries, partitioned as follows:
 *
 *     0 -  7f          7-bit ASCII byte
 *    80 -  ff  META()  ASCII byte with Meta key modifier
 *   100 - 119  KPAD()  Alphabetic character received as part of a single-byte
 *                      cursor control sequence, e.g. ESC [ A
 *   11a - 123  FKEY()  Numeric character received as part of a function key
 *                      control sequence, e.g. ESC [ 4 ~
 *   124 - 1ff          Unused
 */
#define META(c)         (((c) & 0x7f) | 0x80)
#define KPAD(c)         (((c) < 'A' || (c) > 'Z') ? 0 : ((c) - 'A' + 0x100))
#define FKEY(c)         (((c) < '0' || (c) > '9') ? 0 : ((c) - '0' + 0x11a))

/*
 * These macros allow for composition of control sequences for xterm and other
 * terminals that support certain features of the VT102 and later VT terminals.
 * Refer to the classic monograph "Xterm Control Sequences" for more info.
 */
#define TI_DECSET(Pm) "\033[?" Pm "h"   /* Compose DEC private mode set */
#define TI_DECRST(Pm) "\033[?" Pm "l"   /* Compose DEC private mode reset */
#define TI_DECSAV(Pm) "\033[?" Pm "s"   /* Compose DEC private mode save */
#define TI_DECRES(Pm) "\033[?" Pm "r"   /* Compose DEC private mode restore */

#define TI_DECCOLM              "3"     /* Ps = DEC 80/132 column mode */
#define TI_COLENAB              "40"    /* Ps = 80/132 column switch enable */

#define TIO_DEFAULT_ROWS        24      /* Default number of rows */
#define TIO_DEFAULT_COLS        80      /* Default number of columns */

typedef union termio_attr_val {
        const char *at_str;             /* String value */
        int at_val;                     /* Integer or boolean value */
} termio_attr_val_t;

typedef struct termio_info {
        termio_attr_val_t ti_cub1;      /* Move back one space */
        termio_attr_val_t ti_cuf1;      /* Move forward one space */
        termio_attr_val_t ti_cuu1;      /* Move up one line */
        termio_attr_val_t ti_cud1;      /* Move down one line */
        termio_attr_val_t ti_pad;       /* Pad character */
        termio_attr_val_t ti_el;        /* Clear to end-of-line */
        termio_attr_val_t ti_am;        /* Automatic right margin? */
        termio_attr_val_t ti_bw;        /* Backward motion at left edge? */
        termio_attr_val_t ti_npc;       /* No padding character? */
        termio_attr_val_t ti_xenl;      /* Newline ignored at autowrap? */
        termio_attr_val_t ti_xon;       /* Use xon/xoff handshaking? */
        termio_attr_val_t ti_cols;      /* # of columns */
        termio_attr_val_t ti_lines;     /* # of rows */
        termio_attr_val_t ti_pb;        /* Lowest baud rate that requires pad */
        termio_attr_val_t ti_smso;      /* Set standout mode */
        termio_attr_val_t ti_rmso;      /* Remove standout mode */
        termio_attr_val_t ti_smul;      /* Set underline mode */
        termio_attr_val_t ti_rmul;      /* Remove underline mode */
        termio_attr_val_t ti_enacs;     /* Enable alternate character set */
        termio_attr_val_t ti_smacs;     /* Set alternate character set */
        termio_attr_val_t ti_rmacs;     /* Remove alternate character set */
        termio_attr_val_t ti_smcup;     /* Set mode where cup is active */
        termio_attr_val_t ti_rmcup;     /* Remove mode where cup is active */
        termio_attr_val_t ti_rev;       /* Set reverse video mode */
        termio_attr_val_t ti_bold;      /* Set bold text mode */
        termio_attr_val_t ti_dim;       /* Set dim text mode */
        termio_attr_val_t ti_sgr0;      /* Remove all video attributes */
        termio_attr_val_t ti_smir;      /* Set insert mode */
        termio_attr_val_t ti_rmir;      /* Remove insert mode */
        termio_attr_val_t ti_ich1;      /* Insert character */
        termio_attr_val_t ti_ip;        /* Insert pad delay in msecs */
        termio_attr_val_t ti_clear;     /* Clear screen and home cursor */
        termio_attr_val_t ti_cnorm;     /* Make cursor appear normal */
        termio_attr_val_t ti_nel;       /* Newline */
        termio_attr_val_t ti_cr;        /* Carriage return */
        termio_attr_val_t ti_smam;      /* Turn on automatic margins */
} termio_info_t;

typedef enum {
        TIO_ATTR_REQSTR,                /* String attribute that is required */
        TIO_ATTR_STR,                   /* String attribute */
        TIO_ATTR_BOOL,                  /* Boolean attribute */
        TIO_ATTR_INT                    /* Integer attribute */
} termio_attr_type_t;

typedef struct termio_attr {
        const char *ta_name;            /* Capability name */
        termio_attr_type_t ta_type;     /* Capability type */
        termio_attr_val_t *ta_valp;     /* String pointer location */
} termio_attr_t;

struct termio_data;
typedef const char *(*keycb_t)(struct termio_data *, int);
typedef void (*putp_t)(struct termio_data *, const char *, uint_t);

#define TIO_FINDHIST    0x01            /* Find-history-mode */
#define TIO_AUTOWRAP    0x02            /* Terminal has autowrap */
#define TIO_BACKLEFT    0x04            /* Terminal can go back at left edge */
#define TIO_INSERT      0x08            /* Terminal has insert mode */
#define TIO_USECUP      0x10            /* Use smcup/rmcup sequences */
#define TIO_TTYWARN     0x20            /* Warnings about tty issued */
#define TIO_CAPWARN     0x40            /* Warnings about terminfo issued */
#define TIO_XTERM       0x80            /* Terminal is xterm compatible */
#define TIO_TAB         0x100           /* Tab completion mode */
#define TIO_LAZYWRAP    0x200           /* Lazy cursor on autowrap */

static const mdb_bitmask_t tio_flag_masks[] = {
        { "FINDHIST", TIO_FINDHIST, TIO_FINDHIST },
        { "AUTOWRAP", TIO_AUTOWRAP, TIO_AUTOWRAP },
        { "BACKLEFT", TIO_BACKLEFT, TIO_BACKLEFT },
        { "INSERT", TIO_INSERT, TIO_INSERT },
        { "USECUP", TIO_USECUP, TIO_USECUP },
        { "TTYWARN", TIO_TTYWARN, TIO_TTYWARN },
        { "CAPWARN", TIO_CAPWARN, TIO_CAPWARN },
        { "XTERM", TIO_XTERM, TIO_XTERM },
        { "TAB", TIO_TAB, TIO_TAB },
        { "LAZYWRAP", TIO_LAZYWRAP, TIO_LAZYWRAP },
        { NULL, 0, 0 }
};

typedef struct termio_data {
        mdb_io_t *tio_io;               /* Pointer back to containing i/o */
        mdb_io_t *tio_out_io;           /* Terminal output backend */
        mdb_io_t *tio_in_io;            /* Terminal input backend */
        mdb_iob_t *tio_out;             /* I/o buffer for terminal output */
        mdb_iob_t *tio_in;              /* I/o buffer for terminal input */
        mdb_iob_t *tio_link;            /* I/o buffer to resize on WINCH */
        keycb_t tio_keymap[KEY_MAX];    /* Keymap (see comments atop file) */
        mdb_cmdbuf_t tio_cmdbuf;        /* Editable command-line buffer */
        struct termios tio_ptios;       /* Parent terminal settings */
        struct termios tio_ctios;       /* Child terminal settings */
        struct termios tio_rtios;       /* Settings for read loop */
        struct termios tio_dtios;       /* Settings for dcmd execution */
        sigjmp_buf tio_env;             /* Read loop setjmp(3c) environment */
        termio_info_t tio_info;         /* Terminal attribute strings */
        char *tio_attrs;                /* Attribute string buffer */
        size_t tio_attrslen;            /* Length in bytes of tio_attrs */
        const char *tio_prompt;         /* Prompt string for this read */
        size_t tio_promptlen;           /* Length of prompt string */
        size_t tio_rows;                /* Terminal height */
        size_t tio_cols;                /* Terminal width */
        size_t tio_x;                   /* Cursor x coordinate */
        size_t tio_y;                   /* Cursor y coordinate */
        size_t tio_max_x;               /* Previous maximum x coordinate */
        size_t tio_max_y;               /* Previous maximum y coordinate */
        int tio_intr;                   /* Interrupt char */
        int tio_quit;                   /* Quit char */
        int tio_erase;                  /* Erase char */
        int tio_werase;                 /* Word-erase char */
        int tio_kill;                   /* Kill char */
        int tio_eof;                    /* End-of-file char */
        int tio_susp;                   /* Suspend char */
        uint_t tio_flags;               /* Miscellaneous flags */
        volatile mdb_bool_t tio_active; /* Flag denoting read loop active */
        volatile mdb_bool_t tio_rti_on; /* Flag denoting rtios in use */
        putp_t tio_putp;                /* termio_tput() subroutine */
        uint_t tio_baud;                /* Baud rate (chars per second) */
        uint_t tio_usecpc;              /* Usecs per char at given baud rate */
        pid_t tio_opgid;                /* Old process group id for terminal */
        uint_t tio_suspended;           /* termio_suspend_tty() nesting count */
} termio_data_t;

static ssize_t termio_read(mdb_io_t *, void *, size_t);
static ssize_t termio_write(mdb_io_t *, const void *, size_t);
static off64_t termio_seek(mdb_io_t *, off64_t, int);
static int termio_ctl(mdb_io_t *, int, void *);
static void termio_close(mdb_io_t *);
static const char *termio_name(mdb_io_t *);
static void termio_link(mdb_io_t *, mdb_iob_t *);
static void termio_unlink(mdb_io_t *, mdb_iob_t *);
static int termio_setattr(mdb_io_t *, int, uint_t);
static void termio_suspend(mdb_io_t *);
static void termio_resume(mdb_io_t *);

static void termio_suspend_tty(termio_data_t *, struct termios *);
static void termio_resume_tty(termio_data_t *, struct termios *);

static void termio_putp(termio_data_t *, const char *, uint_t);
static void termio_puts(termio_data_t *, const char *, uint_t);
static void termio_tput(termio_data_t *, const char *, uint_t);
static void termio_addch(termio_data_t *, char, size_t);
static void termio_insch(termio_data_t *, char, size_t);
static void termio_mvcur(termio_data_t *);
static void termio_bspch(termio_data_t *);
static void termio_delch(termio_data_t *);
static void termio_clear(termio_data_t *);
static void termio_redraw(termio_data_t *);
static void termio_prompt(termio_data_t *);

static const char *termio_tab(termio_data_t *, int);
static const char *termio_insert(termio_data_t *, int);
static const char *termio_accept(termio_data_t *, int);
static const char *termio_backspace(termio_data_t *, int);
static const char *termio_delchar(termio_data_t *, int);
static const char *termio_fwdchar(termio_data_t *, int);
static const char *termio_backchar(termio_data_t *, int);
static const char *termio_transpose(termio_data_t *, int);
static const char *termio_home(termio_data_t *, int);
static const char *termio_end(termio_data_t *, int);
static const char *termio_fwdword(termio_data_t *, int);
static const char *termio_backword(termio_data_t *, int);
static const char *termio_kill(termio_data_t *, int);
static const char *termio_killfwdword(termio_data_t *, int);
static const char *termio_killbackword(termio_data_t *, int);
static const char *termio_reset(termio_data_t *, int);
static const char *termio_widescreen(termio_data_t *, int);
static const char *termio_prevhist(termio_data_t *, int);
static const char *termio_nexthist(termio_data_t *, int);
static const char *termio_accel(termio_data_t *, int);
static const char *termio_findhist(termio_data_t *, int);
static const char *termio_refresh(termio_data_t *, int);

static const char *termio_intr(termio_data_t *, int);
static const char *termio_quit(termio_data_t *, int);
static const char *termio_susp(termio_data_t *, int);

static void termio_winch(int, siginfo_t *, ucontext_t *, void *);
static void termio_tstp(int, siginfo_t *, ucontext_t *, void *);

extern const char *tigetstr(const char *);
extern int tigetflag(const char *);
extern int tigetnum(const char *);

static const mdb_io_ops_t termio_ops = {
        .io_read = termio_read,
        .io_write = termio_write,
        .io_seek = termio_seek,
        .io_ctl = termio_ctl,
        .io_close = termio_close,
        .io_name = termio_name,
        .io_link = termio_link,
        .io_unlink = termio_unlink,
        .io_setattr = termio_setattr,
        .io_suspend = termio_suspend,
        .io_resume = termio_resume,
};

static termio_info_t termio_info;

static const termio_attr_t termio_attrs[] = {
        { "cub1", TIO_ATTR_REQSTR, &termio_info.ti_cub1 },
        { "cuf1", TIO_ATTR_REQSTR, &termio_info.ti_cuf1 },
        { "cuu1", TIO_ATTR_REQSTR, &termio_info.ti_cuu1 },
        { "cud1", TIO_ATTR_REQSTR, &termio_info.ti_cud1 },
        { "pad", TIO_ATTR_STR, &termio_info.ti_pad },
        { "el", TIO_ATTR_REQSTR, &termio_info.ti_el },
        { "am", TIO_ATTR_BOOL, &termio_info.ti_am },
        { "bw", TIO_ATTR_BOOL, &termio_info.ti_bw },
        { "npc", TIO_ATTR_BOOL, &termio_info.ti_npc },
        { "xenl", TIO_ATTR_BOOL, &termio_info.ti_xenl },
        { "xon", TIO_ATTR_BOOL, &termio_info.ti_xon },
        { "cols", TIO_ATTR_INT, &termio_info.ti_cols },
        { "lines", TIO_ATTR_INT, &termio_info.ti_lines },
        { "pb", TIO_ATTR_INT, &termio_info.ti_pb },
        { "smso", TIO_ATTR_STR, &termio_info.ti_smso },
        { "rmso", TIO_ATTR_STR, &termio_info.ti_rmso },
        { "smul", TIO_ATTR_STR, &termio_info.ti_smul },
        { "rmul", TIO_ATTR_STR, &termio_info.ti_rmul },
        { "enacs", TIO_ATTR_STR, &termio_info.ti_enacs },
        { "smacs", TIO_ATTR_STR, &termio_info.ti_smacs },
        { "rmacs", TIO_ATTR_STR, &termio_info.ti_rmacs },
        { "smcup", TIO_ATTR_STR, &termio_info.ti_smcup },
        { "rmcup", TIO_ATTR_STR, &termio_info.ti_rmcup },
        { "rev", TIO_ATTR_STR, &termio_info.ti_rev },
        { "bold", TIO_ATTR_STR, &termio_info.ti_bold },
        { "dim", TIO_ATTR_STR, &termio_info.ti_dim },
        { "sgr0", TIO_ATTR_STR, &termio_info.ti_sgr0 },
        { "smir", TIO_ATTR_STR, &termio_info.ti_smir },
        { "rmir", TIO_ATTR_STR, &termio_info.ti_rmir },
        { "ich1", TIO_ATTR_STR, &termio_info.ti_ich1 },
        { "ip", TIO_ATTR_STR, &termio_info.ti_ip },
        { "clear", TIO_ATTR_STR, &termio_info.ti_clear },
        { "cnorm", TIO_ATTR_STR, &termio_info.ti_cnorm },
        { "nel", TIO_ATTR_STR, &termio_info.ti_nel },
        { "cr", TIO_ATTR_STR, &termio_info.ti_cr },
        { "smam", TIO_ATTR_STR, &termio_info.ti_smam },
        { NULL, 0, NULL }
};

/*
 * One-key accelerators.  Some commands are used so frequently as to need
 * single-key equivalents.  termio_accelkeys contains a list of the accelerator
 * keys, with termio_accel listing the accelerated commands.  The array is
 * indexed by the offset of the accelerator in the macro string, and as such
 * *must* stay in the same order.
 */
static const char *const termio_accelkeys = "[]";

static const char *const termio_accelstrings[] = {
        "::step over",  /* [ */
        "::step"        /* ] */
};

static const char *
termio_accel_lookup(int c)
{
        const char *acc;

        if ((acc = strchr(termio_accelkeys, c)) == NULL)
                return (NULL);

        return (termio_accelstrings[(int)(acc - termio_accelkeys)]);
}

static ssize_t
termio_read(mdb_io_t *io, void *buf, size_t nbytes)
{
        termio_data_t *td = io->io_data;

        mdb_bool_t esc = FALSE, pad = FALSE;
        ssize_t rlen = 0;
        int c, fkey = 0;

        const char *s;
        size_t len;

        if (io->io_next != NULL)
                return (IOP_READ(io->io_next, buf, nbytes));

        td->tio_rti_on = TRUE;
        if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1)
                warn("failed to set terminal attributes");

        if (nbytes == 1) {
                if ((c = mdb_iob_getc(td->tio_in)) == EOF)
                        goto out;

                *((uchar_t *)buf) = (uchar_t)c;

                rlen = 1;
                goto out;
        }

        if (td->tio_flags & TIO_TAB)
                termio_redraw(td);
        else
                termio_prompt(td);

        /*
         * We need to redraw the entire command-line and restart our read loop
         * in the event of a SIGWINCH or resume following SIGTSTP (SIGCONT).
         */
        if (sigsetjmp(td->tio_env, 1) != 0) {
                td->tio_active = FALSE;
                td->tio_x = td->tio_y = 0;

                len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen;
                td->tio_max_x = len % td->tio_cols;
                td->tio_max_y = len / td->tio_cols;

                esc = pad = FALSE;

                termio_tput(td, td->tio_info.ti_cr.at_str, 1);
                mdb_iob_flush(td->tio_out);
                termio_redraw(td);
        }

        /*
         * Since we're about to start the read loop, we know our linked iob
         * is quiescent. We can now safely resize it to the latest term size.
         */
        if (td->tio_link != NULL)
                mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols);

        td->tio_active = TRUE;

        /*
         * We may have had some error while in tab completion mode which sent us
         * longjmping all over the place. If that's the case, come back here and
         * make sure the flag is off.
         */
        td->tio_flags &= ~TIO_TAB;

        do {
char_loop:
                if ((c = mdb_iob_getc(td->tio_in)) == EOF) {
                        td->tio_active = FALSE;
                        goto out;
                }

                if (c == KEY_ESC && esc == FALSE) {
                        esc = TRUE;
                        goto char_loop;
                }

                if (esc) {
                        esc = FALSE;

                        if (c == '[') {
                                pad++;
                                goto char_loop;
                        }

                        c = META(c);
                }

                if (pad) {
                        pad = FALSE;

                        if ((fkey = FKEY(c)) != 0) {
                                /*
                                 * Some terminals send a multibyte control
                                 * sequence for particular function keys.
                                 * These sequences are of the form:
                                 *
                                 *      ESC [ n ~
                                 *
                                 * where "n" is a numeric character from
                                 * '0' to '9'.
                                 */
                                goto char_loop;
                        }

                        if ((c = KPAD(c)) == 0) {
                                /*
                                 * This was not a valid keypad control
                                 * sequence.
                                 */
                                goto char_loop;
                        }
                }

                if (fkey != 0) {
                        if (c == '~') {
                                /*
                                 * This is a valid special function key
                                 * sequence.  Use the value we stashed
                                 * earlier.
                                 */
                                c = fkey;
                        }

                        fkey = 0;
                }

                len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen;

                td->tio_max_x = len % td->tio_cols;
                td->tio_max_y = len / td->tio_cols;

        } while ((s = (*td->tio_keymap[c])(td, c)) == NULL);

        td->tio_active = FALSE;
        mdb_iob_nl(td->tio_out);

        if ((rlen = strlen(s)) >= nbytes - 1)
                rlen = nbytes - 1;

        (void) strncpy(buf, s, rlen);
        ((char *)buf)[rlen++] = '\n';

out:
        td->tio_rti_on = FALSE;
        if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
                warn("failed to restore terminal attributes");

        return (rlen);
}

static ssize_t
termio_write(mdb_io_t *io, const void *buf, size_t nbytes)
{
        termio_data_t *td = io->io_data;

        if (io->io_next != NULL)
                return (IOP_WRITE(io->io_next, buf, nbytes));

        return (IOP_WRITE(td->tio_out_io, buf, nbytes));
}

/*ARGSUSED*/
static off64_t
termio_seek(mdb_io_t *io, off64_t offset, int whence)
{
        return (set_errno(ENOTSUP));
}

static int
termio_ctl(mdb_io_t *io, int req, void *arg)
{
        termio_data_t *td = io->io_data;

        if (io->io_next != NULL)
                return (IOP_CTL(io->io_next, req, arg));

        if (req == MDB_IOC_CTTY) {
                bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios));
                return (0);
        }

        return (IOP_CTL(td->tio_in_io, req, arg));
}

static void
termio_close(mdb_io_t *io)
{
        termio_data_t *td = io->io_data;

        (void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_DFL, NULL);
        (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL);

        termio_suspend_tty(td, &td->tio_ptios);

        if (td->tio_attrs)
                mdb_free(td->tio_attrs, td->tio_attrslen);

        mdb_cmdbuf_destroy(&td->tio_cmdbuf);

        mdb_iob_destroy(td->tio_out);
        mdb_iob_destroy(td->tio_in);

        mdb_free(td, sizeof (termio_data_t));
}

static const char *
termio_name(mdb_io_t *io)
{
        termio_data_t *td = io->io_data;

        if (io->io_next != NULL)
                return (IOP_NAME(io->io_next));

        return (IOP_NAME(td->tio_in_io));
}

static void
termio_link(mdb_io_t *io, mdb_iob_t *iob)
{
        termio_data_t *td = io->io_data;

        if (io->io_next == NULL) {
                mdb_iob_resize(iob, td->tio_rows, td->tio_cols);
                td->tio_link = iob;
        } else
                IOP_LINK(io->io_next, iob);
}

static void
termio_unlink(mdb_io_t *io, mdb_iob_t *iob)
{
        termio_data_t *td = io->io_data;

        if (io->io_next == NULL) {
                if (td->tio_link == iob)
                        td->tio_link = NULL;
        } else
                IOP_UNLINK(io->io_next, iob);
}

static int
termio_setattr(mdb_io_t *io, int req, uint_t attrs)
{
        termio_data_t *td = io->io_data;

        if (io->io_next != NULL)
                return (IOP_SETATTR(io->io_next, req, attrs));

        if ((req != ATT_ON && req != ATT_OFF) || (attrs & ~ATT_ALL) != 0)
                return (set_errno(EINVAL));

        if (req == ATT_ON) {
                if (attrs & ATT_STANDOUT)
                        termio_tput(td, td->tio_info.ti_smso.at_str, 1);
                if (attrs & ATT_UNDERLINE)
                        termio_tput(td, td->tio_info.ti_smul.at_str, 1);
                if (attrs & ATT_REVERSE)
                        termio_tput(td, td->tio_info.ti_rev.at_str, 1);
                if (attrs & ATT_BOLD)
                        termio_tput(td, td->tio_info.ti_bold.at_str, 1);
                if (attrs & ATT_DIM)
                        termio_tput(td, td->tio_info.ti_dim.at_str, 1);
                if (attrs & ATT_ALTCHARSET)
                        termio_tput(td, td->tio_info.ti_smacs.at_str, 1);
        } else {
                if (attrs & ATT_STANDOUT)
                        termio_tput(td, td->tio_info.ti_rmso.at_str, 1);
                if (attrs & ATT_UNDERLINE)
                        termio_tput(td, td->tio_info.ti_rmul.at_str, 1);
                if (attrs & ATT_ALTCHARSET)
                        termio_tput(td, td->tio_info.ti_rmacs.at_str, 1);
                if (attrs & (ATT_REVERSE | ATT_BOLD | ATT_DIM))
                        termio_tput(td, td->tio_info.ti_sgr0.at_str, 1);
        }

        mdb_iob_flush(td->tio_out);
        return (0);
}

/*
 * Issue a warning message if the given warning flag is clear.  Then set the
 * flag bit so that we do not issue multiple instances of the same warning.
 */
static void
termio_warn(termio_data_t *td, uint_t flag, const char *format, ...)
{
        if (!(td->tio_flags & flag)) {
                va_list alist;

                va_start(alist, format);
                vwarn(format, alist);
                va_end(alist);

                td->tio_flags |= flag;
        }
}

/*
 * Restore the terminal to its previous state before relinquishing control of
 * it to the shell (on a SIGTSTP) or the victim process (on a continue).  If
 * we need to change the foreground process group, we must temporarily ignore
 * SIGTTOU because TIOCSPGRP could trigger it.
 */
static void
termio_suspend_tty(termio_data_t *td, struct termios *iosp)
{
        if (td->tio_suspended++ != 0)
                return; /* already suspended; do not restore state */

        if (td->tio_flags & TIO_XTERM)
                termio_tput(td, TI_DECRES(TI_COLENAB), 1);

        if (td->tio_flags & TIO_USECUP)
                termio_tput(td, td->tio_info.ti_rmcup.at_str, 1);

        termio_tput(td, td->tio_info.ti_sgr0.at_str, 1);
        mdb_iob_flush(td->tio_out);

        if (termio_ctl(td->tio_io, TCSETSW, iosp) == -1)
                warn("failed to restore terminal attributes");

        if (td->tio_opgid > 0 && td->tio_opgid != mdb.m_pgid) {
                mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)td->tio_opgid);
                (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL);
                (void) termio_ctl(td->tio_io, TIOCSPGRP, &td->tio_opgid);
                (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL);
        }
}

/*
 * Resume the debugger's terminal state.  We first save the existing terminal
 * state so we can restore it later, and then install our own state.  We
 * derive our state dynamically from the existing terminal state so that we
 * always reflect the latest modifications made by the user with stty(1).
 */
static void
termio_resume_tty(termio_data_t *td, struct termios *iosp)
{
        /*
         * We use this table of bauds to convert the baud constant returned by
         * the terminal code to a baud rate in characters per second.  The
         * values are in the order of the B* speed defines in <sys/termios.h>.
         * We then compute tio_usecpc (microseconds-per-char) in order to
         * determine how many pad characters need to be issued at the current
         * terminal speed to delay for a given number of microseconds.  For
         * example, at 300 baud (B300 = 7), we look up baud[7] = 300, and then
         * compute usecpc as MICROSEC / 300 = 3333 microseconds per character.
         */
        static const uint_t baud[] = {
                0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
                1800, 2400, 4800, 9600, 19200, 38400, 57600,
                76800, 115200, 153600, 230400, 307200, 460800, 921600,
                1000000, 1152000, 1500000, 2000000, 2500000, 3000000,
                3500000, 4000000
        };

        struct termios *ntios;
        struct winsize winsz;
        uint_t speed;

        if (td->tio_suspended == 0)
                fail("termio_resume called without matching termio_suspend\n");

        if (--td->tio_suspended != 0)
                return; /* nested suspends; do not resume yet */

        td->tio_opgid = -1; /* set to invalid pgid in case TIOCPGRP fails */
        (void) termio_ctl(td->tio_io, TIOCGPGRP, &td->tio_opgid);

        /*
         * If the foreground process group does not include the debugger, reset
         * the foreground process group so we are in control of the terminal.
         * We temporarily ignore TTOU because TIOCSPGRP could trigger it.
         */
        if (td->tio_opgid != mdb.m_pgid) {
                (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL);
                (void) termio_ctl(td->tio_io, TIOCSPGRP, &mdb.m_pgid);
                (void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL);
                mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)mdb.m_pgid);
        }

        /*
         * Read the current set of terminal attributes, and save them in iosp
         * so we can restore them later.  Then derive rtios, dtios, and winsz.
         */
        if (termio_ctl(td->tio_io, TCGETS, iosp) < 0)
                warn("failed to get terminal attributes");

        if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == 0) {
                if (winsz.ws_row != 0)
                        td->tio_rows = (size_t)winsz.ws_row;
                if (winsz.ws_col != 0)
                        td->tio_cols = (size_t)winsz.ws_col;
        }

        mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols);

        td->tio_intr = td->tio_ptios.c_cc[VINTR];
        td->tio_quit = td->tio_ptios.c_cc[VQUIT];
        td->tio_erase = td->tio_ptios.c_cc[VERASE];
        td->tio_werase = td->tio_ptios.c_cc[VWERASE];
        td->tio_kill = td->tio_ptios.c_cc[VKILL];
        td->tio_eof = td->tio_ptios.c_cc[VEOF];
        td->tio_susp = td->tio_ptios.c_cc[VSUSP];

        bcopy(&td->tio_ptios, &td->tio_rtios, sizeof (struct termios));
        td->tio_rtios.c_iflag &= ~(ISTRIP | INPCK | ICRNL | INLCR | IUCLC);
        td->tio_rtios.c_oflag &= ~(OCRNL | ONLRET);
        td->tio_rtios.c_oflag |= ONLCR;
        td->tio_rtios.c_lflag &= ~(ISIG | ICANON | ECHO);
        td->tio_rtios.c_cflag |= CS8;
        td->tio_rtios.c_cc[VTIME] = 0;
        td->tio_rtios.c_cc[VMIN] = 1;

        bcopy(&td->tio_ptios, &td->tio_dtios, sizeof (struct termios));
        td->tio_dtios.c_oflag &= ~(OCRNL | ONLRET);
        td->tio_dtios.c_oflag |= ONLCR;
        td->tio_dtios.c_lflag |= ISIG | ICANON | ECHO;

        /*
         * Select the appropriate modified settings to restore based on our
         * current state, and then install them.
         */
        if (td->tio_rti_on)
                ntios = &td->tio_rtios;
        else
                ntios = &td->tio_dtios;

        if (termio_ctl(td->tio_io, TCSETSW, ntios) < 0)
                warn("failed to reset terminal attributes");

        /*
         * Compute the terminal speed as described in termio(4I), and then
         * look up the corresponding microseconds-per-char in our table.
         */
        if (ntios->c_cflag & CBAUDEXT)
                speed = (ntios->c_cflag & CBAUD) + CBAUD + 1;
        else
                speed = (ntios->c_cflag & CBAUD);

        if (speed >= sizeof (baud) / sizeof (baud[0])) {
                termio_warn(td, TIO_TTYWARN, "invalid speed %u -- assuming "
                    "9600 baud\n", speed);
                speed = B9600;
        }

        td->tio_baud = baud[speed];
        td->tio_usecpc = MICROSEC / td->tio_baud;

        mdb_dprintf(MDB_DBG_CMDBUF, "speed = %u baud (%u usec / char), "
            "putp = %s\n", td->tio_baud, td->tio_usecpc,
            td->tio_putp == &termio_puts ? "fast" : "slow");

        /*
         * Send the necessary terminal initialization sequences to enable
         * enable cursor positioning.  Clear the screen afterward if possible.
         */
        if (td->tio_flags & TIO_USECUP) {
                termio_tput(td, td->tio_info.ti_smcup.at_str, 1);
                if (td->tio_info.ti_clear.at_str) {
                        termio_tput(td, td->tio_info.ti_clear.at_str, 1);
                        td->tio_x = td->tio_y = 0;
                }
        }

        /*
         * If the terminal is xterm-compatible, enable column mode switching.
         * Save the previous value in the terminal so we can restore it.
         */
        if (td->tio_flags & TIO_XTERM) {
                termio_tput(td, TI_DECSAV(TI_COLENAB), 1);
                termio_tput(td, TI_DECSET(TI_COLENAB), 1);
        }

        termio_tput(td, td->tio_info.ti_cnorm.at_str, 1); /* cursor visible */
        termio_tput(td, td->tio_info.ti_enacs.at_str, 1); /* alt char set */

        /*
         * If the terminal is automargin-capable and we have an initialization
         * sequence to enable automargins, we must send it now.  Note that
         * we don't have a way of querying this mode and restoring it; if
         * we are fighting with (say) a target that is depending on automargin
         * being turned off, it will lose.
         */
        if ((td->tio_flags & TIO_AUTOWRAP) &&
            td->tio_info.ti_smam.at_str != NULL) {
                termio_tput(td, td->tio_info.ti_smam.at_str, 1);
        }

        mdb_iob_flush(td->tio_out);
}

static void
termio_suspend(mdb_io_t *io)
{
        termio_data_t *td = io->io_data;
        termio_suspend_tty(td, &td->tio_ctios);
}

static void
termio_resume(mdb_io_t *io)
{
        termio_data_t *td = io->io_data;
        termio_resume_tty(td, &td->tio_ctios);
}

/*
 * Delay for the specified number of microseconds by sending the pad character
 * to the terminal.  We round up by half a frame and then divide by the usecs
 * per character to determine the number of pad characters to send.
 */
static void
termio_delay(termio_data_t *td, uint_t usec)
{
        char pad = td->tio_info.ti_pad.at_str[0];
        uint_t usecpc = td->tio_usecpc;

        for (usec = (usec + usecpc / 2) / usecpc; usec != 0; usec--) {
                mdb_iob_putc(td->tio_out, pad);
                mdb_iob_flush(td->tio_out);
        }
}

/*
 * Parse the terminfo(5) padding sequence "$<...>" and delay for the specified
 * amount of time by sending pad characters to the terminal.
 */
static const char *
termio_pad(termio_data_t *td, const char *s, uint_t lines)
{
        int xon = td->tio_info.ti_xon.at_val;
        int pb = td->tio_info.ti_pb.at_val;

        const char *p = s;
        uint_t usec = 0;

        /*
         * The initial string is a number of milliseconds, followed by an
         * optional decimal point and number of tenths of milliseconds.
         * We convert this to microseconds for greater accuracy.  Only a single
         * digit is permitted after the decimal point; we ignore any others.
         */
        while (*p >= '0' && *p <= '9')
                usec = usec * 10 + *p++ - '0';

        usec *= 1000; /* convert msecs to usecs */

        if (*p == '.') {
                if (p[1] >= '0' && p[1] <= '9')
                        usec += (p[1] - '0') * 100;
                for (p++; *p >= '0' && *p <= '9'; p++)
                        continue;
        }

        /*
         * Following the time delay specifier,
         *
         * 1. An optional "/" indicates that the delay should be done
         *    regardless of the value of the terminal's xon property,
         * 2. An optional "*" indicates that the delay is proportional to the
         *    count of affected lines, and
         * 3. A mandatory ">" terminates the sequence.
         *
         * If we encounter any other characters, we assume that we found "$<"
         * accidentally embedded in another sequence, so we just output "$".
         */
        for (;;) {
                switch (*p++) {
                case '/':
                        xon = FALSE;
                        continue;
                case '*':
                        usec *= lines;
                        continue;
                case '>':
                        if (xon == FALSE && usec != 0 && td->tio_baud >= pb)
                                termio_delay(td, usec);
                        return (p);
                default:
                        mdb_iob_putc(td->tio_out, *s);
                        return (s + 1);
                }
        }
}

/*
 * termio_tput() subroutine for terminals that require padding.  We look ahead
 * for "$<>" sequences, and call termio_pad() to process them; all other chars
 * are output directly to the underlying device and then flushed at the end.
 */
static void
termio_putp(termio_data_t *td, const char *s, uint_t lines)
{
        while (s[0] != '\0') {
                if (s[0] == '$' && s[1] == '<')
                        s = termio_pad(td, s + 2, lines);
                else
                        mdb_iob_putc(td->tio_out, *s++);
        }

        mdb_iob_flush(td->tio_out);
}

/*
 * termio_tput() subroutine for terminals that do not require padding.  We
 * simply output the string to the underlying i/o buffer; we let the caller
 * take care of flushing so that multiple sequences can be concatenated.
 */
/*ARGSUSED*/
static void
termio_puts(termio_data_t *td, const char *s, uint_t lines)
{
        mdb_iob_puts(td->tio_out, s);
}

/*
 * Print a padded escape sequence string to the terminal.  The caller specifies
 * the string 's' and a count of the affected lines.  If the string contains an
 * embedded delay sequence delimited by "$<>" (see terminfo(5)), appropriate
 * padding will be included in the output.  We determine whether or not padding
 * is required during initialization, and set tio_putp to the proper subroutine.
 */
static void
termio_tput(termio_data_t *td, const char *s, uint_t lines)
{
        if (s != NULL)
                td->tio_putp(td, s, lines);
}

static void
termio_addch(termio_data_t *td, char c, size_t width)
{
        if (width == 1) {
                mdb_iob_putc(td->tio_out, c);
                td->tio_x++;

                if (td->tio_x >= td->tio_cols) {
                        if (!(td->tio_flags & TIO_AUTOWRAP))
                                termio_tput(td, td->tio_info.ti_nel.at_str, 1);
                        td->tio_x = 0;
                        td->tio_y++;
                }

                mdb_iob_flush(td->tio_out);
        } else
                termio_redraw(td);
}

static void
termio_insch(termio_data_t *td, char c, size_t width)
{
        if (width == 1 && (td->tio_flags & TIO_INSERT) &&
            td->tio_y == td->tio_max_y) {

                termio_tput(td, td->tio_info.ti_smir.at_str, 1);
                termio_tput(td, td->tio_info.ti_ich1.at_str, 1);

                mdb_iob_putc(td->tio_out, c);
                td->tio_x++;

                termio_tput(td, td->tio_info.ti_ip.at_str, 1);
                termio_tput(td, td->tio_info.ti_rmir.at_str, 1);

                if (td->tio_x >= td->tio_cols) {
                        if (!(td->tio_flags & TIO_AUTOWRAP))
                                termio_tput(td, td->tio_info.ti_nel.at_str, 1);
                        td->tio_x = 0;
                        td->tio_y++;
                }

                mdb_iob_flush(td->tio_out);
        } else
                termio_redraw(td);
}

static void
termio_mvcur(termio_data_t *td)
{
        size_t tipos = td->tio_cmdbuf.cmd_bufidx + td->tio_promptlen;
        size_t dst_x = tipos % td->tio_cols;
        size_t dst_y = tipos / td->tio_cols;

        const char *str;
        size_t cnt, i;

        if (td->tio_y != dst_y) {
                if (td->tio_y < dst_y) {
                        str = td->tio_info.ti_cud1.at_str;
                        cnt = dst_y - td->tio_y;
                        td->tio_x = 0; /* Note: cud1 moves cursor to column 0 */
                } else {
                        str = td->tio_info.ti_cuu1.at_str;
                        cnt = td->tio_y - dst_y;
                }

                for (i = 0; i < cnt; i++)
                        termio_tput(td, str, 1);

                mdb_iob_flush(td->tio_out);
                td->tio_y = dst_y;
        }

        if (td->tio_x != dst_x) {
                if (td->tio_x < dst_x) {
                        str = td->tio_info.ti_cuf1.at_str;
                        cnt = dst_x - td->tio_x;
                } else {
                        str = td->tio_info.ti_cub1.at_str;
                        cnt = td->tio_x - dst_x;
                }

                for (i = 0; i < cnt; i++)
                        termio_tput(td, str, 1);

                mdb_iob_flush(td->tio_out);
                td->tio_x = dst_x;
        }
}

static void
termio_backleft(termio_data_t *td)
{
        size_t i;

        if (td->tio_flags & TIO_BACKLEFT)
                termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
        else {
                termio_tput(td, td->tio_info.ti_cuu1.at_str, 1);
                for (i = 0; i < td->tio_cols - 1; i++)
                        termio_tput(td, td->tio_info.ti_cuf1.at_str, 1);
        }
}

static void
termio_bspch(termio_data_t *td)
{
        if (td->tio_x == 0) {
                /*
                 * If TIO_LAZYWRAP is set, we are regrettably in one of two
                 * states that we cannot differentiate between:  the cursor is
                 * either on the right margin of the previous line (having not
                 * advanced due to the lazy wrap) _or_ the cursor is on the
                 * left margin of the current line because a wrap has already
                 * been induced due to prior activity.  Because these cases are
                 * impossible to differentiate, we force the latter case by
                 * emitting a space and then moving the cursor back.  If the
                 * wrap had not been induced, emitting this space will induce
                 * it -- and will assure that our termio_backleft() is the
                 * correct behavior with respect to the cursor.
                 */
                if (td->tio_flags & TIO_LAZYWRAP) {
                        mdb_iob_putc(td->tio_out, ' ');
                        termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
                }

                termio_backleft(td);
                td->tio_x = td->tio_cols - 1;
                td->tio_y--;
        } else {
                termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
                td->tio_x--;
        }

        termio_delch(td);
}

static void
termio_delch(termio_data_t *td)
{
        mdb_iob_putc(td->tio_out, ' ');

        if (td->tio_x == td->tio_cols - 1 && (td->tio_flags & TIO_AUTOWRAP)) {
                /*
                 * We just overwrote a space in the final column, so we know
                 * that we have autowrapped and we therefore definitely don't
                 * want to issue the ti_cub1.  If we have TIO_LAZYWRAP, we know
                 * that the cursor remains on the final column, and we want to
                 * do nothing -- but if TIO_LAZYWRAP isn't set, we have wrapped
                 * and we need to move the cursor back up and all the way to
                 * the right via termio_backleft().
                 */
                if (!(td->tio_flags & TIO_LAZYWRAP))
                        termio_backleft(td);
        } else {
                termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
        }

        mdb_iob_flush(td->tio_out);
}

static void
termio_clear(termio_data_t *td)
{
        while (td->tio_x-- != 0)
                termio_tput(td, td->tio_info.ti_cub1.at_str, 1);

        while (td->tio_y < td->tio_max_y) {
                termio_tput(td, td->tio_info.ti_cud1.at_str, 1);
                td->tio_y++;
        }

        while (td->tio_y-- != 0) {
                termio_tput(td, td->tio_info.ti_el.at_str, 1);
                termio_tput(td, td->tio_info.ti_cuu1.at_str, 1);
        }

        termio_tput(td, td->tio_info.ti_el.at_str, 1);
        mdb_iob_flush(td->tio_out);

        termio_prompt(td);
}

static void
termio_redraw(termio_data_t *td)
{
        const char *buf = td->tio_cmdbuf.cmd_buf;
        size_t len = td->tio_cmdbuf.cmd_buflen;
        size_t pos, n;

        termio_clear(td);

        if (len == 0)
                return; /* if the buffer is empty, we're done */

        if (td->tio_flags & TIO_AUTOWRAP)
                mdb_iob_nputs(td->tio_out, buf, len);
        else {
                for (pos = td->tio_promptlen; len != 0; pos = 0) {
                        n = MIN(td->tio_cols - pos, len);
                        mdb_iob_nputs(td->tio_out, buf, n);
                        buf += n;
                        len -= n;

                        if (pos + n == td->tio_cols)
                                termio_tput(td, td->tio_info.ti_nel.at_str, 1);
                }
        }

        pos = td->tio_promptlen + td->tio_cmdbuf.cmd_buflen;
        td->tio_x = pos % td->tio_cols;
        td->tio_y = pos / td->tio_cols;

        mdb_iob_flush(td->tio_out);
        termio_mvcur(td);
}

static void
termio_prompt(termio_data_t *td)
{
        mdb_callb_fire(MDB_CALLB_PROMPT);

        /*
         * Findhist (^R) overrides the displayed prompt.  We should only update
         * the main prompt (which may have been changed by the callback) if
         * findhist isn't active.
         */
        if (!(td->tio_flags & TIO_FINDHIST)) {
                td->tio_prompt = mdb.m_prompt;
                td->tio_promptlen = mdb.m_promptlen;
        }

        mdb_iob_puts(td->tio_out, td->tio_prompt);
        mdb_iob_flush(td->tio_out);

        td->tio_x = td->tio_promptlen;
        td->tio_y = 0;
}

/*
 * For debugging purposes, iterate over the table of attributes and output them
 * in human readable form for verification.
 */
static void
termio_dump(termio_data_t *td, const termio_attr_t *ta)
{
        char *str;

        for (; ta->ta_name != NULL; ta++) {
                switch (ta->ta_type) {
                case TIO_ATTR_REQSTR:
                case TIO_ATTR_STR:
                        if (ta->ta_valp->at_str != NULL) {
                                str = strchr2esc(ta->ta_valp->at_str,
                                    strlen(ta->ta_valp->at_str));
                                mdb_dprintf(MDB_DBG_CMDBUF, "%s = \"%s\"\n",
                                    ta->ta_name, str);
                                strfree(str);
                        } else {
                                mdb_dprintf(MDB_DBG_CMDBUF, "%s = <NULL>\n",
                                    ta->ta_name);
                        }
                        break;
                case TIO_ATTR_INT:
                        mdb_dprintf(MDB_DBG_CMDBUF, "%s = %d\n",
                            ta->ta_name, ta->ta_valp->at_val);
                        break;
                case TIO_ATTR_BOOL:
                        mdb_dprintf(MDB_DBG_CMDBUF, "%s = %s\n", ta->ta_name,
                            ta->ta_valp->at_val ? "TRUE" : "FALSE");
                        break;
                }
        }

        mdb_dprintf(MDB_DBG_CMDBUF, "tio_flags = <%#b>\n",
            td->tio_flags, tio_flag_masks);
}

static int
termio_setup_attrs(termio_data_t *td, const char *name)
{
        const termio_attr_t *ta;
        const char *str;
        size_t nbytes;
        char *bufp;

        int need_padding = 0;
        int i;

        /*
         * Load terminal attributes:
         */
        for (nbytes = 0, ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) {
                switch (ta->ta_type) {
                case TIO_ATTR_REQSTR:
                case TIO_ATTR_STR:
                        str = tigetstr(ta->ta_name);

                        if (str == (const char *)-1) {
                                termio_warn(td, TIO_CAPWARN,
                                    "terminal capability '%s' is not of type "
                                    "string as expected\n", ta->ta_name);
                                return (0);
                        }

                        if (str != NULL)
                                nbytes += strlen(str) + 1;
                        else if (ta->ta_type == TIO_ATTR_REQSTR) {
                                termio_warn(td, TIO_CAPWARN,
                                    "terminal capability '%s' is not "
                                    "available\n", ta->ta_name);
                                return (0);
                        }
                        break;

                case TIO_ATTR_BOOL:
                        if (tigetflag(ta->ta_name) == -1) {
                                termio_warn(td, TIO_CAPWARN,
                                    "terminal capability '%s' is not of type "
                                    "boolean as expected\n", ta->ta_name);
                                return (0);
                        }
                        break;

                case TIO_ATTR_INT:
                        if (tigetnum(ta->ta_name) == -2) {
                                termio_warn(td, TIO_CAPWARN,
                                    "terminal capability '%s' is not of type "
                                    "integer as expected\n", ta->ta_name);
                                return (0);
                        }
                        break;
                }
        }

        if (nbytes != 0)
                td->tio_attrs = mdb_alloc(nbytes, UM_SLEEP);
        else
                td->tio_attrs = NULL;

        td->tio_attrslen = nbytes;
        bufp = td->tio_attrs;

        /*
         * Now make another pass through the terminal attributes and load the
         * actual pointers into our static data structure:
         */
        for (ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) {
                switch (ta->ta_type) {
                case TIO_ATTR_REQSTR:
                case TIO_ATTR_STR:
                        if ((str = tigetstr(ta->ta_name)) != NULL) {
                                /*
                                 * Copy the result string into our contiguous
                                 * buffer, and store a pointer to it in at_str.
                                 */
                                (void) strcpy(bufp, str);
                                ta->ta_valp->at_str = bufp;
                                bufp += strlen(str) + 1;
                                /*
                                 * Check the string for a "$<>" pad sequence;
                                 * if none are found, we can optimize later.
                                 */
                                if ((str = strstr(ta->ta_valp->at_str,
                                    "$<")) != NULL && strchr(str, '>') != NULL)
                                        need_padding++;
                        } else {
                                ta->ta_valp->at_str = NULL;
                        }
                        break;

                case TIO_ATTR_BOOL:
                        ta->ta_valp->at_val = tigetflag(ta->ta_name);
                        break;

                case TIO_ATTR_INT:
                        ta->ta_valp->at_val = tigetnum(ta->ta_name);
                        break;
                }
        }

        /*
         * Copy attribute pointers from temporary struct into td->tio_info:
         */
        bcopy(&termio_info, &td->tio_info, sizeof (termio_info_t));

        /*
         * Initialize the terminal size based on the terminfo database.  If it
         * does not have the relevant properties, fall back to the environment
         * settings or to a hardcoded default.  These settings will only be
         * used if we subsequently fail to derive the size with TIOCGWINSZ.
         */
        td->tio_rows = MAX(td->tio_info.ti_lines.at_val, 0);
        td->tio_cols = MAX(td->tio_info.ti_cols.at_val, 0);

        if (td->tio_rows == 0) {
                if ((str = getenv("LINES")) != NULL && strisnum(str) != 0 &&
                    (i = strtoi(str)) > 0)
                        td->tio_rows = i;
                else
                        td->tio_rows = TIO_DEFAULT_ROWS;
        }

        if (td->tio_cols == 0) {
                if ((str = getenv("COLUMNS")) != NULL && strisnum(str) != 0 &&
                    (i = strtoi(str)) > 0)
                        td->tio_cols = i;
                else
                        td->tio_cols = TIO_DEFAULT_COLS;
        }

        td->tio_flags = 0;

        /*
         * We turn on TIO_AUTOWRAP if "am" (automargin) is set on the terminal.
         * Previously, this check was too smart for its own good, turning
         * TIO_AUTOWRAP on only when both "am" was set _and_ "xenl" was unset.
         * "xenl" is one of the (infamous?) so-called "Xanthippe glitches",
         * this one pertaining to the Concept 100's feature of not autowrapping
         * if the autowrap-inducing character is a newline.  (That is, the
         * newline was "eaten" in this case -- and "xenl" accordingly stands
         * for "Xanthippe eat newline", which itself sounds like a command from
         * an Infocom game about line discipline set in ancient Greece.) This
         * (now removed) condition misunderstood the semantics of "xenl",
         * apparently inferring that its presence denoted that the terminal
         * could not autowrap at all.  In fact, "xenl" is commonly set, and
         * denotes that the terminal will not autowrap on a newline -- a
         * feature (nee glitch) that is not particularly relevant with respect
         * to our input processing.  (Ironically, disabling TIO_AUTOWRAP in
         * this case only results in correct-seeming output because of the
         * presence of the feature, as the inserted newline to force the wrap
         * isn't actually displayed; were it displayed, the input buffer would
         * appear double-spaced.)  So with respect to "xenl" and autowrapping,
         * the correct behavior is to ignore it entirely, and adopt the
         * (simpler and less arcane) logic of setting TIO_AUTOWRAP if (and only
         * if) "am" is set on the terminal.  This does not, however, mean that
         * "xenl" is meaningless; see below...
         */
        if (td->tio_info.ti_am.at_val)
                td->tio_flags |= TIO_AUTOWRAP;

        /*
         * While "xenl" doesn't dictate our TIO_AUTOWRAP setting, it does have
         * a subtle impact on the way we process input:  in addition to its
         * eponymous behavior of eating newlines, "xenl" denotes a second,
         * entirely orthogonal idiosyncracy.  As terminfo(5) tells it: "Those
         * terminals whose cursor remains on the right-most column until
         * another character has been received, rather than wrapping
         * immediately upon receiving the right- most character, such as the
         * VT100, should also indicate xenl."  So yes, "xenl" in fact has two
         * entirely different meanings -- it is a glitch-within-a-glitch, in
         * what one assumes (hopes?) to be an accident of history rather than a
         * deliberate act of sabotage.  This second behavior is relevant to us
         * because it affects the way we redraw the screen after a backspace in
         * the left-most column.  We call this second behavior "lazy wrapping",
         * and we set it if (and only if) "xenl" is set on the terminal.
         */
        if (td->tio_info.ti_xenl.at_val)
                td->tio_flags |= TIO_LAZYWRAP;

        if (td->tio_info.ti_bw.at_val)
                td->tio_flags |= TIO_BACKLEFT;

        if (td->tio_info.ti_smir.at_str != NULL ||
            td->tio_info.ti_ich1.at_str != NULL)
                td->tio_flags |= TIO_INSERT;

        if (mdb.m_flags & MDB_FL_USECUP)
                td->tio_flags |= TIO_USECUP;

        if (name != NULL && (strncmp(name, "xterm", 5) == 0 ||
            strcmp(name, "dtterm") == 0))
                td->tio_flags |= TIO_XTERM;

        /*
         * Optimizations for padding: (1) if no pad attribute is present, set
         * its value to "\0" to avoid testing later; (2) if no pad sequences
         * were found, force "npc" to TRUE so we pick the optimized tio_putp;
         * (3) if the padding baud property is not present, reset it to zero
         * since we need to compare it to an unsigned baud value.
         */
        if (td->tio_info.ti_pad.at_str == NULL)
                td->tio_info.ti_pad.at_str = ""; /* \0 is the pad char */

        if (need_padding == 0)
                td->tio_info.ti_npc.at_val = TRUE;

        if (td->tio_info.ti_npc.at_val)
                td->tio_putp = &termio_puts;
        else
                td->tio_putp = &termio_putp;

        if (td->tio_info.ti_pb.at_val < 0)
                td->tio_info.ti_pb.at_val = 0;

        /*
         * If no newline capability is available, assume \r\n will work.  If no
         * carriage return capability is available, assume \r will work.
         */
        if (td->tio_info.ti_nel.at_str == NULL)
                td->tio_info.ti_nel.at_str = "\r\n";
        if (td->tio_info.ti_cr.at_str == NULL)
                td->tio_info.ti_cr.at_str = "\r";

        return (1);
}

mdb_io_t *
mdb_termio_create(const char *name, mdb_io_t *rio, mdb_io_t *wio)
{
        struct termios otios;
        termio_data_t *td;
        int rv, err, i;

        td = mdb_zalloc(sizeof (termio_data_t), UM_SLEEP);
        td->tio_io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);

        /*
         * Save the original user settings before calling setupterm(), which
         * cleverly changes them without telling us what it did or why.
         */
        if (IOP_CTL(rio, TCGETS, &otios) == -1) {
                warn("failed to read terminal attributes for stdin");
                goto err;
        }

        rv = setupterm((char *)name, IOP_CTL(rio, MDB_IOC_GETFD, NULL), &err);
        IOP_CTL(rio, TCSETSW, &otios); /* undo setupterm() stupidity */

        if (rv == ERR) {
                if (err == 0)
                        warn("no terminal data available for TERM=%s\n", name);
                else if (err == -1)
                        warn("failed to locate terminfo database\n");
                else
                        warn("failed to initialize terminal (err=%d)\n", err);
                goto err;
        }

        if (!termio_setup_attrs(td, name))
                goto err;

        /*
         * Do not re-issue terminal capability warnings when mdb re-execs.
         */
        if (mdb.m_flags & MDB_FL_EXEC)
                td->tio_flags |= TIO_TTYWARN | TIO_CAPWARN;

        /*
         * Initialize i/o structures and command-line buffer:
         */
        td->tio_io->io_ops = &termio_ops;
        td->tio_io->io_data = td;
        td->tio_io->io_next = NULL;
        td->tio_io->io_refcnt = 0;

        td->tio_in_io = rio;
        td->tio_in = mdb_iob_create(td->tio_in_io, MDB_IOB_RDONLY);

        td->tio_out_io = wio;
        td->tio_out = mdb_iob_create(td->tio_out_io, MDB_IOB_WRONLY);
        mdb_iob_clrflags(td->tio_out, MDB_IOB_AUTOWRAP);

        td->tio_link = NULL;
        mdb_cmdbuf_create(&td->tio_cmdbuf);

        /*
         * Fill in all the keymap entries with the insert function:
         */
        for (i = 0; i < KEY_MAX; i++)
                td->tio_keymap[i] = termio_insert;

        /*
         * Now override selected entries with editing functions:
         */
        td->tio_keymap['\n'] = termio_accept;
        td->tio_keymap['\r'] = termio_accept;

        td->tio_keymap[CTRL('f')] = termio_fwdchar;
        td->tio_keymap[CTRL('b')] = termio_backchar;
        td->tio_keymap[CTRL('t')] = termio_transpose;
        td->tio_keymap[CTRL('a')] = termio_home;
        td->tio_keymap[CTRL('e')] = termio_end;
        td->tio_keymap[META('f')] = termio_fwdword;
        td->tio_keymap[META('b')] = termio_backword;
        td->tio_keymap[META('d')] = termio_killfwdword;
        td->tio_keymap[META('\b')] = termio_killbackword;
        td->tio_keymap[CTRL('k')] = termio_kill;
        td->tio_keymap[CTRL('p')] = termio_prevhist;
        td->tio_keymap[CTRL('n')] = termio_nexthist;
        td->tio_keymap[CTRL('r')] = termio_findhist;
        td->tio_keymap[CTRL('l')] = termio_refresh;
        td->tio_keymap[CTRL('d')] = termio_delchar;
        td->tio_keymap[CTRL('?')] = termio_widescreen;

        td->tio_keymap[KPAD('A')] = termio_prevhist;
        td->tio_keymap[KPAD('B')] = termio_nexthist;
        td->tio_keymap[KPAD('C')] = termio_fwdchar;
        td->tio_keymap[KPAD('D')] = termio_backchar;

        /*
         * Many modern terminal emulators treat the "Home" and "End" keys on a
         * PC keyboard as cursor keys.  Some others use a multibyte function
         * key control sequence.  We handle both styles here:
         */
        td->tio_keymap[KPAD('H')] = termio_home;
        td->tio_keymap[FKEY('1')] = termio_home;
        td->tio_keymap[KPAD('F')] = termio_end;
        td->tio_keymap[FKEY('4')] = termio_end;

        /*
         * We default both ASCII BS and DEL to termio_backspace for safety.  We
         * want backspace to work whenever possible, regardless of whether or
         * not we're able to ask the terminal for the specific character that
         * it will use.  kmdb, for example, is not able to make this request,
         * and must be prepared to accept both.
         */
        td->tio_keymap[CTRL('h')] = termio_backspace;
        td->tio_keymap[KEY_DEL] = termio_backspace;

        /*
         * Overrides for single-key accelerators
         */
        td->tio_keymap['['] = termio_accel;
        td->tio_keymap[']'] = termio_accel;

        /*
         * Grab tabs
         */
        td->tio_keymap['\t'] = termio_tab;

        td->tio_x = 0;
        td->tio_y = 0;
        td->tio_max_x = 0;
        td->tio_max_y = 0;

        td->tio_active = FALSE;
        td->tio_rti_on = FALSE;
        td->tio_suspended = 1;

        /*
         * Perform a resume operation to complete our terminal initialization,
         * and then adjust the keymap according to the terminal settings.
         */
        termio_resume_tty(td, &td->tio_ptios);
        bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios));

        td->tio_keymap[td->tio_intr] = termio_intr;
        td->tio_keymap[td->tio_quit] = termio_quit;
        td->tio_keymap[td->tio_erase] = termio_backspace;
        td->tio_keymap[td->tio_werase] = termio_killbackword;
        td->tio_keymap[td->tio_kill] = termio_reset;
        td->tio_keymap[td->tio_susp] = termio_susp;

        (void) mdb_signal_sethandler(SIGWINCH, termio_winch, td);
        (void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td);

        if (mdb.m_debug & MDB_DBG_CMDBUF)
                termio_dump(td, &termio_attrs[0]);

        return (td->tio_io);

err:
        mdb_free(td->tio_io, sizeof (mdb_io_t));
        mdb_free(td, sizeof (termio_data_t));

        return (NULL);
}

int
mdb_iob_isatty(mdb_iob_t *iob)
{
        mdb_io_t *io;

        if (iob->iob_flags & MDB_IOB_TTYLIKE)
                return (1);

        for (io = iob->iob_iop; io != NULL; io = io->io_next) {
                if (io->io_ops == &termio_ops)
                        return (1);
        }

        return (0);
}

static const char *
termio_insert(termio_data_t *td, int c)
{
        size_t olen = td->tio_cmdbuf.cmd_buflen;

        if (mdb_cmdbuf_insert(&td->tio_cmdbuf, c) == 0) {
                if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
                        termio_addch(td, c, td->tio_cmdbuf.cmd_buflen - olen);
                else
                        termio_insch(td, c, td->tio_cmdbuf.cmd_buflen - olen);
        }

        return (NULL);
}

static const char *
termio_accept(termio_data_t *td, int c)
{
        if (td->tio_flags & TIO_FINDHIST) {
                (void) mdb_cmdbuf_findhist(&td->tio_cmdbuf, c);

                td->tio_prompt = mdb.m_prompt;
                td->tio_promptlen = mdb.m_promptlen;
                td->tio_flags &= ~TIO_FINDHIST;

                termio_redraw(td);
                return (NULL);
        }

        /* Ensure that the cursor is at the end of the line */
        (void) termio_end(td, c);

        return (mdb_cmdbuf_accept(&td->tio_cmdbuf));
}

static const char *
termio_backspace(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_backspace(&td->tio_cmdbuf, c) == 0) {
                if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
                        termio_bspch(td);
                else
                        termio_redraw(td);
        }

        return (NULL);
}

/*
 * This function may end up calling termio_read recursively as part of invoking
 * the mdb pager. To work around this fact, we need to go through and make sure
 * that we change the underlying terminal settings before and after this
 * function call. If we don't do this, we invoke the pager, and don't abort
 * (which will longjmp us elsewhere) we're going to return to the read loop with
 * the wrong termio settings.
 *
 * Furthermore, because of the fact that we're being invoked in a user context
 * that allows us to be interrupted, we need to actually allocate the memory
 * that we're using with GC so that it gets cleaned up in case of the pager
 * resetting us and never reaching the end.
 */
/*ARGSUSED*/
static const char *
termio_tab(termio_data_t *td, int c)
{
        char *buf;
        const char *result;
        int nres;
        mdb_tab_cookie_t *mtp;

        if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
                warn("failed to restore terminal attributes");

        buf = mdb_alloc(td->tio_cmdbuf.cmd_bufidx + 1, UM_SLEEP | UM_GC);
        (void) strncpy(buf, td->tio_cmdbuf.cmd_buf, td->tio_cmdbuf.cmd_bufidx);
        buf[td->tio_cmdbuf.cmd_bufidx] = '\0';
        td->tio_flags |= TIO_TAB;
        mtp = mdb_tab_init();
        nres = mdb_tab_command(mtp, buf);

        if (nres == 0) {
                result = NULL;
        } else {
                result = mdb_tab_match(mtp);
                if (nres != 1) {
                        mdb_printf("\n");
                        mdb_tab_print(mtp);
                }
        }

        if (result != NULL) {
                int index = 0;

                while (result[index] != '\0') {
                        (void) termio_insert(td, result[index]);
                        index++;
                }
        }

        termio_redraw(td);
        mdb_tab_fini(mtp);
        td->tio_flags &= ~TIO_TAB;
        if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1)
                warn("failed to set terminal attributes");


        return (NULL);
}

static const char *
termio_delchar(termio_data_t *td, int c)
{
        if (!(mdb.m_flags & MDB_FL_IGNEOF) &&
            mdb_cmdbuf_atend(&td->tio_cmdbuf) &&
            mdb_cmdbuf_atstart(&td->tio_cmdbuf))
                return (termio_quit(td, c));

        if (mdb_cmdbuf_delchar(&td->tio_cmdbuf, c) == 0) {
                if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
                        termio_delch(td);
                else
                        termio_redraw(td);
        }

        return (NULL);
}

static const char *
termio_fwdchar(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_fwdchar(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_backchar(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_backchar(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_transpose(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_transpose(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

static const char *
termio_home(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_home(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_end(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_end(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_fwdword(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_fwdword(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_backword(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_backword(&td->tio_cmdbuf, c) == 0)
                termio_mvcur(td);

        return (NULL);
}

static const char *
termio_kill(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_kill(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

static const char *
termio_killfwdword(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_killfwdword(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

static const char *
termio_killbackword(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_killbackword(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

static const char *
termio_reset(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0)
                termio_clear(td);

        return (NULL);
}

/*ARGSUSED*/
static const char *
termio_widescreen(termio_data_t *td, int c)
{
        if (td->tio_flags & TIO_XTERM) {
                if (td->tio_cols == 80)
                        termio_tput(td, TI_DECSET(TI_DECCOLM), 1);
                else
                        termio_tput(td, TI_DECRST(TI_DECCOLM), 1);
                mdb_iob_flush(td->tio_out);
                termio_winch(SIGWINCH, NULL, NULL, td);
        }

        return (NULL);
}

static const char *
termio_prevhist(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_prevhist(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

static const char *
termio_nexthist(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_nexthist(&td->tio_cmdbuf, c) == 0)
                termio_redraw(td);

        return (NULL);
}

/*
 * Single-key accelerator support.  Several commands are so commonly used as to
 * require a single-key equivalent.  If we see one of these accelerator
 * characters at the beginning of an otherwise-empty line, we'll replace it with
 * the expansion.
 */
static const char *
termio_accel(termio_data_t *td, int c)
{
        const char *p;

        if (td->tio_cmdbuf.cmd_buflen != 0 ||
            (p = termio_accel_lookup(c)) == NULL)
                return (termio_insert(td, c));

        while (*p != '\0')
                (void) termio_insert(td, *p++);
        return (termio_accept(td, '\n'));
}

static const char *
termio_findhist(termio_data_t *td, int c)
{
        if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0) {
                td->tio_prompt = "Search: ";
                td->tio_promptlen = strlen(td->tio_prompt);
                td->tio_flags |= TIO_FINDHIST;
                termio_redraw(td);
        }

        return (NULL);
}

/*ARGSUSED*/
static const char *
termio_refresh(termio_data_t *td, int c)
{
        if (td->tio_info.ti_clear.at_str) {
                termio_tput(td, td->tio_info.ti_clear.at_str, 1);
                td->tio_x = td->tio_y = 0;
        }
        termio_redraw(td);
        return (NULL);
}

/*
 * Leave the terminal read code by longjmp'ing up the stack of mdb_frame_t's
 * back to the main parsing loop (see mdb_run() in mdb.c).
 */
static const char *
termio_abort(termio_data_t *td, int c, int err)
{
        (void) mdb_cmdbuf_reset(&td->tio_cmdbuf, c);
        td->tio_active = FALSE;
        td->tio_rti_on = FALSE;

        if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
                warn("failed to restore terminal attributes");

        longjmp(mdb.m_frame->f_pcb, err);
        /*NOTREACHED*/
        return (NULL);
}

static const char *
termio_intr(termio_data_t *td, int c)
{
        return (termio_abort(td, c, MDB_ERR_SIGINT));
}

static const char *
termio_quit(termio_data_t *td, int c)
{
        return (termio_abort(td, c, MDB_ERR_QUIT));
}

/*ARGSUSED*/
static const char *
termio_susp(termio_data_t *td, int c)
{
        (void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_IGN, NULL);
        (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL);

        termio_suspend_tty(td, &td->tio_ptios);
        mdb_iob_nl(td->tio_out);

        (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL);
        (void) mdb_signal_pgrp(SIGTSTP);

        /*
         * When we call mdb_signal_pgrp(SIGTSTP), we are expecting the entire
         * debugger process group to be stopped by the kernel.  Once we return
         * from that call, we assume we are resuming from a subsequent SIGCONT.
         */
        (void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL);
        termio_resume_tty(td, &td->tio_ptios);

        (void) mdb_signal_sethandler(SIGWINCH, termio_winch, td);
        (void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td);

        if (td->tio_active)
                siglongjmp(td->tio_env, SIGCONT);

        return (NULL);
}

/*ARGSUSED*/
static void
termio_winch(int sig, siginfo_t *sip, ucontext_t *ucp, void *data)
{
        termio_data_t *td = data;
        mdb_bool_t change = FALSE;
        struct winsize winsz;

        if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == -1)
                return; /* just ignore this WINCH if the ioctl fails */

        if (td->tio_rows != (size_t)winsz.ws_row ||
            td->tio_cols != (size_t)winsz.ws_col) {

                if (td->tio_active)
                        termio_clear(td);

                if (winsz.ws_row != 0)
                        td->tio_rows = (size_t)winsz.ws_row;

                if (winsz.ws_col != 0)
                        td->tio_cols = (size_t)winsz.ws_col;

                if (td->tio_active)
                        termio_clear(td);

                mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols);
                change = TRUE;
        }

        if (change && td->tio_active)
                siglongjmp(td->tio_env, sig);

        if (change && td->tio_link != NULL)
                mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols);
}

/*ARGSUSED*/
static void
termio_tstp(int sig, siginfo_t *sip, ucontext_t *ucp, void *data)
{
        (void) termio_susp(data, CTRL('Z'));
}