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