/* * 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(©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); 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; }