makedevs: shrink by Vladimir
[oweals/busybox.git] / miscutils / less.c
index 31055a6c7891d362791b887fc572fd3e6be3df94..530a40a8c8314203a5b527f115a24a6eb236641b 100644 (file)
@@ -23,7 +23,7 @@
 
 #include <sched.h>     /* sched_yield() */
 
-#include "busybox.h"
+#include "libbb.h"
 #if ENABLE_FEATURE_LESS_REGEXP
 #include "xregex.h"
 #endif
@@ -48,8 +48,10 @@ enum {
        REAL_KEY_LEFT = 'D',
        REAL_PAGE_UP = '5',
        REAL_PAGE_DOWN = '6',
-       REAL_KEY_HOME = '7',
+       REAL_KEY_HOME = '7', // vt100? linux vt? or what?
        REAL_KEY_END = '8',
+       REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
+       REAL_KEY_END_ALT = '4', // ESC [4~
        REAL_KEY_HOME_XTERM = 'H',
        REAL_KEY_END_XTERM = 'F',
 
@@ -70,27 +72,6 @@ enum {
        TILDES = 1,
 };
 
-static unsigned max_displayed_line;
-static unsigned width;
-static const char *empty_line_marker = "~";
-
-static char *filename;
-static char **files;
-static unsigned num_files = 1;
-static unsigned current_file = 1;
-static const char **buffer;
-static const char **flines;
-static int cur_fline; /* signed */
-static unsigned max_fline;
-static unsigned max_lineno; /* this one tracks linewrap */
-
-static ssize_t eof_error = 1; /* eof if 0, error if < 0 */
-static char terminated = 1;
-static size_t readpos;
-static size_t readeof;
-/* last position in last line, taking into account tabs */
-static size_t linepos;
-
 /* Command line options */
 enum {
        FLAG_E = 1,
@@ -102,25 +83,87 @@ enum {
        LESS_STATE_MATCH_BACKWARDS = 1 << 15,
 };
 
-#if ENABLE_FEATURE_LESS_MARKS
-static unsigned mark_lines[15][2];
-static unsigned num_marks;
+#if !ENABLE_FEATURE_LESS_REGEXP
+enum { pattern_valid = 0 };
 #endif
 
+struct globals {
+       int cur_fline; /* signed */
+       int kbd_fd;  /* fd to get input from */
+       int less_gets_pos;
+/* last position in last line, taking into account tabs */
+       size_t linepos;
+       unsigned max_displayed_line;
+       unsigned max_fline;
+       unsigned max_lineno; /* this one tracks linewrap */
+       unsigned width;
+       ssize_t eof_error; /* eof if 0, error if < 0 */
+       ssize_t readpos;
+       ssize_t readeof; /* must be signed */
+       const char **buffer;
+       const char **flines;
+       const char *empty_line_marker;
+       unsigned num_files;
+       unsigned current_file;
+       char *filename;
+       char **files;
+#if ENABLE_FEATURE_LESS_MARKS
+       unsigned num_marks;
+       unsigned mark_lines[15][2];
+#endif
 #if ENABLE_FEATURE_LESS_REGEXP
-static unsigned *match_lines;
-static int match_pos; /* signed! */
-static unsigned num_matches;
-static regex_t pattern;
-static unsigned pattern_valid;
-#else
-enum { pattern_valid = 0 };
+       unsigned *match_lines;
+       int match_pos; /* signed! */
+       int wanted_match; /* signed! */
+       int num_matches;
+       regex_t pattern;
+       smallint pattern_valid;
 #endif
-
-static struct termios term_orig, term_vi;
-
-/* File pointer to get input from */
-static int kbd_fd;
+       smallint terminated;
+       struct termios term_orig, term_less;
+};
+#define G (*ptr_to_globals)
+#define cur_fline           (G.cur_fline         )
+#define kbd_fd              (G.kbd_fd            )
+#define less_gets_pos       (G.less_gets_pos     )
+#define linepos             (G.linepos           )
+#define max_displayed_line  (G.max_displayed_line)
+#define max_fline           (G.max_fline         )
+#define max_lineno          (G.max_lineno        )
+#define width               (G.width             )
+#define eof_error           (G.eof_error         )
+#define readpos             (G.readpos           )
+#define readeof             (G.readeof           )
+#define buffer              (G.buffer            )
+#define flines              (G.flines            )
+#define empty_line_marker   (G.empty_line_marker )
+#define num_files           (G.num_files         )
+#define current_file        (G.current_file      )
+#define filename            (G.filename          )
+#define files               (G.files             )
+#define num_marks           (G.num_marks         )
+#define mark_lines          (G.mark_lines        )
+#if ENABLE_FEATURE_LESS_REGEXP
+#define match_lines         (G.match_lines       )
+#define match_pos           (G.match_pos         )
+#define num_matches         (G.num_matches       )
+#define wanted_match        (G.wanted_match      )
+#define pattern             (G.pattern           )
+#define pattern_valid       (G.pattern_valid     )
+#endif
+#define terminated          (G.terminated        )
+#define term_orig           (G.term_orig         )
+#define term_less           (G.term_less         )
+#define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       less_gets_pos = -1; \
+       empty_line_marker = "~"; \
+       num_files = 1; \
+       current_file = 1; \
+       eof_error = 1; \
+       terminated = 1; \
+       USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \
+} while (0)
 
 /* Reset terminal input to normal */
 static void set_tty_cooked(void)
@@ -129,17 +172,6 @@ static void set_tty_cooked(void)
        tcsetattr(kbd_fd, TCSANOW, &term_orig);
 }
 
-/* Exit the program gracefully */
-static void less_exit(int code)
-{
-       /* TODO: We really should save the terminal state when we start,
-        * and restore it when we exit. Less does this with the
-        * "ti" and "te" termcap commands; can this be done with
-        * only termios.h? */
-       putchar('\n');
-       fflush_stdout_and_exit(code);
-}
-
 /* Move the cursor to a position (x,y), where (0,0) is the
    top-left corner of the console */
 static void move_cursor(int line, int row)
@@ -163,26 +195,63 @@ static void print_statusline(const char *str)
        printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
 }
 
+/* Exit the program gracefully */
+static void less_exit(int code)
+{
+       set_tty_cooked();
+       clear_line();
+       if (code < 0)
+               kill_myself_with_sig(- code); /* does not return */
+       exit(code);
+}
+
 #if ENABLE_FEATURE_LESS_REGEXP
 static void fill_match_lines(unsigned pos);
 #else
 #define fill_match_lines(pos) ((void)0)
 #endif
 
-
+/* Devilishly complex routine.
+ *
+ * Has to deal with EOF and EPIPE on input,
+ * with line wrapping, with last line not ending in '\n'
+ * (possibly not ending YET!), with backspace and tabs.
+ * It reads input again if last time we got an EOF (thus supporting
+ * growing files) or EPIPE (watching output of slow process like make).
+ *
+ * Variables used:
+ * flines[] - array of lines already read. Linewrap may cause
+ *      one source file line to occupy several flines[n].
+ * flines[max_fline] - last line, possibly incomplete.
+ * terminated - 1 if flines[max_fline] is 'terminated'
+ *      (if there was '\n' [which isn't stored itself, we just remember
+ *      that it was seen])
+ * max_lineno - last line's number, this one doesn't increment
+ *      on line wrap, only on "real" new lines.
+ * readbuf[0..readeof-1] - small preliminary buffer.
+ * readbuf[readpos] - next character to add to current line.
+ * linepos - 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
+ */
 static void read_lines(void)
 {
 #define readbuf bb_common_bufsiz1
        char *current_line, *p;
-       USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;)
        int w = width;
        char last_terminated = terminated;
+#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
 
        if (option_mask32 & FLAG_N)
                w -= 8;
 
-       current_line = xmalloc(w);
-       p = current_line;
+ USE_FEATURE_LESS_REGEXP(again0:)
+
+       p = current_line = xmalloc(w);
        max_fline += last_terminated;
        if (!last_terminated) {
                const char *cp = flines[max_fline];
@@ -190,45 +259,26 @@ static void read_lines(void)
                        cp += 8;
                strcpy(current_line, cp);
                p += strlen(current_line);
+               free((char*)flines[max_fline]);
+               /* linepos is still valid from previous read_lines() */
        } else {
                linepos = 0;
        }
 
-       while (1) {
- again:
+       while (1) { /* read lines until we reach cur_fline or wanted_match */
                *p = '\0';
                terminated = 0;
-               while (1) {
+               while (1) { /* read chars until we have a line */
                        char c;
                        /* if no unprocessed chars left, eat more */
                        if (readpos >= readeof) {
-                               smallint yielded = 0;
-
                                ndelay_on(0);
- read_again:
-                               eof_error = safe_read(0, readbuf, sizeof(readbuf));
+                               eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+                               ndelay_off(0);
                                readpos = 0;
                                readeof = eof_error;
-                               if (eof_error < 0) {
-                                       if (errno == EAGAIN && !yielded) {
-                       /* We can hit EAGAIN while searching for regexp match.
-                        * Yield is not 100% reliable solution in general,
-                        * but for less it should be good enough.
-                        * We give stdin supplier some CPU time to produce more.
-                        * We do it just once. */
-                                               sched_yield();
-                                               yielded = 1;
-                                               goto read_again;
-                                       }
-                                       readeof = 0;
-                                       if (errno != EAGAIN)
-                                               print_statusline("read error");
-                               }
-                               ndelay_off(0);
-
-                               if (eof_error <= 0) {
+                               if (eof_error <= 0)
                                        goto reached_eof;
-                               }
                        }
                        c = readbuf[readpos];
                        /* backspace? [needed for manpages] */
@@ -237,32 +287,42 @@ static void read_lines(void)
                        if (c == '\x8' && linepos && p[-1] != '\t') {
                                readpos++; /* eat it */
                                linepos--;
+                       /* was buggy (p could end up <= current_line)... */
                                *--p = '\0';
                                continue;
                        }
-                       if (c == '\t')
-                               linepos += (linepos^7) & 7;
-                       linepos++;
-                       if (linepos >= w)
-                               break;
+                       {
+                               size_t new_linepos = linepos + 1;
+                               if (c == '\t') {
+                                       new_linepos += 7;
+                                       new_linepos &= (~7);
+                               }
+                               if ((int)new_linepos >= w)
+                                       break;
+                               linepos = new_linepos;
+                       }
                        /* ok, we will eat this char */
                        readpos++;
-                       if (c == '\n') { terminated = 1; break; }
+                       if (c == '\n') {
+                               terminated = 1;
+                               linepos = 0;
+                               break;
+                       }
                        /* NUL is substituted by '\n'! */
                        if (c == '\0') c = '\n';
                        *p++ = c;
                        *p = '\0';
-               }
+               } /* end of "read chars until we have a line" loop */
                /* 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]) {
                        last_terminated = 1;
                        max_lineno++;
-                       goto again;
+                       continue;
                }
  reached_eof:
                last_terminated = terminated;
-               flines = xrealloc(flines, (max_fline+1) * sizeof(char *));
+               flines = xrealloc_vector(flines, 8, max_fline);
                if (option_mask32 & FLAG_N) {
                        /* Width of 7 preserves tab spacing in the text */
                        flines[max_fline] = xasprintf(
@@ -272,27 +332,62 @@ static void read_lines(void)
                        if (terminated)
                                max_lineno++;
                } else {
-                       flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
+                       flines[max_fline] = xrealloc(current_line, strlen(current_line) + 1);
                }
-               if (max_fline >= MAXLINES)
+               if (max_fline >= MAXLINES) {
+                       eof_error = 0; /* Pretend we saw EOF */
                        break;
-               if (max_fline > cur_fline + max_displayed_line)
+               }
+               if (max_fline > cur_fline + max_displayed_line) {
+#if !ENABLE_FEATURE_LESS_REGEXP
                        break;
+#else
+                       if (wanted_match >= num_matches) { /* goto_match called us */
+                               fill_match_lines(old_max_fline);
+                               old_max_fline = max_fline;
+                       }
+                       if (wanted_match < num_matches)
+                               break;
+#endif
+               }
                if (eof_error <= 0) {
-                       if (eof_error < 0 && errno == EAGAIN) {
-                               /* not yet eof or error, reset flag (or else
-                                * we will hog CPU - select() will return
-                                * immediately */
-                               eof_error = 1;
+                       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("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 = xmalloc(w);
                p = current_line;
                linepos = 0;
-       }
+       } /* end of "read lines until we reach cur_fline" loop */
        fill_match_lines(old_max_fline);
+#if ENABLE_FEATURE_LESS_REGEXP
+       /* prevent us from being stuck in search for a match */
+       wanted_match = -1;
+#endif
 #undef readbuf
 }
 
@@ -310,6 +405,9 @@ static void m_status_print(void)
 {
        int percentage;
 
+       if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+               return;
+
        clear_line();
        printf(HIGHLIGHT"%s", filename);
        if (num_files > 1)
@@ -317,7 +415,7 @@ static void m_status_print(void)
        printf(" lines %i-%i/%i ",
                        cur_fline + 1, cur_fline + max_displayed_line + 1,
                        max_fline + 1);
-       if (cur_fline >= max_fline - max_displayed_line) {
+       if (cur_fline >= (int)(max_fline - max_displayed_line)) {
                printf("(END)"NORMAL);
                if (num_files > 1 && current_file != num_files)
                        printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
@@ -333,6 +431,9 @@ static void status_print(void)
 {
        const char *p;
 
+       if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
+               return;
+
        /* Change the status if flags have been set */
 #if ENABLE_FEATURE_LESS_FLAGS
        if (option_mask32 & (FLAG_M|FLAG_m)) {
@@ -343,8 +444,8 @@ static void status_print(void)
 #endif
 
        clear_line();
-       if (cur_fline && cur_fline < max_fline - max_displayed_line) {
-               putchar(':');
+       if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+               bb_putchar(':');
                return;
        }
        p = "(END)";
@@ -375,12 +476,12 @@ static void cap_cur_fline(int nlines)
        }
 }
 
-static char controls[] =
+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"
        "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
        "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
-static char ctrlconv[] =
+static const char ctrlconv[] ALIGN1 =
        /* '\n': it's a former NUL - subst with '@', not 'J' */
        "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
        "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
@@ -437,6 +538,9 @@ static void print_found(const char *line)
  start:
                /* Most of the time doesn't find the regex, optimize for that */
                match_status = regexec(&pattern, line, 1, &match_structs, eflags);
+               /* if even "" matches, treat it as "not a match" */
+               if (match_structs.rm_so >= match_structs.rm_eo)
+                       match_status = 1;
        }
 
        if (!growline) {
@@ -486,7 +590,7 @@ static void print_ascii(const char *str)
 /* Print the buffer */
 static void buffer_print(void)
 {
-       int i;
+       unsigned i;
 
        move_cursor(0, 0);
        for (i = 0; i <= max_displayed_line; i++)
@@ -499,7 +603,7 @@ static void buffer_print(void)
 
 static void buffer_fill_and_print(void)
 {
-       int i;
+       unsigned i;
        for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
                buffer[i] = flines[cur_fline + i];
        }
@@ -561,7 +665,7 @@ static void open_file_and_read_lines(void)
 /* Reinitialize everything for a new file - free the memory and start over */
 static void reinitialize(void)
 {
-       int i;
+       unsigned i;
 
        if (flines) {
                for (i = 0; i <= max_fline; i++)
@@ -577,59 +681,65 @@ static void reinitialize(void)
        buffer_fill_and_print();
 }
 
-static void getch_nowait(char* input, int sz)
+static ssize_t getch_nowait(char* input, int sz)
 {
        ssize_t rd;
-       fd_set readfds;
- again:
-       fflush(stdout);
+       struct pollfd pfd[2];
 
-       /* NB: select returns whenever read will not block. Therefore:
-        * (a) with O_NONBLOCK'ed fds select will return immediately
-        * (b) if eof is reached, select will also return
-        *     because read will immediately return 0 bytes.
-        * Even if select says that input is available, read CAN block
+       pfd[0].fd = STDIN_FILENO;
+       pfd[0].events = POLLIN;
+       pfd[1].fd = kbd_fd;
+       pfd[1].events = POLLIN;
+ again:
+       tcsetattr(kbd_fd, TCSANOW, &term_less);
+       /* NB: select/poll returns whenever read will not block. Therefore:
+        * if eof is reached, select/poll will return immediately
+        * because read will immediately return 0 bytes.
+        * Even if select/poll says that input is available, read CAN block
         * (switch fd into O_NONBLOCK'ed mode to avoid it)
         */
-       FD_ZERO(&readfds);
+       rd = 1;
        if (max_fline <= cur_fline + max_displayed_line
         && eof_error > 0 /* did NOT reach eof yet */
        ) {
                /* We are interested in stdin */
-               FD_SET(0, &readfds);
+               rd = 0;
        }
-       FD_SET(kbd_fd, &readfds);
-       tcsetattr(kbd_fd, TCSANOW, &term_vi);
-       select(kbd_fd + 1, &readfds, NULL, NULL, NULL);
+       /* position cursor if line input is done */
+       if (less_gets_pos >= 0)
+               move_cursor(max_displayed_line + 2, less_gets_pos + 1);
+       fflush(stdout);
+       safe_poll(pfd + rd, 2 - rd, -1);
 
        input[0] = '\0';
-       ndelay_on(kbd_fd);
-       rd = read(kbd_fd, input, sz);
-       ndelay_off(kbd_fd);
-       if (rd < 0) {
-               /* No keyboard input, but we have input on stdin! */
-               if (errno != EAGAIN) /* Huh?? */
-                       return;
+       rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */
+       if (rd < 0 && errno == EAGAIN) {
+               /* No keyboard input -> we have input on stdin! */
                read_lines();
                buffer_fill_and_print();
                goto again;
        }
+       set_tty_cooked();
+       return rd;
 }
 
 /* Grab a character from input without requiring the return key. If the
  * character is ASCII \033, get more characters and assign certain sequences
  * special return codes. Note that this function works best with raw input. */
-static int less_getch(void)
+static int less_getch(int pos)
 {
-       char input[16];
+       unsigned char input[16];
        unsigned i;
+
  again:
-       getch_nowait(input, sizeof(input));
+       less_gets_pos = pos;
+       memset(input, 0, sizeof(input));
+       getch_nowait((char *)input, sizeof(input));
+       less_gets_pos = -1;
+
        /* Detect escape sequences (i.e. arrow keys) and handle
         * them accordingly */
-
        if (input[0] == '\033' && input[1] == '[') {
-               set_tty_cooked();
                i = input[2] - REAL_KEY_UP;
                if (i < 4)
                        return 20 + i;
@@ -638,32 +748,36 @@ static int less_getch(void)
                        return 24 + i;
                if (input[2] == REAL_KEY_HOME_XTERM)
                        return KEY_HOME;
+               if (input[2] == REAL_KEY_HOME_ALT)
+                       return KEY_HOME;
                if (input[2] == REAL_KEY_END_XTERM)
                        return KEY_END;
+               if (input[2] == REAL_KEY_END_ALT)
+                       return KEY_END;
                return 0;
        }
        /* Reject almost all control chars */
        i = input[0];
-       if (i < ' ' && i != 0x0d && i != 8) goto again;
-       set_tty_cooked();
+       if (i < ' ' && i != 0x0d && i != 8)
+               goto again;
        return i;
 }
 
 static char* less_gets(int sz)
 {
        char c;
-       int i = 0;
+       unsigned i = 0;
        char *result = xzalloc(1);
-       while (1) {
-               fflush(stdout);
 
-               /* I be damned if I know why is it needed *repeatedly*,
-                * but it is needed. Is it because of stdio? */
-               tcsetattr(kbd_fd, TCSANOW, &term_vi);
-
-               read(kbd_fd, &c, 1);
-               if (c == 0x0d)
+       while (1) {
+               c = '\0';
+               less_gets_pos = sz + i;
+               getch_nowait(&c, 1);
+               if (c == 0x0d) {
+                       result[i] = '\0';
+                       less_gets_pos = -1;
                        return result;
+               }
                if (c == 0x7f)
                        c = 8;
                if (c == 8 && i) {
@@ -674,18 +788,30 @@ static char* less_gets(int sz)
                        continue;
                if (i >= width - sz - 1)
                        continue; /* len limit */
-               putchar(c);
+               bb_putchar(c);
                result[i++] = c;
                result = xrealloc(result, i+1);
-               result[i] = '\0';
        }
 }
 
 static void examine_file(void)
 {
+       char *new_fname;
+
        print_statusline("Examine: ");
+       new_fname = less_gets(sizeof("Examine: ") - 1);
+       if (!new_fname[0]) {
+               status_print();
+ err:
+               free(new_fname);
+               return;
+       }
+       if (access(new_fname, R_OK) != 0) {
+               print_statusline("Cannot read this file");
+               goto err;
+       }
        free(filename);
-       filename = less_gets(sizeof("Examine: ")-1);
+       filename = new_fname;
        /* files start by = argv. why we assume that argv is infinitely long??
        files[num_files] = filename;
        current_file = num_files + 1;
@@ -713,7 +839,7 @@ static void change_file(int direction)
 
 static void remove_current_file(void)
 {
-       int i;
+       unsigned i;
 
        if (num_files < 2)
                return;
@@ -739,7 +865,7 @@ static void colon_process(void)
        /* Clear the current line and print a prompt */
        print_statusline(" :");
 
-       keypress = less_getch();
+       keypress = less_getch(2);
        switch (keypress) {
        case 'd':
                remove_current_file();
@@ -759,7 +885,7 @@ static void colon_process(void)
                change_file(-1);
                break;
        case 'q':
-               less_exit(0);
+               less_exit(EXIT_SUCCESS);
                break;
        case 'x':
                change_file(0);
@@ -785,13 +911,14 @@ static void goto_match(int match)
                match = 0;
        /* Try to find next match if eof isn't reached yet */
        if (match >= num_matches && eof_error > 0) {
-               cur_fline = MAXLINES; /* look as far as needed */
+               wanted_match = match; /* "I want to read until I see N'th match" */
                read_lines();
-               cap_cur_fline(cur_fline);
        }
        if (num_matches) {
                normalize_match_pos(match);
                buffer_line(match_lines[match_pos]);
+       } else {
+               print_statusline("No matches found");
        }
 }
 
@@ -806,7 +933,7 @@ static void fill_match_lines(unsigned pos)
                /* and we didn't match it last time */
                 && !(num_matches && match_lines[num_matches-1] == pos)
                ) {
-                       match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
+                       match_lines = xrealloc_vector(match_lines, 4, num_matches);
                        match_lines[num_matches++] = pos;
                }
                pos++;
@@ -829,7 +956,7 @@ static void regex_process(void)
 
        /* Get the uncompiled regular expression from the user */
        clear_line();
-       putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
+       bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
        uncomp_regex = less_gets(1);
        if (!uncomp_regex[0]) {
                free(uncomp_regex);
@@ -850,7 +977,7 @@ static void regex_process(void)
        match_pos = 0;
        fill_match_lines(0);
        while (match_pos < num_matches) {
-               if (match_lines[match_pos] > cur_fline)
+               if ((int)match_lines[match_pos] > cur_fline)
                        break;
                match_pos++;
        }
@@ -866,7 +993,7 @@ static void regex_process(void)
 
 static void number_process(int first_digit)
 {
-       int i = 1;
+       unsigned i;
        int num;
        char num_input[sizeof(int)*4]; /* more than enough */
        char keypress;
@@ -878,11 +1005,12 @@ static void number_process(int first_digit)
        printf(":%c", first_digit);
 
        /* Receive input until a letter is given */
+       i = 1;
        while (i < sizeof(num_input)-1) {
-               num_input[i] = less_getch();
+               num_input[i] = less_getch(i + 1);
                if (!num_input[i] || !isdigit(num_input[i]))
                        break;
-               putchar(num_input[i]);
+               bb_putchar(num_input[i]);
                i++;
        }
 
@@ -937,8 +1065,8 @@ static void flag_change(void)
        int keypress;
 
        clear_line();
-       putchar('-');
-       keypress = less_getch();
+       bb_putchar('-');
+       keypress = less_getch(1);
 
        switch (keypress) {
        case 'M':
@@ -962,8 +1090,8 @@ static void show_flag_status(void)
        int flag_val;
 
        clear_line();
-       putchar('_');
-       keypress = less_getch();
+       bb_putchar('_');
+       keypress = less_getch(1);
 
        switch (keypress) {
        case 'M':
@@ -995,13 +1123,13 @@ static void save_input_to_file(void)
 {
        const char *msg = "";
        char *current_line;
-       int i;
+       unsigned i;
        FILE *fp;
 
        print_statusline("Log file: ");
        current_line = less_gets(sizeof("Log file: ")-1);
-       if (strlen(current_line) > 0) {
-               fp = fopen(current_line, "w");
+       if (current_line[0]) {
+               fp = fopen_for_write(current_line);
                if (!fp) {
                        msg = "Error opening log file";
                        goto ret;
@@ -1022,7 +1150,7 @@ static void add_mark(void)
        int letter;
 
        print_statusline("Mark: ");
-       letter = less_getch();
+       letter = less_getch(sizeof("Mark: ") - 1);
 
        if (isalpha(letter)) {
                /* If we exceed 15 marks, start overwriting previous ones */
@@ -1043,7 +1171,7 @@ static void goto_mark(void)
        int i;
 
        print_statusline("Go to mark: ");
-       letter = less_getch();
+       letter = less_getch(sizeof("Go to mark: ") - 1);
        clear_line();
 
        if (isalpha(letter)) {
@@ -1063,41 +1191,40 @@ static void goto_mark(void)
 static char opp_bracket(char bracket)
 {
        switch (bracket) {
-       case '{': case '[':
-               return bracket + 2;
-       case '(':
-               return ')';
-       case '}': case ']':
-               return bracket - 2;
-       case ')':
-               return '(';
-       }
-       return 0;
+               case '{': case '[': /* '}' == '{' + 2. Same for '[' */
+                       bracket++;
+               case '(':           /* ')' == '(' + 1 */
+                       bracket++;
+                       break;
+               case '}': case ']':
+                       bracket--;
+               case ')':
+                       bracket--;
+                       break;
+       };
+       return bracket;
 }
 
 static void match_right_bracket(char bracket)
 {
-       int bracket_line = -1;
-       int i;
+       unsigned i;
 
        if (strchr(flines[cur_fline], bracket) == NULL) {
                print_statusline("No bracket in top line");
                return;
        }
+       bracket = opp_bracket(bracket);
        for (i = cur_fline + 1; i < max_fline; i++) {
-               if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
-                       bracket_line = i;
-                       break;
+               if (strchr(flines[i], bracket) != NULL) {
+                       buffer_line(i);
+                       return;
                }
        }
-       if (bracket_line == -1)
-               print_statusline("No matching bracket found");
-       buffer_line(bracket_line - max_displayed_line);
+       print_statusline("No matching bracket found");
 }
 
 static void match_left_bracket(char bracket)
 {
-       int bracket_line = -1;
        int i;
 
        if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
@@ -1105,15 +1232,14 @@ static void match_left_bracket(char bracket)
                return;
        }
 
+       bracket = opp_bracket(bracket);
        for (i = cur_fline + max_displayed_line; i >= 0; i--) {
-               if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
-                       bracket_line = i;
-                       break;
+               if (strchr(flines[i], bracket) != NULL) {
+                       buffer_line(i);
+                       return;
                }
        }
-       if (bracket_line == -1)
-               print_statusline("No matching bracket found");
-       buffer_line(bracket_line);
+       print_statusline("No matching bracket found");
 }
 #endif  /* FEATURE_LESS_BRACKETS */
 
@@ -1126,7 +1252,7 @@ static void keypress_process(int keypress)
        case KEY_UP: case 'y': case 'k':
                buffer_up(1);
                break;
-       case PAGE_DOWN: case ' ': case 'z':
+       case PAGE_DOWN: case ' ': case 'z': case 'f':
                buffer_down(max_displayed_line + 1);
                break;
        case PAGE_UP: case 'w': case 'b':
@@ -1147,7 +1273,7 @@ static void keypress_process(int keypress)
                buffer_line(cur_fline);
                break;
        case 'q': case 'Q':
-               less_exit(0);
+               less_exit(EXIT_SUCCESS);
                break;
 #if ENABLE_FEATURE_LESS_MARKS
        case 'm':
@@ -1218,21 +1344,22 @@ static void keypress_process(int keypress)
                number_process(keypress);
 }
 
-static void sig_catcher(int sig ATTRIBUTE_UNUSED)
+static void sig_catcher(int sig)
 {
-       set_tty_cooked();
-       exit(1);
+       less_exit(- sig);
 }
 
-int less_main(int argc, char **argv);
+int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int less_main(int argc, char **argv)
 {
        int keypress;
 
+       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(argc, argv, "EMmN~");
+       getopt32(argv, "EMmN~");
        argc -= optind;
        argv += optind;
        num_files = argc;
@@ -1242,6 +1369,10 @@ int less_main(int argc, char **argv)
         * is not a tty and turns into cat. This makes sense. */
        if (!isatty(STDOUT_FILENO))
                return bb_cat(argv);
+       kbd_fd = open(CURRENT_TTY, O_RDONLY);
+       if (kbd_fd < 0)
+               return bb_cat(argv);
+       ndelay_on(kbd_fd);
 
        if (!num_files) {
                if (isatty(STDIN_FILENO)) {
@@ -1252,12 +1383,10 @@ int less_main(int argc, char **argv)
        } else
                filename = xstrdup(files[0]);
 
-       kbd_fd = xopen(CURRENT_TTY, O_RDONLY);
-
        get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
        /* 20: two tabstops + 4 */
        if (width < 20 || max_displayed_line < 3)
-               bb_error_msg_and_die("too narrow here");
+               return bb_cat(argv);
        max_displayed_line -= 2;
 
        buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
@@ -1265,22 +1394,19 @@ int less_main(int argc, char **argv)
                empty_line_marker = "";
 
        tcgetattr(kbd_fd, &term_orig);
-       signal(SIGTERM, sig_catcher);
-       signal(SIGINT, sig_catcher);
-       term_vi = term_orig;
-       term_vi.c_lflag &= ~(ICANON | ECHO);
-       term_vi.c_iflag &= ~(IXON | ICRNL);
-       /*term_vi.c_oflag &= ~ONLCR;*/
-       term_vi.c_cc[VMIN] = 1;
-       term_vi.c_cc[VTIME] = 0;
-
-       /* Want to do it just once, but it doesn't work, */
-       /* so we are redoing it (see code above). Mystery... */
-       /*tcsetattr(kbd_fd, TCSANOW, &term_vi);*/
+       term_less = term_orig;
+       term_less.c_lflag &= ~(ICANON | ECHO);
+       term_less.c_iflag &= ~(IXON | ICRNL);
+       /*term_less.c_oflag &= ~ONLCR;*/
+       term_less.c_cc[VMIN] = 1;
+       term_less.c_cc[VTIME] = 0;
+
+       /* We want to restore term_orig on exit */
+       bb_signals(BB_FATAL_SIGS, sig_catcher);
 
        reinitialize();
        while (1) {
-               keypress = less_getch();
+               keypress = less_getch(-1); /* -1: do not position cursor */
                keypress_process(keypress);
        }
 }