3e5e9939cd
This lets one interrupt DDB's output, which is useful if paging is
disabled and the output device is slow.
This follows a previous implementation in svn r311952 / git
5fddef7999
which was reverted because it
broke DDB type-ahead.
Now, try this again, but with a 512-byte type-ahead buffer. While there
is buffer space, control input is handled and non-control input is
buffered. When the buffer is exhausted, the default is to print a
warning and drop further non-control input in order to continue handling
control input. sysctl debug.ddb.prioritize_control_input can be set to
0 to instead preserve all input but lose immediate handling of control
input. This could for example effect pasting of a large script into the
ddb console.
Suggested by: Anton Rang <rang@acm.org>
Reviewed by: markj
Discussed with: imp
Sponsored by: Dell EMC Isilon
Differential Revision: https://reviews.freebsd.org/D28676
471 lines
9.9 KiB
C
471 lines
9.9 KiB
C
/*-
|
|
* SPDX-License-Identifier: MIT-CMU
|
|
*
|
|
* Mach Operating System
|
|
* Copyright (c) 1991,1990 Carnegie Mellon University
|
|
* All Rights Reserved.
|
|
*
|
|
* Permission to use, copy, modify and distribute this software and its
|
|
* documentation is hereby granted, provided that both the copyright
|
|
* notice and this permission notice appear in all copies of the
|
|
* software, derivative works or modified versions, and any portions
|
|
* thereof, and that both notices appear in supporting documentation.
|
|
*
|
|
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
|
|
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
|
|
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
|
|
*
|
|
* Carnegie Mellon requests users of this software to return to
|
|
*
|
|
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
|
|
* School of Computer Science
|
|
* Carnegie Mellon University
|
|
* Pittsburgh PA 15213-3890
|
|
*
|
|
* any improvements or extensions that they make and grant Carnegie the
|
|
* rights to redistribute these changes.
|
|
*/
|
|
/*
|
|
* Author: David B. Golub, Carnegie Mellon University
|
|
* Date: 7/90
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/cons.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <ddb/ddb.h>
|
|
#include <ddb/db_output.h>
|
|
|
|
/*
|
|
* Character input and editing.
|
|
*/
|
|
|
|
/*
|
|
* We don't track output position while editing input,
|
|
* since input always ends with a new-line. We just
|
|
* reset the line position at the end.
|
|
*/
|
|
static char * db_lbuf_start; /* start of input line buffer */
|
|
static char * db_lbuf_end; /* end of input line buffer */
|
|
static char * db_lc; /* current character */
|
|
static char * db_le; /* one past last character */
|
|
|
|
/*
|
|
* Raw input buffer, processed only for certain control characters.
|
|
*/
|
|
#define DB_RAW_SIZE 512
|
|
static char db_raw[DB_RAW_SIZE];
|
|
static u_int db_raw_pos;
|
|
static u_int db_raw_cnt;
|
|
static int db_raw_warned;
|
|
static int ddb_prioritize_control_input = 1;
|
|
SYSCTL_INT(_debug_ddb, OID_AUTO, prioritize_control_input, CTLFLAG_RWTUN,
|
|
&ddb_prioritize_control_input, 0,
|
|
"Drop input when the buffer fills in order to keep servicing ^C/^S/^Q");
|
|
|
|
/*
|
|
* Simple input line history support.
|
|
*/
|
|
static char db_lhistory[2048];
|
|
static int db_lhistlsize, db_lhistidx, db_lhistcur;
|
|
static int db_lhist_nlines;
|
|
|
|
#define CTRL(c) ((c) & 0x1f)
|
|
#define BLANK ' '
|
|
#define BACKUP '\b'
|
|
|
|
static void db_delete(int n, int bwd);
|
|
static int db_inputchar(int c);
|
|
static void db_putnchars(int c, int count);
|
|
static void db_putstring(char *s, int count);
|
|
static int db_raw_pop(void);
|
|
static void db_raw_push(int);
|
|
static int db_raw_space(void);
|
|
|
|
static void
|
|
db_putstring(s, count)
|
|
char *s;
|
|
int count;
|
|
{
|
|
while (--count >= 0)
|
|
cnputc(*s++);
|
|
}
|
|
|
|
static void
|
|
db_putnchars(c, count)
|
|
int c;
|
|
int count;
|
|
{
|
|
while (--count >= 0)
|
|
cnputc(c);
|
|
}
|
|
|
|
/*
|
|
* Delete N characters, forward or backward
|
|
*/
|
|
#define DEL_FWD 0
|
|
#define DEL_BWD 1
|
|
static void
|
|
db_delete(n, bwd)
|
|
int n;
|
|
int bwd;
|
|
{
|
|
char *p;
|
|
|
|
if (bwd) {
|
|
db_lc -= n;
|
|
db_putnchars(BACKUP, n);
|
|
}
|
|
for (p = db_lc; p < db_le-n; p++) {
|
|
*p = *(p+n);
|
|
cnputc(*p);
|
|
}
|
|
db_putnchars(BLANK, n);
|
|
db_putnchars(BACKUP, db_le - db_lc);
|
|
db_le -= n;
|
|
}
|
|
|
|
/* returns true at end-of-line */
|
|
static int
|
|
db_inputchar(c)
|
|
int c;
|
|
{
|
|
static int escstate;
|
|
|
|
if (escstate == 1) {
|
|
/* ESC seen, look for [ or O */
|
|
if (c == '[' || c == 'O')
|
|
escstate++;
|
|
else
|
|
escstate = 0; /* re-init state machine */
|
|
return (0);
|
|
} else if (escstate == 2) {
|
|
escstate = 0;
|
|
/*
|
|
* If a valid cursor key has been found, translate
|
|
* into an emacs-style control key, and fall through.
|
|
* Otherwise, drop off.
|
|
*/
|
|
switch (c) {
|
|
case 'A': /* up */
|
|
c = CTRL('p');
|
|
break;
|
|
case 'B': /* down */
|
|
c = CTRL('n');
|
|
break;
|
|
case 'C': /* right */
|
|
c = CTRL('f');
|
|
break;
|
|
case 'D': /* left */
|
|
c = CTRL('b');
|
|
break;
|
|
default:
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
switch (c) {
|
|
case CTRL('['):
|
|
escstate = 1;
|
|
break;
|
|
case CTRL('b'):
|
|
/* back up one character */
|
|
if (db_lc > db_lbuf_start) {
|
|
cnputc(BACKUP);
|
|
db_lc--;
|
|
}
|
|
break;
|
|
case CTRL('f'):
|
|
/* forward one character */
|
|
if (db_lc < db_le) {
|
|
cnputc(*db_lc);
|
|
db_lc++;
|
|
}
|
|
break;
|
|
case CTRL('a'):
|
|
/* beginning of line */
|
|
while (db_lc > db_lbuf_start) {
|
|
cnputc(BACKUP);
|
|
db_lc--;
|
|
}
|
|
break;
|
|
case CTRL('e'):
|
|
/* end of line */
|
|
while (db_lc < db_le) {
|
|
cnputc(*db_lc);
|
|
db_lc++;
|
|
}
|
|
break;
|
|
case CTRL('h'):
|
|
case 0177:
|
|
/* erase previous character */
|
|
if (db_lc > db_lbuf_start)
|
|
db_delete(1, DEL_BWD);
|
|
break;
|
|
case CTRL('d'):
|
|
/* erase next character */
|
|
if (db_lc < db_le)
|
|
db_delete(1, DEL_FWD);
|
|
break;
|
|
case CTRL('u'):
|
|
case CTRL('c'):
|
|
/* kill entire line: */
|
|
/* at first, delete to beginning of line */
|
|
if (db_lc > db_lbuf_start)
|
|
db_delete(db_lc - db_lbuf_start, DEL_BWD);
|
|
/* FALLTHROUGH */
|
|
case CTRL('k'):
|
|
/* delete to end of line */
|
|
if (db_lc < db_le)
|
|
db_delete(db_le - db_lc, DEL_FWD);
|
|
break;
|
|
case CTRL('t'):
|
|
/* twiddle last 2 characters */
|
|
if (db_lc >= db_lbuf_start + 2) {
|
|
c = db_lc[-2];
|
|
db_lc[-2] = db_lc[-1];
|
|
db_lc[-1] = c;
|
|
cnputc(BACKUP);
|
|
cnputc(BACKUP);
|
|
cnputc(db_lc[-2]);
|
|
cnputc(db_lc[-1]);
|
|
}
|
|
break;
|
|
case CTRL('w'):
|
|
/* erase previous word */
|
|
for (; db_lc > db_lbuf_start;) {
|
|
if (*(db_lc - 1) != ' ')
|
|
break;
|
|
db_delete(1, DEL_BWD);
|
|
}
|
|
for (; db_lc > db_lbuf_start;) {
|
|
if (*(db_lc - 1) == ' ')
|
|
break;
|
|
db_delete(1, DEL_BWD);
|
|
}
|
|
break;
|
|
case CTRL('r'):
|
|
db_putstring("^R\n", 3);
|
|
redraw:
|
|
if (db_le > db_lbuf_start) {
|
|
db_putstring(db_lbuf_start, db_le - db_lbuf_start);
|
|
db_putnchars(BACKUP, db_le - db_lc);
|
|
}
|
|
break;
|
|
case CTRL('p'):
|
|
/* Make previous history line the active one. */
|
|
if (db_lhistcur >= 0) {
|
|
bcopy(db_lhistory + db_lhistcur * db_lhistlsize,
|
|
db_lbuf_start, db_lhistlsize);
|
|
db_lhistcur--;
|
|
goto hist_redraw;
|
|
}
|
|
break;
|
|
case CTRL('n'):
|
|
/* Make next history line the active one. */
|
|
if (db_lhistcur < db_lhistidx - 1) {
|
|
db_lhistcur += 2;
|
|
bcopy(db_lhistory + db_lhistcur * db_lhistlsize,
|
|
db_lbuf_start, db_lhistlsize);
|
|
} else {
|
|
/*
|
|
* ^N through tail of history, reset the
|
|
* buffer to zero length.
|
|
*/
|
|
*db_lbuf_start = '\0';
|
|
db_lhistcur = db_lhistidx;
|
|
}
|
|
|
|
hist_redraw:
|
|
db_putnchars(BACKUP, db_lc - db_lbuf_start);
|
|
db_putnchars(BLANK, db_le - db_lbuf_start);
|
|
db_putnchars(BACKUP, db_le - db_lbuf_start);
|
|
db_le = strchr(db_lbuf_start, '\0');
|
|
if (db_le[-1] == '\r' || db_le[-1] == '\n')
|
|
*--db_le = '\0';
|
|
db_lc = db_le;
|
|
goto redraw;
|
|
|
|
case -1:
|
|
/*
|
|
* eek! the console returned eof.
|
|
* probably that means we HAVE no console.. we should try bail
|
|
* XXX
|
|
*/
|
|
c = '\r';
|
|
case '\n':
|
|
/* FALLTHROUGH */
|
|
case '\r':
|
|
*db_le++ = c;
|
|
return (1);
|
|
default:
|
|
if (db_le == db_lbuf_end) {
|
|
cnputc('\007');
|
|
}
|
|
else if (c >= ' ' && c <= '~') {
|
|
char *p;
|
|
|
|
for (p = db_le; p > db_lc; p--)
|
|
*p = *(p-1);
|
|
*db_lc++ = c;
|
|
db_le++;
|
|
cnputc(c);
|
|
db_putstring(db_lc, db_le - db_lc);
|
|
db_putnchars(BACKUP, db_le - db_lc);
|
|
}
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Get a character from the console, first checking the raw input buffer. */
|
|
int
|
|
db_getc(void)
|
|
{
|
|
int c;
|
|
|
|
if (db_raw_cnt == 0) {
|
|
c = cngetc();
|
|
} else {
|
|
c = db_raw_pop();
|
|
if (c == '\r')
|
|
c = '\n';
|
|
}
|
|
return (c);
|
|
}
|
|
|
|
/* Whether the raw input buffer has space to accept another character. */
|
|
static int
|
|
db_raw_space(void)
|
|
{
|
|
|
|
return (db_raw_cnt < DB_RAW_SIZE);
|
|
}
|
|
|
|
/* Un-get a character from the console by buffering it. */
|
|
static void
|
|
db_raw_push(int c)
|
|
{
|
|
|
|
if (!db_raw_space())
|
|
db_error(NULL);
|
|
db_raw[(db_raw_pos + db_raw_cnt++) % DB_RAW_SIZE] = c;
|
|
}
|
|
|
|
/* Drain a character from the raw input buffer. */
|
|
static int
|
|
db_raw_pop(void)
|
|
{
|
|
|
|
if (db_raw_cnt == 0)
|
|
return (-1);
|
|
db_raw_cnt--;
|
|
db_raw_warned = 0;
|
|
return (db_raw[db_raw_pos++ % DB_RAW_SIZE]);
|
|
}
|
|
|
|
int
|
|
db_readline(lstart, lsize)
|
|
char * lstart;
|
|
int lsize;
|
|
{
|
|
|
|
if (lsize < 2)
|
|
return (0);
|
|
if (lsize != db_lhistlsize) {
|
|
/*
|
|
* (Re)initialize input line history. Throw away any
|
|
* existing history.
|
|
*/
|
|
db_lhist_nlines = sizeof(db_lhistory) / lsize;
|
|
db_lhistlsize = lsize;
|
|
db_lhistidx = -1;
|
|
}
|
|
db_lhistcur = db_lhistidx;
|
|
|
|
db_force_whitespace(); /* synch output position */
|
|
|
|
db_lbuf_start = lstart;
|
|
db_lbuf_end = lstart + lsize - 2; /* Will append NL and NUL. */
|
|
db_lc = lstart;
|
|
db_le = lstart;
|
|
|
|
while (!db_inputchar(db_getc()))
|
|
continue;
|
|
|
|
db_capture_write(lstart, db_le - db_lbuf_start);
|
|
db_printf("\n"); /* synch output position */
|
|
*db_le = 0;
|
|
|
|
if (db_le - db_lbuf_start > 1) {
|
|
/* Maintain input line history for non-empty lines. */
|
|
if (++db_lhistidx == db_lhist_nlines) {
|
|
/* Rotate history. */
|
|
bcopy(db_lhistory + db_lhistlsize, db_lhistory,
|
|
db_lhistlsize * (db_lhist_nlines - 1));
|
|
db_lhistidx--;
|
|
}
|
|
bcopy(lstart, db_lhistory + db_lhistidx * db_lhistlsize,
|
|
db_lhistlsize);
|
|
}
|
|
|
|
return (db_le - db_lbuf_start);
|
|
}
|
|
|
|
static void
|
|
db_do_interrupt(const char *reason)
|
|
{
|
|
|
|
/* Do a pager quit too because some commands have jmpbuf handling. */
|
|
db_disable_pager();
|
|
db_pager_quit = 1;
|
|
db_error(reason);
|
|
}
|
|
|
|
void
|
|
db_check_interrupt(void)
|
|
{
|
|
int c;
|
|
|
|
/*
|
|
* Check console input for control characters. Non-control input is
|
|
* buffered. When buffer space is exhausted, either stop responding to
|
|
* control input or drop further non-control input on the floor.
|
|
*/
|
|
for (;;) {
|
|
if (!ddb_prioritize_control_input && !db_raw_space())
|
|
return;
|
|
c = cncheckc();
|
|
switch (c) {
|
|
case -1: /* no character */
|
|
return;
|
|
|
|
case CTRL('c'):
|
|
db_do_interrupt("^C");
|
|
/*NOTREACHED*/
|
|
|
|
case CTRL('s'):
|
|
do {
|
|
c = cncheckc();
|
|
if (c == CTRL('c'))
|
|
db_do_interrupt("^C");
|
|
} while (c != CTRL('q'));
|
|
break;
|
|
|
|
default:
|
|
if (db_raw_space()) {
|
|
db_raw_push(c);
|
|
} else if (!db_raw_warned) {
|
|
db_raw_warned = 1;
|
|
db_printf("\n--Exceeded input buffer--\n");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|