Big cleanup in config help and description
[oweals/busybox.git] / miscutils / less.c
index 8fd0874e228854e4ee0b6b34b22fad580172350a..0b0a9aed4be26e7290d9813cf946e5b25e2d44ca 100644 (file)
@@ -87,7 +87,6 @@
 //config:        this option makes less perform a last-ditch effort to find it:
 //config:        position cursor to 999,999 and ask terminal to report real
 //config:        cursor position using "ESC [ 6 n" escape sequence, then read stdin.
-//config:
 //config:        This is not clean but helps a lot on serial lines and such.
 //config:
 //config:config FEATURE_LESS_DASHCMD
 //config:        less itself ('-' keyboard command).
 //config:
 //config:config FEATURE_LESS_LINENUMS
-//config:      bool "Enable dynamic switching of line numbers"
+//config:      bool "Enable -N (dynamic switching of line numbers)"
 //config:      default y
 //config:      depends on FEATURE_LESS_DASHCMD
-//config:      help
-//config:        Enables "-N" command.
+
+//applet:IF_LESS(APPLET(less, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_LESS) += less.o
 
 //usage:#define less_trivial_usage
 //usage:       "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
 #include <sched.h>  /* sched_yield() */
 
 #include "libbb.h"
+#include "common_bufsiz.h"
 #if ENABLE_FEATURE_LESS_REGEXP
 #include "xregex.h"
 #endif
@@ -165,14 +167,10 @@ enum {
 enum { pattern_valid = 0 };
 #endif
 
-enum {
-       READING_FILE = -1,
-       READING_STDIN = -2
-};
-
 struct globals {
        int cur_fline; /* signed */
        int kbd_fd;  /* fd to get input from */
+       int kbd_fd_orig_flags;
        int less_gets_pos;
 /* last position in last line, taking into account tabs */
        size_t last_line_pos;
@@ -194,7 +192,10 @@ struct globals {
        char *filename;
        char **files;
 #if ENABLE_FEATURE_LESS_FLAGS
-       int num_lines; /* input source if < 0, line count if >= 0 */
+       int num_lines; /* a flag if < 0, line count if >= 0 */
+# define REOPEN_AND_COUNT (-1)
+# define REOPEN_STDIN     (-2)
+# define NOT_REGULAR_FILE (-3)
 #endif
 #if ENABLE_FEATURE_LESS_MARKS
        unsigned num_marks;
@@ -305,6 +306,8 @@ static void print_statusline(const char *str)
 static void less_exit(int code)
 {
        set_tty_cooked();
+       if (!(G.kbd_fd_orig_flags & O_NONBLOCK))
+               ndelay_off(kbd_fd);
        clear_line();
        if (code < 0)
                kill_myself_with_sig(- code); /* does not return */
@@ -405,6 +408,14 @@ static void fill_match_lines(unsigned pos);
 #define fill_match_lines(pos) ((void)0)
 #endif
 
+static int at_end(void)
+{
+       return (option_mask32 & FLAG_S)
+               ? !(cur_fline <= max_fline &&
+                       max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+               : !(max_fline > cur_fline + max_displayed_line);
+}
+
 /* Devilishly complex routine.
  *
  * Has to deal with EOF and EPIPE on input,
@@ -433,7 +444,6 @@ static void fill_match_lines(unsigned pos);
  */
 static void read_lines(void)
 {
-#define readbuf bb_common_bufsiz1
        char *current_line, *p;
        int w = width;
        char last_terminated = terminated;
@@ -443,6 +453,9 @@ static void read_lines(void)
        unsigned old_max_fline = max_fline;
 #endif
 
+#define readbuf bb_common_bufsiz1
+       setup_common_bufsiz();
+
        /* (careful: max_fline can be -1) */
        if (max_fline + 1 > MAXLINES)
                return;
@@ -450,7 +463,7 @@ static void read_lines(void)
        if (option_mask32 & FLAG_N)
                w -= 8;
 
-       p = current_line = ((char*)xmalloc(w + 4)) + 4;
+       p = current_line = ((char*)xmalloc(w + 5)) + 4;
        if (!last_terminated) {
                const char *cp = flines[max_fline];
                p = stpcpy(p, cp);
@@ -474,7 +487,7 @@ static void read_lines(void)
                                        time_t t;
 
                                        errno = 0;
-                                       eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+                                       eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
                                        if (errno != EAGAIN)
                                                break;
                                        t = time(NULL);
@@ -509,7 +522,7 @@ static void read_lines(void)
                                        new_last_line_pos += 7;
                                        new_last_line_pos &= (~7);
                                }
-                               if ((int)new_last_line_pos >= w)
+                               if ((int)new_last_line_pos > w)
                                        break;
                                last_line_pos = new_last_line_pos;
                        }
@@ -551,11 +564,7 @@ static void read_lines(void)
                        eof_error = 0; /* Pretend we saw EOF */
                        break;
                }
-               if (!(option_mask32 & FLAG_S)
-                 ? (max_fline > cur_fline + max_displayed_line)
-                 : (max_fline >= cur_fline
-                    && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
-               ) {
+               if (!at_end()) {
 #if !ENABLE_FEATURE_LESS_REGEXP
                        break;
 #else
@@ -571,7 +580,7 @@ static void read_lines(void)
                        break;
                }
                max_fline++;
-               current_line = ((char*)xmalloc(w + 4)) + 4;
+               current_line = ((char*)xmalloc(w + 5)) + 4;
                p = current_line;
                last_line_pos = 0;
        } /* end of "read lines until we reach cur_fline" loop */
@@ -613,17 +622,27 @@ static int safe_lineno(int fline)
 static void update_num_lines(void)
 {
        int count, fd;
+       struct stat stbuf;
        ssize_t len, i;
        char buf[4096];
-       struct stat stbuf;
 
-       if (num_lines == READING_FILE) {
+       /* only do this for regular files */
+       if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
                count = 0;
-               fd = open(filename, O_RDONLY);
-               if (fd < 0)
-                       goto skip;
-               if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode))
+               fd = open("/proc/self/fd/0", O_RDONLY);
+               if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
+                       /* "filename" is valid only if REOPEN_AND_COUNT */
+                       fd = open(filename, O_RDONLY);
+               }
+               if (fd < 0) {
+                       /* somebody stole my file! */
+                       num_lines = NOT_REGULAR_FILE;
+                       return;
+               }
+               if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
+                       num_lines = NOT_REGULAR_FILE;
                        goto do_close;
+               }
                while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
                        for (i = 0; i < len; ++i) {
                                if (buf[i] == '\n' && ++count == MAXLINES)
@@ -634,7 +653,6 @@ static void update_num_lines(void)
                num_lines = count;
  do_close:
                close(fd);
- skip: ;
        }
 }
 
@@ -662,7 +680,7 @@ static void m_status_print(void)
        if (num_lines >= 0)
                printf("/%i", num_lines);
 
-       if (cur_fline >= (int)(max_fline - max_displayed_line)) {
+       if (at_end()) {
                printf(" (END)");
                if (num_files > 1 && current_file != num_files)
                        printf(" - next: %s", files[current_file]);
@@ -692,7 +710,7 @@ static void status_print(void)
 #endif
 
        clear_line();
-       if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+       if (cur_fline && !at_end()) {
                bb_putchar(':');
                return;
        }
@@ -707,23 +725,6 @@ static void status_print(void)
        print_hilite(p);
 }
 
-static void cap_cur_fline(int nlines)
-{
-       int diff;
-       if (cur_fline < 0)
-               cur_fline = 0;
-       if (cur_fline + max_displayed_line > max_fline + TILDES) {
-               cur_fline -= nlines;
-               if (cur_fline < 0)
-                       cur_fline = 0;
-               diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
-               /* As the number of lines requested was too large, we just move
-                * to the end of the file */
-               if (diff > 0)
-                       cur_fline += diff;
-       }
-}
-
 static const char controls[] ALIGN1 =
        /* NUL: never encountered; TAB: not converted */
        /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
@@ -761,7 +762,7 @@ static void print_found(const char *line)
        char *growline;
        regmatch_t match_structs;
 
-       char buf[width];
+       char buf[width+1];
        const char *str = line;
        char *p = buf;
        size_t n;
@@ -820,7 +821,7 @@ void print_found(const char *line);
 
 static void print_ascii(const char *str)
 {
-       char buf[width];
+       char buf[width+1];
        char *p;
        size_t n;
 
@@ -909,45 +910,112 @@ static void buffer_fill_and_print(void)
        buffer_print();
 }
 
+/* move cur_fline to a given line number, reading lines if necessary */
+static void goto_lineno(int target)
+{
+       if (target <= 0 ) {
+               cur_fline = 0;
+       }
+       else if (target > LINENO(flines[cur_fline])) {
+ retry:
+               while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
+                       ++cur_fline;
+               /* target not reached but more input is available */
+               if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
+                       read_lines();
+                       goto retry;
+               }
+       }
+       else {
+               /* search backwards through already-read lines */
+               while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
+                       --cur_fline;
+       }
+}
+
+static void cap_cur_fline(void)
+{
+       if ((option_mask32 & FLAG_S)) {
+               if (cur_fline > max_fline)
+                       cur_fline = max_fline;
+               if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
+                       goto_lineno(max_lineno - max_displayed_line + TILDES);
+                       read_lines();
+               }
+       }
+       else {
+               if (cur_fline + max_displayed_line > max_fline + TILDES)
+                       cur_fline = max_fline - max_displayed_line + TILDES;
+               if (cur_fline < 0)
+                       cur_fline = 0;
+       }
+}
+
 /* Move the buffer up and down in the file in order to scroll */
 static void buffer_down(int nlines)
 {
-       cur_fline += nlines;
+       if ((option_mask32 & FLAG_S))
+               goto_lineno(LINENO(flines[cur_fline]) + nlines);
+       else
+               cur_fline += nlines;
        read_lines();
-       cap_cur_fline(nlines);
+       cap_cur_fline();
        buffer_fill_and_print();
 }
 
 static void buffer_up(int nlines)
 {
-       cur_fline -= nlines;
-       if (cur_fline < 0) cur_fline = 0;
+       if ((option_mask32 & FLAG_S)) {
+               goto_lineno(LINENO(flines[cur_fline]) - nlines);
+       }
+       else {
+               cur_fline -= nlines;
+               if (cur_fline < 0)
+                       cur_fline = 0;
+       }
        read_lines();
        buffer_fill_and_print();
 }
 
-static void buffer_line(int linenum)
+/* display a given line where the argument can be either an index into
+ * the flines array or a line number */
+static void buffer_to_line(int linenum, int is_lineno)
 {
-       if (linenum < 0)
-               linenum = 0;
-       cur_fline = linenum;
+       if (linenum <= 0)
+               cur_fline = 0;
+       else if (is_lineno)
+               goto_lineno(linenum);
+       else
+               cur_fline = linenum;
        read_lines();
-       if (linenum + max_displayed_line > max_fline)
-               linenum = max_fline - max_displayed_line + TILDES;
-       if (linenum < 0)
-               linenum = 0;
-       cur_fline = linenum;
+       cap_cur_fline();
        buffer_fill_and_print();
 }
 
+static void buffer_line(int linenum)
+{
+       buffer_to_line(linenum, FALSE);
+}
+
+static void buffer_lineno(int lineno)
+{
+       buffer_to_line(lineno, TRUE);
+}
+
 static void open_file_and_read_lines(void)
 {
        if (filename) {
                xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
+#if ENABLE_FEATURE_LESS_FLAGS
+               num_lines = REOPEN_AND_COUNT;
+#endif
        } else {
                /* "less" with no arguments in argv[] */
                /* For status line only */
                filename = xstrdup(bb_msg_standard_input);
+#if ENABLE_FEATURE_LESS_FLAGS
+               num_lines = REOPEN_STDIN;
+#endif
        }
        readpos = 0;
        readeof = 0;
@@ -971,9 +1039,6 @@ static void reinitialize(void)
        max_fline = -1;
        cur_fline = 0;
        max_lineno = 0;
-#if ENABLE_FEATURE_LESS_FLAGS
-       num_lines = filename ? READING_FILE : READING_STDIN;
-#endif
        open_file_and_read_lines();
 #if ENABLE_FEATURE_LESS_ASK_TERMINAL
        if (G.winsize_err)
@@ -1002,12 +1067,7 @@ static int64_t getch_nowait(void)
         */
        rd = 1;
        /* Are we interested in stdin? */
-//TODO: reuse code for determining this
-       if (!(option_mask32 & FLAG_S)
-          ? !(max_fline > cur_fline + max_displayed_line)
-          : !(max_fline >= cur_fline
-              && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
-       ) {
+       if (at_end()) {
                if (eof_error > 0) /* did NOT reach eof yet */
                        rd = 0; /* yes, we are interested in stdin */
        }
@@ -1342,15 +1402,16 @@ static void number_process(int first_digit)
                buffer_up(num);
                break;
        case 'g': case '<': case 'G': case '>':
-               cur_fline = num + max_displayed_line;
-               read_lines();
-               buffer_line(num - 1);
+               buffer_lineno(num - 1);
                break;
        case 'p': case '%':
-               num = num * (max_fline / 100); /* + max_fline / 2; */
-               cur_fline = num + max_displayed_line;
-               read_lines();
-               buffer_line(num);
+#if ENABLE_FEATURE_LESS_FLAGS
+               update_num_lines();
+               num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
+#else
+               num = num * max_lineno / 100;
+#endif
+               buffer_lineno(num);
                break;
 #if ENABLE_FEATURE_LESS_REGEXP
        case 'n':
@@ -1532,16 +1593,23 @@ static char opp_bracket(char bracket)
 
 static void match_right_bracket(char bracket)
 {
-       unsigned i;
+       unsigned i = cur_fline;
 
-       if (strchr(flines[cur_fline], bracket) == NULL) {
+       if (i >= max_fline
+        || strchr(flines[i], bracket) == NULL
+       ) {
                print_statusline("No bracket in top line");
                return;
        }
+
        bracket = opp_bracket(bracket);
-       for (i = cur_fline + 1; i < max_fline; i++) {
+       for (; i < max_fline; i++) {
                if (strchr(flines[i], bracket) != NULL) {
-                       buffer_line(i);
+                       /*
+                        * Line with matched right bracket becomes
+                        * last visible line
+                        */
+                       buffer_line(i - max_displayed_line);
                        return;
                }
        }
@@ -1550,16 +1618,22 @@ static void match_right_bracket(char bracket)
 
 static void match_left_bracket(char bracket)
 {
-       int i;
+       int i = cur_fline + max_displayed_line;
 
-       if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
+       if (i >= max_fline
+        || strchr(flines[i], bracket) == NULL
+       ) {
                print_statusline("No bracket in bottom line");
                return;
        }
 
        bracket = opp_bracket(bracket);
-       for (i = cur_fline + max_displayed_line; i >= 0; i--) {
+       for (; i >= 0; i--) {
                if (strchr(flines[i], bracket) != NULL) {
+                       /*
+                        * Line with matched left bracket becomes
+                        * first visible line
+                        */
                        buffer_line(i);
                        return;
                }
@@ -1727,9 +1801,10 @@ int less_main(int argc, char **argv)
        /* 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,
+        *
+        * We prefer not to use STDOUT_FILENO directly,
         * since we want to set this fd to non-blocking mode,
-        * and not bother with restoring it on exit.
+        * and not interfere with other processes which share stdout with us.
         */
        tty_name = xmalloc_ttyname(STDOUT_FILENO);
        if (tty_name) {
@@ -1741,10 +1816,12 @@ int less_main(int argc, char **argv)
                /* Try controlling tty */
  try_ctty:
                tty_fd = open(CURRENT_TTY, O_RDONLY);
-               if (tty_fd < 0)
-                       return bb_cat(argv);
+               if (tty_fd < 0) {
+                       /* If all else fails, less 481 uses stdout. Mimic that */
+                       tty_fd = STDOUT_FILENO;
+               }
        }
-       ndelay_on(tty_fd);
+       G.kbd_fd_orig_flags = ndelay_on(tty_fd);
        kbd_fd = tty_fd; /* save in a global */
 
        tcgetattr(kbd_fd, &term_orig);