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