config: add size estimations for a few applets
[oweals/busybox.git] / miscutils / less.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini less implementation for busybox
4  *
5  * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 /*
10  * TODO:
11  * - Add more regular expression support - search modifiers, certain matches, etc.
12  * - Add more complex bracket searching - currently, nested brackets are
13  *   not considered.
14  * - Add support for "F" as an input. This causes less to act in
15  *   a similar way to tail -f.
16  * - Allow horizontal scrolling.
17  *
18  * Notes:
19  * - the inp file pointer is used so that keyboard input works after
20  *   redirected input has been read from stdin
21  */
22 //config:config LESS
23 //config:       bool "less (15 kb)"
24 //config:       default y
25 //config:       help
26 //config:       'less' is a pager, meaning that it displays text files. It possesses
27 //config:       a wide array of features, and is an improvement over 'more'.
28 //config:
29 //config:config FEATURE_LESS_MAXLINES
30 //config:       int "Max number of input lines less will try to eat"
31 //config:       default 9999999
32 //config:       depends on LESS
33 //config:
34 //config:config FEATURE_LESS_BRACKETS
35 //config:       bool "Enable bracket searching"
36 //config:       default y
37 //config:       depends on LESS
38 //config:       help
39 //config:       This option adds the capability to search for matching left and right
40 //config:       brackets, facilitating programming.
41 //config:
42 //config:config FEATURE_LESS_FLAGS
43 //config:       bool "Enable -m/-M"
44 //config:       default y
45 //config:       depends on LESS
46 //config:       help
47 //config:       The -M/-m flag enables a more sophisticated status line.
48 //config:
49 //config:config FEATURE_LESS_TRUNCATE
50 //config:       bool "Enable -S"
51 //config:       default y
52 //config:       depends on LESS
53 //config:       help
54 //config:       The -S flag causes long lines to be truncated rather than
55 //config:       wrapped.
56 //config:
57 //config:config FEATURE_LESS_MARKS
58 //config:       bool "Enable marks"
59 //config:       default y
60 //config:       depends on LESS
61 //config:       help
62 //config:       Marks enable positions in a file to be stored for easy reference.
63 //config:
64 //config:config FEATURE_LESS_REGEXP
65 //config:       bool "Enable regular expressions"
66 //config:       default y
67 //config:       depends on LESS
68 //config:       help
69 //config:       Enable regular expressions, allowing complex file searches.
70 //config:
71 //config:config FEATURE_LESS_WINCH
72 //config:       bool "Enable automatic resizing on window size changes"
73 //config:       default y
74 //config:       depends on LESS
75 //config:       help
76 //config:       Makes less track window size changes.
77 //config:
78 //config:config FEATURE_LESS_ASK_TERMINAL
79 //config:       bool "Use 'tell me cursor position' ESC sequence to measure window"
80 //config:       default y
81 //config:       depends on FEATURE_LESS_WINCH
82 //config:       help
83 //config:       Makes less track window size changes.
84 //config:       If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
85 //config:       this option makes less perform a last-ditch effort to find it:
86 //config:       position cursor to 999,999 and ask terminal to report real
87 //config:       cursor position using "ESC [ 6 n" escape sequence, then read stdin.
88 //config:       This is not clean but helps a lot on serial lines and such.
89 //config:
90 //config:config FEATURE_LESS_DASHCMD
91 //config:       bool "Enable flag changes ('-' command)"
92 //config:       default y
93 //config:       depends on LESS
94 //config:       help
95 //config:       This enables the ability to change command-line flags within
96 //config:       less itself ('-' keyboard command).
97 //config:
98 //config:config FEATURE_LESS_LINENUMS
99 //config:       bool "Enable -N (dynamic switching of line numbers)"
100 //config:       default y
101 //config:       depends on FEATURE_LESS_DASHCMD
102
103 //applet:IF_LESS(APPLET(less, BB_DIR_USR_BIN, BB_SUID_DROP))
104
105 //kbuild:lib-$(CONFIG_LESS) += less.o
106
107 //usage:#define less_trivial_usage
108 //usage:       "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
109 //usage:       "N" IF_FEATURE_LESS_TRUNCATE("S") "h~] [FILE]..."
110 //usage:#define less_full_usage "\n\n"
111 //usage:       "View FILE (or stdin) one screenful at a time\n"
112 //usage:     "\n        -E      Quit once the end of a file is reached"
113 //usage:        IF_FEATURE_LESS_REGEXP(
114 //usage:     "\n        -I      Ignore case in all searches"
115 //usage:        )
116 //usage:        IF_FEATURE_LESS_FLAGS(
117 //usage:     "\n        -M,-m   Display status line with line numbers"
118 //usage:     "\n                and percentage through the file"
119 //usage:        )
120 //usage:     "\n        -N      Prefix line number to each line"
121 //usage:        IF_FEATURE_LESS_TRUNCATE(
122 //usage:     "\n        -S      Truncate long lines"
123 //usage:        )
124 //usage:     "\n        -~      Suppress ~s displayed past EOF"
125
126 #include <sched.h>  /* sched_yield() */
127
128 #include "libbb.h"
129 #include "common_bufsiz.h"
130 #if ENABLE_FEATURE_LESS_REGEXP
131 #include "xregex.h"
132 #endif
133
134
135 #define ESC "\033"
136 /* The escape codes for highlighted and normal text */
137 #define HIGHLIGHT   ESC"[7m"
138 #define NORMAL      ESC"[m"
139 /* The escape code to home and clear to the end of screen */
140 #define CLEAR       ESC"[H"ESC"[J"
141 /* The escape code to clear to the end of line */
142 #define CLEAR_2_EOL ESC"[K"
143
144 enum {
145 /* Absolute max of lines eaten */
146         MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
147 /* This many "after the end" lines we will show (at max) */
148         TILDES = 1,
149 };
150
151 /* Command line options */
152 enum {
153         FLAG_E = 1 << 0,
154         FLAG_M = 1 << 1,
155         FLAG_m = 1 << 2,
156         FLAG_N = 1 << 3,
157         FLAG_TILDE = 1 << 4,
158         FLAG_I = 1 << 5,
159         FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE,
160 /* hijack command line options variable for internal state vars */
161         LESS_STATE_MATCH_BACKWARDS = 1 << 15,
162 };
163
164 #if !ENABLE_FEATURE_LESS_REGEXP
165 enum { pattern_valid = 0 };
166 #endif
167
168 struct globals {
169         int cur_fline; /* signed */
170         int kbd_fd;  /* fd to get input from */
171         int kbd_fd_orig_flags;
172         int less_gets_pos;
173 /* last position in last line, taking into account tabs */
174         size_t last_line_pos;
175         unsigned max_fline;
176         unsigned max_lineno; /* this one tracks linewrap */
177         unsigned max_displayed_line;
178         unsigned width;
179 #if ENABLE_FEATURE_LESS_WINCH
180         unsigned winch_counter;
181 #endif
182         ssize_t eof_error; /* eof if 0, error if < 0 */
183         ssize_t readpos;
184         ssize_t readeof; /* must be signed */
185         const char **buffer;
186         const char **flines;
187         const char *empty_line_marker;
188         unsigned num_files;
189         unsigned current_file;
190         char *filename;
191         char **files;
192 #if ENABLE_FEATURE_LESS_FLAGS
193         int num_lines; /* a flag if < 0, line count if >= 0 */
194 # define REOPEN_AND_COUNT (-1)
195 # define REOPEN_STDIN     (-2)
196 # define NOT_REGULAR_FILE (-3)
197 #endif
198 #if ENABLE_FEATURE_LESS_MARKS
199         unsigned num_marks;
200         unsigned mark_lines[15][2];
201 #endif
202 #if ENABLE_FEATURE_LESS_REGEXP
203         unsigned *match_lines;
204         int match_pos; /* signed! */
205         int wanted_match; /* signed! */
206         int num_matches;
207         regex_t pattern;
208         smallint pattern_valid;
209 #endif
210 #if ENABLE_FEATURE_LESS_ASK_TERMINAL
211         smallint winsize_err;
212 #endif
213         smallint terminated;
214         struct termios term_orig, term_less;
215         char kbd_input[KEYCODE_BUFFER_SIZE];
216 };
217 #define G (*ptr_to_globals)
218 #define cur_fline           (G.cur_fline         )
219 #define kbd_fd              (G.kbd_fd            )
220 #define less_gets_pos       (G.less_gets_pos     )
221 #define last_line_pos       (G.last_line_pos     )
222 #define max_fline           (G.max_fline         )
223 #define max_lineno          (G.max_lineno        )
224 #define max_displayed_line  (G.max_displayed_line)
225 #define width               (G.width             )
226 #define winch_counter       (G.winch_counter     )
227 /* This one is 100% not cached by compiler on read access */
228 #define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
229 #define eof_error           (G.eof_error         )
230 #define readpos             (G.readpos           )
231 #define readeof             (G.readeof           )
232 #define buffer              (G.buffer            )
233 #define flines              (G.flines            )
234 #define empty_line_marker   (G.empty_line_marker )
235 #define num_files           (G.num_files         )
236 #define current_file        (G.current_file      )
237 #define filename            (G.filename          )
238 #define files               (G.files             )
239 #define num_lines           (G.num_lines         )
240 #define num_marks           (G.num_marks         )
241 #define mark_lines          (G.mark_lines        )
242 #if ENABLE_FEATURE_LESS_REGEXP
243 #define match_lines         (G.match_lines       )
244 #define match_pos           (G.match_pos         )
245 #define num_matches         (G.num_matches       )
246 #define wanted_match        (G.wanted_match      )
247 #define pattern             (G.pattern           )
248 #define pattern_valid       (G.pattern_valid     )
249 #endif
250 #define terminated          (G.terminated        )
251 #define term_orig           (G.term_orig         )
252 #define term_less           (G.term_less         )
253 #define kbd_input           (G.kbd_input         )
254 #define INIT_G() do { \
255         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
256         less_gets_pos = -1; \
257         empty_line_marker = "~"; \
258         current_file = 1; \
259         eof_error = 1; \
260         terminated = 1; \
261         IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
262 } while (0)
263
264 /* flines[] are lines read from stdin, each in malloc'ed buffer.
265  * Line numbers are stored as uint32_t prepended to each line.
266  * Pointer is adjusted so that flines[i] points directly past
267  * line number. Accessor: */
268 #define MEMPTR(p) ((char*)(p) - 4)
269 #define LINENO(p) (*(uint32_t*)((p) - 4))
270
271
272 /* Reset terminal input to normal */
273 static void set_tty_cooked(void)
274 {
275         fflush_all();
276         tcsetattr(kbd_fd, TCSANOW, &term_orig);
277 }
278
279 /* Move the cursor to a position (x,y), where (0,0) is the
280    top-left corner of the console */
281 static void move_cursor(int line, int col)
282 {
283         printf(ESC"[%u;%uH", line, col);
284 }
285
286 static void clear_line(void)
287 {
288         printf(ESC"[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
289 }
290
291 static void print_hilite(const char *str)
292 {
293         printf(HIGHLIGHT"%s"NORMAL, str);
294 }
295
296 static void print_statusline(const char *str)
297 {
298         clear_line();
299         printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
300 }
301
302 /* Exit the program gracefully */
303 static void less_exit(int code)
304 {
305         set_tty_cooked();
306         if (!(G.kbd_fd_orig_flags & O_NONBLOCK))
307                 ndelay_off(kbd_fd);
308         clear_line();
309         if (code < 0)
310                 kill_myself_with_sig(- code); /* does not return */
311         exit(code);
312 }
313
314 #if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
315  || ENABLE_FEATURE_LESS_WINCH
316 static void re_wrap(void)
317 {
318         int w = width;
319         int new_line_pos;
320         int src_idx;
321         int dst_idx;
322         int new_cur_fline = 0;
323         uint32_t lineno;
324         char linebuf[w + 1];
325         const char **old_flines = flines;
326         const char *s;
327         char **new_flines = NULL;
328         char *d;
329
330         if (option_mask32 & FLAG_N)
331                 w -= 8;
332
333         src_idx = 0;
334         dst_idx = 0;
335         s = old_flines[0];
336         lineno = LINENO(s);
337         d = linebuf;
338         new_line_pos = 0;
339         while (1) {
340                 *d = *s;
341                 if (*d != '\0') {
342                         new_line_pos++;
343                         if (*d == '\t') { /* tab */
344                                 new_line_pos += 7;
345                                 new_line_pos &= (~7);
346                         }
347                         s++;
348                         d++;
349                         if (new_line_pos >= w) {
350                                 int sz;
351                                 /* new line is full, create next one */
352                                 *d = '\0';
353  next_new:
354                                 sz = (d - linebuf) + 1; /* + 1: NUL */
355                                 d = ((char*)xmalloc(sz + 4)) + 4;
356                                 LINENO(d) = lineno;
357                                 memcpy(d, linebuf, sz);
358                                 new_flines = xrealloc_vector(new_flines, 8, dst_idx);
359                                 new_flines[dst_idx] = d;
360                                 dst_idx++;
361                                 if (new_line_pos < w) {
362                                         /* if we came here thru "goto next_new" */
363                                         if (src_idx > max_fline)
364                                                 break;
365                                         lineno = LINENO(s);
366                                 }
367                                 d = linebuf;
368                                 new_line_pos = 0;
369                         }
370                         continue;
371                 }
372                 /* *d == NUL: old line ended, go to next old one */
373                 free(MEMPTR(old_flines[src_idx]));
374                 /* btw, convert cur_fline... */
375                 if (cur_fline == src_idx)
376                         new_cur_fline = dst_idx;
377                 src_idx++;
378                 /* no more lines? finish last new line (and exit the loop) */
379                 if (src_idx > max_fline)
380                         goto next_new;
381                 s = old_flines[src_idx];
382                 if (lineno != LINENO(s)) {
383                         /* this is not a continuation line!
384                          * create next _new_ line too */
385                         goto next_new;
386                 }
387         }
388
389         free(old_flines);
390         flines = (const char **)new_flines;
391
392         max_fline = dst_idx - 1;
393         last_line_pos = new_line_pos;
394         cur_fline = new_cur_fline;
395         /* max_lineno is screen-size independent */
396 #if ENABLE_FEATURE_LESS_REGEXP
397         pattern_valid = 0;
398 #endif
399 }
400 #endif
401
402 #if ENABLE_FEATURE_LESS_REGEXP
403 static void fill_match_lines(unsigned pos);
404 #else
405 #define fill_match_lines(pos) ((void)0)
406 #endif
407
408 static int at_end(void)
409 {
410         return (option_mask32 & FLAG_S)
411                 ? !(cur_fline <= max_fline &&
412                         max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
413                 : !(max_fline > cur_fline + max_displayed_line);
414 }
415
416 /* Devilishly complex routine.
417  *
418  * Has to deal with EOF and EPIPE on input,
419  * with line wrapping, with last line not ending in '\n'
420  * (possibly not ending YET!), with backspace and tabs.
421  * It reads input again if last time we got an EOF (thus supporting
422  * growing files) or EPIPE (watching output of slow process like make).
423  *
424  * Variables used:
425  * flines[] - array of lines already read. Linewrap may cause
426  *      one source file line to occupy several flines[n].
427  * flines[max_fline] - last line, possibly incomplete.
428  * terminated - 1 if flines[max_fline] is 'terminated'
429  *      (if there was '\n' [which isn't stored itself, we just remember
430  *      that it was seen])
431  * max_lineno - last line's number, this one doesn't increment
432  *      on line wrap, only on "real" new lines.
433  * readbuf[0..readeof-1] - small preliminary buffer.
434  * readbuf[readpos] - next character to add to current line.
435  * last_line_pos - screen line position of next char to be read
436  *      (takes into account tabs and backspaces)
437  * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
438  *
439  * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
440  * "/search on very long input" and "reaching max line count" corner cases.
441  */
442 static void read_lines(void)
443 {
444         char *current_line, *p;
445         int w = width;
446         char last_terminated = terminated;
447         time_t last_time = 0;
448         int retry_EAGAIN = 2;
449 #if ENABLE_FEATURE_LESS_REGEXP
450         unsigned old_max_fline = max_fline;
451 #endif
452
453 #define readbuf bb_common_bufsiz1
454         setup_common_bufsiz();
455
456         /* (careful: max_fline can be -1) */
457         if (max_fline + 1 > MAXLINES)
458                 return;
459
460         if (option_mask32 & FLAG_N)
461                 w -= 8;
462
463         p = current_line = ((char*)xmalloc(w + 5)) + 4;
464         if (!last_terminated) {
465                 const char *cp = flines[max_fline];
466                 p = stpcpy(p, cp);
467                 free(MEMPTR(cp));
468                 /* last_line_pos is still valid from previous read_lines() */
469         } else {
470                 max_fline++;
471                 last_line_pos = 0;
472         }
473
474         while (1) { /* read lines until we reach cur_fline or wanted_match */
475                 *p = '\0';
476                 terminated = 0;
477                 while (1) { /* read chars until we have a line */
478                         char c;
479                         /* if no unprocessed chars left, eat more */
480                         if (readpos >= readeof) {
481                                 int flags = ndelay_on(0);
482
483                                 while (1) {
484                                         time_t t;
485
486                                         errno = 0;
487                                         eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
488                                         if (errno != EAGAIN)
489                                                 break;
490                                         t = time(NULL);
491                                         if (t != last_time) {
492                                                 last_time = t;
493                                                 if (--retry_EAGAIN < 0)
494                                                         break;
495                                         }
496                                         sched_yield();
497                                 }
498                                 fcntl(0, F_SETFL, flags); /* ndelay_off(0) */
499                                 readpos = 0;
500                                 readeof = eof_error;
501                                 if (eof_error <= 0)
502                                         goto reached_eof;
503                                 retry_EAGAIN = 1;
504                         }
505                         c = readbuf[readpos];
506                         /* backspace? [needed for manpages] */
507                         /* <tab><bs> is (a) insane and */
508                         /* (b) harder to do correctly, so we refuse to do it */
509                         if (c == '\x8' && last_line_pos && p[-1] != '\t') {
510                                 readpos++; /* eat it */
511                                 last_line_pos--;
512                         /* was buggy (p could end up <= current_line)... */
513                                 *--p = '\0';
514                                 continue;
515                         }
516                         {
517                                 size_t new_last_line_pos = last_line_pos + 1;
518                                 if (c == '\t') {
519                                         new_last_line_pos += 7;
520                                         new_last_line_pos &= (~7);
521                                 }
522                                 if ((int)new_last_line_pos > w)
523                                         break;
524                                 last_line_pos = new_last_line_pos;
525                         }
526                         /* ok, we will eat this char */
527                         readpos++;
528                         if (c == '\n') {
529                                 terminated = 1;
530                                 last_line_pos = 0;
531                                 break;
532                         }
533                         /* NUL is substituted by '\n'! */
534                         if (c == '\0') c = '\n';
535                         *p++ = c;
536                         *p = '\0';
537                 } /* end of "read chars until we have a line" loop */
538 #if 0
539 //BUG: also triggers on this:
540 // { printf "\nfoo\n"; sleep 1; printf "\nbar\n"; } | less
541 // (resulting in lost empty line between "foo" and "bar" lines)
542 // the "terminated" logic needs fixing (or explaining)
543                 /* Corner case: linewrap with only "" wrapping to next line */
544                 /* Looks ugly on screen, so we do not store this empty line */
545                 if (!last_terminated && !current_line[0]) {
546                         last_terminated = 1;
547                         max_lineno++;
548                         continue;
549                 }
550 #endif
551  reached_eof:
552                 last_terminated = terminated;
553                 flines = xrealloc_vector(flines, 8, max_fline);
554
555                 flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
556                 LINENO(flines[max_fline]) = max_lineno;
557                 if (terminated)
558                         max_lineno++;
559
560                 if (max_fline >= MAXLINES) {
561                         eof_error = 0; /* Pretend we saw EOF */
562                         break;
563                 }
564                 if (!at_end()) {
565 #if !ENABLE_FEATURE_LESS_REGEXP
566                         break;
567 #else
568                         if (wanted_match >= num_matches) { /* goto_match called us */
569                                 fill_match_lines(old_max_fline);
570                                 old_max_fline = max_fline;
571                         }
572                         if (wanted_match < num_matches)
573                                 break;
574 #endif
575                 }
576                 if (eof_error <= 0) {
577                         break;
578                 }
579                 max_fline++;
580                 current_line = ((char*)xmalloc(w + 5)) + 4;
581                 p = current_line;
582                 last_line_pos = 0;
583         } /* end of "read lines until we reach cur_fline" loop */
584
585         if (eof_error < 0) {
586                 if (errno == EAGAIN) {
587                         eof_error = 1;
588                 } else {
589                         print_statusline(bb_msg_read_error);
590                 }
591         }
592 #if ENABLE_FEATURE_LESS_FLAGS
593         else if (eof_error == 0)
594                 num_lines = max_lineno;
595 #endif
596
597         fill_match_lines(old_max_fline);
598 #if ENABLE_FEATURE_LESS_REGEXP
599         /* prevent us from being stuck in search for a match */
600         wanted_match = -1;
601 #endif
602 #undef readbuf
603 }
604
605 #if ENABLE_FEATURE_LESS_FLAGS
606 static int safe_lineno(int fline)
607 {
608         if (fline >= max_fline)
609                 fline = max_fline - 1;
610
611         /* also catches empty file (max_fline == 0) */
612         if (fline < 0)
613                 return 0;
614
615         return LINENO(flines[fline]) + 1;
616 }
617
618 /* count number of lines in file */
619 static void update_num_lines(void)
620 {
621         int count, fd;
622         struct stat stbuf;
623         ssize_t len, i;
624         char buf[4096];
625
626         /* only do this for regular files */
627         if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
628                 count = 0;
629                 fd = open("/proc/self/fd/0", O_RDONLY);
630                 if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
631                         /* "filename" is valid only if REOPEN_AND_COUNT */
632                         fd = open(filename, O_RDONLY);
633                 }
634                 if (fd < 0) {
635                         /* somebody stole my file! */
636                         num_lines = NOT_REGULAR_FILE;
637                         return;
638                 }
639                 if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
640                         num_lines = NOT_REGULAR_FILE;
641                         goto do_close;
642                 }
643                 while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
644                         for (i = 0; i < len; ++i) {
645                                 if (buf[i] == '\n' && ++count == MAXLINES)
646                                         goto done;
647                         }
648                 }
649  done:
650                 num_lines = count;
651  do_close:
652                 close(fd);
653         }
654 }
655
656 /* Print a status line if -M was specified */
657 static void m_status_print(void)
658 {
659         int first, last;
660         unsigned percent;
661
662         if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
663                 return;
664
665         clear_line();
666         printf(HIGHLIGHT"%s", filename);
667         if (num_files > 1)
668                 printf(" (file %i of %i)", current_file, num_files);
669
670         first = safe_lineno(cur_fline);
671         last = (option_mask32 & FLAG_S)
672                         ? MIN(first + max_displayed_line, max_lineno)
673                         : safe_lineno(cur_fline + max_displayed_line);
674         printf(" lines %i-%i", first, last);
675
676         update_num_lines();
677         if (num_lines >= 0)
678                 printf("/%i", num_lines);
679
680         if (at_end()) {
681                 printf(" (END)");
682                 if (num_files > 1 && current_file != num_files)
683                         printf(" - next: %s", files[current_file]);
684         } else if (num_lines > 0) {
685                 percent = (100 * last + num_lines/2) / num_lines;
686                 printf(" %i%%", percent <= 100 ? percent : 100);
687         }
688         printf(NORMAL);
689 }
690 #endif
691
692 /* Print the status line */
693 static void status_print(void)
694 {
695         const char *p;
696
697         if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
698                 return;
699
700         /* Change the status if flags have been set */
701 #if ENABLE_FEATURE_LESS_FLAGS
702         if (option_mask32 & (FLAG_M|FLAG_m)) {
703                 m_status_print();
704                 return;
705         }
706         /* No flags set */
707 #endif
708
709         clear_line();
710         if (cur_fline && !at_end()) {
711                 bb_putchar(':');
712                 return;
713         }
714         p = "(END)";
715         if (!cur_fline)
716                 p = filename;
717         if (num_files > 1) {
718                 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
719                                 p, current_file, num_files);
720                 return;
721         }
722         print_hilite(p);
723 }
724
725 static const char controls[] ALIGN1 =
726         /* NUL: never encountered; TAB: not converted */
727         /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
728         "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
729         "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
730 static const char ctrlconv[] ALIGN1 =
731         /* why 40 instead of 4a below? - it is a replacement for '\n'.
732          * '\n' is a former NUL - we subst it with @, not J */
733         "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
734         "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
735
736 static void print_lineno(const char *line)
737 {
738         const char *fmt = "        ";
739         unsigned n = n; /* for compiler */
740
741         if (line != empty_line_marker) {
742                 /* Width of 7 preserves tab spacing in the text */
743                 fmt = "%7u ";
744                 n = LINENO(line) + 1;
745                 if (n > 9999999 && MAXLINES > 9999999) {
746                         n %= 10000000;
747                         fmt = "%07u ";
748                 }
749         }
750         printf(fmt, n);
751 }
752
753
754 #if ENABLE_FEATURE_LESS_REGEXP
755 static void print_found(const char *line)
756 {
757         int match_status;
758         int eflags;
759         char *growline;
760         regmatch_t match_structs;
761
762         char buf[width+1];
763         const char *str = line;
764         char *p = buf;
765         size_t n;
766
767         while (*str) {
768                 n = strcspn(str, controls);
769                 if (n) {
770                         if (!str[n]) break;
771                         memcpy(p, str, n);
772                         p += n;
773                         str += n;
774                 }
775                 n = strspn(str, controls);
776                 memset(p, '.', n);
777                 p += n;
778                 str += n;
779         }
780         strcpy(p, str);
781
782         /* buf[] holds quarantined version of str */
783
784         /* Each part of the line that matches has the HIGHLIGHT
785          * and NORMAL escape sequences placed around it.
786          * NB: we regex against line, but insert text
787          * from quarantined copy (buf[]) */
788         str = buf;
789         growline = NULL;
790         eflags = 0;
791         goto start;
792
793         while (match_status == 0) {
794                 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
795                                 growline ? growline : "",
796                                 (int)match_structs.rm_so, str,
797                                 (int)(match_structs.rm_eo - match_structs.rm_so),
798                                                 str + match_structs.rm_so);
799                 free(growline);
800                 growline = new;
801                 str += match_structs.rm_eo;
802                 line += match_structs.rm_eo;
803                 eflags = REG_NOTBOL;
804  start:
805                 /* Most of the time doesn't find the regex, optimize for that */
806                 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
807                 /* if even "" matches, treat it as "not a match" */
808                 if (match_structs.rm_so >= match_structs.rm_eo)
809                         match_status = 1;
810         }
811
812         printf("%s%s\n", growline ? growline : "", str);
813         free(growline);
814 }
815 #else
816 void print_found(const char *line);
817 #endif
818
819 static void print_ascii(const char *str)
820 {
821         char buf[width+1];
822         char *p;
823         size_t n;
824
825         while (*str) {
826                 n = strcspn(str, controls);
827                 if (n) {
828                         if (!str[n]) break;
829                         printf("%.*s", (int) n, str);
830                         str += n;
831                 }
832                 n = strspn(str, controls);
833                 p = buf;
834                 do {
835                         if (*str == 0x7f)
836                                 *p++ = '?';
837                         else if (*str == (char)0x9b)
838                         /* VT100's CSI, aka Meta-ESC. Who's inventor? */
839                         /* I want to know who committed this sin */
840                                 *p++ = '{';
841                         else
842                                 *p++ = ctrlconv[(unsigned char)*str];
843                         str++;
844                 } while (--n);
845                 *p = '\0';
846                 print_hilite(buf);
847         }
848         puts(str);
849 }
850
851 /* Print the buffer */
852 static void buffer_print(void)
853 {
854         unsigned i;
855
856         move_cursor(0, 0);
857         for (i = 0; i <= max_displayed_line; i++) {
858                 printf(CLEAR_2_EOL);
859                 if (option_mask32 & FLAG_N)
860                         print_lineno(buffer[i]);
861                 if (pattern_valid)
862                         print_found(buffer[i]);
863                 else
864                         print_ascii(buffer[i]);
865         }
866         if ((option_mask32 & FLAG_E)
867          && eof_error <= 0
868          && (max_fline - cur_fline) <= max_displayed_line
869         ) {
870                 less_exit(EXIT_SUCCESS);
871         }
872         status_print();
873 }
874
875 static void buffer_fill_and_print(void)
876 {
877         unsigned i;
878 #if ENABLE_FEATURE_LESS_TRUNCATE
879         int fpos = cur_fline;
880
881         if (option_mask32 & FLAG_S) {
882                 /* Go back to the beginning of this line */
883                 while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
884                         fpos--;
885         }
886
887         i = 0;
888         while (i <= max_displayed_line && fpos <= max_fline) {
889                 int lineno = LINENO(flines[fpos]);
890                 buffer[i] = flines[fpos];
891                 i++;
892                 do {
893                         fpos++;
894                 } while ((fpos <= max_fline)
895                       && (option_mask32 & FLAG_S)
896                       && lineno == LINENO(flines[fpos])
897                 );
898         }
899 #else
900         for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
901                 buffer[i] = flines[cur_fline + i];
902         }
903 #endif
904         for (; i <= max_displayed_line; i++) {
905                 buffer[i] = empty_line_marker;
906         }
907         buffer_print();
908 }
909
910 /* move cur_fline to a given line number, reading lines if necessary */
911 static void goto_lineno(int target)
912 {
913         if (target <= 0 ) {
914                 cur_fline = 0;
915         }
916         else if (target > LINENO(flines[cur_fline])) {
917  retry:
918                 while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
919                         ++cur_fline;
920                 /* target not reached but more input is available */
921                 if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
922                         read_lines();
923                         goto retry;
924                 }
925         }
926         else {
927                 /* search backwards through already-read lines */
928                 while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
929                         --cur_fline;
930         }
931 }
932
933 static void cap_cur_fline(void)
934 {
935         if ((option_mask32 & FLAG_S)) {
936                 if (cur_fline > max_fline)
937                         cur_fline = max_fline;
938                 if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
939                         goto_lineno(max_lineno - max_displayed_line + TILDES);
940                         read_lines();
941                 }
942         }
943         else {
944                 if (cur_fline + max_displayed_line > max_fline + TILDES)
945                         cur_fline = max_fline - max_displayed_line + TILDES;
946                 if (cur_fline < 0)
947                         cur_fline = 0;
948         }
949 }
950
951 /* Move the buffer up and down in the file in order to scroll */
952 static void buffer_down(int nlines)
953 {
954         if ((option_mask32 & FLAG_S))
955                 goto_lineno(LINENO(flines[cur_fline]) + nlines);
956         else
957                 cur_fline += nlines;
958         read_lines();
959         cap_cur_fline();
960         buffer_fill_and_print();
961 }
962
963 static void buffer_up(int nlines)
964 {
965         if ((option_mask32 & FLAG_S)) {
966                 goto_lineno(LINENO(flines[cur_fline]) - nlines);
967         }
968         else {
969                 cur_fline -= nlines;
970                 if (cur_fline < 0)
971                         cur_fline = 0;
972         }
973         read_lines();
974         buffer_fill_and_print();
975 }
976
977 /* display a given line where the argument can be either an index into
978  * the flines array or a line number */
979 static void buffer_to_line(int linenum, int is_lineno)
980 {
981         if (linenum <= 0)
982                 cur_fline = 0;
983         else if (is_lineno)
984                 goto_lineno(linenum);
985         else
986                 cur_fline = linenum;
987         read_lines();
988         cap_cur_fline();
989         buffer_fill_and_print();
990 }
991
992 static void buffer_line(int linenum)
993 {
994         buffer_to_line(linenum, FALSE);
995 }
996
997 static void buffer_lineno(int lineno)
998 {
999         buffer_to_line(lineno, TRUE);
1000 }
1001
1002 static void open_file_and_read_lines(void)
1003 {
1004         if (filename) {
1005                 xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
1006 #if ENABLE_FEATURE_LESS_FLAGS
1007                 num_lines = REOPEN_AND_COUNT;
1008 #endif
1009         } else {
1010                 /* "less" with no arguments in argv[] */
1011                 /* For status line only */
1012                 filename = xstrdup(bb_msg_standard_input);
1013 #if ENABLE_FEATURE_LESS_FLAGS
1014                 num_lines = REOPEN_STDIN;
1015 #endif
1016         }
1017         readpos = 0;
1018         readeof = 0;
1019         last_line_pos = 0;
1020         terminated = 1;
1021         read_lines();
1022 }
1023
1024 /* Reinitialize everything for a new file - free the memory and start over */
1025 static void reinitialize(void)
1026 {
1027         unsigned i;
1028
1029         if (flines) {
1030                 for (i = 0; i <= max_fline; i++)
1031                         free(MEMPTR(flines[i]));
1032                 free(flines);
1033                 flines = NULL;
1034         }
1035
1036         max_fline = -1;
1037         cur_fline = 0;
1038         max_lineno = 0;
1039         open_file_and_read_lines();
1040 #if ENABLE_FEATURE_LESS_ASK_TERMINAL
1041         if (G.winsize_err)
1042                 printf(ESC"[999;999H" ESC"[6n");
1043 #endif
1044         buffer_fill_and_print();
1045 }
1046
1047 static int64_t getch_nowait(void)
1048 {
1049         int rd;
1050         int64_t key64;
1051         struct pollfd pfd[2];
1052
1053         pfd[0].fd = STDIN_FILENO;
1054         pfd[0].events = POLLIN;
1055         pfd[1].fd = kbd_fd;
1056         pfd[1].events = POLLIN;
1057  again:
1058         tcsetattr(kbd_fd, TCSANOW, &term_less);
1059         /* NB: select/poll returns whenever read will not block. Therefore:
1060          * if eof is reached, select/poll will return immediately
1061          * because read will immediately return 0 bytes.
1062          * Even if select/poll says that input is available, read CAN block
1063          * (switch fd into O_NONBLOCK'ed mode to avoid it)
1064          */
1065         rd = 1;
1066         /* Are we interested in stdin? */
1067         if (at_end()) {
1068                 if (eof_error > 0) /* did NOT reach eof yet */
1069                         rd = 0; /* yes, we are interested in stdin */
1070         }
1071         /* Position cursor if line input is done */
1072         if (less_gets_pos >= 0)
1073                 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
1074         fflush_all();
1075
1076         if (kbd_input[0] == 0) { /* if nothing is buffered */
1077 #if ENABLE_FEATURE_LESS_WINCH
1078                 while (1) {
1079                         int r;
1080                         /* NB: SIGWINCH interrupts poll() */
1081                         r = poll(pfd + rd, 2 - rd, -1);
1082                         if (/*r < 0 && errno == EINTR &&*/ winch_counter)
1083                                 return '\\'; /* anything which has no defined function */
1084                         if (r) break;
1085                 }
1086 #else
1087                 safe_poll(pfd + rd, 2 - rd, -1);
1088 #endif
1089         }
1090
1091         /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
1092          * would not block even if there is no input available */
1093         key64 = read_key(kbd_fd, kbd_input, /*timeout off:*/ -2);
1094         if ((int)key64 == -1) {
1095                 if (errno == EAGAIN) {
1096                         /* No keyboard input available. Since poll() did return,
1097                          * we should have input on stdin */
1098                         read_lines();
1099                         buffer_fill_and_print();
1100                         goto again;
1101                 }
1102                 /* EOF/error (ssh session got killed etc) */
1103                 less_exit(EXIT_SUCCESS);
1104         }
1105         set_tty_cooked();
1106         return key64;
1107 }
1108
1109 /* Grab a character from input without requiring the return key.
1110  * May return KEYCODE_xxx values.
1111  * Note that this function works best with raw input. */
1112 static int64_t less_getch(int pos)
1113 {
1114         int64_t key64;
1115         int key;
1116
1117  again:
1118         less_gets_pos = pos;
1119         key = key64 = getch_nowait();
1120         less_gets_pos = -1;
1121
1122         /* Discard Ctrl-something chars.
1123          * (checking only lower 32 bits is a size optimization:
1124          * upper 32 bits are used only by KEYCODE_CURSOR_POS)
1125          */
1126         if (key >= 0 && key < ' ' && key != 0x0d && key != 8)
1127                 goto again;
1128
1129         return key64;
1130 }
1131
1132 static char* less_gets(int sz)
1133 {
1134         int c;
1135         unsigned i = 0;
1136         char *result = xzalloc(1);
1137
1138         while (1) {
1139                 c = '\0';
1140                 less_gets_pos = sz + i;
1141                 c = getch_nowait();
1142                 if (c == 0x0d) {
1143                         result[i] = '\0';
1144                         less_gets_pos = -1;
1145                         return result;
1146                 }
1147                 if (c == 0x7f)
1148                         c = 8;
1149                 if (c == 8 && i) {
1150                         printf("\x8 \x8");
1151                         i--;
1152                 }
1153                 if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
1154                         continue;
1155                 if (i >= width - sz - 1)
1156                         continue; /* len limit */
1157                 bb_putchar(c);
1158                 result[i++] = c;
1159                 result = xrealloc(result, i+1);
1160         }
1161 }
1162
1163 static void examine_file(void)
1164 {
1165         char *new_fname;
1166
1167         print_statusline("Examine: ");
1168         new_fname = less_gets(sizeof("Examine: ") - 1);
1169         if (!new_fname[0]) {
1170                 status_print();
1171  err:
1172                 free(new_fname);
1173                 return;
1174         }
1175         if (access(new_fname, R_OK) != 0) {
1176                 print_statusline("Cannot read this file");
1177                 goto err;
1178         }
1179         free(filename);
1180         filename = new_fname;
1181         /* files start by = argv. why we assume that argv is infinitely long??
1182         files[num_files] = filename;
1183         current_file = num_files + 1;
1184         num_files++; */
1185         files[0] = filename;
1186         num_files = current_file = 1;
1187         reinitialize();
1188 }
1189
1190 /* This function changes the file currently being paged. direction can be one of the following:
1191  * -1: go back one file
1192  *  0: go to the first file
1193  *  1: go forward one file */
1194 static void change_file(int direction)
1195 {
1196         if (current_file != ((direction > 0) ? num_files : 1)) {
1197                 current_file = direction ? current_file + direction : 1;
1198                 free(filename);
1199                 filename = xstrdup(files[current_file - 1]);
1200                 reinitialize();
1201         } else {
1202                 print_statusline(direction > 0 ? "No next file" : "No previous file");
1203         }
1204 }
1205
1206 static void remove_current_file(void)
1207 {
1208         unsigned i;
1209
1210         if (num_files < 2)
1211                 return;
1212
1213         if (current_file != 1) {
1214                 change_file(-1);
1215                 for (i = 3; i <= num_files; i++)
1216                         files[i - 2] = files[i - 1];
1217                 num_files--;
1218         } else {
1219                 change_file(1);
1220                 for (i = 2; i <= num_files; i++)
1221                         files[i - 2] = files[i - 1];
1222                 num_files--;
1223                 current_file--;
1224         }
1225 }
1226
1227 static void colon_process(void)
1228 {
1229         int keypress;
1230
1231         /* Clear the current line and print a prompt */
1232         print_statusline(" :");
1233
1234         keypress = less_getch(2);
1235         switch (keypress) {
1236         case 'd':
1237                 remove_current_file();
1238                 break;
1239         case 'e':
1240                 examine_file();
1241                 break;
1242 #if ENABLE_FEATURE_LESS_FLAGS
1243         case 'f':
1244                 m_status_print();
1245                 break;
1246 #endif
1247         case 'n':
1248                 change_file(1);
1249                 break;
1250         case 'p':
1251                 change_file(-1);
1252                 break;
1253         case 'q':
1254                 less_exit(EXIT_SUCCESS);
1255                 break;
1256         case 'x':
1257                 change_file(0);
1258                 break;
1259         }
1260 }
1261
1262 #if ENABLE_FEATURE_LESS_REGEXP
1263 static void normalize_match_pos(int match)
1264 {
1265         if (match >= num_matches)
1266                 match = num_matches - 1;
1267         if (match < 0)
1268                 match = 0;
1269         match_pos = match;
1270 }
1271
1272 static void goto_match(int match)
1273 {
1274         if (!pattern_valid)
1275                 return;
1276         if (match < 0)
1277                 match = 0;
1278         /* Try to find next match if eof isn't reached yet */
1279         if (match >= num_matches && eof_error > 0) {
1280                 wanted_match = match; /* "I want to read until I see N'th match" */
1281                 read_lines();
1282         }
1283         if (num_matches) {
1284                 normalize_match_pos(match);
1285                 buffer_line(match_lines[match_pos]);
1286         } else {
1287                 print_statusline("No matches found");
1288         }
1289 }
1290
1291 static void fill_match_lines(unsigned pos)
1292 {
1293         if (!pattern_valid)
1294                 return;
1295         /* Run the regex on each line of the current file */
1296         while (pos <= max_fline) {
1297                 /* If this line matches */
1298                 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
1299                 /* and we didn't match it last time */
1300                  && !(num_matches && match_lines[num_matches-1] == pos)
1301                 ) {
1302                         match_lines = xrealloc_vector(match_lines, 4, num_matches);
1303                         match_lines[num_matches++] = pos;
1304                 }
1305                 pos++;
1306         }
1307 }
1308
1309 static void regex_process(void)
1310 {
1311         char *uncomp_regex, *err;
1312
1313         /* Reset variables */
1314         free(match_lines);
1315         match_lines = NULL;
1316         match_pos = 0;
1317         num_matches = 0;
1318         if (pattern_valid) {
1319                 regfree(&pattern);
1320                 pattern_valid = 0;
1321         }
1322
1323         /* Get the uncompiled regular expression from the user */
1324         clear_line();
1325         bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
1326         uncomp_regex = less_gets(1);
1327         if (!uncomp_regex[0]) {
1328                 free(uncomp_regex);
1329                 buffer_print();
1330                 return;
1331         }
1332
1333         /* Compile the regex and check for errors */
1334         err = regcomp_or_errmsg(&pattern, uncomp_regex,
1335                                 (option_mask32 & FLAG_I) ? REG_ICASE : 0);
1336         free(uncomp_regex);
1337         if (err) {
1338                 print_statusline(err);
1339                 free(err);
1340                 return;
1341         }
1342
1343         pattern_valid = 1;
1344         match_pos = 0;
1345         fill_match_lines(0);
1346         while (match_pos < num_matches) {
1347                 if ((int)match_lines[match_pos] > cur_fline)
1348                         break;
1349                 match_pos++;
1350         }
1351         if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
1352                 match_pos--;
1353
1354         /* It's possible that no matches are found yet.
1355          * goto_match() will read input looking for match,
1356          * if needed */
1357         goto_match(match_pos);
1358 }
1359 #endif
1360
1361 static void number_process(int first_digit)
1362 {
1363         unsigned i;
1364         int num;
1365         int keypress;
1366         char num_input[sizeof(int)*4]; /* more than enough */
1367
1368         num_input[0] = first_digit;
1369
1370         /* Clear the current line, print a prompt, and then print the digit */
1371         clear_line();
1372         printf(":%c", first_digit);
1373
1374         /* Receive input until a letter is given */
1375         i = 1;
1376         while (i < sizeof(num_input)-1) {
1377                 keypress = less_getch(i + 1);
1378                 if ((unsigned)keypress > 255 || !isdigit(keypress))
1379                         break;
1380                 num_input[i] = keypress;
1381                 bb_putchar(keypress);
1382                 i++;
1383         }
1384
1385         num_input[i] = '\0';
1386         num = bb_strtou(num_input, NULL, 10);
1387         /* on format error, num == -1 */
1388         if (num < 1 || num > MAXLINES) {
1389                 buffer_print();
1390                 return;
1391         }
1392
1393         /* We now know the number and the letter entered, so we process them */
1394         switch (keypress) {
1395         case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1396                 buffer_down(num);
1397                 break;
1398         case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
1399                 buffer_up(num);
1400                 break;
1401         case 'g': case '<': case 'G': case '>':
1402                 buffer_lineno(num - 1);
1403                 break;
1404         case 'p': case '%':
1405 #if ENABLE_FEATURE_LESS_FLAGS
1406                 update_num_lines();
1407                 num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
1408 #else
1409                 num = num * max_lineno / 100;
1410 #endif
1411                 buffer_lineno(num);
1412                 break;
1413 #if ENABLE_FEATURE_LESS_REGEXP
1414         case 'n':
1415                 goto_match(match_pos + num);
1416                 break;
1417         case '/':
1418                 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1419                 regex_process();
1420                 break;
1421         case '?':
1422                 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1423                 regex_process();
1424                 break;
1425 #endif
1426         }
1427 }
1428
1429 #if ENABLE_FEATURE_LESS_DASHCMD
1430 static void flag_change(void)
1431 {
1432         int keypress;
1433
1434         clear_line();
1435         bb_putchar('-');
1436         keypress = less_getch(1);
1437
1438         switch (keypress) {
1439         case 'M':
1440                 option_mask32 ^= FLAG_M;
1441                 break;
1442         case 'm':
1443                 option_mask32 ^= FLAG_m;
1444                 break;
1445         case 'E':
1446                 option_mask32 ^= FLAG_E;
1447                 break;
1448         case '~':
1449                 option_mask32 ^= FLAG_TILDE;
1450                 break;
1451 #if ENABLE_FEATURE_LESS_TRUNCATE
1452         case 'S':
1453                 option_mask32 ^= FLAG_S;
1454                 buffer_fill_and_print();
1455                 break;
1456 #endif
1457 #if ENABLE_FEATURE_LESS_LINENUMS
1458         case 'N':
1459                 option_mask32 ^= FLAG_N;
1460                 re_wrap();
1461                 buffer_fill_and_print();
1462                 break;
1463 #endif
1464         }
1465 }
1466
1467 #ifdef BLOAT
1468 static void show_flag_status(void)
1469 {
1470         int keypress;
1471         int flag_val;
1472
1473         clear_line();
1474         bb_putchar('_');
1475         keypress = less_getch(1);
1476
1477         switch (keypress) {
1478         case 'M':
1479                 flag_val = option_mask32 & FLAG_M;
1480                 break;
1481         case 'm':
1482                 flag_val = option_mask32 & FLAG_m;
1483                 break;
1484         case '~':
1485                 flag_val = option_mask32 & FLAG_TILDE;
1486                 break;
1487         case 'N':
1488                 flag_val = option_mask32 & FLAG_N;
1489                 break;
1490         case 'E':
1491                 flag_val = option_mask32 & FLAG_E;
1492                 break;
1493         default:
1494                 flag_val = 0;
1495                 break;
1496         }
1497
1498         clear_line();
1499         printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1500 }
1501 #endif
1502
1503 #endif /* ENABLE_FEATURE_LESS_DASHCMD */
1504
1505 static void save_input_to_file(void)
1506 {
1507         const char *msg = "";
1508         char *current_line;
1509         unsigned i;
1510         FILE *fp;
1511
1512         print_statusline("Log file: ");
1513         current_line = less_gets(sizeof("Log file: ")-1);
1514         if (current_line[0]) {
1515                 fp = fopen_for_write(current_line);
1516                 if (!fp) {
1517                         msg = "Error opening log file";
1518                         goto ret;
1519                 }
1520                 for (i = 0; i <= max_fline; i++)
1521                         fprintf(fp, "%s\n", flines[i]);
1522                 fclose(fp);
1523                 msg = "Done";
1524         }
1525  ret:
1526         print_statusline(msg);
1527         free(current_line);
1528 }
1529
1530 #if ENABLE_FEATURE_LESS_MARKS
1531 static void add_mark(void)
1532 {
1533         int letter;
1534
1535         print_statusline("Mark: ");
1536         letter = less_getch(sizeof("Mark: ") - 1);
1537
1538         if (isalpha(letter)) {
1539                 /* If we exceed 15 marks, start overwriting previous ones */
1540                 if (num_marks == 14)
1541                         num_marks = 0;
1542
1543                 mark_lines[num_marks][0] = letter;
1544                 mark_lines[num_marks][1] = cur_fline;
1545                 num_marks++;
1546         } else {
1547                 print_statusline("Invalid mark letter");
1548         }
1549 }
1550
1551 static void goto_mark(void)
1552 {
1553         int letter;
1554         int i;
1555
1556         print_statusline("Go to mark: ");
1557         letter = less_getch(sizeof("Go to mark: ") - 1);
1558         clear_line();
1559
1560         if (isalpha(letter)) {
1561                 for (i = 0; i <= num_marks; i++)
1562                         if (letter == mark_lines[i][0]) {
1563                                 buffer_line(mark_lines[i][1]);
1564                                 break;
1565                         }
1566                 if (num_marks == 14 && letter != mark_lines[14][0])
1567                         print_statusline("Mark not set");
1568         } else
1569                 print_statusline("Invalid mark letter");
1570 }
1571 #endif
1572
1573 #if ENABLE_FEATURE_LESS_BRACKETS
1574 static char opp_bracket(char bracket)
1575 {
1576         switch (bracket) {
1577                 case '{': case '[': /* '}' == '{' + 2. Same for '[' */
1578                         bracket++;
1579                 case '(':           /* ')' == '(' + 1 */
1580                         bracket++;
1581                         break;
1582                 case '}': case ']':
1583                         bracket--;
1584                 case ')':
1585                         bracket--;
1586                         break;
1587         };
1588         return bracket;
1589 }
1590
1591 static void match_right_bracket(char bracket)
1592 {
1593         unsigned i = cur_fline;
1594
1595         if (i >= max_fline
1596          || strchr(flines[i], bracket) == NULL
1597         ) {
1598                 print_statusline("No bracket in top line");
1599                 return;
1600         }
1601
1602         bracket = opp_bracket(bracket);
1603         for (; i < max_fline; i++) {
1604                 if (strchr(flines[i], bracket) != NULL) {
1605                         /*
1606                          * Line with matched right bracket becomes
1607                          * last visible line
1608                          */
1609                         buffer_line(i - max_displayed_line);
1610                         return;
1611                 }
1612         }
1613         print_statusline("No matching bracket found");
1614 }
1615
1616 static void match_left_bracket(char bracket)
1617 {
1618         int i = cur_fline + max_displayed_line;
1619
1620         if (i >= max_fline
1621          || strchr(flines[i], bracket) == NULL
1622         ) {
1623                 print_statusline("No bracket in bottom line");
1624                 return;
1625         }
1626
1627         bracket = opp_bracket(bracket);
1628         for (; i >= 0; i--) {
1629                 if (strchr(flines[i], bracket) != NULL) {
1630                         /*
1631                          * Line with matched left bracket becomes
1632                          * first visible line
1633                          */
1634                         buffer_line(i);
1635                         return;
1636                 }
1637         }
1638         print_statusline("No matching bracket found");
1639 }
1640 #endif  /* FEATURE_LESS_BRACKETS */
1641
1642 static void keypress_process(int keypress)
1643 {
1644         switch (keypress) {
1645         case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
1646                 buffer_down(1);
1647                 break;
1648         case KEYCODE_UP: case 'y': case 'k':
1649                 buffer_up(1);
1650                 break;
1651         case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
1652                 buffer_down(max_displayed_line + 1);
1653                 break;
1654         case KEYCODE_PAGEUP: case 'w': case 'b':
1655                 buffer_up(max_displayed_line + 1);
1656                 break;
1657         case 'd':
1658                 buffer_down((max_displayed_line + 1) / 2);
1659                 break;
1660         case 'u':
1661                 buffer_up((max_displayed_line + 1) / 2);
1662                 break;
1663         case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
1664                 buffer_line(0);
1665                 break;
1666         case KEYCODE_END: case 'G': case '>':
1667                 cur_fline = MAXLINES;
1668                 read_lines();
1669                 buffer_line(cur_fline);
1670                 break;
1671         case 'q': case 'Q':
1672                 less_exit(EXIT_SUCCESS);
1673                 break;
1674 #if ENABLE_FEATURE_LESS_MARKS
1675         case 'm':
1676                 add_mark();
1677                 buffer_print();
1678                 break;
1679         case '\'':
1680                 goto_mark();
1681                 buffer_print();
1682                 break;
1683 #endif
1684         case 'r': case 'R':
1685                 /* TODO: (1) also bind ^R, ^L to this?
1686                  * (2) re-measure window size?
1687                  */
1688                 buffer_print();
1689                 break;
1690         /*case 'R':
1691                 full_repaint();
1692                 break;*/
1693         case 's':
1694                 save_input_to_file();
1695                 break;
1696         case 'E':
1697                 examine_file();
1698                 break;
1699 #if ENABLE_FEATURE_LESS_FLAGS
1700         case '=':
1701                 m_status_print();
1702                 break;
1703 #endif
1704 #if ENABLE_FEATURE_LESS_REGEXP
1705         case '/':
1706                 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1707                 regex_process();
1708                 break;
1709         case 'n':
1710                 goto_match(match_pos + 1);
1711                 break;
1712         case 'N':
1713                 goto_match(match_pos - 1);
1714                 break;
1715         case '?':
1716                 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1717                 regex_process();
1718                 break;
1719 #endif
1720 #if ENABLE_FEATURE_LESS_DASHCMD
1721         case '-':
1722                 flag_change();
1723                 buffer_print();
1724                 break;
1725 #ifdef BLOAT
1726         case '_':
1727                 show_flag_status();
1728                 break;
1729 #endif
1730 #endif
1731 #if ENABLE_FEATURE_LESS_BRACKETS
1732         case '{': case '(': case '[':
1733                 match_right_bracket(keypress);
1734                 break;
1735         case '}': case ')': case ']':
1736                 match_left_bracket(keypress);
1737                 break;
1738 #endif
1739         case ':':
1740                 colon_process();
1741                 break;
1742         }
1743
1744         if (isdigit(keypress))
1745                 number_process(keypress);
1746 }
1747
1748 static void sig_catcher(int sig)
1749 {
1750         less_exit(- sig);
1751 }
1752
1753 #if ENABLE_FEATURE_LESS_WINCH
1754 static void sigwinch_handler(int sig UNUSED_PARAM)
1755 {
1756         winch_counter++;
1757 }
1758 #endif
1759
1760 int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1761 int less_main(int argc, char **argv)
1762 {
1763         char *tty_name;
1764         int tty_fd;
1765
1766         INIT_G();
1767
1768         /* TODO: -x: do not interpret backspace, -xx: tab also
1769          * -xxx: newline also
1770          * -w N: assume width N (-xxx -w 32: hex viewer of sorts)
1771          * -s: condense many empty lines to one
1772          *     (used by some setups for manpage display)
1773          */
1774         getopt32(argv, "EMmN~I" IF_FEATURE_LESS_TRUNCATE("S") /*ignored:*/"s");
1775         argv += optind;
1776         num_files = argc - optind;
1777         files = argv;
1778
1779         /* Another popular pager, most, detects when stdout
1780          * is not a tty and turns into cat. This makes sense. */
1781         if (!isatty(STDOUT_FILENO))
1782                 return bb_cat(argv);
1783
1784         if (!num_files) {
1785                 if (isatty(STDIN_FILENO)) {
1786                         /* Just "less"? No args and no redirection? */
1787                         bb_error_msg("missing filename");
1788                         bb_show_usage();
1789                 }
1790         } else {
1791                 filename = xstrdup(files[0]);
1792         }
1793
1794         if (option_mask32 & FLAG_TILDE)
1795                 empty_line_marker = "";
1796
1797         /* Some versions of less can survive w/o controlling tty,
1798          * try to do the same. This also allows to specify an alternative
1799          * tty via "less 1<>TTY".
1800          *
1801          * We prefer not to use STDOUT_FILENO directly,
1802          * since we want to set this fd to non-blocking mode,
1803          * and not interfere with other processes which share stdout with us.
1804          */
1805         tty_name = xmalloc_ttyname(STDOUT_FILENO);
1806         if (tty_name) {
1807                 tty_fd = open(tty_name, O_RDONLY);
1808                 free(tty_name);
1809                 if (tty_fd < 0)
1810                         goto try_ctty;
1811         } else {
1812                 /* Try controlling tty */
1813  try_ctty:
1814                 tty_fd = open(CURRENT_TTY, O_RDONLY);
1815                 if (tty_fd < 0) {
1816                         /* If all else fails, less 481 uses stdout. Mimic that */
1817                         tty_fd = STDOUT_FILENO;
1818                 }
1819         }
1820         G.kbd_fd_orig_flags = ndelay_on(tty_fd);
1821         kbd_fd = tty_fd; /* save in a global */
1822
1823         get_termios_and_make_raw(tty_fd, &term_less, &term_orig, TERMIOS_RAW_CRNL);
1824
1825         IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(tty_fd, &width, &max_displayed_line);
1826         /* 20: two tabstops + 4 */
1827         if (width < 20 || max_displayed_line < 3)
1828                 return bb_cat(argv);
1829         max_displayed_line -= 2;
1830
1831         /* We want to restore term_orig on exit */
1832         bb_signals(BB_FATAL_SIGS, sig_catcher);
1833 #if ENABLE_FEATURE_LESS_WINCH
1834         signal(SIGWINCH, sigwinch_handler);
1835 #endif
1836
1837         buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1838         reinitialize();
1839         while (1) {
1840                 int64_t keypress;
1841
1842 #if ENABLE_FEATURE_LESS_WINCH
1843                 while (WINCH_COUNTER) {
1844  again:
1845                         winch_counter--;
1846                         IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1847  IF_FEATURE_LESS_ASK_TERMINAL(got_size:)
1848                         /* 20: two tabstops + 4 */
1849                         if (width < 20)
1850                                 width = 20;
1851                         if (max_displayed_line < 3)
1852                                 max_displayed_line = 3;
1853                         max_displayed_line -= 2;
1854                         free(buffer);
1855                         buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1856                         /* Avoid re-wrap and/or redraw if we already know
1857                          * we need to do it again. These ops are expensive */
1858                         if (WINCH_COUNTER)
1859                                 goto again;
1860                         re_wrap();
1861                         if (WINCH_COUNTER)
1862                                 goto again;
1863                         buffer_fill_and_print();
1864                         /* This took some time. Loop back and check,
1865                          * were there another SIGWINCH? */
1866                 }
1867                 keypress = less_getch(-1); /* -1: do not position cursor */
1868 # if ENABLE_FEATURE_LESS_ASK_TERMINAL
1869                 if ((int32_t)keypress == KEYCODE_CURSOR_POS) {
1870                         uint32_t rc = (keypress >> 32);
1871                         width = (rc & 0x7fff);
1872                         max_displayed_line = ((rc >> 16) & 0x7fff);
1873                         goto got_size;
1874                 }
1875 # endif
1876 #else
1877                 keypress = less_getch(-1); /* -1: do not position cursor */
1878 #endif
1879                 keypress_process(keypress);
1880         }
1881 }
1882
1883 /*
1884 Help text of less version 418 is below.
1885 If you are implementing something, keeping
1886 key and/or command line switch compatibility is a good idea:
1887
1888
1889                    SUMMARY OF LESS COMMANDS
1890
1891       Commands marked with * may be preceded by a number, N.
1892       Notes in parentheses indicate the behavior if N is given.
1893   h  H                 Display this help.
1894   q  :q  Q  :Q  ZZ     Exit.
1895  ---------------------------------------------------------------------------
1896                            MOVING
1897   e  ^E  j  ^N  CR  *  Forward  one line   (or N lines).
1898   y  ^Y  k  ^K  ^P  *  Backward one line   (or N lines).
1899   f  ^F  ^V  SPACE  *  Forward  one window (or N lines).
1900   b  ^B  ESC-v      *  Backward one window (or N lines).
1901   z                 *  Forward  one window (and set window to N).
1902   w                 *  Backward one window (and set window to N).
1903   ESC-SPACE         *  Forward  one window, but don't stop at end-of-file.
1904   d  ^D             *  Forward  one half-window (and set half-window to N).
1905   u  ^U             *  Backward one half-window (and set half-window to N).
1906   ESC-)  RightArrow *  Left  one half screen width (or N positions).
1907   ESC-(  LeftArrow  *  Right one half screen width (or N positions).
1908   F                    Forward forever; like "tail -f".
1909   r  ^R  ^L            Repaint screen.
1910   R                    Repaint screen, discarding buffered input.
1911         ---------------------------------------------------
1912         Default "window" is the screen height.
1913         Default "half-window" is half of the screen height.
1914  ---------------------------------------------------------------------------
1915                           SEARCHING
1916   /pattern          *  Search forward for (N-th) matching line.
1917   ?pattern          *  Search backward for (N-th) matching line.
1918   n                 *  Repeat previous search (for N-th occurrence).
1919   N                 *  Repeat previous search in reverse direction.
1920   ESC-n             *  Repeat previous search, spanning files.
1921   ESC-N             *  Repeat previous search, reverse dir. & spanning files.
1922   ESC-u                Undo (toggle) search highlighting.
1923         ---------------------------------------------------
1924         Search patterns may be modified by one or more of:
1925         ^N or !  Search for NON-matching lines.
1926         ^E or *  Search multiple files (pass thru END OF FILE).
1927         ^F or @  Start search at FIRST file (for /) or last file (for ?).
1928         ^K       Highlight matches, but don't move (KEEP position).
1929         ^R       Don't use REGULAR EXPRESSIONS.
1930  ---------------------------------------------------------------------------
1931                            JUMPING
1932   g  <  ESC-<       *  Go to first line in file (or line N).
1933   G  >  ESC->       *  Go to last line in file (or line N).
1934   p  %              *  Go to beginning of file (or N percent into file).
1935   t                 *  Go to the (N-th) next tag.
1936   T                 *  Go to the (N-th) previous tag.
1937   {  (  [           *  Find close bracket } ) ].
1938   }  )  ]           *  Find open bracket { ( [.
1939   ESC-^F <c1> <c2>  *  Find close bracket <c2>.
1940   ESC-^B <c1> <c2>  *  Find open bracket <c1>
1941         ---------------------------------------------------
1942         Each "find close bracket" command goes forward to the close bracket
1943           matching the (N-th) open bracket in the top line.
1944         Each "find open bracket" command goes backward to the open bracket
1945           matching the (N-th) close bracket in the bottom line.
1946   m<letter>            Mark the current position with <letter>.
1947   '<letter>            Go to a previously marked position.
1948   ''                   Go to the previous position.
1949   ^X^X                 Same as '.
1950         ---------------------------------------------------
1951         A mark is any upper-case or lower-case letter.
1952         Certain marks are predefined:
1953              ^  means  beginning of the file
1954              $  means  end of the file
1955  ---------------------------------------------------------------------------
1956                         CHANGING FILES
1957   :e [file]            Examine a new file.
1958   ^X^V                 Same as :e.
1959   :n                *  Examine the (N-th) next file from the command line.
1960   :p                *  Examine the (N-th) previous file from the command line.
1961   :x                *  Examine the first (or N-th) file from the command line.
1962   :d                   Delete the current file from the command line list.
1963   =  ^G  :f            Print current file name.
1964  ---------------------------------------------------------------------------
1965                     MISCELLANEOUS COMMANDS
1966   -<flag>              Toggle a command line option [see OPTIONS below].
1967   --<name>             Toggle a command line option, by name.
1968   _<flag>              Display the setting of a command line option.
1969   __<name>             Display the setting of an option, by name.
1970   +cmd                 Execute the less cmd each time a new file is examined.
1971   !command             Execute the shell command with $SHELL.
1972   |Xcommand            Pipe file between current pos & mark X to shell command.
1973   v                    Edit the current file with $VISUAL or $EDITOR.
1974   V                    Print version number of "less".
1975  ---------------------------------------------------------------------------
1976                            OPTIONS
1977         Most options may be changed either on the command line,
1978         or from within less by using the - or -- command.
1979         Options may be given in one of two forms: either a single
1980         character preceded by a -, or a name preceded by --.
1981   -?  ........  --help
1982                   Display help (from command line).
1983   -a  ........  --search-skip-screen
1984                   Forward search skips current screen.
1985   -b [N]  ....  --buffers=[N]
1986                   Number of buffers.
1987   -B  ........  --auto-buffers
1988                   Don't automatically allocate buffers for pipes.
1989   -c  ........  --clear-screen
1990                   Repaint by clearing rather than scrolling.
1991   -d  ........  --dumb
1992                   Dumb terminal.
1993   -D [xn.n]  .  --color=xn.n
1994                   Set screen colors. (MS-DOS only)
1995   -e  -E  ....  --quit-at-eof  --QUIT-AT-EOF
1996                   Quit at end of file.
1997   -f  ........  --force
1998                   Force open non-regular files.
1999   -F  ........  --quit-if-one-screen
2000                   Quit if entire file fits on first screen.
2001   -g  ........  --hilite-search
2002                   Highlight only last match for searches.
2003   -G  ........  --HILITE-SEARCH
2004                   Don't highlight any matches for searches.
2005   -h [N]  ....  --max-back-scroll=[N]
2006                   Backward scroll limit.
2007   -i  ........  --ignore-case
2008                   Ignore case in searches that do not contain uppercase.
2009   -I  ........  --IGNORE-CASE
2010                   Ignore case in all searches.
2011   -j [N]  ....  --jump-target=[N]
2012                   Screen position of target lines.
2013   -J  ........  --status-column
2014                   Display a status column at left edge of screen.
2015   -k [file]  .  --lesskey-file=[file]
2016                   Use a lesskey file.
2017   -L  ........  --no-lessopen
2018                   Ignore the LESSOPEN environment variable.
2019   -m  -M  ....  --long-prompt  --LONG-PROMPT
2020                   Set prompt style.
2021   -n  -N  ....  --line-numbers  --LINE-NUMBERS
2022                   Don't use line numbers.
2023   -o [file]  .  --log-file=[file]
2024                   Copy to log file (standard input only).
2025   -O [file]  .  --LOG-FILE=[file]
2026                   Copy to log file (unconditionally overwrite).
2027   -p [pattern]  --pattern=[pattern]
2028                   Start at pattern (from command line).
2029   -P [prompt]   --prompt=[prompt]
2030                   Define new prompt.
2031   -q  -Q  ....  --quiet  --QUIET  --silent --SILENT
2032                   Quiet the terminal bell.
2033   -r  -R  ....  --raw-control-chars  --RAW-CONTROL-CHARS
2034                   Output "raw" control characters.
2035   -s  ........  --squeeze-blank-lines
2036                   Squeeze multiple blank lines.
2037   -S  ........  --chop-long-lines
2038                   Chop long lines.
2039   -t [tag]  ..  --tag=[tag]
2040                   Find a tag.
2041   -T [tagsfile] --tag-file=[tagsfile]
2042                   Use an alternate tags file.
2043   -u  -U  ....  --underline-special  --UNDERLINE-SPECIAL
2044                   Change handling of backspaces.
2045   -V  ........  --version
2046                   Display the version number of "less".
2047   -w  ........  --hilite-unread
2048                   Highlight first new line after forward-screen.
2049   -W  ........  --HILITE-UNREAD
2050                   Highlight first new line after any forward movement.
2051   -x [N[,...]]  --tabs=[N[,...]]
2052                   Set tab stops.
2053   -X  ........  --no-init
2054                   Don't use termcap init/deinit strings.
2055                 --no-keypad
2056                   Don't use termcap keypad init/deinit strings.
2057   -y [N]  ....  --max-forw-scroll=[N]
2058                   Forward scroll limit.
2059   -z [N]  ....  --window=[N]
2060                   Set size of window.
2061   -" [c[c]]  .  --quotes=[c[c]]
2062                   Set shell quote characters.
2063   -~  ........  --tilde
2064                   Don't display tildes after end of file.
2065   -# [N]  ....  --shift=[N]
2066                   Horizontal scroll amount (0 = one half screen width)
2067
2068  ---------------------------------------------------------------------------
2069                           LINE EDITING
2070         These keys can be used to edit text being entered
2071         on the "command line" at the bottom of the screen.
2072  RightArrow                       ESC-l     Move cursor right one character.
2073  LeftArrow                        ESC-h     Move cursor left one character.
2074  CNTL-RightArrow  ESC-RightArrow  ESC-w     Move cursor right one word.
2075  CNTL-LeftArrow   ESC-LeftArrow   ESC-b     Move cursor left one word.
2076  HOME                             ESC-0     Move cursor to start of line.
2077  END                              ESC-$     Move cursor to end of line.
2078  BACKSPACE                                  Delete char to left of cursor.
2079  DELETE                           ESC-x     Delete char under cursor.
2080  CNTL-BACKSPACE   ESC-BACKSPACE             Delete word to left of cursor.
2081  CNTL-DELETE      ESC-DELETE      ESC-X     Delete word under cursor.
2082  CNTL-U           ESC (MS-DOS only)         Delete entire line.
2083  UpArrow                          ESC-k     Retrieve previous command line.
2084  DownArrow                        ESC-j     Retrieve next command line.
2085  TAB                                        Complete filename & cycle.
2086  SHIFT-TAB                        ESC-TAB   Complete filename & reverse cycle.
2087  CNTL-L                                     Complete filename, list all.
2088 */