traceroute: fix help text to not show -6 when traceroute6 is off
[oweals/busybox.git] / libbb / lineedit.c
index bfd0e3346131b9e5ffa79233f80fc7eeb985cd65..7bb3f2e356489bfd083e45efd20f3a7f7199c657 100644 (file)
@@ -140,6 +140,9 @@ struct lineedit_statics {
        smallint newdelflag;     /* whether delbuf should be reused yet */
        CHAR_T delbuf[DELBUFSIZ];  /* a place to store deleted characters */
 #endif
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
+       smallint sent_ESC_br6n;
+#endif
 
        /* Formerly these were big buffers on stack: */
 #if ENABLE_FEATURE_TAB_COMPLETION
@@ -369,25 +372,14 @@ static void input_backward(unsigned num)
 
 static void put_prompt(void)
 {
+       unsigned w;
+
        out1str(cmdedit_prompt);
-       if (ENABLE_FEATURE_EDITING_ASK_TERMINAL) {
-               /* Ask terminal where is the cursor now.
-                * lineedit_read_key handles response and corrects
-                * our idea of current cursor position.
-                * Testcase: run "echo -n long_line_long_line_long_line",
-                * then type in a long, wrapping command and try to
-                * delete it using backspace key.
-                * Note: we print it _after_ prompt, because
-                * prompt may contain CR. Example: PS1='\[\r\n\]\w '
-                */
-               out1str("\033" "[6n");
-       }
+       fflush_all();
        cursor = 0;
-       {
-               unsigned w = cmdedit_termw; /* volatile var */
-               cmdedit_y = cmdedit_prmt_len / w; /* new quasireal y */
-               cmdedit_x = cmdedit_prmt_len % w;
-       }
+       w = cmdedit_termw; /* read volatile var once */
+       cmdedit_y = cmdedit_prmt_len / w; /* new quasireal y */
+       cmdedit_x = cmdedit_prmt_len % w;
 }
 
 /* draw prompt, editor line, and clear tail */
@@ -1470,6 +1462,51 @@ static void ctrl_right(void)
  * read_line_input and its helpers
  */
 
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
+static void ask_terminal(void)
+{
+       /* Ask terminal where is the cursor now.
+        * lineedit_read_key handles response and corrects
+        * our idea of current cursor position.
+        * Testcase: run "echo -n long_line_long_line_long_line",
+        * then type in a long, wrapping command and try to
+        * delete it using backspace key.
+        * Note: we print it _after_ prompt, because
+        * prompt may contain CR. Example: PS1='\[\r\n\]\w '
+        */
+       /* Problem: if there is buffered input on stdin,
+        * the response will be delivered later,
+        * possibly to an unsuspecting application.
+        * Testcase: "sleep 1; busybox ash" + press and hold [Enter].
+        * Result:
+        * ~/srcdevel/bbox/fix/busybox.t4 #
+        * ~/srcdevel/bbox/fix/busybox.t4 #
+        * ^[[59;34~/srcdevel/bbox/fix/busybox.t4 #  <-- garbage
+        * ~/srcdevel/bbox/fix/busybox.t4 #
+        *
+        * Checking for input with poll only makes the race narrower,
+        * I still can trigger it. Strace:
+        *
+        * write(1, "~/srcdevel/bbox/fix/busybox.t4 # ", 33) = 33
+        * poll([{fd=0, events=POLLIN}], 1, 0) = 0 (Timeout)  <-- no input exists
+        * write(1, "\33[6n", 4) = 4  <-- send the ESC sequence, quick!
+        * poll([{fd=0, events=POLLIN}], 1, 4294967295) = 1 ([{fd=0, revents=POLLIN}])
+        * read(0, "\n", 1)      = 1  <-- oh crap, user's input got in first
+        */
+       struct pollfd pfd;
+
+       pfd.fd = STDIN_FILENO;
+       pfd.events = POLLIN;
+       if (safe_poll(&pfd, 1, 0) == 0) {
+               S.sent_ESC_br6n = 1;
+               out1str("\033" "[6n");
+               fflush_all(); /* make terminal see it ASAP! */
+       }
+}
+#else
+#define ask_terminal() ((void)0)
+#endif
+
 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
 static void parse_and_put_prompt(const char *prmt_ptr)
 {
@@ -1603,7 +1640,7 @@ static void cmdedit_setwidth(unsigned w, int redraw_flg)
                int new_y = (cursor + cmdedit_prmt_len) / w;
                /* redraw */
                redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), command_len - cursor);
-               fflush(stdout);
+               fflush_all();
        }
 }
 
@@ -1629,7 +1666,9 @@ static int lineedit_read_key(char *read_key_buffer)
        pfd.fd = STDIN_FILENO;
        pfd.events = POLLIN;
        do {
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL || ENABLE_FEATURE_ASSUME_UNICODE
  poll_again:
+#endif
                if (read_key_buffer[0] == 0) {
                        /* Wait for input. Can't just call read_key,
                         * it returns at once if stdin
@@ -1639,19 +1678,24 @@ static int lineedit_read_key(char *read_key_buffer)
                /* Note: read_key sets errno to 0 on success: */
                ic = read_key(STDIN_FILENO, read_key_buffer);
 
-               if (ENABLE_FEATURE_EDITING_ASK_TERMINAL
-                && (int32_t)ic == KEYCODE_CURSOR_POS
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
+               if ((int32_t)ic == KEYCODE_CURSOR_POS
+                && S.sent_ESC_br6n
                ) {
-                       int col = ((ic >> 32) & 0x7fff) - 1;
-                       if (col > cmdedit_prmt_len) {
-                               cmdedit_x += (col - cmdedit_prmt_len);
-                               while (cmdedit_x >= cmdedit_termw) {
-                                       cmdedit_x -= cmdedit_termw;
-                                       cmdedit_y++;
+                       S.sent_ESC_br6n = 0;
+                       if (cursor == 0) { /* otherwise it may be bogus */
+                               int col = ((ic >> 32) & 0x7fff) - 1;
+                               if (col > cmdedit_prmt_len) {
+                                       cmdedit_x += (col - cmdedit_prmt_len);
+                                       while (cmdedit_x >= cmdedit_termw) {
+                                               cmdedit_x -= cmdedit_termw;
+                                               cmdedit_y++;
+                                       }
                                }
                        }
                        goto poll_again;
                }
+#endif
 
 #if ENABLE_FEATURE_ASSUME_UNICODE
                {
@@ -1708,7 +1752,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
        ) {
                /* Happens when e.g. stty -echo was run before */
                parse_and_put_prompt(prompt);
-               fflush(stdout);
+               /* fflush_all(); - done by parse_and_put_prompt */
                if (fgets(command, maxsize, stdin) == NULL)
                        len = -1; /* EOF or error */
                else
@@ -1782,8 +1826,9 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
        bb_error_msg("cur_history:%d cnt_history:%d", state->cur_history, state->cnt_history);
 #endif
 
-       /* Print out the command prompt */
+       /* Print out the command prompt, optionally ask where cursor is */
        parse_and_put_prompt(prompt);
+       ask_terminal();
 
        read_key_buffer[0] = 0;
        while (1) {
@@ -1801,7 +1846,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
                };
                int32_t ic, ic_raw;
 
-               fflush(NULL);
+               fflush_all();
                ic = ic_raw = lineedit_read_key(read_key_buffer);
 
 #if ENABLE_FEATURE_EDITING_VI
@@ -2164,6 +2209,21 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
 #endif
        } /* while (1) */
 
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
+       if (S.sent_ESC_br6n) {
+               /* "sleep 1; busybox ash" + hold [Enter] to trigger.
+                * We sent "ESC [ 6 n", but got '\n' first, and
+                * KEYCODE_CURSOR_POS response is now buffered from terminal.
+                * It's bad already and not much can be done with it
+                * (it _will_ be visible for the next process to read stdin),
+                * but without this delay it even shows up on the screen
+                * as garbage because we restore echo settings with tcsetattr
+                * before it comes in. UGLY!
+                */
+               usleep(20*1000);
+       }
+#endif
+
 /* Stop bug catching using "command_must_not_be_used" trick */
 #undef command
 
@@ -2190,7 +2250,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
        tcsetattr_stdin_TCSANOW(&initial_settings);
        /* restore SIGWINCH handler */
        signal(SIGWINCH, previous_SIGWINCH_handler);
-       fflush(stdout);
+       fflush_all();
 
        len = command_len;
        DEINIT_S();
@@ -2204,7 +2264,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
 int FAST_FUNC read_line_input(const char* prompt, char* command, int maxsize)
 {
        fputs(prompt, stdout);
-       fflush(stdout);
+       fflush_all();
        fgets(command, maxsize, stdin);
        return strlen(command);
 }