less: move "retry-on-EAGAIN" logic closer to read ops
[oweals/busybox.git] / miscutils / less.c
index f0187bf8aeaa13e2dc4de88e00d3f825a4fd6037..3016c5b47c69d4817cc30ba3bad81d34c6b7b722 100644 (file)
 //config:        Enables "-N" command.
 
 //usage:#define less_trivial_usage
-//usage:       "[-E" IF_FEATURE_LESS_FLAGS("Mm") "Nh~I?] [FILE]..."
+//usage:       "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") "Nh~] [FILE]..."
 //usage:#define less_full_usage "\n\n"
 //usage:       "View FILE (or stdin) one screenful at a time\n"
 //usage:     "\n       -E      Quit once the end of a file is reached"
+//usage:       IF_FEATURE_LESS_REGEXP(
+//usage:     "\n       -I      Ignore case in all searches"
+//usage:       )
 //usage:       IF_FEATURE_LESS_FLAGS(
 //usage:     "\n       -M,-m   Display status line with line numbers"
 //usage:     "\n               and percentage through the file"
 //usage:       )
 //usage:     "\n       -N      Prefix line number to each line"
-//usage:     "\n       -I      Ignore case in all searches"
 //usage:     "\n       -~      Suppress ~s displayed past EOF"
 
 #include <sched.h>  /* sched_yield() */
@@ -402,6 +404,9 @@ static void fill_match_lines(unsigned pos);
  * last_line_pos - screen line position of next char to be read
  *      (takes into account tabs and backspaces)
  * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
+ *
+ * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
+ * "/search on very long input" and "reaching max line count" corner cases.
  */
 static void read_lines(void)
 {
@@ -409,17 +414,19 @@ static void read_lines(void)
        char *current_line, *p;
        int w = width;
        char last_terminated = terminated;
+       time_t last_time = 0;
+       int retry_EAGAIN = 2;
 #if ENABLE_FEATURE_LESS_REGEXP
        unsigned old_max_fline = max_fline;
-       time_t last_time = 0;
-       unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */
 #endif
 
+       /* (careful: max_fline can be -1) */
+       if (max_fline + 1 > MAXLINES)
+               return;
+
        if (option_mask32 & FLAG_N)
                w -= 8;
 
- IF_FEATURE_LESS_REGEXP(again0:)
-
        p = current_line = ((char*)xmalloc(w + 4)) + 4;
        max_fline += last_terminated;
        if (!last_terminated) {
@@ -439,13 +446,29 @@ static void read_lines(void)
                        char c;
                        /* if no unprocessed chars left, eat more */
                        if (readpos >= readeof) {
-                               ndelay_on(0);
-                               eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
-                               ndelay_off(0);
+                               int flags = ndelay_on(0);
+
+                               while (1) {
+                                       time_t t;
+
+                                       errno = 0;
+                                       eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+                                       if (errno != EAGAIN)
+                                               break;
+                                       t = time(NULL);
+                                       if (t != last_time) {
+                                               last_time = t;
+                                               if (--retry_EAGAIN < 0)
+                                                       break;
+                                       }
+                                       sched_yield();
+                               }
+                               fcntl(0, F_SETFL, flags); /* ndelay_off(0) */
                                readpos = 0;
                                readeof = eof_error;
                                if (eof_error <= 0)
                                        goto reached_eof;
+                               retry_EAGAIN = 1;
                        }
                        c = readbuf[readpos];
                        /* backspace? [needed for manpages] */
@@ -480,6 +503,11 @@ static void read_lines(void)
                        *p++ = c;
                        *p = '\0';
                } /* end of "read chars until we have a line" loop */
+#if 0
+//BUG: also triggers on this:
+// { printf "\nfoo\n"; sleep 1; printf "\nbar\n"; } | less
+// (resulting in lost empty line between "foo" and "bar" lines)
+// the "terminated" logic needs fixing (or explaining)
                /* Corner case: linewrap with only "" wrapping to next line */
                /* Looks ugly on screen, so we do not store this empty line */
                if (!last_terminated && !current_line[0]) {
@@ -487,6 +515,7 @@ static void read_lines(void)
                        max_lineno++;
                        continue;
                }
+#endif
  reached_eof:
                last_terminated = terminated;
                flines = xrealloc_vector(flines, 8, max_fline);
@@ -517,38 +546,22 @@ static void read_lines(void)
 #endif
                }
                if (eof_error <= 0) {
-                       if (eof_error < 0) {
-                               if (errno == EAGAIN) {
-                                       /* not yet eof or error, reset flag (or else
-                                        * we will hog CPU - select() will return
-                                        * immediately */
-                                       eof_error = 1;
-                               } else {
-                                       print_statusline(bb_msg_read_error);
-                               }
-                       }
-#if !ENABLE_FEATURE_LESS_REGEXP
                        break;
-#else
-                       if (wanted_match < num_matches) {
-                               break;
-                       } else { /* goto_match called us */
-                               time_t t = time(NULL);
-                               if (t != last_time) {
-                                       last_time = t;
-                                       if (--seconds_p1 == 0)
-                                               break;
-                               }
-                               sched_yield();
-                               goto again0; /* go loop again (max 2 seconds) */
-                       }
-#endif
                }
                max_fline++;
                current_line = ((char*)xmalloc(w + 4)) + 4;
                p = current_line;
                last_line_pos = 0;
        } /* end of "read lines until we reach cur_fline" loop */
+
+       if (eof_error < 0) {
+               if (errno == EAGAIN) {
+                       eof_error = 1;
+               } else {
+                       print_statusline(bb_msg_read_error);
+               }
+       }
+
        fill_match_lines(old_max_fline);
 #if ENABLE_FEATURE_LESS_REGEXP
        /* prevent us from being stuck in search for a match */
@@ -709,9 +722,9 @@ static void print_found(const char *line)
        /* buf[] holds quarantined version of str */
 
        /* Each part of the line that matches has the HIGHLIGHT
-          and NORMAL escape sequences placed around it.
-          NB: we regex against line, but insert text
-          from quarantined copy (buf[]) */
+        * and NORMAL escape sequences placed around it.
+        * NB: we regex against line, but insert text
+        * from quarantined copy (buf[]) */
        str = buf;
        growline = NULL;
        eflags = 0;
@@ -1608,12 +1621,18 @@ static void sigwinch_handler(int sig UNUSED_PARAM)
 int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int less_main(int argc, char **argv)
 {
+       char *tty_name;
+       int tty_fd;
+
        INIT_G();
 
-       /* TODO: -x: do not interpret backspace, -xx: tab also */
-       /* -xxx: newline also */
-       /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
-       getopt32(argv, "EMmN~I" IF_FEATURE_LESS_DASHCMD("S"));
+       /* TODO: -x: do not interpret backspace, -xx: tab also
+        * -xxx: newline also
+        * -w N: assume width N (-xxx -w 32: hex viewer of sorts)
+        * -s: condense many empty lines to one
+        *     (used by some setups for manpage display)
+        */
+       getopt32(argv, "EMmN~I" IF_FEATURE_LESS_DASHCMD("S") /*ignored:*/"s");
        argc -= optind;
        argv += optind;
        num_files = argc;
@@ -1637,10 +1656,28 @@ int less_main(int argc, char **argv)
        if (option_mask32 & FLAG_TILDE)
                empty_line_marker = "";
 
-       kbd_fd = open(CURRENT_TTY, O_RDONLY);
-       if (kbd_fd < 0)
-               return bb_cat(argv);
-       ndelay_on(kbd_fd);
+       /* Some versions of less can survive w/o controlling tty,
+        * try to do the same. This also allows to specify an alternative
+        * tty via "less 1<>TTY".
+        * We don't try to use STDOUT_FILENO directly,
+        * since we want to set this fd to non-blocking mode,
+        * and not bother with restoring it on exit.
+        */
+       tty_name = xmalloc_ttyname(STDOUT_FILENO);
+       if (tty_name) {
+               tty_fd = open(tty_name, O_RDONLY);
+               free(tty_name);
+               if (tty_fd < 0)
+                       goto try_ctty;
+       } else {
+               /* Try controlling tty */
+ try_ctty:
+               tty_fd = open(CURRENT_TTY, O_RDONLY);
+               if (tty_fd < 0)
+                       return bb_cat(argv);
+       }
+       ndelay_on(tty_fd);
+       kbd_fd = tty_fd; /* save in a global */
 
        tcgetattr(kbd_fd, &term_orig);
        term_less = term_orig;