]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
5c36a0eb | 2 | * Copyright (C) 1980 The Regents of the University of California. |
6dbe3af9 KZ |
3 | * All rights reserved. |
4 | * | |
5 | * Redistribution and use in source and binary forms are permitted | |
6 | * provided that the above copyright notice and this paragraph are | |
7 | * duplicated in all such forms and that any documentation, | |
8 | * advertising materials, and other materials related to such | |
9 | * distribution and use acknowledge that the software was developed | |
10 | * by the University of California, Berkeley. The name of the | |
11 | * University may not be used to endorse or promote products derived | |
12 | * from this software without specific prior written permission. | |
13 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR | |
14 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
218b1dd6 | 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
6dbe3af9 KZ |
16 | */ |
17 | ||
4eaab145 SK |
18 | /* more.c - General purpose tty output filter and file perusal program |
19 | * | |
20 | * by Eric Shienbrood, UC Berkeley | |
21 | * | |
22 | * modified by Geoff Peck | |
23 | * UCB to add underlining, single spacing | |
24 | * modified by John Foderaro | |
25 | * UCB to add -c and MORE environment variable | |
26 | * modified by Erik Troan <ewt@redhat.com> | |
27 | * to be more posix and so compile on linux/axp. | |
28 | * modified by Kars de Jong <jongk@cs.utwente.nl> | |
29 | * to use terminfo instead of termcap. | |
b50945d4 | 30 | * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> |
4eaab145 SK |
31 | * added Native Language Support |
32 | * 1999-03-19 Arnaldo Carvalho de Melo <acme@conectiva.com.br> | |
33 | * more nls translatable strings | |
34 | * 1999-05-09 aeb | |
35 | * applied a RedHat patch (setjmp->sigsetjmp); without it a second | |
36 | * ^Z would fail. | |
37 | * 1999-05-09 aeb | |
38 | * undone Kars' work, so that more works without libcurses (and | |
39 | * hence can be in /bin with libcurses being in | |
40 | * /usr/lib which may not be mounted). However, when termcap is not | |
41 | * present curses can still be used. | |
42 | * 2010-10-21 Davidlohr Bueso <dave@gnu.org> | |
43 | * modified mem allocation handling for util-linux | |
44 | */ | |
6dbe3af9 KZ |
45 | |
46 | #include <stdio.h> | |
fd6b7a7f | 47 | #include <string.h> |
6dbe3af9 | 48 | #include <unistd.h> |
c8b1fa60 SK |
49 | #include <stdlib.h> |
50 | #include <stdarg.h> | |
6dbe3af9 KZ |
51 | #include <sys/param.h> |
52 | #include <ctype.h> | |
53 | #include <signal.h> | |
54 | #include <errno.h> | |
8271f58d | 55 | #include <fcntl.h> |
6dbe3af9 | 56 | #include <termios.h> |
fd6b7a7f | 57 | #include <sys/ioctl.h> |
6dbe3af9 KZ |
58 | #include <sys/stat.h> |
59 | #include <sys/file.h> | |
fd6b7a7f | 60 | #include <sys/wait.h> |
a8f98304 | 61 | #include <regex.h> |
823ef319 SK |
62 | #include <assert.h> |
63 | #include <poll.h> | |
64 | #include <sys/signalfd.h> | |
f8492042 | 65 | #include <paths.h> |
e3cb27f5 | 66 | #include <getopt.h> |
a8f98304 | 67 | |
83efe346 YK |
68 | #ifdef HAVE_SYS_TTYDEFAULTS_H |
69 | # include <sys/ttydefaults.h> | |
70 | #endif | |
71 | ||
a8f98304 SK |
72 | #if defined(HAVE_NCURSESW_TERM_H) |
73 | # include <ncursesw/term.h> | |
74 | #elif defined(HAVE_NCURSES_TERM_H) | |
75 | # include <ncurses/term.h> | |
76 | #elif defined(HAVE_TERM_H) | |
77 | # include <term.h> | |
78 | #endif | |
0d4e5f8e | 79 | |
09070e1a SK |
80 | #ifdef HAVE_MAGIC |
81 | # include <magic.h> | |
82 | #endif | |
83 | ||
b87cbe84 | 84 | #include "strutils.h" |
7eda085c | 85 | #include "nls.h" |
0d4e5f8e | 86 | #include "xalloc.h" |
1d4ad1de | 87 | #include "widechar.h" |
b87cbe84 | 88 | #include "closestream.h" |
09070e1a | 89 | #include "env.h" |
6dbe3af9 | 90 | |
951666e9 KZ |
91 | #ifdef TEST_PROGRAM |
92 | # define NON_INTERACTIVE_MORE 1 | |
93 | #endif | |
94 | ||
5cd45502 | 95 | #define BACKSPACE "\b" |
7cc31c18 | 96 | #define CARAT "^" |
a8f98304 | 97 | |
1f861935 SK |
98 | #define ARROW_UP "\x1b\x5b\x41" |
99 | #define ARROW_DOWN "\x1b\x5b\x42" | |
100 | #define PAGE_UP "\x1b\x5b\x35\x7e" | |
101 | #define PAGE_DOWN "\x1b\x5b\x36\x7e" | |
102 | ||
d910f311 | 103 | #define MIN_LINE_SZ 256 /* minimal line_buf buffer size */ |
4eaab145 | 104 | #define ESC '\033' |
bf6db55a SK |
105 | #define SCROLL_LEN 11 |
106 | #define LINES_PER_PAGE 24 | |
107 | #define NUM_COLUMNS 80 | |
108 | #define TERMINAL_BUF 4096 | |
109 | #define INIT_BUF 80 | |
bf6db55a | 110 | #define COMMAND_BUF 200 |
eecbb2cf | 111 | #define REGERR_BUF NUM_COLUMNS |
a8f98304 SK |
112 | |
113 | #define TERM_AUTO_RIGHT_MARGIN "am" | |
5cd45502 | 114 | #define TERM_BACKSPACE "cub1" |
a8f98304 SK |
115 | #define TERM_CEOL "xhp" |
116 | #define TERM_CLEAR "clear" | |
117 | #define TERM_CLEAR_TO_LINE_END "el" | |
118 | #define TERM_CLEAR_TO_SCREEN_END "ed" | |
119 | #define TERM_COLS "cols" | |
120 | #define TERM_CURSOR_ADDRESS "cup" | |
121 | #define TERM_EAT_NEW_LINE "xenl" | |
a8f98304 | 122 | #define TERM_EXIT_STANDARD_MODE "rmso" |
a8f98304 SK |
123 | #define TERM_HARD_COPY "hc" |
124 | #define TERM_HOME "home" | |
125 | #define TERM_LINE_DOWN "cud1" | |
126 | #define TERM_LINES "lines" | |
127 | #define TERM_OVER_STRIKE "os" | |
a8f98304 SK |
128 | #define TERM_STANDARD_MODE "smso" |
129 | #define TERM_STD_MODE_GLITCH "xmc" | |
6dbe3af9 | 130 | |
1f861935 SK |
131 | /* Used in read_command() */ |
132 | typedef enum { | |
133 | more_kc_unknown_command, | |
134 | more_kc_colon, | |
135 | more_kc_repeat_previous, | |
136 | more_kc_backwards, | |
137 | more_kc_jump_lines_per_screen, | |
138 | more_kc_set_lines_per_screen, | |
139 | more_kc_set_scroll_len, | |
140 | more_kc_quit, | |
9f303a2b HM |
141 | more_kc_skip_forward_screen, |
142 | more_kc_skip_forward_line, | |
1f861935 SK |
143 | more_kc_next_line, |
144 | more_kc_clear_screen, | |
145 | more_kc_previous_search_match, | |
146 | more_kc_display_line, | |
147 | more_kc_display_file_and_line, | |
148 | more_kc_repeat_search, | |
149 | more_kc_search, | |
150 | more_kc_run_shell, | |
151 | more_kc_help, | |
152 | more_kc_next_file, | |
153 | more_kc_previous_file, | |
154 | more_kc_run_editor | |
155 | } more_key_commands; | |
156 | struct number_command { | |
157 | unsigned int number; | |
158 | more_key_commands key; | |
159 | }; | |
160 | ||
7cc31c18 | 161 | struct more_control { |
d910f311 SK |
162 | struct termios output_tty; /* output terminal */ |
163 | struct termios original_tty; /* original terminal settings */ | |
82129a6f | 164 | FILE *current_file; /* currently open input file */ |
ba105bb5 SK |
165 | off_t file_position; /* file position */ |
166 | off_t file_size; /* file size */ | |
d910f311 SK |
167 | int argv_position; /* argv[] position */ |
168 | int lines_per_screen; /* screen size in lines */ | |
169 | int d_scroll_len; /* number of lines scrolled by 'd' */ | |
170 | int prompt_len; /* message prompt length */ | |
171 | int current_line; /* line we are currently at */ | |
e3cb27f5 | 172 | int next_jump; /* number of lines to skip ahead */ |
d910f311 SK |
173 | char **file_names; /* The list of file names */ |
174 | int num_files; /* Number of files left to process */ | |
7cc31c18 | 175 | char *shell; /* name of the shell to use */ |
823ef319 SK |
176 | int sigfd; /* signalfd() file descriptor */ |
177 | sigset_t sigset; /* signal operations */ | |
d910f311 SK |
178 | char *line_buf; /* line buffer */ |
179 | size_t line_sz; /* size of line_buf buffer */ | |
180 | int lines_per_page; /* lines per page */ | |
181 | char *clear; /* clear screen */ | |
182 | char *erase_line; /* erase line */ | |
183 | char *enter_std; /* enter standout mode */ | |
184 | char *exit_std; /* exit standout mode */ | |
d910f311 SK |
185 | char *backspace_ch; /* backspace character */ |
186 | char *go_home; /* go to home */ | |
5cd45502 | 187 | char *move_line_down; /* move line down */ |
d910f311 SK |
188 | char *clear_rest; /* clear rest of screen */ |
189 | int num_columns; /* number of columns */ | |
e3cb27f5 | 190 | char *next_search; /* file beginning search string */ |
d910f311 | 191 | char *previous_search; /* previous search() buf[] item */ |
7cc31c18 | 192 | struct { |
ba105bb5 | 193 | off_t row_num; /* row file position */ |
d910f311 | 194 | long line_num; /* line number */ |
7cc31c18 SK |
195 | } context, |
196 | screen_start; | |
1f861935 SK |
197 | unsigned int leading_number; /* number in front of key command */ |
198 | struct number_command previous_command; /* previous key command */ | |
474c61c0 | 199 | char *shell_line; /* line to execute in subshell */ |
09070e1a SK |
200 | #ifdef HAVE_MAGIC |
201 | magic_t magic; /* libmagic database entries */ | |
202 | #endif | |
7cc31c18 | 203 | unsigned int |
fe237228 | 204 | ignore_stdin:1, /* POLLHUP; peer closed pipe */ |
d910f311 SK |
205 | bad_stdout:1, /* true if overwriting does not turn off standout */ |
206 | catch_suspend:1, /* we should catch the SIGTSTP signal */ | |
207 | clear_line_ends:1, /* do not scroll, paint each screen from the top */ | |
63f96f68 | 208 | clear_first:1, /* is first character in file \f */ |
d910f311 SK |
209 | dumb_tty:1, /* is terminal type known */ |
210 | eat_newline:1, /* is newline ignored after 80 cols */ | |
d910f311 SK |
211 | erase_input_ok:1, /* is erase input supported */ |
212 | erase_previous_ok:1, /* is erase previous supported */ | |
df6b29d3 | 213 | exit_on_eof:1, /* exit on EOF */ |
d910f311 SK |
214 | first_file:1, /* is the input file the first in list */ |
215 | fold_long_lines:1, /* fold long lines */ | |
216 | hard_tabs:1, /* print spaces instead of '\t' */ | |
217 | hard_tty:1, /* is this hard copy terminal (a printer or such) */ | |
1f861935 | 218 | leading_colon:1, /* key command has leading ':' character */ |
df6b29d3 | 219 | is_eof:1, /* EOF detected */ |
d910f311 SK |
220 | is_paused:1, /* is output paused */ |
221 | no_quit_dialog:1, /* suppress quit dialog */ | |
222 | no_scroll:1, /* do not scroll, clear the screen and then display text */ | |
223 | no_tty_in:1, /* is input in interactive mode */ | |
224 | no_tty_out:1, /* is output in interactive mode */ | |
2ae8b757 | 225 | no_tty_err:1, /* is stderr terminal */ |
63f96f68 | 226 | print_banner:1, /* print file name banner */ |
1f861935 | 227 | reading_num:1, /* are we reading leading_number */ |
d910f311 | 228 | report_errors:1, /* is an error reported */ |
63f96f68 | 229 | search_at_start:1, /* search pattern defined at start up */ |
823ef319 | 230 | search_called:1, /* previous more command was a search */ |
d910f311 | 231 | squeeze_spaces:1, /* suppress white space */ |
d910f311 SK |
232 | stdout_glitch:1, /* terminal has standout mode glitch */ |
233 | stop_after_formfeed:1, /* stop after form feeds */ | |
234 | suppress_bell:1, /* suppress bell */ | |
d910f311 | 235 | wrap_margin:1; /* set if automargins */ |
7cc31c18 SK |
236 | }; |
237 | ||
c1da6d1e | 238 | static void __attribute__((__noreturn__)) usage(void) |
bf6db55a | 239 | { |
e3cb27f5 SK |
240 | printf("%s", USAGE_HEADER); |
241 | printf(_(" %s [options] <file>...\n"), program_invocation_short_name); | |
242 | ||
243 | printf("%s", USAGE_SEPARATOR); | |
ed07532a | 244 | printf("%s\n", _("Display the contents of a file in a terminal.")); |
e3cb27f5 SK |
245 | |
246 | printf("%s", USAGE_OPTIONS); | |
247 | printf("%s\n", _(" -d, --silent display help instead of ringing bell")); | |
248 | printf("%s\n", _(" -f, --logical count logical rather than screen lines")); | |
249 | printf("%s\n", _(" -l, --no-pause suppress pause after form feed")); | |
250 | printf("%s\n", _(" -c, --print-over do not scroll, display text and clean line ends")); | |
251 | printf("%s\n", _(" -p, --clean-print do not scroll, clean screen and display text")); | |
df6b29d3 | 252 | printf("%s\n", _(" -e, --exit-on-eof exit on end-of-file")); |
e3cb27f5 SK |
253 | printf("%s\n", _(" -s, --squeeze squeeze multiple blank lines into one")); |
254 | printf("%s\n", _(" -u, --plain suppress underlining and bold")); | |
255 | printf("%s\n", _(" -n, --lines <number> the number of lines per screenful")); | |
256 | printf("%s\n", _(" -<number> same as --lines")); | |
257 | printf("%s\n", _(" +<number> display file beginning from line number")); | |
258 | printf("%s\n", _(" +/<pattern> display file beginning from pattern match")); | |
259 | printf("%s", USAGE_SEPARATOR); | |
260 | printf(USAGE_HELP_OPTIONS(23)); | |
f45f3ec3 | 261 | printf(USAGE_MAN_TAIL("more(1)")); |
c1da6d1e | 262 | exit(EXIT_SUCCESS); |
66ee8158 KZ |
263 | } |
264 | ||
e3cb27f5 | 265 | static void argscan(struct more_control *ctl, int as_argc, char **as_argv) |
4eaab145 | 266 | { |
e3cb27f5 SK |
267 | int c, opt; |
268 | static const struct option longopts[] = { | |
269 | { "silent", no_argument, NULL, 'd' }, | |
270 | { "logical", no_argument, NULL, 'f' }, | |
271 | { "no-pause", no_argument, NULL, 'l' }, | |
272 | { "print-over", no_argument, NULL, 'c' }, | |
273 | { "clean-print", no_argument, NULL, 'p' }, | |
df6b29d3 | 274 | { "exit-on-eof", no_argument, NULL, 'e' }, |
e3cb27f5 SK |
275 | { "squeeze", no_argument, NULL, 's' }, |
276 | { "plain", no_argument, NULL, 'u' }, | |
277 | { "lines", required_argument, NULL, 'n' }, | |
278 | { "version", no_argument, NULL, 'V' }, | |
279 | { "help", no_argument, NULL, 'h' }, | |
280 | { NULL, 0, NULL, 0 } | |
281 | }; | |
282 | ||
283 | /* Take care of number option and +args. */ | |
284 | for (opt = 0; opt < as_argc; opt++) { | |
285 | int move = 0; | |
286 | ||
287 | if (as_argv[opt][0] == '-' && isdigit_string(as_argv[opt] + 1)) { | |
288 | ctl->lines_per_screen = | |
289 | strtos16_or_err(as_argv[opt], _("failed to parse number")); | |
290 | ctl->lines_per_screen = abs(ctl->lines_per_screen); | |
291 | move = 1; | |
292 | } else if (as_argv[opt][0] == '+') { | |
293 | if (isdigit_string(as_argv[opt] + 1)) { | |
294 | ctl->next_jump = strtos32_or_err(as_argv[opt], | |
295 | _("failed to parse number")) - 1; | |
296 | move = 1; | |
297 | } else if (as_argv[opt][1] == '/') { | |
298 | free(ctl->next_search); | |
299 | ctl->next_search = xstrdup(as_argv[opt] + 2); | |
300 | ctl->search_at_start = 1; | |
301 | move = 1; | |
6dbe3af9 | 302 | } |
e3cb27f5 SK |
303 | } |
304 | if (move) { | |
8616dff4 | 305 | as_argc = ul_remove_entry(as_argv, opt, as_argc); |
e3cb27f5 SK |
306 | opt--; |
307 | } | |
308 | } | |
309 | ||
310 | while ((c = getopt_long(as_argc, as_argv, "dflcpsun:eVh", longopts, NULL)) != -1) { | |
311 | switch (c) { | |
4eaab145 | 312 | case 'd': |
d910f311 | 313 | ctl->suppress_bell = 1; |
6dbe3af9 | 314 | break; |
4eaab145 | 315 | case 'l': |
d910f311 | 316 | ctl->stop_after_formfeed = 0; |
6dbe3af9 | 317 | break; |
4eaab145 | 318 | case 'f': |
d910f311 | 319 | ctl->fold_long_lines = 0; |
6dbe3af9 | 320 | break; |
4eaab145 | 321 | case 'p': |
d910f311 | 322 | ctl->no_scroll = 1; |
6dbe3af9 | 323 | break; |
4eaab145 | 324 | case 'c': |
d910f311 | 325 | ctl->clear_line_ends = 1; |
6dbe3af9 | 326 | break; |
4eaab145 | 327 | case 's': |
d910f311 | 328 | ctl->squeeze_spaces = 1; |
6dbe3af9 | 329 | break; |
4eaab145 | 330 | case 'u': |
6dbe3af9 | 331 | break; |
e3cb27f5 SK |
332 | case 'n': |
333 | ctl->lines_per_screen = strtou16_or_err(optarg, _("argument error")); | |
334 | break; | |
df6b29d3 IJ |
335 | case 'e': |
336 | ctl->exit_on_eof = 1; | |
4eaab145 SK |
337 | break; |
338 | case 'V': | |
2c308875 | 339 | print_version(EXIT_SUCCESS); |
e3cb27f5 SK |
340 | case 'h': |
341 | usage(); | |
4eaab145 | 342 | default: |
c1da6d1e | 343 | errtryhelp(EXIT_FAILURE); |
66ee8158 | 344 | break; |
6dbe3af9 | 345 | } |
6dbe3af9 | 346 | } |
e3cb27f5 SK |
347 | ctl->num_files = as_argc - optind; |
348 | ctl->file_names = as_argv + optind; | |
349 | } | |
350 | ||
351 | static void env_argscan(struct more_control *ctl, const char *s) | |
352 | { | |
353 | char **env_argv; | |
354 | int env_argc = 1; | |
355 | int size = 8; | |
356 | const char delim[] = { ' ', '\n', '\t', '\0' }; | |
357 | char *str = xstrdup(s); | |
358 | char *key = NULL, *tok; | |
359 | ||
343b2e4b | 360 | env_argv = xreallocarray(NULL, size, sizeof(char *)); |
e3cb27f5 SK |
361 | env_argv[0] = _("MORE environment variable"); /* program name */ |
362 | for (tok = strtok_r(str, delim, &key); tok; tok = strtok_r(NULL, delim, &key)) { | |
ecdfc9aa | 363 | if (size == env_argc) { |
e3cb27f5 | 364 | size *= 2; |
64d6d400 | 365 | env_argv = xreallocarray(env_argv, size, sizeof(char *)); |
e3cb27f5 | 366 | } |
ecdfc9aa | 367 | env_argv[env_argc++] = tok; |
e3cb27f5 SK |
368 | } |
369 | ||
370 | argscan(ctl, env_argc, env_argv); | |
371 | /* Reset optind, command line parsing needs this. */ | |
372 | optind = 0; | |
373 | free(str); | |
374 | free(env_argv); | |
6dbe3af9 KZ |
375 | } |
376 | ||
ba105bb5 | 377 | static void more_fseek(struct more_control *ctl, off_t pos) |
bd6a253f | 378 | { |
d910f311 | 379 | ctl->file_position = pos; |
ba105bb5 | 380 | fseeko(ctl->current_file, pos, SEEK_SET); |
bd6a253f SK |
381 | } |
382 | ||
82129a6f | 383 | static int more_getc(struct more_control *ctl) |
bd6a253f | 384 | { |
ccb3af02 | 385 | int ret = fgetc(ctl->current_file); |
ba105bb5 SK |
386 | ctl->file_position = ftello(ctl->current_file); |
387 | return ret; | |
bd6a253f SK |
388 | } |
389 | ||
82129a6f | 390 | static int more_ungetc(struct more_control *ctl, int c) |
bd6a253f | 391 | { |
ba105bb5 SK |
392 | int ret = ungetc(c, ctl->current_file); |
393 | ctl->file_position = ftello(ctl->current_file); | |
394 | return ret; | |
bd6a253f SK |
395 | } |
396 | ||
5cd45502 SK |
397 | static void print_separator(const int c, int n) |
398 | { | |
399 | while (n--) | |
400 | putchar(c); | |
401 | putchar('\n'); | |
402 | } | |
403 | ||
09070e1a SK |
404 | /* check_magic -- check for file magic numbers. */ |
405 | static int check_magic(struct more_control *ctl, char *fs) | |
82cfa6f2 | 406 | { |
09070e1a | 407 | #ifdef HAVE_MAGIC |
2eb52772 SK |
408 | const int fd = fileno(ctl->current_file); |
409 | const char *mime_encoding = magic_descriptor(ctl->magic, fd); | |
410 | const char *magic_error_msg = magic_error(ctl->magic); | |
2eb52772 SK |
411 | |
412 | if (magic_error_msg) { | |
32a4efc5 SK |
413 | printf("%s: %s: %s\n", program_invocation_short_name, |
414 | _("magic failed"), magic_error_msg); | |
2eb52772 SK |
415 | return 0; |
416 | } | |
09070e1a SK |
417 | if (!mime_encoding || !(strcmp("binary", mime_encoding))) { |
418 | printf(_("\n******** %s: Not a text file ********\n\n"), fs); | |
419 | return 1; | |
420 | } | |
421 | #else | |
82cfa6f2 SK |
422 | signed char twobytes[2]; |
423 | ||
424 | /* don't try to look ahead if the input is unseekable */ | |
09070e1a | 425 | if (fseek(ctl->current_file, 0L, SEEK_SET)) |
82cfa6f2 SK |
426 | return 0; |
427 | ||
09070e1a | 428 | if (fread(twobytes, 2, 1, ctl->current_file) == 1) { |
82cfa6f2 SK |
429 | switch (twobytes[0] + (twobytes[1] << 8)) { |
430 | case 0407: /* a.out obj */ | |
431 | case 0410: /* a.out exec */ | |
432 | case 0413: /* a.out demand exec */ | |
433 | case 0405: | |
434 | case 0411: | |
435 | case 0177545: | |
436 | case 0x457f: /* simple ELF detection */ | |
437 | printf(_("\n******** %s: Not a text file ********\n\n"), | |
438 | fs); | |
439 | return 1; | |
440 | } | |
441 | } | |
09070e1a SK |
442 | fseek(ctl->current_file, 0L, SEEK_SET); /* rewind() not necessary */ |
443 | #endif | |
82cfa6f2 SK |
444 | return 0; |
445 | } | |
446 | ||
4eaab145 SK |
447 | /* Check whether the file named by fs is an ASCII file which the user may |
448 | * access. If it is, return the opened file. Otherwise return NULL. */ | |
63f96f68 | 449 | static void checkf(struct more_control *ctl, char *fs) |
6dbe3af9 | 450 | { |
d910f311 | 451 | struct stat st; |
e8f26419 | 452 | int c; |
6dbe3af9 | 453 | |
f3c044ee SK |
454 | ctl->current_line = 0; |
455 | ctl->file_position = 0; | |
8fd1e766 | 456 | ctl->file_size = 0; |
f3c044ee | 457 | fflush(NULL); |
32a4efc5 SK |
458 | |
459 | ctl->current_file = fopen(fs, "r"); | |
460 | if (ctl->current_file == NULL) { | |
d910f311 SK |
461 | if (ctl->clear_line_ends) |
462 | putp(ctl->erase_line); | |
09070e1a | 463 | warn(_("cannot open %s"), fs); |
82129a6f | 464 | return; |
6dbe3af9 | 465 | } |
32a4efc5 | 466 | if (fstat(fileno(ctl->current_file), &st) != 0) { |
1293b0f6 | 467 | warn(_("stat of %s failed"), fs); |
32a4efc5 SK |
468 | return; |
469 | } | |
d910f311 | 470 | if ((st.st_mode & S_IFMT) == S_IFDIR) { |
d162fcb5 | 471 | printf(_("\n*** %s: directory ***\n\n"), fs); |
82129a6f SK |
472 | ctl->current_file = NULL; |
473 | return; | |
6dbe3af9 | 474 | } |
8fd1e766 TS |
475 | ctl->file_size = st.st_size; |
476 | if (0 < ctl->file_size && check_magic(ctl, fs)) { | |
82129a6f SK |
477 | fclose(ctl->current_file); |
478 | ctl->current_file = NULL; | |
479 | return; | |
b9579f1f | 480 | } |
82129a6f SK |
481 | fcntl(fileno(ctl->current_file), F_SETFD, FD_CLOEXEC); |
482 | c = more_getc(ctl); | |
63f96f68 | 483 | ctl->clear_first = (c == '\f'); |
82129a6f | 484 | more_ungetc(ctl, c); |
6dbe3af9 KZ |
485 | } |
486 | ||
7cc31c18 | 487 | static void prepare_line_buffer(struct more_control *ctl) |
6dbe3af9 | 488 | { |
d910f311 | 489 | size_t sz = ctl->num_columns * 4; |
6dbe3af9 | 490 | |
d910f311 | 491 | if (ctl->line_sz >= sz) |
f16ca88d | 492 | return; |
1ac30093 | 493 | |
d910f311 SK |
494 | if (sz < MIN_LINE_SZ) |
495 | sz = MIN_LINE_SZ; | |
1ac30093 | 496 | |
d910f311 SK |
497 | /* alloc sz and extra space for \n\0 */ |
498 | ctl->line_buf = xrealloc(ctl->line_buf, sz + 2); | |
499 | ctl->line_sz = sz; | |
1ac30093 KZ |
500 | } |
501 | ||
4eaab145 | 502 | /* Get a logical line */ |
82129a6f | 503 | static int get_line(struct more_control *ctl, int *length) |
6dbe3af9 | 504 | { |
4eaab145 SK |
505 | int c; |
506 | char *p; | |
507 | int column; | |
d910f311 | 508 | static int column_wrap; |
1d4ad1de | 509 | |
06b04b23 | 510 | #ifdef HAVE_WIDECHAR |
4eaab145 SK |
511 | size_t i; |
512 | wchar_t wc; | |
513 | int wc_width; | |
514 | mbstate_t state, state_bak; /* Current status of the stream. */ | |
515 | char mbc[MB_LEN_MAX]; /* Buffer for one multibyte char. */ | |
516 | size_t mblength; /* Byte length of multibyte char. */ | |
517 | size_t mbc_pos = 0; /* Position of the MBC. */ | |
518 | int use_mbc_buffer_flag = 0; /* If 1, mbc has data. */ | |
519 | int break_flag = 0; /* If 1, exit while(). */ | |
ba105bb5 | 520 | off_t file_position_bak = ctl->file_position; |
4eaab145 SK |
521 | |
522 | memset(&state, '\0', sizeof(mbstate_t)); | |
523 | #endif | |
524 | ||
d910f311 | 525 | p = ctl->line_buf; |
4eaab145 | 526 | column = 0; |
82129a6f | 527 | c = more_getc(ctl); |
d910f311 SK |
528 | if (column_wrap && c == '\n') { |
529 | ctl->current_line++; | |
82129a6f | 530 | c = more_getc(ctl); |
4eaab145 | 531 | } |
d910f311 | 532 | while (p < &ctl->line_buf[ctl->line_sz - 1]) { |
06b04b23 | 533 | #ifdef HAVE_WIDECHAR |
d910f311 | 534 | if (ctl->fold_long_lines && use_mbc_buffer_flag && MB_CUR_MAX > 1) { |
4eaab145 SK |
535 | use_mbc_buffer_flag = 0; |
536 | state_bak = state; | |
537 | mbc[mbc_pos++] = c; | |
538 | process_mbc: | |
539 | mblength = mbrtowc(&wc, mbc, mbc_pos, &state); | |
540 | ||
541 | switch (mblength) { | |
542 | case (size_t)-2: /* Incomplete multibyte character. */ | |
543 | use_mbc_buffer_flag = 1; | |
544 | state = state_bak; | |
545 | break; | |
546 | ||
547 | case (size_t)-1: /* Invalid as a multibyte character. */ | |
548 | *p++ = mbc[0]; | |
549 | state = state_bak; | |
550 | column++; | |
d910f311 SK |
551 | file_position_bak++; |
552 | if (column >= ctl->num_columns) { | |
82129a6f | 553 | more_fseek(ctl, file_position_bak); |
4eaab145 SK |
554 | } else { |
555 | memmove(mbc, mbc + 1, --mbc_pos); | |
556 | if (mbc_pos > 0) { | |
557 | mbc[mbc_pos] = '\0'; | |
558 | goto process_mbc; | |
559 | } | |
560 | } | |
561 | break; | |
562 | ||
563 | default: | |
564 | wc_width = wcwidth(wc); | |
d910f311 | 565 | if (column + wc_width > ctl->num_columns) { |
82129a6f | 566 | more_fseek(ctl, file_position_bak); |
4eaab145 SK |
567 | break_flag = 1; |
568 | } else { | |
d910f311 | 569 | for (i = 0; p < &ctl->line_buf[ctl->line_sz - 1] && |
c3640729 | 570 | i < mbc_pos; i++) |
4eaab145 SK |
571 | *p++ = mbc[i]; |
572 | if (wc_width > 0) | |
573 | column += wc_width; | |
574 | } | |
575 | } | |
1d4ad1de | 576 | |
d910f311 | 577 | if (break_flag || column >= ctl->num_columns) |
4eaab145 | 578 | break; |
1d4ad1de | 579 | |
82129a6f | 580 | c = more_getc(ctl); |
4eaab145 SK |
581 | continue; |
582 | } | |
583 | #endif /* HAVE_WIDECHAR */ | |
584 | if (c == EOF) { | |
d910f311 | 585 | if (p > ctl->line_buf) { |
4eaab145 | 586 | *p = '\0'; |
d910f311 | 587 | *length = p - ctl->line_buf; |
f728d8ba | 588 | return column; |
4eaab145 | 589 | } |
d910f311 | 590 | *length = p - ctl->line_buf; |
f728d8ba | 591 | return EOF; |
4eaab145 SK |
592 | } |
593 | if (c == '\n') { | |
d910f311 | 594 | ctl->current_line++; |
4eaab145 | 595 | break; |
1d4ad1de | 596 | } |
1d4ad1de | 597 | |
4eaab145 | 598 | *p++ = c; |
4eaab145 | 599 | if (c == '\t') { |
d910f311 SK |
600 | if (!ctl->hard_tabs || (column < ctl->prompt_len && !ctl->hard_tty)) { |
601 | if (ctl->hard_tabs && ctl->erase_line && !ctl->dumb_tty) { | |
4eaab145 | 602 | column = 1 + (column | 7); |
d910f311 SK |
603 | putp(ctl->erase_line); |
604 | ctl->prompt_len = 0; | |
4eaab145 | 605 | } else { |
d910f311 | 606 | for (--p; p < &ctl->line_buf[ctl->line_sz - 1];) { |
4eaab145 SK |
607 | *p++ = ' '; |
608 | if ((++column & 7) == 0) | |
609 | break; | |
610 | } | |
d910f311 SK |
611 | if (column >= ctl->prompt_len) |
612 | ctl->prompt_len = 0; | |
4eaab145 SK |
613 | } |
614 | } else | |
615 | column = 1 + (column | 7); | |
616 | } else if (c == '\b' && column > 0) { | |
617 | column--; | |
618 | } else if (c == '\r') { | |
82129a6f | 619 | int next = more_getc(ctl); |
4eaab145 SK |
620 | if (next == '\n') { |
621 | p--; | |
d910f311 | 622 | ctl->current_line++; |
4eaab145 SK |
623 | break; |
624 | } | |
82129a6f | 625 | more_ungetc(ctl, c); |
4eaab145 | 626 | column = 0; |
d910f311 | 627 | } else if (c == '\f' && ctl->stop_after_formfeed) { |
4eaab145 SK |
628 | p[-1] = '^'; |
629 | *p++ = 'L'; | |
630 | column += 2; | |
d910f311 | 631 | ctl->is_paused = 1; |
4eaab145 SK |
632 | } else { |
633 | #ifdef HAVE_WIDECHAR | |
d910f311 | 634 | if (ctl->fold_long_lines && MB_CUR_MAX > 1) { |
4eaab145 SK |
635 | memset(mbc, '\0', MB_LEN_MAX); |
636 | mbc_pos = 0; | |
637 | mbc[mbc_pos++] = c; | |
638 | state_bak = state; | |
639 | ||
640 | mblength = mbrtowc(&wc, mbc, mbc_pos, &state); | |
641 | /* The value of mblength is always less than 2 here. */ | |
642 | switch (mblength) { | |
643 | case (size_t)-2: | |
644 | p--; | |
d910f311 | 645 | file_position_bak = ctl->file_position - 1; |
4eaab145 SK |
646 | state = state_bak; |
647 | use_mbc_buffer_flag = 1; | |
648 | break; | |
649 | ||
650 | case (size_t)-1: | |
651 | state = state_bak; | |
652 | column++; | |
653 | break; | |
654 | ||
655 | default: | |
656 | wc_width = wcwidth(wc); | |
657 | if (wc_width > 0) | |
658 | column += wc_width; | |
659 | } | |
660 | } else | |
661 | #endif /* HAVE_WIDECHAR */ | |
662 | { | |
663 | if (isprint(c)) | |
664 | column++; | |
665 | } | |
66ee8158 | 666 | } |
4eaab145 | 667 | |
d910f311 | 668 | if (column >= ctl->num_columns && ctl->fold_long_lines) |
4eaab145 | 669 | break; |
418cb4b3 | 670 | #ifdef HAVE_WIDECHAR |
d910f311 | 671 | if (use_mbc_buffer_flag == 0 && p >= &ctl->line_buf[ctl->line_sz - 1 - 4]) |
418cb4b3 KZ |
672 | /* don't read another char if there is no space for |
673 | * whole multibyte sequence */ | |
674 | break; | |
675 | #endif | |
82129a6f | 676 | c = more_getc(ctl); |
66ee8158 | 677 | } |
d910f311 SK |
678 | if (column >= ctl->num_columns && ctl->num_columns > 0) { |
679 | if (!ctl->wrap_margin) { | |
4eaab145 | 680 | *p++ = '\n'; |
6dbe3af9 | 681 | } |
1d4ad1de | 682 | } |
d910f311 SK |
683 | column_wrap = column == ctl->num_columns && ctl->fold_long_lines; |
684 | if (column_wrap && ctl->eat_newline && ctl->wrap_margin) { | |
4eaab145 | 685 | *p++ = '\n'; /* simulate normal wrap */ |
6dbe3af9 | 686 | } |
d910f311 | 687 | *length = p - ctl->line_buf; |
4eaab145 | 688 | *p = 0; |
f728d8ba | 689 | return column; |
6dbe3af9 KZ |
690 | } |
691 | ||
4eaab145 | 692 | /* Erase the rest of the prompt, assuming we are starting at column col. */ |
21d09623 | 693 | static void erase_to_col(struct more_control *ctl, int col) |
6dbe3af9 KZ |
694 | { |
695 | ||
d910f311 | 696 | if (ctl->prompt_len == 0) |
4eaab145 | 697 | return; |
21d09623 SK |
698 | if (col == 0 && ctl->clear_line_ends) |
699 | puts(ctl->erase_line); | |
700 | else if (ctl->hard_tty) | |
4eaab145 | 701 | putchar('\n'); |
21d09623 | 702 | else { |
4eaab145 SK |
703 | if (col == 0) |
704 | putchar('\r'); | |
d910f311 SK |
705 | if (!ctl->dumb_tty && ctl->erase_line) |
706 | putp(ctl->erase_line); | |
21d09623 | 707 | else { |
d910f311 | 708 | printf("%*s", ctl->prompt_len - col, ""); |
21d09623 SK |
709 | if (col == 0) |
710 | putchar('\r'); | |
711 | } | |
4eaab145 | 712 | } |
21d09623 | 713 | ctl->prompt_len = col; |
6dbe3af9 KZ |
714 | } |
715 | ||
275d47c9 | 716 | static void output_prompt(struct more_control *ctl, char *filename) |
f16ca88d | 717 | { |
d910f311 SK |
718 | if (ctl->clear_line_ends) |
719 | putp(ctl->erase_line); | |
720 | else if (ctl->prompt_len > 0) | |
21d09623 | 721 | erase_to_col(ctl, 0); |
d910f311 SK |
722 | if (!ctl->hard_tty) { |
723 | ctl->prompt_len = 0; | |
5cd45502 | 724 | if (ctl->enter_std) { |
d910f311 SK |
725 | putp(ctl->enter_std); |
726 | ctl->prompt_len += (2 * ctl->stdout_glitch); | |
f16ca88d | 727 | } |
d910f311 SK |
728 | if (ctl->clear_line_ends) |
729 | putp(ctl->erase_line); | |
730 | ctl->prompt_len += printf(_("--More--")); | |
f16ca88d | 731 | if (filename != NULL) { |
d910f311 | 732 | ctl->prompt_len += printf(_("(Next file: %s)"), filename); |
ec4153cc | 733 | } else if (!ctl->no_tty_in && 0 < ctl->file_size) { |
df6b29d3 IJ |
734 | int position = ((ctl->file_position * 100) / ctl->file_size); |
735 | if (position == 100) { | |
736 | erase_to_col(ctl, 0); | |
737 | ctl->prompt_len += printf(_("(END)")); | |
738 | } else { | |
739 | ctl->prompt_len += printf("(%d%%)", position); | |
740 | } | |
741 | } else if (ctl->is_eof) { | |
742 | erase_to_col(ctl, 0); | |
743 | ctl->prompt_len += printf(_("(END)")); | |
f16ca88d | 744 | } |
df6b29d3 | 745 | |
d910f311 SK |
746 | if (ctl->suppress_bell) { |
747 | ctl->prompt_len += | |
f16ca88d SK |
748 | printf(_("[Press space to continue, 'q' to quit.]")); |
749 | } | |
5cd45502 | 750 | if (ctl->exit_std) |
d910f311 SK |
751 | putp(ctl->exit_std); |
752 | if (ctl->clear_line_ends) | |
753 | putp(ctl->clear_rest); | |
f16ca88d | 754 | } else |
508b2d79 | 755 | fprintf(stderr, "\a"); |
f3c044ee | 756 | fflush(NULL); |
6dbe3af9 KZ |
757 | } |
758 | ||
823ef319 | 759 | static void reset_tty(struct more_control *ctl) |
82cfa6f2 | 760 | { |
823ef319 | 761 | if (ctl->no_tty_out) |
82cfa6f2 | 762 | return; |
f3c044ee | 763 | fflush(NULL); |
823ef319 SK |
764 | ctl->output_tty.c_lflag |= ICANON | ECHO; |
765 | ctl->output_tty.c_cc[VMIN] = ctl->original_tty.c_cc[VMIN]; | |
766 | ctl->output_tty.c_cc[VTIME] = ctl->original_tty.c_cc[VTIME]; | |
767 | tcsetattr(STDERR_FILENO, TCSANOW, &ctl->original_tty); | |
82cfa6f2 SK |
768 | } |
769 | ||
770 | /* Clean up terminal state and exit. Also come here if interrupt signal received */ | |
823ef319 | 771 | static void __attribute__((__noreturn__)) more_exit(struct more_control *ctl) |
82cfa6f2 | 772 | { |
09070e1a SK |
773 | #ifdef HAVE_MAGIC |
774 | magic_close(ctl->magic); | |
775 | #endif | |
823ef319 SK |
776 | reset_tty(ctl); |
777 | if (ctl->clear_line_ends) { | |
82cfa6f2 | 778 | putchar('\r'); |
823ef319 | 779 | putp(ctl->erase_line); |
f3c044ee | 780 | } else if (!ctl->clear_line_ends && (ctl->prompt_len > 0)) |
21d09623 | 781 | erase_to_col(ctl, 0); |
f3c044ee | 782 | fflush(NULL); |
823ef319 | 783 | free(ctl->previous_search); |
474c61c0 | 784 | free(ctl->shell_line); |
823ef319 | 785 | free(ctl->line_buf); |
22ff9a38 | 786 | free(ctl->go_home); |
73ff5a95 SK |
787 | if (ctl->current_file) |
788 | fclose(ctl->current_file); | |
789 | del_curterm(cur_term); | |
82cfa6f2 SK |
790 | _exit(EXIT_SUCCESS); |
791 | } | |
792 | ||
ba105bb5 | 793 | static cc_t read_user_input(struct more_control *ctl) |
6dbe3af9 | 794 | { |
ba105bb5 | 795 | cc_t c; |
f16ca88d SK |
796 | |
797 | errno = 0; | |
1f861935 SK |
798 | /* |
799 | * Key commands can be read() from either stderr or stdin. If they | |
800 | * are read from stdin such as 'cat file.txt | more' then the pipe | |
801 | * input is understood as series key commands - and that is not | |
802 | * wanted. Keep the read() reading from stderr. | |
803 | */ | |
d2b54002 | 804 | if (read(STDERR_FILENO, &c, 1) <= 0) { |
f16ca88d | 805 | if (errno != EINTR) |
823ef319 | 806 | more_exit(ctl); |
f16ca88d | 807 | else |
d910f311 | 808 | c = ctl->output_tty.c_cc[VKILL]; |
f16ca88d | 809 | } |
f728d8ba | 810 | return c; |
6dbe3af9 KZ |
811 | } |
812 | ||
1f861935 | 813 | /* Read a number and command from the terminal. Set cmd to the non-digit |
f16ca88d | 814 | * which terminates the number. */ |
1f861935 | 815 | static struct number_command read_command(struct more_control *ctl) |
6dbe3af9 | 816 | { |
1f861935 SK |
817 | cc_t input[8] = { 0 }; |
818 | ssize_t i, ilen; | |
819 | struct number_command cmd = { .key = more_kc_unknown_command }; | |
820 | ||
821 | /* See stderr note in read_user_input() */ | |
822 | if ((ilen = read(STDERR_FILENO, &input, sizeof(input))) <= 0) | |
823 | return cmd; | |
824 | if (2 < ilen) { | |
825 | if (!memcmp(input, ARROW_UP, sizeof(ARROW_UP))) { | |
826 | cmd.key = more_kc_backwards; | |
827 | return cmd; | |
828 | } else if (!memcmp(input, ARROW_DOWN, sizeof(ARROW_DOWN))) { | |
e899168a | 829 | cmd.key = more_kc_jump_lines_per_screen; |
1f861935 SK |
830 | return cmd; |
831 | } else if (!memcmp(input, PAGE_UP, sizeof(PAGE_UP))) { | |
832 | cmd.key = more_kc_backwards; | |
833 | return cmd; | |
834 | } else if (!memcmp(input, PAGE_DOWN, sizeof(PAGE_DOWN))) { | |
e899168a | 835 | cmd.key = more_kc_jump_lines_per_screen; |
1f861935 SK |
836 | return cmd; |
837 | } | |
838 | } | |
839 | for (i = 0; i < ilen; i++) { | |
840 | if (isdigit(input[i])) { | |
841 | if (0 < ctl->reading_num) { | |
842 | ctl->leading_number *= 10; | |
843 | ctl->leading_number += input[i] - '0'; | |
844 | } else | |
845 | ctl->leading_number = input[i] - '0'; | |
846 | ctl->reading_num = 1; | |
847 | continue; | |
848 | } | |
849 | cmd.number = ctl->leading_number; | |
850 | ctl->reading_num = 0; | |
851 | ctl->leading_number = 0; | |
852 | if (ctl->leading_colon) { | |
853 | ctl->leading_colon = 0; | |
854 | switch (input[i]) { | |
855 | case 'f': | |
856 | cmd.key = more_kc_display_file_and_line; | |
857 | return cmd; | |
858 | case 'n': | |
859 | cmd.key = more_kc_next_file; | |
860 | return cmd; | |
861 | case 'p': | |
862 | cmd.key = more_kc_previous_file; | |
863 | return cmd; | |
864 | default: | |
865 | cmd.key = more_kc_unknown_command; | |
866 | return cmd; | |
867 | } | |
868 | } | |
869 | /* command is a single char */ | |
870 | switch (input[i]) { | |
871 | case '.': | |
872 | cmd.key = more_kc_repeat_previous; | |
873 | break; | |
874 | case ':': | |
875 | ctl->leading_colon = 1; | |
876 | break; | |
877 | case 'b': | |
878 | case CTRL('B'): | |
879 | cmd.key = more_kc_backwards; | |
880 | break; | |
881 | case ' ': | |
882 | cmd.key = more_kc_jump_lines_per_screen; | |
883 | break; | |
884 | case 'z': | |
885 | cmd.key = more_kc_set_lines_per_screen; | |
886 | break; | |
887 | case 'd': | |
888 | case CTRL('D'): | |
889 | cmd.key = more_kc_set_scroll_len; | |
890 | break; | |
891 | case 'q': | |
892 | case 'Q': | |
893 | cmd.key = more_kc_quit; | |
bf3e1c78 | 894 | return cmd; |
1f861935 SK |
895 | break; |
896 | case 'f': | |
1f861935 | 897 | case CTRL('F'): |
9f303a2b HM |
898 | cmd.key = more_kc_skip_forward_screen; |
899 | break; | |
900 | case 's': | |
901 | cmd.key = more_kc_skip_forward_line; | |
1f861935 SK |
902 | break; |
903 | case '\n': | |
904 | cmd.key = more_kc_next_line; | |
905 | break; | |
906 | case '\f': | |
907 | cmd.key = more_kc_clear_screen; | |
908 | break; | |
909 | case '\'': | |
910 | cmd.key = more_kc_previous_search_match; | |
911 | break; | |
912 | case '=': | |
913 | cmd.key = more_kc_display_line; | |
914 | break; | |
915 | case 'n': | |
916 | cmd.key = more_kc_repeat_search; | |
917 | break; | |
918 | case '/': | |
919 | cmd.key = more_kc_search; | |
920 | break; | |
921 | case '!': | |
922 | cmd.key = more_kc_run_shell; | |
923 | break; | |
924 | case '?': | |
925 | case 'h': | |
926 | cmd.key = more_kc_help; | |
927 | break; | |
928 | case 'v': | |
929 | cmd.key = more_kc_run_editor; | |
f16ca88d | 930 | break; |
6dbe3af9 | 931 | } |
f16ca88d | 932 | } |
1f861935 | 933 | return cmd; |
f16ca88d | 934 | } |
4eaab145 | 935 | |
275d47c9 SK |
936 | /* Change displayed file from command line list to next nskip, where nskip |
937 | * is relative position in argv and can be negative, that is a previous | |
938 | * file. */ | |
939 | static void change_file(struct more_control *ctl, int nskip) | |
f16ca88d SK |
940 | { |
941 | if (nskip == 0) | |
942 | return; | |
943 | if (nskip > 0) { | |
d910f311 SK |
944 | if (ctl->argv_position + nskip > ctl->num_files - 1) |
945 | nskip = ctl->num_files - ctl->argv_position - 1; | |
823ef319 | 946 | } |
d910f311 SK |
947 | ctl->argv_position += nskip; |
948 | if (ctl->argv_position < 0) | |
949 | ctl->argv_position = 0; | |
f16ca88d | 950 | puts(_("\n...Skipping ")); |
d910f311 SK |
951 | if (ctl->clear_line_ends) |
952 | putp(ctl->erase_line); | |
f16ca88d | 953 | if (nskip > 0) |
bd6a253f | 954 | fputs(_("...Skipping to file "), stdout); |
f16ca88d | 955 | else |
bd6a253f | 956 | fputs(_("...Skipping back to file "), stdout); |
d910f311 SK |
957 | puts(ctl->file_names[ctl->argv_position]); |
958 | if (ctl->clear_line_ends) | |
959 | putp(ctl->erase_line); | |
f16ca88d | 960 | putchar('\n'); |
d910f311 | 961 | ctl->argv_position--; |
f16ca88d | 962 | } |
4eaab145 | 963 | |
7cc31c18 | 964 | static void show(struct more_control *ctl, char c) |
f16ca88d | 965 | { |
508b2d79 SK |
966 | if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) { |
967 | c += (c == CERASE) ? -0100 : 0100; | |
bd6a253f | 968 | fputs(CARAT, stderr); |
d910f311 | 969 | ctl->prompt_len++; |
f16ca88d | 970 | } |
bd6a253f | 971 | fputc(c, stderr); |
d910f311 | 972 | ctl->prompt_len++; |
f16ca88d | 973 | } |
4eaab145 | 974 | |
7cc31c18 | 975 | static void more_error(struct more_control *ctl, char *mess) |
f16ca88d | 976 | { |
d910f311 SK |
977 | if (ctl->clear_line_ends) |
978 | putp(ctl->erase_line); | |
f16ca88d | 979 | else |
21d09623 | 980 | erase_to_col(ctl, 0); |
d910f311 | 981 | ctl->prompt_len += strlen(mess); |
5cd45502 | 982 | if (ctl->enter_std) |
d910f311 | 983 | putp(ctl->enter_std); |
5cd45502 SK |
984 | fputs(mess, stdout); |
985 | if (ctl->exit_std) | |
d910f311 | 986 | putp(ctl->exit_std); |
f3c044ee | 987 | fflush(NULL); |
d910f311 | 988 | ctl->report_errors++; |
f16ca88d | 989 | } |
6dbe3af9 | 990 | |
bd6a253f SK |
991 | static void erase_one_column(struct more_control *ctl) |
992 | { | |
d910f311 | 993 | if (ctl->erase_previous_ok) |
5cd45502 SK |
994 | fprintf(stderr, "%s ", ctl->backspace_ch); |
995 | fputs(ctl->backspace_ch, stderr); | |
bd6a253f SK |
996 | } |
997 | ||
0b735ea4 | 998 | static void ttyin(struct more_control *ctl, char buf[], int nmax, char pchar) |
f16ca88d SK |
999 | { |
1000 | char *sp; | |
ba105bb5 | 1001 | cc_t c; |
f16ca88d SK |
1002 | int slash = 0; |
1003 | int maxlen; | |
6dbe3af9 | 1004 | |
f16ca88d SK |
1005 | sp = buf; |
1006 | maxlen = 0; | |
1007 | while (sp - buf < nmax) { | |
d910f311 SK |
1008 | if (ctl->prompt_len > maxlen) |
1009 | maxlen = ctl->prompt_len; | |
275d47c9 | 1010 | c = read_user_input(ctl); |
f16ca88d SK |
1011 | if (c == '\\') { |
1012 | slash++; | |
ba105bb5 | 1013 | } else if (c == ctl->output_tty.c_cc[VERASE] && !slash) { |
f16ca88d SK |
1014 | if (sp > buf) { |
1015 | #ifdef HAVE_WIDECHAR | |
1016 | if (MB_CUR_MAX > 1) { | |
1017 | wchar_t wc; | |
1018 | size_t pos = 0, mblength; | |
1019 | mbstate_t state, state_bak; | |
1020 | ||
1021 | memset(&state, '\0', sizeof(mbstate_t)); | |
1022 | ||
1023 | while (1) { | |
1024 | state_bak = state; | |
1025 | mblength = | |
1026 | mbrtowc(&wc, buf + pos, | |
1027 | sp - buf, &state); | |
1028 | ||
5cd45502 SK |
1029 | switch (mblength) { |
1030 | case (size_t)-2: | |
1031 | case (size_t)-1: | |
1032 | state = state_bak; | |
1033 | /* fallthrough */ | |
1034 | case 0: | |
1035 | mblength = 1; | |
1036 | } | |
f16ca88d SK |
1037 | if (buf + pos + mblength >= sp) |
1038 | break; | |
1039 | ||
1040 | pos += mblength; | |
4eaab145 | 1041 | } |
f16ca88d SK |
1042 | |
1043 | if (mblength == 1) { | |
bd6a253f | 1044 | erase_one_column(ctl); |
f16ca88d SK |
1045 | } else { |
1046 | int wc_width; | |
1047 | wc_width = wcwidth(wc); | |
1048 | wc_width = | |
1049 | (wc_width < | |
1050 | 1) ? 1 : wc_width; | |
1051 | while (wc_width--) { | |
bd6a253f | 1052 | erase_one_column(ctl); |
f16ca88d SK |
1053 | } |
1054 | } | |
1055 | ||
1056 | while (mblength--) { | |
d910f311 | 1057 | --ctl->prompt_len; |
f16ca88d SK |
1058 | --sp; |
1059 | } | |
1060 | } else | |
1061 | #endif /* HAVE_WIDECHAR */ | |
1062 | { | |
d910f311 | 1063 | --ctl->prompt_len; |
bd6a253f | 1064 | erase_one_column(ctl); |
f16ca88d SK |
1065 | --sp; |
1066 | } | |
1067 | ||
508b2d79 | 1068 | if ((*sp < ' ' && *sp != '\n') || *sp == CERASE) { |
d910f311 | 1069 | --ctl->prompt_len; |
bd6a253f | 1070 | erase_one_column(ctl); |
f16ca88d SK |
1071 | } |
1072 | continue; | |
4eaab145 | 1073 | } |
042f62df RP |
1074 | if (!ctl->erase_line) |
1075 | ctl->prompt_len = maxlen; | |
ba105bb5 | 1076 | } else if (c == ctl->output_tty.c_cc[VKILL] && !slash) { |
d910f311 | 1077 | if (ctl->hard_tty) { |
7cc31c18 | 1078 | show(ctl, c); |
f16ca88d SK |
1079 | putchar('\n'); |
1080 | putchar(pchar); | |
4eaab145 | 1081 | } else { |
f16ca88d SK |
1082 | putchar('\r'); |
1083 | putchar(pchar); | |
d910f311 | 1084 | if (ctl->erase_line) |
21d09623 | 1085 | erase_to_col(ctl, 1); |
d910f311 SK |
1086 | else if (ctl->erase_input_ok) |
1087 | while (ctl->prompt_len-- > 1) | |
5cd45502 | 1088 | fprintf(stderr, "%s %s", ctl->backspace_ch, ctl->backspace_ch); |
d910f311 | 1089 | ctl->prompt_len = 1; |
4eaab145 | 1090 | } |
f16ca88d | 1091 | sp = buf; |
f3c044ee | 1092 | fflush(NULL); |
f16ca88d SK |
1093 | continue; |
1094 | } | |
ba105bb5 SK |
1095 | if (slash && (c == ctl->output_tty.c_cc[VKILL] || |
1096 | c == ctl->output_tty.c_cc[VERASE])) { | |
bd6a253f | 1097 | erase_one_column(ctl); |
f16ca88d SK |
1098 | --sp; |
1099 | } | |
1100 | if (c != '\\') | |
1101 | slash = 0; | |
1102 | *sp++ = c; | |
508b2d79 SK |
1103 | if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) { |
1104 | c += (c == CERASE) ? -0100 : 0100; | |
bd6a253f | 1105 | fputs(CARAT, stderr); |
d910f311 | 1106 | ctl->prompt_len++; |
f16ca88d SK |
1107 | } |
1108 | if (c != '\n' && c != ESC) { | |
bd6a253f | 1109 | fputc(c, stderr); |
d910f311 | 1110 | ctl->prompt_len++; |
f16ca88d | 1111 | } else |
4eaab145 | 1112 | break; |
f16ca88d SK |
1113 | } |
1114 | *--sp = '\0'; | |
d910f311 SK |
1115 | if (!ctl->erase_line) |
1116 | ctl->prompt_len = maxlen; | |
f16ca88d | 1117 | if (sp - buf >= nmax - 1) |
7cc31c18 | 1118 | more_error(ctl, _("Line too long")); |
f16ca88d | 1119 | } |
4eaab145 | 1120 | |
474c61c0 SK |
1121 | /* Expand shell command line. */ |
1122 | static void expand(struct more_control *ctl, char *inbuf) | |
f16ca88d SK |
1123 | { |
1124 | char *inpstr; | |
1125 | char *outstr; | |
1126 | char c; | |
1127 | char *temp; | |
86f05d39 KZ |
1128 | int tempsz, xtra = 0, offset; |
1129 | ||
1130 | if (!ctl->no_tty_in) | |
1131 | xtra += strlen(ctl->file_names[ctl->argv_position]) + 1; | |
1132 | if (ctl->shell_line) | |
1133 | xtra += strlen(ctl->shell_line) + 1; | |
4eaab145 | 1134 | |
474c61c0 | 1135 | tempsz = COMMAND_BUF + xtra; |
f16ca88d SK |
1136 | temp = xmalloc(tempsz); |
1137 | inpstr = inbuf; | |
1138 | outstr = temp; | |
86f05d39 | 1139 | |
f16ca88d SK |
1140 | while ((c = *inpstr++) != '\0') { |
1141 | offset = outstr - temp; | |
1142 | if (tempsz - offset - 1 < xtra) { | |
474c61c0 | 1143 | tempsz += COMMAND_BUF + xtra; |
f16ca88d SK |
1144 | temp = xrealloc(temp, tempsz); |
1145 | outstr = temp + offset; | |
1146 | } | |
1147 | switch (c) { | |
1148 | case '%': | |
d910f311 SK |
1149 | if (!ctl->no_tty_in) { |
1150 | strcpy(outstr, ctl->file_names[ctl->argv_position]); | |
1151 | outstr += strlen(ctl->file_names[ctl->argv_position]); | |
f16ca88d SK |
1152 | } else |
1153 | *outstr++ = c; | |
1154 | break; | |
1155 | case '!': | |
474c61c0 SK |
1156 | if (ctl->shell_line) { |
1157 | strcpy(outstr, ctl->shell_line); | |
1158 | outstr += strlen(ctl->shell_line); | |
1159 | } else | |
7cc31c18 | 1160 | more_error(ctl, _ |
f16ca88d | 1161 | ("No previous command to substitute for")); |
f16ca88d SK |
1162 | break; |
1163 | case '\\': | |
1164 | if (*inpstr == '%' || *inpstr == '!') { | |
1165 | *outstr++ = *inpstr++; | |
4eaab145 SK |
1166 | break; |
1167 | } | |
b1557fe9 | 1168 | /* fallthrough */ |
4eaab145 | 1169 | default: |
f16ca88d | 1170 | *outstr++ = c; |
6dbe3af9 | 1171 | } |
6dbe3af9 | 1172 | } |
f16ca88d | 1173 | *outstr++ = '\0'; |
474c61c0 SK |
1174 | free(ctl->shell_line); |
1175 | ctl->shell_line = temp; | |
6dbe3af9 KZ |
1176 | } |
1177 | ||
7cc31c18 | 1178 | static void set_tty(struct more_control *ctl) |
4eaab145 | 1179 | { |
d910f311 SK |
1180 | ctl->output_tty.c_lflag &= ~(ICANON | ECHO); |
1181 | ctl->output_tty.c_cc[VMIN] = 1; /* read at least 1 char */ | |
1182 | ctl->output_tty.c_cc[VTIME] = 0; /* no timeout */ | |
1183 | tcsetattr(STDERR_FILENO, TCSANOW, &ctl->output_tty); | |
f16ca88d SK |
1184 | } |
1185 | ||
82cfa6f2 | 1186 | /* Come here if a quit signal is received */ |
823ef319 | 1187 | static void sigquit_handler(struct more_control *ctl) |
f16ca88d | 1188 | { |
823ef319 SK |
1189 | if (!ctl->dumb_tty && ctl->no_quit_dialog) { |
1190 | ctl->prompt_len += fprintf(stderr, _("[Use q or Q to quit]")); | |
1191 | ctl->no_quit_dialog = 0; | |
1192 | } else | |
1193 | more_exit(ctl); | |
6dbe3af9 KZ |
1194 | } |
1195 | ||
82cfa6f2 | 1196 | /* Come here when we get a suspend signal from the terminal */ |
823ef319 | 1197 | static void sigtstp_handler(struct more_control *ctl) |
6dbe3af9 | 1198 | { |
823ef319 | 1199 | reset_tty(ctl); |
f3c044ee | 1200 | fflush(NULL); |
6dcaaa9e SK |
1201 | kill(getpid(), SIGSTOP); |
1202 | } | |
1203 | ||
1204 | /* Come here when we get a continue signal from the terminal */ | |
1205 | static void sigcont_handler(struct more_control *ctl) | |
1206 | { | |
823ef319 SK |
1207 | set_tty(ctl); |
1208 | } | |
82cfa6f2 | 1209 | |
823ef319 SK |
1210 | /* Come here if a signal for a window size change is received */ |
1211 | static void sigwinch_handler(struct more_control *ctl) | |
1212 | { | |
1213 | struct winsize win; | |
82cfa6f2 | 1214 | |
823ef319 SK |
1215 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1) { |
1216 | if (win.ws_row != 0) { | |
1217 | ctl->lines_per_page = win.ws_row; | |
1218 | ctl->d_scroll_len = ctl->lines_per_page / 2 - 1; | |
1219 | if (ctl->d_scroll_len < 1) | |
1220 | ctl->d_scroll_len = 1; | |
1221 | ctl->lines_per_screen = ctl->lines_per_page - 1; | |
1222 | } | |
1223 | if (win.ws_col != 0) | |
1224 | ctl->num_columns = win.ws_col; | |
1225 | } | |
5cd45502 | 1226 | prepare_line_buffer(ctl); |
f16ca88d | 1227 | } |
6dbe3af9 | 1228 | |
2f38ae59 KZ |
1229 | static void __attribute__((__format__ (__printf__, 3, 4))) |
1230 | execute(struct more_control *ctl, char *filename, const char *cmd, ...) | |
f16ca88d | 1231 | { |
c366ebc7 | 1232 | pid_t id; |
f16ca88d SK |
1233 | va_list argp; |
1234 | char *arg; | |
1235 | char **args; | |
1236 | int argcount; | |
1237 | ||
f3c044ee | 1238 | fflush(NULL); |
c366ebc7 | 1239 | id = fork(); |
f16ca88d SK |
1240 | if (id == 0) { |
1241 | int errsv; | |
c8b1fa60 SK |
1242 | if (!isatty(STDIN_FILENO)) { |
1243 | close(STDIN_FILENO); | |
c67497ce | 1244 | ignore_result( open("/dev/tty", 0) ); |
f16ca88d | 1245 | } |
437cec38 | 1246 | reset_tty(ctl); |
f16ca88d SK |
1247 | |
1248 | va_start(argp, cmd); | |
1249 | arg = va_arg(argp, char *); | |
1250 | argcount = 0; | |
1251 | while (arg) { | |
1252 | argcount++; | |
1253 | arg = va_arg(argp, char *); | |
1254 | } | |
1255 | va_end(argp); | |
1256 | ||
c988ccdc | 1257 | args = xcalloc(argcount + 1, sizeof(char *)); |
f16ca88d SK |
1258 | |
1259 | va_start(argp, cmd); | |
1260 | arg = va_arg(argp, char *); | |
1261 | argcount = 0; | |
1262 | while (arg) { | |
1263 | args[argcount] = arg; | |
1264 | argcount++; | |
1265 | arg = va_arg(argp, char *); | |
6dbe3af9 | 1266 | } |
f16ca88d SK |
1267 | va_end(argp); |
1268 | ||
17fc8693 KZ |
1269 | if ((geteuid() != getuid() || getegid() != getgid()) |
1270 | && drop_permissions() != 0) | |
1271 | err(EXIT_FAILURE, _("drop permissions failed")); | |
8fb5fe30 | 1272 | |
f16ca88d SK |
1273 | execvp(cmd, args); |
1274 | errsv = errno; | |
bd6a253f | 1275 | fputs(_("exec failed\n"), stderr); |
f16ca88d | 1276 | exit(errsv == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED); |
6dbe3af9 | 1277 | } |
f16ca88d | 1278 | if (id > 0) { |
c366ebc7 SK |
1279 | errno = 0; |
1280 | while (wait(NULL) > 0) { | |
1281 | if (errno == EINTR) | |
1282 | continue; | |
1283 | } | |
f16ca88d | 1284 | } else |
bd6a253f | 1285 | fputs(_("can't fork\n"), stderr); |
7cc31c18 | 1286 | set_tty(ctl); |
5cd45502 | 1287 | print_separator('-', 24); |
275d47c9 | 1288 | output_prompt(ctl, filename); |
6dbe3af9 KZ |
1289 | } |
1290 | ||
275d47c9 | 1291 | static void run_shell(struct more_control *ctl, char *filename) |
6dbe3af9 | 1292 | { |
bf6db55a | 1293 | char cmdbuf[COMMAND_BUF]; |
6dbe3af9 | 1294 | |
21d09623 | 1295 | erase_to_col(ctl, 0); |
d162fcb5 | 1296 | putchar('!'); |
f3c044ee | 1297 | fflush(NULL); |
1f861935 | 1298 | if (ctl->previous_command.key == more_kc_run_shell && ctl->shell_line) |
e5e0eac4 | 1299 | fputs(ctl->shell_line, stderr); |
6dbe3af9 | 1300 | else { |
7cc31c18 | 1301 | ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!'); |
474c61c0 SK |
1302 | if (strpbrk(cmdbuf, "%!\\")) |
1303 | expand(ctl, cmdbuf); | |
1304 | else { | |
1305 | free(ctl->shell_line); | |
1306 | ctl->shell_line = xstrdup(cmdbuf); | |
6dbe3af9 KZ |
1307 | } |
1308 | } | |
bd6a253f | 1309 | fputc('\n', stderr); |
f3c044ee | 1310 | fflush(NULL); |
d910f311 | 1311 | ctl->prompt_len = 0; |
7cc31c18 | 1312 | execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0); |
6dbe3af9 KZ |
1313 | } |
1314 | ||
f16ca88d | 1315 | /* Skip n lines in the file f */ |
e3cb27f5 | 1316 | static void skip_lines(struct more_control *ctl) |
f16ca88d | 1317 | { |
0b735ea4 | 1318 | int c; |
f16ca88d | 1319 | |
e3cb27f5 | 1320 | while (ctl->next_jump > 0) { |
82129a6f | 1321 | while ((c = more_getc(ctl)) != '\n') |
f16ca88d SK |
1322 | if (c == EOF) |
1323 | return; | |
e3cb27f5 | 1324 | ctl->next_jump--; |
d910f311 | 1325 | ctl->current_line++; |
f16ca88d SK |
1326 | } |
1327 | } | |
1328 | ||
1329 | /* Clear the screen */ | |
275d47c9 | 1330 | static void more_clear_screen(struct more_control *ctl) |
f16ca88d | 1331 | { |
d910f311 SK |
1332 | if (ctl->clear && !ctl->hard_tty) { |
1333 | putp(ctl->clear); | |
f16ca88d SK |
1334 | /* Put out carriage return so that system doesn't get |
1335 | * confused by escape sequences when expanding tabs */ | |
1336 | putchar('\r'); | |
d910f311 | 1337 | ctl->prompt_len = 0; |
f16ca88d SK |
1338 | } |
1339 | } | |
1340 | ||
82129a6f | 1341 | static void read_line(struct more_control *ctl) |
f16ca88d | 1342 | { |
0b735ea4 SK |
1343 | int c; |
1344 | char *p; | |
f16ca88d | 1345 | |
d910f311 | 1346 | p = ctl->line_buf; |
82129a6f | 1347 | while ((c = more_getc(ctl)) != '\n' && c != EOF |
ba105bb5 | 1348 | && (ptrdiff_t)p != (ptrdiff_t)(ctl->line_buf + ctl->line_sz - 1)) |
f16ca88d SK |
1349 | *p++ = c; |
1350 | if (c == '\n') | |
d910f311 | 1351 | ctl->current_line++; |
f16ca88d SK |
1352 | *p = '\0'; |
1353 | } | |
1354 | ||
fe237228 | 1355 | /* returns: 0 timeout or nothing; <0 error, >0 success */ |
640b9480 | 1356 | static int more_poll(struct more_control *ctl, int timeout, int *stderr_active) |
823ef319 | 1357 | { |
fe237228 KZ |
1358 | enum { |
1359 | POLLFD_SIGNAL = 0, | |
1360 | POLLFD_STDIN, | |
1361 | POLLFD_STDERR, | |
1362 | }; | |
1363 | struct pollfd pfd[] = { | |
1364 | [POLLFD_SIGNAL] = { .fd = ctl->sigfd, .events = POLLIN | POLLERR | POLLHUP }, | |
1365 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP }, | |
1366 | [POLLFD_STDERR] = { .fd = STDERR_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
1367 | }; | |
1368 | int has_data = 0; | |
823ef319 | 1369 | |
640b9480 KZ |
1370 | if (stderr_active) |
1371 | *stderr_active = 0; | |
1372 | ||
fe237228 KZ |
1373 | while (!has_data) { |
1374 | int rc; | |
823ef319 | 1375 | |
fe237228 KZ |
1376 | if (ctl->ignore_stdin) |
1377 | pfd[POLLFD_STDIN].fd = -1; /* probably closed, ignore */ | |
1378 | ||
1379 | rc = poll(pfd, ARRAY_SIZE(pfd), timeout); | |
1380 | ||
1381 | /* error */ | |
1382 | if (rc < 0) { | |
1383 | if (errno == EAGAIN) | |
1384 | continue; | |
1385 | ||
1386 | more_error(ctl, _("poll failed")); | |
1387 | return rc; | |
823ef319 | 1388 | } |
68e14d3d | 1389 | |
fe237228 KZ |
1390 | /* timeout */ |
1391 | if (rc == 0) | |
1392 | return 0; | |
68e14d3d | 1393 | |
fe237228 KZ |
1394 | /* event on signal FD */ |
1395 | if (pfd[POLLFD_SIGNAL].revents) { | |
1396 | struct signalfd_siginfo info; | |
1397 | ssize_t sz; | |
1398 | ||
1399 | sz = read(pfd[POLLFD_SIGNAL].fd, &info, sizeof(info)); | |
1400 | assert(sz == sizeof(info)); | |
1401 | switch (info.ssi_signo) { | |
1402 | case SIGINT: | |
1403 | more_exit(ctl); | |
1404 | break; | |
1405 | case SIGQUIT: | |
1406 | sigquit_handler(ctl); | |
1407 | break; | |
1408 | case SIGTSTP: | |
1409 | sigtstp_handler(ctl); | |
1410 | break; | |
1411 | case SIGCONT: | |
1412 | sigcont_handler(ctl); | |
1413 | break; | |
1414 | case SIGWINCH: | |
1415 | sigwinch_handler(ctl); | |
1416 | break; | |
1417 | default: | |
1418 | abort(); | |
1419 | } | |
1420 | } | |
1421 | ||
1422 | /* event on stdin */ | |
1423 | if (pfd[POLLFD_STDIN].revents) { | |
1424 | /* Check for POLLERR and POLLHUP in stdin revents */ | |
1425 | if ((pfd[POLLFD_STDIN].revents & POLLERR) && | |
1426 | (pfd[POLLFD_STDIN].revents & POLLHUP)) | |
1427 | more_exit(ctl); | |
1428 | ||
1429 | /* poll() return POLLHUP event after pipe close() and POLLNVAL | |
1430 | * means that fd is already closed. */ | |
1431 | if ((pfd[POLLFD_STDIN].revents & POLLHUP) || | |
1432 | (pfd[POLLFD_STDIN].revents & POLLNVAL)) | |
1433 | ctl->ignore_stdin = 1; | |
1434 | else | |
1435 | has_data++; | |
1436 | } | |
1437 | ||
1438 | /* event on stderr (we reads user commands from stderr!) */ | |
640b9480 | 1439 | if (pfd[POLLFD_STDERR].revents) { |
fe237228 | 1440 | has_data++; |
640b9480 KZ |
1441 | if (stderr_active) |
1442 | *stderr_active = 1; | |
1443 | } | |
fe237228 KZ |
1444 | } |
1445 | ||
1446 | return has_data; | |
823ef319 SK |
1447 | } |
1448 | ||
4eaab145 SK |
1449 | /* Search for nth occurrence of regular expression contained in buf in |
1450 | * the file */ | |
82129a6f | 1451 | static void search(struct more_control *ctl, char buf[], int n) |
6dbe3af9 | 1452 | { |
ba105bb5 SK |
1453 | off_t startline = ctl->file_position; |
1454 | off_t line1 = startline; | |
1455 | off_t line2 = startline; | |
1456 | off_t line3; | |
0b735ea4 | 1457 | int lncount; |
cdd2cf46 | 1458 | int saveln, rc; |
4eaab145 SK |
1459 | regex_t re; |
1460 | ||
e3cb27f5 SK |
1461 | if (buf != ctl->previous_search) { |
1462 | free(ctl->previous_search); | |
1463 | ctl->previous_search = buf; | |
1464 | } | |
1465 | ||
1466 | ctl->search_called = 1; | |
d910f311 SK |
1467 | ctl->context.line_num = saveln = ctl->current_line; |
1468 | ctl->context.row_num = startline; | |
4eaab145 | 1469 | lncount = 0; |
bc1ed338 SK |
1470 | if (!buf) |
1471 | goto notfound; | |
4eaab145 SK |
1472 | if ((rc = regcomp(&re, buf, REG_NOSUB)) != 0) { |
1473 | char s[REGERR_BUF]; | |
1474 | regerror(rc, &re, s, sizeof s); | |
7cc31c18 | 1475 | more_error(ctl, s); |
823ef319 | 1476 | return; |
4eaab145 | 1477 | } |
82129a6f | 1478 | while (!feof(ctl->current_file)) { |
4eaab145 SK |
1479 | line3 = line2; |
1480 | line2 = line1; | |
d910f311 | 1481 | line1 = ctl->file_position; |
82129a6f | 1482 | read_line(ctl); |
4eaab145 | 1483 | lncount++; |
5cd45502 SK |
1484 | if (regexec(&re, ctl->line_buf, 0, NULL, 0) == 0 && --n == 0) { |
1485 | if ((1 < lncount && ctl->no_tty_in) || 3 < lncount) { | |
1486 | putchar('\n'); | |
1487 | if (ctl->clear_line_ends) | |
1488 | putp(ctl->erase_line); | |
1489 | fputs(_("...skipping\n"), stdout); | |
1490 | } | |
1491 | if (!ctl->no_tty_in) { | |
1492 | ctl->current_line -= (lncount < 3 ? lncount : 3); | |
1493 | more_fseek(ctl, line3); | |
1494 | if (ctl->no_scroll) { | |
1495 | if (ctl->clear_line_ends) { | |
1496 | putp(ctl->go_home); | |
d910f311 | 1497 | putp(ctl->erase_line); |
5cd45502 SK |
1498 | } else |
1499 | more_clear_screen(ctl); | |
4eaab145 | 1500 | } |
5cd45502 | 1501 | } else { |
21d09623 | 1502 | erase_to_col(ctl, 0); |
5cd45502 SK |
1503 | if (ctl->no_scroll) { |
1504 | if (ctl->clear_line_ends) { | |
1505 | putp(ctl->go_home); | |
1506 | putp(ctl->erase_line); | |
1507 | } else | |
1508 | more_clear_screen(ctl); | |
4eaab145 | 1509 | } |
5cd45502 | 1510 | puts(ctl->line_buf); |
5c36a0eb | 1511 | } |
5cd45502 | 1512 | break; |
042f62df | 1513 | } |
640b9480 | 1514 | more_poll(ctl, 0, NULL); |
7f84e444 | 1515 | } |
823ef319 SK |
1516 | /* Move ctrl+c signal handling back to more_key_command(). */ |
1517 | signal(SIGINT, SIG_DFL); | |
1518 | sigaddset(&ctl->sigset, SIGINT); | |
1519 | sigprocmask(SIG_BLOCK, &ctl->sigset, NULL); | |
53078e08 | 1520 | regfree(&re); |
82129a6f | 1521 | if (feof(ctl->current_file)) { |
d910f311 SK |
1522 | if (!ctl->no_tty_in) { |
1523 | ctl->current_line = saveln; | |
82129a6f | 1524 | more_fseek(ctl, startline); |
4eaab145 | 1525 | } else { |
bd6a253f | 1526 | fputs(_("\nPattern not found\n"), stdout); |
823ef319 | 1527 | more_exit(ctl); |
4eaab145 | 1528 | } |
bc1ed338 | 1529 | notfound: |
7cc31c18 | 1530 | more_error(ctl, _("Pattern not found")); |
6dbe3af9 | 1531 | } |
6dbe3af9 KZ |
1532 | } |
1533 | ||
f8492042 SK |
1534 | static char *find_editor(void) |
1535 | { | |
1536 | static char *editor; | |
1537 | ||
1538 | editor = getenv("VISUAL"); | |
1539 | if (editor == NULL || *editor == '\0') | |
1540 | editor = getenv("EDITOR"); | |
1541 | if (editor == NULL || *editor == '\0') | |
1542 | editor = _PATH_VI; | |
1543 | return editor; | |
1544 | } | |
1545 | ||
b6818219 SK |
1546 | static void runtime_usage(void) |
1547 | { | |
1548 | fputs(_("Most commands optionally preceded by integer argument k. " | |
1549 | "Defaults in brackets.\n" | |
1550 | "Star (*) indicates argument becomes new default.\n"), stdout); | |
5cd45502 | 1551 | print_separator('-', 79); |
b6818219 SK |
1552 | fprintf(stdout, |
1553 | _ | |
1554 | ("<space> Display next k lines of text [current screen size]\n" | |
1555 | "z Display next k lines of text [current screen size]*\n" | |
1556 | "<return> Display next k lines of text [1]*\n" | |
1557 | "d or ctrl-D Scroll k lines [current scroll size, initially 11]*\n" | |
1558 | "q or Q or <interrupt> Exit from more\n" | |
1559 | "s Skip forward k lines of text [1]\n" | |
1560 | "f Skip forward k screenfuls of text [1]\n" | |
1561 | "b or ctrl-B Skip backwards k screenfuls of text [1]\n" | |
1562 | "' Go to place where previous search started\n" | |
1563 | "= Display current line number\n" | |
1564 | "/<regular expression> Search for kth occurrence of regular expression [1]\n" | |
1565 | "n Search for kth occurrence of last r.e [1]\n" | |
1566 | "!<cmd> or :!<cmd> Execute <cmd> in a subshell\n" | |
1567 | "v Start up '%s' at current line\n" | |
1568 | "ctrl-L Redraw screen\n" | |
1569 | ":n Go to kth next file [1]\n" | |
1570 | ":p Go to kth previous file [1]\n" | |
1571 | ":f Display current file name and line number\n" | |
1572 | ". Repeat previous command\n"), | |
1573 | find_editor()); | |
5cd45502 | 1574 | print_separator('-', 79); |
b6818219 SK |
1575 | } |
1576 | ||
042fbf37 | 1577 | static void execute_editor(struct more_control *ctl, char *cmdbuf, size_t buflen, char *filename) |
1f5d21ec SK |
1578 | { |
1579 | char *editor, *p; | |
1f5d21ec | 1580 | int split = 0; |
5cd45502 | 1581 | int n; |
1f5d21ec | 1582 | |
5cd45502 SK |
1583 | if ((ctl->current_line - ctl->lines_per_screen) < 1) |
1584 | n = 1; | |
1585 | else | |
1586 | n = ctl->current_line - (ctl->lines_per_screen + 1) / 2; | |
1f5d21ec SK |
1587 | editor = find_editor(); |
1588 | p = strrchr(editor, '/'); | |
1589 | if (p) | |
1590 | p++; | |
1591 | else | |
1592 | p = editor; | |
1593 | /* | |
1594 | * Earlier: call vi +n file. This also works for emacs. | |
1595 | * POSIX: call vi -c n file (when editor is vi or ex). | |
1596 | */ | |
1597 | if (!strcmp(p, "vi") || !strcmp(p, "ex")) { | |
042fbf37 | 1598 | snprintf(cmdbuf, buflen, "-c %d", n); |
1f5d21ec SK |
1599 | split = 1; |
1600 | } else | |
042fbf37 | 1601 | snprintf(cmdbuf, buflen, "+%d", n); |
1f5d21ec | 1602 | |
21d09623 | 1603 | erase_to_col(ctl, 0); |
1f5d21ec SK |
1604 | printf("%s %s %s", editor, cmdbuf, ctl->file_names[ctl->argv_position]); |
1605 | if (split) { | |
1606 | cmdbuf[2] = 0; | |
1607 | execute(ctl, filename, editor, editor, | |
1608 | cmdbuf, cmdbuf + 3, | |
1609 | ctl->file_names[ctl->argv_position], (char *)0); | |
1610 | } else | |
1611 | execute(ctl, filename, editor, editor, | |
1612 | cmdbuf, ctl->file_names[ctl->argv_position], (char *)0); | |
1613 | } | |
1614 | ||
82129a6f | 1615 | static int skip_backwards(struct more_control *ctl, int nlines) |
1f5d21ec | 1616 | { |
1f5d21ec SK |
1617 | if (nlines == 0) |
1618 | nlines++; | |
21d09623 | 1619 | erase_to_col(ctl, 0); |
1f5d21ec | 1620 | printf(P_("...back %d page", "...back %d pages", nlines), nlines); |
1f5d21ec | 1621 | putchar('\n'); |
837f49c6 | 1622 | ctl->next_jump = ctl->current_line - (ctl->lines_per_screen * (nlines + 1)) - 1; |
e3cb27f5 SK |
1623 | if (ctl->next_jump < 0) |
1624 | ctl->next_jump = 0; | |
ba105bb5 | 1625 | more_fseek(ctl, 0); |
837f49c6 | 1626 | ctl->current_line = 0; |
e3cb27f5 | 1627 | skip_lines(ctl); |
837f49c6 | 1628 | return ctl->lines_per_screen; |
1f5d21ec SK |
1629 | } |
1630 | ||
ba105bb5 | 1631 | static int skip_forwards(struct more_control *ctl, int nlines, cc_t comchar) |
1f5d21ec SK |
1632 | { |
1633 | int c; | |
1634 | ||
1635 | if (nlines == 0) | |
1636 | nlines++; | |
1637 | if (comchar == 'f') | |
1638 | nlines *= ctl->lines_per_screen; | |
1639 | putchar('\r'); | |
21d09623 | 1640 | erase_to_col(ctl, 0); |
1f5d21ec SK |
1641 | putchar('\n'); |
1642 | if (ctl->clear_line_ends) | |
1643 | putp(ctl->erase_line); | |
1644 | printf(P_("...skipping %d line", | |
1645 | "...skipping %d lines", nlines), nlines); | |
1646 | ||
1647 | if (ctl->clear_line_ends) | |
1648 | putp(ctl->erase_line); | |
1649 | putchar('\n'); | |
1650 | ||
1651 | while (nlines > 0) { | |
82129a6f | 1652 | while ((c = more_getc(ctl)) != '\n') |
1f5d21ec SK |
1653 | if (c == EOF) |
1654 | return 0; | |
1655 | ctl->current_line++; | |
1656 | nlines--; | |
1657 | } | |
1658 | return 1; | |
1659 | } | |
1660 | ||
f16ca88d SK |
1661 | /* Read a command and do it. A command consists of an optional integer |
1662 | * argument followed by the command character. Return the number of | |
1663 | * lines to display in the next screenful. If there is nothing more to | |
1664 | * display in the current file, zero is returned. */ | |
82129a6f | 1665 | static int more_key_command(struct more_control *ctl, char *filename) |
6dbe3af9 | 1666 | { |
0b735ea4 | 1667 | int retval = 0; |
640b9480 | 1668 | int done = 0, search_again = 0, stderr_active = 0; |
ba105bb5 | 1669 | char cmdbuf[INIT_BUF]; |
1f861935 SK |
1670 | struct number_command cmd; |
1671 | ||
d910f311 | 1672 | if (!ctl->report_errors) |
275d47c9 | 1673 | output_prompt(ctl, filename); |
f16ca88d | 1674 | else |
d910f311 | 1675 | ctl->report_errors = 0; |
823ef319 | 1676 | ctl->search_called = 0; |
f16ca88d | 1677 | for (;;) { |
640b9480 KZ |
1678 | if (more_poll(ctl, -1, &stderr_active) <= 0) |
1679 | continue; | |
1680 | if (stderr_active == 0) | |
823ef319 | 1681 | continue; |
1f861935 SK |
1682 | cmd = read_command(ctl); |
1683 | if (cmd.key == more_kc_unknown_command) | |
f16ca88d | 1684 | continue; |
1f861935 SK |
1685 | if (cmd.key == more_kc_repeat_previous) |
1686 | cmd = ctl->previous_command; | |
1687 | switch (cmd.key) { | |
1688 | case more_kc_backwards: | |
1f5d21ec | 1689 | if (ctl->no_tty_in) { |
508b2d79 | 1690 | fprintf(stderr, "\a"); |
1f5d21ec | 1691 | return -1; |
f16ca88d | 1692 | } |
1f861935 | 1693 | retval = skip_backwards(ctl, cmd.number); |
1f5d21ec SK |
1694 | done = 1; |
1695 | break; | |
1f861935 SK |
1696 | case more_kc_jump_lines_per_screen: |
1697 | case more_kc_set_lines_per_screen: | |
1698 | if (cmd.number == 0) | |
1699 | cmd.number = ctl->lines_per_screen; | |
1700 | else if (cmd.key == more_kc_set_lines_per_screen) | |
1701 | ctl->lines_per_screen = cmd.number; | |
1702 | retval = cmd.number; | |
bd6a253f SK |
1703 | done = 1; |
1704 | break; | |
1f861935 SK |
1705 | case more_kc_set_scroll_len: |
1706 | if (cmd.number != 0) | |
1707 | ctl->d_scroll_len = cmd.number; | |
d910f311 | 1708 | retval = ctl->d_scroll_len; |
bd6a253f SK |
1709 | done = 1; |
1710 | break; | |
1f861935 | 1711 | case more_kc_quit: |
823ef319 | 1712 | more_exit(ctl); |
9f303a2b HM |
1713 | case more_kc_skip_forward_screen: |
1714 | if (skip_forwards(ctl, cmd.number, 'f')) | |
1715 | retval = ctl->lines_per_screen; | |
1716 | done = 1; | |
1717 | break; | |
1718 | case more_kc_skip_forward_line: | |
1719 | if (skip_forwards(ctl, cmd.number, 's')) | |
1f5d21ec | 1720 | retval = ctl->lines_per_screen; |
bd6a253f SK |
1721 | done = 1; |
1722 | break; | |
1f861935 SK |
1723 | case more_kc_next_line: |
1724 | if (cmd.number != 0) | |
1725 | ctl->lines_per_screen = cmd.number; | |
f16ca88d | 1726 | else |
1f861935 SK |
1727 | cmd.number = 1; |
1728 | retval = cmd.number; | |
bd6a253f SK |
1729 | done = 1; |
1730 | break; | |
1f861935 | 1731 | case more_kc_clear_screen: |
d910f311 | 1732 | if (!ctl->no_tty_in) { |
275d47c9 | 1733 | more_clear_screen(ctl); |
82129a6f | 1734 | more_fseek(ctl, ctl->screen_start.row_num); |
d910f311 SK |
1735 | ctl->current_line = ctl->screen_start.line_num; |
1736 | retval = ctl->lines_per_screen; | |
bd6a253f SK |
1737 | done = 1; |
1738 | break; | |
4eaab145 | 1739 | } else { |
508b2d79 | 1740 | fprintf(stderr, "\a"); |
f16ca88d | 1741 | break; |
4eaab145 | 1742 | } |
1f861935 | 1743 | case more_kc_previous_search_match: |
d910f311 | 1744 | if (!ctl->no_tty_in) { |
21d09623 | 1745 | erase_to_col(ctl, 0); |
bd6a253f | 1746 | fputs(_("\n***Back***\n\n"), stdout); |
82129a6f | 1747 | more_fseek(ctl, ctl->context.row_num); |
d910f311 SK |
1748 | ctl->current_line = ctl->context.line_num; |
1749 | retval = ctl->lines_per_screen; | |
bd6a253f SK |
1750 | done = 1; |
1751 | break; | |
f16ca88d | 1752 | } else { |
508b2d79 | 1753 | fprintf(stderr, "\a"); |
f16ca88d | 1754 | break; |
4eaab145 | 1755 | } |
1f861935 | 1756 | case more_kc_display_line: |
21d09623 | 1757 | erase_to_col(ctl, 0); |
d910f311 | 1758 | ctl->prompt_len = printf("%d", ctl->current_line); |
f3c044ee | 1759 | fflush(NULL); |
f16ca88d | 1760 | break; |
1f861935 SK |
1761 | case more_kc_display_file_and_line: |
1762 | erase_to_col(ctl, 0); | |
1763 | if (!ctl->no_tty_in) | |
1764 | ctl->prompt_len = | |
1765 | printf(_("\"%s\" line %d"), | |
1766 | ctl->file_names[ctl->argv_position], ctl->current_line); | |
1767 | else | |
1768 | ctl->prompt_len = printf(_("[Not a file] line %d"), | |
1769 | ctl->current_line); | |
1770 | fflush(NULL); | |
1771 | break; | |
1772 | case more_kc_repeat_search: | |
d910f311 | 1773 | if (!ctl->previous_search) { |
7cc31c18 | 1774 | more_error(ctl, _("No previous regular expression")); |
f16ca88d SK |
1775 | break; |
1776 | } | |
1f861935 | 1777 | search_again = 1; |
f16ca88d | 1778 | /* fallthrough */ |
1f861935 SK |
1779 | case more_kc_search: |
1780 | if (cmd.number == 0) | |
1781 | cmd.number++; | |
21d09623 | 1782 | erase_to_col(ctl, 0); |
f16ca88d | 1783 | putchar('/'); |
d910f311 | 1784 | ctl->prompt_len = 1; |
f3c044ee | 1785 | fflush(NULL); |
1f861935 | 1786 | if (search_again) { |
bd6a253f | 1787 | fputc('\r', stderr); |
1f861935 SK |
1788 | search(ctl, ctl->previous_search, cmd.number); |
1789 | search_again = 0; | |
4eaab145 | 1790 | } else { |
7cc31c18 | 1791 | ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/'); |
bd6a253f | 1792 | fputc('\r', stderr); |
e3cb27f5 | 1793 | ctl->next_search = xstrdup(cmdbuf); |
1f861935 | 1794 | search(ctl, ctl->next_search, cmd.number); |
4eaab145 | 1795 | } |
d910f311 | 1796 | retval = ctl->lines_per_screen - 1; |
bd6a253f SK |
1797 | done = 1; |
1798 | break; | |
1f861935 | 1799 | case more_kc_run_shell: |
275d47c9 | 1800 | run_shell(ctl, filename); |
f16ca88d | 1801 | break; |
1f861935 | 1802 | case more_kc_help: |
d910f311 | 1803 | if (ctl->no_scroll) |
275d47c9 | 1804 | more_clear_screen(ctl); |
21d09623 | 1805 | erase_to_col(ctl, 0); |
b6818219 | 1806 | runtime_usage(); |
275d47c9 | 1807 | output_prompt(ctl, filename); |
f16ca88d | 1808 | break; |
1f861935 SK |
1809 | case more_kc_next_file: |
1810 | putchar('\r'); | |
1811 | erase_to_col(ctl, 0); | |
1812 | if (cmd.number == 0) | |
1813 | cmd.number = 1; | |
1814 | if (ctl->argv_position + cmd.number >= (unsigned int)ctl->num_files) | |
1815 | more_exit(ctl); | |
1816 | change_file(ctl, cmd.number); | |
1817 | done = 1; | |
1818 | break; | |
1819 | case more_kc_previous_file: | |
1820 | if (ctl->no_tty_in) { | |
1821 | fprintf(stderr, "\a"); | |
1822 | break; | |
1823 | } | |
1824 | putchar('\r'); | |
1825 | erase_to_col(ctl, 0); | |
1826 | if (cmd.number == 0) | |
1827 | cmd.number = 1; | |
1828 | change_file(ctl, -cmd.number); | |
1829 | done = 1; | |
1830 | break; | |
1831 | case more_kc_run_editor: /* This case should go right before default */ | |
d910f311 | 1832 | if (!ctl->no_tty_in) { |
042fbf37 | 1833 | execute_editor(ctl, cmdbuf, sizeof(cmdbuf), filename); |
f16ca88d SK |
1834 | break; |
1835 | } | |
1836 | /* fallthrough */ | |
1837 | default: | |
d910f311 | 1838 | if (ctl->suppress_bell) { |
21d09623 | 1839 | erase_to_col(ctl, 0); |
5cd45502 | 1840 | if (ctl->enter_std) |
d910f311 | 1841 | putp(ctl->enter_std); |
5cd45502 SK |
1842 | ctl->prompt_len = |
1843 | printf(_("[Press 'h' for instructions.]")) | |
d910f311 | 1844 | + 2 * ctl->stdout_glitch; |
5cd45502 | 1845 | if (ctl->exit_std) |
d910f311 | 1846 | putp(ctl->exit_std); |
f16ca88d | 1847 | } else |
508b2d79 | 1848 | fprintf(stderr, "\a"); |
f3c044ee | 1849 | fflush(NULL); |
f16ca88d | 1850 | break; |
4eaab145 | 1851 | } |
1f861935 SK |
1852 | ctl->previous_command = cmd; |
1853 | if (done) { | |
1854 | cmd.key = more_kc_unknown_command; | |
f16ca88d | 1855 | break; |
1f861935 | 1856 | } |
5c36a0eb | 1857 | } |
f16ca88d | 1858 | putchar('\r'); |
d910f311 | 1859 | ctl->no_quit_dialog = 1; |
f728d8ba | 1860 | return retval; |
6dbe3af9 KZ |
1861 | } |
1862 | ||
f16ca88d | 1863 | /* Print out the contents of the file f, one screenful at a time. */ |
82129a6f | 1864 | static void screen(struct more_control *ctl, int num_lines) |
4eaab145 | 1865 | { |
0b735ea4 SK |
1866 | int c; |
1867 | int nchars; | |
f16ca88d SK |
1868 | int length; /* length of current line */ |
1869 | static int prev_len = 1; /* length of previous line */ | |
4eaab145 | 1870 | |
f16ca88d | 1871 | for (;;) { |
d910f311 | 1872 | while (num_lines > 0 && !ctl->is_paused) { |
df6b29d3 IJ |
1873 | nchars = get_line(ctl, &length); |
1874 | ctl->is_eof = nchars == EOF; | |
1875 | if (ctl->is_eof && ctl->exit_on_eof) { | |
d910f311 SK |
1876 | if (ctl->clear_line_ends) |
1877 | putp(ctl->clear_rest); | |
f16ca88d | 1878 | return; |
4eaab145 | 1879 | } |
5c8f5f17 | 1880 | if (ctl->squeeze_spaces && length == 0 && prev_len == 0 && !ctl->is_eof) |
f16ca88d SK |
1881 | continue; |
1882 | prev_len = length; | |
d910f311 SK |
1883 | if (ctl->bad_stdout |
1884 | || ((ctl->enter_std && *ctl->enter_std == ' ') && (ctl->prompt_len > 0))) | |
21d09623 | 1885 | erase_to_col(ctl, 0); |
f16ca88d SK |
1886 | /* must clear before drawing line since tabs on |
1887 | * some terminals do not erase what they tab | |
1888 | * over. */ | |
d910f311 SK |
1889 | if (ctl->clear_line_ends) |
1890 | putp(ctl->erase_line); | |
94bece3c | 1891 | fwrite(ctl->line_buf, length, 1, stdout); |
d910f311 | 1892 | if (nchars < ctl->prompt_len) |
21d09623 SK |
1893 | erase_to_col(ctl, nchars); |
1894 | ctl->prompt_len = 0; | |
d910f311 | 1895 | if (nchars < ctl->num_columns || !ctl->fold_long_lines) |
94bece3c | 1896 | putchar('\n'); |
f16ca88d | 1897 | num_lines--; |
6dbe3af9 | 1898 | } |
f3c044ee | 1899 | fflush(NULL); |
df6b29d3 IJ |
1900 | |
1901 | c = more_getc(ctl); | |
1902 | ctl->is_eof = c == EOF; | |
1903 | ||
1904 | if (ctl->is_eof && ctl->exit_on_eof) { | |
d910f311 SK |
1905 | if (ctl->clear_line_ends) |
1906 | putp(ctl->clear_rest); | |
f16ca88d | 1907 | return; |
4eaab145 | 1908 | } |
f16ca88d | 1909 | |
d910f311 SK |
1910 | if (ctl->is_paused && ctl->clear_line_ends) |
1911 | putp(ctl->clear_rest); | |
82129a6f | 1912 | more_ungetc(ctl, c); |
d910f311 | 1913 | ctl->is_paused = 0; |
823ef319 | 1914 | do { |
82129a6f | 1915 | if ((num_lines = more_key_command(ctl, NULL)) == 0) |
823ef319 SK |
1916 | return; |
1917 | } while (ctl->search_called && !ctl->previous_search); | |
d910f311 | 1918 | if (ctl->hard_tty && ctl->prompt_len > 0) |
21d09623 | 1919 | erase_to_col(ctl, 0); |
d910f311 SK |
1920 | if (ctl->no_scroll && num_lines >= ctl->lines_per_screen) { |
1921 | if (ctl->clear_line_ends) | |
1922 | putp(ctl->go_home); | |
f16ca88d | 1923 | else |
275d47c9 | 1924 | more_clear_screen(ctl); |
f16ca88d | 1925 | } |
d910f311 SK |
1926 | ctl->screen_start.line_num = ctl->current_line; |
1927 | ctl->screen_start.row_num = ctl->file_position; | |
6dbe3af9 | 1928 | } |
6dbe3af9 KZ |
1929 | } |
1930 | ||
0b735ea4 | 1931 | static void copy_file(FILE *f) |
4eaab145 | 1932 | { |
f16ca88d SK |
1933 | char buf[BUFSIZ]; |
1934 | size_t sz; | |
1935 | ||
1936 | while ((sz = fread(&buf, sizeof(char), sizeof(buf), f)) > 0) | |
1937 | fwrite(&buf, sizeof(char), sz, stdout); | |
6dbe3af9 KZ |
1938 | } |
1939 | ||
63f96f68 | 1940 | |
e3cb27f5 | 1941 | static void display_file(struct more_control *ctl, int left) |
63f96f68 SK |
1942 | { |
1943 | if (!ctl->current_file) | |
1944 | return; | |
1945 | ctl->context.line_num = ctl->context.row_num = 0; | |
1946 | ctl->current_line = 0; | |
1947 | if (ctl->first_file) { | |
1948 | ctl->first_file = 0; | |
e3cb27f5 SK |
1949 | if (ctl->next_jump) |
1950 | skip_lines(ctl); | |
63f96f68 | 1951 | if (ctl->search_at_start) { |
e3cb27f5 | 1952 | search(ctl, ctl->next_search, 1); |
63f96f68 SK |
1953 | if (ctl->no_scroll) |
1954 | left--; | |
e3cb27f5 | 1955 | } |
63f96f68 SK |
1956 | } else if (ctl->argv_position < ctl->num_files && !ctl->no_tty_out) |
1957 | left = | |
1958 | more_key_command(ctl, ctl->file_names[ctl->argv_position]); | |
1959 | if (left != 0) { | |
1960 | if ((ctl->no_scroll || ctl->clear_first) | |
8fd1e766 | 1961 | && 0 < ctl->file_size) { |
63f96f68 SK |
1962 | if (ctl->clear_line_ends) |
1963 | putp(ctl->go_home); | |
1964 | else | |
1965 | more_clear_screen(ctl); | |
1966 | } | |
1967 | if (ctl->print_banner) { | |
1968 | if (ctl->bad_stdout) | |
21d09623 | 1969 | erase_to_col(ctl, 0); |
63f96f68 SK |
1970 | if (ctl->clear_line_ends) |
1971 | putp(ctl->erase_line); | |
63f96f68 | 1972 | if (ctl->prompt_len > 14) |
21d09623 | 1973 | erase_to_col(ctl, 14); |
63f96f68 SK |
1974 | if (ctl->clear_line_ends) |
1975 | putp(ctl->erase_line); | |
5cd45502 | 1976 | print_separator(':', 14); |
9c59b604 VS |
1977 | if (ctl->clear_line_ends) |
1978 | putp(ctl->erase_line); | |
63f96f68 SK |
1979 | puts(ctl->file_names[ctl->argv_position]); |
1980 | if (ctl->clear_line_ends) | |
1981 | putp(ctl->erase_line); | |
5cd45502 | 1982 | print_separator(':', 14); |
63f96f68 SK |
1983 | if (left > ctl->lines_per_page - 4) |
1984 | left = ctl->lines_per_page - 4; | |
1985 | } | |
1986 | if (ctl->no_tty_out) | |
1987 | copy_file(ctl->current_file); | |
1988 | else | |
1989 | screen(ctl, left); | |
1990 | } | |
f3c044ee | 1991 | fflush(NULL); |
63f96f68 SK |
1992 | fclose(ctl->current_file); |
1993 | ctl->current_file = NULL; | |
ba105bb5 | 1994 | ctl->screen_start.line_num = ctl->screen_start.row_num = 0; |
63f96f68 SK |
1995 | ctl->context.line_num = ctl->context.row_num = 0L; |
1996 | } | |
1997 | ||
7cc31c18 | 1998 | static void initterm(struct more_control *ctl) |
f16ca88d SK |
1999 | { |
2000 | int ret; | |
f16ca88d SK |
2001 | char *term; |
2002 | struct winsize win; | |
22ff9a38 | 2003 | char *cursor_addr; |
f16ca88d | 2004 | |
f16ca88d | 2005 | #ifndef NON_INTERACTIVE_MORE |
d910f311 | 2006 | ctl->no_tty_out = tcgetattr(STDOUT_FILENO, &ctl->output_tty); |
f16ca88d | 2007 | #endif |
d910f311 | 2008 | ctl->no_tty_in = tcgetattr(STDIN_FILENO, &ctl->output_tty); |
2ae8b757 | 2009 | ctl->no_tty_err = tcgetattr(STDERR_FILENO, &ctl->output_tty); |
d910f311 | 2010 | ctl->original_tty = ctl->output_tty; |
2ae8b757 | 2011 | |
d910f311 | 2012 | ctl->hard_tabs = (ctl->output_tty.c_oflag & TABDLY) != TAB3; |
22ff9a38 SK |
2013 | if (ctl->no_tty_out) |
2014 | return; | |
2015 | ||
2016 | ctl->output_tty.c_lflag &= ~(ICANON | ECHO); | |
2017 | ctl->output_tty.c_cc[VMIN] = 1; | |
2018 | ctl->output_tty.c_cc[VTIME] = 0; | |
2019 | ctl->erase_previous_ok = (ctl->output_tty.c_cc[VERASE] != 255); | |
2020 | ctl->erase_input_ok = (ctl->output_tty.c_cc[VKILL] != 255); | |
2021 | if ((term = getenv("TERM")) == NULL) { | |
2022 | ctl->dumb_tty = 1; | |
22ff9a38 SK |
2023 | } |
2024 | setupterm(term, 1, &ret); | |
2025 | if (ret <= 0) { | |
2026 | ctl->dumb_tty = 1; | |
22ff9a38 SK |
2027 | return; |
2028 | } | |
2029 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) { | |
2030 | ctl->lines_per_page = tigetnum(TERM_LINES); | |
2031 | ctl->num_columns = tigetnum(TERM_COLS); | |
2032 | } else { | |
2033 | if ((ctl->lines_per_page = win.ws_row) == 0) | |
2034 | ctl->lines_per_page = tigetnum(TERM_LINES); | |
2035 | if ((ctl->num_columns = win.ws_col) == 0) | |
2036 | ctl->num_columns = tigetnum(TERM_COLS); | |
2037 | } | |
2038 | if ((ctl->lines_per_page <= 0) || tigetflag(TERM_HARD_COPY)) { | |
2039 | ctl->hard_tty = 1; | |
2040 | ctl->lines_per_page = LINES_PER_PAGE; | |
4eaab145 | 2041 | } |
22ff9a38 SK |
2042 | |
2043 | if (tigetflag(TERM_EAT_NEW_LINE)) | |
2044 | /* Eat newline at last column + 1; dec, concept */ | |
2045 | ctl->eat_newline++; | |
2046 | if (ctl->num_columns <= 0) | |
2047 | ctl->num_columns = NUM_COLUMNS; | |
2048 | ||
2049 | ctl->wrap_margin = tigetflag(TERM_AUTO_RIGHT_MARGIN); | |
2050 | ctl->bad_stdout = tigetflag(TERM_CEOL); | |
2051 | ctl->erase_line = tigetstr(TERM_CLEAR_TO_LINE_END); | |
2052 | ctl->clear = tigetstr(TERM_CLEAR); | |
5cd45502 SK |
2053 | if ((ctl->enter_std = tigetstr(TERM_STANDARD_MODE)) != NULL) { |
2054 | ctl->exit_std = tigetstr(TERM_EXIT_STANDARD_MODE); | |
2055 | if (0 < tigetnum(TERM_STD_MODE_GLITCH)) | |
2056 | ctl->stdout_glitch = 1; | |
2057 | } | |
22ff9a38 | 2058 | |
22ff9a38 SK |
2059 | cursor_addr = tigetstr(TERM_HOME); |
2060 | if (cursor_addr == NULL || *cursor_addr == '\0') { | |
2061 | cursor_addr = tigetstr(TERM_CURSOR_ADDRESS); | |
2062 | if (cursor_addr) | |
2063 | cursor_addr = tparm(cursor_addr, 0, 0); | |
2064 | } | |
2065 | if (cursor_addr) | |
2066 | ctl->go_home = xstrdup(cursor_addr); | |
2067 | ||
5cd45502 SK |
2068 | if ((ctl->move_line_down = tigetstr(TERM_LINE_DOWN)) == NULL) |
2069 | ctl->move_line_down = BACKSPACE; | |
22ff9a38 | 2070 | ctl->clear_rest = tigetstr(TERM_CLEAR_TO_SCREEN_END); |
5cd45502 SK |
2071 | if ((ctl->backspace_ch = tigetstr(TERM_BACKSPACE)) == NULL) |
2072 | ctl->backspace_ch = BACKSPACE; | |
22ff9a38 SK |
2073 | |
2074 | if ((ctl->shell = getenv("SHELL")) == NULL) | |
2075 | ctl->shell = _PATH_BSHELL; | |
6dbe3af9 KZ |
2076 | } |
2077 | ||
f16ca88d SK |
2078 | int main(int argc, char **argv) |
2079 | { | |
f16ca88d | 2080 | char *s; |
f16ca88d | 2081 | int left; |
7cc31c18 | 2082 | struct more_control ctl = { |
d910f311 SK |
2083 | .first_file = 1, |
2084 | .fold_long_lines = 1, | |
2085 | .no_quit_dialog = 1, | |
d910f311 | 2086 | .stop_after_formfeed = 1, |
d910f311 SK |
2087 | .wrap_margin = 1, |
2088 | .lines_per_page = LINES_PER_PAGE, | |
2089 | .num_columns = NUM_COLUMNS, | |
2090 | .d_scroll_len = SCROLL_LEN, | |
7cc31c18 SK |
2091 | 0 |
2092 | }; | |
f16ca88d SK |
2093 | |
2094 | setlocale(LC_ALL, ""); | |
2095 | bindtextdomain(PACKAGE, LOCALEDIR); | |
2096 | textdomain(PACKAGE); | |
2c308875 | 2097 | close_stdout_atexit(); |
f16ca88d | 2098 | setlocale(LC_ALL, ""); |
f16ca88d SK |
2099 | |
2100 | /* Auto set no scroll on when binary is called page */ | |
2101 | if (!(strcmp(program_invocation_short_name, "page"))) | |
d910f311 | 2102 | ctl.no_scroll++; |
f16ca88d | 2103 | |
28b391ce KZ |
2104 | ctl.exit_on_eof = getenv("POSIXLY_CORRECT") ? 0 : 1; |
2105 | ||
e3cb27f5 SK |
2106 | if ((s = getenv("MORE")) != NULL) |
2107 | env_argscan(&ctl, s); | |
28b391ce | 2108 | |
e3cb27f5 SK |
2109 | argscan(&ctl, argc, argv); |
2110 | ||
e18f896e KZ |
2111 | /* clear any inherited settings */ |
2112 | signal(SIGCHLD, SIG_DFL); | |
2113 | ||
e3cb27f5 SK |
2114 | initterm(&ctl); |
2115 | ||
2ae8b757 KZ |
2116 | if (ctl.no_tty_err) |
2117 | /* exit when we cannot read user's input */ | |
2118 | ctl.exit_on_eof = 1; | |
2119 | ||
09070e1a SK |
2120 | #ifdef HAVE_MAGIC |
2121 | ctl.magic = magic_open(MAGIC_MIME_ENCODING | MAGIC_SYMLINK); | |
2122 | magic_load(ctl.magic, NULL); | |
2123 | #endif | |
7cc31c18 | 2124 | prepare_line_buffer(&ctl); |
f16ca88d | 2125 | |
d910f311 SK |
2126 | ctl.d_scroll_len = ctl.lines_per_page / 2 - 1; |
2127 | if (ctl.d_scroll_len <= 0) | |
2128 | ctl.d_scroll_len = 1; | |
f16ca88d | 2129 | |
d910f311 SK |
2130 | /* allow clear_line_ends only if go_home and erase_line and clear_rest strings are |
2131 | * defined, and in that case, make sure we are in no_scroll mode */ | |
2132 | if (ctl.clear_line_ends) { | |
2133 | if ((ctl.go_home == NULL) || (*ctl.go_home == '\0') || | |
2134 | (ctl.erase_line == NULL) || (*ctl.erase_line == '\0') || | |
2135 | (ctl.clear_rest == NULL) || (*ctl.clear_rest == '\0')) | |
2136 | ctl.clear_line_ends = 0; | |
f16ca88d | 2137 | else |
d910f311 | 2138 | ctl.no_scroll = 1; |
f16ca88d | 2139 | } |
d910f311 SK |
2140 | if (ctl.lines_per_screen == 0) |
2141 | ctl.lines_per_screen = ctl.lines_per_page - 1; | |
2142 | left = ctl.lines_per_screen; | |
2143 | if (ctl.num_files > 1) | |
63f96f68 | 2144 | ctl.print_banner = 1; |
d910f311 | 2145 | if (!ctl.no_tty_in && ctl.num_files == 0) { |
f16ca88d SK |
2146 | warnx(_("bad usage")); |
2147 | errtryhelp(EXIT_FAILURE); | |
2148 | } else | |
82129a6f | 2149 | ctl.current_file = stdin; |
d910f311 | 2150 | if (!ctl.no_tty_out) { |
f16ca88d | 2151 | if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) { |
d910f311 | 2152 | ctl.catch_suspend++; |
f16ca88d | 2153 | } |
d910f311 | 2154 | tcsetattr(STDERR_FILENO, TCSANOW, &ctl.output_tty); |
f16ca88d | 2155 | } |
823ef319 SK |
2156 | sigemptyset(&ctl.sigset); |
2157 | sigaddset(&ctl.sigset, SIGINT); | |
2158 | sigaddset(&ctl.sigset, SIGQUIT); | |
2159 | sigaddset(&ctl.sigset, SIGTSTP); | |
6dcaaa9e | 2160 | sigaddset(&ctl.sigset, SIGCONT); |
823ef319 SK |
2161 | sigaddset(&ctl.sigset, SIGWINCH); |
2162 | sigprocmask(SIG_BLOCK, &ctl.sigset, NULL); | |
2163 | ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC); | |
d910f311 SK |
2164 | if (ctl.no_tty_in) { |
2165 | if (ctl.no_tty_out) | |
f16ca88d SK |
2166 | copy_file(stdin); |
2167 | else { | |
82129a6f | 2168 | ctl.current_file = stdin; |
e3cb27f5 | 2169 | display_file(&ctl, left); |
f16ca88d | 2170 | } |
d910f311 | 2171 | ctl.no_tty_in = 0; |
63f96f68 | 2172 | ctl.print_banner = 1; |
d910f311 | 2173 | ctl.first_file = 0; |
f16ca88d SK |
2174 | } |
2175 | ||
d910f311 | 2176 | while (ctl.argv_position < ctl.num_files) { |
63f96f68 | 2177 | checkf(&ctl, ctl.file_names[ctl.argv_position]); |
e3cb27f5 | 2178 | display_file(&ctl, left); |
d910f311 | 2179 | ctl.first_file = 0; |
63f96f68 | 2180 | ctl.argv_position++; |
f16ca88d | 2181 | } |
73ff5a95 SK |
2182 | ctl.clear_line_ends = 0; |
2183 | ctl.prompt_len = 0; | |
2184 | more_exit(&ctl); | |
f16ca88d | 2185 | } |