Logo Search packages:      
Sourcecode: util-linux version File versions  Download package

pg.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 */

/*
 * #define _XOPEN_SOURCE  500L
 *
 * Adding this define gives us the correct prototypes for fseeko, ftello,
 * but (for some glibc versions) conflicting prototype for wcwidth.
 * So, avoid defining _XOPEN_SOURCE, and give prototypes for fseeko, ftello
 * by hand.
 */

#include <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 <locale.h>
#include <nl_types.h>
#include <libgen.h>
#if NCH
#include <ncurses.h>
#else
#include <curses.h>
#endif
#include <term.h>

#include "nls.h"
#include "widechar.h"
#include "../defines.h"       /* for HAVE_fseeko */

#define     READBUF           LINE_MAX    /* size of input buffer */
#define CMDBUF          255         /* size of command buffer */
#define     TABSIZE           8           /* spaces consumed by tab character */

/*
 * Avoid the message "`var' might be clobbered by `longjmp' or `vfork'"
 */
#define     CLOBBGRD(a) (void)(&(a));

#define     cuc(c)            ((c) & 0377)

enum { FORWARD = 1, BACKWARD = 2 }; /* search direction */
enum { TOP, MIDDLE, BOTTOM };       /* position of matching line */

/*
 * States for syntax-aware command line editor.
 */
enum {
      COUNT,
      SIGN,
      CMD_FIN,
      SEARCH,
      SEARCH_FIN,
      ADDON_FIN,
      STRING,
      INVALID
};

/*
 * Current command
 */
struct {
      char cmdline[CMDBUF];
      size_t cmdlen;
      int count;
      int key;
      char pattern[CMDBUF];
      char addon;
} cmd;

/*
 * Position of file arguments on argv[] to main()
 */
struct {
      int first;
      int current;
      int last;
} files;

void        (*oldint)(int);         /* old SIGINT handler */
void        (*oldquit)(int);  /* old SIGQUIT handler */
void        (*oldterm)(int);  /* old SIGTERM handler */
char        *tty;             /* result of ttyname(1) */
char        *progname;        /* program name */
unsigned    ontty;                  /* whether running on tty device */
unsigned    exitstatus;       /* exit status */
int         pagelen = 23;           /* lines on a single screen page */
int         ttycols = 79;           /* screen columns (starting at 0) */
struct termios    otio;             /* old termios settings */
int         tinfostat = -1;         /* terminfo routines initialized */
int         searchdisplay = TOP;    /* matching line position */
regex_t           re;               /* regular expression to search for */
int         remembered;       /* have a remembered search string */
int         cflag;                  /* clear screen before each page */
int         eflag;                  /* suppress (EOF) */
int         fflag;                  /* do not split lines */
int         nflag;                  /* no newline for commands required */
int         rflag;                  /* "restricted" pg */
int         sflag;                  /* use standout mode */
char        *pstring = ":";         /* prompt string */
char        *searchfor;       /* search pattern from argv[] */
int         havepagelen;            /* page length is manually defined */
long        startline;        /* start line from argv[] */
int         nextfile = 1;           /* files to advance */
jmp_buf           jmpenv;                 /* jump from signal handlers */
int         canjump;          /* jmpenv is valid */
wchar_t           wbuf[READBUF];          /* used in several widechar routines */

const char *copyright =
"@(#)pg 1.44 2/8/02. Copyright (c) 2000-2001 Gunnar Ritter. ";
const char *helpscreen = "All rights reserved.\n\
-------------------------------------------------------\n\
  h                       this screen\n\
  q or Q                  quit program\n\
  <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";

#ifdef HAVE_fseeko
#if defined (_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS) == 64
  extern int fseeko64(FILE *f, off_t off, int whence);
  extern off_t ftello64(FILE *f);
  #define      my_fseeko       fseeko64
  #define      my_ftello       ftello64
#else
  extern int fseeko(FILE *f, off_t off, int whence);
  extern off_t ftello(FILE *f);
  #define   my_fseeko   fseeko
  #define   my_ftello   ftello
#endif
#else
  static int my_fseeko(FILE *f, off_t off, int whence) {
      return fseek(f, (long) off, whence);
  }
  static off_t my_ftello(FILE *f) {
      return (off_t) ftell(f);
  }
#endif

#ifdef USE_SIGSET /* never defined */
/* sigset and sigrelse are obsolete - use when POSIX stuff is unavailable */
#define my_sigset sigset
#define my_sigrelse     sigrelse
#else
static int my_sigrelse(int sig) {
      sigset_t sigs;

      if (sigemptyset(&sigs) || sigaddset(&sigs, sig))
            return -1;
      return sigprocmask(SIG_UNBLOCK, &sigs, NULL);
}
typedef void (*my_sighandler_t)(int);
static my_sighandler_t my_sigset(int sig, my_sighandler_t disp) {
      struct sigaction act, oact;

      act.sa_handler = disp;
      if (sigemptyset(&act.sa_mask))
            return SIG_ERR;
      act.sa_flags = 0;
      if (sigaction(sig, &act, &oact))
            return SIG_ERR;
      if (my_sigrelse(sig))
            return SIG_ERR;
      return oact.sa_handler;
}
#endif

/*
 * Quit pg.
 */
static void
quit(int status)
{
      exit(status < 0100 ? status : 077);
}

/*
 * Memory allocator including check.
 */
static char *
smalloc(size_t s)
{
        char *m = (char *)malloc(s);
        if (m == NULL) {
                write(2, "Out of memory\n", 14);
                quit(++exitstatus);
        }
        return m;
}

/*
 * Usage message and similar routines.
 */
static void
usage(void)
{
      fprintf(stderr, _("%s: Usage: %s [-number] [-p string] [-cefnrs] "
                    "[+line] [+/pattern/] [files]\n"),
                  progname, progname);
      quit(2);
}

static void
needarg(char *s)
{
      fprintf(stderr, _("%s: option requires an argument -- %s\n"),
            progname, s);
      usage();
}

static void
invopt(char *s)
{
      fprintf(stderr, _("%s: illegal option -- %s\n"), progname, s);
      usage();
}

#ifdef ENABLE_WIDECHAR
/*
 * A mbstowcs()-alike function that transparently handles invalid sequences.
 */
static size_t
xmbstowcs(wchar_t *pwcs, const char *s, size_t nwcs)
{
      size_t n = nwcs;
      int c;

      mbtowc(pwcs, NULL, MB_CUR_MAX);
      while (*s && n) {
            if ((c = mbtowc(pwcs, s, MB_CUR_MAX)) < 0) {
                  s++;
                  *pwcs = L'?';
            } else
                  s += c;
            pwcs++;
            n--;
      }
      if (n)
            *pwcs = L'\0';
      mbtowc(pwcs, NULL, MB_CUR_MAX);
      return nwcs - n;
}
#endif

/*
 * Helper function for tputs().
 */
static int
outcap(int i)
{
      char c = i;
      return write(1, &c, 1);
}

/*
 * Write messages to terminal.
 */
static void
mesg(char *message)
{
      if (ontty == 0)
            return;
      if (*message != '\n' && sflag)
            vidputs(A_STANDOUT, outcap);
      write(1, message, strlen(message));
      if (*message != '\n' && sflag)
            vidputs(A_NORMAL, outcap);
}

/*
 * Get the window size.
 */
static void
getwinsize(void)
{
      static int initialized, envlines, envcols, deflines, defcols;
#ifdef      TIOCGWINSZ
      struct winsize winsz;
      int badioctl;
#endif
      char *p;

      if (initialized == 0) {
            if ((p = getenv("LINES")) != NULL && *p != '\0')
                  if ((envlines = atoi(p)) < 0)
                        envlines = 0;
            if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
                  if ((envcols = atoi(p)) < 0)
                        envcols = 0;
            /* terminfo values. */
            if (tinfostat != 1 || columns == 0)
                  defcols = 24;
            else
                  defcols = columns;
            if (tinfostat != 1 || lines == 0)
                  deflines = 80;
            else
                  deflines = lines;
            initialized = 1;
      }
#ifdef      TIOCGWINSZ
      badioctl = ioctl(1, TIOCGWINSZ, &winsz);
#endif
      if (envcols)
            ttycols = envcols - 1;
#ifdef      TIOCGWINSZ
      else if (!badioctl)
            ttycols = winsz.ws_col - 1;
#endif
      else
            ttycols = defcols - 1;
      if (havepagelen == 0) {
            if (envlines)
                  pagelen = envlines - 1;
#ifdef      TIOCGWINSZ
            else if (!badioctl)
                  pagelen = winsz.ws_row - 1;
#endif
            else
                  pagelen = deflines - 1;
      }
}

/*
 * Message if skipping parts of files.
 */
static void
skip(int direction)
{
      if (direction > 0)
            mesg(_("...skipping forward\n"));
      else
            mesg(_("...skipping backward\n"));
}

/*
 * Signal handler while reading from input file.
 */
static void
sighandler(int signum)
{
      if (canjump && (signum == SIGINT || signum == SIGQUIT))
            longjmp(jmpenv, signum);
      tcsetattr(1, TCSADRAIN, &otio);
      quit(exitstatus);
}

/*
 * Check whether the requested file was specified on the command line.
 */
static int
checkf(void)
{
      if (files.current + nextfile >= files.last) {
            mesg(_("No next file"));
            return 1;
      }
      if (files.current + nextfile < files.first) {
            mesg(_("No previous file"));
            return 1;
      }
      return 0;
}

#ifdef ENABLE_WIDECHAR
/*
 * Return the last character that will fit on the line at col columns
 * in case MB_CUR_MAX > 1.
 */
static char *
endline_for_mb(unsigned col, char *s)
{
      unsigned pos = 0;
      wchar_t *p = wbuf;
      wchar_t *end;
      size_t wl;
      char *t = s;

      if ((wl = xmbstowcs(wbuf, t, sizeof wbuf - 1)) == (size_t)-1)
            return s + 1;
      wbuf[wl] = L'\0';
      while (*p != L'\0') {
            switch (*p) {
                  /*
                   * Cursor left.
                   */
            case L'\b':
                  if (pos > 0)
                        pos--;
                  break;
                  /*
                   * No cursor movement.
                   */
            case L'\a':
                  break;
                  /*
                   * Special.
                   */
            case L'\r':
                  pos = 0;
                  break;
            case L'\n':
                  end = p + 1;
                  goto ended;
                  /*
                   * Cursor right.
                   */
            case L'\t':
                  pos += TABSIZE - (pos % TABSIZE);
                  break;
            default:
                  pos += wcwidth(*p);
            }
            if (pos > col) {
                  if (*p == L'\t')
                        p++;
                  else if (pos > col + 1)
                        /*
                         * wcwidth() found a character that
                         * has multiple columns. What happens
                         * now? Assume the terminal will print
                         * the entire character onto the next
                         * row.
                         */
                        p--;
                  if (*++p == L'\n')
                        p++;
                  end = p;
                  goto ended;
            }
            p++;
      }
      end = p;
 ended:
      *end = L'\0';
      p = wbuf;
      if ((pos = wcstombs(NULL, p, READBUF)) == -1)
            return s + 1;
      return s + pos;
}
#endif

/*
 * Return the last character that will fit on the line at col columns.
 */
static char *
endline(unsigned col, char *s)
{
      unsigned pos = 0;
      char *t = s;

#ifdef ENABLE_WIDECHAR
      if (MB_CUR_MAX > 1)
            return endline_for_mb(col, s);
#endif

      while (*s != '\0') {
            switch (*s) {
                  /*
                   * Cursor left.
                   */
            case '\b':
                  if (pos > 0)
                        pos--;
                  break;
                  /*
                   * No cursor movement.
                   */
            case '\a':
                  break;
                  /*
                   * Special.
                   */
            case '\r':
                  pos = 0;
                  break;
            case '\n':
                  t = s + 1;
                  goto cend;
                  /*
                   * Cursor right.
                   */
            case '\t':
                  pos += TABSIZE - (pos % TABSIZE);
                  break;
            default:
                  pos++;
            }
            if (pos > col) {
                  if (*s == '\t')
                        s++;
                  if (*++s == '\n')
                        s++;
                  t = s;
                  goto cend;
            }
            s++;
      }
      t = s;
 cend:
      return t;
}

/*
 * Clear the current line on the terminal's screen.
 */
static void
cline(void)
{
      char *buf = (char *)smalloc(ttycols + 2);
      memset(buf, ' ', ttycols + 2);
      buf[0] = '\r';
      buf[ttycols + 1] = '\r';
      write(1, buf, ttycols + 2);
      free(buf);
}

/*
 * Evaluate a command character's semantics.
 */
static int
getstate(int c)
{
      switch (c) {
      case '1': case '2': case '3': case '4': case '5':
      case '6': case '7': case '8': case '9': case '0':
      case '\0':
            return COUNT;
      case '-': case '+':
            return SIGN;
      case 'l': case 'd': case '\004': case 'f': case 'z':
      case '.': case '\014': case '$': case 'n': case 'p':
      case 'w': case 'h': case 'q': case 'Q':
            return CMD_FIN;
      case '/': case '?': case '^':
            return SEARCH;
      case 's': case '!':
            return STRING;
      case 'm': case 'b': case 't':
            return ADDON_FIN;
      default:
#ifndef     PGNOBELL
            if (bell)
                  tputs(bell, 1, outcap);
#endif      /* PGNOBELL */
            return INVALID;
      }
}

/*
 * Get the count and ignore last character of string.
 */
static int
getcount(char *cmdstr)
{
      char *buf;
      char *p;
      int i;

      if (*cmdstr == '\0')
            return 1;
      buf = (char *)smalloc(strlen(cmdstr) + 1);
      strcpy(buf, cmdstr);
      if (cmd.key != '\0') {
            if (cmd.key == '/' || cmd.key == '?' || cmd.key == '^') {
                  if ((p = strchr(buf, cmd.key)) != NULL)
                        *p = '\0';
            } else
                  *(buf + strlen(buf) - 1) = '\0';
      }
      if (*buf == '\0')
            return 1;
      if (buf[0] == '-' && buf[1] == '\0') {
            i = -1;
      } else {
            if (*buf == '+')
                  i = atoi(buf + 1);
            else
                  i = atoi(buf);
      }
      free(buf);
      return i;
}

/*
 * Read what the user writes at the prompt. This is tricky because
 * we check for valid input.
 */
static void
prompt(long long pageno)
{
      struct termios tio;
      char key;
      int state = COUNT;
      int escape = 0;
      char b[LINE_MAX], *p;

      if (pageno != -1) {
            if ((p = strstr(pstring, "%d")) == NULL) {
                  mesg(pstring);
            } else {
                  strcpy(b, pstring);
                  sprintf(b + (p - pstring), "%lld", pageno);
                  strcat(b, p + 2);
                  mesg(b);
            }
      }
      cmd.key = cmd.addon = cmd.cmdline[0] = '\0';
      cmd.cmdlen = 0;
      tcgetattr(1, &tio);
      tio.c_lflag &= ~(ICANON | ECHO);
      tio.c_cc[VMIN] = 1;
      tio.c_cc[VTIME] = 0;
      tcsetattr(1, TCSADRAIN, &tio);
      tcflush(1, TCIFLUSH);
      for (;;) {
            switch (read(1, &key, 1)) {
            case 0: quit(0);
                  /*NOTREACHED*/
            case -1: quit(1);
            }
            if (key == tio.c_cc[VERASE]) {
                  if (cmd.cmdlen) {
                        write(1, "\b \b", 3);
                        cmd.cmdline[--cmd.cmdlen] = '\0';
                        switch (state) {
                        case ADDON_FIN:
                              state = SEARCH_FIN;
                              cmd.addon = '\0';
                              break;
                        case CMD_FIN:
                              cmd.key = '\0';
                              state = COUNT;
                              break;
                        case SEARCH_FIN:
                              state = SEARCH;
                              /*FALLTHRU*/
                        case SEARCH:
                              if (cmd.cmdline[cmd.cmdlen - 1]
                                          == '\\') {
                                    escape = 1;
                                    while(cmd.cmdline[cmd.cmdlen
                                                - escape - 1]
                                          == '\\') escape++;
                                    escape %= 2;
                              }
                              else {
                                    escape = 0;
                                    if (strchr(cmd.cmdline, cmd.key)
                                          == NULL) {
                                          cmd.key = '\0';
                                          state = COUNT;
                                    }
                              }
                              break;
                        }
                  }
                  if (cmd.cmdlen == 0) {
                        state = COUNT;
                        cmd.key = '\0';
                  }
                  continue;
            }
            if (key == tio.c_cc[VKILL]) {
                  cline();
                  cmd.cmdlen = 0;
                  cmd.cmdline[0] = '\0';
                  state = COUNT;
                  cmd.key = '\0';
                  continue;
            }
            if (key == '\n' || (nflag && state == COUNT && key == ' '))
                  break;
            if (cmd.cmdlen >= CMDBUF - 1)
                  continue;
            switch (state) {
            case STRING:
                  break;
            case SEARCH:
                  if (!escape) {
                        if (key == cmd.key)
                              state = SEARCH_FIN;
                        if (key == '\\')
                              escape = 1;
                  } else
                        escape = 0;
                  break;
            case SEARCH_FIN:
                  if (getstate(key) != ADDON_FIN)
                        continue;
                  state = ADDON_FIN;
                  cmd.addon = key;
                  switch (key) {
                  case 't':
                        searchdisplay = TOP;
                        break;
                  case 'm':
                        searchdisplay = MIDDLE;
                        break;
                  case 'b':
                        searchdisplay = BOTTOM;
                        break;
                  }
                  break;
            case CMD_FIN:
            case ADDON_FIN:
                  continue;
            default:
                  state = getstate(key);
                  switch (state) {
                  case SIGN:
                        if (cmd.cmdlen != 0) {
                              state = INVALID;
                              continue;
                        }
                        state = COUNT;
                        /*FALLTHRU*/
                  case COUNT:
                        break;
                  case ADDON_FIN:
                  case INVALID:
                        continue;
                  default:
                        cmd.key = key;
                  }
            }
            write(1, &key, 1);
            cmd.cmdline[cmd.cmdlen++] = key;
            cmd.cmdline[cmd.cmdlen] = '\0';
            if (nflag && state == CMD_FIN)
                  goto endprompt;
      }
endprompt:
      tcsetattr(1, TCSADRAIN, &otio);
      cline();
      cmd.count = getcount(cmd.cmdline);
}

#ifdef ENABLE_WIDECHAR
/*
 * Remove backspace formatting, for searches
 * in case MB_CUR_MAX > 1.
 */
static char *
colb_for_mb(char *s)
{
      char *p = s;
      wchar_t *wp, *wq;
      size_t l = strlen(s), wl;
      unsigned i;

      if ((wl = xmbstowcs(wbuf, p, sizeof wbuf)) == (size_t)-1)
            return s;
      for (wp = wbuf, wq = wbuf, i = 0; *wp != L'\0' && i < wl;
           wp++, wq++) {
            if (*wp == L'\b') {
                  if (wq != wbuf)
                        wq -= 2;
                  else
                        wq--;
            } else
                  *wq = *wp;
      }
      *wq = L'\0';
      wp = wbuf;
      wcstombs(s, wp, l + 1);

      return s;
}
#endif

/*
 * Remove backspace formatting, for searches.
 */
static char *
colb(char *s)
{
      char *p = s, *q;

#ifdef ENABLE_WIDECHAR
      if (MB_CUR_MAX > 1)
            return colb_for_mb(s);
#endif

      for (q = s; *p != '\0'; p++, q++) {
            if (*p == '\b') {
                  if (q != s)
                        q -= 2;
                  else
                        q--;
            } else
                  *q = *p;
      }
      *q = '\0';

      return s;
}

#ifdef ENABLE_WIDECHAR
/*
 * Convert nonprintable characters to spaces
 * in case MB_CUR_MAX > 1.
 */
static void
makeprint_for_mb(char *s, size_t l)
{
      char *t = s;
      wchar_t *wp = wbuf;
      size_t wl;

      if ((wl = xmbstowcs(wbuf, t, sizeof wbuf)) == (size_t)-1)
            return;
      while (wl--) {
            if (!iswprint(*wp) && *wp != L'\n' && *wp != L'\r'
                && *wp != L'\b' && *wp != L'\t')
                  *wp = L'?';
            wp++;
      }
      wp = wbuf;
      wcstombs(s, wp, l);
}
#endif

/*
 * Convert nonprintable characters to spaces.
 */
static void
makeprint(char *s, size_t l)
{
#ifdef ENABLE_WIDECHAR
      if (MB_CUR_MAX > 1)
            return makeprint_for_mb(s, l);
#endif

      while (l--) {
            if (!isprint(cuc(*s)) && *s != '\n' && *s != '\r'
                && *s != '\b' && *s != '\t')
                  *s = '?';
            s++;
      }
}

/*
 * Strip backslash characters from the given string.
 */
static void
striprs(char *s)
{
      char *p = s;

      do {
            if (*s == '\\') {
                  s++;
            }
            *p++ = *s;
      } while (*s++ != '\0');
}

/*
 * Extract the search pattern off the command line.
 */
static char *
makepat(void)
{
      char *p;

      if (cmd.addon == '\0')
            p = cmd.cmdline + strlen(cmd.cmdline) - 1;
      else
            p = cmd.cmdline + strlen(cmd.cmdline) - 2;
      if (*p == cmd.key)
            *p = '\0';
      else
            *(p + 1) = '\0';
      if ((p = strchr(cmd.cmdline, cmd.key)) != NULL) {
            p++;
            striprs(p);
      }
      return p;
}

/*
 * Process errors that occurred in temporary file operations.
 */
static void
tmperr(FILE *f, char *ftype)
{
      if (ferror(f))
            fprintf(stderr, _("%s: Read error from %s file\n"),
                  progname, ftype);
      else if (feof(f))
            /*
             * Most likely '\0' in input.
             */
            fprintf(stderr, _("%s: Unexpected EOF in %s file\n"),
                  progname, ftype);
      else
            fprintf(stderr, _("%s: Unknown error in %s file\n"),
                  progname, ftype);
      quit(++exitstatus);
}

/*
 * perror()-like, but showing the program's name.
 */
static void
pgerror(int eno, char *string)
{
      fprintf(stderr, "%s: %s: %s\n", progname, string, strerror(eno));
}

/*
 * Read the file and respond to user input.
 * Beware: long and ugly.
 */
static void
pgfile(FILE *f, char *name)
{
      off_t pos, oldpos, fpos;
      off_t line = 0, fline = 0, bline = 0, oldline = 0, eofline = 0;
      int dline = 0;
      /*
       * These are the line counters:
       * line           the line desired to display
       * fline    the current line of the input file
       * bline    the current line of the file buffer
       * oldline  the line before a search was started
       * eofline  the last line of the file if it is already reached
       * dline    the line on the display
       */
      int search = 0;
      unsigned searchcount = 0;
      /*
       * Advance to EOF immediately.
       */
      int seekeof = 0;
      /*
       * EOF has been reached by `line'.
       */
      int eof = 0;
      /*
       * f and fbuf refer to the same file.
       */
      int nobuf = 0;
      int sig;
      int rerror;
      size_t sz;
      char b[READBUF + 1];
      char *p;
      /*
       * fbuf           an exact copy of the input file as it gets read
       * find           index table for input, one entry per line
       * save           for the s command, to save to a file
       */
      FILE *fbuf, *find, *save;

      /* silence compiler - it may warn about longjmp() */
      CLOBBGRD(line);
      CLOBBGRD(fline);
      CLOBBGRD(bline);
      CLOBBGRD(oldline);
      CLOBBGRD(eofline);
      CLOBBGRD(dline);
      CLOBBGRD(ttycols);
      CLOBBGRD(search);
      CLOBBGRD(searchcount);
      CLOBBGRD(seekeof);
      CLOBBGRD(eof);
      CLOBBGRD(fpos);
      CLOBBGRD(nobuf);
      CLOBBGRD(fbuf);

      if (ontty == 0) {
            /*
             * Just copy stdin to stdout.
             */
            while ((sz = fread(b, sizeof *b, READBUF, f)) != 0)
                  write(1, b, sz);
            if (ferror(f)) {
                  pgerror(errno, name);
                  exitstatus++;
            }
            return;
      }
      if ((fpos = my_fseeko(f, (off_t)0, SEEK_SET)) == -1)
            fbuf = tmpfile();
      else {
            fbuf = f;
            nobuf = 1;
      }
      find = tmpfile();
      if (fbuf == NULL || find == NULL) {
            fprintf(stderr, _("%s: Cannot create tempfile\n"), progname);
            quit(++exitstatus);
      }
      if (searchfor) {
            search = FORWARD;
            oldline = 0;
            searchcount = 1;
            rerror = regcomp(&re, searchfor, REG_NOSUB | REG_NEWLINE);
            if (rerror != 0) {
                  mesg(_("RE error: "));
                  regerror(rerror, &re, b, READBUF);
                  mesg(b);
                  goto newcmd;
            }
            remembered = 1;
      }

      for (line = startline; ; ) {
            /*
             * Get a line from input file or buffer.
             */
            if (line < bline) {
                  my_fseeko(find, line * sizeof pos, SEEK_SET);
                  if (fread(&pos, sizeof pos, 1, find) == 0)
                        tmperr(find, "index");
                  my_fseeko(find, (off_t)0, SEEK_END);
                  my_fseeko(fbuf, pos, SEEK_SET);
                  if (fgets(b, READBUF, fbuf) == NULL)
                        tmperr(fbuf, "buffer");
            } else if (eofline == 0) {
                  my_fseeko(find, (off_t)0, SEEK_END);
                  do {
                        if (!nobuf)
                              my_fseeko(fbuf, (off_t)0, SEEK_END);
                        pos = my_ftello(fbuf);
                        if ((sig = setjmp(jmpenv)) != 0) {
                              /*
                               * We got a signal.
                               */
                              canjump = 0;
                              my_sigrelse(sig);
                              my_fseeko(fbuf, pos, SEEK_SET);
                              *b = '\0';
                              dline = pagelen;
                              break;
                        } else {
                              if (nobuf)
                                    my_fseeko(f, fpos, SEEK_SET);
                              canjump = 1;
                              p = fgets(b, READBUF, f);
                              if (nobuf)
                                    if ((fpos = my_ftello(f)) == -1)
                                          pgerror(errno, name);
                              canjump = 0;
                        }
                        if (p == NULL || *b == '\0') {
                              if (ferror(f))
                                    pgerror(errno, name);
                              eofline = fline;
                              eof = 1;
                              break;
                        } else {
                              if (!nobuf)
                                    fputs(b, fbuf);
                              fwrite(&pos, sizeof pos, 1, find);
                              if (!fflag) {
                                    oldpos = pos;
                                    p = b;
                                    while (*(p = endline(ttycols,
                                                      p))
                                                != '\0') {
                                          pos = oldpos + (p - b);
                                          fwrite(&pos,
                                                sizeof pos,
                                                1, find);
                                          fline++;
                                          bline++;
                                    }
                              }
                              fline++;
                        }
                  } while (line > bline++);
            } else {
                  /*
                   * eofline != 0
                   */
                  eof = 1;
            }
            if (search == FORWARD) {
                  if (eof) {
                        line = oldline;
                        search = searchcount = 0;
                        mesg(_("Pattern not found"));
                        eof = 0;
                        goto newcmd;
                  }
                  line++;
                  colb(b);
                  if (regexec(&re, b, 0, NULL, 0) == 0) {
                        searchcount--;
                  }
                  if (searchcount == 0) {
                        search = dline = 0;
                        switch (searchdisplay) {
                        case TOP:
                              line -= 1;
                              break;
                        case MIDDLE:
                              line -= pagelen / 2 + 1;
                              break;
                        case BOTTOM:
                              line -= pagelen;
                              break;
                        }
                        skip(1);
                  }
                  continue;
            } else if (eof)   {     /*
                               * We are not searching.
                               */
                  line = bline;
            } else if (*b != '\0') {
                  if (cflag && clear_screen) {
                        switch (dline) {
                        case 0:
                              tputs(clear_screen, 1, outcap);
                              dline = 0;
                        }
                  }
                  line++;
                  if (eofline && line == eofline)
                        eof = 1;
                  dline++;
                  if ((sig = setjmp(jmpenv)) != 0) {
                        /*
                         * We got a signal.
                         */
                        canjump = 0;
                        my_sigrelse(sig);
                        dline = pagelen;
                  } else {
                        p = endline(ttycols, b);
                        sz = p - b;
                        makeprint(b, sz);
                        canjump = 1;
                        write(1, b, sz);
                        canjump = 0;
                  }
            }
            if (dline >= pagelen || eof) {
                  /*
                   * Time for prompting!
                   */
                  if (eof && seekeof) {
                        eof = seekeof = 0;
                        if (line >= pagelen)
                              line -= pagelen;
                        else
                              line = 0;
                        dline = -1;
                        continue;
                  }
newcmd:
                  if (eof) {
                        if (fline == 0 || eflag)
                              break;
                        mesg(_("(EOF)"));
                  }
                  prompt((line - 1) / pagelen + 1);
                  switch (cmd.key) {
                  case '/':
                        /*
                         * Search forward.
                         */
                        search = FORWARD;
                        oldline = line;
                        searchcount = cmd.count;
                        p = makepat();
                        if (p != NULL && *p) {
                              if (remembered == 1)
                                    regfree(&re);
                              rerror = regcomp(&re, p,
                                    REG_NOSUB | REG_NEWLINE);
                              if (rerror != 0) {
                                    mesg(_("RE error: "));
                                    sz = regerror(rerror, &re,
                                                b, READBUF);
                                    mesg(b);
                                    goto newcmd;
                              }
                              remembered = 1;
                        } else if (remembered == 0) {
                              mesg(_("No remembered search string"));
                              goto newcmd;
                        }
                        continue;
                  case '?':
                  case '^':
                        /*
                         * Search backward.
                         */
                        search = BACKWARD;
                        oldline = line;
                        searchcount = cmd.count;
                        p = makepat();
                        if (p != NULL && *p) {
                              if (remembered == 1)
                                    regfree(&re);
                              rerror = regcomp(&re, p,
                                    REG_NOSUB | REG_NEWLINE);
                              if (rerror != 0) {
                                    mesg("RE error: ");
                                    regerror(rerror, &re,
                                                b, READBUF);
                                    mesg(b);
                                    goto newcmd;
                              }
                              remembered = 1;
                        } else if (remembered == 0) {
                              mesg("No remembered search string");
                              goto newcmd;
                        }
                        line -= pagelen;
                        if (line <= 0)
                              goto notfound_bw;
                        while (line) {
                              my_fseeko(find, --line * sizeof pos,
                                          SEEK_SET);
                              if(fread(&pos, sizeof pos, 1,find)==0)
                                    tmperr(find, "index");
                              my_fseeko(find, (off_t)0, SEEK_END);
                              my_fseeko(fbuf, pos, SEEK_SET);
                              if (fgets(b, READBUF, fbuf) == NULL)
                                    tmperr(fbuf, "buffer");
                              colb(b);
                              if (regexec(&re, b, 0, NULL, 0) == 0)
                                    searchcount--;
                              if (searchcount == 0)
                                    goto found_bw;
                        }
notfound_bw:
                        line = oldline;
                        search = searchcount = 0;
                        mesg(_("Pattern not found"));
                        goto newcmd;
found_bw:
                        eof = search = dline = 0;
                        skip(-1);
                        switch (searchdisplay) {
                        case TOP:
                              /* line -= 1; */
                              break;
                        case MIDDLE:
                              line -= pagelen / 2;
                              break;
                        case BOTTOM:
                              if (line != 0)
                                    dline = -1;
                              line -= pagelen;
                              break;
                        }
                        if (line < 0)
                              line = 0;
                        continue;
                  case 's':
                        /*
                         * Save to file.
                         */
                        p = cmd.cmdline;
                        while (*++p == ' ');
                        if (*p == '\0')
                              goto newcmd;
                        save = fopen(p, "wb");
                        if (save == NULL) {
                              cmd.count = errno;
                              mesg(_("Cannot open "));
                              mesg(p);
                              mesg(": ");
                              mesg(strerror(cmd.count));
                              goto newcmd;
                        }
                        /*
                         * Advance to EOF.
                         */
                        my_fseeko(find, (off_t)0, SEEK_END);
                        for (;;) {
                              if (!nobuf)
                                    my_fseeko(fbuf,(off_t)0,SEEK_END);
                              pos = my_ftello(fbuf);
                              if (fgets(b, READBUF, f) == NULL) {
                                    eofline = fline;
                                    break;
                              }
                              if (!nobuf)
                                    fputs(b, fbuf);
                              fwrite(&pos, sizeof pos, 1, find);
                              if (!fflag) {
                                    oldpos = pos;
                                    p = b;
                                    while (*(p = endline(ttycols,
                                                      p))
                                                != '\0') {
                                          pos = oldpos + (p - b);
                                          fwrite(&pos,
                                                sizeof pos,
                                                1, find);
                                          fline++;
                                          bline++;
                                    }
                              }
                              fline++;
                              bline++;
                        }
                        my_fseeko(fbuf, (off_t)0, SEEK_SET);
                        while ((sz = fread(b, sizeof *b, READBUF,
                                          fbuf)) != 0) {
                              /*
                               * No error check for compat.
                               */
                              fwrite(b, sizeof *b, sz, save);
                        }
                        fclose(save);
                        my_fseeko(fbuf, (off_t)0, SEEK_END);
                        mesg(_("saved"));
                        goto newcmd;
                  case 'l':
                        /*
                         * Next line.
                         */
                        if (*cmd.cmdline != 'l')
                              eof = 0;
                        if (cmd.count == 0)
                              cmd.count = 1; /* compat */
                        if (isdigit(cuc(*cmd.cmdline))) {
                              line = cmd.count - 2;
                              dline = 0;
                        } else {
                              if (cmd.count != 1) {
                                    line += cmd.count - 1
                                          - pagelen;
                                    dline = -1;
                                    skip(cmd.count);
                              }
                              /*
                               * Nothing to do if count==1.
                               */
                        }
                        break;
                  case 'd':
                        /*
                         * Half screen forward.
                         */
                  case '\004':      /* ^D */
                        if (*cmd.cmdline != cmd.key)
                              eof = 0;
                        if (cmd.count == 0)
                              cmd.count = 1; /* compat */
                        line += (cmd.count * pagelen / 2)
                              - pagelen - 1;
                        dline = -1;
                        skip(cmd.count);
                        break;
                  case 'f':
                        /*
                         * Skip forward.
                         */
                        if (cmd.count <= 0)
                              cmd.count = 1; /* compat */
                        line += cmd.count * pagelen - 2;
                        if (eof)
                              line += 2;
                        if (*cmd.cmdline != 'f')
                              eof = 0;
                        else if (eof)
                              break;
                        if (eofline && line >= eofline)
                              line -= pagelen;
                        dline = -1;
                        skip(cmd.count);
                        break;
                  case '\0':
                        /*
                         * Just a number, or '-', or <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!
                         */
                        write(1, copyright + 4, strlen(copyright + 4));
                        write(1, helpscreen, strlen(helpscreen));
                        goto newcmd;
                  case 'n':
                        /*
                         * Next file.
                         */
                        if (cmd.count == 0)
                              cmd.count = 1;
                        nextfile = cmd.count;
                        if (checkf()) {
                              nextfile = 1;
                              goto newcmd;
                        }
                        eof = 1;
                        break;
                  case 'p':
                        /*
                         * Previous file.
                         */
                        if (cmd.count == 0)
                              cmd.count = 1;
                        nextfile = 0 - cmd.count;
                        if (checkf()) {
                              nextfile = 1;
                              goto newcmd;
                        }
                        eof = 1;
                        break;
                  case 'q':
                  case 'Q':
                        /*
                         * Exit pg.
                         */
                        quit(exitstatus);
                        /*NOTREACHED*/
                  case 'w':
                  case 'z':
                        /*
                         * Set window size.
                         */
                        if (cmd.count < 0)
                              cmd.count = 0;
                        if (*cmd.cmdline != cmd.key)
                              pagelen = ++cmd.count;
                        dline = 1;
                        break;
                  }
                  if (line <= 0) {
                        line = 0;
                        dline = 0;
                  }
                  if (cflag && dline == 1) {
                        dline = 0;
                        line--;
                  }
            }
            if (eof)
                  break;
      }
      fclose(find);
      if (!nobuf)
            fclose(fbuf);
}

int
main(int argc, char **argv)
{
      int arg, i;
      char *p;
      FILE *input;

      progname = basename(argv[0]);

      setlocale(LC_MESSAGES, "");
      bindtextdomain(PACKAGE, LOCALEDIR);
      textdomain(PACKAGE);

      if (tcgetattr(1, &otio) == 0) {
            ontty = 1;
            oldint = my_sigset(SIGINT, sighandler);
            oldquit = my_sigset(SIGQUIT, sighandler);
            oldterm = my_sigset(SIGTERM, sighandler);
            setlocale(LC_CTYPE, "");
            setlocale(LC_COLLATE, "");
            tty = ttyname(1);
            setupterm(NULL, 1, &tinfostat);
            getwinsize();
            helpscreen = _(helpscreen);
      }
      for (arg = 1; argv[arg]; arg++) {
            if (*argv[arg] == '+')
                  continue;
            if (*argv[arg] != '-' || argv[arg][1] == '\0')
                  break;
            argc--;
            for (i = 1; argv[arg][i]; i++) {
                  switch (argv[arg][i]) {
                  case '-':
                        if (i != 1 || argv[arg][i + 1])
                              invopt(&argv[arg][i]);
                        goto endargs;
                  case '1': case '2': case '3': case '4': case '5':
                  case '6': case '7': case '8': case '9': case '0':
                        pagelen = atoi(argv[arg] + i);
                        havepagelen = 1;
                        goto nextarg;
                  case 'c':
                        cflag = 1;
                        break;
                  case 'e':
                        eflag = 1;
                        break;
                  case 'f':
                        fflag = 1;
                        break;
                  case 'n':
                        nflag = 1;
                        break;
                  case 'p':
                        if (argv[arg][i + 1]) {
                              pstring = &argv[arg][i + 1];
                        } else if (argv[++arg]) {
                              --argc;
                              pstring = argv[arg];
                        } else
                              needarg("-p");
                        goto nextarg;
                  case 'r':
                        rflag = 1;
                        break;
                  case 's':
                        sflag = 1;
                        break;
                  default:
                        invopt(&argv[arg][i]);
                  }
            }
nextarg:
            ;
      }
endargs:
      for (arg = 1; argv[arg]; arg++) {
            if (*argv[arg] == '-') {
                  if (argv[arg][1] == '-') {
                        arg++;
                        break;
                  }
                  if (argv[arg][1] == '\0')
                        break;
                  if (argv[arg][1] == 'p' && argv[arg][2] == '\0')
                        arg++;
                  continue;
            }
            if (*argv[arg] != '+')
                  break;
            argc--;
            switch (*(argv[arg] + 1)) {
            case '\0':
                  needarg("+");
                  /*NOTREACHED*/
            case '1': case '2': case '3': case '4': case '5':
            case '6': case '7': case '8': case '9': case '0':
                  startline = atoi(argv[arg] + 1);
                  break;
            case '/':
                  searchfor = argv[arg] + 2;
                  if (*searchfor == '\0')
                        needarg("+/");
                  p = searchfor + strlen(searchfor) - 1;
                  if (*p == '/') *p = '\0';
                  if (*searchfor == '\0')
                        needarg("+/");
                  break;
            default:
                  invopt(argv[arg]);
            }
      }
      if (argc == 1) {
            pgfile(stdin, "stdin");
      } else {
            files.first = arg;
            files.last = arg + argc - 1;
            for ( ; argv[arg]; arg += nextfile) {
                  nextfile = 1;
                  files.current = arg;
                  if (argc > 2) {
                        static int firsttime;
                        firsttime++;
                        if (firsttime > 1) {
                              mesg(_("(Next file: "));
                              mesg(argv[arg]);
                              mesg(")");
newfile:
                              if (ontty) {
                              prompt(-1);
                              switch(cmd.key) {
                              case 'n':
                                    /*
                                    * Next file.
                                    */
                                    if (cmd.count == 0)
                                          cmd.count = 1;
                                    nextfile = cmd.count;
                                    if (checkf()) {
                                          nextfile = 1;
                                          mesg(":");
                                          goto newfile;
                                    }
                                    continue;
                              case 'p':
                                    /*
                                    * Previous file.
                                    */
                                    if (cmd.count == 0)
                                          cmd.count = 1;
                                    nextfile = 0 - cmd.count;
                                    if (checkf()) {
                                          nextfile = 1;
                                          mesg(":");
                                          goto newfile;
                                    }
                                    continue;
                              case 'q':
                              case 'Q':
                                    quit(exitstatus);
                        }
                        } else mesg("\n");
                        }
                  }
                  if (strcmp(argv[arg], "-") == 0)
                        input = stdin;
                  else {
                        input = fopen(argv[arg], "r");
                        if (input == NULL) {
                              pgerror(errno, argv[arg]);
                              exitstatus++;
                              continue;
                        }
                  }
                  if (ontty == 0 && argc > 2) {
                        /*
                         * Use the prefix as specified by SUSv2.
                         */
                        write(1, "::::::::::::::\n", 15);
                        write(1, argv[arg], strlen(argv[arg]));
                        write(1, "\n::::::::::::::\n", 16);
                  }
                  pgfile(input, argv[arg]);
                  if (input != stdin)
                        fclose(input);
            }
      }
      quit(exitstatus);
      /*NOTREACHED*/
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index