xref: /freebsd/sys/ddb/db_input.c (revision 1843dfb05ed80149f5a412180af882e3cb8f451b)
1 /*-
2  * SPDX-License-Identifier: MIT-CMU
3  *
4  * Mach Operating System
5  * Copyright (c) 1991,1990 Carnegie Mellon University
6  * All Rights Reserved.
7  *
8  * Permission to use, copy, modify and distribute this software and its
9  * documentation is hereby granted, provided that both the copyright
10  * notice and this permission notice appear in all copies of the
11  * software, derivative works or modified versions, and any portions
12  * thereof, and that both notices appear in supporting documentation.
13  *
14  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
15  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
16  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
17  *
18  * Carnegie Mellon requests users of this software to return to
19  *
20  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
21  *  School of Computer Science
22  *  Carnegie Mellon University
23  *  Pittsburgh PA 15213-3890
24  *
25  * any improvements or extensions that they make and grant Carnegie the
26  * rights to redistribute these changes.
27  */
28 /*
29  *	Author: David B. Golub, Carnegie Mellon University
30  *	Date:	7/90
31  */
32 
33 #include <sys/cdefs.h>
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/cons.h>
37 #include <sys/sysctl.h>
38 
39 #include <ddb/ddb.h>
40 #include <ddb/db_output.h>
41 
42 /*
43  * Character input and editing.
44  */
45 
46 /*
47  * We don't track output position while editing input,
48  * since input always ends with a new-line.  We just
49  * reset the line position at the end.
50  */
51 static char *	db_lbuf_start;	/* start of input line buffer */
52 static char *	db_lbuf_end;	/* end of input line buffer */
53 static char *	db_lc;		/* current character */
54 static char *	db_le;		/* one past last character */
55 
56 /*
57  * Raw input buffer, processed only for certain control characters.
58  */
59 #define	DB_RAW_SIZE	512
60 static char	db_raw[DB_RAW_SIZE];
61 static u_int	db_raw_pos;
62 static u_int	db_raw_cnt;
63 static int	db_raw_warned;
64 static int	ddb_prioritize_control_input = 1;
65 SYSCTL_INT(_debug_ddb, OID_AUTO, prioritize_control_input, CTLFLAG_RWTUN,
66     &ddb_prioritize_control_input, 0,
67     "Drop input when the buffer fills in order to keep servicing ^C/^S/^Q");
68 
69 /*
70  * Simple input line history support.
71  */
72 static char	db_lhistory[2048];
73 static int	db_lhistlsize, db_lhistidx, db_lhistcur;
74 static int	db_lhist_nlines;
75 
76 #define	CTRL(c)		((c) & 0x1f)
77 #define	BLANK		' '
78 #define	BACKUP		'\b'
79 
80 static void	db_delete(int n, int bwd);
81 static int	db_inputchar(int c);
82 static void	db_putnchars(int c, int count);
83 static void	db_putstring(char *s, int count);
84 static int	db_raw_pop(void);
85 static void	db_raw_push(int);
86 static int	db_raw_space(void);
87 
88 static void
89 db_putstring(char *s, int count)
90 {
91 	while (--count >= 0)
92 	    cnputc(*s++);
93 }
94 
95 static void
96 db_putnchars(int c, int count)
97 {
98 	while (--count >= 0)
99 	    cnputc(c);
100 }
101 
102 /*
103  * Delete N characters, forward or backward
104  */
105 #define	DEL_FWD		0
106 #define	DEL_BWD		1
107 static void
108 db_delete(int n, int bwd)
109 {
110 	char *p;
111 
112 	if (bwd) {
113 	    db_lc -= n;
114 	    db_putnchars(BACKUP, n);
115 	}
116 	for (p = db_lc; p < db_le-n; p++) {
117 	    *p = *(p+n);
118 	    cnputc(*p);
119 	}
120 	db_putnchars(BLANK, n);
121 	db_putnchars(BACKUP, db_le - db_lc);
122 	db_le -= n;
123 }
124 
125 /* returns true at end-of-line */
126 static int
127 db_inputchar(int c)
128 {
129 	static int escstate;
130 
131 	if (escstate == 1) {
132 		/* ESC seen, look for [ or O */
133 		if (c == '[' || c == 'O')
134 			escstate++;
135 		else
136 			escstate = 0; /* re-init state machine */
137 		return (0);
138 	} else if (escstate == 2) {
139 		escstate = 0;
140 		/*
141 		 * If a valid cursor key has been found, translate
142 		 * into an emacs-style control key, and fall through.
143 		 * Otherwise, drop off.
144 		 */
145 		switch (c) {
146 		case 'A':	/* up */
147 			c = CTRL('p');
148 			break;
149 		case 'B':	/* down */
150 			c = CTRL('n');
151 			break;
152 		case 'C':	/* right */
153 			c = CTRL('f');
154 			break;
155 		case 'D':	/* left */
156 			c = CTRL('b');
157 			break;
158 		default:
159 			return (0);
160 		}
161 	}
162 
163 	switch (c) {
164 	    case CTRL('['):
165 		escstate = 1;
166 		break;
167 	    case CTRL('b'):
168 		/* back up one character */
169 		if (db_lc > db_lbuf_start) {
170 		    cnputc(BACKUP);
171 		    db_lc--;
172 		}
173 		break;
174 	    case CTRL('f'):
175 		/* forward one character */
176 		if (db_lc < db_le) {
177 		    cnputc(*db_lc);
178 		    db_lc++;
179 		}
180 		break;
181 	    case CTRL('a'):
182 		/* beginning of line */
183 		while (db_lc > db_lbuf_start) {
184 		    cnputc(BACKUP);
185 		    db_lc--;
186 		}
187 		break;
188 	    case CTRL('e'):
189 		/* end of line */
190 		while (db_lc < db_le) {
191 		    cnputc(*db_lc);
192 		    db_lc++;
193 		}
194 		break;
195 	    case CTRL('h'):
196 	    case 0177:
197 		/* erase previous character */
198 		if (db_lc > db_lbuf_start)
199 		    db_delete(1, DEL_BWD);
200 		break;
201 	    case CTRL('d'):
202 		/* erase next character */
203 		if (db_lc < db_le)
204 		    db_delete(1, DEL_FWD);
205 		break;
206 	    case CTRL('u'):
207 	    case CTRL('c'):
208 		/* kill entire line: */
209 		/* at first, delete to beginning of line */
210 		if (db_lc > db_lbuf_start)
211 		    db_delete(db_lc - db_lbuf_start, DEL_BWD);
212 		/* FALLTHROUGH */
213 	    case CTRL('k'):
214 		/* delete to end of line */
215 		if (db_lc < db_le)
216 		    db_delete(db_le - db_lc, DEL_FWD);
217 		break;
218 	    case CTRL('t'):
219 		/* twiddle last 2 characters */
220 		if (db_lc >= db_lbuf_start + 2) {
221 		    c = db_lc[-2];
222 		    db_lc[-2] = db_lc[-1];
223 		    db_lc[-1] = c;
224 		    cnputc(BACKUP);
225 		    cnputc(BACKUP);
226 		    cnputc(db_lc[-2]);
227 		    cnputc(db_lc[-1]);
228 		}
229 		break;
230 	    case CTRL('w'):
231 		/* erase previous word */
232 		for (; db_lc > db_lbuf_start;) {
233 		    if (*(db_lc - 1) != ' ')
234 			break;
235 		    db_delete(1, DEL_BWD);
236 		}
237 		for (; db_lc > db_lbuf_start;) {
238 		    if (*(db_lc - 1) == ' ')
239 			break;
240 		    db_delete(1, DEL_BWD);
241 		}
242 		break;
243 	    case CTRL('r'):
244 		db_putstring("^R\n", 3);
245 	    redraw:
246 		if (db_le > db_lbuf_start) {
247 		    db_putstring(db_lbuf_start, db_le - db_lbuf_start);
248 		    db_putnchars(BACKUP, db_le - db_lc);
249 		}
250 		break;
251 	    case CTRL('p'):
252 		/* Make previous history line the active one. */
253 		if (db_lhistcur >= 0) {
254 		    bcopy(db_lhistory + db_lhistcur * db_lhistlsize,
255 			  db_lbuf_start, db_lhistlsize);
256 		    db_lhistcur--;
257 		    goto hist_redraw;
258 		}
259 		break;
260 	    case CTRL('n'):
261 		/* Make next history line the active one. */
262 		if (db_lhistcur < db_lhistidx - 1) {
263 		    db_lhistcur += 2;
264 		    bcopy(db_lhistory + db_lhistcur * db_lhistlsize,
265 			  db_lbuf_start, db_lhistlsize);
266 		} else {
267 		    /*
268 		     * ^N through tail of history, reset the
269 		     * buffer to zero length.
270 		     */
271 		    *db_lbuf_start = '\0';
272 		    db_lhistcur = db_lhistidx;
273 		}
274 
275 	    hist_redraw:
276 		db_putnchars(BACKUP, db_lc - db_lbuf_start);
277 		db_putnchars(BLANK, db_le - db_lbuf_start);
278 		db_putnchars(BACKUP, db_le - db_lbuf_start);
279 		db_le = strchr(db_lbuf_start, '\0');
280 		if (db_le[-1] == '\r' || db_le[-1] == '\n')
281 		    *--db_le = '\0';
282 		db_lc = db_le;
283 		goto redraw;
284 
285 	    case -1:
286 		/*
287 		 * eek! the console returned eof.
288 		 * probably that means we HAVE no console.. we should try bail
289 		 * XXX
290 		 */
291 		c = '\r';
292 	    case '\n':
293 		/* FALLTHROUGH */
294 	    case '\r':
295 		*db_le++ = c;
296 		return (1);
297 	    default:
298 		if (db_le == db_lbuf_end) {
299 		    cnputc('\007');
300 		}
301 		else if (c >= ' ' && c <= '~') {
302 		    char *p;
303 
304 		    for (p = db_le; p > db_lc; p--)
305 			*p = *(p-1);
306 		    *db_lc++ = c;
307 		    db_le++;
308 		    cnputc(c);
309 		    db_putstring(db_lc, db_le - db_lc);
310 		    db_putnchars(BACKUP, db_le - db_lc);
311 		}
312 		break;
313 	}
314 	return (0);
315 }
316 
317 /* Get a character from the console, first checking the raw input buffer. */
318 int
319 db_getc(void)
320 {
321 	int c;
322 
323 	if (db_raw_cnt == 0) {
324 		c = cngetc();
325 	} else {
326 		c = db_raw_pop();
327 		if (c == '\r')
328 			c = '\n';
329 	}
330 	return (c);
331 }
332 
333 /* Whether the raw input buffer has space to accept another character. */
334 static int
335 db_raw_space(void)
336 {
337 
338 	return (db_raw_cnt < DB_RAW_SIZE);
339 }
340 
341 /* Un-get a character from the console by buffering it. */
342 static void
343 db_raw_push(int c)
344 {
345 
346 	if (!db_raw_space())
347 		db_error(NULL);
348 	db_raw[(db_raw_pos + db_raw_cnt++) % DB_RAW_SIZE] = c;
349 }
350 
351 /* Drain a character from the raw input buffer. */
352 static int
353 db_raw_pop(void)
354 {
355 
356 	if (db_raw_cnt == 0)
357 		return (-1);
358 	db_raw_cnt--;
359 	db_raw_warned = 0;
360 	return (db_raw[db_raw_pos++ % DB_RAW_SIZE]);
361 }
362 
363 int
364 db_readline(char *lstart, int lsize)
365 {
366 
367 	if (lsize < 2)
368 		return (0);
369 	if (lsize != db_lhistlsize) {
370 		/*
371 		 * (Re)initialize input line history.  Throw away any
372 		 * existing history.
373 		 */
374 		db_lhist_nlines = sizeof(db_lhistory) / lsize;
375 		db_lhistlsize = lsize;
376 		db_lhistidx = -1;
377 	}
378 	db_lhistcur = db_lhistidx;
379 
380 	db_force_whitespace();	/* synch output position */
381 
382 	db_lbuf_start = lstart;
383 	db_lbuf_end   = lstart + lsize - 2;	/* Will append NL and NUL. */
384 	db_lc = lstart;
385 	db_le = lstart;
386 
387 	while (!db_inputchar(db_getc()))
388 	    continue;
389 
390 	db_capture_write(lstart, db_le - db_lbuf_start);
391 	db_printf("\n");	/* synch output position */
392 	*db_le = 0;
393 
394 	if (db_le - db_lbuf_start > 1) {
395 	    /* Maintain input line history for non-empty lines. */
396 	    if (++db_lhistidx == db_lhist_nlines) {
397 		/* Rotate history. */
398 		bcopy(db_lhistory + db_lhistlsize, db_lhistory,
399 		      db_lhistlsize * (db_lhist_nlines - 1));
400 		db_lhistidx--;
401 	    }
402 	    bcopy(lstart, db_lhistory + db_lhistidx * db_lhistlsize,
403 		  db_lhistlsize);
404 	}
405 
406 	return (db_le - db_lbuf_start);
407 }
408 
409 static void
410 db_do_interrupt(const char *reason)
411 {
412 
413 	/* Do a pager quit too because some commands have jmpbuf handling. */
414 	db_disable_pager();
415 	db_pager_quit = 1;
416 	db_error(reason);
417 }
418 
419 void
420 db_check_interrupt(void)
421 {
422 	int	c;
423 
424 	/*
425 	 * Check console input for control characters.  Non-control input is
426 	 * buffered.  When buffer space is exhausted, either stop responding to
427 	 * control input or drop further non-control input on the floor.
428 	 */
429 	for (;;) {
430 		if (!ddb_prioritize_control_input && !db_raw_space())
431 			return;
432 		c = cncheckc();
433 		switch (c) {
434 		case -1:		/* no character */
435 			return;
436 
437 		case CTRL('c'):
438 			db_do_interrupt("^C");
439 			/*NOTREACHED*/
440 
441 		case CTRL('s'):
442 			do {
443 				c = cncheckc();
444 				if (c == CTRL('c'))
445 					db_do_interrupt("^C");
446 			} while (c != CTRL('q'));
447 			break;
448 
449 		default:
450 			if (db_raw_space()) {
451 				db_raw_push(c);
452 			} else if (!db_raw_warned) {
453 				db_raw_warned = 1;
454 				db_printf("\n--Exceeded input buffer--\n");
455 			}
456 			break;
457 		}
458 	}
459 }
460