xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_termio.c (revision 306990466301759c88d84c51c57b57ca930a3635)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2012 by Delphix. All rights reserved.
29  * Copyright 2017 Joyent, Inc.
30  */
31 
32 /*
33  * Terminal I/O Backend
34  *
35  * Terminal editing backend for standard input.  The terminal i/o backend is
36  * actually built on top of two other i/o backends: one for raw input and
37  * another for raw output (presumably stdin and stdout).  When IOP_READ is
38  * invoked, the terminal backend enters a read-loop in which it can perform
39  * command-line editing and access a history buffer.  Once a newline is read,
40  * the entire buffered command-line is returned to the caller.  The termio
41  * code makes use of a command buffer (see mdb_cmdbuf.c) to maintain and
42  * manipulate the state of a command line, and store it for re-use in a
43  * history list.  The termio code manipulates the terminal to keep it in
44  * sync with the contents of the command buffer, and moves the cursor in
45  * response to editing commands.
46  *
47  * The terminal backend is also responsible for maintaining and manipulating
48  * the settings (see stty(1) and termio(4I)) associated with the terminal.
49  * The debugger makes use of four distinct sets of terminal attributes:
50  *
51  * (1) the settings used by the debugger's parent process (tio_ptios),
52  * (2) the settings used by a controlled child process (tio_ctios),
53  * (3) the settings used for reading and command-line editing (tio_rtios), and
54  * (4) the settings used when mdb dcmds are executing (tio_dtios).
55  *
56  * The parent settings (1) are read from the terminal during initialization.
57  * These settings are restored before the debugger exits or when it is stopped
58  * by SIGTSTP.  The child settings (2) are initially a copy of (1), but are
59  * then restored prior to continuing execution of a victim process.  The new
60  * settings (3) and (4) are both derived from (1).  The raw settings (3) used
61  * for reading from the terminal allow the terminal code to respond instantly
62  * to keypresses and perform all the necessary handling.  The dcmd settings (4)
63  * are essentially the same as (1), except that we make sure ISIG is enabled
64  * so that we will receive asynchronous SIGINT notification from the terminal
65  * driver if the user types the interrupt character (typically ^C).
66  */
67 
68 #include <setjmp.h>
69 #include <unistd.h>
70 #include <stdlib.h>
71 #include <limits.h>
72 
73 #include <mdb/mdb_types.h>
74 #include <mdb/mdb_cmdbuf.h>
75 #include <mdb/mdb_err.h>
76 #include <mdb/mdb_io_impl.h>
77 #include <mdb/mdb_debug.h>
78 #include <mdb/mdb_signal.h>
79 #include <mdb/mdb_callb.h>
80 #include <mdb/mdb_stdlib.h>
81 #include <mdb/mdb_string.h>
82 #include <mdb/mdb_modapi.h>
83 #include <mdb/mdb_frame.h>
84 #include <mdb/mdb_tab.h>
85 #include <mdb/mdb.h>
86 
87 #ifdef ERR
88 #undef ERR
89 #endif
90 
91 #include <curses.h>
92 
93 #define	KEY_ESC	(0x01b)			/* Escape key code */
94 #define	KEY_DEL (0x07f)			/* ASCII DEL key code */
95 
96 /*
97  * These macros support the use of various ranges within the "tio_keymap"
98  * member of "termio_data_t" objects.  This array maps from an input byte, or
99  * special control code, to the appropriate terminal handling callback.  The
100  * array has KEY_MAX (0x1ff) entries, partitioned as follows:
101  *
102  *     0 -  7f		7-bit ASCII byte
103  *    80 -  ff	META()	ASCII byte with Meta key modifier
104  *   100 - 119	KPAD()	Alphabetic character received as part of a single-byte
105  *			cursor control sequence, e.g. ESC [ A
106  *   11a - 123	FKEY()	Numeric character received as part of a function key
107  *			control sequence, e.g. ESC [ 4 ~
108  *   124 - 1ff		Unused
109  */
110 #define	META(c)		(((c) & 0x7f) | 0x80)
111 #define	KPAD(c)		(((c) < 'A' || (c) > 'Z') ? 0 : ((c) - 'A' + 0x100))
112 #define	FKEY(c)		(((c) < '0' || (c) > '9') ? 0 : ((c) - '0' + 0x11a))
113 
114 /*
115  * These macros allow for composition of control sequences for xterm and other
116  * terminals that support certain features of the VT102 and later VT terminals.
117  * Refer to the classic monograph "Xterm Control Sequences" for more info.
118  */
119 #define	TI_DECSET(Pm) "\033[?" Pm "h"	/* Compose DEC private mode set */
120 #define	TI_DECRST(Pm) "\033[?" Pm "l"	/* Compose DEC private mode reset */
121 #define	TI_DECSAV(Pm) "\033[?" Pm "s"	/* Compose DEC private mode save */
122 #define	TI_DECRES(Pm) "\033[?" Pm "r"	/* Compose DEC private mode restore */
123 
124 #define	TI_DECCOLM		"3"	/* Ps = DEC 80/132 column mode */
125 #define	TI_COLENAB		"40"	/* Ps = 80/132 column switch enable */
126 
127 #define	TIO_DEFAULT_ROWS	24	/* Default number of rows */
128 #define	TIO_DEFAULT_COLS	80	/* Default number of columns */
129 
130 typedef union termio_attr_val {
131 	const char *at_str;		/* String value */
132 	int at_val;			/* Integer or boolean value */
133 } termio_attr_val_t;
134 
135 typedef struct termio_info {
136 	termio_attr_val_t ti_cub1;	/* Move back one space */
137 	termio_attr_val_t ti_cuf1;	/* Move forward one space */
138 	termio_attr_val_t ti_cuu1;	/* Move up one line */
139 	termio_attr_val_t ti_cud1;	/* Move down one line */
140 	termio_attr_val_t ti_pad;	/* Pad character */
141 	termio_attr_val_t ti_el;	/* Clear to end-of-line */
142 	termio_attr_val_t ti_am;	/* Automatic right margin? */
143 	termio_attr_val_t ti_bw;	/* Backward motion at left edge? */
144 	termio_attr_val_t ti_npc;	/* No padding character? */
145 	termio_attr_val_t ti_xenl;	/* Newline ignored at autowrap? */
146 	termio_attr_val_t ti_xon;	/* Use xon/xoff handshaking? */
147 	termio_attr_val_t ti_cols;	/* # of columns */
148 	termio_attr_val_t ti_lines;	/* # of rows */
149 	termio_attr_val_t ti_pb;	/* Lowest baud rate that requires pad */
150 	termio_attr_val_t ti_smso;	/* Set standout mode */
151 	termio_attr_val_t ti_rmso;	/* Remove standout mode */
152 	termio_attr_val_t ti_smul;	/* Set underline mode */
153 	termio_attr_val_t ti_rmul;	/* Remove underline mode */
154 	termio_attr_val_t ti_enacs;	/* Enable alternate character set */
155 	termio_attr_val_t ti_smacs;	/* Set alternate character set */
156 	termio_attr_val_t ti_rmacs;	/* Remove alternate character set */
157 	termio_attr_val_t ti_smcup;	/* Set mode where cup is active */
158 	termio_attr_val_t ti_rmcup;	/* Remove mode where cup is active */
159 	termio_attr_val_t ti_rev;	/* Set reverse video mode */
160 	termio_attr_val_t ti_bold;	/* Set bold text mode */
161 	termio_attr_val_t ti_dim;	/* Set dim text mode */
162 	termio_attr_val_t ti_sgr0;	/* Remove all video attributes */
163 	termio_attr_val_t ti_smir;	/* Set insert mode */
164 	termio_attr_val_t ti_rmir;	/* Remove insert mode */
165 	termio_attr_val_t ti_ich1;	/* Insert character */
166 	termio_attr_val_t ti_ip;	/* Insert pad delay in msecs */
167 	termio_attr_val_t ti_clear;	/* Clear screen and home cursor */
168 	termio_attr_val_t ti_cnorm;	/* Make cursor appear normal */
169 	termio_attr_val_t ti_nel;	/* Newline */
170 	termio_attr_val_t ti_cr;	/* Carriage return */
171 	termio_attr_val_t ti_smam;	/* Turn on automatic margins */
172 } termio_info_t;
173 
174 typedef enum {
175 	TIO_ATTR_REQSTR,		/* String attribute that is required */
176 	TIO_ATTR_STR,			/* String attribute */
177 	TIO_ATTR_BOOL,			/* Boolean attribute */
178 	TIO_ATTR_INT			/* Integer attribute */
179 } termio_attr_type_t;
180 
181 typedef struct termio_attr {
182 	const char *ta_name;		/* Capability name */
183 	termio_attr_type_t ta_type;	/* Capability type */
184 	termio_attr_val_t *ta_valp;	/* String pointer location */
185 } termio_attr_t;
186 
187 struct termio_data;
188 typedef const char *(*keycb_t)(struct termio_data *, int);
189 typedef void (*putp_t)(struct termio_data *, const char *, uint_t);
190 
191 #define	TIO_FINDHIST	0x01		/* Find-history-mode */
192 #define	TIO_AUTOWRAP	0x02		/* Terminal has autowrap */
193 #define	TIO_BACKLEFT	0x04		/* Terminal can go back at left edge */
194 #define	TIO_INSERT	0x08		/* Terminal has insert mode */
195 #define	TIO_USECUP	0x10		/* Use smcup/rmcup sequences */
196 #define	TIO_TTYWARN	0x20		/* Warnings about tty issued */
197 #define	TIO_CAPWARN	0x40		/* Warnings about terminfo issued */
198 #define	TIO_XTERM	0x80		/* Terminal is xterm compatible */
199 #define	TIO_TAB		0x100		/* Tab completion mode */
200 #define	TIO_LAZYWRAP	0x200		/* Lazy cursor on autowrap */
201 
202 static const mdb_bitmask_t tio_flag_masks[] = {
203 	{ "FINDHIST", TIO_FINDHIST, TIO_FINDHIST },
204 	{ "AUTOWRAP", TIO_AUTOWRAP, TIO_AUTOWRAP },
205 	{ "BACKLEFT", TIO_BACKLEFT, TIO_BACKLEFT },
206 	{ "INSERT", TIO_INSERT, TIO_INSERT },
207 	{ "USECUP", TIO_USECUP, TIO_USECUP },
208 	{ "TTYWARN", TIO_TTYWARN, TIO_TTYWARN },
209 	{ "CAPWARN", TIO_CAPWARN, TIO_CAPWARN },
210 	{ "XTERM", TIO_XTERM, TIO_XTERM },
211 	{ "TAB", TIO_TAB, TIO_TAB },
212 	{ "LAZYWRAP", TIO_LAZYWRAP, TIO_LAZYWRAP },
213 	{ NULL, 0, 0 }
214 };
215 
216 typedef struct termio_data {
217 	mdb_io_t *tio_io;		/* Pointer back to containing i/o */
218 	mdb_io_t *tio_out_io;		/* Terminal output backend */
219 	mdb_io_t *tio_in_io;		/* Terminal input backend */
220 	mdb_iob_t *tio_out;		/* I/o buffer for terminal output */
221 	mdb_iob_t *tio_in;		/* I/o buffer for terminal input */
222 	mdb_iob_t *tio_link;		/* I/o buffer to resize on WINCH */
223 	keycb_t tio_keymap[KEY_MAX];	/* Keymap (see comments atop file) */
224 	mdb_cmdbuf_t tio_cmdbuf;	/* Editable command-line buffer */
225 	struct termios tio_ptios;	/* Parent terminal settings */
226 	struct termios tio_ctios;	/* Child terminal settings */
227 	struct termios tio_rtios;	/* Settings for read loop */
228 	struct termios tio_dtios;	/* Settings for dcmd execution */
229 	sigjmp_buf tio_env;		/* Read loop setjmp(3c) environment */
230 	termio_info_t tio_info;		/* Terminal attribute strings */
231 	char *tio_attrs;		/* Attribute string buffer */
232 	size_t tio_attrslen;		/* Length in bytes of tio_attrs */
233 	const char *tio_prompt;		/* Prompt string for this read */
234 	size_t tio_promptlen;		/* Length of prompt string */
235 	size_t tio_rows;		/* Terminal height */
236 	size_t tio_cols;		/* Terminal width */
237 	size_t tio_x;			/* Cursor x coordinate */
238 	size_t tio_y;			/* Cursor y coordinate */
239 	size_t tio_max_x;		/* Previous maximum x coordinate */
240 	size_t tio_max_y;		/* Previous maximum y coordinate */
241 	int tio_intr;			/* Interrupt char */
242 	int tio_quit;			/* Quit char */
243 	int tio_erase;			/* Erase char */
244 	int tio_werase;			/* Word-erase char */
245 	int tio_kill;			/* Kill char */
246 	int tio_eof;			/* End-of-file char */
247 	int tio_susp;			/* Suspend char */
248 	uint_t tio_flags;		/* Miscellaneous flags */
249 	volatile mdb_bool_t tio_active;	/* Flag denoting read loop active */
250 	volatile mdb_bool_t tio_rti_on;	/* Flag denoting rtios in use */
251 	putp_t tio_putp;		/* termio_tput() subroutine */
252 	uint_t tio_baud;		/* Baud rate (chars per second) */
253 	uint_t tio_usecpc;		/* Usecs per char at given baud rate */
254 	pid_t tio_opgid;		/* Old process group id for terminal */
255 	uint_t tio_suspended;		/* termio_suspend_tty() nesting count */
256 } termio_data_t;
257 
258 static ssize_t termio_read(mdb_io_t *, void *, size_t);
259 static ssize_t termio_write(mdb_io_t *, const void *, size_t);
260 static off64_t termio_seek(mdb_io_t *, off64_t, int);
261 static int termio_ctl(mdb_io_t *, int, void *);
262 static void termio_close(mdb_io_t *);
263 static const char *termio_name(mdb_io_t *);
264 static void termio_link(mdb_io_t *, mdb_iob_t *);
265 static void termio_unlink(mdb_io_t *, mdb_iob_t *);
266 static int termio_setattr(mdb_io_t *, int, uint_t);
267 static void termio_suspend(mdb_io_t *);
268 static void termio_resume(mdb_io_t *);
269 
270 static void termio_suspend_tty(termio_data_t *, struct termios *);
271 static void termio_resume_tty(termio_data_t *, struct termios *);
272 
273 static void termio_putp(termio_data_t *, const char *, uint_t);
274 static void termio_puts(termio_data_t *, const char *, uint_t);
275 static void termio_tput(termio_data_t *, const char *, uint_t);
276 static void termio_addch(termio_data_t *, char, size_t);
277 static void termio_insch(termio_data_t *, char, size_t);
278 static void termio_mvcur(termio_data_t *);
279 static void termio_bspch(termio_data_t *);
280 static void termio_delch(termio_data_t *);
281 static void termio_clear(termio_data_t *);
282 static void termio_redraw(termio_data_t *);
283 static void termio_prompt(termio_data_t *);
284 
285 static const char *termio_tab(termio_data_t *, int);
286 static const char *termio_insert(termio_data_t *, int);
287 static const char *termio_accept(termio_data_t *, int);
288 static const char *termio_backspace(termio_data_t *, int);
289 static const char *termio_delchar(termio_data_t *, int);
290 static const char *termio_fwdchar(termio_data_t *, int);
291 static const char *termio_backchar(termio_data_t *, int);
292 static const char *termio_transpose(termio_data_t *, int);
293 static const char *termio_home(termio_data_t *, int);
294 static const char *termio_end(termio_data_t *, int);
295 static const char *termio_fwdword(termio_data_t *, int);
296 static const char *termio_backword(termio_data_t *, int);
297 static const char *termio_kill(termio_data_t *, int);
298 static const char *termio_killfwdword(termio_data_t *, int);
299 static const char *termio_killbackword(termio_data_t *, int);
300 static const char *termio_reset(termio_data_t *, int);
301 static const char *termio_widescreen(termio_data_t *, int);
302 static const char *termio_prevhist(termio_data_t *, int);
303 static const char *termio_nexthist(termio_data_t *, int);
304 static const char *termio_accel(termio_data_t *, int);
305 static const char *termio_findhist(termio_data_t *, int);
306 static const char *termio_refresh(termio_data_t *, int);
307 
308 static const char *termio_intr(termio_data_t *, int);
309 static const char *termio_quit(termio_data_t *, int);
310 static const char *termio_susp(termio_data_t *, int);
311 
312 static void termio_winch(int, siginfo_t *, ucontext_t *, void *);
313 static void termio_tstp(int, siginfo_t *, ucontext_t *, void *);
314 
315 extern const char *tigetstr(const char *);
316 extern int tigetflag(const char *);
317 extern int tigetnum(const char *);
318 
319 static const mdb_io_ops_t termio_ops = {
320 	.io_read = termio_read,
321 	.io_write = termio_write,
322 	.io_seek = termio_seek,
323 	.io_ctl = termio_ctl,
324 	.io_close = termio_close,
325 	.io_name = termio_name,
326 	.io_link = termio_link,
327 	.io_unlink = termio_unlink,
328 	.io_setattr = termio_setattr,
329 	.io_suspend = termio_suspend,
330 	.io_resume = termio_resume,
331 };
332 
333 static termio_info_t termio_info;
334 
335 static const termio_attr_t termio_attrs[] = {
336 	{ "cub1", TIO_ATTR_REQSTR, &termio_info.ti_cub1 },
337 	{ "cuf1", TIO_ATTR_REQSTR, &termio_info.ti_cuf1 },
338 	{ "cuu1", TIO_ATTR_REQSTR, &termio_info.ti_cuu1 },
339 	{ "cud1", TIO_ATTR_REQSTR, &termio_info.ti_cud1 },
340 	{ "pad", TIO_ATTR_STR, &termio_info.ti_pad },
341 	{ "el", TIO_ATTR_REQSTR, &termio_info.ti_el },
342 	{ "am", TIO_ATTR_BOOL, &termio_info.ti_am },
343 	{ "bw", TIO_ATTR_BOOL, &termio_info.ti_bw },
344 	{ "npc", TIO_ATTR_BOOL, &termio_info.ti_npc },
345 	{ "xenl", TIO_ATTR_BOOL, &termio_info.ti_xenl },
346 	{ "xon", TIO_ATTR_BOOL, &termio_info.ti_xon },
347 	{ "cols", TIO_ATTR_INT, &termio_info.ti_cols },
348 	{ "lines", TIO_ATTR_INT, &termio_info.ti_lines },
349 	{ "pb", TIO_ATTR_INT, &termio_info.ti_pb },
350 	{ "smso", TIO_ATTR_STR, &termio_info.ti_smso },
351 	{ "rmso", TIO_ATTR_STR, &termio_info.ti_rmso },
352 	{ "smul", TIO_ATTR_STR, &termio_info.ti_smul },
353 	{ "rmul", TIO_ATTR_STR, &termio_info.ti_rmul },
354 	{ "enacs", TIO_ATTR_STR, &termio_info.ti_enacs },
355 	{ "smacs", TIO_ATTR_STR, &termio_info.ti_smacs },
356 	{ "rmacs", TIO_ATTR_STR, &termio_info.ti_rmacs },
357 	{ "smcup", TIO_ATTR_STR, &termio_info.ti_smcup },
358 	{ "rmcup", TIO_ATTR_STR, &termio_info.ti_rmcup },
359 	{ "rev", TIO_ATTR_STR, &termio_info.ti_rev },
360 	{ "bold", TIO_ATTR_STR, &termio_info.ti_bold },
361 	{ "dim", TIO_ATTR_STR, &termio_info.ti_dim },
362 	{ "sgr0", TIO_ATTR_STR, &termio_info.ti_sgr0 },
363 	{ "smir", TIO_ATTR_STR, &termio_info.ti_smir },
364 	{ "rmir", TIO_ATTR_STR, &termio_info.ti_rmir },
365 	{ "ich1", TIO_ATTR_STR, &termio_info.ti_ich1 },
366 	{ "ip", TIO_ATTR_STR, &termio_info.ti_ip },
367 	{ "clear", TIO_ATTR_STR, &termio_info.ti_clear },
368 	{ "cnorm", TIO_ATTR_STR, &termio_info.ti_cnorm },
369 	{ "nel", TIO_ATTR_STR, &termio_info.ti_nel },
370 	{ "cr", TIO_ATTR_STR, &termio_info.ti_cr },
371 	{ "smam", TIO_ATTR_STR, &termio_info.ti_smam },
372 	{ NULL, 0, NULL }
373 };
374 
375 /*
376  * One-key accelerators.  Some commands are used so frequently as to need
377  * single-key equivalents.  termio_accelkeys contains a list of the accelerator
378  * keys, with termio_accel listing the accelerated commands.  The array is
379  * indexed by the offset of the accelerator in the macro string, and as such
380  * *must* stay in the same order.
381  */
382 static const char *const termio_accelkeys = "[]";
383 
384 static const char *const termio_accelstrings[] = {
385 	"::step over",	/* [ */
386 	"::step"	/* ] */
387 };
388 
389 static const char *
termio_accel_lookup(int c)390 termio_accel_lookup(int c)
391 {
392 	const char *acc;
393 
394 	if ((acc = strchr(termio_accelkeys, c)) == NULL)
395 		return (NULL);
396 
397 	return (termio_accelstrings[(int)(acc - termio_accelkeys)]);
398 }
399 
400 static ssize_t
termio_read(mdb_io_t * io,void * buf,size_t nbytes)401 termio_read(mdb_io_t *io, void *buf, size_t nbytes)
402 {
403 	termio_data_t *td = io->io_data;
404 
405 	mdb_bool_t esc = FALSE, pad = FALSE;
406 	ssize_t rlen = 0;
407 	int c, fkey = 0;
408 
409 	const char *s;
410 	size_t len;
411 
412 	if (io->io_next != NULL)
413 		return (IOP_READ(io->io_next, buf, nbytes));
414 
415 	td->tio_rti_on = TRUE;
416 	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1)
417 		warn("failed to set terminal attributes");
418 
419 	if (nbytes == 1) {
420 		if ((c = mdb_iob_getc(td->tio_in)) == EOF)
421 			goto out;
422 
423 		*((uchar_t *)buf) = (uchar_t)c;
424 
425 		rlen = 1;
426 		goto out;
427 	}
428 
429 	if (td->tio_flags & TIO_TAB)
430 		termio_redraw(td);
431 	else
432 		termio_prompt(td);
433 
434 	/*
435 	 * We need to redraw the entire command-line and restart our read loop
436 	 * in the event of a SIGWINCH or resume following SIGTSTP (SIGCONT).
437 	 */
438 	if (sigsetjmp(td->tio_env, 1) != 0) {
439 		td->tio_active = FALSE;
440 		td->tio_x = td->tio_y = 0;
441 
442 		len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen;
443 		td->tio_max_x = len % td->tio_cols;
444 		td->tio_max_y = len / td->tio_cols;
445 
446 		esc = pad = FALSE;
447 
448 		termio_tput(td, td->tio_info.ti_cr.at_str, 1);
449 		mdb_iob_flush(td->tio_out);
450 		termio_redraw(td);
451 	}
452 
453 	/*
454 	 * Since we're about to start the read loop, we know our linked iob
455 	 * is quiescent. We can now safely resize it to the latest term size.
456 	 */
457 	if (td->tio_link != NULL)
458 		mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols);
459 
460 	td->tio_active = TRUE;
461 
462 	/*
463 	 * We may have had some error while in tab completion mode which sent us
464 	 * longjmping all over the place. If that's the case, come back here and
465 	 * make sure the flag is off.
466 	 */
467 	td->tio_flags &= ~TIO_TAB;
468 
469 	do {
470 char_loop:
471 		if ((c = mdb_iob_getc(td->tio_in)) == EOF) {
472 			td->tio_active = FALSE;
473 			goto out;
474 		}
475 
476 		if (c == KEY_ESC && esc == FALSE) {
477 			esc = TRUE;
478 			goto char_loop;
479 		}
480 
481 		if (esc) {
482 			esc = FALSE;
483 
484 			if (c == '[') {
485 				pad++;
486 				goto char_loop;
487 			}
488 
489 			c = META(c);
490 		}
491 
492 		if (pad) {
493 			pad = FALSE;
494 
495 			if ((fkey = FKEY(c)) != 0) {
496 				/*
497 				 * Some terminals send a multibyte control
498 				 * sequence for particular function keys.
499 				 * These sequences are of the form:
500 				 *
501 				 *	ESC [ n ~
502 				 *
503 				 * where "n" is a numeric character from
504 				 * '0' to '9'.
505 				 */
506 				goto char_loop;
507 			}
508 
509 			if ((c = KPAD(c)) == 0) {
510 				/*
511 				 * This was not a valid keypad control
512 				 * sequence.
513 				 */
514 				goto char_loop;
515 			}
516 		}
517 
518 		if (fkey != 0) {
519 			if (c == '~') {
520 				/*
521 				 * This is a valid special function key
522 				 * sequence.  Use the value we stashed
523 				 * earlier.
524 				 */
525 				c = fkey;
526 			}
527 
528 			fkey = 0;
529 		}
530 
531 		len = td->tio_cmdbuf.cmd_buflen + td->tio_promptlen;
532 
533 		td->tio_max_x = len % td->tio_cols;
534 		td->tio_max_y = len / td->tio_cols;
535 
536 	} while ((s = (*td->tio_keymap[c])(td, c)) == NULL);
537 
538 	td->tio_active = FALSE;
539 	mdb_iob_nl(td->tio_out);
540 
541 	if ((rlen = strlen(s)) >= nbytes - 1)
542 		rlen = nbytes - 1;
543 
544 	(void) strncpy(buf, s, rlen);
545 	((char *)buf)[rlen++] = '\n';
546 
547 out:
548 	td->tio_rti_on = FALSE;
549 	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
550 		warn("failed to restore terminal attributes");
551 
552 	return (rlen);
553 }
554 
555 static ssize_t
termio_write(mdb_io_t * io,const void * buf,size_t nbytes)556 termio_write(mdb_io_t *io, const void *buf, size_t nbytes)
557 {
558 	termio_data_t *td = io->io_data;
559 
560 	if (io->io_next != NULL)
561 		return (IOP_WRITE(io->io_next, buf, nbytes));
562 
563 	return (IOP_WRITE(td->tio_out_io, buf, nbytes));
564 }
565 
566 /*ARGSUSED*/
567 static off64_t
termio_seek(mdb_io_t * io,off64_t offset,int whence)568 termio_seek(mdb_io_t *io, off64_t offset, int whence)
569 {
570 	return (set_errno(ENOTSUP));
571 }
572 
573 static int
termio_ctl(mdb_io_t * io,int req,void * arg)574 termio_ctl(mdb_io_t *io, int req, void *arg)
575 {
576 	termio_data_t *td = io->io_data;
577 
578 	if (io->io_next != NULL)
579 		return (IOP_CTL(io->io_next, req, arg));
580 
581 	if (req == MDB_IOC_CTTY) {
582 		bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios));
583 		return (0);
584 	}
585 
586 	return (IOP_CTL(td->tio_in_io, req, arg));
587 }
588 
589 static void
termio_close(mdb_io_t * io)590 termio_close(mdb_io_t *io)
591 {
592 	termio_data_t *td = io->io_data;
593 
594 	(void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_DFL, NULL);
595 	(void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL);
596 
597 	termio_suspend_tty(td, &td->tio_ptios);
598 
599 	if (td->tio_attrs)
600 		mdb_free(td->tio_attrs, td->tio_attrslen);
601 
602 	mdb_cmdbuf_destroy(&td->tio_cmdbuf);
603 
604 	mdb_iob_destroy(td->tio_out);
605 	mdb_iob_destroy(td->tio_in);
606 
607 	mdb_free(td, sizeof (termio_data_t));
608 }
609 
610 static const char *
termio_name(mdb_io_t * io)611 termio_name(mdb_io_t *io)
612 {
613 	termio_data_t *td = io->io_data;
614 
615 	if (io->io_next != NULL)
616 		return (IOP_NAME(io->io_next));
617 
618 	return (IOP_NAME(td->tio_in_io));
619 }
620 
621 static void
termio_link(mdb_io_t * io,mdb_iob_t * iob)622 termio_link(mdb_io_t *io, mdb_iob_t *iob)
623 {
624 	termio_data_t *td = io->io_data;
625 
626 	if (io->io_next == NULL) {
627 		mdb_iob_resize(iob, td->tio_rows, td->tio_cols);
628 		td->tio_link = iob;
629 	} else
630 		IOP_LINK(io->io_next, iob);
631 }
632 
633 static void
termio_unlink(mdb_io_t * io,mdb_iob_t * iob)634 termio_unlink(mdb_io_t *io, mdb_iob_t *iob)
635 {
636 	termio_data_t *td = io->io_data;
637 
638 	if (io->io_next == NULL) {
639 		if (td->tio_link == iob)
640 			td->tio_link = NULL;
641 	} else
642 		IOP_UNLINK(io->io_next, iob);
643 }
644 
645 static int
termio_setattr(mdb_io_t * io,int req,uint_t attrs)646 termio_setattr(mdb_io_t *io, int req, uint_t attrs)
647 {
648 	termio_data_t *td = io->io_data;
649 
650 	if (io->io_next != NULL)
651 		return (IOP_SETATTR(io->io_next, req, attrs));
652 
653 	if ((req != ATT_ON && req != ATT_OFF) || (attrs & ~ATT_ALL) != 0)
654 		return (set_errno(EINVAL));
655 
656 	if (req == ATT_ON) {
657 		if (attrs & ATT_STANDOUT)
658 			termio_tput(td, td->tio_info.ti_smso.at_str, 1);
659 		if (attrs & ATT_UNDERLINE)
660 			termio_tput(td, td->tio_info.ti_smul.at_str, 1);
661 		if (attrs & ATT_REVERSE)
662 			termio_tput(td, td->tio_info.ti_rev.at_str, 1);
663 		if (attrs & ATT_BOLD)
664 			termio_tput(td, td->tio_info.ti_bold.at_str, 1);
665 		if (attrs & ATT_DIM)
666 			termio_tput(td, td->tio_info.ti_dim.at_str, 1);
667 		if (attrs & ATT_ALTCHARSET)
668 			termio_tput(td, td->tio_info.ti_smacs.at_str, 1);
669 	} else {
670 		if (attrs & ATT_STANDOUT)
671 			termio_tput(td, td->tio_info.ti_rmso.at_str, 1);
672 		if (attrs & ATT_UNDERLINE)
673 			termio_tput(td, td->tio_info.ti_rmul.at_str, 1);
674 		if (attrs & ATT_ALTCHARSET)
675 			termio_tput(td, td->tio_info.ti_rmacs.at_str, 1);
676 		if (attrs & (ATT_REVERSE | ATT_BOLD | ATT_DIM))
677 			termio_tput(td, td->tio_info.ti_sgr0.at_str, 1);
678 	}
679 
680 	mdb_iob_flush(td->tio_out);
681 	return (0);
682 }
683 
684 /*
685  * Issue a warning message if the given warning flag is clear.  Then set the
686  * flag bit so that we do not issue multiple instances of the same warning.
687  */
688 static void
termio_warn(termio_data_t * td,uint_t flag,const char * format,...)689 termio_warn(termio_data_t *td, uint_t flag, const char *format, ...)
690 {
691 	if (!(td->tio_flags & flag)) {
692 		va_list alist;
693 
694 		va_start(alist, format);
695 		vwarn(format, alist);
696 		va_end(alist);
697 
698 		td->tio_flags |= flag;
699 	}
700 }
701 
702 /*
703  * Restore the terminal to its previous state before relinquishing control of
704  * it to the shell (on a SIGTSTP) or the victim process (on a continue).  If
705  * we need to change the foreground process group, we must temporarily ignore
706  * SIGTTOU because TIOCSPGRP could trigger it.
707  */
708 static void
termio_suspend_tty(termio_data_t * td,struct termios * iosp)709 termio_suspend_tty(termio_data_t *td, struct termios *iosp)
710 {
711 	if (td->tio_suspended++ != 0)
712 		return; /* already suspended; do not restore state */
713 
714 	if (td->tio_flags & TIO_XTERM)
715 		termio_tput(td, TI_DECRES(TI_COLENAB), 1);
716 
717 	if (td->tio_flags & TIO_USECUP)
718 		termio_tput(td, td->tio_info.ti_rmcup.at_str, 1);
719 
720 	termio_tput(td, td->tio_info.ti_sgr0.at_str, 1);
721 	mdb_iob_flush(td->tio_out);
722 
723 	if (termio_ctl(td->tio_io, TCSETSW, iosp) == -1)
724 		warn("failed to restore terminal attributes");
725 
726 	if (td->tio_opgid > 0 && td->tio_opgid != mdb.m_pgid) {
727 		mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)td->tio_opgid);
728 		(void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL);
729 		(void) termio_ctl(td->tio_io, TIOCSPGRP, &td->tio_opgid);
730 		(void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL);
731 	}
732 }
733 
734 /*
735  * Resume the debugger's terminal state.  We first save the existing terminal
736  * state so we can restore it later, and then install our own state.  We
737  * derive our state dynamically from the existing terminal state so that we
738  * always reflect the latest modifications made by the user with stty(1).
739  */
740 static void
termio_resume_tty(termio_data_t * td,struct termios * iosp)741 termio_resume_tty(termio_data_t *td, struct termios *iosp)
742 {
743 	/*
744 	 * We use this table of bauds to convert the baud constant returned by
745 	 * the terminal code to a baud rate in characters per second.  The
746 	 * values are in the order of the B* speed defines in <sys/termios.h>.
747 	 * We then compute tio_usecpc (microseconds-per-char) in order to
748 	 * determine how many pad characters need to be issued at the current
749 	 * terminal speed to delay for a given number of microseconds.  For
750 	 * example, at 300 baud (B300 = 7), we look up baud[7] = 300, and then
751 	 * compute usecpc as MICROSEC / 300 = 3333 microseconds per character.
752 	 */
753 	static const uint_t baud[] = {
754 		0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
755 		1800, 2400, 4800, 9600, 19200, 38400, 57600,
756 		76800, 115200, 153600, 230400, 307200, 460800, 921600,
757 		1000000, 1152000, 1500000, 2000000, 2500000, 3000000,
758 		3500000, 4000000
759 	};
760 
761 	struct termios *ntios;
762 	struct winsize winsz;
763 	uint_t speed;
764 
765 	if (td->tio_suspended == 0)
766 		fail("termio_resume called without matching termio_suspend\n");
767 
768 	if (--td->tio_suspended != 0)
769 		return; /* nested suspends; do not resume yet */
770 
771 	td->tio_opgid = -1; /* set to invalid pgid in case TIOCPGRP fails */
772 	(void) termio_ctl(td->tio_io, TIOCGPGRP, &td->tio_opgid);
773 
774 	/*
775 	 * If the foreground process group does not include the debugger, reset
776 	 * the foreground process group so we are in control of the terminal.
777 	 * We temporarily ignore TTOU because TIOCSPGRP could trigger it.
778 	 */
779 	if (td->tio_opgid != mdb.m_pgid) {
780 		(void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_IGN, NULL);
781 		(void) termio_ctl(td->tio_io, TIOCSPGRP, &mdb.m_pgid);
782 		(void) mdb_signal_sethandler(SIGTTOU, MDB_SIG_DFL, NULL);
783 		mdb_dprintf(MDB_DBG_CMDBUF, "fg pgid=%d\n", (int)mdb.m_pgid);
784 	}
785 
786 	/*
787 	 * Read the current set of terminal attributes, and save them in iosp
788 	 * so we can restore them later.  Then derive rtios, dtios, and winsz.
789 	 */
790 	if (termio_ctl(td->tio_io, TCGETS, iosp) < 0)
791 		warn("failed to get terminal attributes");
792 
793 	if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == 0) {
794 		if (winsz.ws_row != 0)
795 			td->tio_rows = (size_t)winsz.ws_row;
796 		if (winsz.ws_col != 0)
797 			td->tio_cols = (size_t)winsz.ws_col;
798 	}
799 
800 	mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols);
801 
802 	td->tio_intr = td->tio_ptios.c_cc[VINTR];
803 	td->tio_quit = td->tio_ptios.c_cc[VQUIT];
804 	td->tio_erase = td->tio_ptios.c_cc[VERASE];
805 	td->tio_werase = td->tio_ptios.c_cc[VWERASE];
806 	td->tio_kill = td->tio_ptios.c_cc[VKILL];
807 	td->tio_eof = td->tio_ptios.c_cc[VEOF];
808 	td->tio_susp = td->tio_ptios.c_cc[VSUSP];
809 
810 	bcopy(&td->tio_ptios, &td->tio_rtios, sizeof (struct termios));
811 	td->tio_rtios.c_iflag &= ~(ISTRIP | INPCK | ICRNL | INLCR | IUCLC);
812 	td->tio_rtios.c_oflag &= ~(OCRNL | ONLRET);
813 	td->tio_rtios.c_oflag |= ONLCR;
814 	td->tio_rtios.c_lflag &= ~(ISIG | ICANON | ECHO);
815 	td->tio_rtios.c_cflag |= CS8;
816 	td->tio_rtios.c_cc[VTIME] = 0;
817 	td->tio_rtios.c_cc[VMIN] = 1;
818 
819 	bcopy(&td->tio_ptios, &td->tio_dtios, sizeof (struct termios));
820 	td->tio_dtios.c_oflag &= ~(OCRNL | ONLRET);
821 	td->tio_dtios.c_oflag |= ONLCR;
822 	td->tio_dtios.c_lflag |= ISIG | ICANON | ECHO;
823 
824 	/*
825 	 * Select the appropriate modified settings to restore based on our
826 	 * current state, and then install them.
827 	 */
828 	if (td->tio_rti_on)
829 		ntios = &td->tio_rtios;
830 	else
831 		ntios = &td->tio_dtios;
832 
833 	if (termio_ctl(td->tio_io, TCSETSW, ntios) < 0)
834 		warn("failed to reset terminal attributes");
835 
836 	/*
837 	 * Compute the terminal speed as described in termio(4I), and then
838 	 * look up the corresponding microseconds-per-char in our table.
839 	 */
840 	if (ntios->c_cflag & CBAUDEXT)
841 		speed = (ntios->c_cflag & CBAUD) + CBAUD + 1;
842 	else
843 		speed = (ntios->c_cflag & CBAUD);
844 
845 	if (speed >= sizeof (baud) / sizeof (baud[0])) {
846 		termio_warn(td, TIO_TTYWARN, "invalid speed %u -- assuming "
847 		    "9600 baud\n", speed);
848 		speed = B9600;
849 	}
850 
851 	td->tio_baud = baud[speed];
852 	td->tio_usecpc = MICROSEC / td->tio_baud;
853 
854 	mdb_dprintf(MDB_DBG_CMDBUF, "speed = %u baud (%u usec / char), "
855 	    "putp = %s\n", td->tio_baud, td->tio_usecpc,
856 	    td->tio_putp == &termio_puts ? "fast" : "slow");
857 
858 	/*
859 	 * Send the necessary terminal initialization sequences to enable
860 	 * enable cursor positioning.  Clear the screen afterward if possible.
861 	 */
862 	if (td->tio_flags & TIO_USECUP) {
863 		termio_tput(td, td->tio_info.ti_smcup.at_str, 1);
864 		if (td->tio_info.ti_clear.at_str) {
865 			termio_tput(td, td->tio_info.ti_clear.at_str, 1);
866 			td->tio_x = td->tio_y = 0;
867 		}
868 	}
869 
870 	/*
871 	 * If the terminal is xterm-compatible, enable column mode switching.
872 	 * Save the previous value in the terminal so we can restore it.
873 	 */
874 	if (td->tio_flags & TIO_XTERM) {
875 		termio_tput(td, TI_DECSAV(TI_COLENAB), 1);
876 		termio_tput(td, TI_DECSET(TI_COLENAB), 1);
877 	}
878 
879 	termio_tput(td, td->tio_info.ti_cnorm.at_str, 1); /* cursor visible */
880 	termio_tput(td, td->tio_info.ti_enacs.at_str, 1); /* alt char set */
881 
882 	/*
883 	 * If the terminal is automargin-capable and we have an initialization
884 	 * sequence to enable automargins, we must send it now.  Note that
885 	 * we don't have a way of querying this mode and restoring it; if
886 	 * we are fighting with (say) a target that is depending on automargin
887 	 * being turned off, it will lose.
888 	 */
889 	if ((td->tio_flags & TIO_AUTOWRAP) &&
890 	    td->tio_info.ti_smam.at_str != NULL) {
891 		termio_tput(td, td->tio_info.ti_smam.at_str, 1);
892 	}
893 
894 	mdb_iob_flush(td->tio_out);
895 }
896 
897 static void
termio_suspend(mdb_io_t * io)898 termio_suspend(mdb_io_t *io)
899 {
900 	termio_data_t *td = io->io_data;
901 	termio_suspend_tty(td, &td->tio_ctios);
902 }
903 
904 static void
termio_resume(mdb_io_t * io)905 termio_resume(mdb_io_t *io)
906 {
907 	termio_data_t *td = io->io_data;
908 	termio_resume_tty(td, &td->tio_ctios);
909 }
910 
911 /*
912  * Delay for the specified number of microseconds by sending the pad character
913  * to the terminal.  We round up by half a frame and then divide by the usecs
914  * per character to determine the number of pad characters to send.
915  */
916 static void
termio_delay(termio_data_t * td,uint_t usec)917 termio_delay(termio_data_t *td, uint_t usec)
918 {
919 	char pad = td->tio_info.ti_pad.at_str[0];
920 	uint_t usecpc = td->tio_usecpc;
921 
922 	for (usec = (usec + usecpc / 2) / usecpc; usec != 0; usec--) {
923 		mdb_iob_putc(td->tio_out, pad);
924 		mdb_iob_flush(td->tio_out);
925 	}
926 }
927 
928 /*
929  * Parse the terminfo(5) padding sequence "$<...>" and delay for the specified
930  * amount of time by sending pad characters to the terminal.
931  */
932 static const char *
termio_pad(termio_data_t * td,const char * s,uint_t lines)933 termio_pad(termio_data_t *td, const char *s, uint_t lines)
934 {
935 	int xon = td->tio_info.ti_xon.at_val;
936 	int pb = td->tio_info.ti_pb.at_val;
937 
938 	const char *p = s;
939 	uint_t usec = 0;
940 
941 	/*
942 	 * The initial string is a number of milliseconds, followed by an
943 	 * optional decimal point and number of tenths of milliseconds.
944 	 * We convert this to microseconds for greater accuracy.  Only a single
945 	 * digit is permitted after the decimal point; we ignore any others.
946 	 */
947 	while (*p >= '0' && *p <= '9')
948 		usec = usec * 10 + *p++ - '0';
949 
950 	usec *= 1000; /* convert msecs to usecs */
951 
952 	if (*p == '.') {
953 		if (p[1] >= '0' && p[1] <= '9')
954 			usec += (p[1] - '0') * 100;
955 		for (p++; *p >= '0' && *p <= '9'; p++)
956 			continue;
957 	}
958 
959 	/*
960 	 * Following the time delay specifier,
961 	 *
962 	 * 1. An optional "/" indicates that the delay should be done
963 	 *    regardless of the value of the terminal's xon property,
964 	 * 2. An optional "*" indicates that the delay is proportional to the
965 	 *    count of affected lines, and
966 	 * 3. A mandatory ">" terminates the sequence.
967 	 *
968 	 * If we encounter any other characters, we assume that we found "$<"
969 	 * accidentally embedded in another sequence, so we just output "$".
970 	 */
971 	for (;;) {
972 		switch (*p++) {
973 		case '/':
974 			xon = FALSE;
975 			continue;
976 		case '*':
977 			usec *= lines;
978 			continue;
979 		case '>':
980 			if (xon == FALSE && usec != 0 && td->tio_baud >= pb)
981 				termio_delay(td, usec);
982 			return (p);
983 		default:
984 			mdb_iob_putc(td->tio_out, *s);
985 			return (s + 1);
986 		}
987 	}
988 }
989 
990 /*
991  * termio_tput() subroutine for terminals that require padding.  We look ahead
992  * for "$<>" sequences, and call termio_pad() to process them; all other chars
993  * are output directly to the underlying device and then flushed at the end.
994  */
995 static void
termio_putp(termio_data_t * td,const char * s,uint_t lines)996 termio_putp(termio_data_t *td, const char *s, uint_t lines)
997 {
998 	while (s[0] != '\0') {
999 		if (s[0] == '$' && s[1] == '<')
1000 			s = termio_pad(td, s + 2, lines);
1001 		else
1002 			mdb_iob_putc(td->tio_out, *s++);
1003 	}
1004 
1005 	mdb_iob_flush(td->tio_out);
1006 }
1007 
1008 /*
1009  * termio_tput() subroutine for terminals that do not require padding.  We
1010  * simply output the string to the underlying i/o buffer; we let the caller
1011  * take care of flushing so that multiple sequences can be concatenated.
1012  */
1013 /*ARGSUSED*/
1014 static void
termio_puts(termio_data_t * td,const char * s,uint_t lines)1015 termio_puts(termio_data_t *td, const char *s, uint_t lines)
1016 {
1017 	mdb_iob_puts(td->tio_out, s);
1018 }
1019 
1020 /*
1021  * Print a padded escape sequence string to the terminal.  The caller specifies
1022  * the string 's' and a count of the affected lines.  If the string contains an
1023  * embedded delay sequence delimited by "$<>" (see terminfo(5)), appropriate
1024  * padding will be included in the output.  We determine whether or not padding
1025  * is required during initialization, and set tio_putp to the proper subroutine.
1026  */
1027 static void
termio_tput(termio_data_t * td,const char * s,uint_t lines)1028 termio_tput(termio_data_t *td, const char *s, uint_t lines)
1029 {
1030 	if (s != NULL)
1031 		td->tio_putp(td, s, lines);
1032 }
1033 
1034 static void
termio_addch(termio_data_t * td,char c,size_t width)1035 termio_addch(termio_data_t *td, char c, size_t width)
1036 {
1037 	if (width == 1) {
1038 		mdb_iob_putc(td->tio_out, c);
1039 		td->tio_x++;
1040 
1041 		if (td->tio_x >= td->tio_cols) {
1042 			if (!(td->tio_flags & TIO_AUTOWRAP))
1043 				termio_tput(td, td->tio_info.ti_nel.at_str, 1);
1044 			td->tio_x = 0;
1045 			td->tio_y++;
1046 		}
1047 
1048 		mdb_iob_flush(td->tio_out);
1049 	} else
1050 		termio_redraw(td);
1051 }
1052 
1053 static void
termio_insch(termio_data_t * td,char c,size_t width)1054 termio_insch(termio_data_t *td, char c, size_t width)
1055 {
1056 	if (width == 1 && (td->tio_flags & TIO_INSERT) &&
1057 	    td->tio_y == td->tio_max_y) {
1058 
1059 		termio_tput(td, td->tio_info.ti_smir.at_str, 1);
1060 		termio_tput(td, td->tio_info.ti_ich1.at_str, 1);
1061 
1062 		mdb_iob_putc(td->tio_out, c);
1063 		td->tio_x++;
1064 
1065 		termio_tput(td, td->tio_info.ti_ip.at_str, 1);
1066 		termio_tput(td, td->tio_info.ti_rmir.at_str, 1);
1067 
1068 		if (td->tio_x >= td->tio_cols) {
1069 			if (!(td->tio_flags & TIO_AUTOWRAP))
1070 				termio_tput(td, td->tio_info.ti_nel.at_str, 1);
1071 			td->tio_x = 0;
1072 			td->tio_y++;
1073 		}
1074 
1075 		mdb_iob_flush(td->tio_out);
1076 	} else
1077 		termio_redraw(td);
1078 }
1079 
1080 static void
termio_mvcur(termio_data_t * td)1081 termio_mvcur(termio_data_t *td)
1082 {
1083 	size_t tipos = td->tio_cmdbuf.cmd_bufidx + td->tio_promptlen;
1084 	size_t dst_x = tipos % td->tio_cols;
1085 	size_t dst_y = tipos / td->tio_cols;
1086 
1087 	const char *str;
1088 	size_t cnt, i;
1089 
1090 	if (td->tio_y != dst_y) {
1091 		if (td->tio_y < dst_y) {
1092 			str = td->tio_info.ti_cud1.at_str;
1093 			cnt = dst_y - td->tio_y;
1094 			td->tio_x = 0; /* Note: cud1 moves cursor to column 0 */
1095 		} else {
1096 			str = td->tio_info.ti_cuu1.at_str;
1097 			cnt = td->tio_y - dst_y;
1098 		}
1099 
1100 		for (i = 0; i < cnt; i++)
1101 			termio_tput(td, str, 1);
1102 
1103 		mdb_iob_flush(td->tio_out);
1104 		td->tio_y = dst_y;
1105 	}
1106 
1107 	if (td->tio_x != dst_x) {
1108 		if (td->tio_x < dst_x) {
1109 			str = td->tio_info.ti_cuf1.at_str;
1110 			cnt = dst_x - td->tio_x;
1111 		} else {
1112 			str = td->tio_info.ti_cub1.at_str;
1113 			cnt = td->tio_x - dst_x;
1114 		}
1115 
1116 		for (i = 0; i < cnt; i++)
1117 			termio_tput(td, str, 1);
1118 
1119 		mdb_iob_flush(td->tio_out);
1120 		td->tio_x = dst_x;
1121 	}
1122 }
1123 
1124 static void
termio_backleft(termio_data_t * td)1125 termio_backleft(termio_data_t *td)
1126 {
1127 	size_t i;
1128 
1129 	if (td->tio_flags & TIO_BACKLEFT)
1130 		termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
1131 	else {
1132 		termio_tput(td, td->tio_info.ti_cuu1.at_str, 1);
1133 		for (i = 0; i < td->tio_cols - 1; i++)
1134 			termio_tput(td, td->tio_info.ti_cuf1.at_str, 1);
1135 	}
1136 }
1137 
1138 static void
termio_bspch(termio_data_t * td)1139 termio_bspch(termio_data_t *td)
1140 {
1141 	if (td->tio_x == 0) {
1142 		/*
1143 		 * If TIO_LAZYWRAP is set, we are regrettably in one of two
1144 		 * states that we cannot differentiate between:  the cursor is
1145 		 * either on the right margin of the previous line (having not
1146 		 * advanced due to the lazy wrap) _or_ the cursor is on the
1147 		 * left margin of the current line because a wrap has already
1148 		 * been induced due to prior activity.  Because these cases are
1149 		 * impossible to differentiate, we force the latter case by
1150 		 * emitting a space and then moving the cursor back.  If the
1151 		 * wrap had not been induced, emitting this space will induce
1152 		 * it -- and will assure that our termio_backleft() is the
1153 		 * correct behavior with respect to the cursor.
1154 		 */
1155 		if (td->tio_flags & TIO_LAZYWRAP) {
1156 			mdb_iob_putc(td->tio_out, ' ');
1157 			termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
1158 		}
1159 
1160 		termio_backleft(td);
1161 		td->tio_x = td->tio_cols - 1;
1162 		td->tio_y--;
1163 	} else {
1164 		termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
1165 		td->tio_x--;
1166 	}
1167 
1168 	termio_delch(td);
1169 }
1170 
1171 static void
termio_delch(termio_data_t * td)1172 termio_delch(termio_data_t *td)
1173 {
1174 	mdb_iob_putc(td->tio_out, ' ');
1175 
1176 	if (td->tio_x == td->tio_cols - 1 && (td->tio_flags & TIO_AUTOWRAP)) {
1177 		/*
1178 		 * We just overwrote a space in the final column, so we know
1179 		 * that we have autowrapped and we therefore definitely don't
1180 		 * want to issue the ti_cub1.  If we have TIO_LAZYWRAP, we know
1181 		 * that the cursor remains on the final column, and we want to
1182 		 * do nothing -- but if TIO_LAZYWRAP isn't set, we have wrapped
1183 		 * and we need to move the cursor back up and all the way to
1184 		 * the right via termio_backleft().
1185 		 */
1186 		if (!(td->tio_flags & TIO_LAZYWRAP))
1187 			termio_backleft(td);
1188 	} else {
1189 		termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
1190 	}
1191 
1192 	mdb_iob_flush(td->tio_out);
1193 }
1194 
1195 static void
termio_clear(termio_data_t * td)1196 termio_clear(termio_data_t *td)
1197 {
1198 	while (td->tio_x-- != 0)
1199 		termio_tput(td, td->tio_info.ti_cub1.at_str, 1);
1200 
1201 	while (td->tio_y < td->tio_max_y) {
1202 		termio_tput(td, td->tio_info.ti_cud1.at_str, 1);
1203 		td->tio_y++;
1204 	}
1205 
1206 	while (td->tio_y-- != 0) {
1207 		termio_tput(td, td->tio_info.ti_el.at_str, 1);
1208 		termio_tput(td, td->tio_info.ti_cuu1.at_str, 1);
1209 	}
1210 
1211 	termio_tput(td, td->tio_info.ti_el.at_str, 1);
1212 	mdb_iob_flush(td->tio_out);
1213 
1214 	termio_prompt(td);
1215 }
1216 
1217 static void
termio_redraw(termio_data_t * td)1218 termio_redraw(termio_data_t *td)
1219 {
1220 	const char *buf = td->tio_cmdbuf.cmd_buf;
1221 	size_t len = td->tio_cmdbuf.cmd_buflen;
1222 	size_t pos, n;
1223 
1224 	termio_clear(td);
1225 
1226 	if (len == 0)
1227 		return; /* if the buffer is empty, we're done */
1228 
1229 	if (td->tio_flags & TIO_AUTOWRAP)
1230 		mdb_iob_nputs(td->tio_out, buf, len);
1231 	else {
1232 		for (pos = td->tio_promptlen; len != 0; pos = 0) {
1233 			n = MIN(td->tio_cols - pos, len);
1234 			mdb_iob_nputs(td->tio_out, buf, n);
1235 			buf += n;
1236 			len -= n;
1237 
1238 			if (pos + n == td->tio_cols)
1239 				termio_tput(td, td->tio_info.ti_nel.at_str, 1);
1240 		}
1241 	}
1242 
1243 	pos = td->tio_promptlen + td->tio_cmdbuf.cmd_buflen;
1244 	td->tio_x = pos % td->tio_cols;
1245 	td->tio_y = pos / td->tio_cols;
1246 
1247 	mdb_iob_flush(td->tio_out);
1248 	termio_mvcur(td);
1249 }
1250 
1251 static void
termio_prompt(termio_data_t * td)1252 termio_prompt(termio_data_t *td)
1253 {
1254 	mdb_callb_fire(MDB_CALLB_PROMPT);
1255 
1256 	/*
1257 	 * Findhist (^R) overrides the displayed prompt.  We should only update
1258 	 * the main prompt (which may have been changed by the callback) if
1259 	 * findhist isn't active.
1260 	 */
1261 	if (!(td->tio_flags & TIO_FINDHIST)) {
1262 		td->tio_prompt = mdb.m_prompt;
1263 		td->tio_promptlen = mdb.m_promptlen;
1264 	}
1265 
1266 	mdb_iob_puts(td->tio_out, td->tio_prompt);
1267 	mdb_iob_flush(td->tio_out);
1268 
1269 	td->tio_x = td->tio_promptlen;
1270 	td->tio_y = 0;
1271 }
1272 
1273 /*
1274  * For debugging purposes, iterate over the table of attributes and output them
1275  * in human readable form for verification.
1276  */
1277 static void
termio_dump(termio_data_t * td,const termio_attr_t * ta)1278 termio_dump(termio_data_t *td, const termio_attr_t *ta)
1279 {
1280 	char *str;
1281 
1282 	for (; ta->ta_name != NULL; ta++) {
1283 		switch (ta->ta_type) {
1284 		case TIO_ATTR_REQSTR:
1285 		case TIO_ATTR_STR:
1286 			if (ta->ta_valp->at_str != NULL) {
1287 				str = strchr2esc(ta->ta_valp->at_str,
1288 				    strlen(ta->ta_valp->at_str));
1289 				mdb_dprintf(MDB_DBG_CMDBUF, "%s = \"%s\"\n",
1290 				    ta->ta_name, str);
1291 				strfree(str);
1292 			} else {
1293 				mdb_dprintf(MDB_DBG_CMDBUF, "%s = <NULL>\n",
1294 				    ta->ta_name);
1295 			}
1296 			break;
1297 		case TIO_ATTR_INT:
1298 			mdb_dprintf(MDB_DBG_CMDBUF, "%s = %d\n",
1299 			    ta->ta_name, ta->ta_valp->at_val);
1300 			break;
1301 		case TIO_ATTR_BOOL:
1302 			mdb_dprintf(MDB_DBG_CMDBUF, "%s = %s\n", ta->ta_name,
1303 			    ta->ta_valp->at_val ? "TRUE" : "FALSE");
1304 			break;
1305 		}
1306 	}
1307 
1308 	mdb_dprintf(MDB_DBG_CMDBUF, "tio_flags = <%#b>\n",
1309 	    td->tio_flags, tio_flag_masks);
1310 }
1311 
1312 static int
termio_setup_attrs(termio_data_t * td,const char * name)1313 termio_setup_attrs(termio_data_t *td, const char *name)
1314 {
1315 	const termio_attr_t *ta;
1316 	const char *str;
1317 	size_t nbytes;
1318 	char *bufp;
1319 
1320 	int need_padding = 0;
1321 	int i;
1322 
1323 	/*
1324 	 * Load terminal attributes:
1325 	 */
1326 	for (nbytes = 0, ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) {
1327 		switch (ta->ta_type) {
1328 		case TIO_ATTR_REQSTR:
1329 		case TIO_ATTR_STR:
1330 			str = tigetstr(ta->ta_name);
1331 
1332 			if (str == (const char *)-1) {
1333 				termio_warn(td, TIO_CAPWARN,
1334 				    "terminal capability '%s' is not of type "
1335 				    "string as expected\n", ta->ta_name);
1336 				return (0);
1337 			}
1338 
1339 			if (str != NULL)
1340 				nbytes += strlen(str) + 1;
1341 			else if (ta->ta_type == TIO_ATTR_REQSTR) {
1342 				termio_warn(td, TIO_CAPWARN,
1343 				    "terminal capability '%s' is not "
1344 				    "available\n", ta->ta_name);
1345 				return (0);
1346 			}
1347 			break;
1348 
1349 		case TIO_ATTR_BOOL:
1350 			if (tigetflag(ta->ta_name) == -1) {
1351 				termio_warn(td, TIO_CAPWARN,
1352 				    "terminal capability '%s' is not of type "
1353 				    "boolean as expected\n", ta->ta_name);
1354 				return (0);
1355 			}
1356 			break;
1357 
1358 		case TIO_ATTR_INT:
1359 			if (tigetnum(ta->ta_name) == -2) {
1360 				termio_warn(td, TIO_CAPWARN,
1361 				    "terminal capability '%s' is not of type "
1362 				    "integer as expected\n", ta->ta_name);
1363 				return (0);
1364 			}
1365 			break;
1366 		}
1367 	}
1368 
1369 	if (nbytes != 0)
1370 		td->tio_attrs = mdb_alloc(nbytes, UM_SLEEP);
1371 	else
1372 		td->tio_attrs = NULL;
1373 
1374 	td->tio_attrslen = nbytes;
1375 	bufp = td->tio_attrs;
1376 
1377 	/*
1378 	 * Now make another pass through the terminal attributes and load the
1379 	 * actual pointers into our static data structure:
1380 	 */
1381 	for (ta = &termio_attrs[0]; ta->ta_name != NULL; ta++) {
1382 		switch (ta->ta_type) {
1383 		case TIO_ATTR_REQSTR:
1384 		case TIO_ATTR_STR:
1385 			if ((str = tigetstr(ta->ta_name)) != NULL) {
1386 				/*
1387 				 * Copy the result string into our contiguous
1388 				 * buffer, and store a pointer to it in at_str.
1389 				 */
1390 				(void) strcpy(bufp, str);
1391 				ta->ta_valp->at_str = bufp;
1392 				bufp += strlen(str) + 1;
1393 				/*
1394 				 * Check the string for a "$<>" pad sequence;
1395 				 * if none are found, we can optimize later.
1396 				 */
1397 				if ((str = strstr(ta->ta_valp->at_str,
1398 				    "$<")) != NULL && strchr(str, '>') != NULL)
1399 					need_padding++;
1400 			} else {
1401 				ta->ta_valp->at_str = NULL;
1402 			}
1403 			break;
1404 
1405 		case TIO_ATTR_BOOL:
1406 			ta->ta_valp->at_val = tigetflag(ta->ta_name);
1407 			break;
1408 
1409 		case TIO_ATTR_INT:
1410 			ta->ta_valp->at_val = tigetnum(ta->ta_name);
1411 			break;
1412 		}
1413 	}
1414 
1415 	/*
1416 	 * Copy attribute pointers from temporary struct into td->tio_info:
1417 	 */
1418 	bcopy(&termio_info, &td->tio_info, sizeof (termio_info_t));
1419 
1420 	/*
1421 	 * Initialize the terminal size based on the terminfo database.  If it
1422 	 * does not have the relevant properties, fall back to the environment
1423 	 * settings or to a hardcoded default.  These settings will only be
1424 	 * used if we subsequently fail to derive the size with TIOCGWINSZ.
1425 	 */
1426 	td->tio_rows = MAX(td->tio_info.ti_lines.at_val, 0);
1427 	td->tio_cols = MAX(td->tio_info.ti_cols.at_val, 0);
1428 
1429 	if (td->tio_rows == 0) {
1430 		if ((str = getenv("LINES")) != NULL && strisnum(str) != 0 &&
1431 		    (i = strtoi(str)) > 0)
1432 			td->tio_rows = i;
1433 		else
1434 			td->tio_rows = TIO_DEFAULT_ROWS;
1435 	}
1436 
1437 	if (td->tio_cols == 0) {
1438 		if ((str = getenv("COLUMNS")) != NULL && strisnum(str) != 0 &&
1439 		    (i = strtoi(str)) > 0)
1440 			td->tio_cols = i;
1441 		else
1442 			td->tio_cols = TIO_DEFAULT_COLS;
1443 	}
1444 
1445 	td->tio_flags = 0;
1446 
1447 	/*
1448 	 * We turn on TIO_AUTOWRAP if "am" (automargin) is set on the terminal.
1449 	 * Previously, this check was too smart for its own good, turning
1450 	 * TIO_AUTOWRAP on only when both "am" was set _and_ "xenl" was unset.
1451 	 * "xenl" is one of the (infamous?) so-called "Xanthippe glitches",
1452 	 * this one pertaining to the Concept 100's feature of not autowrapping
1453 	 * if the autowrap-inducing character is a newline.  (That is, the
1454 	 * newline was "eaten" in this case -- and "xenl" accordingly stands
1455 	 * for "Xanthippe eat newline", which itself sounds like a command from
1456 	 * an Infocom game about line discipline set in ancient Greece.) This
1457 	 * (now removed) condition misunderstood the semantics of "xenl",
1458 	 * apparently inferring that its presence denoted that the terminal
1459 	 * could not autowrap at all.  In fact, "xenl" is commonly set, and
1460 	 * denotes that the terminal will not autowrap on a newline -- a
1461 	 * feature (nee glitch) that is not particularly relevant with respect
1462 	 * to our input processing.  (Ironically, disabling TIO_AUTOWRAP in
1463 	 * this case only results in correct-seeming output because of the
1464 	 * presence of the feature, as the inserted newline to force the wrap
1465 	 * isn't actually displayed; were it displayed, the input buffer would
1466 	 * appear double-spaced.)  So with respect to "xenl" and autowrapping,
1467 	 * the correct behavior is to ignore it entirely, and adopt the
1468 	 * (simpler and less arcane) logic of setting TIO_AUTOWRAP if (and only
1469 	 * if) "am" is set on the terminal.  This does not, however, mean that
1470 	 * "xenl" is meaningless; see below...
1471 	 */
1472 	if (td->tio_info.ti_am.at_val)
1473 		td->tio_flags |= TIO_AUTOWRAP;
1474 
1475 	/*
1476 	 * While "xenl" doesn't dictate our TIO_AUTOWRAP setting, it does have
1477 	 * a subtle impact on the way we process input:  in addition to its
1478 	 * eponymous behavior of eating newlines, "xenl" denotes a second,
1479 	 * entirely orthogonal idiosyncracy.  As terminfo(5) tells it: "Those
1480 	 * terminals whose cursor remains on the right-most column until
1481 	 * another character has been received, rather than wrapping
1482 	 * immediately upon receiving the right- most character, such as the
1483 	 * VT100, should also indicate xenl."  So yes, "xenl" in fact has two
1484 	 * entirely different meanings -- it is a glitch-within-a-glitch, in
1485 	 * what one assumes (hopes?) to be an accident of history rather than a
1486 	 * deliberate act of sabotage.  This second behavior is relevant to us
1487 	 * because it affects the way we redraw the screen after a backspace in
1488 	 * the left-most column.  We call this second behavior "lazy wrapping",
1489 	 * and we set it if (and only if) "xenl" is set on the terminal.
1490 	 */
1491 	if (td->tio_info.ti_xenl.at_val)
1492 		td->tio_flags |= TIO_LAZYWRAP;
1493 
1494 	if (td->tio_info.ti_bw.at_val)
1495 		td->tio_flags |= TIO_BACKLEFT;
1496 
1497 	if (td->tio_info.ti_smir.at_str != NULL ||
1498 	    td->tio_info.ti_ich1.at_str != NULL)
1499 		td->tio_flags |= TIO_INSERT;
1500 
1501 	if (mdb.m_flags & MDB_FL_USECUP)
1502 		td->tio_flags |= TIO_USECUP;
1503 
1504 	if (name != NULL && (strncmp(name, "xterm", 5) == 0 ||
1505 	    strcmp(name, "dtterm") == 0))
1506 		td->tio_flags |= TIO_XTERM;
1507 
1508 	/*
1509 	 * Optimizations for padding: (1) if no pad attribute is present, set
1510 	 * its value to "\0" to avoid testing later; (2) if no pad sequences
1511 	 * were found, force "npc" to TRUE so we pick the optimized tio_putp;
1512 	 * (3) if the padding baud property is not present, reset it to zero
1513 	 * since we need to compare it to an unsigned baud value.
1514 	 */
1515 	if (td->tio_info.ti_pad.at_str == NULL)
1516 		td->tio_info.ti_pad.at_str = ""; /* \0 is the pad char */
1517 
1518 	if (need_padding == 0)
1519 		td->tio_info.ti_npc.at_val = TRUE;
1520 
1521 	if (td->tio_info.ti_npc.at_val)
1522 		td->tio_putp = &termio_puts;
1523 	else
1524 		td->tio_putp = &termio_putp;
1525 
1526 	if (td->tio_info.ti_pb.at_val < 0)
1527 		td->tio_info.ti_pb.at_val = 0;
1528 
1529 	/*
1530 	 * If no newline capability is available, assume \r\n will work.  If no
1531 	 * carriage return capability is available, assume \r will work.
1532 	 */
1533 	if (td->tio_info.ti_nel.at_str == NULL)
1534 		td->tio_info.ti_nel.at_str = "\r\n";
1535 	if (td->tio_info.ti_cr.at_str == NULL)
1536 		td->tio_info.ti_cr.at_str = "\r";
1537 
1538 	return (1);
1539 }
1540 
1541 mdb_io_t *
mdb_termio_create(const char * name,mdb_io_t * rio,mdb_io_t * wio)1542 mdb_termio_create(const char *name, mdb_io_t *rio, mdb_io_t *wio)
1543 {
1544 	struct termios otios;
1545 	termio_data_t *td;
1546 	int rv, err, i;
1547 
1548 	td = mdb_zalloc(sizeof (termio_data_t), UM_SLEEP);
1549 	td->tio_io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);
1550 
1551 	/*
1552 	 * Save the original user settings before calling setupterm(), which
1553 	 * cleverly changes them without telling us what it did or why.
1554 	 */
1555 	if (IOP_CTL(rio, TCGETS, &otios) == -1) {
1556 		warn("failed to read terminal attributes for stdin");
1557 		goto err;
1558 	}
1559 
1560 	rv = setupterm((char *)name, IOP_CTL(rio, MDB_IOC_GETFD, NULL), &err);
1561 	IOP_CTL(rio, TCSETSW, &otios); /* undo setupterm() stupidity */
1562 
1563 	if (rv == ERR) {
1564 		if (err == 0)
1565 			warn("no terminal data available for TERM=%s\n", name);
1566 		else if (err == -1)
1567 			warn("failed to locate terminfo database\n");
1568 		else
1569 			warn("failed to initialize terminal (err=%d)\n", err);
1570 		goto err;
1571 	}
1572 
1573 	if (!termio_setup_attrs(td, name))
1574 		goto err;
1575 
1576 	/*
1577 	 * Do not re-issue terminal capability warnings when mdb re-execs.
1578 	 */
1579 	if (mdb.m_flags & MDB_FL_EXEC)
1580 		td->tio_flags |= TIO_TTYWARN | TIO_CAPWARN;
1581 
1582 	/*
1583 	 * Initialize i/o structures and command-line buffer:
1584 	 */
1585 	td->tio_io->io_ops = &termio_ops;
1586 	td->tio_io->io_data = td;
1587 	td->tio_io->io_next = NULL;
1588 	td->tio_io->io_refcnt = 0;
1589 
1590 	td->tio_in_io = rio;
1591 	td->tio_in = mdb_iob_create(td->tio_in_io, MDB_IOB_RDONLY);
1592 
1593 	td->tio_out_io = wio;
1594 	td->tio_out = mdb_iob_create(td->tio_out_io, MDB_IOB_WRONLY);
1595 	mdb_iob_clrflags(td->tio_out, MDB_IOB_AUTOWRAP);
1596 
1597 	td->tio_link = NULL;
1598 	mdb_cmdbuf_create(&td->tio_cmdbuf);
1599 
1600 	/*
1601 	 * Fill in all the keymap entries with the insert function:
1602 	 */
1603 	for (i = 0; i < KEY_MAX; i++)
1604 		td->tio_keymap[i] = termio_insert;
1605 
1606 	/*
1607 	 * Now override selected entries with editing functions:
1608 	 */
1609 	td->tio_keymap['\n'] = termio_accept;
1610 	td->tio_keymap['\r'] = termio_accept;
1611 
1612 	td->tio_keymap[CTRL('f')] = termio_fwdchar;
1613 	td->tio_keymap[CTRL('b')] = termio_backchar;
1614 	td->tio_keymap[CTRL('t')] = termio_transpose;
1615 	td->tio_keymap[CTRL('a')] = termio_home;
1616 	td->tio_keymap[CTRL('e')] = termio_end;
1617 	td->tio_keymap[META('f')] = termio_fwdword;
1618 	td->tio_keymap[META('b')] = termio_backword;
1619 	td->tio_keymap[META('d')] = termio_killfwdword;
1620 	td->tio_keymap[META('\b')] = termio_killbackword;
1621 	td->tio_keymap[CTRL('k')] = termio_kill;
1622 	td->tio_keymap[CTRL('p')] = termio_prevhist;
1623 	td->tio_keymap[CTRL('n')] = termio_nexthist;
1624 	td->tio_keymap[CTRL('r')] = termio_findhist;
1625 	td->tio_keymap[CTRL('l')] = termio_refresh;
1626 	td->tio_keymap[CTRL('d')] = termio_delchar;
1627 	td->tio_keymap[CTRL('?')] = termio_widescreen;
1628 
1629 	td->tio_keymap[KPAD('A')] = termio_prevhist;
1630 	td->tio_keymap[KPAD('B')] = termio_nexthist;
1631 	td->tio_keymap[KPAD('C')] = termio_fwdchar;
1632 	td->tio_keymap[KPAD('D')] = termio_backchar;
1633 
1634 	/*
1635 	 * Many modern terminal emulators treat the "Home" and "End" keys on a
1636 	 * PC keyboard as cursor keys.  Some others use a multibyte function
1637 	 * key control sequence.  We handle both styles here:
1638 	 */
1639 	td->tio_keymap[KPAD('H')] = termio_home;
1640 	td->tio_keymap[FKEY('1')] = termio_home;
1641 	td->tio_keymap[KPAD('F')] = termio_end;
1642 	td->tio_keymap[FKEY('4')] = termio_end;
1643 
1644 	/*
1645 	 * We default both ASCII BS and DEL to termio_backspace for safety.  We
1646 	 * want backspace to work whenever possible, regardless of whether or
1647 	 * not we're able to ask the terminal for the specific character that
1648 	 * it will use.  kmdb, for example, is not able to make this request,
1649 	 * and must be prepared to accept both.
1650 	 */
1651 	td->tio_keymap[CTRL('h')] = termio_backspace;
1652 	td->tio_keymap[KEY_DEL] = termio_backspace;
1653 
1654 	/*
1655 	 * Overrides for single-key accelerators
1656 	 */
1657 	td->tio_keymap['['] = termio_accel;
1658 	td->tio_keymap[']'] = termio_accel;
1659 
1660 	/*
1661 	 * Grab tabs
1662 	 */
1663 	td->tio_keymap['\t'] = termio_tab;
1664 
1665 	td->tio_x = 0;
1666 	td->tio_y = 0;
1667 	td->tio_max_x = 0;
1668 	td->tio_max_y = 0;
1669 
1670 	td->tio_active = FALSE;
1671 	td->tio_rti_on = FALSE;
1672 	td->tio_suspended = 1;
1673 
1674 	/*
1675 	 * Perform a resume operation to complete our terminal initialization,
1676 	 * and then adjust the keymap according to the terminal settings.
1677 	 */
1678 	termio_resume_tty(td, &td->tio_ptios);
1679 	bcopy(&td->tio_ptios, &td->tio_ctios, sizeof (struct termios));
1680 
1681 	td->tio_keymap[td->tio_intr] = termio_intr;
1682 	td->tio_keymap[td->tio_quit] = termio_quit;
1683 	td->tio_keymap[td->tio_erase] = termio_backspace;
1684 	td->tio_keymap[td->tio_werase] = termio_killbackword;
1685 	td->tio_keymap[td->tio_kill] = termio_reset;
1686 	td->tio_keymap[td->tio_susp] = termio_susp;
1687 
1688 	(void) mdb_signal_sethandler(SIGWINCH, termio_winch, td);
1689 	(void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td);
1690 
1691 	if (mdb.m_debug & MDB_DBG_CMDBUF)
1692 		termio_dump(td, &termio_attrs[0]);
1693 
1694 	return (td->tio_io);
1695 
1696 err:
1697 	mdb_free(td->tio_io, sizeof (mdb_io_t));
1698 	mdb_free(td, sizeof (termio_data_t));
1699 
1700 	return (NULL);
1701 }
1702 
1703 int
mdb_iob_isatty(mdb_iob_t * iob)1704 mdb_iob_isatty(mdb_iob_t *iob)
1705 {
1706 	mdb_io_t *io;
1707 
1708 	if (iob->iob_flags & MDB_IOB_TTYLIKE)
1709 		return (1);
1710 
1711 	for (io = iob->iob_iop; io != NULL; io = io->io_next) {
1712 		if (io->io_ops == &termio_ops)
1713 			return (1);
1714 	}
1715 
1716 	return (0);
1717 }
1718 
1719 static const char *
termio_insert(termio_data_t * td,int c)1720 termio_insert(termio_data_t *td, int c)
1721 {
1722 	size_t olen = td->tio_cmdbuf.cmd_buflen;
1723 
1724 	if (mdb_cmdbuf_insert(&td->tio_cmdbuf, c) == 0) {
1725 		if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
1726 			termio_addch(td, c, td->tio_cmdbuf.cmd_buflen - olen);
1727 		else
1728 			termio_insch(td, c, td->tio_cmdbuf.cmd_buflen - olen);
1729 	}
1730 
1731 	return (NULL);
1732 }
1733 
1734 static const char *
termio_accept(termio_data_t * td,int c)1735 termio_accept(termio_data_t *td, int c)
1736 {
1737 	if (td->tio_flags & TIO_FINDHIST) {
1738 		(void) mdb_cmdbuf_findhist(&td->tio_cmdbuf, c);
1739 
1740 		td->tio_prompt = mdb.m_prompt;
1741 		td->tio_promptlen = mdb.m_promptlen;
1742 		td->tio_flags &= ~TIO_FINDHIST;
1743 
1744 		termio_redraw(td);
1745 		return (NULL);
1746 	}
1747 
1748 	/* Ensure that the cursor is at the end of the line */
1749 	(void) termio_end(td, c);
1750 
1751 	return (mdb_cmdbuf_accept(&td->tio_cmdbuf));
1752 }
1753 
1754 static const char *
termio_backspace(termio_data_t * td,int c)1755 termio_backspace(termio_data_t *td, int c)
1756 {
1757 	if (mdb_cmdbuf_backspace(&td->tio_cmdbuf, c) == 0) {
1758 		if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
1759 			termio_bspch(td);
1760 		else
1761 			termio_redraw(td);
1762 	}
1763 
1764 	return (NULL);
1765 }
1766 
1767 /*
1768  * This function may end up calling termio_read recursively as part of invoking
1769  * the mdb pager. To work around this fact, we need to go through and make sure
1770  * that we change the underlying terminal settings before and after this
1771  * function call. If we don't do this, we invoke the pager, and don't abort
1772  * (which will longjmp us elsewhere) we're going to return to the read loop with
1773  * the wrong termio settings.
1774  *
1775  * Furthermore, because of the fact that we're being invoked in a user context
1776  * that allows us to be interrupted, we need to actually allocate the memory
1777  * that we're using with GC so that it gets cleaned up in case of the pager
1778  * resetting us and never reaching the end.
1779  */
1780 /*ARGSUSED*/
1781 static const char *
termio_tab(termio_data_t * td,int c)1782 termio_tab(termio_data_t *td, int c)
1783 {
1784 	char *buf;
1785 	const char *result;
1786 	int nres;
1787 	mdb_tab_cookie_t *mtp;
1788 
1789 	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
1790 		warn("failed to restore terminal attributes");
1791 
1792 	buf = mdb_alloc(td->tio_cmdbuf.cmd_bufidx + 1, UM_SLEEP | UM_GC);
1793 	(void) strncpy(buf, td->tio_cmdbuf.cmd_buf, td->tio_cmdbuf.cmd_bufidx);
1794 	buf[td->tio_cmdbuf.cmd_bufidx] = '\0';
1795 	td->tio_flags |= TIO_TAB;
1796 	mtp = mdb_tab_init();
1797 	nres = mdb_tab_command(mtp, buf);
1798 
1799 	if (nres == 0) {
1800 		result = NULL;
1801 	} else {
1802 		result = mdb_tab_match(mtp);
1803 		if (nres != 1) {
1804 			mdb_printf("\n");
1805 			mdb_tab_print(mtp);
1806 		}
1807 	}
1808 
1809 	if (result != NULL) {
1810 		int index = 0;
1811 
1812 		while (result[index] != '\0') {
1813 			(void) termio_insert(td, result[index]);
1814 			index++;
1815 		}
1816 	}
1817 
1818 	termio_redraw(td);
1819 	mdb_tab_fini(mtp);
1820 	td->tio_flags &= ~TIO_TAB;
1821 	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_rtios) == -1)
1822 		warn("failed to set terminal attributes");
1823 
1824 
1825 	return (NULL);
1826 }
1827 
1828 static const char *
termio_delchar(termio_data_t * td,int c)1829 termio_delchar(termio_data_t *td, int c)
1830 {
1831 	if (!(mdb.m_flags & MDB_FL_IGNEOF) &&
1832 	    mdb_cmdbuf_atend(&td->tio_cmdbuf) &&
1833 	    mdb_cmdbuf_atstart(&td->tio_cmdbuf))
1834 		return (termio_quit(td, c));
1835 
1836 	if (mdb_cmdbuf_delchar(&td->tio_cmdbuf, c) == 0) {
1837 		if (mdb_cmdbuf_atend(&td->tio_cmdbuf))
1838 			termio_delch(td);
1839 		else
1840 			termio_redraw(td);
1841 	}
1842 
1843 	return (NULL);
1844 }
1845 
1846 static const char *
termio_fwdchar(termio_data_t * td,int c)1847 termio_fwdchar(termio_data_t *td, int c)
1848 {
1849 	if (mdb_cmdbuf_fwdchar(&td->tio_cmdbuf, c) == 0)
1850 		termio_mvcur(td);
1851 
1852 	return (NULL);
1853 }
1854 
1855 static const char *
termio_backchar(termio_data_t * td,int c)1856 termio_backchar(termio_data_t *td, int c)
1857 {
1858 	if (mdb_cmdbuf_backchar(&td->tio_cmdbuf, c) == 0)
1859 		termio_mvcur(td);
1860 
1861 	return (NULL);
1862 }
1863 
1864 static const char *
termio_transpose(termio_data_t * td,int c)1865 termio_transpose(termio_data_t *td, int c)
1866 {
1867 	if (mdb_cmdbuf_transpose(&td->tio_cmdbuf, c) == 0)
1868 		termio_redraw(td);
1869 
1870 	return (NULL);
1871 }
1872 
1873 static const char *
termio_home(termio_data_t * td,int c)1874 termio_home(termio_data_t *td, int c)
1875 {
1876 	if (mdb_cmdbuf_home(&td->tio_cmdbuf, c) == 0)
1877 		termio_mvcur(td);
1878 
1879 	return (NULL);
1880 }
1881 
1882 static const char *
termio_end(termio_data_t * td,int c)1883 termio_end(termio_data_t *td, int c)
1884 {
1885 	if (mdb_cmdbuf_end(&td->tio_cmdbuf, c) == 0)
1886 		termio_mvcur(td);
1887 
1888 	return (NULL);
1889 }
1890 
1891 static const char *
termio_fwdword(termio_data_t * td,int c)1892 termio_fwdword(termio_data_t *td, int c)
1893 {
1894 	if (mdb_cmdbuf_fwdword(&td->tio_cmdbuf, c) == 0)
1895 		termio_mvcur(td);
1896 
1897 	return (NULL);
1898 }
1899 
1900 static const char *
termio_backword(termio_data_t * td,int c)1901 termio_backword(termio_data_t *td, int c)
1902 {
1903 	if (mdb_cmdbuf_backword(&td->tio_cmdbuf, c) == 0)
1904 		termio_mvcur(td);
1905 
1906 	return (NULL);
1907 }
1908 
1909 static const char *
termio_kill(termio_data_t * td,int c)1910 termio_kill(termio_data_t *td, int c)
1911 {
1912 	if (mdb_cmdbuf_kill(&td->tio_cmdbuf, c) == 0)
1913 		termio_redraw(td);
1914 
1915 	return (NULL);
1916 }
1917 
1918 static const char *
termio_killfwdword(termio_data_t * td,int c)1919 termio_killfwdword(termio_data_t *td, int c)
1920 {
1921 	if (mdb_cmdbuf_killfwdword(&td->tio_cmdbuf, c) == 0)
1922 		termio_redraw(td);
1923 
1924 	return (NULL);
1925 }
1926 
1927 static const char *
termio_killbackword(termio_data_t * td,int c)1928 termio_killbackword(termio_data_t *td, int c)
1929 {
1930 	if (mdb_cmdbuf_killbackword(&td->tio_cmdbuf, c) == 0)
1931 		termio_redraw(td);
1932 
1933 	return (NULL);
1934 }
1935 
1936 static const char *
termio_reset(termio_data_t * td,int c)1937 termio_reset(termio_data_t *td, int c)
1938 {
1939 	if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0)
1940 		termio_clear(td);
1941 
1942 	return (NULL);
1943 }
1944 
1945 /*ARGSUSED*/
1946 static const char *
termio_widescreen(termio_data_t * td,int c)1947 termio_widescreen(termio_data_t *td, int c)
1948 {
1949 	if (td->tio_flags & TIO_XTERM) {
1950 		if (td->tio_cols == 80)
1951 			termio_tput(td, TI_DECSET(TI_DECCOLM), 1);
1952 		else
1953 			termio_tput(td, TI_DECRST(TI_DECCOLM), 1);
1954 		mdb_iob_flush(td->tio_out);
1955 		termio_winch(SIGWINCH, NULL, NULL, td);
1956 	}
1957 
1958 	return (NULL);
1959 }
1960 
1961 static const char *
termio_prevhist(termio_data_t * td,int c)1962 termio_prevhist(termio_data_t *td, int c)
1963 {
1964 	if (mdb_cmdbuf_prevhist(&td->tio_cmdbuf, c) == 0)
1965 		termio_redraw(td);
1966 
1967 	return (NULL);
1968 }
1969 
1970 static const char *
termio_nexthist(termio_data_t * td,int c)1971 termio_nexthist(termio_data_t *td, int c)
1972 {
1973 	if (mdb_cmdbuf_nexthist(&td->tio_cmdbuf, c) == 0)
1974 		termio_redraw(td);
1975 
1976 	return (NULL);
1977 }
1978 
1979 /*
1980  * Single-key accelerator support.  Several commands are so commonly used as to
1981  * require a single-key equivalent.  If we see one of these accelerator
1982  * characters at the beginning of an otherwise-empty line, we'll replace it with
1983  * the expansion.
1984  */
1985 static const char *
termio_accel(termio_data_t * td,int c)1986 termio_accel(termio_data_t *td, int c)
1987 {
1988 	const char *p;
1989 
1990 	if (td->tio_cmdbuf.cmd_buflen != 0 ||
1991 	    (p = termio_accel_lookup(c)) == NULL)
1992 		return (termio_insert(td, c));
1993 
1994 	while (*p != '\0')
1995 		(void) termio_insert(td, *p++);
1996 	return (termio_accept(td, '\n'));
1997 }
1998 
1999 static const char *
termio_findhist(termio_data_t * td,int c)2000 termio_findhist(termio_data_t *td, int c)
2001 {
2002 	if (mdb_cmdbuf_reset(&td->tio_cmdbuf, c) == 0) {
2003 		td->tio_prompt = "Search: ";
2004 		td->tio_promptlen = strlen(td->tio_prompt);
2005 		td->tio_flags |= TIO_FINDHIST;
2006 		termio_redraw(td);
2007 	}
2008 
2009 	return (NULL);
2010 }
2011 
2012 /*ARGSUSED*/
2013 static const char *
termio_refresh(termio_data_t * td,int c)2014 termio_refresh(termio_data_t *td, int c)
2015 {
2016 	if (td->tio_info.ti_clear.at_str) {
2017 		termio_tput(td, td->tio_info.ti_clear.at_str, 1);
2018 		td->tio_x = td->tio_y = 0;
2019 	}
2020 	termio_redraw(td);
2021 	return (NULL);
2022 }
2023 
2024 /*
2025  * Leave the terminal read code by longjmp'ing up the stack of mdb_frame_t's
2026  * back to the main parsing loop (see mdb_run() in mdb.c).
2027  */
2028 static const char *
termio_abort(termio_data_t * td,int c,int err)2029 termio_abort(termio_data_t *td, int c, int err)
2030 {
2031 	(void) mdb_cmdbuf_reset(&td->tio_cmdbuf, c);
2032 	td->tio_active = FALSE;
2033 	td->tio_rti_on = FALSE;
2034 
2035 	if (termio_ctl(td->tio_io, TCSETSW, &td->tio_dtios) == -1)
2036 		warn("failed to restore terminal attributes");
2037 
2038 	longjmp(mdb.m_frame->f_pcb, err);
2039 	/*NOTREACHED*/
2040 	return (NULL);
2041 }
2042 
2043 static const char *
termio_intr(termio_data_t * td,int c)2044 termio_intr(termio_data_t *td, int c)
2045 {
2046 	return (termio_abort(td, c, MDB_ERR_SIGINT));
2047 }
2048 
2049 static const char *
termio_quit(termio_data_t * td,int c)2050 termio_quit(termio_data_t *td, int c)
2051 {
2052 	return (termio_abort(td, c, MDB_ERR_QUIT));
2053 }
2054 
2055 /*ARGSUSED*/
2056 static const char *
termio_susp(termio_data_t * td,int c)2057 termio_susp(termio_data_t *td, int c)
2058 {
2059 	(void) mdb_signal_sethandler(SIGWINCH, MDB_SIG_IGN, NULL);
2060 	(void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL);
2061 
2062 	termio_suspend_tty(td, &td->tio_ptios);
2063 	mdb_iob_nl(td->tio_out);
2064 
2065 	(void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_DFL, NULL);
2066 	(void) mdb_signal_pgrp(SIGTSTP);
2067 
2068 	/*
2069 	 * When we call mdb_signal_pgrp(SIGTSTP), we are expecting the entire
2070 	 * debugger process group to be stopped by the kernel.  Once we return
2071 	 * from that call, we assume we are resuming from a subsequent SIGCONT.
2072 	 */
2073 	(void) mdb_signal_sethandler(SIGTSTP, MDB_SIG_IGN, NULL);
2074 	termio_resume_tty(td, &td->tio_ptios);
2075 
2076 	(void) mdb_signal_sethandler(SIGWINCH, termio_winch, td);
2077 	(void) mdb_signal_sethandler(SIGTSTP, termio_tstp, td);
2078 
2079 	if (td->tio_active)
2080 		siglongjmp(td->tio_env, SIGCONT);
2081 
2082 	return (NULL);
2083 }
2084 
2085 /*ARGSUSED*/
2086 static void
termio_winch(int sig,siginfo_t * sip,ucontext_t * ucp,void * data)2087 termio_winch(int sig, siginfo_t *sip, ucontext_t *ucp, void *data)
2088 {
2089 	termio_data_t *td = data;
2090 	mdb_bool_t change = FALSE;
2091 	struct winsize winsz;
2092 
2093 	if (termio_ctl(td->tio_io, TIOCGWINSZ, &winsz) == -1)
2094 		return; /* just ignore this WINCH if the ioctl fails */
2095 
2096 	if (td->tio_rows != (size_t)winsz.ws_row ||
2097 	    td->tio_cols != (size_t)winsz.ws_col) {
2098 
2099 		if (td->tio_active)
2100 			termio_clear(td);
2101 
2102 		if (winsz.ws_row != 0)
2103 			td->tio_rows = (size_t)winsz.ws_row;
2104 
2105 		if (winsz.ws_col != 0)
2106 			td->tio_cols = (size_t)winsz.ws_col;
2107 
2108 		if (td->tio_active)
2109 			termio_clear(td);
2110 
2111 		mdb_iob_resize(td->tio_out, td->tio_rows, td->tio_cols);
2112 		change = TRUE;
2113 	}
2114 
2115 	if (change && td->tio_active)
2116 		siglongjmp(td->tio_env, sig);
2117 
2118 	if (change && td->tio_link != NULL)
2119 		mdb_iob_resize(td->tio_link, td->tio_rows, td->tio_cols);
2120 }
2121 
2122 /*ARGSUSED*/
2123 static void
termio_tstp(int sig,siginfo_t * sip,ucontext_t * ucp,void * data)2124 termio_tstp(int sig, siginfo_t *sip, ucontext_t *ucp, void *data)
2125 {
2126 	(void) termio_susp(data, CTRL('Z'));
2127 }
2128