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