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