| /* |
| * pg - A clone of the System V CRT paging utility. |
| * |
| * Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. [deleted] |
| * 4. Neither the name of Gunnar Ritter nor the names of his contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| /* Sccsid @(#)pg.c 1.44 (gritter) 2/8/02 - modified for util-linux */ |
| |
| /* |
| * This command is deprecated. The utility is in maintenance mode, |
| * meaning we keep them in source tree for backward compatibility |
| * only. Do not waste time making this command better, unless the |
| * fix is about security or other very critical issue. |
| * |
| * See Documentation/deprecated.txt for more information. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #ifndef TIOCGWINSZ |
| # include <sys/ioctl.h> |
| #endif |
| #include <termios.h> |
| #include <fcntl.h> |
| #include <regex.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <setjmp.h> |
| |
| #if defined(HAVE_NCURSESW_NCURSES_H) |
| # include <ncursesw/ncurses.h> |
| #elif defined(HAVE_NCURSES_NCURSES_H) |
| # include <ncurses/ncurses.h> |
| #elif defined(HAVE_NCURSES_H) |
| # include <ncurses.h> |
| #endif |
| |
| #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 "nls.h" |
| #include "xalloc.h" |
| #include "widechar.h" |
| #include "all-io.h" |
| #include "closestream.h" |
| #include "strutils.h" |
| |
| #define READBUF LINE_MAX /* size of input buffer */ |
| #define CMDBUF 255 /* size of command buffer */ |
| #define PG_TABSIZE 8 /* spaces consumed by tab character */ |
| |
| #define cuc(c) ((c) & 0377) |
| |
| enum { FORWARD = 1, BACKWARD = 2 }; /* search direction */ |
| enum { TOP, MIDDLE, BOTTOM }; /* position of matching line */ |
| |
| /* States for syntax-aware command line editor. */ |
| enum { |
| COUNT, |
| SIGN, |
| CMD_FIN, |
| SEARCH, |
| SEARCH_FIN, |
| ADDON_FIN, |
| STRING, |
| INVALID |
| }; |
| |
| /* Current command */ |
| static struct { |
| char cmdline[CMDBUF]; |
| size_t cmdlen; |
| int count; |
| int key; |
| char pattern[CMDBUF]; |
| char addon; |
| } cmd; |
| |
| /* Position of file arguments on argv[] to main() */ |
| static struct { |
| int first; |
| int current; |
| int last; |
| } files; |
| |
| static void (*oldint) (int); /* old SIGINT handler */ |
| static void (*oldquit) (int); /* old SIGQUIT handler */ |
| static void (*oldterm) (int); /* old SIGTERM handler */ |
| static char *tty; /* result of ttyname(1) */ |
| static unsigned ontty; /* whether running on tty device */ |
| static unsigned exitstatus; /* exit status */ |
| static int pagelen = 23; /* lines on a single screen page */ |
| static int ttycols = 79; /* screen columns (starting at 0) */ |
| static struct termios otio; /* old termios settings */ |
| static int tinfostat = -1; /* terminfo routines initialized */ |
| static int searchdisplay = TOP; /* matching line position */ |
| static regex_t re; /* regular expression to search for */ |
| static int remembered; /* have a remembered search string */ |
| static int cflag; /* clear screen before each page */ |
| static int eflag; /* suppress (EOF) */ |
| static int fflag; /* do not split lines */ |
| static int nflag; /* no newline for commands required */ |
| static int rflag; /* "restricted" pg */ |
| static int sflag; /* use standout mode */ |
| static const char *pstring = ":"; /* prompt string */ |
| static char *searchfor; /* search pattern from argv[] */ |
| static int havepagelen; /* page length is manually defined */ |
| static long startline; /* start line from argv[] */ |
| static int nextfile = 1; /* files to advance */ |
| static jmp_buf jmpenv; /* jump from signal handlers */ |
| static int canjump; /* jmpenv is valid */ |
| static wchar_t wbuf[READBUF]; /* used in several widechar routines */ |
| |
| static char *copyright; |
| static const char *helpscreen = N_("\ |
| -------------------------------------------------------\n\ |
| h this screen\n\ |
| q or Q quit program\n\ |
| <newline> next page\n\ |
| f skip a page forward\n\ |
| d or ^D next halfpage\n\ |
| l next line\n\ |
| $ last page\n\ |
| /regex/ search forward for regex\n\ |
| ?regex? or ^regex^ search backward for regex\n\ |
| . or ^L redraw screen\n\ |
| w or z set page size and go to next page\n\ |
| s filename save current file to filename\n\ |
| !command shell escape\n\ |
| p go to previous file\n\ |
| n go to next file\n\ |
| \n\ |
| Many commands accept preceding numbers, for example:\n\ |
| +1<newline> (next page); -1<newline> (previous page); 1<newline> (first page).\n\ |
| \n\ |
| See pg(1) for more information.\n\ |
| -------------------------------------------------------\n"); |
| |
| #ifndef HAVE_FSEEKO |
| static int fseeko(FILE *f, off_t off, int whence) |
| { |
| return fseek(f, (long)off, whence); |
| } |
| |
| static off_t ftello(FILE *f) |
| { |
| return (off_t) ftell(f); |
| } |
| #endif |
| |
| #ifdef USE_SIGSET /* never defined */ |
| /* sigset and sigrelse are obsolete - use when POSIX stuff is unavailable */ |
| # define my_sigset sigset |
| # define my_sigrelse sigrelse |
| #else |
| static int my_sigrelse(int sig) |
| { |
| sigset_t sigs; |
| |
| if (sigemptyset(&sigs) || sigaddset(&sigs, sig)) |
| return -1; |
| return sigprocmask(SIG_UNBLOCK, &sigs, NULL); |
| } |
| |
| typedef void (*my_sighandler_t) (int); |
| static my_sighandler_t my_sigset(int sig, my_sighandler_t disp) |
| { |
| struct sigaction act, oact; |
| |
| act.sa_handler = disp; |
| if (sigemptyset(&act.sa_mask)) |
| return SIG_ERR; |
| act.sa_flags = 0; |
| if (sigaction(sig, &act, &oact)) |
| return SIG_ERR; |
| if (my_sigrelse(sig)) |
| return SIG_ERR; |
| return oact.sa_handler; |
| } |
| #endif /* USE_SIGSET */ |
| |
| /* Quit pg. */ |
| static void __attribute__((__noreturn__)) quit(int status) |
| { |
| exit(status < 0100 ? status : 077); |
| } |
| |
| /* Usage message and similar routines. */ |
| static void __attribute__((__noreturn__)) usage(void) |
| { |
| FILE *out = stdout; |
| fputs(USAGE_HEADER, out); |
| fprintf(out, |
| _(" %s [options] [+line] [+/pattern/] [files]\n"), |
| program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, out); |
| fputs(_("Browse pagewise through text files.\n"), out); |
| |
| fputs(USAGE_OPTIONS, out); |
| fputs(_(" -number lines per page\n"), out); |
| fputs(_(" -c clear screen before displaying\n"), out); |
| fputs(_(" -e do not pause at end of a file\n"), out); |
| fputs(_(" -f do not split long lines\n"), out); |
| fputs(_(" -n terminate command with new line\n"), out); |
| fputs(_(" -p <prompt> specify prompt\n"), out); |
| fputs(_(" -r disallow shell escape\n"), out); |
| fputs(_(" -s print messages to stdout\n"), out); |
| fputs(_(" +number start at the given line\n"), out); |
| fputs(_(" +/pattern/ start at the line containing pattern\n"), out); |
| |
| fputs(USAGE_SEPARATOR, out); |
| printf(USAGE_HELP_OPTIONS(16)); |
| |
| printf(USAGE_MAN_TAIL("pg(1)")); |
| exit(0); |
| } |
| |
| static void __attribute__((__noreturn__)) needarg(const char *s) |
| { |
| warnx(_("option requires an argument -- %s"), s); |
| errtryhelp(2); |
| } |
| |
| static void __attribute__((__noreturn__)) invopt(const char *s) |
| { |
| warnx(_("illegal option -- %s"), s); |
| errtryhelp(2); |
| } |
| |
| #ifdef HAVE_WIDECHAR |
| /* A mbstowcs()-alike function that transparently handles invalid |
| * sequences. */ |
| static size_t xmbstowcs(wchar_t * pwcs, const char *s, size_t nwcs) |
| { |
| size_t n = nwcs; |
| int c; |
| |
| ignore_result(mbtowc(pwcs, NULL, MB_CUR_MAX)); /* reset shift state */ |
| while (*s && n) { |
| if ((c = mbtowc(pwcs, s, MB_CUR_MAX)) < 0) { |
| s++; |
| *pwcs = L'?'; |
| } else |
| s += c; |
| pwcs++; |
| n--; |
| } |
| if (n) |
| *pwcs = L'\0'; |
| ignore_result(mbtowc(pwcs, NULL, MB_CUR_MAX)); |
| return nwcs - n; |
| } |
| #endif |
| |
| /* Helper function for tputs(). */ |
| static int outcap(int i) |
| { |
| char c = i; |
| return write_all(STDOUT_FILENO, &c, 1) == 0 ? 1 : -1; |
| } |
| |
| /* Write messages to terminal. */ |
| static void mesg(const char *message) |
| { |
| if (ontty == 0) |
| return; |
| if (*message != '\n' && sflag) |
| vidputs(A_STANDOUT, outcap); |
| write_all(STDOUT_FILENO, message, strlen(message)); |
| if (*message != '\n' && sflag) |
| vidputs(A_NORMAL, outcap); |
| } |
| |
| /* Get the window size. */ |
| static void getwinsize(void) |
| { |
| static int initialized, envlines, envcols, deflines, defcols; |
| #ifdef TIOCGWINSZ |
| struct winsize winsz; |
| int badioctl; |
| #endif |
| char *p; |
| |
| if (initialized == 0) { |
| if ((p = getenv("LINES")) != NULL && *p != '\0') |
| if ((envlines = atoi(p)) < 0) |
| envlines = 0; |
| if ((p = getenv("COLUMNS")) != NULL && *p != '\0') |
| if ((envcols = atoi(p)) < 0) |
| envcols = 0; |
| /* terminfo values. */ |
| if (tinfostat != 1 || columns == 0) |
| defcols = 24; |
| else |
| defcols = columns; |
| if (tinfostat != 1 || lines == 0) |
| deflines = 80; |
| else |
| deflines = lines; |
| initialized = 1; |
| } |
| #ifdef TIOCGWINSZ |
| badioctl = ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsz); |
| #endif |
| if (envcols) |
| ttycols = envcols - 1; |
| #ifdef TIOCGWINSZ |
| else if (!badioctl) |
| ttycols = winsz.ws_col - 1; |
| #endif |
| else |
| ttycols = defcols - 1; |
| if (havepagelen == 0) { |
| if (envlines) |
| pagelen = envlines - 1; |
| #ifdef TIOCGWINSZ |
| else if (!badioctl) |
| pagelen = winsz.ws_row - 1; |
| #endif |
| else |
| pagelen = deflines - 1; |
| } |
| } |
| |
| /* Message if skipping parts of files. */ |
| static void skip(int direction) |
| { |
| if (direction > 0) |
| mesg(_("...skipping forward\n")); |
| else |
| mesg(_("...skipping backward\n")); |
| } |
| |
| /* Signal handler while reading from input file. */ |
| static void sighandler(int signum) |
| { |
| if (canjump && (signum == SIGINT || signum == SIGQUIT)) |
| longjmp(jmpenv, signum); |
| tcsetattr(STDOUT_FILENO, TCSADRAIN, &otio); |
| quit(exitstatus); |
| } |
| |
| /* Check whether the requested file was specified on the command line. */ |
| static int checkf(void) |
| { |
| if (files.current + nextfile >= files.last) { |
| mesg(_("No next file")); |
| return 1; |
| } |
| if (files.current + nextfile < files.first) { |
| mesg(_("No previous file")); |
| return 1; |
| } |
| return 0; |
| } |
| |
| #ifdef HAVE_WIDECHAR |
| /* Return the last character that will fit on the line at col columns in |
| * case MB_CUR_MAX > 1. */ |
| static char *endline_for_mb(unsigned col, char *s) |
| { |
| size_t pos = 0; |
| wchar_t *p = wbuf; |
| wchar_t *end; |
| size_t wl; |
| char *t = s; |
| |
| if ((wl = xmbstowcs(wbuf, t, sizeof wbuf - 1)) == (size_t)-1) |
| return s + 1; |
| wbuf[wl] = L'\0'; |
| while (*p != L'\0') { |
| switch (*p) { |
| /* Cursor left. */ |
| case L'\b': |
| if (pos > 0) |
| pos--; |
| break; |
| /* No cursor movement. */ |
| case L'\a': |
| break; |
| /* Special. */ |
| case L'\r': |
| pos = 0; |
| break; |
| case L'\n': |
| end = p + 1; |
| goto ended; |
| /* Cursor right. */ |
| case L'\t': |
| pos += PG_TABSIZE - (pos % PG_TABSIZE); |
| break; |
| default: |
| if (iswprint(*p)) |
| pos += wcwidth(*p); |
| else |
| pos += wcwidth(L'?'); |
| } |
| if (pos > col) { |
| if (*p == L'\t') |
| p++; |
| else if (pos > col + 1) |
| /* wcwidth() found a character that has |
| * multiple columns. What happens now? |
| * Assume the terminal will print the |
| * entire character onto the next row. */ |
| p--; |
| if (*++p == L'\n') |
| p++; |
| end = p; |
| goto ended; |
| } |
| p++; |
| } |
| end = p; |
| ended: |
| *end = L'\0'; |
| p = wbuf; |
| if ((pos = wcstombs(NULL, p, READBUF)) == (size_t)-1) |
| return s + 1; |
| return s + pos; |
| } |
| #endif /* HAVE_WIDECHAR */ |
| |
| /* Return the last character that will fit on the line at col columns. */ |
| static char *endline(unsigned col, char *s) |
| { |
| unsigned pos = 0; |
| char *t = s; |
| |
| #ifdef HAVE_WIDECHAR |
| if (MB_CUR_MAX > 1) |
| return endline_for_mb(col, s); |
| #endif |
| |
| while (*s != '\0') { |
| switch (*s) { |
| /* Cursor left. */ |
| case '\b': |
| if (pos > 0) |
| pos--; |
| break; |
| /* No cursor movement. */ |
| case '\a': |
| break; |
| /* Special. */ |
| case '\r': |
| pos = 0; |
| break; |
| case '\n': |
| t = s + 1; |
| goto cend; |
| /* Cursor right. */ |
| case '\t': |
| pos += PG_TABSIZE - (pos % PG_TABSIZE); |
| break; |
| default: |
| pos++; |
| } |
| if (pos > col) { |
| if (*s == '\t') |
| s++; |
| if (*++s == '\n') |
| s++; |
| t = s; |
| goto cend; |
| } |
| s++; |
| } |
| t = s; |
| cend: |
| return t; |
| } |
| |
| /* Clear the current line on the terminal's screen. */ |
| static void cline(void) |
| { |
| char *buf = xmalloc(ttycols + 2); |
| memset(buf, ' ', ttycols + 2); |
| buf[0] = '\r'; |
| buf[ttycols + 1] = '\r'; |
| write_all(STDOUT_FILENO, buf, ttycols + 2); |
| free(buf); |
| } |
| |
| /* Evaluate a command character's semantics. */ |
| static int getstate(int c) |
| { |
| switch (c) { |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case '0': |
| case '\0': |
| return COUNT; |
| case '-': |
| case '+': |
| return SIGN; |
| case 'l': |
| case 'd': |
| case '\004': |
| case 'f': |
| case 'z': |
| case '.': |
| case '\014': |
| case '$': |
| case 'n': |
| case 'p': |
| case 'w': |
| case 'h': |
| case 'q': |
| case 'Q': |
| return CMD_FIN; |
| case '/': |
| case '?': |
| case '^': |
| return SEARCH; |
| case 's': |
| case '!': |
| return STRING; |
| case 'm': |
| case 'b': |
| case 't': |
| return ADDON_FIN; |
| default: |
| #ifdef PG_BELL |
| if (bell) |
| tputs(bell, STDOUT_FILENO, outcap); |
| #endif |
| return INVALID; |
| } |
| } |
| |
| /* Get the count and ignore last character of string. */ |
| static int getcount(char *cmdstr) |
| { |
| char *buf; |
| char *p; |
| int i; |
| |
| if (*cmdstr == '\0') |
| return 1; |
| buf = xmalloc(strlen(cmdstr) + 1); |
| strcpy(buf, cmdstr); |
| if (cmd.key != '\0') { |
| if (cmd.key == '/' || cmd.key == '?' || cmd.key == '^') { |
| if ((p = strchr(buf, cmd.key)) != NULL) |
| *p = '\0'; |
| } else |
| *(buf + strlen(buf) - 1) = '\0'; |
| } |
| if (*buf == '\0') { |
| free(buf); |
| return 1; |
| } |
| if (buf[0] == '-' && buf[1] == '\0') { |
| i = -1; |
| } else { |
| if (*buf == '+') |
| i = atoi(buf + 1); |
| else |
| i = atoi(buf); |
| } |
| free(buf); |
| return i; |
| } |
| |
| /* Read what the user writes at the prompt. This is tricky because we |
| * check for valid input. */ |
| static void prompt(long long pageno) |
| { |
| struct termios tio; |
| char key; |
| int state = COUNT; |
| int escape = 0; |
| char b[LINE_MAX], *p; |
| |
| if (pageno != -1) { |
| if ((p = strstr(pstring, "%d")) == NULL) { |
| mesg(pstring); |
| } else { |
| strcpy(b, pstring); |
| sprintf(b + (p - pstring), "%lld", pageno); |
| strcat(b, p + 2); |
| mesg(b); |
| } |
| } |
| cmd.key = cmd.addon = cmd.cmdline[0] = '\0'; |
| cmd.cmdlen = 0; |
| tcgetattr(STDOUT_FILENO, &tio); |
| tio.c_lflag &= ~(ICANON | ECHO); |
| tio.c_cc[VMIN] = 1; |
| tio.c_cc[VTIME] = 0; |
| tcsetattr(STDOUT_FILENO, TCSADRAIN, &tio); |
| tcflush(STDOUT_FILENO, TCIFLUSH); |
| for (;;) { |
| switch (read(STDOUT_FILENO, &key, 1)) { |
| case 0: |
| quit(0); |
| /* NOTREACHED */ |
| case -1: |
| quit(1); |
| } |
| if (key == tio.c_cc[VERASE]) { |
| if (cmd.cmdlen) { |
| write_all(STDOUT_FILENO, "\b \b", 3); |
| cmd.cmdline[--cmd.cmdlen] = '\0'; |
| switch (state) { |
| case ADDON_FIN: |
| state = SEARCH_FIN; |
| cmd.addon = '\0'; |
| break; |
| case CMD_FIN: |
| cmd.key = '\0'; |
| state = COUNT; |
| break; |
| case SEARCH_FIN: |
| state = SEARCH; |
| /* fallthrough */ |
| case SEARCH: |
| if (cmd.cmdline[cmd.cmdlen - 1] == '\\') { |
| escape = 1; |
| while (cmd.cmdline[cmd.cmdlen |
| - escape - 1] |
| == '\\') |
| escape++; |
| escape %= 2; |
| } else { |
| escape = 0; |
| if (strchr(cmd.cmdline, cmd.key) |
| == NULL) { |
| cmd.key = '\0'; |
| state = COUNT; |
| } |
| } |
| break; |
| } |
| } |
| if (cmd.cmdlen == 0) { |
| state = COUNT; |
| cmd.key = '\0'; |
| } |
| continue; |
| } |
| if (key == tio.c_cc[VKILL]) { |
| cline(); |
| cmd.cmdlen = 0; |
| cmd.cmdline[0] = '\0'; |
| state = COUNT; |
| cmd.key = '\0'; |
| continue; |
| } |
| if (key == '\n' || (nflag && state == COUNT && key == ' ')) |
| break; |
| if (cmd.cmdlen >= CMDBUF - 1) |
| continue; |
| switch (state) { |
| case STRING: |
| break; |
| case SEARCH: |
| if (!escape) { |
| if (key == cmd.key) |
| state = SEARCH_FIN; |
| if (key == '\\') |
| escape = 1; |
| } else |
| escape = 0; |
| break; |
| case SEARCH_FIN: |
| if (getstate(key) != ADDON_FIN) |
| continue; |
| state = ADDON_FIN; |
| cmd.addon = key; |
| switch (key) { |
| case 't': |
| searchdisplay = TOP; |
| break; |
| case 'm': |
| searchdisplay = MIDDLE; |
| break; |
| case 'b': |
| searchdisplay = BOTTOM; |
| break; |
| } |
| break; |
| case CMD_FIN: |
| case ADDON_FIN: |
| continue; |
| default: |
| state = getstate(key); |
| switch (state) { |
| case SIGN: |
| if (cmd.cmdlen != 0) { |
| state = INVALID; |
| continue; |
| } |
| state = COUNT; |
| /* fallthrough */ |
| case COUNT: |
| break; |
| case ADDON_FIN: |
| case INVALID: |
| continue; |
| default: |
| cmd.key = key; |
| } |
| } |
| write_all(STDOUT_FILENO, &key, 1); |
| cmd.cmdline[cmd.cmdlen++] = key; |
| cmd.cmdline[cmd.cmdlen] = '\0'; |
| if (nflag && state == CMD_FIN) |
| goto endprompt; |
| } |
| endprompt: |
| tcsetattr(STDOUT_FILENO, TCSADRAIN, &otio); |
| cline(); |
| cmd.count = getcount(cmd.cmdline); |
| } |
| |
| #ifdef HAVE_WIDECHAR |
| /* Remove backspace formatting, for searches in case MB_CUR_MAX > 1. */ |
| static char *colb_for_mb(char *s) |
| { |
| char *p = s; |
| wchar_t *wp, *wq; |
| size_t l = strlen(s), wl; |
| unsigned i; |
| |
| if ((wl = xmbstowcs(wbuf, p, sizeof wbuf)) == (size_t)-1) |
| return s; |
| for (wp = wbuf, wq = wbuf, i = 0; *wp != L'\0' && i < wl; wp++, wq++) { |
| if (*wp == L'\b') { |
| if (wq != wbuf) |
| wq -= 2; |
| else |
| wq--; |
| } else |
| *wq = *wp; |
| } |
| *wq = L'\0'; |
| wp = wbuf; |
| wcstombs(s, wp, l + 1); |
| |
| return s; |
| } |
| #endif |
| |
| /* Remove backspace formatting, for searches. */ |
| static char *colb(char *s) |
| { |
| char *p = s, *q; |
| |
| #ifdef HAVE_WIDECHAR |
| if (MB_CUR_MAX > 1) |
| return colb_for_mb(s); |
| #endif |
| |
| for (q = s; *p != '\0'; p++, q++) { |
| if (*p == '\b') { |
| if (q != s) |
| q -= 2; |
| else |
| q--; |
| } else |
| *q = *p; |
| } |
| *q = '\0'; |
| |
| return s; |
| } |
| |
| #ifdef HAVE_WIDECHAR |
| /* Convert non-printable characters to spaces in case MB_CUR_MAX > 1. */ |
| static void makeprint_for_mb(char *s, size_t l) |
| { |
| char *t = s; |
| wchar_t *wp = wbuf; |
| size_t wl; |
| |
| if ((wl = xmbstowcs(wbuf, t, sizeof wbuf)) == (size_t)-1) |
| return; |
| while (wl--) { |
| if (!iswprint(*wp) && *wp != L'\n' && *wp != L'\r' |
| && *wp != L'\b' && *wp != L'\t') |
| *wp = L'?'; |
| wp++; |
| } |
| wp = wbuf; |
| wcstombs(s, wp, l); |
| } |
| #endif |
| |
| /* Convert non-printable characters to spaces. */ |
| static void makeprint(char *s, size_t l) |
| { |
| #ifdef HAVE_WIDECHAR |
| if (MB_CUR_MAX > 1) { |
| makeprint_for_mb(s, l); |
| return; |
| } |
| #endif |
| |
| while (l--) { |
| if (!isprint(cuc(*s)) && *s != '\n' && *s != '\r' |
| && *s != '\b' && *s != '\t') |
| *s = '?'; |
| s++; |
| } |
| } |
| |
| /* Strip backslash characters from the given string. */ |
| static void striprs(char *s) |
| { |
| char *p = s; |
| |
| do { |
| if (*s == '\\') { |
| s++; |
| } |
| *p++ = *s; |
| } while (*s++ != '\0'); |
| } |
| |
| /* Extract the search pattern off the command line. */ |
| static char *makepat(void) |
| { |
| char *p; |
| |
| if (cmd.addon == '\0') |
| p = cmd.cmdline + strlen(cmd.cmdline) - 1; |
| else |
| p = cmd.cmdline + strlen(cmd.cmdline) - 2; |
| if (*p == cmd.key) |
| *p = '\0'; |
| else |
| *(p + 1) = '\0'; |
| if ((p = strchr(cmd.cmdline, cmd.key)) != NULL) { |
| p++; |
| striprs(p); |
| } |
| return p; |
| } |
| |
| /* Process errors that occurred in temporary file operations. */ |
| static void __attribute__((__noreturn__)) tmperr(FILE *f, const char *ftype) |
| { |
| if (ferror(f)) |
| warn(_("Read error from %s file"), ftype); |
| else if (feof(f)) |
| /* Most likely '\0' in input. */ |
| warnx(_("Unexpected EOF in %s file"), ftype); |
| else |
| warn(_("Unknown error in %s file"), ftype); |
| quit(++exitstatus); |
| } |
| |
| /* Read the file and respond to user input. Beware: long and ugly. */ |
| static void pgfile(FILE *f, const char *name) |
| { |
| off_t pos, oldpos, fpos; |
| /* These are the line counters: |
| * line the line desired to display |
| * fline the current line of the input file |
| * bline the current line of the file buffer |
| * oldline the line before a search was started |
| * eofline the last line of the file if it is already reached |
| * dline the line on the display */ |
| off_t line = 0, fline = 0, bline = 0, oldline = 0, eofline = 0; |
| int dline = 0; |
| int search = 0; |
| unsigned searchcount = 0; |
| /* Advance to EOF immediately. */ |
| int seekeof = 0; |
| /* EOF has been reached by `line'. */ |
| int eof = 0; |
| /* f and fbuf refer to the same file. */ |
| int nobuf = 0; |
| int sig; |
| int rerror; |
| size_t sz; |
| char b[READBUF + 1]; |
| char *p; |
| /* fbuf an exact copy of the input file as it gets read |
| * find index table for input, one entry per line |
| * save for the s command, to save to a file */ |
| FILE *fbuf, *find, *save; |
| |
| if (ontty == 0) { |
| /* Just copy stdin to stdout. */ |
| while ((sz = fread(b, sizeof *b, READBUF, f)) != 0) |
| write_all(STDOUT_FILENO, b, sz); |
| if (ferror(f)) { |
| warn("%s", name); |
| exitstatus++; |
| } |
| return; |
| } |
| if ((fpos = fseeko(f, (off_t)0, SEEK_SET)) == -1) |
| fbuf = tmpfile(); |
| else { |
| fbuf = f; |
| nobuf = 1; |
| } |
| find = tmpfile(); |
| if (fbuf == NULL || find == NULL) { |
| warn(_("Cannot create temporary file")); |
| quit(++exitstatus); |
| } |
| if (searchfor) { |
| search = FORWARD; |
| oldline = 0; |
| searchcount = 1; |
| rerror = regcomp(&re, searchfor, REG_NOSUB | REG_NEWLINE); |
| if (rerror != 0) { |
| mesg(_("RE error: ")); |
| regerror(rerror, &re, b, READBUF); |
| mesg(b); |
| goto newcmd; |
| } |
| remembered = 1; |
| } |
| |
| for (line = startline;;) { |
| /* Get a line from input file or buffer. */ |
| if (line < bline) { |
| fseeko(find, line * sizeof pos, SEEK_SET); |
| if (fread(&pos, sizeof pos, 1, find) == 0) |
| tmperr(find, "index"); |
| fseeko(find, (off_t)0, SEEK_END); |
| fseeko(fbuf, pos, SEEK_SET); |
| if (fgets(b, READBUF, fbuf) == NULL) |
| tmperr(fbuf, "buffer"); |
| } else if (eofline == 0) { |
| fseeko(find, (off_t)0, SEEK_END); |
| do { |
| if (!nobuf) |
| fseeko(fbuf, (off_t)0, SEEK_END); |
| pos = ftello(fbuf); |
| if ((sig = setjmp(jmpenv)) != 0) { |
| /* We got a signal. */ |
| canjump = 0; |
| my_sigrelse(sig); |
| fseeko(fbuf, pos, SEEK_SET); |
| *b = '\0'; |
| dline = pagelen; |
| break; |
| } else { |
| if (nobuf) |
| fseeko(f, fpos, SEEK_SET); |
| canjump = 1; |
| p = fgets(b, READBUF, f); |
| if (nobuf) |
| if ((fpos = ftello(f)) == -1) |
| warn("%s", name); |
| canjump = 0; |
| } |
| if (p == NULL || *b == '\0') { |
| if (ferror(f)) |
| warn("%s", name); |
| eofline = fline; |
| eof = 1; |
| break; |
| } else { |
| if (!nobuf) |
| fputs(b, fbuf); |
| fwrite_all(&pos, sizeof pos, 1, find); |
| if (!fflag) { |
| oldpos = pos; |
| p = b; |
| while (*(p = endline(ttycols, |
| p)) |
| != '\0') { |
| pos = oldpos + (p - b); |
| fwrite_all(&pos, |
| sizeof pos, |
| 1, find); |
| fline++; |
| bline++; |
| } |
| } |
| fline++; |
| } |
| } while (line > bline++); |
| } else { |
| /* eofline != 0 */ |
| eof = 1; |
| } |
| if (search == FORWARD && remembered == 1) { |
| if (eof) { |
| line = oldline; |
| search = searchcount = 0; |
| mesg(_("Pattern not found")); |
| eof = 0; |
| goto newcmd; |
| } |
| line++; |
| colb(b); |
| if (regexec(&re, b, 0, NULL, 0) == 0) { |
| searchcount--; |
| } |
| if (searchcount == 0) { |
| search = dline = 0; |
| switch (searchdisplay) { |
| case TOP: |
| line -= 1; |
| break; |
| case MIDDLE: |
| line -= pagelen / 2 + 1; |
| break; |
| case BOTTOM: |
| line -= pagelen; |
| break; |
| } |
| skip(1); |
| } |
| continue; |
| } else if (eof) { |
| /* We are not searching. */ |
| line = bline; |
| } else if (*b != '\0') { |
| if (cflag && clear_screen) { |
| switch (dline) { |
| case 0: |
| tputs(clear_screen, STDOUT_FILENO, |
| outcap); |
| dline = 0; |
| } |
| } |
| line++; |
| if (eofline && line == eofline) |
| eof = 1; |
| dline++; |
| if ((sig = setjmp(jmpenv)) != 0) { |
| /* We got a signal. */ |
| canjump = 0; |
| my_sigrelse(sig); |
| dline = pagelen; |
| } else { |
| p = endline(ttycols, b); |
| sz = p - b; |
| makeprint(b, sz); |
| canjump = 1; |
| write_all(STDOUT_FILENO, b, sz); |
| canjump = 0; |
| } |
| } |
| if (dline >= pagelen || eof) { |
| /* Time for prompting! */ |
| if (eof && seekeof) { |
| eof = seekeof = 0; |
| if (line >= pagelen) |
| line -= pagelen; |
| else |
| line = 0; |
| dline = -1; |
| continue; |
| } |
| newcmd: |
| if (eof) { |
| if (fline == 0 || eflag) |
| break; |
| mesg(_("(EOF)")); |
| } |
| prompt((line - 1) / pagelen + 1); |
| switch (cmd.key) { |
| case '/': |
| /* Search forward. */ |
| search = FORWARD; |
| oldline = line; |
| searchcount = cmd.count; |
| p = makepat(); |
| if (p != NULL && *p) { |
| if (remembered == 1) |
| regfree(&re); |
| rerror = regcomp(&re, p, |
| REG_NOSUB | |
| REG_NEWLINE); |
| if (rerror != 0) { |
| mesg(_("RE error: ")); |
| sz = regerror(rerror, &re, |
| b, READBUF); |
| mesg(b); |
| goto newcmd; |
| } |
| remembered = 1; |
| } else if (remembered == 0) { |
| mesg(_("No remembered search string")); |
| goto newcmd; |
| } |
| continue; |
| case '?': |
| case '^': |
| /* Search backward. */ |
| search = BACKWARD; |
| oldline = line; |
| searchcount = cmd.count; |
| p = makepat(); |
| if (p != NULL && *p) { |
| if (remembered == 1) |
| regfree(&re); |
| rerror = regcomp(&re, p, |
| REG_NOSUB | |
| REG_NEWLINE); |
| if (rerror != 0) { |
| mesg(_("RE error: ")); |
| regerror(rerror, &re, |
| b, READBUF); |
| mesg(b); |
| goto newcmd; |
| } |
| remembered = 1; |
| } else if (remembered == 0) { |
| mesg(_("No remembered search string")); |
| goto newcmd; |
| } |
| line -= pagelen; |
| if (line <= 0) |
| goto notfound_bw; |
| while (line) { |
| fseeko(find, --line * sizeof pos, |
| SEEK_SET); |
| if (fread(&pos, sizeof pos, 1, find) == |
| 0) |
| tmperr(find, "index"); |
| fseeko(find, (off_t)0, SEEK_END); |
| fseeko(fbuf, pos, SEEK_SET); |
| if (fgets(b, READBUF, fbuf) == NULL) |
| tmperr(fbuf, "buffer"); |
| colb(b); |
| if (regexec(&re, b, 0, NULL, 0) == 0) |
| searchcount--; |
| if (searchcount == 0) |
| goto found_bw; |
| } |
| notfound_bw: |
| line = oldline; |
| search = searchcount = 0; |
| mesg(_("Pattern not found")); |
| goto newcmd; |
| found_bw: |
| eof = search = dline = 0; |
| skip(-1); |
| switch (searchdisplay) { |
| case TOP: |
| /* line -= 1; */ |
| break; |
| case MIDDLE: |
| line -= pagelen / 2; |
| break; |
| case BOTTOM: |
| if (line != 0) |
| dline = -1; |
| line -= pagelen; |
| break; |
| } |
| if (line < 0) |
| line = 0; |
| continue; |
| case 's': |
| /* Save to file. */ |
| p = cmd.cmdline; |
| while (*++p == ' ') ; |
| if (*p == '\0') |
| goto newcmd; |
| save = fopen(p, "wb"); |
| if (save == NULL) { |
| cmd.count = errno; |
| mesg(_("cannot open ")); |
| mesg(p); |
| mesg(": "); |
| mesg(strerror(cmd.count)); |
| goto newcmd; |
| } |
| /* Advance to EOF. */ |
| fseeko(find, (off_t)0, SEEK_END); |
| for (;;) { |
| if (!nobuf) |
| fseeko(fbuf, (off_t)0, |
| SEEK_END); |
| pos = ftello(fbuf); |
| if (fgets(b, READBUF, f) == NULL) { |
| eofline = fline; |
| break; |
| } |
| if (!nobuf) |
| fputs(b, fbuf); |
| fwrite_all(&pos, sizeof pos, 1, find); |
| if (!fflag) { |
| oldpos = pos; |
| p = b; |
| while (*(p = endline(ttycols, |
| p)) |
| != '\0') { |
| pos = oldpos + (p - b); |
| fwrite_all(&pos, |
| sizeof pos, |
| 1, find); |
| fline++; |
| bline++; |
| } |
| } |
| fline++; |
| bline++; |
| } |
| fseeko(fbuf, (off_t)0, SEEK_SET); |
| while ((sz = fread(b, sizeof *b, READBUF, |
| fbuf)) != 0) { |
| /* No error check for compat. */ |
| fwrite_all(b, sizeof *b, sz, save); |
| } |
| if (close_stream(save) != 0) { |
| cmd.count = errno; |
| mesg(_("write failed")); |
| mesg(": "); |
| mesg(p); |
| mesg(strerror(cmd.count)); |
| goto newcmd; |
| } |
| fseeko(fbuf, (off_t)0, SEEK_END); |
| mesg(_("saved")); |
| goto newcmd; |
| case 'l': |
| /* Next line. */ |
| if (*cmd.cmdline != 'l') |
| eof = 0; |
| if (cmd.count == 0) |
| cmd.count = 1; /* compat */ |
| if (isdigit(cuc(*cmd.cmdline))) { |
| line = cmd.count - 2; |
| dline = 0; |
| } else { |
| if (cmd.count != 1) { |
| line += cmd.count - 1 - pagelen; |
| dline = -1; |
| skip(cmd.count); |
| } |
| /* Nothing to do if (count == 1) */ |
| } |
| break; |
| case 'd': |
| /* Half screen forward. */ |
| case '\004': /* ^D */ |
| if (*cmd.cmdline != cmd.key) |
| eof = 0; |
| if (cmd.count == 0) |
| cmd.count = 1; /* compat */ |
| line += (cmd.count * pagelen / 2) |
| - pagelen - 1; |
| dline = -1; |
| skip(cmd.count); |
| break; |
| case 'f': |
| /* Skip forward. */ |
| if (cmd.count <= 0) |
| cmd.count = 1; /* compat */ |
| line += cmd.count * pagelen - 2; |
| if (eof) |
| line += 2; |
| if (*cmd.cmdline != 'f') |
| eof = 0; |
| else if (eof) |
| break; |
| if (eofline && line >= eofline) |
| line -= pagelen; |
| dline = -1; |
| skip(cmd.count); |
| break; |
| case '\0': |
| /* Just a number, or '-', or <newline>. */ |
| if (cmd.count == 0) |
| cmd.count = 1; /* compat */ |
| if (isdigit(cuc(*cmd.cmdline))) |
| line = (cmd.count - 1) * pagelen - 2; |
| else |
| line += (cmd.count - 1) |
| * (pagelen - 1) - 2; |
| if (*cmd.cmdline != '\0') |
| eof = 0; |
| if (cmd.count != 1) { |
| skip(cmd.count); |
| dline = -1; |
| } else { |
| dline = 1; |
| line += 2; |
| } |
| break; |
| case '$': |
| /* Advance to EOF. */ |
| if (!eof) |
| skip(1); |
| eof = 0; |
| line = LONG_MAX; |
| seekeof = 1; |
| dline = -1; |
| break; |
| case '.': |
| case '\014': /* ^L */ |
| /* Repaint screen. */ |
| eof = 0; |
| if (line >= pagelen) |
| line -= pagelen; |
| else |
| line = 0; |
| dline = 0; |
| break; |
| case '!': |
| /* Shell escape. */ |
| if (rflag) { |
| mesg(program_invocation_short_name); |
| mesg(_(": !command not allowed in " |
| "rflag mode.\n")); |
| } else { |
| pid_t cpid; |
| |
| write_all(STDOUT_FILENO, cmd.cmdline, |
| strlen(cmd.cmdline)); |
| write_all(STDOUT_FILENO, "\n", 1); |
| my_sigset(SIGINT, SIG_IGN); |
| my_sigset(SIGQUIT, SIG_IGN); |
| switch (cpid = fork()) { |
| case 0: |
| { |
| const char *sh = getenv("SHELL"); |
| if (!sh) |
| sh = "/bin/sh"; |
| if (!nobuf) |
| fclose(fbuf); |
| fclose(find); |
| if (isatty(0) == 0) { |
| close(0); |
| open(tty, O_RDONLY); |
| } else { |
| fclose(f); |
| } |
| my_sigset(SIGINT, oldint); |
| my_sigset(SIGQUIT, oldquit); |
| my_sigset(SIGTERM, oldterm); |
| execl(sh, sh, "-c", |
| cmd.cmdline + 1, NULL); |
| errexec(sh); |
| break; |
| } |
| case -1: |
| mesg(_("fork() failed, " |
| "try again later\n")); |
| break; |
| default: |
| while (wait(NULL) != cpid) ; |
| } |
| my_sigset(SIGINT, sighandler); |
| my_sigset(SIGQUIT, sighandler); |
| mesg("!\n"); |
| } |
| goto newcmd; |
| case 'h': |
| { |
| /* Help! */ |
| const char *help = _(helpscreen); |
| write_all(STDOUT_FILENO, copyright, |
| strlen(copyright)); |
| write_all(STDOUT_FILENO, help, |
| strlen(help)); |
| goto newcmd; |
| } |
| case 'n': |
| /* Next file. */ |
| if (cmd.count == 0) |
| cmd.count = 1; |
| nextfile = cmd.count; |
| if (checkf()) { |
| nextfile = 1; |
| goto newcmd; |
| } |
| eof = 1; |
| break; |
| case 'p': |
| /* Previous file. */ |
| if (cmd.count == 0) |
| cmd.count = 1; |
| nextfile = 0 - cmd.count; |
| if (checkf()) { |
| nextfile = 1; |
| goto newcmd; |
| } |
| eof = 1; |
| break; |
| case 'q': |
| case 'Q': |
| /* Exit pg. */ |
| quit(exitstatus); |
| /* NOTREACHED */ |
| case 'w': |
| case 'z': |
| /* Set window size. */ |
| if (cmd.count < 0) |
| cmd.count = 0; |
| if (*cmd.cmdline != cmd.key) |
| pagelen = ++cmd.count; |
| dline = 1; |
| break; |
| } |
| if (line <= 0) { |
| line = 0; |
| dline = 0; |
| } |
| if (cflag && dline == 1) { |
| dline = 0; |
| line--; |
| } |
| } |
| if (eof) |
| break; |
| } |
| fclose(find); |
| if (!nobuf) |
| fclose(fbuf); |
| } |
| |
| static int parse_arguments(int arg, int argc, char **argv) |
| { |
| FILE *input; |
| |
| files.first = arg; |
| files.last = arg + argc - 1; |
| for (; argv[arg]; arg += nextfile) { |
| nextfile = 1; |
| files.current = arg; |
| if (argc > 2) { |
| static int firsttime; |
| firsttime++; |
| if (firsttime > 1) { |
| mesg(_("(Next file: ")); |
| mesg(argv[arg]); |
| mesg(")"); |
| newfile: |
| if (ontty) { |
| prompt(-1); |
| switch (cmd.key) { |
| case 'n': |
| /* Next file. */ |
| if (cmd.count == 0) |
| cmd.count = 1; |
| nextfile = cmd.count; |
| if (checkf()) { |
| nextfile = 1; |
| mesg(":"); |
| goto newfile; |
| } |
| continue; |
| case 'p': |
| /* Previous file. */ |
| if (cmd.count == 0) |
| cmd.count = 1; |
| nextfile = 0 - cmd.count; |
| if (checkf()) { |
| nextfile = 1; |
| mesg(":"); |
| goto newfile; |
| } |
| continue; |
| case 'q': |
| case 'Q': |
| quit(exitstatus); |
| } |
| } else |
| mesg("\n"); |
| } |
| } |
| if (strcmp(argv[arg], "-") == 0) |
| input = stdin; |
| else { |
| input = fopen(argv[arg], "r"); |
| if (input == NULL) { |
| warn("%s", argv[arg]); |
| exitstatus++; |
| continue; |
| } |
| } |
| if (ontty == 0 && argc > 2) { |
| /* Use the prefix as specified by SUSv2. */ |
| write_all(STDOUT_FILENO, "::::::::::::::\n", 15); |
| write_all(STDOUT_FILENO, argv[arg], strlen(argv[arg])); |
| write_all(STDOUT_FILENO, "\n::::::::::::::\n", 16); |
| } |
| pgfile(input, argv[arg]); |
| if (input != stdin) |
| fclose(input); |
| } |
| return exitstatus; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int arg, i; |
| char *p; |
| |
| xasprintf(©right, |
| _("%s %s Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved.\n"), |
| program_invocation_short_name, PACKAGE_VERSION); |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| atexit(close_stdout); |
| |
| if (tcgetattr(STDOUT_FILENO, &otio) == 0) { |
| ontty = 1; |
| oldint = my_sigset(SIGINT, sighandler); |
| oldquit = my_sigset(SIGQUIT, sighandler); |
| oldterm = my_sigset(SIGTERM, sighandler); |
| setlocale(LC_CTYPE, ""); |
| setlocale(LC_COLLATE, ""); |
| tty = ttyname(STDOUT_FILENO); |
| setupterm(NULL, STDOUT_FILENO, &tinfostat); |
| getwinsize(); |
| helpscreen = _(helpscreen); |
| } |
| for (arg = 1; argv[arg]; arg++) { |
| if (*argv[arg] == '+') |
| continue; |
| if (*argv[arg] != '-' || argv[arg][1] == '\0') |
| break; |
| argc--; |
| |
| if (!strcmp(argv[arg], "--help")) { |
| usage(); |
| } |
| |
| if (!strcmp(argv[arg], "--version")) { |
| printf(UTIL_LINUX_VERSION); |
| return EXIT_SUCCESS; |
| } |
| |
| for (i = 1; argv[arg][i]; i++) { |
| switch (argv[arg][i]) { |
| case '-': |
| if (i != 1 || argv[arg][i + 1]) |
| invopt(&argv[arg][i]); |
| goto endargs; |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case '0': |
| pagelen = strtol_or_err(argv[arg] + 1, |
| _("failed to parse number of lines per page")); |
| havepagelen = 1; |
| goto nextarg; |
| case 'c': |
| cflag = 1; |
| break; |
| case 'e': |
| eflag = 1; |
| break; |
| case 'f': |
| fflag = 1; |
| break; |
| case 'n': |
| nflag = 1; |
| break; |
| case 'p': |
| if (argv[arg][i + 1]) { |
| pstring = &argv[arg][i + 1]; |
| } else if (argv[++arg]) { |
| --argc; |
| pstring = argv[arg]; |
| } else |
| needarg("-p"); |
| goto nextarg; |
| case 'r': |
| rflag = 1; |
| break; |
| case 's': |
| sflag = 1; |
| break; |
| case 'h': |
| usage(); |
| case 'V': |
| printf(UTIL_LINUX_VERSION); |
| return EXIT_SUCCESS; |
| default: |
| invopt(&argv[arg][i]); |
| } |
| } |
| nextarg: |
| ; |
| } |
| endargs: |
| for (arg = 1; argv[arg]; arg++) { |
| if (*argv[arg] == '-') { |
| if (argv[arg][1] == '-') { |
| arg++; |
| break; |
| } |
| if (argv[arg][1] == '\0') |
| break; |
| if (argv[arg][1] == 'p' && argv[arg][2] == '\0') |
| arg++; |
| continue; |
| } |
| if (*argv[arg] != '+') |
| break; |
| argc--; |
| switch (*(argv[arg] + 1)) { |
| case '\0': |
| needarg("+"); |
| /*NOTREACHED*/ |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case '0': |
| startline = strtol_or_err(argv[arg] + 1, |
| _("failed to parse number of lines per page")); |
| break; |
| case '/': |
| searchfor = argv[arg] + 2; |
| if (*searchfor == '\0') |
| needarg("+/"); |
| p = searchfor + strlen(searchfor) - 1; |
| if (*p == '/') |
| *p = '\0'; |
| if (*searchfor == '\0') |
| needarg("+/"); |
| break; |
| default: |
| invopt(argv[arg]); |
| } |
| } |
| if (argc == 1) |
| pgfile(stdin, "stdin"); |
| else |
| exitstatus = parse_arguments(arg, argc, argv); |
| |
| quit(exitstatus); |
| /* NOTREACHED */ |
| return 0; |
| } |