| /* |
| * 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 */ |
| |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #ifndef TIOCGWINSZ |
| #include <sys/ioctl.h> |
| #endif |
| #include <sys/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> |
| #include <libgen.h> |
| |
| #ifdef HAVE_NCURSES_H |
| #include <ncurses.h> |
| #elif defined(HAVE_NCURSES_NCURSES_H) |
| #include <ncurses/ncurses.h> |
| #endif |
| |
| #include <term.h> |
| |
| #include "nls.h" |
| #include "widechar.h" |
| |
| #define READBUF LINE_MAX /* size of input buffer */ |
| #define CMDBUF 255 /* size of command buffer */ |
| #define TABSIZE 8 /* spaces consumed by tab character */ |
| |
| /* |
| * Avoid the message "`var' might be clobbered by `longjmp' or `vfork'" |
| */ |
| #define CLOBBGRD(a) (void)(&(a)); |
| |
| #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 |
| */ |
| 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() |
| */ |
| struct { |
| int first; |
| int current; |
| int last; |
| } files; |
| |
| void (*oldint)(int); /* old SIGINT handler */ |
| void (*oldquit)(int); /* old SIGQUIT handler */ |
| void (*oldterm)(int); /* old SIGTERM handler */ |
| char *tty; /* result of ttyname(1) */ |
| char *progname; /* program name */ |
| unsigned ontty; /* whether running on tty device */ |
| unsigned exitstatus; /* exit status */ |
| int pagelen = 23; /* lines on a single screen page */ |
| int ttycols = 79; /* screen columns (starting at 0) */ |
| struct termios otio; /* old termios settings */ |
| int tinfostat = -1; /* terminfo routines initialized */ |
| int searchdisplay = TOP; /* matching line position */ |
| regex_t re; /* regular expression to search for */ |
| int remembered; /* have a remembered search string */ |
| int cflag; /* clear screen before each page */ |
| int eflag; /* suppress (EOF) */ |
| int fflag; /* do not split lines */ |
| int nflag; /* no newline for commands required */ |
| int rflag; /* "restricted" pg */ |
| int sflag; /* use standout mode */ |
| char *pstring = ":"; /* prompt string */ |
| char *searchfor; /* search pattern from argv[] */ |
| int havepagelen; /* page length is manually defined */ |
| long startline; /* start line from argv[] */ |
| int nextfile = 1; /* files to advance */ |
| jmp_buf jmpenv; /* jump from signal handlers */ |
| int canjump; /* jmpenv is valid */ |
| wchar_t wbuf[READBUF]; /* used in several widechar routines */ |
| |
| const char *copyright = |
| "@(#)pg 1.44 2/8/02. Copyright (c) 2000-2001 Gunnar Ritter. "; |
| const char *helpscreen = N_("All rights reserved.\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 |
| |
| /* |
| * Quit pg. |
| */ |
| static void |
| quit(int status) |
| { |
| exit(status < 0100 ? status : 077); |
| } |
| |
| /* |
| * Memory allocator including check. |
| */ |
| static char * |
| smalloc(size_t s) |
| { |
| char *m = (char *)malloc(s); |
| if (m == NULL) { |
| const char *p = _("Out of memory\n"); |
| write(2, p, strlen(p)); |
| quit(++exitstatus); |
| } |
| return m; |
| } |
| |
| /* |
| * Usage message and similar routines. |
| */ |
| static void |
| usage(void) |
| { |
| fprintf(stderr, _("%s: Usage: %s [-number] [-p string] [-cefnrs] " |
| "[+line] [+/pattern/] [files]\n"), |
| progname, progname); |
| quit(2); |
| } |
| |
| static void |
| needarg(char *s) |
| { |
| fprintf(stderr, _("%s: option requires an argument -- %s\n"), |
| progname, s); |
| usage(); |
| } |
| |
| static void |
| invopt(char *s) |
| { |
| fprintf(stderr, _("%s: illegal option -- %s\n"), progname, s); |
| usage(); |
| } |
| |
| #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; |
| |
| mbtowc(pwcs, NULL, MB_CUR_MAX); |
| 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'; |
| mbtowc(pwcs, NULL, MB_CUR_MAX); |
| return nwcs - n; |
| } |
| #endif |
| |
| /* |
| * Helper function for tputs(). |
| */ |
| static int |
| outcap(int i) |
| { |
| char c = i; |
| return write(1, &c, 1); |
| } |
| |
| /* |
| * Write messages to terminal. |
| */ |
| static void |
| mesg(char *message) |
| { |
| if (ontty == 0) |
| return; |
| if (*message != '\n' && sflag) |
| vidputs(A_STANDOUT, outcap); |
| write(1, 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(1, 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(1, 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) |
| { |
| unsigned 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 += TABSIZE - (pos % 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)) == -1) |
| return s + 1; |
| return s + pos; |
| } |
| #endif |
| |
| /* |
| * 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 += TABSIZE - (pos % 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 = (char *)smalloc(ttycols + 2); |
| memset(buf, ' ', ttycols + 2); |
| buf[0] = '\r'; |
| buf[ttycols + 1] = '\r'; |
| write(1, 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, 1, outcap); |
| #endif /* PG_BELL */ |
| 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 = (char *)smalloc(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') |
| 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(1, &tio); |
| tio.c_lflag &= ~(ICANON | ECHO); |
| tio.c_cc[VMIN] = 1; |
| tio.c_cc[VTIME] = 0; |
| tcsetattr(1, TCSADRAIN, &tio); |
| tcflush(1, TCIFLUSH); |
| for (;;) { |
| switch (read(1, &key, 1)) { |
| case 0: quit(0); |
| /*NOTREACHED*/ |
| case -1: quit(1); |
| } |
| if (key == tio.c_cc[VERASE]) { |
| if (cmd.cmdlen) { |
| write(1, "\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; |
| /*FALLTHRU*/ |
| 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; |
| /*FALLTHRU*/ |
| case COUNT: |
| break; |
| case ADDON_FIN: |
| case INVALID: |
| continue; |
| default: |
| cmd.key = key; |
| } |
| } |
| write(1, &key, 1); |
| cmd.cmdline[cmd.cmdlen++] = key; |
| cmd.cmdline[cmd.cmdlen] = '\0'; |
| if (nflag && state == CMD_FIN) |
| goto endprompt; |
| } |
| endprompt: |
| tcsetattr(1, 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 nonprintable 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 nonprintable characters to spaces. |
| */ |
| static void |
| makeprint(char *s, size_t l) |
| { |
| #ifdef HAVE_WIDECHAR |
| if (MB_CUR_MAX > 1) |
| return makeprint_for_mb(s, l); |
| #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 |
| tmperr(FILE *f, char *ftype) |
| { |
| if (ferror(f)) |
| fprintf(stderr, _("%s: Read error from %s file\n"), |
| progname, ftype); |
| else if (feof(f)) |
| /* |
| * Most likely '\0' in input. |
| */ |
| fprintf(stderr, _("%s: Unexpected EOF in %s file\n"), |
| progname, ftype); |
| else |
| fprintf(stderr, _("%s: Unknown error in %s file\n"), |
| progname, ftype); |
| quit(++exitstatus); |
| } |
| |
| /* |
| * perror()-like, but showing the program's name. |
| */ |
| static void |
| pgerror(int eno, char *string) |
| { |
| fprintf(stderr, "%s: %s: %s\n", progname, string, strerror(eno)); |
| } |
| |
| /* |
| * Read the file and respond to user input. |
| * Beware: long and ugly. |
| */ |
| static void |
| pgfile(FILE *f, char *name) |
| { |
| off_t pos, oldpos, fpos; |
| off_t line = 0, fline = 0, bline = 0, oldline = 0, eofline = 0; |
| int dline = 0; |
| /* |
| * 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 |
| */ |
| 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; |
| |
| /* silence compiler - it may warn about longjmp() */ |
| CLOBBGRD(line); |
| CLOBBGRD(fline); |
| CLOBBGRD(bline); |
| CLOBBGRD(oldline); |
| CLOBBGRD(eofline); |
| CLOBBGRD(dline); |
| CLOBBGRD(ttycols); |
| CLOBBGRD(search); |
| CLOBBGRD(searchcount); |
| CLOBBGRD(seekeof); |
| CLOBBGRD(eof); |
| CLOBBGRD(fpos); |
| CLOBBGRD(nobuf); |
| CLOBBGRD(fbuf); |
| |
| if (ontty == 0) { |
| /* |
| * Just copy stdin to stdout. |
| */ |
| while ((sz = fread(b, sizeof *b, READBUF, f)) != 0) |
| write(1, b, sz); |
| if (ferror(f)) { |
| pgerror(errno, 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) { |
| fprintf(stderr, _("%s: Cannot create tempfile\n"), progname); |
| 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) |
| pgerror(errno, name); |
| canjump = 0; |
| } |
| if (p == NULL || *b == '\0') { |
| if (ferror(f)) |
| pgerror(errno, name); |
| eofline = fline; |
| eof = 1; |
| break; |
| } else { |
| if (!nobuf) |
| fputs(b, fbuf); |
| fwrite(&pos, sizeof pos, 1, find); |
| if (!fflag) { |
| oldpos = pos; |
| p = b; |
| while (*(p = endline(ttycols, |
| p)) |
| != '\0') { |
| pos = oldpos + (p - b); |
| fwrite(&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, 1, 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(1, 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(&pos, sizeof pos, 1, find); |
| if (!fflag) { |
| oldpos = pos; |
| p = b; |
| while (*(p = endline(ttycols, |
| p)) |
| != '\0') { |
| pos = oldpos + (p - b); |
| fwrite(&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(b, sizeof *b, sz, save); |
| } |
| fclose(save); |
| 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(progname); |
| mesg(_(": !command not allowed in " |
| "rflag mode.\n")); |
| } else { |
| pid_t cpid; |
| |
| write(1, cmd.cmdline, |
| strlen(cmd.cmdline)); |
| write(1, "\n", 1); |
| my_sigset(SIGINT, SIG_IGN); |
| my_sigset(SIGQUIT, SIG_IGN); |
| switch (cpid = fork()) { |
| case 0: |
| p = getenv("SHELL"); |
| if (p == NULL) p = "/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(p, p, "-c", |
| cmd.cmdline + 1, NULL); |
| pgerror(errno, p); |
| _exit(0177); |
| /*NOTREACHED*/ |
| 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(1, copyright + 4, strlen(copyright + 4)); |
| write(1, 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); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| int arg, i; |
| char *p; |
| FILE *input; |
| |
| progname = basename(argv[0]); |
| |
| setlocale(LC_MESSAGES, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| |
| if (tcgetattr(1, &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(1); |
| setupterm(NULL, 1, &tinfostat); |
| getwinsize(); |
| helpscreen = _(helpscreen); |
| } |
| for (arg = 1; argv[arg]; arg++) { |
| if (*argv[arg] == '+') |
| continue; |
| if (*argv[arg] != '-' || argv[arg][1] == '\0') |
| break; |
| argc--; |
| 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 = atoi(argv[arg] + i); |
| 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; |
| 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 = atoi(argv[arg] + 1); |
| 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 { |
| 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) { |
| pgerror(errno, argv[arg]); |
| exitstatus++; |
| continue; |
| } |
| } |
| if (ontty == 0 && argc > 2) { |
| /* |
| * Use the prefix as specified by SUSv2. |
| */ |
| write(1, "::::::::::::::\n", 15); |
| write(1, argv[arg], strlen(argv[arg])); |
| write(1, "\n::::::::::::::\n", 16); |
| } |
| pgfile(input, argv[arg]); |
| if (input != stdin) |
| fclose(input); |
| } |
| } |
| quit(exitstatus); |
| /*NOTREACHED*/ |
| return 0; |
| } |