1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127#include <sched.h>
128
129#include "libbb.h"
130#if ENABLE_FEATURE_LESS_REGEXP
131#include "xregex.h"
132#endif
133
134
135#define ESC "\033"
136
137#define HIGHLIGHT ESC"[7m"
138#define NORMAL ESC"[0m"
139
140#define CLEAR ESC"[H\033[J"
141
142#define CLEAR_2_EOL ESC"[K"
143
144enum {
145
146 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
147
148 TILDES = 1,
149};
150
151
152enum {
153 FLAG_E = 1 << 0,
154 FLAG_M = 1 << 1,
155 FLAG_m = 1 << 2,
156 FLAG_N = 1 << 3,
157 FLAG_TILDE = 1 << 4,
158 FLAG_I = 1 << 5,
159 FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE,
160
161 LESS_STATE_MATCH_BACKWARDS = 1 << 15,
162};
163
164#if !ENABLE_FEATURE_LESS_REGEXP
165enum { pattern_valid = 0 };
166#endif
167
168struct globals {
169 int cur_fline;
170 int kbd_fd;
171 int less_gets_pos;
172
173 size_t last_line_pos;
174 unsigned max_fline;
175 unsigned max_lineno;
176 unsigned max_displayed_line;
177 unsigned width;
178#if ENABLE_FEATURE_LESS_WINCH
179 unsigned winch_counter;
180#endif
181 ssize_t eof_error;
182 ssize_t readpos;
183 ssize_t readeof;
184 const char **buffer;
185 const char **flines;
186 const char *empty_line_marker;
187 unsigned num_files;
188 unsigned current_file;
189 char *filename;
190 char **files;
191#if ENABLE_FEATURE_LESS_FLAGS
192 int num_lines;
193# define REOPEN_AND_COUNT (-1)
194# define REOPEN_STDIN (-2)
195# define NOT_REGULAR_FILE (-3)
196#endif
197#if ENABLE_FEATURE_LESS_MARKS
198 unsigned num_marks;
199 unsigned mark_lines[15][2];
200#endif
201#if ENABLE_FEATURE_LESS_REGEXP
202 unsigned *match_lines;
203 int match_pos;
204 int wanted_match;
205 int num_matches;
206 regex_t pattern;
207 smallint pattern_valid;
208#endif
209#if ENABLE_FEATURE_LESS_ASK_TERMINAL
210 smallint winsize_err;
211#endif
212 smallint terminated;
213 struct termios term_orig, term_less;
214 char kbd_input[KEYCODE_BUFFER_SIZE];
215};
216#define G (*ptr_to_globals)
217#define cur_fline (G.cur_fline )
218#define kbd_fd (G.kbd_fd )
219#define less_gets_pos (G.less_gets_pos )
220#define last_line_pos (G.last_line_pos )
221#define max_fline (G.max_fline )
222#define max_lineno (G.max_lineno )
223#define max_displayed_line (G.max_displayed_line)
224#define width (G.width )
225#define winch_counter (G.winch_counter )
226
227#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
228#define eof_error (G.eof_error )
229#define readpos (G.readpos )
230#define readeof (G.readeof )
231#define buffer (G.buffer )
232#define flines (G.flines )
233#define empty_line_marker (G.empty_line_marker )
234#define num_files (G.num_files )
235#define current_file (G.current_file )
236#define filename (G.filename )
237#define files (G.files )
238#define num_lines (G.num_lines )
239#define num_marks (G.num_marks )
240#define mark_lines (G.mark_lines )
241#if ENABLE_FEATURE_LESS_REGEXP
242#define match_lines (G.match_lines )
243#define match_pos (G.match_pos )
244#define num_matches (G.num_matches )
245#define wanted_match (G.wanted_match )
246#define pattern (G.pattern )
247#define pattern_valid (G.pattern_valid )
248#endif
249#define terminated (G.terminated )
250#define term_orig (G.term_orig )
251#define term_less (G.term_less )
252#define kbd_input (G.kbd_input )
253#define INIT_G() do { \
254 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
255 less_gets_pos = -1; \
256 empty_line_marker = "~"; \
257 num_files = 1; \
258 current_file = 1; \
259 eof_error = 1; \
260 terminated = 1; \
261 IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
262} while (0)
263
264
265
266
267
268#define MEMPTR(p) ((char*)(p) - 4)
269#define LINENO(p) (*(uint32_t*)((p) - 4))
270
271
272
273static void set_tty_cooked(void)
274{
275 fflush_all();
276 tcsetattr(kbd_fd, TCSANOW, &term_orig);
277}
278
279
280
281static void move_cursor(int line, int row)
282{
283 printf(ESC"[%u;%uH", line, row);
284}
285
286static void clear_line(void)
287{
288 printf(ESC"[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
289}
290
291static void print_hilite(const char *str)
292{
293 printf(HIGHLIGHT"%s"NORMAL, str);
294}
295
296static void print_statusline(const char *str)
297{
298 clear_line();
299 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
300}
301
302
303static void less_exit(int code)
304{
305 set_tty_cooked();
306 clear_line();
307 if (code < 0)
308 kill_myself_with_sig(- code);
309 exit(code);
310}
311
312#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
313 || ENABLE_FEATURE_LESS_WINCH
314static void re_wrap(void)
315{
316 int w = width;
317 int new_line_pos;
318 int src_idx;
319 int dst_idx;
320 int new_cur_fline = 0;
321 uint32_t lineno;
322 char linebuf[w + 1];
323 const char **old_flines = flines;
324 const char *s;
325 char **new_flines = NULL;
326 char *d;
327
328 if (option_mask32 & FLAG_N)
329 w -= 8;
330
331 src_idx = 0;
332 dst_idx = 0;
333 s = old_flines[0];
334 lineno = LINENO(s);
335 d = linebuf;
336 new_line_pos = 0;
337 while (1) {
338 *d = *s;
339 if (*d != '\0') {
340 new_line_pos++;
341 if (*d == '\t') {
342 new_line_pos += 7;
343 new_line_pos &= (~7);
344 }
345 s++;
346 d++;
347 if (new_line_pos >= w) {
348 int sz;
349
350 *d = '\0';
351 next_new:
352 sz = (d - linebuf) + 1;
353 d = ((char*)xmalloc(sz + 4)) + 4;
354 LINENO(d) = lineno;
355 memcpy(d, linebuf, sz);
356 new_flines = xrealloc_vector(new_flines, 8, dst_idx);
357 new_flines[dst_idx] = d;
358 dst_idx++;
359 if (new_line_pos < w) {
360
361 if (src_idx > max_fline)
362 break;
363 lineno = LINENO(s);
364 }
365 d = linebuf;
366 new_line_pos = 0;
367 }
368 continue;
369 }
370
371 free(MEMPTR(old_flines[src_idx]));
372
373 if (cur_fline == src_idx)
374 new_cur_fline = dst_idx;
375 src_idx++;
376
377 if (src_idx > max_fline)
378 goto next_new;
379 s = old_flines[src_idx];
380 if (lineno != LINENO(s)) {
381
382
383 goto next_new;
384 }
385 }
386
387 free(old_flines);
388 flines = (const char **)new_flines;
389
390 max_fline = dst_idx - 1;
391 last_line_pos = new_line_pos;
392 cur_fline = new_cur_fline;
393
394#if ENABLE_FEATURE_LESS_REGEXP
395 pattern_valid = 0;
396#endif
397}
398#endif
399
400#if ENABLE_FEATURE_LESS_REGEXP
401static void fill_match_lines(unsigned pos);
402#else
403#define fill_match_lines(pos) ((void)0)
404#endif
405
406static int at_end(void)
407{
408 return (option_mask32 & FLAG_S)
409 ? !(cur_fline <= max_fline &&
410 max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
411 : !(max_fline > cur_fline + max_displayed_line);
412}
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440static void read_lines(void)
441{
442#define readbuf bb_common_bufsiz1
443 char *current_line, *p;
444 int w = width;
445 char last_terminated = terminated;
446 time_t last_time = 0;
447 int retry_EAGAIN = 2;
448#if ENABLE_FEATURE_LESS_REGEXP
449 unsigned old_max_fline = max_fline;
450#endif
451
452
453 if (max_fline + 1 > MAXLINES)
454 return;
455
456 if (option_mask32 & FLAG_N)
457 w -= 8;
458
459 p = current_line = ((char*)xmalloc(w + 5)) + 4;
460 if (!last_terminated) {
461 const char *cp = flines[max_fline];
462 p = stpcpy(p, cp);
463 free(MEMPTR(cp));
464
465 } else {
466 max_fline++;
467 last_line_pos = 0;
468 }
469
470 while (1) {
471 *p = '\0';
472 terminated = 0;
473 while (1) {
474 char c;
475
476 if (readpos >= readeof) {
477 int flags = ndelay_on(0);
478
479 while (1) {
480 time_t t;
481
482 errno = 0;
483 eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
484 if (errno != EAGAIN)
485 break;
486 t = time(NULL);
487 if (t != last_time) {
488 last_time = t;
489 if (--retry_EAGAIN < 0)
490 break;
491 }
492 sched_yield();
493 }
494 fcntl(0, F_SETFL, flags);
495 readpos = 0;
496 readeof = eof_error;
497 if (eof_error <= 0)
498 goto reached_eof;
499 retry_EAGAIN = 1;
500 }
501 c = readbuf[readpos];
502
503
504
505 if (c == '\x8' && last_line_pos && p[-1] != '\t') {
506 readpos++;
507 last_line_pos--;
508
509 *--p = '\0';
510 continue;
511 }
512 {
513 size_t new_last_line_pos = last_line_pos + 1;
514 if (c == '\t') {
515 new_last_line_pos += 7;
516 new_last_line_pos &= (~7);
517 }
518 if ((int)new_last_line_pos > w)
519 break;
520 last_line_pos = new_last_line_pos;
521 }
522
523 readpos++;
524 if (c == '\n') {
525 terminated = 1;
526 last_line_pos = 0;
527 break;
528 }
529
530 if (c == '\0') c = '\n';
531 *p++ = c;
532 *p = '\0';
533 }
534#if 0
535
536
537
538
539
540
541 if (!last_terminated && !current_line[0]) {
542 last_terminated = 1;
543 max_lineno++;
544 continue;
545 }
546#endif
547 reached_eof:
548 last_terminated = terminated;
549 flines = xrealloc_vector(flines, 8, max_fline);
550
551 flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
552 LINENO(flines[max_fline]) = max_lineno;
553 if (terminated)
554 max_lineno++;
555
556 if (max_fline >= MAXLINES) {
557 eof_error = 0;
558 break;
559 }
560 if (!at_end()) {
561#if !ENABLE_FEATURE_LESS_REGEXP
562 break;
563#else
564 if (wanted_match >= num_matches) {
565 fill_match_lines(old_max_fline);
566 old_max_fline = max_fline;
567 }
568 if (wanted_match < num_matches)
569 break;
570#endif
571 }
572 if (eof_error <= 0) {
573 break;
574 }
575 max_fline++;
576 current_line = ((char*)xmalloc(w + 5)) + 4;
577 p = current_line;
578 last_line_pos = 0;
579 }
580
581 if (eof_error < 0) {
582 if (errno == EAGAIN) {
583 eof_error = 1;
584 } else {
585 print_statusline(bb_msg_read_error);
586 }
587 }
588#if ENABLE_FEATURE_LESS_FLAGS
589 else if (eof_error == 0)
590 num_lines = max_lineno;
591#endif
592
593 fill_match_lines(old_max_fline);
594#if ENABLE_FEATURE_LESS_REGEXP
595
596 wanted_match = -1;
597#endif
598#undef readbuf
599}
600
601#if ENABLE_FEATURE_LESS_FLAGS
602static int safe_lineno(int fline)
603{
604 if (fline >= max_fline)
605 fline = max_fline - 1;
606
607
608 if (fline < 0)
609 return 0;
610
611 return LINENO(flines[fline]) + 1;
612}
613
614
615static void update_num_lines(void)
616{
617 int count, fd;
618 struct stat stbuf;
619 ssize_t len, i;
620 char buf[4096];
621
622
623 if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
624 count = 0;
625 fd = open("/proc/self/fd/0", O_RDONLY);
626 if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
627
628 fd = open(filename, O_RDONLY);
629 }
630 if (fd < 0) {
631
632 num_lines = NOT_REGULAR_FILE;
633 return;
634 }
635 if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
636 num_lines = NOT_REGULAR_FILE;
637 goto do_close;
638 }
639 while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
640 for (i = 0; i < len; ++i) {
641 if (buf[i] == '\n' && ++count == MAXLINES)
642 goto done;
643 }
644 }
645 done:
646 num_lines = count;
647 do_close:
648 close(fd);
649 }
650}
651
652
653static void m_status_print(void)
654{
655 int first, last;
656 unsigned percent;
657
658 if (less_gets_pos >= 0)
659 return;
660
661 clear_line();
662 printf(HIGHLIGHT"%s", filename);
663 if (num_files > 1)
664 printf(" (file %i of %i)", current_file, num_files);
665
666 first = safe_lineno(cur_fline);
667 last = (option_mask32 & FLAG_S)
668 ? MIN(first + max_displayed_line, max_lineno)
669 : safe_lineno(cur_fline + max_displayed_line);
670 printf(" lines %i-%i", first, last);
671
672 update_num_lines();
673 if (num_lines >= 0)
674 printf("/%i", num_lines);
675
676 if (at_end()) {
677 printf(" (END)");
678 if (num_files > 1 && current_file != num_files)
679 printf(" - next: %s", files[current_file]);
680 } else if (num_lines > 0) {
681 percent = (100 * last + num_lines/2) / num_lines;
682 printf(" %i%%", percent <= 100 ? percent : 100);
683 }
684 printf(NORMAL);
685}
686#endif
687
688
689static void status_print(void)
690{
691 const char *p;
692
693 if (less_gets_pos >= 0)
694 return;
695
696
697#if ENABLE_FEATURE_LESS_FLAGS
698 if (option_mask32 & (FLAG_M|FLAG_m)) {
699 m_status_print();
700 return;
701 }
702
703#endif
704
705 clear_line();
706 if (cur_fline && !at_end()) {
707 bb_putchar(':');
708 return;
709 }
710 p = "(END)";
711 if (!cur_fline)
712 p = filename;
713 if (num_files > 1) {
714 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
715 p, current_file, num_files);
716 return;
717 }
718 print_hilite(p);
719}
720
721static const char controls[] ALIGN1 =
722
723 "\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
724 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
725 "\x7f\x9b";
726static const char ctrlconv[] ALIGN1 =
727
728
729 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
730 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
731
732static void print_lineno(const char *line)
733{
734 const char *fmt = " ";
735 unsigned n = n;
736
737 if (line != empty_line_marker) {
738
739 fmt = "%7u ";
740 n = LINENO(line) + 1;
741 if (n > 9999999 && MAXLINES > 9999999) {
742 n %= 10000000;
743 fmt = "%07u ";
744 }
745 }
746 printf(fmt, n);
747}
748
749
750#if ENABLE_FEATURE_LESS_REGEXP
751static void print_found(const char *line)
752{
753 int match_status;
754 int eflags;
755 char *growline;
756 regmatch_t match_structs;
757
758 char buf[width+1];
759 const char *str = line;
760 char *p = buf;
761 size_t n;
762
763 while (*str) {
764 n = strcspn(str, controls);
765 if (n) {
766 if (!str[n]) break;
767 memcpy(p, str, n);
768 p += n;
769 str += n;
770 }
771 n = strspn(str, controls);
772 memset(p, '.', n);
773 p += n;
774 str += n;
775 }
776 strcpy(p, str);
777
778
779
780
781
782
783
784 str = buf;
785 growline = NULL;
786 eflags = 0;
787 goto start;
788
789 while (match_status == 0) {
790 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
791 growline ? growline : "",
792 (int)match_structs.rm_so, str,
793 (int)(match_structs.rm_eo - match_structs.rm_so),
794 str + match_structs.rm_so);
795 free(growline);
796 growline = new;
797 str += match_structs.rm_eo;
798 line += match_structs.rm_eo;
799 eflags = REG_NOTBOL;
800 start:
801
802 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
803
804 if (match_structs.rm_so >= match_structs.rm_eo)
805 match_status = 1;
806 }
807
808 printf("%s%s\n", growline ? growline : "", str);
809 free(growline);
810}
811#else
812void print_found(const char *line);
813#endif
814
815static void print_ascii(const char *str)
816{
817 char buf[width+1];
818 char *p;
819 size_t n;
820
821 while (*str) {
822 n = strcspn(str, controls);
823 if (n) {
824 if (!str[n]) break;
825 printf("%.*s", (int) n, str);
826 str += n;
827 }
828 n = strspn(str, controls);
829 p = buf;
830 do {
831 if (*str == 0x7f)
832 *p++ = '?';
833 else if (*str == (char)0x9b)
834
835
836 *p++ = '{';
837 else
838 *p++ = ctrlconv[(unsigned char)*str];
839 str++;
840 } while (--n);
841 *p = '\0';
842 print_hilite(buf);
843 }
844 puts(str);
845}
846
847
848static void buffer_print(void)
849{
850 unsigned i;
851
852 move_cursor(0, 0);
853 for (i = 0; i <= max_displayed_line; i++) {
854 printf(CLEAR_2_EOL);
855 if (option_mask32 & FLAG_N)
856 print_lineno(buffer[i]);
857 if (pattern_valid)
858 print_found(buffer[i]);
859 else
860 print_ascii(buffer[i]);
861 }
862 if ((option_mask32 & FLAG_E)
863 && eof_error <= 0
864 && (max_fline - cur_fline) <= max_displayed_line
865 ) {
866 less_exit(EXIT_SUCCESS);
867 }
868 status_print();
869}
870
871static void buffer_fill_and_print(void)
872{
873 unsigned i;
874#if ENABLE_FEATURE_LESS_TRUNCATE
875 int fpos = cur_fline;
876
877 if (option_mask32 & FLAG_S) {
878
879 while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
880 fpos--;
881 }
882
883 i = 0;
884 while (i <= max_displayed_line && fpos <= max_fline) {
885 int lineno = LINENO(flines[fpos]);
886 buffer[i] = flines[fpos];
887 i++;
888 do {
889 fpos++;
890 } while ((fpos <= max_fline)
891 && (option_mask32 & FLAG_S)
892 && lineno == LINENO(flines[fpos])
893 );
894 }
895#else
896 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
897 buffer[i] = flines[cur_fline + i];
898 }
899#endif
900 for (; i <= max_displayed_line; i++) {
901 buffer[i] = empty_line_marker;
902 }
903 buffer_print();
904}
905
906
907static void goto_lineno(int target)
908{
909 if (target <= 0 ) {
910 cur_fline = 0;
911 }
912 else if (target > LINENO(flines[cur_fline])) {
913 retry:
914 while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
915 ++cur_fline;
916
917 if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
918 read_lines();
919 goto retry;
920 }
921 }
922 else {
923
924 while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
925 --cur_fline;
926 }
927}
928
929static void cap_cur_fline(void)
930{
931 if ((option_mask32 & FLAG_S)) {
932 if (cur_fline > max_fline)
933 cur_fline = max_fline;
934 if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
935 goto_lineno(max_lineno - max_displayed_line + TILDES);
936 read_lines();
937 }
938 }
939 else {
940 if (cur_fline + max_displayed_line > max_fline + TILDES)
941 cur_fline = max_fline - max_displayed_line + TILDES;
942 if (cur_fline < 0)
943 cur_fline = 0;
944 }
945}
946
947
948static void buffer_down(int nlines)
949{
950 if ((option_mask32 & FLAG_S))
951 goto_lineno(LINENO(flines[cur_fline]) + nlines);
952 else
953 cur_fline += nlines;
954 read_lines();
955 cap_cur_fline();
956 buffer_fill_and_print();
957}
958
959static void buffer_up(int nlines)
960{
961 if ((option_mask32 & FLAG_S)) {
962 goto_lineno(LINENO(flines[cur_fline]) - nlines);
963 }
964 else {
965 cur_fline -= nlines;
966 if (cur_fline < 0)
967 cur_fline = 0;
968 }
969 read_lines();
970 buffer_fill_and_print();
971}
972
973
974
975static void buffer_to_line(int linenum, int is_lineno)
976{
977 if (linenum <= 0)
978 cur_fline = 0;
979 else if (is_lineno)
980 goto_lineno(linenum);
981 else
982 cur_fline = linenum;
983 read_lines();
984 cap_cur_fline();
985 buffer_fill_and_print();
986}
987
988static void buffer_line(int linenum)
989{
990 buffer_to_line(linenum, FALSE);
991}
992
993static void buffer_lineno(int lineno)
994{
995 buffer_to_line(lineno, TRUE);
996}
997
998static void open_file_and_read_lines(void)
999{
1000 if (filename) {
1001 xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
1002#if ENABLE_FEATURE_LESS_FLAGS
1003 num_lines = REOPEN_AND_COUNT;
1004#endif
1005 } else {
1006
1007
1008 filename = xstrdup(bb_msg_standard_input);
1009#if ENABLE_FEATURE_LESS_FLAGS
1010 num_lines = REOPEN_STDIN;
1011#endif
1012 }
1013 readpos = 0;
1014 readeof = 0;
1015 last_line_pos = 0;
1016 terminated = 1;
1017 read_lines();
1018}
1019
1020
1021static void reinitialize(void)
1022{
1023 unsigned i;
1024
1025 if (flines) {
1026 for (i = 0; i <= max_fline; i++)
1027 free(MEMPTR(flines[i]));
1028 free(flines);
1029 flines = NULL;
1030 }
1031
1032 max_fline = -1;
1033 cur_fline = 0;
1034 max_lineno = 0;
1035 open_file_and_read_lines();
1036#if ENABLE_FEATURE_LESS_ASK_TERMINAL
1037 if (G.winsize_err)
1038 printf("\033[999;999H" "\033[6n");
1039#endif
1040 buffer_fill_and_print();
1041}
1042
1043static int64_t getch_nowait(void)
1044{
1045 int rd;
1046 int64_t key64;
1047 struct pollfd pfd[2];
1048
1049 pfd[0].fd = STDIN_FILENO;
1050 pfd[0].events = POLLIN;
1051 pfd[1].fd = kbd_fd;
1052 pfd[1].events = POLLIN;
1053 again:
1054 tcsetattr(kbd_fd, TCSANOW, &term_less);
1055
1056
1057
1058
1059
1060
1061 rd = 1;
1062
1063 if (at_end()) {
1064 if (eof_error > 0)
1065 rd = 0;
1066 }
1067
1068 if (less_gets_pos >= 0)
1069 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
1070 fflush_all();
1071
1072 if (kbd_input[0] == 0) {
1073#if ENABLE_FEATURE_LESS_WINCH
1074 while (1) {
1075 int r;
1076
1077 r = poll(pfd + rd, 2 - rd, -1);
1078 if ( winch_counter)
1079 return '\\';
1080 if (r) break;
1081 }
1082#else
1083 safe_poll(pfd + rd, 2 - rd, -1);
1084#endif
1085 }
1086
1087
1088
1089 key64 = read_key(kbd_fd, kbd_input, -2);
1090 if ((int)key64 == -1) {
1091 if (errno == EAGAIN) {
1092
1093
1094 read_lines();
1095 buffer_fill_and_print();
1096 goto again;
1097 }
1098
1099 less_exit(0);
1100 }
1101 set_tty_cooked();
1102 return key64;
1103}
1104
1105
1106
1107
1108static int64_t less_getch(int pos)
1109{
1110 int64_t key64;
1111 int key;
1112
1113 again:
1114 less_gets_pos = pos;
1115 key = key64 = getch_nowait();
1116 less_gets_pos = -1;
1117
1118
1119
1120
1121
1122 if (key >= 0 && key < ' ' && key != 0x0d && key != 8)
1123 goto again;
1124
1125 return key64;
1126}
1127
1128static char* less_gets(int sz)
1129{
1130 int c;
1131 unsigned i = 0;
1132 char *result = xzalloc(1);
1133
1134 while (1) {
1135 c = '\0';
1136 less_gets_pos = sz + i;
1137 c = getch_nowait();
1138 if (c == 0x0d) {
1139 result[i] = '\0';
1140 less_gets_pos = -1;
1141 return result;
1142 }
1143 if (c == 0x7f)
1144 c = 8;
1145 if (c == 8 && i) {
1146 printf("\x8 \x8");
1147 i--;
1148 }
1149 if (c < ' ')
1150 continue;
1151 if (i >= width - sz - 1)
1152 continue;
1153 bb_putchar(c);
1154 result[i++] = c;
1155 result = xrealloc(result, i+1);
1156 }
1157}
1158
1159static void examine_file(void)
1160{
1161 char *new_fname;
1162
1163 print_statusline("Examine: ");
1164 new_fname = less_gets(sizeof("Examine: ") - 1);
1165 if (!new_fname[0]) {
1166 status_print();
1167 err:
1168 free(new_fname);
1169 return;
1170 }
1171 if (access(new_fname, R_OK) != 0) {
1172 print_statusline("Cannot read this file");
1173 goto err;
1174 }
1175 free(filename);
1176 filename = new_fname;
1177
1178
1179
1180
1181 files[0] = filename;
1182 num_files = current_file = 1;
1183 reinitialize();
1184}
1185
1186
1187
1188
1189
1190static void change_file(int direction)
1191{
1192 if (current_file != ((direction > 0) ? num_files : 1)) {
1193 current_file = direction ? current_file + direction : 1;
1194 free(filename);
1195 filename = xstrdup(files[current_file - 1]);
1196 reinitialize();
1197 } else {
1198 print_statusline(direction > 0 ? "No next file" : "No previous file");
1199 }
1200}
1201
1202static void remove_current_file(void)
1203{
1204 unsigned i;
1205
1206 if (num_files < 2)
1207 return;
1208
1209 if (current_file != 1) {
1210 change_file(-1);
1211 for (i = 3; i <= num_files; i++)
1212 files[i - 2] = files[i - 1];
1213 num_files--;
1214 } else {
1215 change_file(1);
1216 for (i = 2; i <= num_files; i++)
1217 files[i - 2] = files[i - 1];
1218 num_files--;
1219 current_file--;
1220 }
1221}
1222
1223static void colon_process(void)
1224{
1225 int keypress;
1226
1227
1228 print_statusline(" :");
1229
1230 keypress = less_getch(2);
1231 switch (keypress) {
1232 case 'd':
1233 remove_current_file();
1234 break;
1235 case 'e':
1236 examine_file();
1237 break;
1238#if ENABLE_FEATURE_LESS_FLAGS
1239 case 'f':
1240 m_status_print();
1241 break;
1242#endif
1243 case 'n':
1244 change_file(1);
1245 break;
1246 case 'p':
1247 change_file(-1);
1248 break;
1249 case 'q':
1250 less_exit(EXIT_SUCCESS);
1251 break;
1252 case 'x':
1253 change_file(0);
1254 break;
1255 }
1256}
1257
1258#if ENABLE_FEATURE_LESS_REGEXP
1259static void normalize_match_pos(int match)
1260{
1261 if (match >= num_matches)
1262 match = num_matches - 1;
1263 if (match < 0)
1264 match = 0;
1265 match_pos = match;
1266}
1267
1268static void goto_match(int match)
1269{
1270 if (!pattern_valid)
1271 return;
1272 if (match < 0)
1273 match = 0;
1274
1275 if (match >= num_matches && eof_error > 0) {
1276 wanted_match = match;
1277 read_lines();
1278 }
1279 if (num_matches) {
1280 normalize_match_pos(match);
1281 buffer_line(match_lines[match_pos]);
1282 } else {
1283 print_statusline("No matches found");
1284 }
1285}
1286
1287static void fill_match_lines(unsigned pos)
1288{
1289 if (!pattern_valid)
1290 return;
1291
1292 while (pos <= max_fline) {
1293
1294 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
1295
1296 && !(num_matches && match_lines[num_matches-1] == pos)
1297 ) {
1298 match_lines = xrealloc_vector(match_lines, 4, num_matches);
1299 match_lines[num_matches++] = pos;
1300 }
1301 pos++;
1302 }
1303}
1304
1305static void regex_process(void)
1306{
1307 char *uncomp_regex, *err;
1308
1309
1310 free(match_lines);
1311 match_lines = NULL;
1312 match_pos = 0;
1313 num_matches = 0;
1314 if (pattern_valid) {
1315 regfree(&pattern);
1316 pattern_valid = 0;
1317 }
1318
1319
1320 clear_line();
1321 bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
1322 uncomp_regex = less_gets(1);
1323 if (!uncomp_regex[0]) {
1324 free(uncomp_regex);
1325 buffer_print();
1326 return;
1327 }
1328
1329
1330 err = regcomp_or_errmsg(&pattern, uncomp_regex,
1331 (option_mask32 & FLAG_I) ? REG_ICASE : 0);
1332 free(uncomp_regex);
1333 if (err) {
1334 print_statusline(err);
1335 free(err);
1336 return;
1337 }
1338
1339 pattern_valid = 1;
1340 match_pos = 0;
1341 fill_match_lines(0);
1342 while (match_pos < num_matches) {
1343 if ((int)match_lines[match_pos] > cur_fline)
1344 break;
1345 match_pos++;
1346 }
1347 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
1348 match_pos--;
1349
1350
1351
1352
1353 goto_match(match_pos);
1354}
1355#endif
1356
1357static void number_process(int first_digit)
1358{
1359 unsigned i;
1360 int num;
1361 int keypress;
1362 char num_input[sizeof(int)*4];
1363
1364 num_input[0] = first_digit;
1365
1366
1367 clear_line();
1368 printf(":%c", first_digit);
1369
1370
1371 i = 1;
1372 while (i < sizeof(num_input)-1) {
1373 keypress = less_getch(i + 1);
1374 if ((unsigned)keypress > 255 || !isdigit(keypress))
1375 break;
1376 num_input[i] = keypress;
1377 bb_putchar(keypress);
1378 i++;
1379 }
1380
1381 num_input[i] = '\0';
1382 num = bb_strtou(num_input, NULL, 10);
1383
1384 if (num < 1 || num > MAXLINES) {
1385 buffer_print();
1386 return;
1387 }
1388
1389
1390 switch (keypress) {
1391 case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1392 buffer_down(num);
1393 break;
1394 case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
1395 buffer_up(num);
1396 break;
1397 case 'g': case '<': case 'G': case '>':
1398 buffer_lineno(num - 1);
1399 break;
1400 case 'p': case '%':
1401#if ENABLE_FEATURE_LESS_FLAGS
1402 update_num_lines();
1403 num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
1404#else
1405 num = num * max_lineno / 100;
1406#endif
1407 buffer_lineno(num);
1408 break;
1409#if ENABLE_FEATURE_LESS_REGEXP
1410 case 'n':
1411 goto_match(match_pos + num);
1412 break;
1413 case '/':
1414 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1415 regex_process();
1416 break;
1417 case '?':
1418 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1419 regex_process();
1420 break;
1421#endif
1422 }
1423}
1424
1425#if ENABLE_FEATURE_LESS_DASHCMD
1426static void flag_change(void)
1427{
1428 int keypress;
1429
1430 clear_line();
1431 bb_putchar('-');
1432 keypress = less_getch(1);
1433
1434 switch (keypress) {
1435 case 'M':
1436 option_mask32 ^= FLAG_M;
1437 break;
1438 case 'm':
1439 option_mask32 ^= FLAG_m;
1440 break;
1441 case 'E':
1442 option_mask32 ^= FLAG_E;
1443 break;
1444 case '~':
1445 option_mask32 ^= FLAG_TILDE;
1446 break;
1447#if ENABLE_FEATURE_LESS_TRUNCATE
1448 case 'S':
1449 option_mask32 ^= FLAG_S;
1450 buffer_fill_and_print();
1451 break;
1452#endif
1453#if ENABLE_FEATURE_LESS_LINENUMS
1454 case 'N':
1455 option_mask32 ^= FLAG_N;
1456 re_wrap();
1457 buffer_fill_and_print();
1458 break;
1459#endif
1460 }
1461}
1462
1463#ifdef BLOAT
1464static void show_flag_status(void)
1465{
1466 int keypress;
1467 int flag_val;
1468
1469 clear_line();
1470 bb_putchar('_');
1471 keypress = less_getch(1);
1472
1473 switch (keypress) {
1474 case 'M':
1475 flag_val = option_mask32 & FLAG_M;
1476 break;
1477 case 'm':
1478 flag_val = option_mask32 & FLAG_m;
1479 break;
1480 case '~':
1481 flag_val = option_mask32 & FLAG_TILDE;
1482 break;
1483 case 'N':
1484 flag_val = option_mask32 & FLAG_N;
1485 break;
1486 case 'E':
1487 flag_val = option_mask32 & FLAG_E;
1488 break;
1489 default:
1490 flag_val = 0;
1491 break;
1492 }
1493
1494 clear_line();
1495 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1496}
1497#endif
1498
1499#endif
1500
1501static void save_input_to_file(void)
1502{
1503 const char *msg = "";
1504 char *current_line;
1505 unsigned i;
1506 FILE *fp;
1507
1508 print_statusline("Log file: ");
1509 current_line = less_gets(sizeof("Log file: ")-1);
1510 if (current_line[0]) {
1511 fp = fopen_for_write(current_line);
1512 if (!fp) {
1513 msg = "Error opening log file";
1514 goto ret;
1515 }
1516 for (i = 0; i <= max_fline; i++)
1517 fprintf(fp, "%s\n", flines[i]);
1518 fclose(fp);
1519 msg = "Done";
1520 }
1521 ret:
1522 print_statusline(msg);
1523 free(current_line);
1524}
1525
1526#if ENABLE_FEATURE_LESS_MARKS
1527static void add_mark(void)
1528{
1529 int letter;
1530
1531 print_statusline("Mark: ");
1532 letter = less_getch(sizeof("Mark: ") - 1);
1533
1534 if (isalpha(letter)) {
1535
1536 if (num_marks == 14)
1537 num_marks = 0;
1538
1539 mark_lines[num_marks][0] = letter;
1540 mark_lines[num_marks][1] = cur_fline;
1541 num_marks++;
1542 } else {
1543 print_statusline("Invalid mark letter");
1544 }
1545}
1546
1547static void goto_mark(void)
1548{
1549 int letter;
1550 int i;
1551
1552 print_statusline("Go to mark: ");
1553 letter = less_getch(sizeof("Go to mark: ") - 1);
1554 clear_line();
1555
1556 if (isalpha(letter)) {
1557 for (i = 0; i <= num_marks; i++)
1558 if (letter == mark_lines[i][0]) {
1559 buffer_line(mark_lines[i][1]);
1560 break;
1561 }
1562 if (num_marks == 14 && letter != mark_lines[14][0])
1563 print_statusline("Mark not set");
1564 } else
1565 print_statusline("Invalid mark letter");
1566}
1567#endif
1568
1569#if ENABLE_FEATURE_LESS_BRACKETS
1570static char opp_bracket(char bracket)
1571{
1572 switch (bracket) {
1573 case '{': case '[':
1574 bracket++;
1575 case '(':
1576 bracket++;
1577 break;
1578 case '}': case ']':
1579 bracket--;
1580 case ')':
1581 bracket--;
1582 break;
1583 };
1584 return bracket;
1585}
1586
1587static void match_right_bracket(char bracket)
1588{
1589 unsigned i;
1590
1591 if (strchr(flines[cur_fline], bracket) == NULL) {
1592 print_statusline("No bracket in top line");
1593 return;
1594 }
1595 bracket = opp_bracket(bracket);
1596 for (i = cur_fline + 1; i < max_fline; i++) {
1597 if (strchr(flines[i], bracket) != NULL) {
1598 buffer_line(i);
1599 return;
1600 }
1601 }
1602 print_statusline("No matching bracket found");
1603}
1604
1605static void match_left_bracket(char bracket)
1606{
1607 int i;
1608
1609 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1610 print_statusline("No bracket in bottom line");
1611 return;
1612 }
1613
1614 bracket = opp_bracket(bracket);
1615 for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1616 if (strchr(flines[i], bracket) != NULL) {
1617 buffer_line(i);
1618 return;
1619 }
1620 }
1621 print_statusline("No matching bracket found");
1622}
1623#endif
1624
1625static void keypress_process(int keypress)
1626{
1627 switch (keypress) {
1628 case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
1629 buffer_down(1);
1630 break;
1631 case KEYCODE_UP: case 'y': case 'k':
1632 buffer_up(1);
1633 break;
1634 case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
1635 buffer_down(max_displayed_line + 1);
1636 break;
1637 case KEYCODE_PAGEUP: case 'w': case 'b':
1638 buffer_up(max_displayed_line + 1);
1639 break;
1640 case 'd':
1641 buffer_down((max_displayed_line + 1) / 2);
1642 break;
1643 case 'u':
1644 buffer_up((max_displayed_line + 1) / 2);
1645 break;
1646 case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
1647 buffer_line(0);
1648 break;
1649 case KEYCODE_END: case 'G': case '>':
1650 cur_fline = MAXLINES;
1651 read_lines();
1652 buffer_line(cur_fline);
1653 break;
1654 case 'q': case 'Q':
1655 less_exit(EXIT_SUCCESS);
1656 break;
1657#if ENABLE_FEATURE_LESS_MARKS
1658 case 'm':
1659 add_mark();
1660 buffer_print();
1661 break;
1662 case '\'':
1663 goto_mark();
1664 buffer_print();
1665 break;
1666#endif
1667 case 'r': case 'R':
1668
1669
1670
1671 buffer_print();
1672 break;
1673
1674
1675
1676 case 's':
1677 save_input_to_file();
1678 break;
1679 case 'E':
1680 examine_file();
1681 break;
1682#if ENABLE_FEATURE_LESS_FLAGS
1683 case '=':
1684 m_status_print();
1685 break;
1686#endif
1687#if ENABLE_FEATURE_LESS_REGEXP
1688 case '/':
1689 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1690 regex_process();
1691 break;
1692 case 'n':
1693 goto_match(match_pos + 1);
1694 break;
1695 case 'N':
1696 goto_match(match_pos - 1);
1697 break;
1698 case '?':
1699 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1700 regex_process();
1701 break;
1702#endif
1703#if ENABLE_FEATURE_LESS_DASHCMD
1704 case '-':
1705 flag_change();
1706 buffer_print();
1707 break;
1708#ifdef BLOAT
1709 case '_':
1710 show_flag_status();
1711 break;
1712#endif
1713#endif
1714#if ENABLE_FEATURE_LESS_BRACKETS
1715 case '{': case '(': case '[':
1716 match_right_bracket(keypress);
1717 break;
1718 case '}': case ')': case ']':
1719 match_left_bracket(keypress);
1720 break;
1721#endif
1722 case ':':
1723 colon_process();
1724 break;
1725 }
1726
1727 if (isdigit(keypress))
1728 number_process(keypress);
1729}
1730
1731static void sig_catcher(int sig)
1732{
1733 less_exit(- sig);
1734}
1735
1736#if ENABLE_FEATURE_LESS_WINCH
1737static void sigwinch_handler(int sig UNUSED_PARAM)
1738{
1739 winch_counter++;
1740}
1741#endif
1742
1743int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1744int less_main(int argc, char **argv)
1745{
1746 char *tty_name;
1747 int tty_fd;
1748
1749 INIT_G();
1750
1751
1752
1753
1754
1755
1756
1757 getopt32(argv, "EMmN~I" IF_FEATURE_LESS_TRUNCATE("S") "s");
1758 argc -= optind;
1759 argv += optind;
1760 num_files = argc;
1761 files = argv;
1762
1763
1764
1765 if (!isatty(STDOUT_FILENO))
1766 return bb_cat(argv);
1767
1768 if (!num_files) {
1769 if (isatty(STDIN_FILENO)) {
1770
1771 bb_error_msg("missing filename");
1772 bb_show_usage();
1773 }
1774 } else {
1775 filename = xstrdup(files[0]);
1776 }
1777
1778 if (option_mask32 & FLAG_TILDE)
1779 empty_line_marker = "";
1780
1781
1782
1783
1784
1785
1786
1787
1788 tty_name = xmalloc_ttyname(STDOUT_FILENO);
1789 if (tty_name) {
1790 tty_fd = open(tty_name, O_RDONLY);
1791 free(tty_name);
1792 if (tty_fd < 0)
1793 goto try_ctty;
1794 } else {
1795
1796 try_ctty:
1797 tty_fd = open(CURRENT_TTY, O_RDONLY);
1798 if (tty_fd < 0)
1799 return bb_cat(argv);
1800 }
1801 ndelay_on(tty_fd);
1802 kbd_fd = tty_fd;
1803
1804 tcgetattr(kbd_fd, &term_orig);
1805 term_less = term_orig;
1806 term_less.c_lflag &= ~(ICANON | ECHO);
1807 term_less.c_iflag &= ~(IXON | ICRNL);
1808
1809 term_less.c_cc[VMIN] = 1;
1810 term_less.c_cc[VTIME] = 0;
1811
1812 IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1813
1814 if (width < 20 || max_displayed_line < 3)
1815 return bb_cat(argv);
1816 max_displayed_line -= 2;
1817
1818
1819 bb_signals(BB_FATAL_SIGS, sig_catcher);
1820#if ENABLE_FEATURE_LESS_WINCH
1821 signal(SIGWINCH, sigwinch_handler);
1822#endif
1823
1824 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1825 reinitialize();
1826 while (1) {
1827 int64_t keypress;
1828
1829#if ENABLE_FEATURE_LESS_WINCH
1830 while (WINCH_COUNTER) {
1831 again:
1832 winch_counter--;
1833 IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1834 IF_FEATURE_LESS_ASK_TERMINAL(got_size:)
1835
1836 if (width < 20)
1837 width = 20;
1838 if (max_displayed_line < 3)
1839 max_displayed_line = 3;
1840 max_displayed_line -= 2;
1841 free(buffer);
1842 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1843
1844
1845 if (WINCH_COUNTER)
1846 goto again;
1847 re_wrap();
1848 if (WINCH_COUNTER)
1849 goto again;
1850 buffer_fill_and_print();
1851
1852
1853 }
1854 keypress = less_getch(-1);
1855# if ENABLE_FEATURE_LESS_ASK_TERMINAL
1856 if ((int32_t)keypress == KEYCODE_CURSOR_POS) {
1857 uint32_t rc = (keypress >> 32);
1858 width = (rc & 0x7fff);
1859 max_displayed_line = ((rc >> 16) & 0x7fff);
1860 goto got_size;
1861 }
1862# endif
1863#else
1864 keypress = less_getch(-1);
1865#endif
1866 keypress_process(keypress);
1867 }
1868}
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076