u-boot/common/cli_getch.c
Simon Glass 17b45e684a cli: Correct several bugs in cli_getch()
This function does not behave as expected when unknown escape sequences
are sent to it:

- it fails to store (and thus echo) the last character of the invalid
  sequence
- it fails to set esc_len to 0 when it finishes emitting the invalid
  sequence, meaning that the following character will appear to be part
  of a new escape sequence
- it processes the first character of the rejected sequence as a valid
  character, just starting the sequence all over again

The last two bugs conspire to produce an "impossible condition #876"
message which is the main symptom of this behaviour.

Fix these bugs and add a test to verify the behaviour.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reported-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
2023-03-28 09:25:51 -04:00

210 lines
4.5 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* (C) Copyright 2000
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* Copyright 2022 Google LLC
*/
#include <common.h>
#include <cli.h>
/**
* enum cli_esc_state_t - indicates what to do with an escape character
*
* @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
* returned from each subsequent call to cli_ch_esc()
* @ESC_SAVE: Character should be saved in esc_save until we have another one
* @ESC_CONVERTED: Escape sequence has been completed and the resulting
* character is available
*/
enum cli_esc_state_t {
ESC_REJECT,
ESC_SAVE,
ESC_CONVERTED
};
void cli_ch_init(struct cli_ch_state *cch)
{
memset(cch, '\0', sizeof(*cch));
}
/**
* cli_ch_esc() - Process a character in an ongoing escape sequence
*
* @cch: State information
* @ichar: Character to process
* @actp: Returns the action to take
* Returns: Output character if *actp is ESC_CONVERTED, else 0
*/
static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
enum cli_esc_state_t *actp)
{
enum cli_esc_state_t act = ESC_REJECT;
switch (cch->esc_len) {
case 1:
if (ichar == '[' || ichar == 'O')
act = ESC_SAVE;
break;
case 2:
switch (ichar) {
case 'D': /* <- key */
ichar = CTL_CH('b');
act = ESC_CONVERTED;
break; /* pass off to ^B handler */
case 'C': /* -> key */
ichar = CTL_CH('f');
act = ESC_CONVERTED;
break; /* pass off to ^F handler */
case 'H': /* Home key */
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass off to ^A handler */
case 'F': /* End key */
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass off to ^E handler */
case 'A': /* up arrow */
ichar = CTL_CH('p');
act = ESC_CONVERTED;
break; /* pass off to ^P handler */
case 'B': /* down arrow */
ichar = CTL_CH('n');
act = ESC_CONVERTED;
break; /* pass off to ^N handler */
case '1':
case '2':
case '3':
case '4':
case '7':
case '8':
if (cch->esc_save[1] == '[') {
/* see if next character is ~ */
act = ESC_SAVE;
}
break;
}
break;
case 3:
switch (ichar) {
case '~':
switch (cch->esc_save[2]) {
case '3': /* Delete key */
ichar = CTL_CH('d');
act = ESC_CONVERTED;
break; /* pass to ^D handler */
case '1': /* Home key */
case '7':
ichar = CTL_CH('a');
act = ESC_CONVERTED;
break; /* pass to ^A handler */
case '4': /* End key */
case '8':
ichar = CTL_CH('e');
act = ESC_CONVERTED;
break; /* pass to ^E handler */
}
break;
case '0':
if (cch->esc_save[2] == '2')
act = ESC_SAVE;
break;
}
break;
case 4:
switch (ichar) {
case '0':
case '1':
act = ESC_SAVE;
break; /* bracketed paste */
}
break;
case 5:
if (ichar == '~') { /* bracketed paste */
ichar = 0;
act = ESC_CONVERTED;
}
}
*actp = act;
return ichar;
}
int cli_ch_process(struct cli_ch_state *cch, int ichar)
{
/*
* ichar=0x0 when error occurs in U-Boot getchar() or when the caller
* wants to check if there are more characters saved in the escape
* sequence
*/
if (!ichar) {
if (cch->emitting) {
if (cch->emit_upto < cch->esc_len)
return cch->esc_save[cch->emit_upto++];
cch->emit_upto = 0;
cch->emitting = false;
cch->esc_len = 0;
}
return 0;
} else if (ichar == -ETIMEDOUT) {
/*
* If we are in an escape sequence but nothing has followed the
* Escape character, then the user probably just pressed the
* Escape key. Return it and clear the sequence.
*/
if (cch->esc_len) {
cch->esc_len = 0;
return '\e';
}
/* Otherwise there is nothing to return */
return 0;
}
if (ichar == '\n' || ichar == '\r')
return '\n';
/* handle standard linux xterm esc sequences for arrow key, etc. */
if (cch->esc_len != 0) {
enum cli_esc_state_t act;
ichar = cli_ch_esc(cch, ichar, &act);
switch (act) {
case ESC_SAVE:
/* save this character and return nothing */
cch->esc_save[cch->esc_len++] = ichar;
ichar = 0;
break;
case ESC_REJECT:
/*
* invalid escape sequence, start returning the
* characters in it
*/
cch->esc_save[cch->esc_len++] = ichar;
ichar = cch->esc_save[cch->emit_upto++];
cch->emitting = true;
return ichar;
case ESC_CONVERTED:
/* valid escape sequence, return the resulting char */
cch->esc_len = 0;
break;
}
}
if (ichar == '\e') {
if (!cch->esc_len) {
cch->esc_save[cch->esc_len] = ichar;
cch->esc_len = 1;
} else {
puts("impossible condition #876\n");
cch->esc_len = 0;
}
return 0;
}
return ichar;
}