summaryrefslogtreecommitdiffstats
path: root/text-utils/pg.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--text-utils/pg.c1690
1 files changed, 1690 insertions, 0 deletions
diff --git a/text-utils/pg.c b/text-utils/pg.c
new file mode 100644
index 0000000..adb3840
--- /dev/null
+++ b/text-utils/pg.c
@@ -0,0 +1,1690 @@
+/*
+ * 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
+ if (initialized == 0) {
+ uint32_t tmp = 0;
+
+ if (ul_strtou32(getenv("LINES"), &tmp, 10) == 0)
+ envlines = tmp;
+ if (ul_strtou32(getenv("COLUMNS"), &tmp, 10) == 0)
+ envcols = tmp;
+
+ /* 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, 0)) == (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 (ul_strtos32(*buf == '+' ? buf + 1 : buf, &i, 10) != 0) {
+ i = -1;
+ }
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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, (char *)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(&copyright,
+ _("%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);
+ close_stdout_atexit();
+
+ 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")) {
+ print_version(EXIT_SUCCESS);
+ 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':
+ print_version(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;
+}