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 [email protected]
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
db_putstring(char * s,int count)89 db_putstring(char *s, int count)
90 {
91 while (--count >= 0)
92 cnputc(*s++);
93 }
94
95 static void
db_putnchars(int c,int count)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
db_delete(int n,int bwd)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
db_inputchar(int c)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
db_getc(void)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
db_raw_space(void)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
db_raw_push(int c)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
db_raw_pop(void)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
db_readline(char * lstart,int lsize)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
db_do_interrupt(const char * reason)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
db_check_interrupt(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