| /* |
| * 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 MERCHANTIBILITY 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> /* for alloca() */ |
| #include <stdarg.h> /* for va_start() etc */ |
| #include <sys/param.h> |
| #include <ctype.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <termios.h> |
| #include <setjmp.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/file.h> |
| #include <sys/wait.h> |
| #include <regex.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 |
| |
| #include "strutils.h" |
| #include "nls.h" |
| #include "xalloc.h" |
| #include "widechar.h" |
| #include "closestream.h" |
| |
| #ifdef TEST_PROGRAM |
| # define NON_INTERACTIVE_MORE 1 |
| #endif |
| |
| #ifndef XTABS |
| # define XTABS TAB3 |
| #endif |
| |
| #define VI "vi" /* found on the user's path */ |
| |
| #define BS "\b" |
| #define BSB "\b \b" |
| #define CARAT "^" |
| #define RINGBELL '\007' |
| |
| #define TBUFSIZ 1024 |
| #define LINSIZ 256 /* minimal Line buffer size */ |
| #define ctrl(letter) (letter & 077) |
| #define RUBOUT '\177' |
| #define ESC '\033' |
| #define QUIT '\034' |
| #define SCROLL_LEN 11 |
| #define LINES_PER_PAGE 24 |
| #define NUM_COLUMNS 80 |
| #define TERMINAL_BUF 4096 |
| #define INIT_BUF 80 |
| #define SHELL_LINE 1000 |
| #define COMMAND_BUF 200 |
| #define REGERR_BUF NUM_COLUMNS |
| #define STOP -10 |
| |
| #define TERM_AUTO_RIGHT_MARGIN "am" |
| #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_ENTER_UNDERLINE "smul" |
| #define TERM_EXIT_STANDARD_MODE "rmso" |
| #define TERM_EXIT_UNDERLINE "rmul" |
| #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_PAD_CHAR "pad" |
| #define TERM_STANDARD_MODE "smso" |
| #define TERM_STD_MODE_GLITCH "xmc" |
| #define TERM_UNDERLINE_CHAR "uc" |
| #define TERM_UNDERLINE "ul" |
| |
| struct more_control { |
| struct termios otty; /* output terminal */ |
| struct termios savetty0; /* original terminal settings */ |
| long file_pos; /* file position */ |
| long file_size; /* file size */ |
| int fnum; /* argv[] position */ |
| int dlines; /* screen size in lines */ |
| int nscroll; /* number of lines scrolled by 'd' */ |
| int promptlen; /* message prompt length */ |
| int Currline; /* line we are currently at */ |
| char **fnames; /* The list of file names */ |
| int nfiles; /* Number of files left to process */ |
| char *shell; /* name of the shell to use */ |
| int shellp; /* does previous shell command exist */ |
| sigjmp_buf restore; /* siglongjmp() destination */ |
| char *Line; /* line buffer */ |
| size_t LineLen; /* size of Line buffer */ |
| int Lpp; /* lines per page */ |
| char *Clear; /* clear screen */ |
| char *eraseln; /* erase line */ |
| char *Senter; /* enter standout mode */ |
| char *Sexit; /* exit standout mode */ |
| char *ULenter; /* enter underline mode */ |
| char *ULexit; /* exit underline mode */ |
| char *chUL; /* underline character */ |
| char *chBS; /* backspace character */ |
| char *Home; /* go to home */ |
| char *cursorm; /* cursor movement */ |
| char cursorhome[40]; /* contains cursor movement to home */ |
| char *EodClr; /* clear rest of screen */ |
| int Mcol; /* number of columns */ |
| char *previousre; /* previous search() buf[] item */ |
| struct { |
| long chrctr; /* row number */ |
| long line; /* line number */ |
| } context, |
| screen_start; |
| char ch; |
| int lastcmd; /* previous more key command */ |
| int lastarg; /* previous key command argument */ |
| int lastcolon; /* is a colon-prefixed key command */ |
| char shell_line[SHELL_LINE]; |
| char PC; /* pad character */ |
| unsigned int |
| bad_so:1, /* true if overwriting does not turn off standout */ |
| catch_susp:1, /* we should catch the SIGTSTP signal */ |
| clreol:1, /* do not scroll, paint each screen from the top */ |
| docrterase:1, /* is erase previous supported */ |
| docrtkill:1, /* is erase input supported */ |
| dumb:1, /* is terminal type known */ |
| dum_opt:1, /* suppress bell */ |
| eatnl:1, /* is newline ignored after 80 cols */ |
| errors:1, /* is an error reported */ |
| firstf:1, /* is the input file the first in list */ |
| fold_opt:1, /* fold long lines */ |
| hard:1, /* is this hard copy terminal (a printer or such) */ |
| hardtabs:1, /* print spaces instead of '\t' */ |
| inwait:1, /* is waiting user input */ |
| lastp:1, /* run previous key command */ |
| no_intty:1, /* is input in interactive mode */ |
| noscroll:1, /* do not scroll, clear the screen and then display text */ |
| notell:1, /* suppress quit dialog */ |
| no_tty:1, /* is output in interactive mode */ |
| Pause:1, /* is output paused */ |
| pstate:1, /* current UL state */ |
| soglitch:1, /* terminal has standout mode glitch */ |
| ssp_opt:1, /* suppress white space */ |
| startup:1, /* is startup completed */ |
| stop_opt:1, /* stop after form feeds */ |
| ulglitch:1, /* terminal has underline mode glitch */ |
| ul_opt:1, /* underline as best we can */ |
| within:1, /* true if we are within a file, false if we are between files */ |
| Wrap:1; /* set if automargins */ |
| }; |
| |
| /* FIXME: global_ctl is used in signal handlers. */ |
| struct more_control *global_ctl; |
| |
| static void putstring(char *s) |
| { |
| tputs(s, fileno(stdout), putchar); /* putp(s); */ |
| } |
| |
| static void __attribute__((__noreturn__)) usage(void) |
| { |
| FILE *out = stdout; |
| fputs(USAGE_HEADER, out); |
| fprintf(out, _(" %s [options] <file>...\n"), program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, out); |
| fputs(_("A file perusal filter for CRT viewing.\n"), out); |
| |
| fputs(USAGE_OPTIONS, out); |
| fputs(_(" -d display help instead of ringing bell\n"), out); |
| fputs(_(" -f count logical rather than screen lines\n"), out); |
| fputs(_(" -l suppress pause after form feed\n"), out); |
| fputs(_(" -c do not scroll, display text and clean line ends\n"), out); |
| fputs(_(" -p do not scroll, clean screen and display text\n"), out); |
| fputs(_(" -s squeeze multiple blank lines into one\n"), out); |
| fputs(_(" -u suppress underlining\n"), out); |
| fputs(_(" -<number> the number of lines per screenful\n"), out); |
| fputs(_(" +<number> display file beginning from line number\n"), out); |
| fputs(_(" +/<string> display file beginning from search string match\n"), out); |
| |
| fputs(USAGE_SEPARATOR, out); |
| printf( " --help %s\n", USAGE_OPTSTR_HELP); |
| printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION); |
| printf(USAGE_MAN_TAIL("more(1)")); |
| exit(EXIT_SUCCESS); |
| } |
| |
| |
| static void argscan(struct more_control *ctl, char *s) |
| { |
| int seen_num = 0; |
| |
| while (*s != '\0') { |
| switch (*s) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| if (!seen_num) { |
| ctl->dlines = 0; |
| seen_num = 1; |
| } |
| ctl->dlines = ctl->dlines * 10 + *s - '0'; |
| break; |
| case 'd': |
| ctl->dum_opt = 1; |
| break; |
| case 'l': |
| ctl->stop_opt = 0; |
| break; |
| case 'f': |
| ctl->fold_opt = 0; |
| break; |
| case 'p': |
| ctl->noscroll = 1; |
| break; |
| case 'c': |
| ctl->clreol = 1; |
| break; |
| case 's': |
| ctl->ssp_opt = 1; |
| break; |
| case 'u': |
| ctl->ul_opt = 0; |
| break; |
| case '-': |
| case ' ': |
| case '\t': |
| break; |
| case 'V': |
| printf(UTIL_LINUX_VERSION); |
| exit(EXIT_SUCCESS); |
| break; |
| default: |
| warnx(_("unknown option -%s"), s); |
| errtryhelp(EXIT_FAILURE); |
| break; |
| } |
| s++; |
| } |
| } |
| |
| static void more_fseek(struct more_control *ctl, FILE *stream, long pos) |
| { |
| ctl->file_pos = pos; |
| fseek(stream, pos, 0); |
| } |
| |
| static int more_getc(struct more_control *ctl, FILE *stream) |
| { |
| ctl->file_pos++; |
| return getc(stream); |
| } |
| |
| static int more_ungetc(struct more_control *ctl, int c, FILE *stream) |
| { |
| ctl->file_pos--; |
| return ungetc(c, stream); |
| } |
| |
| /* force clear to end of line */ |
| static void cleareol(struct more_control *ctl) |
| { |
| putstring(ctl->eraseln); |
| } |
| |
| /* magic -- |
| * check for file magic numbers. This code would best be shared |
| * with the file(1) program or, perhaps, more should not try to be |
| * so smart. */ |
| static int magic(FILE *f, char *fs) |
| { |
| signed char twobytes[2]; |
| |
| /* don't try to look ahead if the input is unseekable */ |
| if (fseek(f, 0L, SEEK_SET)) |
| return 0; |
| |
| if (fread(twobytes, 2, 1, f) == 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(f, 0L, SEEK_SET); /* rewind() not necessary */ |
| 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 FILE *checkf(struct more_control *ctl, register char *fs, int *clearfirst) |
| { |
| struct stat stbuf; |
| register FILE *f; |
| int c; |
| |
| if (stat(fs, &stbuf) == -1) { |
| fflush(stdout); |
| if (ctl->clreol) |
| cleareol(ctl); |
| warn(_("stat of %s failed"), fs); |
| return ((FILE *)NULL); |
| } |
| if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { |
| printf(_("\n*** %s: directory ***\n\n"), fs); |
| return ((FILE *)NULL); |
| } |
| ctl->Currline = 0; |
| ctl->file_pos = 0; |
| if ((f = fopen(fs, "r")) == NULL) { |
| fflush(stdout); |
| warn(_("cannot open %s"), fs); |
| return ((FILE *)NULL); |
| } |
| if (magic(f, fs)) { |
| fclose(f); |
| return ((FILE *)NULL); |
| } |
| fcntl(fileno(f), F_SETFD, FD_CLOEXEC); |
| c = more_getc(ctl, f); |
| *clearfirst = (c == '\f'); |
| more_ungetc(ctl, c, f); |
| if ((ctl->file_size = stbuf.st_size) == 0) |
| ctl->file_size = LONG_MAX; |
| return (f); |
| } |
| |
| static void prepare_line_buffer(struct more_control *ctl) |
| { |
| char *nline; |
| size_t nsz = ctl->Mcol * 4; |
| |
| if (ctl->LineLen >= nsz) |
| return; |
| |
| if (nsz < LINSIZ) |
| nsz = LINSIZ; |
| |
| /* alloc nsz and extra space for \n\0 */ |
| nline = xrealloc(ctl->Line, nsz + 2); |
| ctl->Line = nline; |
| ctl->LineLen = nsz; |
| } |
| |
| /* Get a logical line */ |
| static int get_line(struct more_control *ctl, register FILE *f, int *length) |
| { |
| int c; |
| char *p; |
| int column; |
| static int colflg; |
| |
| #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(). */ |
| long file_pos_bak = ctl->file_pos; |
| |
| memset(&state, '\0', sizeof(mbstate_t)); |
| #endif |
| |
| prepare_line_buffer(ctl); |
| |
| p = ctl->Line; |
| column = 0; |
| c = more_getc(ctl, f); |
| if (colflg && c == '\n') { |
| ctl->Currline++; |
| c = more_getc(ctl, f); |
| } |
| while (p < &ctl->Line[ctl->LineLen - 1]) { |
| #ifdef HAVE_WIDECHAR |
| if (ctl->fold_opt && 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_pos_bak++; |
| |
| if (column >= ctl->Mcol) { |
| more_fseek(ctl, f, file_pos_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->Mcol) { |
| more_fseek(ctl, f, file_pos_bak); |
| break_flag = 1; |
| } else { |
| for (i = 0; p < &ctl->Line[ctl->LineLen - 1] && |
| i < mbc_pos; i++) |
| *p++ = mbc[i]; |
| if (wc_width > 0) |
| column += wc_width; |
| } |
| } |
| |
| if (break_flag || column >= ctl->Mcol) |
| break; |
| |
| c = more_getc(ctl, f); |
| continue; |
| } |
| #endif /* HAVE_WIDECHAR */ |
| if (c == EOF) { |
| if (p > ctl->Line) { |
| *p = '\0'; |
| *length = p - ctl->Line; |
| return (column); |
| } |
| *length = p - ctl->Line; |
| return (EOF); |
| } |
| if (c == '\n') { |
| ctl->Currline++; |
| break; |
| } |
| |
| *p++ = c; |
| #if 0 |
| if (c == '\033') { /* ESC */ |
| c = more_getc(ctl, f); |
| while (c > ' ' && c < '0' && p < &Line[LineLen - 1]) { |
| *p++ = c; |
| c = more_getc(ctl, f); |
| } |
| if (c >= '0' && c < '\177' && p < &Line[LineLen - 1]) { |
| *p++ = c; |
| c = more_getc(ctl, f); |
| continue; |
| } |
| } |
| #endif /* 0 */ |
| if (c == '\t') { |
| if (!ctl->hardtabs || (column < ctl->promptlen && !ctl->hard)) { |
| if (ctl->hardtabs && ctl->eraseln && !ctl->dumb) { |
| column = 1 + (column | 7); |
| putp(ctl->eraseln); |
| ctl->promptlen = 0; |
| } else { |
| for (--p; p < &ctl->Line[ctl->LineLen - 1];) { |
| *p++ = ' '; |
| if ((++column & 7) == 0) |
| break; |
| } |
| if (column >= ctl->promptlen) |
| ctl->promptlen = 0; |
| } |
| } else |
| column = 1 + (column | 7); |
| } else if (c == '\b' && column > 0) { |
| column--; |
| } else if (c == '\r') { |
| int next = more_getc(ctl, f); |
| if (next == '\n') { |
| p--; |
| ctl->Currline++; |
| break; |
| } |
| more_ungetc(ctl, c, f); |
| column = 0; |
| } else if (c == '\f' && ctl->stop_opt) { |
| p[-1] = '^'; |
| *p++ = 'L'; |
| column += 2; |
| ctl->Pause = 1; |
| } else if (c == EOF) { |
| *length = p - ctl->Line; |
| return (column); |
| } else { |
| #ifdef HAVE_WIDECHAR |
| if (ctl->fold_opt && 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_pos_bak = ctl->file_pos - 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->Mcol && ctl->fold_opt) |
| break; |
| #ifdef HAVE_WIDECHAR |
| if (use_mbc_buffer_flag == 0 && p >= &ctl->Line[ctl->LineLen - 1 - 4]) |
| /* don't read another char if there is no space for |
| * whole multibyte sequence */ |
| break; |
| #endif |
| c = more_getc(ctl, f); |
| } |
| if (column >= ctl->Mcol && ctl->Mcol > 0) { |
| if (!ctl->Wrap) { |
| *p++ = '\n'; |
| } |
| } |
| colflg = column == ctl->Mcol && ctl->fold_opt; |
| if (colflg && ctl->eatnl && ctl->Wrap) { |
| *p++ = '\n'; /* simulate normal wrap */ |
| } |
| *length = p - ctl->Line; |
| *p = 0; |
| return (column); |
| } |
| |
| static void clreos(struct more_control *ctl) |
| { |
| putstring(ctl->EodClr); |
| } |
| |
| /* Erase the rest of the prompt, assuming we are starting at column col. */ |
| static void erasep(struct more_control *ctl, register int col) |
| { |
| |
| if (ctl->promptlen == 0) |
| return; |
| if (ctl->hard) { |
| putchar('\n'); |
| } else { |
| if (col == 0) |
| putchar('\r'); |
| if (!ctl->dumb && ctl->eraseln) |
| putstring(ctl->eraseln); |
| else |
| printf("%*s", ctl->promptlen - col, ""); |
| } |
| ctl->promptlen = 0; |
| } |
| |
| #ifdef HAVE_WIDECHAR |
| static UL_ASAN_BLACKLIST size_t xmbrtowc(wchar_t *wc, const char *s, size_t n, |
| mbstate_t *mbstate) |
| { |
| const size_t mblength = mbrtowc(wc, s, n, mbstate); |
| if (mblength == (size_t)-2 || mblength == (size_t)-1) |
| return 1; |
| return mblength; |
| } |
| #endif |
| |
| static int wouldul(char *s, int n) |
| { |
| if (n < 2) |
| return 0; |
| if ((s[0] == '_' && s[1] == '\b') || (s[1] == '\b' && s[2] == '_')) |
| return 1; |
| return 0; |
| } |
| |
| /* Print a buffer of n characters */ |
| static void prbuf(struct more_control *ctl, register char *s, register int n) |
| { |
| register char c; /* next output character */ |
| register int state; /* next output char's UL state */ |
| |
| while (--n >= 0) |
| if (!ctl->ul_opt) |
| putchar(*s++); |
| else { |
| if (*s == ' ' && ctl->pstate == 0 && ctl->ulglitch |
| && wouldul(s + 1, n - 1)) { |
| s++; |
| continue; |
| } |
| if ((state = wouldul(s, n)) != 0) { |
| c = (*s == '_') ? s[2] : *s; |
| n -= 2; |
| s += 3; |
| } else |
| c = *s++; |
| if (state != ctl->pstate) { |
| if (c == ' ' && state == 0 && ctl->ulglitch |
| && wouldul(s, n - 1)) |
| state = 1; |
| else |
| putp(state ? ctl->ULenter : ctl->ULexit); |
| } |
| if (c != ' ' || ctl->pstate == 0 || state != 0 |
| || ctl->ulglitch == 0) |
| #ifdef HAVE_WIDECHAR |
| { |
| wchar_t wc; |
| size_t mblength; |
| mbstate_t mbstate; |
| memset(&mbstate, '\0', sizeof(mbstate_t)); |
| s--; |
| n++; |
| mblength = xmbrtowc(&wc, s, n, &mbstate); |
| while (mblength--) |
| putchar(*s++); |
| n += mblength; |
| } |
| #else |
| putchar(c); |
| #endif /* HAVE_WIDECHAR */ |
| if (state && *ctl->chUL) { |
| fputs(ctl->chBS, stdout); |
| putstring(ctl->chUL); |
| } |
| ctl->pstate = state; |
| } |
| } |
| |
| /* Erase the current line entirely */ |
| static void kill_line(struct more_control *ctl) |
| { |
| erasep(ctl, 0); |
| if (!ctl->eraseln || ctl->dumb) |
| putchar('\r'); |
| } |
| |
| static void prompt(struct more_control *ctl, char *filename) |
| { |
| if (ctl->clreol) |
| cleareol(ctl); |
| else if (ctl->promptlen > 0) |
| kill_line(ctl); |
| if (!ctl->hard) { |
| ctl->promptlen = 0; |
| if (ctl->Senter && ctl->Sexit) { |
| putstring(ctl->Senter); |
| ctl->promptlen += (2 * ctl->soglitch); |
| } |
| if (ctl->clreol) |
| cleareol(ctl); |
| ctl->promptlen += printf(_("--More--")); |
| if (filename != NULL) { |
| ctl->promptlen += printf(_("(Next file: %s)"), filename); |
| } else if (!ctl->no_intty) { |
| ctl->promptlen += |
| printf("(%d%%)", |
| (int)((ctl->file_pos * 100) / ctl->file_size)); |
| } |
| if (ctl->dum_opt) { |
| ctl->promptlen += |
| printf(_("[Press space to continue, 'q' to quit.]")); |
| } |
| if (ctl->Senter && ctl->Sexit) |
| putstring(ctl->Sexit); |
| if (ctl->clreol) |
| clreos(ctl); |
| fflush(stdout); |
| } else |
| fputc(RINGBELL, stderr); |
| ctl->inwait++; |
| } |
| |
| static int ourputch(int c) |
| { |
| return putc(c, stdout); |
| } |
| |
| static void reset_tty(void) |
| { |
| if (global_ctl->no_tty) |
| return; |
| if (global_ctl->pstate) { |
| /* putchar - if that isn't a macro */ |
| tputs(global_ctl->ULexit, fileno(stdout), ourputch); |
| fflush(stdout); |
| global_ctl->pstate = 0; |
| } |
| global_ctl->otty.c_lflag |= ICANON | ECHO; |
| global_ctl->otty.c_cc[VMIN] = global_ctl->savetty0.c_cc[VMIN]; |
| global_ctl->otty.c_cc[VTIME] = global_ctl->savetty0.c_cc[VTIME]; |
| tcsetattr(STDERR_FILENO, TCSANOW, &global_ctl->savetty0); |
| } |
| |
| /* Clean up terminal state and exit. Also come here if interrupt signal received */ |
| static void __attribute__((__noreturn__)) end_it(int dummy __attribute__((__unused__))) |
| { |
| /* May be executed as a signal handler as well as by main process. |
| * |
| * The _exit() may wait for pending I/O for really long time, be sure |
| * that signal handler is not executed in this time to avoid double |
| * de-initialization (free() calls, etc.). |
| */ |
| signal(SIGINT, SIG_IGN); |
| |
| reset_tty(); |
| if (global_ctl->clreol) { |
| putchar('\r'); |
| clreos(global_ctl); |
| fflush(stdout); |
| } else if (!global_ctl->clreol && (global_ctl->promptlen > 0)) { |
| kill_line(global_ctl); |
| fflush(stdout); |
| } else |
| fputc('\n', stderr); |
| free(global_ctl->previousre); |
| free(global_ctl->Line); |
| _exit(EXIT_SUCCESS); |
| } |
| |
| static int readch(struct more_control *ctl) |
| { |
| unsigned char c; |
| |
| errno = 0; |
| if (read(fileno(stderr), &c, 1) <= 0) { |
| if (errno != EINTR) |
| end_it(0); |
| else |
| c = ctl->otty.c_cc[VKILL]; |
| } |
| return (c); |
| } |
| |
| /* Read a decimal number from the terminal. Set cmd to the non-digit |
| * which terminates the number. */ |
| static int number(struct more_control *ctl, char *cmd) |
| { |
| register int i; |
| |
| i = 0; |
| ctl->ch = ctl->otty.c_cc[VKILL]; |
| for (;;) { |
| ctl->ch = readch(ctl); |
| if (isdigit(ctl->ch)) |
| i = i * 10 + ctl->ch - '0'; |
| else if ((cc_t) ctl->ch == ctl->otty.c_cc[VKILL]) |
| i = 0; |
| else { |
| *cmd = ctl->ch; |
| break; |
| } |
| } |
| return (i); |
| } |
| |
| /* Skip nskip files in the file list (from the command line). Nskip may |
| * be negative. */ |
| static void skipf(struct more_control *ctl, register int nskip) |
| { |
| if (nskip == 0) |
| return; |
| if (nskip > 0) { |
| if (ctl->fnum + nskip > ctl->nfiles - 1) |
| nskip = ctl->nfiles - ctl->fnum - 1; |
| } else if (ctl->within) |
| ctl->fnum++; |
| ctl->fnum += nskip; |
| if (ctl->fnum < 0) |
| ctl->fnum = 0; |
| puts(_("\n...Skipping ")); |
| if (ctl->clreol) |
| cleareol(ctl); |
| if (nskip > 0) |
| fputs(_("...Skipping to file "), stdout); |
| else |
| fputs(_("...Skipping back to file "), stdout); |
| puts(ctl->fnames[ctl->fnum]); |
| if (ctl->clreol) |
| cleareol(ctl); |
| putchar('\n'); |
| ctl->fnum--; |
| } |
| |
| static void show(struct more_control *ctl, char c) |
| { |
| if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) { |
| c += (c == RUBOUT) ? -0100 : 0100; |
| fputs(CARAT, stderr); |
| ctl->promptlen++; |
| } |
| fputc(c, stderr); |
| ctl->promptlen++; |
| } |
| |
| static void more_error(struct more_control *ctl, char *mess) |
| { |
| if (ctl->clreol) |
| cleareol(ctl); |
| else |
| kill_line(ctl); |
| ctl->promptlen += strlen(mess); |
| if (ctl->Senter && ctl->Sexit) { |
| putp(ctl->Senter); |
| fputs(mess, stdout); |
| putp(ctl->Sexit); |
| } else |
| fputs(mess, stdout); |
| fflush(stdout); |
| ctl->errors++; |
| siglongjmp(ctl->restore, 1); |
| } |
| |
| static void erase_one_column(struct more_control *ctl) |
| { |
| if (ctl->docrterase) |
| fputs(BSB, stderr); |
| else |
| fputs(BS, stderr); |
| } |
| |
| static void ttyin(struct more_control *ctl, char buf[], register int nmax, char pchar) |
| { |
| char *sp; |
| int c; |
| int slash = 0; |
| int maxlen; |
| |
| sp = buf; |
| maxlen = 0; |
| while (sp - buf < nmax) { |
| if (ctl->promptlen > maxlen) |
| maxlen = ctl->promptlen; |
| c = readch(ctl); |
| if (c == '\\') { |
| slash++; |
| } else if (((cc_t) c == ctl->otty.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); |
| |
| state = (mblength == (size_t)-2 |
| || mblength == |
| (size_t)-1) ? state_bak |
| : state; |
| mblength = |
| (mblength == (size_t)-2 |
| || mblength == (size_t)-1 |
| || mblength == |
| 0) ? 1 : mblength; |
| |
| 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->promptlen; |
| --sp; |
| } |
| } else |
| #endif /* HAVE_WIDECHAR */ |
| { |
| --ctl->promptlen; |
| erase_one_column(ctl); |
| --sp; |
| } |
| |
| if ((*sp < ' ' && *sp != '\n') || *sp == RUBOUT) { |
| --ctl->promptlen; |
| erase_one_column(ctl); |
| } |
| continue; |
| } else { |
| if (!ctl->eraseln) |
| ctl->promptlen = maxlen; |
| siglongjmp(ctl->restore, 1); |
| } |
| } else if (((cc_t) c == ctl->otty.c_cc[VKILL]) && !slash) { |
| if (ctl->hard) { |
| show(ctl, c); |
| putchar('\n'); |
| putchar(pchar); |
| } else { |
| putchar('\r'); |
| putchar(pchar); |
| if (ctl->eraseln) |
| erasep(ctl, 1); |
| else if (ctl->docrtkill) |
| while (ctl->promptlen-- > 1) |
| fputs(BSB, stderr); |
| ctl->promptlen = 1; |
| } |
| sp = buf; |
| fflush(stdout); |
| continue; |
| } |
| if (slash && ((cc_t) c == ctl->otty.c_cc[VKILL] |
| || (cc_t) c == ctl->otty.c_cc[VERASE])) { |
| erase_one_column(ctl); |
| --sp; |
| } |
| if (c != '\\') |
| slash = 0; |
| *sp++ = c; |
| if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) { |
| c += (c == RUBOUT) ? -0100 : 0100; |
| fputs(CARAT, stderr); |
| ctl->promptlen++; |
| } |
| if (c != '\n' && c != ESC) { |
| fputc(c, stderr); |
| ctl->promptlen++; |
| } else |
| break; |
| } |
| *--sp = '\0'; |
| if (!ctl->eraseln) |
| ctl->promptlen = maxlen; |
| if (sp - buf >= nmax - 1) |
| more_error(ctl, _("Line too long")); |
| } |
| |
| /* return: 0 - unchanged, 1 - changed, -1 - overflow (unchanged) */ |
| static int expand(struct more_control *ctl, char **outbuf, char *inbuf) |
| { |
| char *inpstr; |
| char *outstr; |
| char c; |
| char *temp; |
| int changed = 0; |
| int tempsz, xtra, offset; |
| |
| xtra = strlen(ctl->fnames[ctl->fnum]) + strlen(ctl->shell_line) + 1; |
| tempsz = 200 + xtra; |
| temp = xmalloc(tempsz); |
| inpstr = inbuf; |
| outstr = temp; |
| while ((c = *inpstr++) != '\0') { |
| offset = outstr - temp; |
| if (tempsz - offset - 1 < xtra) { |
| tempsz += 200 + xtra; |
| temp = xrealloc(temp, tempsz); |
| outstr = temp + offset; |
| } |
| switch (c) { |
| case '%': |
| if (!ctl->no_intty) { |
| strcpy(outstr, ctl->fnames[ctl->fnum]); |
| outstr += strlen(ctl->fnames[ctl->fnum]); |
| changed++; |
| } else |
| *outstr++ = c; |
| break; |
| case '!': |
| if (!ctl->shellp) |
| more_error(ctl, _ |
| ("No previous command to substitute for")); |
| strcpy(outstr, ctl->shell_line); |
| outstr += strlen(ctl->shell_line); |
| changed++; |
| break; |
| case '\\': |
| if (*inpstr == '%' || *inpstr == '!') { |
| *outstr++ = *inpstr++; |
| break; |
| } |
| /* fallthrough */ |
| default: |
| *outstr++ = c; |
| } |
| } |
| *outstr++ = '\0'; |
| *outbuf = temp; |
| return (changed); |
| } |
| |
| static void set_tty(struct more_control *ctl) |
| { |
| ctl->otty.c_lflag &= ~(ICANON | ECHO); |
| ctl->otty.c_cc[VMIN] = 1; /* read at least 1 char */ |
| ctl->otty.c_cc[VTIME] = 0; /* no timeout */ |
| tcsetattr(STDERR_FILENO, TCSANOW, &ctl->otty); |
| } |
| |
| /* Come here if a quit signal is received */ |
| static void onquit(int dummy __attribute__((__unused__))) |
| { |
| signal(SIGQUIT, SIG_IGN); |
| if (!global_ctl->inwait) { |
| putchar('\n'); |
| if (!global_ctl->startup) { |
| signal(SIGQUIT, onquit); |
| siglongjmp(global_ctl->restore, 1); |
| } else |
| global_ctl->Pause = 1; |
| } else if (!global_ctl->dum_opt && global_ctl->notell) { |
| global_ctl->promptlen += fprintf(stderr, _("[Use q or Q to quit]")); |
| global_ctl->notell = 0; |
| } |
| signal(SIGQUIT, onquit); |
| } |
| |
| /* Come here when we get a suspend signal from the terminal */ |
| static void onsusp(int dummy __attribute__((__unused__))) |
| { |
| sigset_t signals, oldmask; |
| |
| /* ignore SIGTTOU so we don't get stopped if csh grabs the tty */ |
| signal(SIGTTOU, SIG_IGN); |
| reset_tty(); |
| fflush(stdout); |
| signal(SIGTTOU, SIG_DFL); |
| /* Send the TSTP signal to suspend our process group */ |
| signal(SIGTSTP, SIG_DFL); |
| |
| /* unblock SIGTSTP or we won't be able to suspend ourself */ |
| sigemptyset(&signals); |
| sigaddset(&signals, SIGTSTP); |
| sigprocmask(SIG_UNBLOCK, &signals, &oldmask); |
| |
| kill(0, SIGTSTP); |
| /* Pause for station break */ |
| |
| sigprocmask(SIG_SETMASK, &oldmask, NULL); |
| |
| /* We're back */ |
| signal(SIGTSTP, onsusp); |
| set_tty(global_ctl); |
| if (global_ctl->inwait) |
| siglongjmp(global_ctl->restore, 1); |
| } |
| |
| static void execute(struct more_control *ctl, char *filename, char *cmd, ...) |
| { |
| int id; |
| int n; |
| va_list argp; |
| char *arg; |
| char **args; |
| int argcount; |
| |
| fflush(stdout); |
| reset_tty(); |
| for (n = 10; (id = fork()) < 0 && n > 0; n--) |
| sleep(5); |
| if (id == 0) { |
| int errsv; |
| if (!isatty(0)) { |
| close(0); |
| open("/dev/tty", 0); |
| } |
| |
| 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); |
| |
| execvp(cmd, args); |
| errsv = errno; |
| fputs(_("exec failed\n"), stderr); |
| exit(errsv == ENOENT ? EX_EXEC_ENOENT : EX_EXEC_FAILED); |
| } |
| if (id > 0) { |
| signal(SIGINT, SIG_IGN); |
| signal(SIGQUIT, SIG_IGN); |
| if (ctl->catch_susp) |
| signal(SIGTSTP, SIG_DFL); |
| while (wait(NULL) > 0) ; |
| signal(SIGINT, end_it); |
| signal(SIGQUIT, onquit); |
| if (ctl->catch_susp) |
| signal(SIGTSTP, onsusp); |
| } else |
| fputs(_("can't fork\n"), stderr); |
| set_tty(ctl); |
| puts("------------------------"); |
| prompt(ctl, filename); |
| } |
| |
| static void do_shell(struct more_control *ctl, char *filename) |
| { |
| char cmdbuf[COMMAND_BUF]; |
| int rc; |
| char *expanded; |
| |
| kill_line(ctl); |
| putchar('!'); |
| fflush(stdout); |
| ctl->promptlen = 1; |
| if (ctl->lastp) |
| fputs(ctl->shell_line, stdout); |
| else { |
| ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!'); |
| expanded = NULL; |
| rc = expand(ctl, &expanded, cmdbuf); |
| if (expanded) { |
| if (strlen(expanded) < sizeof(ctl->shell_line)) |
| strcpy(ctl->shell_line, expanded); |
| else |
| rc = -1; |
| free(expanded); |
| } |
| if (rc < 0) { |
| fputs(_(" Overflow\n"), stderr); |
| prompt(ctl, filename); |
| return; |
| } else if (rc > 0) { |
| kill_line(ctl); |
| ctl->promptlen = printf("!%s", ctl->shell_line); |
| } |
| } |
| fflush(stdout); |
| fputc('\n', stderr); |
| ctl->promptlen = 0; |
| ctl->shellp = 1; |
| execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0); |
| } |
| |
| /* Execute a colon-prefixed command. Returns <0 if not a command that |
| * should cause more of the file to be printed. */ |
| static int colon(struct more_control *ctl, char *filename, int cmd, int nlines) |
| { |
| if (cmd == 0) |
| ctl->ch = readch(ctl); |
| else |
| ctl->ch = cmd; |
| ctl->lastcolon = ctl->ch; |
| switch (ctl->ch) { |
| case 'f': |
| kill_line(ctl); |
| if (!ctl->no_intty) |
| ctl->promptlen = |
| printf(_("\"%s\" line %d"), ctl->fnames[ctl->fnum], ctl->Currline); |
| else |
| ctl->promptlen = printf(_("[Not a file] line %d"), ctl->Currline); |
| fflush(stdout); |
| return (-1); |
| case 'n': |
| if (nlines == 0) { |
| if (ctl->fnum >= ctl->nfiles - 1) |
| end_it(0); |
| nlines++; |
| } |
| putchar('\r'); |
| erasep(ctl, 0); |
| skipf(ctl, nlines); |
| return (0); |
| case 'p': |
| if (ctl->no_intty) { |
| fputc(RINGBELL, stderr); |
| return (-1); |
| } |
| putchar('\r'); |
| erasep(ctl, 0); |
| if (nlines == 0) |
| nlines++; |
| skipf(ctl, -nlines); |
| return (0); |
| case '!': |
| do_shell(ctl, filename); |
| return (-1); |
| case 'q': |
| case 'Q': |
| end_it(0); |
| default: |
| fputc(RINGBELL, stderr); |
| return (-1); |
| } |
| } |
| |
| /* Skip n lines in the file f */ |
| static void skiplns(struct more_control *ctl, register int n, register FILE *f) |
| { |
| register int c; |
| |
| while (n > 0) { |
| while ((c = more_getc(ctl, f)) != '\n') |
| if (c == EOF) |
| return; |
| n--; |
| ctl->Currline++; |
| } |
| } |
| |
| /* Clear the screen */ |
| static void doclear(struct more_control *ctl) |
| { |
| if (ctl->Clear && !ctl->hard) { |
| putp(ctl->Clear); |
| /* Put out carriage return so that system doesn't get |
| * confused by escape sequences when expanding tabs */ |
| putchar('\r'); |
| ctl->promptlen = 0; |
| } |
| } |
| |
| static void rdline(struct more_control *ctl, register FILE *f) |
| { |
| register int c; |
| register char *p; |
| |
| prepare_line_buffer(ctl); |
| |
| p = ctl->Line; |
| while ((c = more_getc(ctl, f)) != '\n' && c != EOF |
| && (size_t)(p - ctl->Line) < ctl->LineLen - 1) |
| *p++ = c; |
| if (c == '\n') |
| ctl->Currline++; |
| *p = '\0'; |
| } |
| |
| /* Go to home position */ |
| static void home(struct more_control *ctl) |
| { |
| putp(ctl->Home); |
| } |
| |
| /* Search for nth occurrence of regular expression contained in buf in |
| * the file */ |
| static void search(struct more_control *ctl, char buf[], FILE *file, register int n) |
| { |
| long startline = ctl->file_pos; |
| register long line1 = startline; |
| register long line2 = startline; |
| register long line3; |
| register int lncount; |
| int saveln, rc; |
| regex_t re; |
| |
| ctl->context.line = saveln = ctl->Currline; |
| ctl->context.chrctr = 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); |
| } |
| while (!feof(file)) { |
| line3 = line2; |
| line2 = line1; |
| line1 = ctl->file_pos; |
| rdline(ctl, file); |
| lncount++; |
| if (regexec(&re, ctl->Line, 0, NULL, 0) == 0) { |
| if (--n == 0) { |
| if (lncount > 3 || (lncount > 1 && ctl->no_intty)) { |
| putchar('\n'); |
| if (ctl->clreol) |
| cleareol(ctl); |
| fputs(_("...skipping\n"), stdout); |
| } |
| if (!ctl->no_intty) { |
| ctl->Currline -= |
| (lncount >= 3 ? 3 : lncount); |
| more_fseek(ctl, file, line3); |
| if (ctl->noscroll) { |
| if (ctl->clreol) { |
| home(ctl); |
| cleareol(ctl); |
| } else |
| doclear(ctl); |
| } |
| } else { |
| kill_line(ctl); |
| if (ctl->noscroll) { |
| if (ctl->clreol) { |
| home(ctl); |
| cleareol(ctl); |
| } else |
| doclear(ctl); |
| } |
| puts(ctl->Line); |
| } |
| break; |
| } |
| } |
| } |
| regfree(&re); |
| if (feof(file)) { |
| if (!ctl->no_intty) { |
| ctl->Currline = saveln; |
| more_fseek(ctl, file, startline); |
| } else { |
| fputs(_("\nPattern not found\n"), stdout); |
| end_it(0); |
| } |
| free(ctl->previousre); |
| ctl->previousre = NULL; |
| notfound: |
| more_error(ctl, _("Pattern not found")); |
| } |
| } |
| |
| /* 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 command(struct more_control *ctl, char *filename, register FILE *f) |
| { |
| register int nlines; |
| register int retval = 0; |
| register int c; |
| char colonch; |
| int done; |
| char comchar, cmdbuf[INIT_BUF]; |
| |
| done = 0; |
| if (!ctl->errors) |
| prompt(ctl, filename); |
| else |
| ctl->errors = 0; |
| for (;;) { |
| nlines = number(ctl, &comchar); |
| ctl->lastp = colonch = 0; |
| if (comchar == '.') { /* Repeat last command */ |
| ctl->lastp++; |
| comchar = ctl->lastcmd; |
| nlines = ctl->lastarg; |
| if (ctl->lastcmd == ':') |
| colonch = ctl->lastcolon; |
| } |
| ctl->lastcmd = comchar; |
| ctl->lastarg = nlines; |
| if ((cc_t) comchar == ctl->otty.c_cc[VERASE]) { |
| kill_line(ctl); |
| prompt(ctl, filename); |
| continue; |
| } |
| switch (comchar) { |
| case ':': |
| retval = colon(ctl, filename, colonch, nlines); |
| if (retval >= 0) |
| done++; |
| break; |
| case 'b': |
| case ctrl('B'): |
| { |
| register int initline; |
| |
| if (ctl->no_intty) { |
| fputc(RINGBELL, stderr); |
| return (-1); |
| } |
| |
| if (nlines == 0) |
| nlines++; |
| |
| putchar('\r'); |
| erasep(ctl, 0); |
| putchar('\n'); |
| if (ctl->clreol) |
| cleareol(ctl); |
| printf(P_("...back %d page", |
| "...back %d pages", nlines), |
| nlines); |
| if (ctl->clreol) |
| cleareol(ctl); |
| putchar('\n'); |
| |
| initline = ctl->Currline - ctl->dlines * (nlines + 1); |
| if (!ctl->noscroll) |
| --initline; |
| if (initline < 0) |
| initline = 0; |
| more_fseek(ctl, f, 0L); |
| ctl->Currline = 0; /* skiplns() will make Currline correct */ |
| skiplns(ctl, initline, f); |
| if (!ctl->noscroll) { |
| retval = ctl->dlines + 1; |
| } else { |
| retval = ctl->dlines; |
| } |
| done = 1; |
| break; |
| } |
| case ' ': |
| case 'z': |
| if (nlines == 0) |
| nlines = ctl->dlines; |
| else if (comchar == 'z') |
| ctl->dlines = nlines; |
| retval = nlines; |
| done = 1; |
| break; |
| case 'd': |
| case ctrl('D'): |
| if (nlines != 0) |
| ctl->nscroll = nlines; |
| retval = ctl->nscroll; |
| done = 1; |
| break; |
| case 'q': |
| case 'Q': |
| end_it(0); |
| case 's': |
| case 'f': |
| case ctrl('F'): |
| if (nlines == 0) |
| nlines++; |
| if (comchar == 'f') |
| nlines *= ctl->dlines; |
| putchar('\r'); |
| erasep(ctl, 0); |
| putchar('\n'); |
| if (ctl->clreol) |
| cleareol(ctl); |
| printf(P_("...skipping %d line", |
| "...skipping %d lines", nlines), |
| nlines); |
| |
| if (ctl->clreol) |
| cleareol(ctl); |
| putchar('\n'); |
| |
| while (nlines > 0) { |
| while ((c = more_getc(ctl, f)) != '\n') |
| if (c == EOF) { |
| retval = 0; |
| done++; |
| goto endsw; |
| } |
| ctl->Currline++; |
| nlines--; |
| } |
| retval = ctl->dlines; |
| done = 1; |
| break; |
| case '\n': |
| if (nlines != 0) |
| ctl->dlines = nlines; |
| else |
| nlines = 1; |
| retval = nlines; |
| done = 1; |
| break; |
| case '\f': |
| if (!ctl->no_intty) { |
| doclear(ctl); |
| more_fseek(ctl, f, ctl->screen_start.chrctr); |
| ctl->Currline = ctl->screen_start.line; |
| retval = ctl->dlines; |
| done = 1; |
| break; |
| } else { |
| fputc(RINGBELL, stderr); |
| break; |
| } |
| case '\'': |
| if (!ctl->no_intty) { |
| kill_line(ctl); |
| fputs(_("\n***Back***\n\n"), stdout); |
| more_fseek(ctl, f, ctl->context.chrctr); |
| ctl->Currline = ctl->context.line; |
| retval = ctl->dlines; |
| done = 1; |
| break; |
| } else { |
| fputc(RINGBELL, stderr); |
| break; |
| } |
| case '=': |
| kill_line(ctl); |
| ctl->promptlen = printf("%d", ctl->Currline); |
| fflush(stdout); |
| break; |
| case 'n': |
| if (!ctl->previousre) { |
| more_error(ctl, _("No previous regular expression")); |
| break; |
| } |
| ctl->lastp++; |
| /* fallthrough */ |
| case '/': |
| if (nlines == 0) |
| nlines++; |
| kill_line(ctl); |
| putchar('/'); |
| ctl->promptlen = 1; |
| fflush(stdout); |
| if (ctl->lastp) { |
| fputc('\r', stderr); |
| search(ctl, ctl->previousre, f, nlines); |
| } else { |
| ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/'); |
| fputc('\r', stderr); |
| free(ctl->previousre); |
| ctl->previousre = xstrdup(cmdbuf); |
| search(ctl, cmdbuf, f, nlines); |
| } |
| retval = ctl->dlines - 1; |
| done = 1; |
| break; |
| case '!': |
| do_shell(ctl, filename); |
| break; |
| case '?': |
| case 'h': |
| if (ctl->noscroll) |
| doclear(ctl); |
| fputs(_("\n" |
| "Most commands optionally preceded by integer argument k. " |
| "Defaults in brackets.\n" |
| "Star (*) indicates argument becomes new default.\n"), stdout); |
| puts("---------------------------------------" |
| "----------------------------------------"); |
| fputs(_ |
| ("<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 /usr/bin/vi 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"), stdout); |
| puts("---------------------------------------" |
| "----------------------------------------"); |
| prompt(ctl, filename); |
| break; |
| case 'v': /* This case should go right before default */ |
| if (!ctl->no_intty) { |
| /* Earlier: call vi +n file. This also |
| * works for emacs. POSIX: call vi -c n |
| * file (when editor is vi or ex). */ |
| char *editor, *p; |
| int n = (ctl->Currline - ctl->dlines <= 0 ? 1 : |
| ctl->Currline - (ctl->dlines + 1) / 2); |
| int split = 0; |
| |
| editor = getenv("VISUAL"); |
| if (editor == NULL || *editor == '\0') |
| editor = getenv("EDITOR"); |
| if (editor == NULL || *editor == '\0') |
| editor = VI; |
| |
| p = strrchr(editor, '/'); |
| if (p) |
| p++; |
| else |
| p = editor; |
| if (!strcmp(p, "vi") || !strcmp(p, "ex")) { |
| sprintf(cmdbuf, "-c %d", n); |
| split = 1; |
| } else { |
| sprintf(cmdbuf, "+%d", n); |
| } |
| |
| kill_line(ctl); |
| printf("%s %s %s", editor, cmdbuf, |
| ctl->fnames[ctl->fnum]); |
| if (split) { |
| cmdbuf[2] = 0; |
| execute(ctl, filename, editor, editor, |
| cmdbuf, cmdbuf + 3, |
| ctl->fnames[ctl->fnum], (char *)0); |
| } else |
| execute(ctl, filename, editor, editor, |
| cmdbuf, ctl->fnames[ctl->fnum], |
| (char *)0); |
| break; |
| } |
| /* fallthrough */ |
| default: |
| if (ctl->dum_opt) { |
| kill_line(ctl); |
| if (ctl->Senter && ctl->Sexit) { |
| putstring(ctl->Senter); |
| ctl->promptlen = |
| printf(_ |
| ("[Press 'h' for instructions.]")) |
| + 2 * ctl->soglitch; |
| putstring(ctl->Sexit); |
| } else |
| ctl->promptlen = |
| printf(_ |
| ("[Press 'h' for instructions.]")); |
| fflush(stdout); |
| } else |
| fputc(RINGBELL, stderr); |
| break; |
| } |
| if (done) |
| break; |
| } |
| putchar('\r'); |
| endsw: |
| ctl->inwait = 0; |
| ctl->notell = 1; |
| return (retval); |
| } |
| |
| /* Print out the contents of the file f, one screenful at a time. */ |
| static void screen(struct more_control *ctl, register FILE *f, register int num_lines) |
| { |
| register int c; |
| register int nchars; |
| int length; /* length of current line */ |
| static int prev_len = 1; /* length of previous line */ |
| |
| for (;;) { |
| while (num_lines > 0 && !ctl->Pause) { |
| if ((nchars = get_line(ctl, f, &length)) == EOF) { |
| if (ctl->clreol) |
| clreos(ctl); |
| return; |
| } |
| if (ctl->ssp_opt && length == 0 && prev_len == 0) |
| continue; |
| prev_len = length; |
| if (ctl->bad_so |
| || ((ctl->Senter && *ctl->Senter == ' ') && (ctl->promptlen > 0))) |
| erasep(ctl, 0); |
| /* must clear before drawing line since tabs on |
| * some terminals do not erase what they tab |
| * over. */ |
| if (ctl->clreol) |
| cleareol(ctl); |
| prbuf(ctl, ctl->Line, length); |
| if (nchars < ctl->promptlen) |
| erasep(ctl, nchars); /* erasep () sets promptlen to 0 */ |
| else |
| ctl->promptlen = 0; |
| /* is this needed? |
| * if (clreol) |
| * cleareol(); * must clear again in case we wrapped * |
| */ |
| if (nchars < ctl->Mcol || !ctl->fold_opt) |
| prbuf(ctl, "\n", 1); /* will turn off UL if necessary */ |
| if (nchars == STOP) |
| break; |
| num_lines--; |
| } |
| if (ctl->pstate) { |
| putp(ctl->ULexit); |
| ctl->pstate = 0; |
| } |
| fflush(stdout); |
| if ((c = more_getc(ctl, f)) == EOF) { |
| if (ctl->clreol) |
| clreos(ctl); |
| return; |
| } |
| |
| if (ctl->Pause && ctl->clreol) |
| clreos(ctl); |
| more_ungetc(ctl, c, f); |
| sigsetjmp(ctl->restore, 1); |
| ctl->Pause = 0; |
| ctl->startup = 0; |
| if ((num_lines = command(ctl, NULL, f)) == 0) |
| return; |
| if (ctl->hard && ctl->promptlen > 0) |
| erasep(ctl, 0); |
| if (ctl->noscroll && num_lines >= ctl->dlines) { |
| if (ctl->clreol) |
| home(ctl); |
| else |
| doclear(ctl); |
| } |
| ctl->screen_start.line = ctl->Currline; |
| ctl->screen_start.chrctr = ctl->file_pos; |
| } |
| } |
| |
| /* Come here if a signal for a window size change is received */ |
| #ifdef SIGWINCH |
| static void chgwinsz(int dummy __attribute__((__unused__))) |
| { |
| struct winsize win; |
| |
| signal(SIGWINCH, SIG_IGN); |
| if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) { |
| if (win.ws_row != 0) { |
| global_ctl->Lpp = win.ws_row; |
| global_ctl->nscroll = global_ctl->Lpp / 2 - 1; |
| if (global_ctl->nscroll <= 0) |
| global_ctl->nscroll = 1; |
| global_ctl->dlines = global_ctl->Lpp - 1; /* was: Lpp - (noscroll ? 1 : 2) */ |
| } |
| if (win.ws_col != 0) |
| global_ctl->Mcol = win.ws_col; |
| } |
| signal(SIGWINCH, chgwinsz); |
| } |
| #endif /* SIGWINCH */ |
| |
| static void copy_file(register FILE *f) |
| { |
| char buf[BUFSIZ]; |
| size_t sz; |
| |
| while ((sz = fread(&buf, sizeof(char), sizeof(buf), f)) > 0) |
| fwrite(&buf, sizeof(char), sz, stdout); |
| } |
| |
| |
| /*----------------------------- Terminal I/O -------------------------------*/ |
| static void initterm(struct more_control *ctl) |
| { |
| int ret; |
| char *padstr; |
| char *term; |
| struct winsize win; |
| |
| #ifdef do_SIGTTOU |
| retry: |
| #endif |
| |
| #ifndef NON_INTERACTIVE_MORE |
| ctl->no_tty = tcgetattr(fileno(stdout), &ctl->otty); |
| #endif |
| if (!ctl->no_tty) { |
| ctl->docrterase = (ctl->otty.c_cc[VERASE] != 255); |
| ctl->docrtkill = (ctl->otty.c_cc[VKILL] != 255); |
| #ifdef do_SIGTTOU |
| { |
| int tgrp; |
| /* Wait until we're in the foreground before we |
| * save the terminal modes. */ |
| if ((tgrp = tcgetpgrp(fileno(stdout))) < 0) |
| err(EXIT_FAILURE, "tcgetpgrp"); |
| if (tgrp != getpgrp(0)) { |
| kill(0, SIGTTOU); |
| goto retry; |
| } |
| } |
| #endif /* do_SIGTTOU */ |
| if ((term = getenv("TERM")) == NULL) { |
| ctl->dumb = 1; |
| ctl->ul_opt = 0; |
| } |
| setupterm(term, 1, &ret); |
| if (ret <= 0) { |
| ctl->dumb = 1; |
| ctl->ul_opt = 0; |
| } else { |
| #ifdef TIOCGWINSZ |
| if (ioctl(fileno(stdout), TIOCGWINSZ, &win) < 0) { |
| #endif |
| ctl->Lpp = tigetnum(TERM_LINES); |
| ctl->Mcol = tigetnum(TERM_COLS); |
| #ifdef TIOCGWINSZ |
| } else { |
| if ((ctl->Lpp = win.ws_row) == 0) |
| ctl->Lpp = tigetnum(TERM_LINES); |
| if ((ctl->Mcol = win.ws_col) == 0) |
| ctl->Mcol = tigetnum(TERM_COLS); |
| } |
| #endif |
| if ((ctl->Lpp <= 0) || tigetflag(TERM_HARD_COPY)) { |
| ctl->hard = 1; /* Hard copy terminal */ |
| ctl->Lpp = LINES_PER_PAGE; |
| } |
| |
| if (tigetflag(TERM_EAT_NEW_LINE)) |
| /* Eat newline at last column + 1; dec, concept */ |
| ctl->eatnl++; |
| if (ctl->Mcol <= 0) |
| ctl->Mcol = NUM_COLUMNS; |
| |
| ctl->Wrap = tigetflag(TERM_AUTO_RIGHT_MARGIN); |
| ctl->bad_so = tigetflag(TERM_CEOL); |
| ctl->eraseln = tigetstr(TERM_CLEAR_TO_LINE_END); |
| ctl->Clear = tigetstr(TERM_CLEAR); |
| ctl->Senter = tigetstr(TERM_STANDARD_MODE); |
| ctl->Sexit = tigetstr(TERM_EXIT_STANDARD_MODE); |
| if (0 < tigetnum(TERM_STD_MODE_GLITCH)) |
| ctl->soglitch = 1; |
| |
| /* Set up for underlining: some terminals don't |
| * need it; others have start/stop sequences, |
| * still others have an underline char sequence |
| * which is assumed to move the cursor forward |
| * one character. If underline sequence isn't |
| * available, settle for standout sequence. */ |
| if (tigetflag(TERM_UNDERLINE) |
| || tigetflag(TERM_OVER_STRIKE)) |
| ctl->ul_opt = 0; |
| if ((ctl->chUL = tigetstr(TERM_UNDERLINE_CHAR)) == NULL) |
| ctl->chUL = ""; |
| if (((ctl->ULenter = |
| tigetstr(TERM_ENTER_UNDERLINE)) == NULL |
| || (ctl->ULexit = |
| tigetstr(TERM_EXIT_UNDERLINE)) == NULL) |
| && !*ctl->chUL) { |
| if ((ctl->ULenter = ctl->Senter) == NULL |
| || (ctl->ULexit = ctl->Sexit) == NULL) { |
| ctl->ULenter = ""; |
| ctl->ULexit = ""; |
| } else |
| ctl->ulglitch = ctl->soglitch; |
| } else { |
| ctl->ulglitch = 0; |
| } |
| |
| if ((padstr = tigetstr(TERM_PAD_CHAR)) != NULL) |
| ctl->PC = *padstr; |
| ctl->Home = tigetstr(TERM_HOME); |
| if (ctl->Home == NULL || *ctl->Home == '\0') { |
| if ((ctl->cursorm = |
| tigetstr(TERM_CURSOR_ADDRESS)) != NULL) { |
| const char *t = |
| (const char *)tparm(ctl->cursorm, 0, |
| 0); |
| xstrncpy(ctl->cursorhome, t, |
| sizeof(ctl->cursorhome)); |
| ctl->Home = ctl->cursorhome; |
| } |
| } |
| ctl->EodClr = tigetstr(TERM_CLEAR_TO_SCREEN_END); |
| if ((ctl->chBS = tigetstr(TERM_LINE_DOWN)) == NULL) |
| ctl->chBS = BS; |
| |
| } |
| if ((ctl->shell = getenv("SHELL")) == NULL) |
| ctl->shell = "/bin/sh"; |
| } |
| ctl->no_intty = tcgetattr(fileno(stdin), &ctl->otty); |
| tcgetattr(fileno(stderr), &ctl->otty); |
| ctl->savetty0 = ctl->otty; |
| ctl->hardtabs = (ctl->otty.c_oflag & TABDLY) != XTABS; |
| if (!ctl->no_tty) { |
| ctl->otty.c_lflag &= ~(ICANON | ECHO); |
| ctl->otty.c_cc[VMIN] = 1; |
| ctl->otty.c_cc[VTIME] = 0; |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| FILE *f; |
| char *s; |
| int chr; |
| int left; |
| int prnames = 0; |
| int initopt = 0; |
| int srchopt = 0; |
| int clearit = 0; |
| int initline = 0; |
| char *initbuf = NULL; |
| struct more_control ctl = { |
| .firstf = 1, |
| .fold_opt = 1, |
| .notell = 1, |
| .startup = 1, |
| .stop_opt = 1, |
| .ul_opt = 1, |
| .Wrap = 1, |
| .Lpp = LINES_PER_PAGE, |
| .Mcol = NUM_COLUMNS, |
| .nscroll = SCROLL_LEN, |
| 0 |
| }; |
| global_ctl = &ctl; |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| atexit(close_stdout); |
| |
| if (argc > 1) { |
| /* first arg may be one of our standard longopts */ |
| if (!strcmp(argv[1], "--help")) |
| usage(); |
| if (!strcmp(argv[1], "--version")) { |
| printf(UTIL_LINUX_VERSION); |
| exit(EXIT_SUCCESS); |
| } |
| } |
| |
| ctl.nfiles = argc; |
| ctl.fnames = argv; |
| setlocale(LC_ALL, ""); |
| initterm(&ctl); |
| |
| /* Auto set no scroll on when binary is called page */ |
| if (!(strcmp(program_invocation_short_name, "page"))) |
| ctl.noscroll++; |
| |
| prepare_line_buffer(&ctl); |
| |
| ctl.nscroll = ctl.Lpp / 2 - 1; |
| if (ctl.nscroll <= 0) |
| ctl.nscroll = 1; |
| |
| if ((s = getenv("MORE")) != NULL) |
| argscan(&ctl, s); |
| |
| while (--ctl.nfiles > 0) { |
| if ((chr = (*++ctl.fnames)[0]) == '-') { |
| argscan(&ctl, *ctl.fnames + 1); |
| } else if (chr == '+') { |
| s = *ctl.fnames; |
| if (*++s == '/') { |
| srchopt++; |
| initbuf = xstrdup(s + 1); |
| } else { |
| initopt++; |
| for (initline = 0; *s != '\0'; s++) |
| if (isdigit(*s)) |
| initline = |
| initline * 10 + *s - '0'; |
| --initline; |
| } |
| } else |
| break; |
| } |
| /* allow clreol only if Home and eraseln and EodClr strings are |
| * defined, and in that case, make sure we are in noscroll mode */ |
| if (ctl.clreol) { |
| if ((ctl.Home == NULL) || (*ctl.Home == '\0') || |
| (ctl.eraseln == NULL) || (*ctl.eraseln == '\0') || |
| (ctl.EodClr == NULL) || (*ctl.EodClr == '\0')) |
| ctl.clreol = 0; |
| else |
| ctl.noscroll = 1; |
| } |
| if (ctl.dlines == 0) |
| ctl.dlines = ctl.Lpp - 1; /* was: Lpp - (noscroll ? 1 : 2) */ |
| left = ctl.dlines; |
| if (ctl.nfiles > 1) |
| prnames++; |
| if (!ctl.no_intty && ctl.nfiles == 0) { |
| warnx(_("bad usage")); |
| errtryhelp(EXIT_FAILURE); |
| } else |
| f = stdin; |
| if (!ctl.no_tty) { |
| signal(SIGQUIT, onquit); |
| signal(SIGINT, end_it); |
| #ifdef SIGWINCH |
| signal(SIGWINCH, chgwinsz); |
| #endif |
| if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) { |
| signal(SIGTSTP, onsusp); |
| ctl.catch_susp++; |
| } |
| tcsetattr(STDERR_FILENO, TCSANOW, &ctl.otty); |
| } |
| if (ctl.no_intty) { |
| if (ctl.no_tty) |
| copy_file(stdin); |
| else { |
| if ((chr = getc(f)) == '\f') |
| doclear(&ctl); |
| else { |
| ungetc(chr, f); |
| if (ctl.noscroll && (chr != EOF)) { |
| if (ctl.clreol) |
| home(&ctl); |
| else |
| doclear(&ctl); |
| } |
| } |
| if (srchopt) { |
| free(ctl.previousre); |
| ctl.previousre = xstrdup(initbuf); |
| search(&ctl, initbuf, stdin, 1); |
| if (ctl.noscroll) |
| left--; |
| } else if (initopt) |
| skiplns(&ctl, initline, stdin); |
| screen(&ctl, stdin, left); |
| } |
| ctl.no_intty = 0; |
| prnames++; |
| ctl.firstf = 0; |
| } |
| |
| while (ctl.fnum < ctl.nfiles) { |
| if ((f = checkf(&ctl, ctl.fnames[ctl.fnum], &clearit)) != NULL) { |
| ctl.context.line = ctl.context.chrctr = 0; |
| ctl.Currline = 0; |
| if (ctl.firstf) |
| sigsetjmp(ctl.restore, 1); |
| if (ctl.firstf) { |
| ctl.firstf = 0; |
| if (srchopt) { |
| free(ctl.previousre); |
| ctl.previousre = xstrdup(initbuf); |
| search(&ctl, initbuf, f, 1); |
| if (ctl.noscroll) |
| left--; |
| } else if (initopt) |
| skiplns(&ctl, initline, f); |
| } else if (ctl.fnum < ctl.nfiles && !ctl.no_tty) { |
| sigsetjmp(ctl.restore, 1); |
| left = command(&ctl, ctl.fnames[ctl.fnum], f); |
| } |
| if (left != 0) { |
| if ((ctl.noscroll || clearit) |
| && (ctl.file_size != LONG_MAX)) { |
| if (ctl.clreol) |
| home(&ctl); |
| else |
| doclear(&ctl); |
| } |
| if (prnames) { |
| if (ctl.bad_so) |
| erasep(&ctl, 0); |
| if (ctl.clreol) |
| cleareol(&ctl); |
| fputs("::::::::::::::", stdout); |
| if (ctl.promptlen > 14) |
| erasep(&ctl, 14); |
| putchar('\n'); |
| if (ctl.clreol) |
| cleareol(&ctl); |
| puts(ctl.fnames[ctl.fnum]); |
| if (ctl.clreol) |
| cleareol(&ctl); |
| fputs("::::::::::::::\n", stdout); |
| if (left > ctl.Lpp - 4) |
| left = ctl.Lpp - 4; |
| } |
| if (ctl.no_tty) |
| copy_file(f); |
| else { |
| ctl.within = 1; |
| screen(&ctl, f, left); |
| ctl.within = 0; |
| } |
| } |
| sigsetjmp(ctl.restore, 1); |
| fflush(stdout); |
| fclose(f); |
| ctl.screen_start.line = ctl.screen_start.chrctr = 0L; |
| ctl.context.line = ctl.context.chrctr = 0L; |
| } |
| ctl.fnum++; |
| ctl.firstf = 0; |
| } |
| free(ctl.previousre); |
| free(initbuf); |
| free(ctl.Line); |
| reset_tty(); |
| exit(EXIT_SUCCESS); |
| } |