mirror of
https://github.com/u-boot/u-boot.git
synced 2024-11-25 13:14:19 +08:00
17b45e684a
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>
210 lines
4.5 KiB
C
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;
|
|
}
|