lineedit: use read_key to recognize ESC sequence.

This fixes several vi mode bugs and prepares for further fixes.

function                                             old     new   delta
read_line_input                                     3287    5511   +2224
remember_in_history                                    -     499    +499
lineedit_read_key                                      -      70     +70
read_key                                             321     332     +11
input_tab                                           2823       -   -2823
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 2/0 up/down: 2804/-2823)        Total: -19 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/include/libbb.h b/include/libbb.h
index 3a94a00..128aa92 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1198,23 +1198,23 @@
 
 #if ENABLE_FEATURE_EDITING
 /* It's NOT just ENABLEd or disabled. It's a number: */
-#ifdef CONFIG_FEATURE_EDITING_HISTORY
-# define MAX_HISTORY (CONFIG_FEATURE_EDITING_HISTORY + 0)
-#else
-# define MAX_HISTORY 0
-#endif
+# ifdef CONFIG_FEATURE_EDITING_HISTORY
+#  define MAX_HISTORY (CONFIG_FEATURE_EDITING_HISTORY + 0)
+# else
+#  define MAX_HISTORY 0
+# endif
 typedef struct line_input_t {
 	int flags;
 	const char *path_lookup;
-#if MAX_HISTORY
+# if MAX_HISTORY
 	int cnt_history;
 	int cur_history;
-#if ENABLE_FEATURE_EDITING_SAVEHISTORY
+#  if ENABLE_FEATURE_EDITING_SAVEHISTORY
 	unsigned cnt_history_in_file;
 	const char *hist_file;
-#endif
+#  endif
 	char *history[MAX_HISTORY + 1];
-#endif
+# endif
 } line_input_t;
 enum {
 	DO_HISTORY = 1 * (MAX_HISTORY > 0),
@@ -1241,12 +1241,12 @@
 
 
 #ifndef COMM_LEN
-#ifdef TASK_COMM_LEN
+# ifdef TASK_COMM_LEN
 enum { COMM_LEN = TASK_COMM_LEN };
-#else
+# else
 /* synchronize with sizeof(task_struct.comm) in /usr/include/linux/sched.h */
 enum { COMM_LEN = 16 };
-#endif
+# endif
 #endif
 typedef struct procps_status_t {
 	DIR *dir;
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index e0ab732..ccf3e0d 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1430,6 +1430,22 @@
 		signal(SIGWINCH, win_changed); /* rearm ourself */
 }
 
+static int lineedit_read_key(smalluint *read_key_bufsize, char *read_key_buffer)
+{
+	int ic;
+	struct pollfd pfd;
+	pfd.fd = STDIN_FILENO;
+	pfd.events = POLLIN;
+	do {
+		/* Wait for input. Can't just call read_key, it will return
+		 * at once if stdin is in non-blocking mode. */
+		safe_poll(&pfd, 1, -1);
+		/* note: read_key sets errno to 0 on success: */
+		ic = read_key(STDIN_FILENO, read_key_bufsize, read_key_buffer);
+	} while (errno == EAGAIN);
+	return ic;
+}
+
 /*
  * The emacs and vi modes share much of the code in the big
  * command loop.  Commands entered when in vi's command mode (aka
@@ -1438,7 +1454,7 @@
  * big switch a bit, but keeps all the code in one place.
  */
 
-#define vbit 0x100
+#define VI_CMDMODE_BIT 0x100
 
 /* leave out the "vi-mode"-only case labels if vi editing isn't
  * configured. */
@@ -1459,15 +1475,15 @@
 #if ENABLE_FEATURE_TAB_COMPLETION
 	smallint lastWasTab = FALSE;
 #endif
-	unsigned ic;
-	unsigned char c;
+	int ic;
 	smallint break_out = 0;
 #if ENABLE_FEATURE_EDITING_VI
 	smallint vi_cmdmode = 0;
-	smalluint prevc;
 #endif
 	struct termios initial_settings;
 	struct termios new_settings;
+	smalluint read_key_bufsize;
+	char read_key_buffer[KEYCODE_BUFFER_SIZE];
 
 	INIT_S();
 
@@ -1545,42 +1561,40 @@
 
 	while (1) {
 		fflush(NULL);
-
-		if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) {
-			/* if we can't read input then exit */
-			goto prepare_to_die;
-		}
-
-		ic = c;
+		ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
 
 #if ENABLE_FEATURE_EDITING_VI
 		newdelflag = 1;
-		if (vi_cmdmode)
-			ic |= vbit;
+		if (vi_cmdmode) {
+			/* btw, since KEYCODE_xxx are all < 0, this doesn't
+			 * change ic if it contains one of them: */
+			ic |= VI_CMDMODE_BIT;
+		}
 #endif
+
 		switch (ic) {
 		case '\n':
 		case '\r':
-		vi_case('\n'|vbit:)
-		vi_case('\r'|vbit:)
+		vi_case('\n'|VI_CMDMODE_BIT:)
+		vi_case('\r'|VI_CMDMODE_BIT:)
 			/* Enter */
 			goto_new_line();
 			break_out = 1;
 			break;
 		case CTRL('A'):
-		vi_case('0'|vbit:)
+		vi_case('0'|VI_CMDMODE_BIT:)
 			/* Control-a -- Beginning of line */
 			input_backward(cursor);
 			break;
 		case CTRL('B'):
-		vi_case('h'|vbit:)
-		vi_case('\b'|vbit:)
-		vi_case('\x7f'|vbit:) /* DEL */
+		vi_case('h'|VI_CMDMODE_BIT:)
+		vi_case('\b'|VI_CMDMODE_BIT:)
+		vi_case('\x7f'|VI_CMDMODE_BIT:) /* DEL */
 			/* Control-b -- Move back one character */
 			input_backward(1);
 			break;
 		case CTRL('C'):
-		vi_case(CTRL('C')|vbit:)
+		vi_case(CTRL('C')|VI_CMDMODE_BIT:)
 			/* Control-c -- stop gathering input */
 			goto_new_line();
 			command_len = 0;
@@ -1598,31 +1612,27 @@
 			}
 			input_delete(0);
 			break;
-
 		case CTRL('E'):
-		vi_case('$'|vbit:)
+		vi_case('$'|VI_CMDMODE_BIT:)
 			/* Control-e -- End of line */
 			input_end();
 			break;
 		case CTRL('F'):
-		vi_case('l'|vbit:)
-		vi_case(' '|vbit:)
+		vi_case('l'|VI_CMDMODE_BIT:)
+		vi_case(' '|VI_CMDMODE_BIT:)
 			/* Control-f -- Move forward one character */
 			input_forward();
 			break;
-
 		case '\b':
 		case '\x7f': /* DEL */
 			/* Control-h and DEL */
 			input_backspace();
 			break;
-
 #if ENABLE_FEATURE_TAB_COMPLETION
 		case '\t':
 			input_tab(&lastWasTab);
 			break;
 #endif
-
 		case CTRL('K'):
 			/* Control-k -- clear to end of line */
 			command[cursor] = 0;
@@ -1630,31 +1640,29 @@
 			printf("\033[J");
 			break;
 		case CTRL('L'):
-		vi_case(CTRL('L')|vbit:)
+		vi_case(CTRL('L')|VI_CMDMODE_BIT:)
 			/* Control-l -- clear screen */
 			printf("\033[H");
 			redraw(0, command_len - cursor);
 			break;
-
 #if MAX_HISTORY > 0
 		case CTRL('N'):
-		vi_case(CTRL('N')|vbit:)
-		vi_case('j'|vbit:)
+		vi_case(CTRL('N')|VI_CMDMODE_BIT:)
+		vi_case('j'|VI_CMDMODE_BIT:)
 			/* Control-n -- Get next command in history */
 			if (get_next_history())
 				goto rewrite_line;
 			break;
 		case CTRL('P'):
-		vi_case(CTRL('P')|vbit:)
-		vi_case('k'|vbit:)
+		vi_case(CTRL('P')|VI_CMDMODE_BIT:)
+		vi_case('k'|VI_CMDMODE_BIT:)
 			/* Control-p -- Get previous command from history */
 			if (get_previous_history())
 				goto rewrite_line;
 			break;
 #endif
-
 		case CTRL('U'):
-		vi_case(CTRL('U')|vbit:)
+		vi_case(CTRL('U')|VI_CMDMODE_BIT:)
 			/* Control-U -- Clear line before cursor */
 			if (cursor) {
 				overlapping_strcpy(command, command + cursor);
@@ -1663,7 +1671,7 @@
 			}
 			break;
 		case CTRL('W'):
-		vi_case(CTRL('W')|vbit:)
+		vi_case(CTRL('W')|VI_CMDMODE_BIT:)
 			/* Control-W -- Remove the last word */
 			while (cursor > 0 && isspace(command[cursor-1]))
 				input_backspace();
@@ -1672,75 +1680,80 @@
 			break;
 
 #if ENABLE_FEATURE_EDITING_VI
-		case 'i'|vbit:
+		case 'i'|VI_CMDMODE_BIT:
 			vi_cmdmode = 0;
 			break;
-		case 'I'|vbit:
+		case 'I'|VI_CMDMODE_BIT:
 			input_backward(cursor);
 			vi_cmdmode = 0;
 			break;
-		case 'a'|vbit:
+		case 'a'|VI_CMDMODE_BIT:
 			input_forward();
 			vi_cmdmode = 0;
 			break;
-		case 'A'|vbit:
+		case 'A'|VI_CMDMODE_BIT:
 			input_end();
 			vi_cmdmode = 0;
 			break;
-		case 'x'|vbit:
+		case 'x'|VI_CMDMODE_BIT:
 			input_delete(1);
 			break;
-		case 'X'|vbit:
+		case 'X'|VI_CMDMODE_BIT:
 			if (cursor > 0) {
 				input_backward(1);
 				input_delete(1);
 			}
 			break;
-		case 'W'|vbit:
+		case 'W'|VI_CMDMODE_BIT:
 			vi_Word_motion(command, 1);
 			break;
-		case 'w'|vbit:
+		case 'w'|VI_CMDMODE_BIT:
 			vi_word_motion(command, 1);
 			break;
-		case 'E'|vbit:
+		case 'E'|VI_CMDMODE_BIT:
 			vi_End_motion(command);
 			break;
-		case 'e'|vbit:
+		case 'e'|VI_CMDMODE_BIT:
 			vi_end_motion(command);
 			break;
-		case 'B'|vbit:
+		case 'B'|VI_CMDMODE_BIT:
 			vi_Back_motion(command);
 			break;
-		case 'b'|vbit:
+		case 'b'|VI_CMDMODE_BIT:
 			vi_back_motion(command);
 			break;
-		case 'C'|vbit:
+		case 'C'|VI_CMDMODE_BIT:
 			vi_cmdmode = 0;
 			/* fall through */
-		case 'D'|vbit:
+		case 'D'|VI_CMDMODE_BIT:
 			goto clear_to_eol;
 
-		case 'c'|vbit:
+		case 'c'|VI_CMDMODE_BIT:
 			vi_cmdmode = 0;
 			/* fall through */
-		case 'd'|vbit: {
+		case 'd'|VI_CMDMODE_BIT: {
 			int nc, sc;
+			int prev_ic;
+
 			sc = cursor;
-			prevc = ic;
-			if (safe_read(STDIN_FILENO, &c, 1) < 1)
+			prev_ic = ic;
+
+			ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
+			if (errno) /* error */
 				goto prepare_to_die;
-			if (c == (prevc & 0xff)) {
+
+			if ((ic | VI_CMDMODE_BIT) == prev_ic) {
 				/* "cc", "dd" */
 				input_backward(cursor);
 				goto clear_to_eol;
 				break;
 			}
-			switch (c) {
+			switch (ic) {
 			case 'w':
 			case 'W':
 			case 'e':
 			case 'E':
-				switch (c) {
+				switch (ic) {
 				case 'w':   /* "dw", "cw" */
 					vi_word_motion(command, vi_cmdmode);
 					break;
@@ -1763,7 +1776,7 @@
 				break;
 			case 'b':  /* "db", "cb" */
 			case 'B':  /* implemented as B */
-				if (c == 'b')
+				if (ic == 'b')
 					vi_back_motion(command);
 				else
 					vi_Back_motion(command);
@@ -1774,143 +1787,109 @@
 				input_delete(1);
 				break;
 			case '$':  /* "d$", "c$" */
-			clear_to_eol:
+ clear_to_eol:
 				while (cursor < command_len)
 					input_delete(1);
 				break;
 			}
 			break;
 		}
-		case 'p'|vbit:
+		case 'p'|VI_CMDMODE_BIT:
 			input_forward();
 			/* fallthrough */
-		case 'P'|vbit:
+		case 'P'|VI_CMDMODE_BIT:
 			put();
 			break;
-		case 'r'|vbit:
-			if (safe_read(STDIN_FILENO, &c, 1) < 1)
+		case 'r'|VI_CMDMODE_BIT:
+			ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
+			if (errno) /* error */
 				goto prepare_to_die;
-			if (c == 0)
+			if (ic < ' ' || ic > 255) {
 				beep();
-			else {
-				*(command + cursor) = c;
-				bb_putchar(c);
+			} else {
+				command[cursor] = ic;
+				bb_putchar(ic);
 				bb_putchar('\b');
 			}
 			break;
+		case '\x1b': /* ESC */
+			if (state->flags & VI_MODE) {
+				/* insert mode --> command mode */
+				vi_cmdmode = 1;
+				input_backward(1);
+			}
+			break;
 #endif /* FEATURE_COMMAND_EDITING_VI */
 
-		case '\x1b': /* ESC */
-
-#if ENABLE_FEATURE_EDITING_VI
-			if (state->flags & VI_MODE) {
-				/* ESC: insert mode --> command mode */
-				vi_cmdmode = 1;
-				input_backward(1);
-				break;
-			}
-#endif
-			/* escape sequence follows */
-			if (safe_read(STDIN_FILENO, &c, 1) < 1)
-				goto prepare_to_die;
-			/* different vt100 emulations */
-			if (c == '[' || c == 'O') {
-		vi_case('['|vbit:)
-		vi_case('O'|vbit:)
-				if (safe_read(STDIN_FILENO, &c, 1) < 1)
-					goto prepare_to_die;
-			}
-			if (c >= '1' && c <= '9') {
-				unsigned char dummy;
-
-				if (safe_read(STDIN_FILENO, &dummy, 1) < 1)
-					goto prepare_to_die;
-				if (dummy != '~')
-					c = '\0';
-			}
-
-			switch (c) {
-#if ENABLE_FEATURE_TAB_COMPLETION
-			case '\t':                      /* Alt-Tab */
-				input_tab(&lastWasTab);
-				break;
-#endif
 #if MAX_HISTORY > 0
-			case 'A':
-				/* Up Arrow -- Get previous command from history */
-				if (get_previous_history())
-					goto rewrite_line;
-				beep();
+		case KEYCODE_UP:
+			if (get_previous_history())
+				goto rewrite_line;
+			beep();
+			break;
+		case KEYCODE_DOWN:
+			if (!get_next_history())
 				break;
-			case 'B':
-				/* Down Arrow -- Get next command in history */
-				if (!get_next_history())
-					break;
  rewrite_line:
-				/* Rewrite the line with the selected history item */
-				/* change command */
-				command_len = strlen(strcpy(command, state->history[state->cur_history] ? : ""));
-				/* redraw and go to eol (bol, in vi */
-				redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
-				break;
+			/* Rewrite the line with the selected history item */
+			/* change command */
+			command_len = strlen(strcpy(command, state->history[state->cur_history] ? : ""));
+			/* redraw and go to eol (bol, in vi) */
+			redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0);
+			break;
 #endif
-			case 'C':
-				/* Right Arrow -- Move forward one character */
-				input_forward();
-				break;
-			case 'D':
-				/* Left Arrow -- Move back one character */
-				input_backward(1);
-				break;
-			case '3':
-				/* Delete */
-				input_delete(0);
-				break;
-			case '1': // vt100? linux vt? or what?
-			case '7': // vt100? linux vt? or what?
-			case 'H': /* xterm's <Home> */
-				input_backward(cursor);
-				break;
-			case '4': // vt100? linux vt? or what?
-			case '8': // vt100? linux vt? or what?
-			case 'F': /* xterm's <End> */
-				input_end();
-				break;
-			default:
-				c = '\0';
-				beep();
-			}
+		case KEYCODE_RIGHT:
+			input_forward();
+			break;
+		case KEYCODE_LEFT:
+			input_backward(1);
+			break;
+		case KEYCODE_DELETE:
+			input_delete(0);
+			break;
+		case KEYCODE_HOME:
+			input_backward(cursor);
+			break;
+		case KEYCODE_END:
+			input_end();
 			break;
 
-		default:        /* If it's regular input, do the normal thing */
-
-			/* Control-V -- force insert of next char */
-			if (c == CTRL('V')) {
-				if (safe_read(STDIN_FILENO, &c, 1) < 1)
-					goto prepare_to_die;
-				if (c == 0) {
-					beep();
-					break;
-				}
+		default:
+//			/* Control-V -- force insert of next char */
+//			if (c == CTRL('V')) {
+//				if (safe_read(STDIN_FILENO, &c, 1) < 1)
+//					goto prepare_to_die;
+//				if (c == 0) {
+//					beep();
+//					break;
+//				}
+//			}
+			if (ic < ' ' || ic > 255) {
+				/* If VI_CMDMODE_BIT is set, ic is >= 256
+				 * and command mode ignores unexpected chars.
+				 * Otherwise, we are here if ic is a
+				 * control char or an unhandled ESC sequence,
+				 * which is also ignored.
+				 */
+				break;
+			}
+			if ((int)command_len >= (maxsize - 2)) {
+				/* Not enough space for the char and EOL */
+				break;
 			}
 
-#if ENABLE_FEATURE_EDITING_VI
-			if (vi_cmdmode)  /* Don't self-insert */
-				break;
-#endif
-			if ((int)command_len >= (maxsize - 2))        /* Need to leave space for enter */
-				break;
-
 			command_len++;
-			if (cursor == (command_len - 1)) {      /* Append if at the end of the line */
-				command[cursor] = c;
-				command[cursor+1] = '\0';
+			if (cursor == (command_len - 1)) {
+				/* We are at the end, append */
+				command[cursor] = ic;
+				command[cursor + 1] = '\0';
 				cmdedit_set_out_char(' ');
-			} else {                        /* Insert otherwise */
+			} else {
+				/* In the middle, insert */
 				int sc = cursor;
 
 				memmove(command + sc + 1, command + sc, command_len - sc);
-				command[sc] = c;
+				command[sc] = ic;
 				sc++;
 				/* rewrite from cursor */
 				input_end();
@@ -1918,15 +1897,17 @@
 				input_backward(cursor - sc);
 			}
 			break;
-		}
-		if (break_out)                  /* Enter is the command terminator, no more input. */
+		} /* switch (input_key) */
+
+		if (break_out)
 			break;
 
 #if ENABLE_FEATURE_TAB_COMPLETION
-		if (c != '\t')
+		ic &= ~VI_CMDMODE_BIT;
+		if (ic != '\t')
 			lastWasTab = FALSE;
 #endif
-	}
+	} /* while (1) */
 
 	if (command_len > 0)
 		remember_in_history(command);
diff --git a/libbb/read_key.c b/libbb/read_key.c
index 0f36d20..fd100b0 100644
--- a/libbb/read_key.c
+++ b/libbb/read_key.c
@@ -66,6 +66,7 @@
 		0
 	};
 
+	errno = 0;
 	n = 0;
 	if (nbuffered)
 		n = *nbuffered;