freebsd-dev/sys/ddb/db_input.c
Ryan Libby 3e5e9939cd ddb: enable the use of ^C and ^S/^Q
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
2021-03-14 16:04:27 -07:00

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;
}
}
}