From 63cccae4684f83d2a462bc8abf24e51d1bd6efb6 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Thu, 7 Dec 2006 00:25:58 +0100 Subject: Imported from util-linux-2.11t tarball. --- text-utils/pg.c | 1775 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1775 insertions(+) create mode 100644 text-utils/pg.c (limited to 'text-utils/pg.c') diff --git a/text-utils/pg.c b/text-utils/pg.c new file mode 100644 index 000000000..57290756c --- /dev/null +++ b/text-utils/pg.c @@ -0,0 +1,1775 @@ +/* + * 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 */ + +/* + * #define _XOPEN_SOURCE 500L + * + * Adding this define gives us the correct prototypes for fseeko, ftello, + * but (for some glibc versions) conflicting prototype for wcwidth. + * So, avoid defining _XOPEN_SOURCE, and give prototypes for fseeko, ftello + * by hand. + */ + +#include +#include +#include +#ifndef TIOCGWINSZ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nls.h" +#include "widechar.h" +#include "../defines.h" /* for HAVE_fseeko */ + +#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 = "All rights reserved.\n\ +-------------------------------------------------------\n\ + h this screen\n\ + q or Q quit program\n\ + 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 (next page); -1 (previous page); 1 (first page).\n\ +\n\ +See pg(1) for more information.\n\ +-------------------------------------------------------\n"; + +#ifdef HAVE_fseeko + extern int fseeko(FILE *f, off_t off, int whence); + extern off_t ftello(FILE *f); + #define my_fseeko fseeko + #define my_ftello ftello +#else + static int my_fseeko(FILE *f, off_t off, int whence) { + return fseek(f, (long) off, whence); + } + static off_t my_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) { + write(2, "Out of memory\n", 14); + 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 ENABLE_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 ENABLE_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: + pos += wcwidth(*p); + } + 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 ENABLE_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: +#ifndef PGNOBELL + if (bell) + tputs(bell, 1, outcap); +#endif /* PGNOBELL */ + 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 ENABLE_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 ENABLE_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 ENABLE_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 ENABLE_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 = my_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) { + my_fseeko(find, line * sizeof pos, SEEK_SET); + if (fread(&pos, sizeof pos, 1, find) == 0) + tmperr(find, "index"); + my_fseeko(find, (off_t)0, SEEK_END); + my_fseeko(fbuf, pos, SEEK_SET); + if (fgets(b, READBUF, fbuf) == NULL) + tmperr(fbuf, "buffer"); + } else if (eofline == 0) { + my_fseeko(find, (off_t)0, SEEK_END); + do { + if (!nobuf) + my_fseeko(fbuf, (off_t)0, SEEK_END); + pos = my_ftello(fbuf); + if ((sig = setjmp(jmpenv)) != 0) { + /* + * We got a signal. + */ + canjump = 0; + my_sigrelse(sig); + my_fseeko(fbuf, pos, SEEK_SET); + *b = '\0'; + dline = pagelen; + break; + } else { + if (nobuf) + my_fseeko(f, fpos, SEEK_SET); + canjump = 1; + p = fgets(b, READBUF, f); + if (nobuf) + if ((fpos = my_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) { + 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) { + my_fseeko(find, --line * sizeof pos, + SEEK_SET); + if(fread(&pos, sizeof pos, 1,find)==0) + tmperr(find, "index"); + my_fseeko(find, (off_t)0, SEEK_END); + my_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. + */ + my_fseeko(find, (off_t)0, SEEK_END); + for (;;) { + if (!nobuf) + my_fseeko(fbuf,(off_t)0,SEEK_END); + pos = my_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++; + } + my_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); + my_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 . + */ + 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! + */ + write(1, copyright + 4, strlen(copyright + 4)); + write(1, helpscreen, strlen(helpscreen)); + 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; +} -- cgit v1.2.3-55-g7522