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