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