util-linux/text-utils/pg.c

1692 lines
36 KiB
C

/*
* 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
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(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, READBUF)) == (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 (*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(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;
} else {
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;
} else {
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;
} else 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, NULL);
errexec(sh);
break;
}
case -1:
mesg(_("fork() failed, "
"try again later\n"));
break;
default:
while (wait(NULL) != cpid) ;
}
my_sigset(SIGINT, sighandler);
my_sigset(SIGQUIT, sighandler);
mesg("!\n");
}
goto newcmd;
case 'h':
{
/* Help! */
const char *help = _(helpscreen);
write_all(STDOUT_FILENO, copyright,
strlen(copyright));
write_all(STDOUT_FILENO, help,
strlen(help));
goto newcmd;
}
case 'n':
/* Next file. */
if (cmd.count == 0)
cmd.count = 1;
nextfile = cmd.count;
if (checkf()) {
nextfile = 1;
goto newcmd;
}
eof = 1;
break;
case 'p':
/* Previous file. */
if (cmd.count == 0)
cmd.count = 1;
nextfile = 0 - cmd.count;
if (checkf()) {
nextfile = 1;
goto newcmd;
}
eof = 1;
break;
case 'q':
case 'Q':
/* Exit pg. */
quit(exitstatus);
/* NOTREACHED */
case 'w':
case 'z':
/* Set window size. */
if (cmd.count < 0)
cmd.count = 0;
if (*cmd.cmdline != cmd.key)
pagelen = ++cmd.count;
dline = 1;
break;
}
if (line <= 0) {
line = 0;
dline = 0;
}
if (cflag && dline == 1) {
dline = 0;
line--;
}
}
if (eof)
break;
}
fclose(find);
if (!nobuf)
fclose(fbuf);
}
static int parse_arguments(int arg, int argc, char **argv)
{
FILE *input;
files.first = arg;
files.last = arg + argc - 1;
for (; argv[arg]; arg += nextfile) {
nextfile = 1;
files.current = arg;
if (argc > 2) {
static int firsttime;
firsttime++;
if (firsttime > 1) {
mesg(_("(Next file: "));
mesg(argv[arg]);
mesg(")");
newfile:
if (ontty) {
prompt(-1);
switch (cmd.key) {
case 'n':
/* Next file. */
if (cmd.count == 0)
cmd.count = 1;
nextfile = cmd.count;
if (checkf()) {
nextfile = 1;
mesg(":");
goto newfile;
}
continue;
case 'p':
/* Previous file. */
if (cmd.count == 0)
cmd.count = 1;
nextfile = 0 - cmd.count;
if (checkf()) {
nextfile = 1;
mesg(":");
goto newfile;
}
continue;
case 'q':
case 'Q':
quit(exitstatus);
}
} else
mesg("\n");
}
}
if (strcmp(argv[arg], "-") == 0)
input = stdin;
else {
input = fopen(argv[arg], "r");
if (input == NULL) {
warn("%s", argv[arg]);
exitstatus++;
continue;
}
}
if (ontty == 0 && argc > 2) {
/* Use the prefix as specified by SUSv2. */
write_all(STDOUT_FILENO, "::::::::::::::\n", 15);
write_all(STDOUT_FILENO, argv[arg], strlen(argv[arg]));
write_all(STDOUT_FILENO, "\n::::::::::::::\n", 16);
}
pgfile(input, argv[arg]);
if (input != stdin)
fclose(input);
}
return exitstatus;
}
int main(int argc, char **argv)
{
int arg, i;
char *p;
xasprintf(&copyright,
_("%s %s Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved.\n"),
program_invocation_short_name, PACKAGE_VERSION);
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
close_stdout_atexit();
if (tcgetattr(STDOUT_FILENO, &otio) == 0) {
ontty = 1;
oldint = my_sigset(SIGINT, sighandler);
oldquit = my_sigset(SIGQUIT, sighandler);
oldterm = my_sigset(SIGTERM, sighandler);
setlocale(LC_CTYPE, "");
setlocale(LC_COLLATE, "");
tty = ttyname(STDOUT_FILENO);
setupterm(NULL, STDOUT_FILENO, &tinfostat);
getwinsize();
helpscreen = _(helpscreen);
}
for (arg = 1; argv[arg]; arg++) {
if (*argv[arg] == '+')
continue;
if (*argv[arg] != '-' || argv[arg][1] == '\0')
break;
argc--;
if (!strcmp(argv[arg], "--help")) {
usage();
}
if (!strcmp(argv[arg], "--version")) {
print_version(EXIT_SUCCESS);
return EXIT_SUCCESS;
}
for (i = 1; argv[arg][i]; i++) {
switch (argv[arg][i]) {
case '-':
if (i != 1 || argv[arg][i + 1])
invopt(&argv[arg][i]);
goto endargs;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
pagelen = strtol_or_err(argv[arg] + 1,
_("failed to parse number of lines per page"));
havepagelen = 1;
goto nextarg;
case 'c':
cflag = 1;
break;
case 'e':
eflag = 1;
break;
case 'f':
fflag = 1;
break;
case 'n':
nflag = 1;
break;
case 'p':
if (argv[arg][i + 1]) {
pstring = &argv[arg][i + 1];
} else if (argv[++arg]) {
--argc;
pstring = argv[arg];
} else
needarg("-p");
goto nextarg;
case 'r':
rflag = 1;
break;
case 's':
sflag = 1;
break;
case 'h':
usage();
case 'V':
print_version(EXIT_SUCCESS);
default:
invopt(&argv[arg][i]);
}
}
nextarg:
;
}
endargs:
for (arg = 1; argv[arg]; arg++) {
if (*argv[arg] == '-') {
if (argv[arg][1] == '-') {
arg++;
break;
}
if (argv[arg][1] == '\0')
break;
if (argv[arg][1] == 'p' && argv[arg][2] == '\0')
arg++;
continue;
}
if (*argv[arg] != '+')
break;
argc--;
switch (*(argv[arg] + 1)) {
case '\0':
needarg("+");
/*NOTREACHED*/
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
startline = strtol_or_err(argv[arg] + 1,
_("failed to parse number of lines per page"));
break;
case '/':
searchfor = argv[arg] + 2;
if (*searchfor == '\0')
needarg("+/");
p = searchfor + strlen(searchfor) - 1;
if (*p == '/')
*p = '\0';
if (*searchfor == '\0')
needarg("+/");
break;
default:
invopt(argv[arg]);
}
}
if (argc == 1)
pgfile(stdin, "stdin");
else
exitstatus = parse_arguments(arg, argc, argv);
quit(exitstatus);
/* NOTREACHED */
return 0;
}