blob: 04ad2d1a31c7e4b10169278c01dfd3b45a370bbe
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Mini more implementation for busybox |
4 | * |
5 | * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>. |
6 | * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> |
7 | * |
8 | * Latest version blended together by Erik Andersen <andersen@codepoet.org>, |
9 | * based on the original more implementation by Bruce, and code from the |
10 | * Debian boot-floppies team. |
11 | * |
12 | * Termios corrects by Vladimir Oleynik <dzo@simtreas.ru> |
13 | * |
14 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
15 | */ |
16 | |
17 | //usage:#define more_trivial_usage |
18 | //usage: "[FILE]..." |
19 | //usage:#define more_full_usage "\n\n" |
20 | //usage: "View FILE (or stdin) one screenful at a time" |
21 | //usage: |
22 | //usage:#define more_example_usage |
23 | //usage: "$ dmesg | more\n" |
24 | |
25 | #include "libbb.h" |
26 | |
27 | /* Support for FEATURE_USE_TERMIOS */ |
28 | |
29 | struct globals { |
30 | int cin_fileno; |
31 | struct termios initial_settings; |
32 | struct termios new_settings; |
33 | } FIX_ALIASING; |
34 | #define G (*(struct globals*)bb_common_bufsiz1) |
35 | #define INIT_G() ((void)0) |
36 | #define initial_settings (G.initial_settings) |
37 | #define new_settings (G.new_settings ) |
38 | #define cin_fileno (G.cin_fileno ) |
39 | |
40 | #define setTermSettings(fd, argp) \ |
41 | do { \ |
42 | if (ENABLE_FEATURE_USE_TERMIOS) \ |
43 | tcsetattr(fd, TCSANOW, argp); \ |
44 | } while (0) |
45 | #define getTermSettings(fd, argp) tcgetattr(fd, argp) |
46 | |
47 | static void gotsig(int sig UNUSED_PARAM) |
48 | { |
49 | /* bb_putchar_stderr doesn't use stdio buffering, |
50 | * therefore it is safe in signal handler */ |
51 | bb_putchar_stderr('\n'); |
52 | setTermSettings(cin_fileno, &initial_settings); |
53 | _exit(EXIT_FAILURE); |
54 | } |
55 | |
56 | #define CONVERTED_TAB_SIZE 8 |
57 | |
58 | int more_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
59 | int more_main(int argc UNUSED_PARAM, char **argv) |
60 | { |
61 | int c = EOF; /* for compiler */ |
62 | int input = 0; |
63 | int spaces = 0; |
64 | int please_display_more_prompt; |
65 | struct stat st; |
66 | FILE *file; |
67 | FILE *cin; |
68 | unsigned len, lines; |
69 | unsigned terminal_width; |
70 | unsigned terminal_height; |
71 | |
72 | INIT_G(); |
73 | |
74 | argv++; |
75 | /* Another popular pager, most, detects when stdout |
76 | * is not a tty and turns into cat. This makes sense. */ |
77 | if (!isatty(STDOUT_FILENO)) |
78 | return bb_cat(argv); |
79 | cin = fopen_for_read(CURRENT_TTY); |
80 | if (!cin) |
81 | return bb_cat(argv); |
82 | |
83 | if (ENABLE_FEATURE_USE_TERMIOS) { |
84 | cin_fileno = fileno(cin); |
85 | getTermSettings(cin_fileno, &initial_settings); |
86 | new_settings = initial_settings; |
87 | new_settings.c_lflag &= ~(ICANON | ECHO); |
88 | new_settings.c_cc[VMIN] = 1; |
89 | new_settings.c_cc[VTIME] = 0; |
90 | setTermSettings(cin_fileno, &new_settings); |
91 | bb_signals(0 |
92 | + (1 << SIGINT) |
93 | + (1 << SIGQUIT) |
94 | + (1 << SIGTERM) |
95 | , gotsig); |
96 | } |
97 | |
98 | do { |
99 | file = stdin; |
100 | if (*argv) { |
101 | file = fopen_or_warn(*argv, "r"); |
102 | if (!file) |
103 | continue; |
104 | } |
105 | st.st_size = 0; |
106 | fstat(fileno(file), &st); |
107 | |
108 | please_display_more_prompt = 0; |
109 | /* never returns w, h <= 1 */ |
110 | get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height); |
111 | terminal_height -= 1; |
112 | |
113 | len = 0; |
114 | lines = 0; |
115 | while (spaces || (c = getc(file)) != EOF) { |
116 | int wrap; |
117 | if (spaces) |
118 | spaces--; |
119 | loop_top: |
120 | if (input != 'r' && please_display_more_prompt) { |
121 | len = printf("--More-- "); |
122 | if (st.st_size != 0) { |
123 | len += printf("(%u%% of %"FILESIZE_FMT"u bytes)", |
124 | (int) (ftello(file)*100 / st.st_size), |
125 | st.st_size); |
126 | } |
127 | fflush_all(); |
128 | |
129 | /* |
130 | * We've just displayed the "--More--" prompt, so now we need |
131 | * to get input from the user. |
132 | */ |
133 | for (;;) { |
134 | input = getc(cin); |
135 | input = tolower(input); |
136 | if (!ENABLE_FEATURE_USE_TERMIOS) |
137 | printf("\033[A"); /* cursor up */ |
138 | /* Erase the last message */ |
139 | printf("\r%*s\r", len, ""); |
140 | |
141 | /* Due to various multibyte escape |
142 | * sequences, it's not ok to accept |
143 | * any input as a command to scroll |
144 | * the screen. We only allow known |
145 | * commands, else we show help msg. */ |
146 | if (input == ' ' || input == '\n' || input == 'q' || input == 'r') |
147 | break; |
148 | len = printf("(Enter:next line Space:next page Q:quit R:show the rest)"); |
149 | } |
150 | len = 0; |
151 | lines = 0; |
152 | please_display_more_prompt = 0; |
153 | |
154 | if (input == 'q') |
155 | goto end; |
156 | |
157 | /* The user may have resized the terminal. |
158 | * Re-read the dimensions. */ |
159 | if (ENABLE_FEATURE_USE_TERMIOS) { |
160 | get_terminal_width_height(cin_fileno, &terminal_width, &terminal_height); |
161 | terminal_height -= 1; |
162 | } |
163 | } |
164 | |
165 | /* Crudely convert tabs into spaces, which are |
166 | * a bajillion times easier to deal with. */ |
167 | if (c == '\t') { |
168 | spaces = ((unsigned)~len) % CONVERTED_TAB_SIZE; |
169 | c = ' '; |
170 | } |
171 | |
172 | /* |
173 | * There are two input streams to worry about here: |
174 | * |
175 | * c : the character we are reading from the file being "mored" |
176 | * input: a character received from the keyboard |
177 | * |
178 | * If we hit a newline in the _file_ stream, we want to test and |
179 | * see if any characters have been hit in the _input_ stream. This |
180 | * allows the user to quit while in the middle of a file. |
181 | */ |
182 | wrap = (++len > terminal_width); |
183 | if (c == '\n' || wrap) { |
184 | /* Then outputting this character |
185 | * will move us to a new line. */ |
186 | if (++lines >= terminal_height || input == '\n') |
187 | please_display_more_prompt = 1; |
188 | len = 0; |
189 | } |
190 | if (c != '\n' && wrap) { |
191 | /* Then outputting this will also put a character on |
192 | * the beginning of that new line. Thus we first want to |
193 | * display the prompt (if any), so we skip the putchar() |
194 | * and go back to the top of the loop, without reading |
195 | * a new character. */ |
196 | goto loop_top; |
197 | } |
198 | /* My small mind cannot fathom backspaces and UTF-8 */ |
199 | putchar(c); |
200 | die_if_ferror_stdout(); /* if tty was destroyed (closed xterm, etc) */ |
201 | } |
202 | fclose(file); |
203 | fflush_all(); |
204 | } while (*argv && *++argv); |
205 | end: |
206 | setTermSettings(cin_fileno, &initial_settings); |
207 | return 0; |
208 | } |
209 |