mdb_termio.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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(7I)) 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_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>
#ifdef ERR
#endif
#include <curses.h>
/*
* 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.
*/
typedef union termio_attr_val {
const char *at_str; /* String value */
int at_val; /* Integer or boolean value */
typedef struct termio_info {
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 */
typedef struct termio_attr {
const char *ta_name; /* Capability name */
struct termio_data;
typedef const char *(*keycb_t)(struct termio_data *, int);
static const mdb_bitmask_t tio_flag_masks[] = {
{ NULL, 0, 0 }
};
typedef struct termio_data {
char *tio_attrs; /* Attribute string buffer */
const char *tio_prompt; /* Prompt string for this read */
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 */
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_suspend(mdb_io_t *);
static void termio_resume(mdb_io_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_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);
extern const char *tigetstr(const char *);
extern int tigetflag(const char *);
extern int tigetnum(const char *);
static const mdb_io_ops_t termio_ops = {
};
static termio_info_t termio_info;
static const termio_attr_t termio_attrs[] = {
};
/*
* 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;
return (NULL);
}
static ssize_t
{
int c;
const char *s;
warn("failed to set terminal attributes");
if (nbytes == 1) {
goto out;
rlen = 1;
goto out;
}
/*
* We need to redraw the entire command-line and restart our read loop
* in the event of a SIGWINCH or resume following SIGTSTP (SIGCONT).
*/
}
/*
* 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.
*/
do {
goto out;
}
goto char_loop;
}
if (esc) {
if (c == '[') {
pad++;
goto char_loop;
}
c = META(c);
}
if (pad) {
}
out:
warn("failed to restore terminal attributes");
return (rlen);
}
static ssize_t
{
}
/*ARGSUSED*/
static off64_t
{
}
static int
{
if (req == MDB_IOC_CTTY) {
return (0);
}
}
static void
{
}
static const char *
{
}
static void
{
} else
}
static void
{
} else
}
static int
{
if (attrs & ATT_STANDOUT)
if (attrs & ATT_UNDERLINE)
if (attrs & ATT_REVERSE)
if (attrs & ATT_ALTCHARSET)
} else {
if (attrs & ATT_STANDOUT)
if (attrs & ATT_UNDERLINE)
if (attrs & ATT_ALTCHARSET)
}
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
{
}
}
/*
* 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
{
if (td->tio_suspended++ != 0)
return; /* already suspended; do not restore state */
warn("failed to restore terminal attributes");
}
}
/*
* 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
{
/*
* 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
* 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.
*/
0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
1800, 2400, 4800, 9600, 19200, 38400, 57600,
76800, 115200, 153600, 230400, 307200, 460800
};
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 */
/*
* 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.
*/
}
/*
* Read the current set of terminal attributes, and save them in iosp
* so we can restore them later. Then derive rtios, dtios, and winsz.
*/
warn("failed to get terminal attributes");
}
/*
* Select the appropriate modified settings to restore based on our
* current state, and then install them.
*/
if (td->tio_rti_on)
else
warn("failed to reset terminal attributes");
/*
* Compute the terminal speed as described in termio(7I), and then
* look up the corresponding microseconds-per-char in our table.
*/
else
"9600 baud\n", speed);
}
/*
* Send the necessary terminal initialization sequences to enable
* enable cursor positioning. Clear the screen afterward if possible.
*/
}
}
/*
* If the terminal is xterm-compatible, enable column mode switching.
* Save the previous value in the terminal so we can restore it.
*/
}
}
static void
{
}
static void
{
}
/*
* 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
{
}
}
/*
* Parse the terminfo(4) padding sequence "$<...>" and delay for the specified
* amount of time by sending pad characters to the terminal.
*/
static const char *
{
const char *p = s;
/*
* 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')
if (*p == '.') {
if (p[1] >= '0' && p[1] <= '9')
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 '/':
continue;
case '*':
continue;
case '>':
return (p);
default:
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
{
while (s[0] != '\0') {
if (s[0] == '$' && s[1] == '<')
else
}
}
/*
* 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
{
}
/*
* 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(4)), 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
{
if (s != NULL)
}
static void
{
if (width == 1) {
}
} else
}
static void
{
}
} else
}
static void
{
const char *str;
} else {
}
for (i = 0; i < cnt; i++)
}
} else {
}
for (i = 0; i < cnt; i++)
}
}
static void
{
size_t i;
else {
}
}
static void
{
} else {
}
}
static void
{
else
}
static void
{
}
}
}
static void
{
if (len == 0)
return; /* if the buffer is empty, we're done */
else {
buf += n;
len -= n;
}
}
}
static void
{
/*
* 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.
*/
}
}
/*
* For debugging purposes, iterate over the table of attributes and output them
* in human readable form for verification.
*/
static void
{
char *str;
case TIO_ATTR_REQSTR:
case TIO_ATTR_STR:
} else {
}
break;
case TIO_ATTR_INT:
break;
case TIO_ATTR_BOOL:
break;
}
}
}
static int
{
const termio_attr_t *ta;
const char *str;
char *bufp;
int need_padding = 0;
int i;
/*
* Load terminal attributes:
*/
case TIO_ATTR_REQSTR:
case TIO_ATTR_STR:
if (str == (const char *)-1) {
"terminal capability '%s' is not of type "
return (0);
}
"terminal capability '%s' is not "
return (0);
}
break;
case TIO_ATTR_BOOL:
"terminal capability '%s' is not of type "
return (0);
}
break;
case TIO_ATTR_INT:
"terminal capability '%s' is not of type "
return (0);
}
break;
}
}
if (nbytes != 0)
else
/*
* Now make another pass through the terminal attributes and load the
* actual pointers into our static data structure:
*/
case TIO_ATTR_REQSTR:
case TIO_ATTR_STR:
/*
* Copy the result string into our contiguous
* buffer, and store a pointer to it in at_str.
*/
/*
* Check the string for a "$<>" pad sequence;
* if none are found, we can optimize later.
*/
need_padding++;
} else {
}
break;
case TIO_ATTR_BOOL:
break;
case TIO_ATTR_INT:
break;
}
}
/*
* Copy attribute pointers from temporary struct into td->tio_info:
*/
/*
* 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.
*/
else
}
else
}
/*
* 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 (need_padding == 0)
else
/*
* If no newline capability is available, assume \r\n will work. If no
* carriage return capability is available, assume \r will work.
*/
return (1);
}
mdb_io_t *
{
/*
* Save the original user settings before calling setupterm(), which
* cleverly changes them without telling us what it did or why.
*/
warn("failed to read terminal attributes for stdin");
goto err;
}
if (err == 0)
else if (err == -1)
warn("failed to locate terminfo database\n");
else
goto err;
}
goto err;
/*
* Do not re-issue terminal capability warnings when mdb re-execs.
*/
/*
* Initialize i/o structures and command-line buffer:
*/
/*
* Fill in all the keymap entries with the insert function:
*/
for (i = 0; i < KEY_MAX; i++)
/*
* Now override selected entries with editing functions:
*/
/*
* 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.
*/
/*
* Overrides for single-key accelerators
*/
/*
* Perform a resume operation to complete our terminal initialization,
* and then adjust the keymap according to the terminal settings.
*/
err:
return (NULL);
}
int
{
return (1);
return (1);
}
return (0);
}
static const char *
{
else
}
return (NULL);
}
static const char *
{
return (NULL);
}
/* Ensure that the cursor is at the end of the line */
(void) termio_end(td, c);
}
static const char *
{
else
}
return (NULL);
}
static const char *
{
return (termio_quit(td, c));
else
}
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
return (NULL);
}
/*ARGSUSED*/
static const char *
{
else
}
return (NULL);
}
static const char *
{
return (NULL);
}
static const char *
{
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 *
{
const char *p;
(p = termio_accel_lookup(c)) == NULL)
return (termio_insert(td, c));
while (*p != '\0')
(void) termio_insert(td, *p++);
}
static const char *
{
}
return (NULL);
}
/*ARGSUSED*/
static const char *
{
}
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 *
{
warn("failed to restore terminal attributes");
/*NOTREACHED*/
return (NULL);
}
static const char *
{
}
static const char *
{
}
/*ARGSUSED*/
static const char *
{
(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.
*/
if (td->tio_active)
return (NULL);
}
/*ARGSUSED*/
static void
{
return; /* just ignore this WINCH if the ioctl fails */
if (td->tio_active)
if (td->tio_active)
}
}
/*ARGSUSED*/
static void
{
}