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