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