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