1692 lines
36 KiB
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(©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;
|
|
}
|