diff options
Diffstat (limited to 'text-utils/more.c')
-rw-r--r-- | text-utils/more.c | 2135 |
1 files changed, 2135 insertions, 0 deletions
diff --git a/text-utils/more.c b/text-utils/more.c new file mode 100644 index 0000000..22adaab --- /dev/null +++ b/text-utils/more.c @@ -0,0 +1,2135 @@ +/* + * Copyright (C) 1980 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* more.c - General purpose tty output filter and file perusal program + * + * by Eric Shienbrood, UC Berkeley + * + * modified by Geoff Peck + * UCB to add underlining, single spacing + * modified by John Foderaro + * UCB to add -c and MORE environment variable + * modified by Erik Troan <ewt@redhat.com> + * to be more posix and so compile on linux/axp. + * modified by Kars de Jong <jongk@cs.utwente.nl> + * to use terminfo instead of termcap. + * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> + * added Native Language Support + * 1999-03-19 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * more nls translatable strings + * 1999-05-09 aeb + * applied a RedHat patch (setjmp->sigsetjmp); without it a second + * ^Z would fail. + * 1999-05-09 aeb + * undone Kars' work, so that more works without libcurses (and + * hence can be in /bin with libcurses being in + * /usr/lib which may not be mounted). However, when termcap is not + * present curses can still be used. + * 2010-10-21 Davidlohr Bueso <dave@gnu.org> + * modified mem allocation handling for util-linux + */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdarg.h> +#include <sys/param.h> +#include <ctype.h> +#include <signal.h> +#include <errno.h> +#include <fcntl.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/ttydefaults.h> +#include <sys/wait.h> +#include <regex.h> +#include <assert.h> +#include <poll.h> +#include <sys/signalfd.h> +#include <paths.h> +#include <getopt.h> + +#if defined(HAVE_NCURSESW_TERM_H) +# include <ncursesw/term.h> +#elif defined(HAVE_NCURSES_TERM_H) +# include <ncurses/term.h> +#elif defined(HAVE_TERM_H) +# include <term.h> +#endif + +#ifdef HAVE_MAGIC +# include <magic.h> +#endif + +#include "strutils.h" +#include "nls.h" +#include "xalloc.h" +#include "widechar.h" +#include "closestream.h" +#include "env.h" + +#ifdef TEST_PROGRAM +# define NON_INTERACTIVE_MORE 1 +#endif + +#define BACKSPACE "\b" +#define CARAT "^" + +#define ARROW_UP "\x1b\x5b\x41" +#define ARROW_DOWN "\x1b\x5b\x42" +#define PAGE_UP "\x1b\x5b\x35\x7e" +#define PAGE_DOWN "\x1b\x5b\x36\x7e" + +#define MIN_LINE_SZ 256 /* minimal line_buf buffer size */ +#define ESC '\033' +#define SCROLL_LEN 11 +#define LINES_PER_PAGE 24 +#define NUM_COLUMNS 80 +#define TERMINAL_BUF 4096 +#define INIT_BUF 80 +#define COMMAND_BUF 200 +#define REGERR_BUF NUM_COLUMNS + +#define TERM_AUTO_RIGHT_MARGIN "am" +#define TERM_BACKSPACE "cub1" +#define TERM_CEOL "xhp" +#define TERM_CLEAR "clear" +#define TERM_CLEAR_TO_LINE_END "el" +#define TERM_CLEAR_TO_SCREEN_END "ed" +#define TERM_COLS "cols" +#define TERM_CURSOR_ADDRESS "cup" +#define TERM_EAT_NEW_LINE "xenl" +#define TERM_EXIT_STANDARD_MODE "rmso" +#define TERM_HARD_COPY "hc" +#define TERM_HOME "home" +#define TERM_LINE_DOWN "cud1" +#define TERM_LINES "lines" +#define TERM_OVER_STRIKE "os" +#define TERM_STANDARD_MODE "smso" +#define TERM_STD_MODE_GLITCH "xmc" + +/* Used in read_command() */ +typedef enum { + more_kc_unknown_command, + more_kc_colon, + more_kc_repeat_previous, + more_kc_backwards, + more_kc_jump_lines_per_screen, + more_kc_set_lines_per_screen, + more_kc_set_scroll_len, + more_kc_quit, + more_kc_skip_forward_screen, + more_kc_skip_forward_line, + more_kc_next_line, + more_kc_clear_screen, + more_kc_previous_search_match, + more_kc_display_line, + more_kc_display_file_and_line, + more_kc_repeat_search, + more_kc_search, + more_kc_run_shell, + more_kc_help, + more_kc_next_file, + more_kc_previous_file, + more_kc_run_editor +} more_key_commands; +struct number_command { + unsigned int number; + more_key_commands key; +}; + +struct more_control { + struct termios output_tty; /* output terminal */ + struct termios original_tty; /* original terminal settings */ + FILE *current_file; /* currently open input file */ + off_t file_position; /* file position */ + off_t file_size; /* file size */ + int argv_position; /* argv[] position */ + int lines_per_screen; /* screen size in lines */ + int d_scroll_len; /* number of lines scrolled by 'd' */ + int prompt_len; /* message prompt length */ + int current_line; /* line we are currently at */ + int next_jump; /* number of lines to skip ahead */ + char **file_names; /* The list of file names */ + int num_files; /* Number of files left to process */ + char *shell; /* name of the shell to use */ + int sigfd; /* signalfd() file descriptor */ + sigset_t sigset; /* signal operations */ + char *line_buf; /* line buffer */ + size_t line_sz; /* size of line_buf buffer */ + int lines_per_page; /* lines per page */ + char *clear; /* clear screen */ + char *erase_line; /* erase line */ + char *enter_std; /* enter standout mode */ + char *exit_std; /* exit standout mode */ + char *backspace_ch; /* backspace character */ + char *go_home; /* go to home */ + char *move_line_down; /* move line down */ + char *clear_rest; /* clear rest of screen */ + int num_columns; /* number of columns */ + char *next_search; /* file beginning search string */ + char *previous_search; /* previous search() buf[] item */ + struct { + off_t row_num; /* row file position */ + long line_num; /* line number */ + } context, + screen_start; + unsigned int leading_number; /* number in front of key command */ + struct number_command previous_command; /* previous key command */ + char *shell_line; /* line to execute in subshell */ +#ifdef HAVE_MAGIC + magic_t magic; /* libmagic database entries */ +#endif + unsigned int + bad_stdout:1, /* true if overwriting does not turn off standout */ + catch_suspend:1, /* we should catch the SIGTSTP signal */ + clear_line_ends:1, /* do not scroll, paint each screen from the top */ + clear_first:1, /* is first character in file \f */ + dumb_tty:1, /* is terminal type known */ + eat_newline:1, /* is newline ignored after 80 cols */ + erase_input_ok:1, /* is erase input supported */ + erase_previous_ok:1, /* is erase previous supported */ + exit_on_eof:1, /* exit on EOF */ + first_file:1, /* is the input file the first in list */ + fold_long_lines:1, /* fold long lines */ + hard_tabs:1, /* print spaces instead of '\t' */ + hard_tty:1, /* is this hard copy terminal (a printer or such) */ + leading_colon:1, /* key command has leading ':' character */ + is_eof:1, /* EOF detected */ + is_paused:1, /* is output paused */ + no_quit_dialog:1, /* suppress quit dialog */ + no_scroll:1, /* do not scroll, clear the screen and then display text */ + no_tty_in:1, /* is input in interactive mode */ + no_tty_out:1, /* is output in interactive mode */ + no_tty_err:1, /* is stderr terminal */ + print_banner:1, /* print file name banner */ + reading_num:1, /* are we reading leading_number */ + report_errors:1, /* is an error reported */ + search_at_start:1, /* search pattern defined at start up */ + search_called:1, /* previous more command was a search */ + squeeze_spaces:1, /* suppress white space */ + stdout_glitch:1, /* terminal has standout mode glitch */ + stop_after_formfeed:1, /* stop after form feeds */ + suppress_bell:1, /* suppress bell */ + wrap_margin:1; /* set if automargins */ +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + printf("%s", USAGE_HEADER); + printf(_(" %s [options] <file>...\n"), program_invocation_short_name); + + printf("%s", USAGE_SEPARATOR); + printf("%s\n", _("Display the contents of a file in a terminal.")); + + printf("%s", USAGE_OPTIONS); + printf("%s\n", _(" -d, --silent display help instead of ringing bell")); + printf("%s\n", _(" -f, --logical count logical rather than screen lines")); + printf("%s\n", _(" -l, --no-pause suppress pause after form feed")); + printf("%s\n", _(" -c, --print-over do not scroll, display text and clean line ends")); + printf("%s\n", _(" -p, --clean-print do not scroll, clean screen and display text")); + printf("%s\n", _(" -e, --exit-on-eof exit on end-of-file")); + printf("%s\n", _(" -s, --squeeze squeeze multiple blank lines into one")); + printf("%s\n", _(" -u, --plain suppress underlining and bold")); + printf("%s\n", _(" -n, --lines <number> the number of lines per screenful")); + printf("%s\n", _(" -<number> same as --lines")); + printf("%s\n", _(" +<number> display file beginning from line number")); + printf("%s\n", _(" +/<pattern> display file beginning from pattern match")); + printf("%s", USAGE_SEPARATOR); + printf(USAGE_HELP_OPTIONS(23)); + printf(USAGE_MAN_TAIL("more(1)")); + exit(EXIT_SUCCESS); +} + +static void argscan(struct more_control *ctl, int as_argc, char **as_argv) +{ + int c, opt; + static const struct option longopts[] = { + { "silent", no_argument, NULL, 'd' }, + { "logical", no_argument, NULL, 'f' }, + { "no-pause", no_argument, NULL, 'l' }, + { "print-over", no_argument, NULL, 'c' }, + { "clean-print", no_argument, NULL, 'p' }, + { "exit-on-eof", no_argument, NULL, 'e' }, + { "squeeze", no_argument, NULL, 's' }, + { "plain", no_argument, NULL, 'u' }, + { "lines", required_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + /* Take care of number option and +args. */ + for (opt = 0; opt < as_argc; opt++) { + int move = 0; + + if (as_argv[opt][0] == '-' && isdigit_string(as_argv[opt] + 1)) { + ctl->lines_per_screen = + strtos16_or_err(as_argv[opt], _("failed to parse number")); + ctl->lines_per_screen = abs(ctl->lines_per_screen); + move = 1; + } else if (as_argv[opt][0] == '+') { + if (isdigit_string(as_argv[opt] + 1)) { + ctl->next_jump = strtos32_or_err(as_argv[opt], + _("failed to parse number")) - 1; + move = 1; + } else if (as_argv[opt][1] == '/') { + free(ctl->next_search); + ctl->next_search = xstrdup(as_argv[opt] + 2); + ctl->search_at_start = 1; + move = 1; + } + } + if (move) { + as_argc = remote_entry(as_argv, opt, as_argc); + opt--; + } + } + + while ((c = getopt_long(as_argc, as_argv, "dflcpsun:eVh", longopts, NULL)) != -1) { + switch (c) { + case 'd': + ctl->suppress_bell = 1; + break; + case 'l': + ctl->stop_after_formfeed = 0; + break; + case 'f': + ctl->fold_long_lines = 0; + break; + case 'p': + ctl->no_scroll = 1; + break; + case 'c': + ctl->clear_line_ends = 1; + break; + case 's': + ctl->squeeze_spaces = 1; + break; + case 'u': + break; + case 'n': + ctl->lines_per_screen = strtou16_or_err(optarg, _("argument error")); + break; + case 'e': + ctl->exit_on_eof = 1; + break; + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + break; + } + } + ctl->num_files = as_argc - optind; + ctl->file_names = as_argv + optind; +} + +static void env_argscan(struct more_control *ctl, const char *s) +{ + char **env_argv; + int env_argc = 1; + int size = 8; + const char delim[] = { ' ', '\n', '\t', '\0' }; + char *str = xstrdup(s); + char *key = NULL, *tok; + + env_argv = xmalloc(sizeof(char *) * size); + env_argv[0] = _("MORE environment variable"); /* program name */ + for (tok = strtok_r(str, delim, &key); tok; tok = strtok_r(NULL, delim, &key)) { + if (size == env_argc) { + size *= 2; + env_argv = xrealloc(env_argv, sizeof(char *) * size); + } + env_argv[env_argc++] = tok; + } + + argscan(ctl, env_argc, env_argv); + /* Reset optind, command line parsing needs this. */ + optind = 0; + free(str); + free(env_argv); +} + +static void more_fseek(struct more_control *ctl, off_t pos) +{ + ctl->file_position = pos; + fseeko(ctl->current_file, pos, SEEK_SET); +} + +static int more_getc(struct more_control *ctl) +{ + int ret = getc(ctl->current_file); + ctl->file_position = ftello(ctl->current_file); + return ret; +} + +static int more_ungetc(struct more_control *ctl, int c) +{ + int ret = ungetc(c, ctl->current_file); + ctl->file_position = ftello(ctl->current_file); + return ret; +} + +static void print_separator(const int c, int n) +{ + while (n--) + putchar(c); + putchar('\n'); +} + +/* check_magic -- check for file magic numbers. */ +static int check_magic(struct more_control *ctl, char *fs) +{ +#ifdef HAVE_MAGIC + const int fd = fileno(ctl->current_file); + const char *mime_encoding = magic_descriptor(ctl->magic, fd); + const char *magic_error_msg = magic_error(ctl->magic); + + if (magic_error_msg) { + printf("%s: %s: %s\n", program_invocation_short_name, + _("magic failed"), magic_error_msg); + return 0; + } + if (!mime_encoding || !(strcmp("binary", mime_encoding))) { + printf(_("\n******** %s: Not a text file ********\n\n"), fs); + return 1; + } +#else + signed char twobytes[2]; + + /* don't try to look ahead if the input is unseekable */ + if (fseek(ctl->current_file, 0L, SEEK_SET)) + return 0; + + if (fread(twobytes, 2, 1, ctl->current_file) == 1) { + switch (twobytes[0] + (twobytes[1] << 8)) { + case 0407: /* a.out obj */ + case 0410: /* a.out exec */ + case 0413: /* a.out demand exec */ + case 0405: + case 0411: + case 0177545: + case 0x457f: /* simple ELF detection */ + printf(_("\n******** %s: Not a text file ********\n\n"), + fs); + return 1; + } + } + fseek(ctl->current_file, 0L, SEEK_SET); /* rewind() not necessary */ +#endif + return 0; +} + +/* Check whether the file named by fs is an ASCII file which the user may + * access. If it is, return the opened file. Otherwise return NULL. */ +static void checkf(struct more_control *ctl, char *fs) +{ + struct stat st; + int c; + + ctl->current_line = 0; + ctl->file_position = 0; + ctl->file_size = 0; + fflush(NULL); + + ctl->current_file = fopen(fs, "r"); + if (ctl->current_file == NULL) { + if (ctl->clear_line_ends) + putp(ctl->erase_line); + warn(_("cannot open %s"), fs); + return; + } + if (fstat(fileno(ctl->current_file), &st) != 0) { + warn(_("stat of %s failed"), fs); + return; + } + if ((st.st_mode & S_IFMT) == S_IFDIR) { + printf(_("\n*** %s: directory ***\n\n"), fs); + ctl->current_file = NULL; + return; + } + ctl->file_size = st.st_size; + if (0 < ctl->file_size && check_magic(ctl, fs)) { + fclose(ctl->current_file); + ctl->current_file = NULL; + return; + } + fcntl(fileno(ctl->current_file), F_SETFD, FD_CLOEXEC); + c = more_getc(ctl); + ctl->clear_first = (c == '\f'); + more_ungetc(ctl, c); +} + +static void prepare_line_buffer(struct more_control *ctl) +{ + size_t sz = ctl->num_columns * 4; + + if (ctl->line_sz >= sz) + return; + + if (sz < MIN_LINE_SZ) + sz = MIN_LINE_SZ; + + /* alloc sz and extra space for \n\0 */ + ctl->line_buf = xrealloc(ctl->line_buf, sz + 2); + ctl->line_sz = sz; +} + +/* Get a logical line */ +static int get_line(struct more_control *ctl, int *length) +{ + int c; + char *p; + int column; + static int column_wrap; + +#ifdef HAVE_WIDECHAR + size_t i; + wchar_t wc; + int wc_width; + mbstate_t state, state_bak; /* Current status of the stream. */ + char mbc[MB_LEN_MAX]; /* Buffer for one multibyte char. */ + size_t mblength; /* Byte length of multibyte char. */ + size_t mbc_pos = 0; /* Position of the MBC. */ + int use_mbc_buffer_flag = 0; /* If 1, mbc has data. */ + int break_flag = 0; /* If 1, exit while(). */ + off_t file_position_bak = ctl->file_position; + + memset(&state, '\0', sizeof(mbstate_t)); +#endif + + p = ctl->line_buf; + column = 0; + c = more_getc(ctl); + if (column_wrap && c == '\n') { + ctl->current_line++; + c = more_getc(ctl); + } + while (p < &ctl->line_buf[ctl->line_sz - 1]) { +#ifdef HAVE_WIDECHAR + if (ctl->fold_long_lines && use_mbc_buffer_flag && MB_CUR_MAX > 1) { + use_mbc_buffer_flag = 0; + state_bak = state; + mbc[mbc_pos++] = c; + process_mbc: + mblength = mbrtowc(&wc, mbc, mbc_pos, &state); + + switch (mblength) { + case (size_t)-2: /* Incomplete multibyte character. */ + use_mbc_buffer_flag = 1; + state = state_bak; + break; + + case (size_t)-1: /* Invalid as a multibyte character. */ + *p++ = mbc[0]; + state = state_bak; + column++; + file_position_bak++; + if (column >= ctl->num_columns) { + more_fseek(ctl, file_position_bak); + } else { + memmove(mbc, mbc + 1, --mbc_pos); + if (mbc_pos > 0) { + mbc[mbc_pos] = '\0'; + goto process_mbc; + } + } + break; + + default: + wc_width = wcwidth(wc); + if (column + wc_width > ctl->num_columns) { + more_fseek(ctl, file_position_bak); + break_flag = 1; + } else { + for (i = 0; p < &ctl->line_buf[ctl->line_sz - 1] && + i < mbc_pos; i++) + *p++ = mbc[i]; + if (wc_width > 0) + column += wc_width; + } + } + + if (break_flag || column >= ctl->num_columns) + break; + + c = more_getc(ctl); + continue; + } +#endif /* HAVE_WIDECHAR */ + if (c == EOF) { + if (p > ctl->line_buf) { + *p = '\0'; + *length = p - ctl->line_buf; + return column; + } + *length = p - ctl->line_buf; + return EOF; + } + if (c == '\n') { + ctl->current_line++; + break; + } + + *p++ = c; + if (c == '\t') { + if (!ctl->hard_tabs || (column < ctl->prompt_len && !ctl->hard_tty)) { + if (ctl->hard_tabs && ctl->erase_line && !ctl->dumb_tty) { + column = 1 + (column | 7); + putp(ctl->erase_line); + ctl->prompt_len = 0; + } else { + for (--p; p < &ctl->line_buf[ctl->line_sz - 1];) { + *p++ = ' '; + if ((++column & 7) == 0) + break; + } + if (column >= ctl->prompt_len) + ctl->prompt_len = 0; + } + } else + column = 1 + (column | 7); + } else if (c == '\b' && column > 0) { + column--; + } else if (c == '\r') { + int next = more_getc(ctl); + if (next == '\n') { + p--; + ctl->current_line++; + break; + } + more_ungetc(ctl, c); + column = 0; + } else if (c == '\f' && ctl->stop_after_formfeed) { + p[-1] = '^'; + *p++ = 'L'; + column += 2; + ctl->is_paused = 1; + } else if (c == EOF) { + *length = p - ctl->line_buf; + return column; + } else { +#ifdef HAVE_WIDECHAR + if (ctl->fold_long_lines && MB_CUR_MAX > 1) { + memset(mbc, '\0', MB_LEN_MAX); + mbc_pos = 0; + mbc[mbc_pos++] = c; + state_bak = state; + + mblength = mbrtowc(&wc, mbc, mbc_pos, &state); + /* The value of mblength is always less than 2 here. */ + switch (mblength) { + case (size_t)-2: + p--; + file_position_bak = ctl->file_position - 1; + state = state_bak; + use_mbc_buffer_flag = 1; + break; + + case (size_t)-1: + state = state_bak; + column++; + break; + + default: + wc_width = wcwidth(wc); + if (wc_width > 0) + column += wc_width; + } + } else +#endif /* HAVE_WIDECHAR */ + { + if (isprint(c)) + column++; + } + } + + if (column >= ctl->num_columns && ctl->fold_long_lines) + break; +#ifdef HAVE_WIDECHAR + if (use_mbc_buffer_flag == 0 && p >= &ctl->line_buf[ctl->line_sz - 1 - 4]) + /* don't read another char if there is no space for + * whole multibyte sequence */ + break; +#endif + c = more_getc(ctl); + } + if (column >= ctl->num_columns && ctl->num_columns > 0) { + if (!ctl->wrap_margin) { + *p++ = '\n'; + } + } + column_wrap = column == ctl->num_columns && ctl->fold_long_lines; + if (column_wrap && ctl->eat_newline && ctl->wrap_margin) { + *p++ = '\n'; /* simulate normal wrap */ + } + *length = p - ctl->line_buf; + *p = 0; + return column; +} + +/* Erase the rest of the prompt, assuming we are starting at column col. */ +static void erase_to_col(struct more_control *ctl, int col) +{ + + if (ctl->prompt_len == 0) + return; + if (col == 0 && ctl->clear_line_ends) + puts(ctl->erase_line); + else if (ctl->hard_tty) + putchar('\n'); + else { + if (col == 0) + putchar('\r'); + if (!ctl->dumb_tty && ctl->erase_line) + putp(ctl->erase_line); + else { + printf("%*s", ctl->prompt_len - col, ""); + if (col == 0) + putchar('\r'); + } + } + ctl->prompt_len = col; +} + +static void output_prompt(struct more_control *ctl, char *filename) +{ + if (ctl->clear_line_ends) + putp(ctl->erase_line); + else if (ctl->prompt_len > 0) + erase_to_col(ctl, 0); + if (!ctl->hard_tty) { + ctl->prompt_len = 0; + if (ctl->enter_std) { + putp(ctl->enter_std); + ctl->prompt_len += (2 * ctl->stdout_glitch); + } + if (ctl->clear_line_ends) + putp(ctl->erase_line); + ctl->prompt_len += printf(_("--More--")); + if (filename != NULL) { + ctl->prompt_len += printf(_("(Next file: %s)"), filename); + } else if (!ctl->no_tty_in && 0 < ctl->file_size) { + int position = ((ctl->file_position * 100) / ctl->file_size); + if (position == 100) { + erase_to_col(ctl, 0); + ctl->prompt_len += printf(_("(END)")); + } else { + ctl->prompt_len += printf("(%d%%)", position); + } + } else if (ctl->is_eof) { + erase_to_col(ctl, 0); + ctl->prompt_len += printf(_("(END)")); + } + + if (ctl->suppress_bell) { + ctl->prompt_len += + printf(_("[Press space to continue, 'q' to quit.]")); + } + if (ctl->exit_std) + putp(ctl->exit_std); + if (ctl->clear_line_ends) + putp(ctl->clear_rest); + } else + fprintf(stderr, "\a"); + fflush(NULL); +} + +static void reset_tty(struct more_control *ctl) +{ + if (ctl->no_tty_out) + return; + fflush(NULL); + ctl->output_tty.c_lflag |= ICANON | ECHO; + ctl->output_tty.c_cc[VMIN] = ctl->original_tty.c_cc[VMIN]; + ctl->output_tty.c_cc[VTIME] = ctl->original_tty.c_cc[VTIME]; + tcsetattr(STDERR_FILENO, TCSANOW, &ctl->original_tty); +} + +/* Clean up terminal state and exit. Also come here if interrupt signal received */ +static void __attribute__((__noreturn__)) more_exit(struct more_control *ctl) +{ +#ifdef HAVE_MAGIC + magic_close(ctl->magic); +#endif + reset_tty(ctl); + if (ctl->clear_line_ends) { + putchar('\r'); + putp(ctl->erase_line); + } else if (!ctl->clear_line_ends && (ctl->prompt_len > 0)) + erase_to_col(ctl, 0); + fflush(NULL); + free(ctl->previous_search); + free(ctl->shell_line); + free(ctl->line_buf); + free(ctl->go_home); + if (ctl->current_file) + fclose(ctl->current_file); + del_curterm(cur_term); + _exit(EXIT_SUCCESS); +} + +static cc_t read_user_input(struct more_control *ctl) +{ + cc_t c; + + errno = 0; + /* + * Key commands can be read() from either stderr or stdin. If they + * are read from stdin such as 'cat file.txt | more' then the pipe + * input is understood as series key commands - and that is not + * wanted. Keep the read() reading from stderr. + */ + if (read(STDERR_FILENO, &c, 1) <= 0) { + if (errno != EINTR) + more_exit(ctl); + else + c = ctl->output_tty.c_cc[VKILL]; + } + return c; +} + +/* Read a number and command from the terminal. Set cmd to the non-digit + * which terminates the number. */ +static struct number_command read_command(struct more_control *ctl) +{ + cc_t input[8] = { 0 }; + ssize_t i, ilen; + struct number_command cmd = { .key = more_kc_unknown_command }; + + /* See stderr note in read_user_input() */ + if ((ilen = read(STDERR_FILENO, &input, sizeof(input))) <= 0) + return cmd; + if (2 < ilen) { + if (!memcmp(input, ARROW_UP, sizeof(ARROW_UP))) { + cmd.key = more_kc_backwards; + return cmd; + } else if (!memcmp(input, ARROW_DOWN, sizeof(ARROW_DOWN))) { + cmd.key = more_kc_jump_lines_per_screen; + return cmd; + } else if (!memcmp(input, PAGE_UP, sizeof(PAGE_UP))) { + cmd.key = more_kc_backwards; + return cmd; + } else if (!memcmp(input, PAGE_DOWN, sizeof(PAGE_DOWN))) { + cmd.key = more_kc_jump_lines_per_screen; + return cmd; + } + } + for (i = 0; i < ilen; i++) { + if (isdigit(input[i])) { + if (0 < ctl->reading_num) { + ctl->leading_number *= 10; + ctl->leading_number += input[i] - '0'; + } else + ctl->leading_number = input[i] - '0'; + ctl->reading_num = 1; + continue; + } + cmd.number = ctl->leading_number; + ctl->reading_num = 0; + ctl->leading_number = 0; + if (ctl->leading_colon) { + ctl->leading_colon = 0; + switch (input[i]) { + case 'f': + cmd.key = more_kc_display_file_and_line; + return cmd; + case 'n': + cmd.key = more_kc_next_file; + return cmd; + case 'p': + cmd.key = more_kc_previous_file; + return cmd; + default: + cmd.key = more_kc_unknown_command; + return cmd; + } + } + /* command is a single char */ + switch (input[i]) { + case '.': + cmd.key = more_kc_repeat_previous; + break; + case ':': + ctl->leading_colon = 1; + break; + case 'b': + case CTRL('B'): + cmd.key = more_kc_backwards; + break; + case ' ': + cmd.key = more_kc_jump_lines_per_screen; + break; + case 'z': + cmd.key = more_kc_set_lines_per_screen; + break; + case 'd': + case CTRL('D'): + cmd.key = more_kc_set_scroll_len; + break; + case 'q': + case 'Q': + cmd.key = more_kc_quit; + return cmd; + break; + case 'f': + case CTRL('F'): + cmd.key = more_kc_skip_forward_screen; + break; + case 's': + cmd.key = more_kc_skip_forward_line; + break; + case '\n': + cmd.key = more_kc_next_line; + break; + case '\f': + cmd.key = more_kc_clear_screen; + break; + case '\'': + cmd.key = more_kc_previous_search_match; + break; + case '=': + cmd.key = more_kc_display_line; + break; + case 'n': + cmd.key = more_kc_repeat_search; + break; + case '/': + cmd.key = more_kc_search; + break; + case '!': + cmd.key = more_kc_run_shell; + break; + case '?': + case 'h': + cmd.key = more_kc_help; + break; + case 'v': + cmd.key = more_kc_run_editor; + break; + } + } + return cmd; +} + +/* Change displayed file from command line list to next nskip, where nskip + * is relative position in argv and can be negative, that is a previous + * file. */ +static void change_file(struct more_control *ctl, int nskip) +{ + if (nskip == 0) + return; + if (nskip > 0) { + if (ctl->argv_position + nskip > ctl->num_files - 1) + nskip = ctl->num_files - ctl->argv_position - 1; + } + ctl->argv_position += nskip; + if (ctl->argv_position < 0) + ctl->argv_position = 0; + puts(_("\n...Skipping ")); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + if (nskip > 0) + fputs(_("...Skipping to file "), stdout); + else + fputs(_("...Skipping back to file "), stdout); + puts(ctl->file_names[ctl->argv_position]); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + putchar('\n'); + ctl->argv_position--; +} + +static void show(struct more_control *ctl, char c) +{ + if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) { + c += (c == CERASE) ? -0100 : 0100; + fputs(CARAT, stderr); + ctl->prompt_len++; + } + fputc(c, stderr); + ctl->prompt_len++; +} + +static void more_error(struct more_control *ctl, char *mess) +{ + if (ctl->clear_line_ends) + putp(ctl->erase_line); + else + erase_to_col(ctl, 0); + ctl->prompt_len += strlen(mess); + if (ctl->enter_std) + putp(ctl->enter_std); + fputs(mess, stdout); + if (ctl->exit_std) + putp(ctl->exit_std); + fflush(NULL); + ctl->report_errors++; +} + +static void erase_one_column(struct more_control *ctl) +{ + if (ctl->erase_previous_ok) + fprintf(stderr, "%s ", ctl->backspace_ch); + fputs(ctl->backspace_ch, stderr); +} + +static void ttyin(struct more_control *ctl, char buf[], int nmax, char pchar) +{ + char *sp; + cc_t c; + int slash = 0; + int maxlen; + + sp = buf; + maxlen = 0; + while (sp - buf < nmax) { + if (ctl->prompt_len > maxlen) + maxlen = ctl->prompt_len; + c = read_user_input(ctl); + if (c == '\\') { + slash++; + } else if (c == ctl->output_tty.c_cc[VERASE] && !slash) { + if (sp > buf) { +#ifdef HAVE_WIDECHAR + if (MB_CUR_MAX > 1) { + wchar_t wc; + size_t pos = 0, mblength; + mbstate_t state, state_bak; + + memset(&state, '\0', sizeof(mbstate_t)); + + while (1) { + state_bak = state; + mblength = + mbrtowc(&wc, buf + pos, + sp - buf, &state); + + switch (mblength) { + case (size_t)-2: + case (size_t)-1: + state = state_bak; + /* fallthrough */ + case 0: + mblength = 1; + } + if (buf + pos + mblength >= sp) + break; + + pos += mblength; + } + + if (mblength == 1) { + erase_one_column(ctl); + } else { + int wc_width; + wc_width = wcwidth(wc); + wc_width = + (wc_width < + 1) ? 1 : wc_width; + while (wc_width--) { + erase_one_column(ctl); + } + } + + while (mblength--) { + --ctl->prompt_len; + --sp; + } + } else +#endif /* HAVE_WIDECHAR */ + { + --ctl->prompt_len; + erase_one_column(ctl); + --sp; + } + + if ((*sp < ' ' && *sp != '\n') || *sp == CERASE) { + --ctl->prompt_len; + erase_one_column(ctl); + } + continue; + } + if (!ctl->erase_line) + ctl->prompt_len = maxlen; + } else if (c == ctl->output_tty.c_cc[VKILL] && !slash) { + if (ctl->hard_tty) { + show(ctl, c); + putchar('\n'); + putchar(pchar); + } else { + putchar('\r'); + putchar(pchar); + if (ctl->erase_line) + erase_to_col(ctl, 1); + else if (ctl->erase_input_ok) + while (ctl->prompt_len-- > 1) + fprintf(stderr, "%s %s", ctl->backspace_ch, ctl->backspace_ch); + ctl->prompt_len = 1; + } + sp = buf; + fflush(NULL); + continue; + } + if (slash && (c == ctl->output_tty.c_cc[VKILL] || + c == ctl->output_tty.c_cc[VERASE])) { + erase_one_column(ctl); + --sp; + } + if (c != '\\') + slash = 0; + *sp++ = c; + if ((c < ' ' && c != '\n' && c != ESC) || c == CERASE) { + c += (c == CERASE) ? -0100 : 0100; + fputs(CARAT, stderr); + ctl->prompt_len++; + } + if (c != '\n' && c != ESC) { + fputc(c, stderr); + ctl->prompt_len++; + } else + break; + } + *--sp = '\0'; + if (!ctl->erase_line) + ctl->prompt_len = maxlen; + if (sp - buf >= nmax - 1) + more_error(ctl, _("Line too long")); +} + +/* Expand shell command line. */ +static void expand(struct more_control *ctl, char *inbuf) +{ + char *inpstr; + char *outstr; + char c; + char *temp; + int tempsz, xtra = 0, offset; + + if (!ctl->no_tty_in) + xtra += strlen(ctl->file_names[ctl->argv_position]) + 1; + if (ctl->shell_line) + xtra += strlen(ctl->shell_line) + 1; + + tempsz = COMMAND_BUF + xtra; + temp = xmalloc(tempsz); + inpstr = inbuf; + outstr = temp; + + while ((c = *inpstr++) != '\0') { + offset = outstr - temp; + if (tempsz - offset - 1 < xtra) { + tempsz += COMMAND_BUF + xtra; + temp = xrealloc(temp, tempsz); + outstr = temp + offset; + } + switch (c) { + case '%': + if (!ctl->no_tty_in) { + strcpy(outstr, ctl->file_names[ctl->argv_position]); + outstr += strlen(ctl->file_names[ctl->argv_position]); + } else + *outstr++ = c; + break; + case '!': + if (ctl->shell_line) { + strcpy(outstr, ctl->shell_line); + outstr += strlen(ctl->shell_line); + } else + more_error(ctl, _ + ("No previous command to substitute for")); + break; + case '\\': + if (*inpstr == '%' || *inpstr == '!') { + *outstr++ = *inpstr++; + break; + } + /* fallthrough */ + default: + *outstr++ = c; + } + } + *outstr++ = '\0'; + free(ctl->shell_line); + ctl->shell_line = temp; +} + +static void set_tty(struct more_control *ctl) +{ + ctl->output_tty.c_lflag &= ~(ICANON | ECHO); + ctl->output_tty.c_cc[VMIN] = 1; /* read at least 1 char */ + ctl->output_tty.c_cc[VTIME] = 0; /* no timeout */ + tcsetattr(STDERR_FILENO, TCSANOW, &ctl->output_tty); +} + +/* Come here if a quit signal is received */ +static void sigquit_handler(struct more_control *ctl) +{ + if (!ctl->dumb_tty && ctl->no_quit_dialog) { + ctl->prompt_len += fprintf(stderr, _("[Use q or Q to quit]")); + ctl->no_quit_dialog = 0; + } else + more_exit(ctl); +} + +/* Come here when we get a suspend signal from the terminal */ +static void sigtstp_handler(struct more_control *ctl) +{ + reset_tty(ctl); + fflush(NULL); + kill(getpid(), SIGSTOP); +} + +/* Come here when we get a continue signal from the terminal */ +static void sigcont_handler(struct more_control *ctl) +{ + set_tty(ctl); +} + +/* Come here if a signal for a window size change is received */ +static void sigwinch_handler(struct more_control *ctl) +{ + struct winsize win; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1) { + if (win.ws_row != 0) { + ctl->lines_per_page = win.ws_row; + ctl->d_scroll_len = ctl->lines_per_page / 2 - 1; + if (ctl->d_scroll_len < 1) + ctl->d_scroll_len = 1; + ctl->lines_per_screen = ctl->lines_per_page - 1; + } + if (win.ws_col != 0) + ctl->num_columns = win.ws_col; + } + prepare_line_buffer(ctl); +} + +static void __attribute__((__format__ (__printf__, 3, 4))) + execute(struct more_control *ctl, char *filename, const char *cmd, ...) +{ + pid_t id; + va_list argp; + char *arg; + char **args; + int argcount; + + fflush(NULL); + id = fork(); + if (id == 0) { + int errsv; + if (!isatty(STDIN_FILENO)) { + close(STDIN_FILENO); + ignore_result( open("/dev/tty", 0) ); + } + reset_tty(ctl); + + va_start(argp, cmd); + arg = va_arg(argp, char *); + argcount = 0; + while (arg) { + argcount++; + arg = va_arg(argp, char *); + } + va_end(argp); + + args = alloca(sizeof(char *) * (argcount + 1)); + args[argcount] = NULL; + + va_start(argp, cmd); + arg = va_arg(argp, char *); + argcount = 0; + while (arg) { + args[argcount] = arg; + argcount++; + arg = va_arg(argp, char *); + } + va_end(argp); + + if ((geteuid() != getuid() || getegid() != getgid()) + && drop_permissions() != 0) + err(EXIT_FAILURE, _("drop permissions failed")); + + execvp(cmd, args); + errsv = errno; + fputs(_("exec failed\n"), stderr); + exit(errsv == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED); + } + if (id > 0) { + errno = 0; + while (wait(NULL) > 0) { + if (errno == EINTR) + continue; + } + } else + fputs(_("can't fork\n"), stderr); + set_tty(ctl); + print_separator('-', 24); + output_prompt(ctl, filename); +} + +static void run_shell(struct more_control *ctl, char *filename) +{ + char cmdbuf[COMMAND_BUF]; + + erase_to_col(ctl, 0); + putchar('!'); + fflush(NULL); + if (ctl->previous_command.key == more_kc_run_shell && ctl->shell_line) + fputs(ctl->shell_line, stderr); + else { + ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!'); + if (strpbrk(cmdbuf, "%!\\")) + expand(ctl, cmdbuf); + else { + free(ctl->shell_line); + ctl->shell_line = xstrdup(cmdbuf); + } + } + fputc('\n', stderr); + fflush(NULL); + ctl->prompt_len = 0; + execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0); +} + +/* Skip n lines in the file f */ +static void skip_lines(struct more_control *ctl) +{ + int c; + + while (ctl->next_jump > 0) { + while ((c = more_getc(ctl)) != '\n') + if (c == EOF) + return; + ctl->next_jump--; + ctl->current_line++; + } +} + +/* Clear the screen */ +static void more_clear_screen(struct more_control *ctl) +{ + if (ctl->clear && !ctl->hard_tty) { + putp(ctl->clear); + /* Put out carriage return so that system doesn't get + * confused by escape sequences when expanding tabs */ + putchar('\r'); + ctl->prompt_len = 0; + } +} + +static void read_line(struct more_control *ctl) +{ + int c; + char *p; + + p = ctl->line_buf; + while ((c = more_getc(ctl)) != '\n' && c != EOF + && (ptrdiff_t)p != (ptrdiff_t)(ctl->line_buf + ctl->line_sz - 1)) + *p++ = c; + if (c == '\n') + ctl->current_line++; + *p = '\0'; +} + +static int more_poll(struct more_control *ctl, int timeout) +{ + struct pollfd pfd[2]; + + pfd[0].fd = ctl->sigfd; + pfd[0].events = POLLIN | POLLERR | POLLHUP; + pfd[1].fd = STDIN_FILENO; + pfd[1].events = POLLIN; + + if (poll(pfd, 2, timeout) < 0) { + if (errno == EAGAIN) + return 1; + more_error(ctl, _("poll failed")); + return 1; + } + if (pfd[0].revents != 0) { + struct signalfd_siginfo info; + ssize_t sz; + + sz = read(pfd[0].fd, &info, sizeof(info)); + assert(sz == sizeof(info)); + switch (info.ssi_signo) { + case SIGINT: + more_exit(ctl); + break; + case SIGQUIT: + sigquit_handler(ctl); + break; + case SIGTSTP: + sigtstp_handler(ctl); + break; + case SIGCONT: + sigcont_handler(ctl); + break; + case SIGWINCH: + sigwinch_handler(ctl); + break; + default: + abort(); + } + } + if (pfd[1].revents == 0) + return 1; + return 0; +} + +/* Search for nth occurrence of regular expression contained in buf in + * the file */ +static void search(struct more_control *ctl, char buf[], int n) +{ + off_t startline = ctl->file_position; + off_t line1 = startline; + off_t line2 = startline; + off_t line3; + int lncount; + int saveln, rc; + regex_t re; + + if (buf != ctl->previous_search) { + free(ctl->previous_search); + ctl->previous_search = buf; + } + + ctl->search_called = 1; + ctl->context.line_num = saveln = ctl->current_line; + ctl->context.row_num = startline; + lncount = 0; + if (!buf) + goto notfound; + if ((rc = regcomp(&re, buf, REG_NOSUB)) != 0) { + char s[REGERR_BUF]; + regerror(rc, &re, s, sizeof s); + more_error(ctl, s); + return; + } + while (!feof(ctl->current_file)) { + line3 = line2; + line2 = line1; + line1 = ctl->file_position; + read_line(ctl); + lncount++; + if (regexec(&re, ctl->line_buf, 0, NULL, 0) == 0 && --n == 0) { + if ((1 < lncount && ctl->no_tty_in) || 3 < lncount) { + putchar('\n'); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + fputs(_("...skipping\n"), stdout); + } + if (!ctl->no_tty_in) { + ctl->current_line -= (lncount < 3 ? lncount : 3); + more_fseek(ctl, line3); + if (ctl->no_scroll) { + if (ctl->clear_line_ends) { + putp(ctl->go_home); + putp(ctl->erase_line); + } else + more_clear_screen(ctl); + } + } else { + erase_to_col(ctl, 0); + if (ctl->no_scroll) { + if (ctl->clear_line_ends) { + putp(ctl->go_home); + putp(ctl->erase_line); + } else + more_clear_screen(ctl); + } + puts(ctl->line_buf); + } + break; + } + more_poll(ctl, 1); + } + /* Move ctrl+c signal handling back to more_key_command(). */ + signal(SIGINT, SIG_DFL); + sigaddset(&ctl->sigset, SIGINT); + sigprocmask(SIG_BLOCK, &ctl->sigset, NULL); + regfree(&re); + if (feof(ctl->current_file)) { + if (!ctl->no_tty_in) { + ctl->current_line = saveln; + more_fseek(ctl, startline); + } else { + fputs(_("\nPattern not found\n"), stdout); + more_exit(ctl); + } +notfound: + more_error(ctl, _("Pattern not found")); + } +} + +static char *find_editor(void) +{ + static char *editor; + + editor = getenv("VISUAL"); + if (editor == NULL || *editor == '\0') + editor = getenv("EDITOR"); + if (editor == NULL || *editor == '\0') + editor = _PATH_VI; + return editor; +} + +static void runtime_usage(void) +{ + fputs(_("Most commands optionally preceded by integer argument k. " + "Defaults in brackets.\n" + "Star (*) indicates argument becomes new default.\n"), stdout); + print_separator('-', 79); + fprintf(stdout, + _ + ("<space> Display next k lines of text [current screen size]\n" + "z Display next k lines of text [current screen size]*\n" + "<return> Display next k lines of text [1]*\n" + "d or ctrl-D Scroll k lines [current scroll size, initially 11]*\n" + "q or Q or <interrupt> Exit from more\n" + "s Skip forward k lines of text [1]\n" + "f Skip forward k screenfuls of text [1]\n" + "b or ctrl-B Skip backwards k screenfuls of text [1]\n" + "' Go to place where previous search started\n" + "= Display current line number\n" + "/<regular expression> Search for kth occurrence of regular expression [1]\n" + "n Search for kth occurrence of last r.e [1]\n" + "!<cmd> or :!<cmd> Execute <cmd> in a subshell\n" + "v Start up '%s' at current line\n" + "ctrl-L Redraw screen\n" + ":n Go to kth next file [1]\n" + ":p Go to kth previous file [1]\n" + ":f Display current file name and line number\n" + ". Repeat previous command\n"), + find_editor()); + print_separator('-', 79); +} + +static void execute_editor(struct more_control *ctl, char *cmdbuf, size_t buflen, char *filename) +{ + char *editor, *p; + int split = 0; + int n; + + if ((ctl->current_line - ctl->lines_per_screen) < 1) + n = 1; + else + n = ctl->current_line - (ctl->lines_per_screen + 1) / 2; + editor = find_editor(); + p = strrchr(editor, '/'); + if (p) + p++; + else + p = editor; + /* + * Earlier: call vi +n file. This also works for emacs. + * POSIX: call vi -c n file (when editor is vi or ex). + */ + if (!strcmp(p, "vi") || !strcmp(p, "ex")) { + snprintf(cmdbuf, buflen, "-c %d", n); + split = 1; + } else + snprintf(cmdbuf, buflen, "+%d", n); + + erase_to_col(ctl, 0); + printf("%s %s %s", editor, cmdbuf, ctl->file_names[ctl->argv_position]); + if (split) { + cmdbuf[2] = 0; + execute(ctl, filename, editor, editor, + cmdbuf, cmdbuf + 3, + ctl->file_names[ctl->argv_position], (char *)0); + } else + execute(ctl, filename, editor, editor, + cmdbuf, ctl->file_names[ctl->argv_position], (char *)0); +} + +static int skip_backwards(struct more_control *ctl, int nlines) +{ + if (nlines == 0) + nlines++; + erase_to_col(ctl, 0); + printf(P_("...back %d page", "...back %d pages", nlines), nlines); + putchar('\n'); + ctl->next_jump = ctl->current_line - (ctl->lines_per_screen * (nlines + 1)) - 1; + if (ctl->next_jump < 0) + ctl->next_jump = 0; + more_fseek(ctl, 0); + ctl->current_line = 0; + skip_lines(ctl); + return ctl->lines_per_screen; +} + +static int skip_forwards(struct more_control *ctl, int nlines, cc_t comchar) +{ + int c; + + if (nlines == 0) + nlines++; + if (comchar == 'f') + nlines *= ctl->lines_per_screen; + putchar('\r'); + erase_to_col(ctl, 0); + putchar('\n'); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + printf(P_("...skipping %d line", + "...skipping %d lines", nlines), nlines); + + if (ctl->clear_line_ends) + putp(ctl->erase_line); + putchar('\n'); + + while (nlines > 0) { + while ((c = more_getc(ctl)) != '\n') + if (c == EOF) + return 0; + ctl->current_line++; + nlines--; + } + return 1; +} + +/* Read a command and do it. A command consists of an optional integer + * argument followed by the command character. Return the number of + * lines to display in the next screenful. If there is nothing more to + * display in the current file, zero is returned. */ +static int more_key_command(struct more_control *ctl, char *filename) +{ + int retval = 0; + int done = 0, search_again = 0; + char cmdbuf[INIT_BUF]; + struct number_command cmd; + + if (!ctl->report_errors) + output_prompt(ctl, filename); + else + ctl->report_errors = 0; + ctl->search_called = 0; + for (;;) { + if (more_poll(ctl, -1) != 0) + continue; + cmd = read_command(ctl); + if (cmd.key == more_kc_unknown_command) + continue; + if (cmd.key == more_kc_repeat_previous) + cmd = ctl->previous_command; + switch (cmd.key) { + case more_kc_backwards: + if (ctl->no_tty_in) { + fprintf(stderr, "\a"); + return -1; + } + retval = skip_backwards(ctl, cmd.number); + done = 1; + break; + case more_kc_jump_lines_per_screen: + case more_kc_set_lines_per_screen: + if (cmd.number == 0) + cmd.number = ctl->lines_per_screen; + else if (cmd.key == more_kc_set_lines_per_screen) + ctl->lines_per_screen = cmd.number; + retval = cmd.number; + done = 1; + break; + case more_kc_set_scroll_len: + if (cmd.number != 0) + ctl->d_scroll_len = cmd.number; + retval = ctl->d_scroll_len; + done = 1; + break; + case more_kc_quit: + more_exit(ctl); + case more_kc_skip_forward_screen: + if (skip_forwards(ctl, cmd.number, 'f')) + retval = ctl->lines_per_screen; + done = 1; + break; + case more_kc_skip_forward_line: + if (skip_forwards(ctl, cmd.number, 's')) + retval = ctl->lines_per_screen; + done = 1; + break; + case more_kc_next_line: + if (cmd.number != 0) + ctl->lines_per_screen = cmd.number; + else + cmd.number = 1; + retval = cmd.number; + done = 1; + break; + case more_kc_clear_screen: + if (!ctl->no_tty_in) { + more_clear_screen(ctl); + more_fseek(ctl, ctl->screen_start.row_num); + ctl->current_line = ctl->screen_start.line_num; + retval = ctl->lines_per_screen; + done = 1; + break; + } else { + fprintf(stderr, "\a"); + break; + } + case more_kc_previous_search_match: + if (!ctl->no_tty_in) { + erase_to_col(ctl, 0); + fputs(_("\n***Back***\n\n"), stdout); + more_fseek(ctl, ctl->context.row_num); + ctl->current_line = ctl->context.line_num; + retval = ctl->lines_per_screen; + done = 1; + break; + } else { + fprintf(stderr, "\a"); + break; + } + case more_kc_display_line: + erase_to_col(ctl, 0); + ctl->prompt_len = printf("%d", ctl->current_line); + fflush(NULL); + break; + case more_kc_display_file_and_line: + erase_to_col(ctl, 0); + if (!ctl->no_tty_in) + ctl->prompt_len = + printf(_("\"%s\" line %d"), + ctl->file_names[ctl->argv_position], ctl->current_line); + else + ctl->prompt_len = printf(_("[Not a file] line %d"), + ctl->current_line); + fflush(NULL); + break; + case more_kc_repeat_search: + if (!ctl->previous_search) { + more_error(ctl, _("No previous regular expression")); + break; + } + search_again = 1; + /* fallthrough */ + case more_kc_search: + if (cmd.number == 0) + cmd.number++; + erase_to_col(ctl, 0); + putchar('/'); + ctl->prompt_len = 1; + fflush(NULL); + if (search_again) { + fputc('\r', stderr); + search(ctl, ctl->previous_search, cmd.number); + search_again = 0; + } else { + ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/'); + fputc('\r', stderr); + ctl->next_search = xstrdup(cmdbuf); + search(ctl, ctl->next_search, cmd.number); + } + retval = ctl->lines_per_screen - 1; + done = 1; + break; + case more_kc_run_shell: + run_shell(ctl, filename); + break; + case more_kc_help: + if (ctl->no_scroll) + more_clear_screen(ctl); + erase_to_col(ctl, 0); + runtime_usage(); + output_prompt(ctl, filename); + break; + case more_kc_next_file: + putchar('\r'); + erase_to_col(ctl, 0); + if (cmd.number == 0) + cmd.number = 1; + if (ctl->argv_position + cmd.number >= (unsigned int)ctl->num_files) + more_exit(ctl); + change_file(ctl, cmd.number); + done = 1; + break; + case more_kc_previous_file: + if (ctl->no_tty_in) { + fprintf(stderr, "\a"); + break; + } + putchar('\r'); + erase_to_col(ctl, 0); + if (cmd.number == 0) + cmd.number = 1; + change_file(ctl, -cmd.number); + done = 1; + break; + case more_kc_run_editor: /* This case should go right before default */ + if (!ctl->no_tty_in) { + execute_editor(ctl, cmdbuf, sizeof(cmdbuf), filename); + break; + } + /* fallthrough */ + default: + if (ctl->suppress_bell) { + erase_to_col(ctl, 0); + if (ctl->enter_std) + putp(ctl->enter_std); + ctl->prompt_len = + printf(_("[Press 'h' for instructions.]")) + + 2 * ctl->stdout_glitch; + if (ctl->exit_std) + putp(ctl->exit_std); + } else + fprintf(stderr, "\a"); + fflush(NULL); + break; + } + ctl->previous_command = cmd; + if (done) { + cmd.key = more_kc_unknown_command; + break; + } + } + putchar('\r'); + ctl->no_quit_dialog = 1; + return retval; +} + +/* Print out the contents of the file f, one screenful at a time. */ +static void screen(struct more_control *ctl, int num_lines) +{ + int c; + int nchars; + int length; /* length of current line */ + static int prev_len = 1; /* length of previous line */ + + for (;;) { + while (num_lines > 0 && !ctl->is_paused) { + nchars = get_line(ctl, &length); + ctl->is_eof = nchars == EOF; + if (ctl->is_eof && ctl->exit_on_eof) { + if (ctl->clear_line_ends) + putp(ctl->clear_rest); + return; + } + if (ctl->squeeze_spaces && length == 0 && prev_len == 0 && !ctl->is_eof) + continue; + prev_len = length; + if (ctl->bad_stdout + || ((ctl->enter_std && *ctl->enter_std == ' ') && (ctl->prompt_len > 0))) + erase_to_col(ctl, 0); + /* must clear before drawing line since tabs on + * some terminals do not erase what they tab + * over. */ + if (ctl->clear_line_ends) + putp(ctl->erase_line); + fwrite(ctl->line_buf, length, 1, stdout); + if (nchars < ctl->prompt_len) + erase_to_col(ctl, nchars); + ctl->prompt_len = 0; + if (nchars < ctl->num_columns || !ctl->fold_long_lines) + putchar('\n'); + num_lines--; + } + fflush(NULL); + + c = more_getc(ctl); + ctl->is_eof = c == EOF; + + if (ctl->is_eof && ctl->exit_on_eof) { + if (ctl->clear_line_ends) + putp(ctl->clear_rest); + return; + } + + if (ctl->is_paused && ctl->clear_line_ends) + putp(ctl->clear_rest); + more_ungetc(ctl, c); + ctl->is_paused = 0; + do { + if ((num_lines = more_key_command(ctl, NULL)) == 0) + return; + } while (ctl->search_called && !ctl->previous_search); + if (ctl->hard_tty && ctl->prompt_len > 0) + erase_to_col(ctl, 0); + if (ctl->no_scroll && num_lines >= ctl->lines_per_screen) { + if (ctl->clear_line_ends) + putp(ctl->go_home); + else + more_clear_screen(ctl); + } + ctl->screen_start.line_num = ctl->current_line; + ctl->screen_start.row_num = ctl->file_position; + } +} + +static void copy_file(FILE *f) +{ + char buf[BUFSIZ]; + size_t sz; + + while ((sz = fread(&buf, sizeof(char), sizeof(buf), f)) > 0) + fwrite(&buf, sizeof(char), sz, stdout); +} + + +static void display_file(struct more_control *ctl, int left) +{ + if (!ctl->current_file) + return; + ctl->context.line_num = ctl->context.row_num = 0; + ctl->current_line = 0; + if (ctl->first_file) { + ctl->first_file = 0; + if (ctl->next_jump) + skip_lines(ctl); + if (ctl->search_at_start) { + search(ctl, ctl->next_search, 1); + if (ctl->no_scroll) + left--; + } + } else if (ctl->argv_position < ctl->num_files && !ctl->no_tty_out) + left = + more_key_command(ctl, ctl->file_names[ctl->argv_position]); + if (left != 0) { + if ((ctl->no_scroll || ctl->clear_first) + && 0 < ctl->file_size) { + if (ctl->clear_line_ends) + putp(ctl->go_home); + else + more_clear_screen(ctl); + } + if (ctl->print_banner) { + if (ctl->bad_stdout) + erase_to_col(ctl, 0); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + if (ctl->prompt_len > 14) + erase_to_col(ctl, 14); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + print_separator(':', 14); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + puts(ctl->file_names[ctl->argv_position]); + if (ctl->clear_line_ends) + putp(ctl->erase_line); + print_separator(':', 14); + if (left > ctl->lines_per_page - 4) + left = ctl->lines_per_page - 4; + } + if (ctl->no_tty_out) + copy_file(ctl->current_file); + else + screen(ctl, left); + } + fflush(NULL); + fclose(ctl->current_file); + ctl->current_file = NULL; + ctl->screen_start.line_num = ctl->screen_start.row_num = 0; + ctl->context.line_num = ctl->context.row_num = 0L; +} + +static void initterm(struct more_control *ctl) +{ + int ret; + char *term; + struct winsize win; + char *cursor_addr; + +#ifndef NON_INTERACTIVE_MORE + ctl->no_tty_out = tcgetattr(STDOUT_FILENO, &ctl->output_tty); +#endif + ctl->no_tty_in = tcgetattr(STDIN_FILENO, &ctl->output_tty); + ctl->no_tty_err = tcgetattr(STDERR_FILENO, &ctl->output_tty); + ctl->original_tty = ctl->output_tty; + + ctl->hard_tabs = (ctl->output_tty.c_oflag & TABDLY) != TAB3; + if (ctl->no_tty_out) + return; + + ctl->output_tty.c_lflag &= ~(ICANON | ECHO); + ctl->output_tty.c_cc[VMIN] = 1; + ctl->output_tty.c_cc[VTIME] = 0; + ctl->erase_previous_ok = (ctl->output_tty.c_cc[VERASE] != 255); + ctl->erase_input_ok = (ctl->output_tty.c_cc[VKILL] != 255); + if ((term = getenv("TERM")) == NULL) { + ctl->dumb_tty = 1; + } + setupterm(term, 1, &ret); + if (ret <= 0) { + ctl->dumb_tty = 1; + return; + } + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) { + ctl->lines_per_page = tigetnum(TERM_LINES); + ctl->num_columns = tigetnum(TERM_COLS); + } else { + if ((ctl->lines_per_page = win.ws_row) == 0) + ctl->lines_per_page = tigetnum(TERM_LINES); + if ((ctl->num_columns = win.ws_col) == 0) + ctl->num_columns = tigetnum(TERM_COLS); + } + if ((ctl->lines_per_page <= 0) || tigetflag(TERM_HARD_COPY)) { + ctl->hard_tty = 1; + ctl->lines_per_page = LINES_PER_PAGE; + } + + if (tigetflag(TERM_EAT_NEW_LINE)) + /* Eat newline at last column + 1; dec, concept */ + ctl->eat_newline++; + if (ctl->num_columns <= 0) + ctl->num_columns = NUM_COLUMNS; + + ctl->wrap_margin = tigetflag(TERM_AUTO_RIGHT_MARGIN); + ctl->bad_stdout = tigetflag(TERM_CEOL); + ctl->erase_line = tigetstr(TERM_CLEAR_TO_LINE_END); + ctl->clear = tigetstr(TERM_CLEAR); + if ((ctl->enter_std = tigetstr(TERM_STANDARD_MODE)) != NULL) { + ctl->exit_std = tigetstr(TERM_EXIT_STANDARD_MODE); + if (0 < tigetnum(TERM_STD_MODE_GLITCH)) + ctl->stdout_glitch = 1; + } + + cursor_addr = tigetstr(TERM_HOME); + if (cursor_addr == NULL || *cursor_addr == '\0') { + cursor_addr = tigetstr(TERM_CURSOR_ADDRESS); + if (cursor_addr) + cursor_addr = tparm(cursor_addr, 0, 0); + } + if (cursor_addr) + ctl->go_home = xstrdup(cursor_addr); + + if ((ctl->move_line_down = tigetstr(TERM_LINE_DOWN)) == NULL) + ctl->move_line_down = BACKSPACE; + ctl->clear_rest = tigetstr(TERM_CLEAR_TO_SCREEN_END); + if ((ctl->backspace_ch = tigetstr(TERM_BACKSPACE)) == NULL) + ctl->backspace_ch = BACKSPACE; + + if ((ctl->shell = getenv("SHELL")) == NULL) + ctl->shell = _PATH_BSHELL; +} + +int main(int argc, char **argv) +{ + char *s; + int left; + struct more_control ctl = { + .first_file = 1, + .fold_long_lines = 1, + .no_quit_dialog = 1, + .stop_after_formfeed = 1, + .wrap_margin = 1, + .lines_per_page = LINES_PER_PAGE, + .num_columns = NUM_COLUMNS, + .d_scroll_len = SCROLL_LEN, + 0 + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + setlocale(LC_ALL, ""); + + /* Auto set no scroll on when binary is called page */ + if (!(strcmp(program_invocation_short_name, "page"))) + ctl.no_scroll++; + + ctl.exit_on_eof = getenv("POSIXLY_CORRECT") ? 0 : 1; + + if ((s = getenv("MORE")) != NULL) + env_argscan(&ctl, s); + + argscan(&ctl, argc, argv); + + /* clear any inherited settings */ + signal(SIGCHLD, SIG_DFL); + + initterm(&ctl); + + if (ctl.no_tty_err) + /* exit when we cannot read user's input */ + ctl.exit_on_eof = 1; + +#ifdef HAVE_MAGIC + ctl.magic = magic_open(MAGIC_MIME_ENCODING | MAGIC_SYMLINK); + magic_load(ctl.magic, NULL); +#endif + prepare_line_buffer(&ctl); + + ctl.d_scroll_len = ctl.lines_per_page / 2 - 1; + if (ctl.d_scroll_len <= 0) + ctl.d_scroll_len = 1; + + /* allow clear_line_ends only if go_home and erase_line and clear_rest strings are + * defined, and in that case, make sure we are in no_scroll mode */ + if (ctl.clear_line_ends) { + if ((ctl.go_home == NULL) || (*ctl.go_home == '\0') || + (ctl.erase_line == NULL) || (*ctl.erase_line == '\0') || + (ctl.clear_rest == NULL) || (*ctl.clear_rest == '\0')) + ctl.clear_line_ends = 0; + else + ctl.no_scroll = 1; + } + if (ctl.lines_per_screen == 0) + ctl.lines_per_screen = ctl.lines_per_page - 1; + left = ctl.lines_per_screen; + if (ctl.num_files > 1) + ctl.print_banner = 1; + if (!ctl.no_tty_in && ctl.num_files == 0) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } else + ctl.current_file = stdin; + if (!ctl.no_tty_out) { + if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) { + ctl.catch_suspend++; + } + tcsetattr(STDERR_FILENO, TCSANOW, &ctl.output_tty); + } + sigemptyset(&ctl.sigset); + sigaddset(&ctl.sigset, SIGINT); + sigaddset(&ctl.sigset, SIGQUIT); + sigaddset(&ctl.sigset, SIGTSTP); + sigaddset(&ctl.sigset, SIGCONT); + sigaddset(&ctl.sigset, SIGWINCH); + sigprocmask(SIG_BLOCK, &ctl.sigset, NULL); + ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC); + if (ctl.no_tty_in) { + if (ctl.no_tty_out) + copy_file(stdin); + else { + ctl.current_file = stdin; + display_file(&ctl, left); + } + ctl.no_tty_in = 0; + ctl.print_banner = 1; + ctl.first_file = 0; + } + + while (ctl.argv_position < ctl.num_files) { + checkf(&ctl, ctl.file_names[ctl.argv_position]); + display_file(&ctl, left); + ctl.first_file = 0; + ctl.argv_position++; + } + ctl.clear_line_ends = 0; + ctl.prompt_len = 0; + more_exit(&ctl); +} |