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