summaryrefslogtreecommitdiffstats
path: root/text-utils/more.c
diff options
context:
space:
mode:
Diffstat (limited to 'text-utils/more.c')
-rw-r--r--text-utils/more.c2133
1 files changed, 2133 insertions, 0 deletions
diff --git a/text-utils/more.c b/text-utils/more.c
new file mode 100644
index 0000000..e3e2a43
--- /dev/null
+++ b/text-utils/more.c
@@ -0,0 +1,2133 @@
+/*
+ * 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 "rpmatch.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", _("A file perusal filter for CRT viewing."));
+
+ 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)) {
+ env_argv[env_argc++] = tok;
+ if (size < env_argc) {
+ size *= 2;
+ env_argv = xrealloc(env_argv, sizeof(char *) * size);
+ }
+ }
+
+ 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++;
+
+ 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);
+}