/*	$OpenBSD: pr.c,v 1.41 2017/12/23 20:53:07 cheloha Exp $	*/

/*-
 * Copyright (c) 1991 Keith Muller.
 * Copyright (c) 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Keith Muller of the University of California, San Diego.
 *
 * 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. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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.
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "pr.h"
#include "extern.h"

/*
 * pr:	a printing and pagination filter. If multiple input files
 *	are specified, each is read, formatted, and written to standard
 *	output. By default, input is separated into 66-line pages, each
 *	with a header that includes the page number, date, time and the
 *	files pathname.
 *
 *	Complies with posix P1003.2/D11
 */

/*
 * pr: more boundary conditions than a four-legged porcupine
 *
 * the original version didn't support form-feeds, while many of the ad-hoc
 * pr implementations out there do.  Addding this and making it work reasonably
 * in all four output modes required quite a bit of hacking and a few minor
 * bugs were noted and fixed in the process.  Some implementations have this
 * as the as -f, some as -F so we accept either.
 *
 * The implementation of form feeds on top of the existing I/O structure is
 * a bit idiosyncratic.  Basically they are treated as temporary end-of-file
 * conditions and an additional level of "loop on form feed" is added to each
 * of the output modes to continue after such a transient end-of-file's. This
 * has the general benefit of making the existing header/trailer logic work
 * and provides a usable framework for rational behavior in multi-column modes.
 *
 * The original "efficient" implementation of the "skip to page N" option was
 * bogus and I substituted the basic inhibit printing until page N approach.
 * This is still fairly bogus vis-a-vis numbering pages on multiple files
 * restarting at one, but at least lets you consistently reprint some large
 * document starting in the middle, in any of the output modes.
 *
 * Additional support for overprinting via <back-space> or <return> would
 * be nice, but is not trivial across tab interpretation, output formatting
 * and the different operating modes.  Support for line-wrapping, either
 * strict or word-wrapped would be really useful and not all that hard to
 * kludge into the inln() implementation.  The general notion is that -wc n
 * would specify width and wrapping with a marker character c and -Wc n
 * would add word wrapping with a minimum width n and delimiters c, defaulting
 * to tab, blank, and -, and column width.  Word wrapping always involves
 * painful policy questions which are difficult to specify unless you just
 * hardwire in some fixed rules. Think quotes, punctuation and white-space
 * elimination and whether you'd do the same thing with a C program and
 * something like columninated newspaper text.
 *
 *				George Robbins <grr@tharsis.com> 4/22/97.
 */

/*
 * parameter variables
 */
int	pgnm;		/* starting page number */
int	skipping;	/* we're skipping to page pgnum */
int	clcnt;		/* number of columns */
int	colwd;		/* column data width - multiple columns */
int	across;		/* mult col flag; write across page */
int	dspace;		/* double space flag */
char	inchar;		/* expand input char */
int	ingap;		/* expand input gap */
int	formfeed;	/* use formfeed as trailer */
int	inform;		/* grok formfeeds in input */
char	*header;	/* header name instead of file name */
char	ochar;		/* contract output char */
int	ogap;		/* contract output gap */
int	lines;		/* number of lines per page */
int	merge;		/* merge multiple files in output */
char	nmchar;		/* line numbering append char */
int	nmwd;		/* width of line number field */
int	offst;		/* number of page offset spaces */
int	nodiag;		/* do not report file open errors */
char	schar;		/* text column separation character */
int	sflag;		/* -s option for multiple columns */
int	nohead;		/* do not write head and trailer */
int	pgwd;		/* page width with multiple col output */

/*
 * misc globals
 */
volatile sig_atomic_t	ferr;	/* error message delayed */
int	addone = 0;	/* page length is odd with double space */
int	errcnt = 0;	/* error count on file processing */
int	beheaded = 0;	/* header / trailer link */
char	digs[] = "0123456789";	/* page number translation map */

int
main(int argc, char *argv[])
{
    int ret_val;

    if (pledge("stdio rpath", NULL) == -1) {
	perror("pledge");
	exit(1);
    }

    if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	(void)signal(SIGINT, terminate);
    ret_val = setup(argc, argv);
    if (!ret_val) {
	/*
	 * select the output format based on options
	 */
	if (merge)
	    ret_val = mulfile(argc, argv);
	else if (clcnt == 1)
	    ret_val = onecol(argc, argv);
	else if (across)
	    ret_val = horzcol(argc, argv);
	else
	    ret_val = vertcol(argc, argv);
    } else
	usage();
    flsh_errs();
    if (errcnt || ret_val)
	exit(1);
    return(0);
}

/*
 * onecol:    print files with only one column of output.
 *        Line length is unlimited.
 */
int
onecol(int argc, char *argv[])
{
    int off;
    int lrgln;
    int linecnt;
    int num;
    int cnt;
    int rc;
    int lncnt;
    int pagecnt;
    int ips;
    int ops;
    int cps;
    char *obuf = NULL;
    char *lbuf;
    char *nbuf;
    char *hbuf = NULL;
    char *ohbuf;
    FILE *inf = NULL;
    char *fname;
    int mor;
    int error = 1;

    if (nmwd)
	num = nmwd + 1;
    else
	num = 0;
    off = num + offst;

    /*
     * allocate line buffer
     */
    if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL)
	goto oomem;

    /*
     * allocate header buffer
     */
    if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL)
	goto oomem;

    ohbuf = hbuf + offst;
    nbuf = obuf + offst;
    lbuf = nbuf + num;

    if (num)
	nbuf[--num] = nmchar;

    if (offst) {
	(void)memset(obuf, (int)' ', offst);
	(void)memset(hbuf, (int)' ', offst);
    }

    /*
     * loop by file
     */
    while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
	pagecnt = 0;
	lncnt = 0;

	/*
	 * loop by "form"
	 */
	for(;;) {

	    /*
	     * loop by page
	     */
	    for(;;) {
		linecnt = 0;
		lrgln = 0;
		ops = 0;
		ips = 0;
		cps = 0;

		/*
		 * loop by line
		 */
		while (linecnt < lines) {

		    /*
		     * input next line
		     */
		    rc = inln(inf,lbuf,LBUF,&cnt,&cps,0,&mor);
		    if (cnt >= 0) {
			if (!lrgln)
			    if (!linecnt && prhead(hbuf, fname, ++pagecnt))
			         goto out;

			/*
			 * start new line or continue a long one
			 */
			if (!lrgln) {
			    if (num)
				addnum(nbuf, num, ++lncnt);
			    if (otln(obuf,cnt+off, &ips, &ops, mor))
				goto out;
			} else
			    if (otln(lbuf, cnt, &ips, &ops, mor))
				goto out;

			/*
			 * if line bigger than buffer, get more
			 */
			if (mor) {
			    lrgln = 1;
			} else {
			    /*
			     * whole line rcvd. reset tab proc. state
			     */
			    ++linecnt;
			    lrgln = 0;
			    ops = 0;
			    ips = 0;
			}
		    }

		    if (rc != NORMAL)
			break;
		}

		/*
		 * fill to end of page
		 */
		if (prtail(lines - linecnt, lrgln))
		    goto out;

		/*
		 * unless END continue
		 */
		if (rc == END)
		    break;
	    }

	    /*
	     * On EOF go to next file
	     */
	    if (rc == END)
	    break;
	}

	if (inf != stdin)
	    (void)fclose(inf);
    }
    /*
     * If we didn't process all the files, return error
     */
    if (eoptind < argc) {
	goto out;
    } else {
	error = 0;
	goto out;
    }

oomem:
    mfail();
out:
    free(obuf);
    free(hbuf);
    if (inf != NULL && inf != stdin)
	(void)fclose(inf);
    return error;
}

/*
 * vertcol:	print files with more than one column of output down a page
 *		the general approach is to buffer a page of data, then print
 */
int
vertcol(int argc, char *argv[])
{
    char *ptbf;
    char **lstdat = NULL;
    int i;
    int j;
    int pln;
    int *indy = NULL;
    int cnt;
    int rc;
    int cvc;
    int *lindy = NULL;
    int lncnt;
    int stp;
    int pagecnt;
    int col = colwd + 1;
    int mxlen = pgwd + offst + 1;
    int mclcnt = clcnt - 1;
    struct vcol *vc = NULL;
    int mvc;
    int tvc;
    int cw = nmwd + 1;
    int fullcol;
    char *buf = NULL;
    char *hbuf = NULL;
    char *ohbuf;
    char *fname;
    FILE *inf = NULL;
    int ips = 0;
    int cps = 0;
    int ops = 0;
    int mor = 0;
    int error = 1;

    /*
     * allocate page buffer
     */
    if ((buf = calloc((unsigned)lines, mxlen)) == NULL)
	goto oomem;

    /*
     * allocate page header
     */
    if ((hbuf = malloc((unsigned)HDBUF + offst)) == NULL)
	goto oomem;

    ohbuf = hbuf + offst;
    if (offst)
	(void)memset(hbuf, (int)' ', offst);

    /*
     * col pointers when no headers
     */
    mvc = lines * clcnt;
    if ((vc = calloc((unsigned)mvc, sizeof(struct vcol))) == NULL)
	goto oomem;

    /*
     * pointer into page where last data per line is located
     */
    if ((lstdat = calloc((unsigned)lines, sizeof(char *))) == NULL)
	goto oomem;

    /*
     * fast index lookups to locate start of lines
     */
    if ((indy = calloc((unsigned)lines, sizeof(int))) == NULL)
	goto oomem;
    if ((lindy = calloc((unsigned)lines, sizeof(int))) == NULL)
	goto oomem;

    if (nmwd)
	fullcol = col + cw;
    else
	fullcol = col;

    /*
     * initialize buffer lookup indexes and offset area
     */
    for (j = 0; j < lines; ++j) {
	lindy[j] = j * mxlen;
	indy[j] = lindy[j] + offst;
	if (offst) {
	    ptbf = buf + lindy[j];
	    (void)memset(ptbf, (int)' ', offst);
	    ptbf += offst;
	} else
	    ptbf = buf + indy[j];
	lstdat[j] = ptbf;
    }

    /*
     * loop by file
     */
    while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
	pagecnt = 0;
	lncnt = 0;

	/*
	 * loop by "form"
	 */
	 for (;;) {

	    /*
	     * loop by page
	     */
	    for(;;) {

		/*
		 * loop by column
		 */
		cvc = 0;
		for (i = 0; i < clcnt; ++i) {
		    j = 0;
		    /*
		     * if last column, do not pad
		     */
		    if (i == mclcnt)
			stp = 1;
		    else
			stp = 0;

		    /*
		     * loop by line
		     */
		    for(;;) {
			/*
			 * is this first column
			 */
			if (!i) {
			    ptbf = buf + indy[j];
			    lstdat[j] = ptbf;
			} else 
			    ptbf = lstdat[j];
			vc[cvc].pt = ptbf;

			/*
			 * add number
			 */
			if (nmwd) {
			    addnum(ptbf, nmwd, ++lncnt);
			    ptbf += nmwd;
			    *ptbf++ = nmchar;
			}

			/*
			 * input next line
			 */
			rc = inln(inf,ptbf,colwd,&cnt,&cps,1,&mor);
			vc[cvc++].cnt = cnt;
			if (cnt >= 0) {
			    ptbf += cnt;

			    /*
			     * pad all but last column on page
			     */
			    if (!stp) {
				/*
				 * pad to end of column
				 */
				if (sflag)
				    *ptbf++ = schar;
				else if ((pln = col-cnt) > 0) {
				    (void)memset(ptbf,
					(int)' ',pln);
				    ptbf += pln;
				}
			    }

			    /*
			     * remember last char in line
			     */
			    lstdat[j] = ptbf;
			    if (++j >= lines)
				break;
			} /* end of if cnt >= 0 */

			if (rc != NORMAL)
			    break;
		    } /* end of for line */

		    if (rc != NORMAL)
			break;
		} /* end of for column */

		/*
		 * when -t (no header) is specified the spec requires
		 * the min number of lines. The last page may not have
		 * balanced length columns. To fix this we must reorder
		 * the columns. This is a very slow technique so it is
		 * only used under limited conditions. Without -t, the
		 * balancing of text columns is unspecified. To NOT
		 * balance the last page, add the global variable
		 * nohead to the if statement below e.g.
		 */

		/*
		 * print header iff we got anything on the first read
		 */
		if (vc[0].cnt >= 0) {
		    if (prhead(hbuf, fname, ++pagecnt))
		    	goto out;

		    /*
		     * check to see if "last" page needs to be reordered
		     */
		    --cvc;
		    if ((rc != NORMAL) && cvc && ((mvc-cvc) >= clcnt)){
			pln = cvc/clcnt;
			if (cvc % clcnt)
			    ++pln;

			for (i = 0; i < pln; ++i) {
			    ips = 0;
			    ops = 0;
			    if (offst && otln(buf,offst,&ips,&ops,1))
				goto out;
			    tvc = i;

			    for (j = 0; j < clcnt; ++j) {
				/*
				 * determine column length
				 */
				if (j == mclcnt) {
				    /*
				     * last column
				     */
				    cnt = vc[tvc].cnt;
				    if (nmwd)
					cnt += cw;
				} else if (sflag) {
				    /*
				     * single ch between
				     */
				    cnt = vc[tvc].cnt + 1;
				    if (nmwd)
					cnt += cw;
				} else
				    cnt = fullcol;

				if (otln(vc[tvc].pt, cnt, &ips, &ops, 1))
				    goto out;
				tvc += pln;
				if (tvc > cvc)
				    break;
			    }
			    /*
			     * terminate line
			     */
			    if (otln(buf, 0, &ips, &ops, 0))
				goto out;
			}

		    } else {

			/*
			 * just a normal page...
			 * determine how many lines to output
			 */
			if (i > 0)
			    pln = lines;
			else
			    pln = j;

			/*
			 * output each line
			 */
			for (i = 0; i < pln; ++i) {
			    ptbf = buf + lindy[i];
			    if ((j = lstdat[i] - ptbf) <= offst)
				break;
			    else {
				ips = 0;
				ops = 0;
				if (otln(ptbf, j, &ips, &ops, 0))
				    goto out;
			    }
			}
		    }
		}

		/*
		 * pad to end of page
		 */
		if (prtail((lines - pln), 0))
		    goto out;

		/*
		 * if FORM continue
		 */
		if (rc != NORMAL)
		    break;
	    }

	    /*
	     * if EOF go to next file
	     */
	    if (rc == END)
		break;
	}

	if (inf != stdin)
	    (void)fclose(inf);
    }

    if (eoptind < argc){
	goto out;
    } else {
	error = 0;
	goto out;
    }

oomem:
    mfail();
out:
    free(buf);
    free(hbuf);
    free(vc);
    free(lstdat);
    free(lindy);
    if (inf != NULL && inf != stdin)
	(void)fclose(inf);
    return error;

}

/*
 * horzcol:    print files with more than one column of output across a page
 */
int
horzcol(int argc, char *argv[])
{
    char *ptbf;
    int pln;
    char *lstdat;
    int col = colwd + 1;
    int j;
    int i;
    int cnt;
    int rc;
    int lncnt;
    int pagecnt;
    char *buf = NULL;
    char *hbuf = NULL;
    char *ohbuf;
    char *fname;
    FILE *inf = NULL;
    int cps = 0;
    int mor = 0;
    int ips = 0;
    int ops = 0;
    int error = 1;

    if ((buf = malloc((unsigned)pgwd + offst + 1)) == NULL)
	goto oomem;

    /*
     * page header
     */
    if ((hbuf = malloc((unsigned)HDBUF + offst)) == NULL)
	goto oomem;

    ohbuf = hbuf + offst;
    if (offst) {
	(void)memset(buf, (int)' ', offst);
	(void)memset(hbuf, (int)' ', offst);
    }

    /*
     * loop by file
     */
    while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
	pagecnt = 0;
	lncnt = 0;

	/*
	 * loop by form
	 */
	for (;;) {

	    /*
	     * loop by page
	     */
	    for(;;) {

		/*
		 * loop by line
		 */
		for (i = 0; i < lines; ++i) {
		    ptbf = buf + offst;
		    lstdat = ptbf;
		    j = 0;

		    /*
		     * loop by col
		     */
		    for(;;) {
			if (nmwd) {
			    /*
			     * add number to column
			     */
			    addnum(ptbf, nmwd, ++lncnt);
			    ptbf += nmwd;
			    *ptbf++ = nmchar;
			}
			/*
			 * input line
			 */
			rc = inln(inf,ptbf,colwd,&cnt,&cps,1, &mor);
			if (cnt >= 0) {
			    if (!i && !j && prhead(hbuf, fname, ++pagecnt))
			        goto out;

			    ptbf += cnt;
			    lstdat = ptbf;

			    /*
			     * if last line skip padding
			     */
			    if (++j >= clcnt)
				break;

			    /*
			     * pad to end of column
			     */
			    if (sflag)
				*ptbf++ = schar;
			    else if ((pln = col - cnt) > 0) {
				(void)memset(ptbf,(int)' ',pln);
				ptbf += pln;
			    }
			}
			if (rc != NORMAL)
			    break;
		    }

		    /*
		     * output line if any columns on it
		     */
		    if (j) {
			if (otln(buf, lstdat-buf, &ips, &ops, 0))
			    goto out;
		    }

		    if (rc != NORMAL)
			break;
		}

		/*
		 * pad to end of page
		 */
		if (prtail(lines - i, 0))
		    return(1);

		/*
		 * if FORM continue
		 */
		if (rc == END)
		    break;
	    }
	    /*
	     * if EOF go to next file
	     */
	    if (rc == END)
		break;
	}
	if (inf != stdin)
	    (void)fclose(inf);
    }
    if (eoptind < argc){
	goto out;
    } else {
	error = 0;
	goto out;
    }

oomem:
    mfail();
out:
    free(buf);
    free(hbuf);
    if (inf != NULL && inf != stdin)
	(void)fclose(inf);
    return error;
}

struct ferrlist {
	struct ferrlist *next;
	char *buf;
};
struct ferrlist *ferrhead, *ferrtail;

/*
 * flsh_errs():    output saved up diagnostic messages after all normal
 *        processing has completed
 */
void
flsh_errs(void)
{
    struct ferrlist *f;

    if (ferr) {
	for (f = ferrhead; f; f = f->next)
	    (void)write(STDERR_FILENO, f->buf, strlen(f->buf));
    }
}

static void ferrout(char *fmt, ...) __attribute__((format (printf, 1, 2)));
static void
ferrout(char *fmt, ...)
{
    sigset_t block, oblock;
    struct ferrlist *f;
    va_list ap;
    char *p;

    va_start(ap, fmt);
    if (ferr == 0)
        vfprintf(stderr, fmt, ap);
    else {
	sigemptyset(&block);
	sigaddset(&block, SIGINT);
	sigprocmask(SIG_BLOCK, &block, &oblock);

	if (vasprintf(&p, fmt, ap) == -1 || (f = malloc(sizeof(*f))) == NULL) {
		va_end(ap);
		va_start(ap, fmt);
		flsh_errs();
		vfprintf(stderr, fmt, ap);
		fputs("pr: memory allocation failed\n", stderr);
		exit(1);
	}

	f->next = NULL;
	f->buf = p;
	if (ferrhead == NULL)
	    ferrhead = f;
	if (ferrtail)
		ferrtail->next = f;
	ferrtail = f;
	sigprocmask(SIG_SETMASK, &oblock, NULL);
    }
    va_end(ap);
}

/*
 * mulfile:    print files with more than one column of output and
 *        more than one file concurrently
 */
int
mulfile(int argc, char *argv[])
{
    char *ptbf;
    int j;
    int pln;
    int *rc;
    int cnt;
    char *lstdat;
    int i;
    FILE **fbuf = NULL;
    int actf;
    int lncnt;
    int col;
    int pagecnt;
    int fproc;
    char *buf = NULL;
    char *hbuf = NULL;
    char *ohbuf;
    char *fname;
    int ips = 0;
    int cps = 0;
    int ops = 0;
    int mor = 0;
    int error = 1;

    /*
     * array of FILE *, one for each operand
     */
    if ((fbuf = calloc((unsigned)clcnt, sizeof(FILE *))) == NULL)
	goto oomem;

    /*
     * array of int *, one for each operand
     */
    if ((rc = calloc((unsigned)clcnt, sizeof(int))) == NULL)
	goto oomem;

    /*
     * page header
     */
    if ((hbuf = malloc((unsigned)HDBUF + offst)) == NULL)
	goto oomem;

    ohbuf = hbuf + offst;

    /*
     * do not know how many columns yet. The number of operands provide an
     * upper bound on the number of columns. We use the number of files
     * we can open successfully to set the number of columns. The operation
     * of the merge operation (-m) in relation to unsuccessful file opens
     * is unspecified by posix.
     *
     * XXX - this seems moderately bogus, you'd think that specifying
     * "pr -2 a b c d" would run though all the files in pairs, but
     * the existing code says up two files, or fewer if one is bogus.
     * fixing it would require modifying the looping structure, so be it.
     */
    j = 0;
    while (j < clcnt) {
	if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) != NULL) {
	    rc[j] = NORMAL;
	    j++;
	}
    }

    /*
     * if no files, exit
     */
    if (j)
	clcnt = j;
    else
	goto out;

    /*
     * calculate page boundaries based on open file count
     */
    if (nmwd) {
	colwd = (pgwd - clcnt - nmwd)/clcnt;
	pgwd = ((colwd + 1) * clcnt) - nmwd - 2;
    } else {
	colwd = (pgwd + 1 - clcnt)/clcnt;
	pgwd = ((colwd + 1) * clcnt) - 1;
    }
    if (colwd < 1) {
	ferrout("pr: page width too small for %d columns\n", clcnt);
	goto out;
    }
    col = colwd + 1;

    /*
     * line buffer
     */
    if ((buf = malloc((unsigned)pgwd + offst + 1)) == NULL)
	goto oomem;

    if (offst) {
	(void)memset(buf, (int)' ', offst);
	(void)memset(hbuf, (int)' ', offst);
    }

    pagecnt = 0;
    lncnt = 0;
    actf = clcnt;

    /*
     * continue to loop while any file still has data
     */
    while (actf > 0) {

	/*
	 * loop on "form"
	 */
	for (;;) {

	    /*
	     * loop by line
	     */
	    for (i = 0; i < lines; ++i) {
		ptbf = buf + offst;
		lstdat = ptbf;
		if (nmwd) {
		    /*
		     * add line number to line
		     */
		    addnum(ptbf, nmwd, ++lncnt);
		    ptbf += nmwd;
		    *ptbf++ = nmchar;
		}

		fproc = 0;
		/*
		 * loop by column
		 */
		for (j = 0; j < clcnt; ++j) {
		    if (rc[j] == NORMAL ) {
			rc[j] = inln(fbuf[j], ptbf, colwd, &cnt, &cps, 1, &mor);
			if (cnt >= 0) {
			    /*
			     * process file data
			     */
			    ptbf += cnt;
			    lstdat = ptbf;
			    fproc++;
			} else
			    cnt = 0;
			
			if (rc[j] == END) {
			    /*
			     * EOF close file
			     */
			    if (fbuf[j] != stdin)
				(void)fclose(fbuf[j]);
			    --actf;
			}
		    } else
			cnt = 0;

		    /*
		     * if last ACTIVE column, done with line
		     */
		    if (fproc >= actf)
			break;

		    /*
		     * pad to end of column
		     */
		    if (sflag) {
			*ptbf++ = schar;
		    } else {
			if (cnt >= 0)
			    pln = col - cnt;
			else
			    pln = col;
			if (pln > 0) {
			    (void)memset(ptbf, (int)' ', pln);
			    ptbf += pln;
			}
		    }
		}

		/*
		 * if there was anything to do, print it
		 */
		if (fproc != 0) {
		    if (!i && prhead(hbuf, fname, ++pagecnt))
			goto out;

		    /*
		     * output line
		     */
		    if (otln(buf, lstdat-buf, &ips, &ops, 0))
			goto out;
		} else
		    break;
	    }

	    /*
	     * pad to end of page
	     */
	    if (prtail(lines - i, 0))
		return(1);

	    for (j = 0; j < clcnt; ++j)
		if (rc[j] != END)
		    rc[j] = NORMAL;

	    if (actf <= 0)
		break;
	}
	if (actf <= 0)
	break;
    }
    if (eoptind < argc){
	goto out;
    } else {
	error = 0;
	goto out;
    }

oomem:
	mfail();
out:
    if (fbuf) {
	for (j = 0; j < clcnt; j++) {
	    if (fbuf[j] && fbuf[j] != stdin)
		(void)fclose(fbuf[j]);
	}
	free(fbuf);
    }
    free(hbuf);
    free(buf);
    return error;
}

/*
 * inln():    input a line of data (unlimited length lines supported)
 *        Input is optionally expanded to spaces
 *        Returns 0 if normal LF, FORM on Formfeed, and END on EOF
 *
 *    inf:    file
 *    buf:    buffer
 *    lim:    buffer length
 *    cnt:    line length or -1 if no line (EOF for example)
 *    cps:    column position 1st char in buffer (large line support)
 *    trnc:    throw away data more than lim up to \n 
 *    mor:    set if more data in line (not truncated)
 */
int
inln(FILE *inf, char *buf, int lim, int *cnt, int *cps, int trnc, int *mor)
{
    int col;
    int gap = ingap;
    int ch = -1;
    char *ptbuf;
    int chk = (int)inchar;

    ptbuf = buf;

    if (gap) {
	/*
	 * expanding input option
	 */
	while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
	    /*
	     * is this the input "tab" char
	     */
	    if (ch == chk) {
		/*
		 * expand to number of spaces
		 */
		col = (ptbuf - buf) + *cps;
		col = gap - (col % gap);

		/*
		 * if more than this line, push back
		 */
		if ((col > lim) && (ungetc(ch, inf) == EOF)) {
		    *cnt = -1;
		    return(END);    /* shouldn't happen */
		}

		/*
		 * expand to spaces
		 */
		while ((--col >= 0) && (--lim >= 0))
		    *ptbuf++ = ' ';
		continue;
	    }
	    if (ch == '\n' || (inform && ch == INFF))
		break;
	    *ptbuf++ = ch;
	}
    } else {
	/*
	 * no expansion
	 */
	while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
	    if (ch == '\n' || (inform && ch == INFF))
		break;
	    *ptbuf++ = ch;
	}
    }
    col = ptbuf - buf;
    if (ch == EOF) {
	*mor = 0;
	*cps = 0;
	*cnt = col ? col : -1;
	return(END);
    }
    if (inform && ch == INFF) {
	*mor = 0;
	*cps = 0;
	*cnt = col;
	return(FORM);
    }
    if (ch == '\n') {
	/*
	 * entire line processed
	 */
	*mor = 0;
	*cps = 0;
	*cnt = col;
	return(NORMAL);
    }

    /*
     * line was larger than limit
     */
    if (trnc) {
	/*
	 * throw away rest of line
	 */
	while ((ch = getc(inf)) != EOF) {
	    if (ch == '\n')
		break;
	}
	*cps = 0;
	*mor = 0;
    } else {
	/*
	 * save column offset if not truncated
	 */
	*cps += col;
	*mor = 1;
    }

    *cnt = col;
    return(NORMAL);
}

/*
 * otln():    output a line of data. (Supports unlimited length lines)
 *        output is optionally contracted to tabs
 *
 *    buf:    output buffer with data
 *    cnt:    number of chars of valid data in buf
 *    svips:    buffer input column position (for large lines)
 *    svops:    buffer output column position (for large lines)
 *    mor:    output line not complete in this buf; more data to come.    
 *        1 is more, 0 is complete, -1 is no \n's
 */
int
otln(char *buf, int cnt, int *svips, int *svops, int mor)
{
    int ops;        /* last col output */
    int ips;        /* last col in buf examined */
    int gap = ogap;
    int tbps;
    char *endbuf;

    /* skipping is only changed at header time not mid-line! */
    if (skipping)
	return (0);

    if (ogap) {
	/*
	 * contracting on output
	 */
	endbuf = buf + cnt;
	ops = *svops;
	ips = *svips;
	while (buf < endbuf) {
	    /*
	     * count number of spaces and ochar in buffer
	     */
	    if (*buf == ' ') {
		++ips;
		++buf;
		continue;
	    }

	    /*
	     * simulate ochar processing
	     */
	    if (*buf == ochar) {
		ips += gap - (ips % gap);
		++buf;
		continue;
	    }

	    /*
	     * got a non space char; contract out spaces
	     */
	    while (ops < ips) {
		/*
		 * use one space if necessary
		 */
		if (ips - ops == 1) {
			putchar(' ');
			break;
		}
		/*
		 * use as many ochar as will fit
		 */
		if ((tbps = ops + gap - (ops % gap)) > ips)
		    break;
		if (putchar(ochar) == EOF) {
		    pfail();
		    return(1);
		}
		ops = tbps;
	    }

	    while (ops < ips) {
		/*
		 * finish off with spaces
		 */
		if (putchar(' ') == EOF) {
		    pfail();
		    return(1);
		}
		++ops;
	    }

	    /*
	     * output non space char
	     */
	    if (putchar(*buf++) == EOF) {
		pfail();
		return(1);
	    }
	    ++ips;
	    ++ops;
	}

	if (mor > 0) {
	    /*
	     * if incomplete line, save position counts
	     */
	    *svops = ops;
	    *svips = ips;
	    return(0);
	}

	if (mor < 0) {
	    while (ops < ips) {
		/*
		 * use one space if necessary
		 */
		if (ips - ops == 1) {
			putchar(' ');
			break;
		}
		/*
		 * use as many ochar as will fit
		 */
		if ((tbps = ops + gap - (ops % gap)) > ips)
		    break;
		if (putchar(ochar) == EOF) {
		    pfail();
		    return(1);
		}
		ops = tbps;
	    }

	    while (ops < ips) {
		/*
		 * finish off with spaces
		 */
		if (putchar(' ') == EOF) {
		    pfail();
		    return(1);
		}
		++ops;
	    }
	    return(0);
	}
    } else {
	/*
	 * output is not contracted
	 */
	if (cnt && (fwrite(buf, sizeof(char), cnt, stdout) < cnt)) {
	    pfail();
	    return(1);
	}
	if (mor != 0)
	    return(0);
    }

    /*
     * process line end and double space as required
     */
    if ((putchar('\n') == EOF) || (dspace && (putchar('\n') == EOF))) {
	pfail();
	return(1);
    }
    return(0);
}

#ifdef notused
/*
 * inskip():    skip over pgcnt pages with lncnt lines per page
 *        file is closed at EOF (if not stdin).
 *
 *    inf    FILE * to read from
 *    pgcnt    number of pages to skip
 *    lncnt    number of lines per page
 */
int
inskip(FILE *inf, int pgcnt, int lncnt)
{
    int c;
    int cnt;

    while(--pgcnt > 0) {
	cnt = lncnt;
	while ((c = getc(inf)) != EOF) {
	    if ((c == '\n') && (--cnt == 0))
		break;
	}
	if (c == EOF) {
	    if (inf != stdin)
		(void)fclose(inf);
	    return(1);
	}
    }
    return(0);
}
#endif

/*
 * nxtfile:    returns a FILE * to next file in arg list and sets the
 *        time field for this file (or current date).
 *
 *    buf    array to store proper date for the header.
 *    dt    if set skips the date processing (used with -m)
 */
FILE *
nxtfile(int argc, char *argv[], char **fname, char *buf, int dt)
{
    FILE *inf = NULL;
    struct tm *timeptr = NULL;
    struct stat statbuf;
    time_t curtime;
    static int twice = -1;

    ++twice;
    if (eoptind >= argc) {
	/*
	 * no file listed; default, use standard input
	 */
	if (twice)
	    return(NULL);
	clearerr(stdin);
	inf = stdin;
	if (header != NULL)
	    *fname = header;
	else
	    *fname = FNAME;
	if (nohead)
	    return(inf);
	curtime = time(NULL);;
	timeptr = localtime(&curtime);
    }
    for (; eoptind < argc; ++eoptind) {
	if (strcmp(argv[eoptind], "-") == 0) {
	    /*
	     * process a "-" for filename
	     */
	    clearerr(stdin);
	    inf = stdin;
	    if (header != NULL)
		*fname = header;
	    else
		*fname = FNAME;
	    ++eoptind;
	    if (nohead || (dt && twice))
		return(inf);
	    curtime = time(NULL);
	    timeptr = localtime(&curtime);
	} else {
	    /*
	     * normal file processing
	     */
	    if ((inf = fopen(argv[eoptind], "r")) == NULL) {
		++errcnt;
		if (nodiag)
		    continue;
		ferrout("pr: Cannot open %s, %s\n",
		    argv[eoptind], strerror(errno));
		continue;
	    }
	    if (header != NULL)
		*fname = header;
	    else if (dt)
		*fname = FNAME;
	    else
		*fname = argv[eoptind];
	    ++eoptind;
	    if (nohead || (dt && twice))
		return(inf);

	    if (dt) {
		curtime = time(NULL);
		timeptr = localtime(&curtime);
	    } else {
		if (fstat(fileno(inf), &statbuf) < 0) {
		    ++errcnt;
		    (void)fclose(inf);
		    ferrout("pr: Cannot stat %s, %s\n",
			argv[eoptind], strerror(errno));
		    return(NULL);
		}
		timeptr = localtime(&(statbuf.st_mtime));
	    }
	}
	break;
    }
    if (inf == NULL)
	return(NULL);

    /*
     * set up time field used in header
     */
    if (strftime(buf, HDBUF, TIMEFMT, timeptr) == 0) {
	++errcnt;
	if (inf != stdin)
	    (void)fclose(inf);
	ferrout("pr: time conversion failed\n");
	return(NULL);
    }
    return(inf);
}

/*
 * addnum():    adds the line number to the column
 *        Truncates from the front or pads with spaces as required.
 *        Numbers are right justified.
 *
 *    buf    buffer to store the number
 *    wdth    width of buffer to fill
 *    line    line number
 *
 *        NOTE: numbers occupy part of the column. The posix
 *        spec does not specify if -i processing should or should not
 *        occur on number padding. The spec does say it occupies
 *        part of the column. The usage of addnum    currently treats
 *        numbers as part of the column so spaces may be replaced.
 */
void
addnum(char *buf, int wdth, int line)
{
    char *pt = buf + wdth;

    do {
	*--pt = digs[line % 10];
	line /= 10;
    } while (line && (pt > buf));

    /*
     * pad with space as required
     */
    while (pt > buf)
	*--pt = ' ';
}

/*
 * prhead():    prints the top of page header
 *
 *    buf    buffer with time field (and offset)
 *    cnt    number of chars in buf
 *    fname    fname field for header
 *    pagcnt    page number
 *
 * prhead() should be used carefully, we don't want to print out headers
 * for null input files or orphan headers at the end of files, and also
 * trailer processing is typically conditional on whether you've called
 * prhead() at least once for a file and incremented pagecnt.  Exactly
 * how to determine whether to print a header is a little different in
 * the context each output mode, but we let the caller figure that out.
 */
int
prhead(char *buf, char *fname, int pagcnt)
{
    int ips = 0;
    int ops = 0;

    beheaded = 1;

    if (skipping && pagcnt >= pgnm)
	skipping = 0;

    if (nohead || skipping)
	return (0);

    if ((putchar('\n') == EOF) || (putchar('\n') == EOF)) {
	pfail();
	return(1);
    }
    /*
     * posix is not clear if the header is subject to line length
     * restrictions. The specification for header line format
     * in the spec clearly does not limit length. No pr currently
     * restricts header length. However if we need to truncate in
     * an reasonable way, adjust the length of the printf by
     * changing HDFMT to allow a length max as an argument printf.
     * buf (which contains the offset spaces and time field could
     * also be trimmed
     *
     * note only the offset (if any) is processed for tab expansion
     */
    if (offst && otln(buf, offst, &ips, &ops, -1))
	return(1);
    (void)printf(HDFMT,buf+offst, fname, pagcnt);
    return(0);
}

/*
 * prtail():    pad page with empty lines (if required) and print page trailer
 *        if requested
 *
 *    cnt    	number of lines of padding needed
 *    incomp    was a '\n' missing from last line output
 *
 * prtail() can now be invoked unconditionally, with the notion that if
 * we haven't printed a header, there is no need for a trailer
 */
int
prtail(int cnt, int incomp)
{
    /*
     * if were's skipping to page N or haven't put out anything yet just exit
     */
    if (skipping || beheaded == 0)
	return (0);
    beheaded = 0;

    /*
     * if noheaders, only terminate an incomplete last line
     */
    if (nohead) {

	if (incomp) {
	    if (dspace)
		if (putchar('\n') == EOF) {
		    pfail();
		    return(1);
		}
	    if (putchar('\n') == EOF) {
		pfail();
		return(1);
	     }
	}
	/*
	 * but honor the formfeed request
	 */
	if (formfeed)
	    if (putchar(OUTFF) == EOF) {
		pfail();
		return(1);
	    }

    } else {

	/*
	 * if double space output two \n
	 *
  	 * XXX this all seems bogus, why are we doing it here???
	 * page length is in terms of output lines and only the input is
	 * supposed to be double spaced...  otln() users should be doing
	 * something like linect+=(dspace ? 2:1).
	 */
	if (dspace)
	    cnt *= 2;

	/*
	 * if an odd number of lines per page, add an extra \n
	 */
	if (addone)
	    ++cnt;

	/*
	 * either put out a form-feed or pad page with blanks
	 */
	if (formfeed) {
	    if (incomp)
		if (putchar('\n') == EOF) {
		    pfail();
		    return(1);
		}
	    if (putchar(OUTFF) == EOF) {
		    pfail();
		    return(1);
	    }

	} else {

	    if (incomp)
		cnt++;

	    cnt += TAILLEN;
	    while (--cnt >= 0) {
		if (putchar('\n') == EOF) {
		    pfail();
		    return(1);
		}
	    }
	}
    }

    return(0);
}

/*
 * terminate():    when a SIGINT is recvd
 */
/*ARGSUSED*/
void
terminate(int which_sig)
{
    flsh_errs();
    _exit(1);
}

void
mfail(void)
{
    ferrout("pr: memory allocation failed\n");
}

void
pfail(void)
{
    ferrout("pr: write failure, %s\n", strerror(errno));
}

void
usage(void)
{
    ferrout(
     "usage: pr [+page] [-column] [-adFfmrt] [-e[char][gap]] [-h header]\n");
    ferrout(
     "\t[-i[char][gap]] [-l lines] [-n[char][width]] [-o offset] [-s[char]]\n");
    ferrout(
     "\t[-w width] [file ...]\n");
}

/*
 * setup:    Validate command args, initialize and perform sanity 
 *        checks on options
 */
int
setup(int argc, char *argv[])
{
    int c;
    int eflag = 0;
    int iflag = 0;
    int wflag = 0;
    int cflag = 0;
    const char *errstr;

    if (isatty(fileno(stdout)))
	ferr = 1;

    while ((c = egetopt(argc, argv, "#adfFmrte?h:i?l:n?o:s?w:")) != -1) {
	switch (c) {
	case '+':
	    pgnm = strtonum(eoptarg, 1, INT_MAX, &errstr);
	    if (errstr) {
		ferrout("pr: +page number is %s: %s\n", errstr, eoptarg);
		return(1);
	    }
	    skipping = 1;
	    break;
	case '-':
	    clcnt = strtonum(eoptarg, 1, INT_MAX, &errstr);
	    if (errstr) {
		ferrout("pr: -columns number is %s: %s\n", errstr, eoptarg);
		return(1);
	    }
	    if (clcnt > 1)
		cflag = 1;
	    break;
	case 'a':
	    across = 1;
	    break;
	case 'd':
	    dspace = 1;
	    break;
	case 'e':
	    eflag = 1;
	    if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
		inchar = *eoptarg++;
	    else
		inchar = INCHAR;
	    if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
		ingap = strtonum(eoptarg, 0, INT_MAX, &errstr);
		if (errstr) {
		    ferrout("pr: -e gap is %s: %s\n", errstr, eoptarg);
		    return(1);
		}
		if (ingap == 0)
		    ingap = INGAP;
	    } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
		ferrout("pr: invalid value for -e %s\n", eoptarg);
		return(1);
	    } else
		ingap = INGAP;
	    break;
	case 'f':
	case 'F':
	    formfeed = 1;
	    break;
	case 'h':
	    header = eoptarg;
	    break;
	case 'i':
	    iflag = 1;
	    if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
		ochar = *eoptarg++;
	    else
		ochar = OCHAR;
	    if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
		ogap = strtonum(eoptarg, 0, INT_MAX, &errstr);
		if (errstr) {
		    ferrout("pr: -i gap is %s: %s\n", errstr, eoptarg);
		    return(1);
		}
		if (ogap == 0)
		    ogap = OGAP;
	    } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
		ferrout("pr: invalid value for -i %s\n", eoptarg);
		return(1);
	    } else
		ogap = OGAP;
	    break;
	case 'l':
	    lines = strtonum(eoptarg, 1, INT_MAX, &errstr);
	    if (errstr) {
		ferrout("pr: number of lines is %s: %s\n", errstr, eoptarg);
		return(1);
	    }
	    break;
	case 'm':
	    merge = 1;
	    break;
	case 'n':
	    if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
		nmchar = *eoptarg++;
	    else
		nmchar = NMCHAR;
	    if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
		nmwd = strtonum(eoptarg, 1, INT_MAX, &errstr);
		if (errstr) {
		    ferrout("pr: -n width is %s: %s\n", errstr, eoptarg);
		    return(1);
		}
	    } else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
		ferrout("pr: invalid value for -n %s\n", eoptarg);
		return(1);
	    } else
		nmwd = NMWD;
	    break;
	case 'o':
	    offst = strtonum(eoptarg, 1, INT_MAX, &errstr);
	    if (errstr) {
		ferrout("pr: -o offset is %s: %s\n", errstr, eoptarg);
		return(1);
	    }
	    break;
	case 'r':
	    nodiag = 1;
	    break;
	case 's':
	    sflag = 1;
	    if (eoptarg == NULL)
		schar = SCHAR;
	    else {
		schar = *eoptarg++;
		if (*eoptarg != '\0') {
		    ferrout("pr: invalid value for -s %s\n", eoptarg);
		    return(1);
		}
	    }
	    break;
	case 't':
	    nohead = 1;
	    break;
	case 'w':
	    wflag = 1;
	    pgwd = strtonum(eoptarg, 1, INT_MAX, &errstr);
	    if (errstr) {
		ferrout("pr: -w width is %s: %s\n", errstr, eoptarg);
		return(1);
	    }
	    break;
	default:
	    return(1);
	}
    }

    /*
     * default and sanity checks
     */
    inform++;

    if (!clcnt) {
	if (merge) {
	    if ((clcnt = argc - eoptind) <= 1) {
		clcnt = CLCNT;
#ifdef stupid
		merge = 0;
#endif
	    }
	} else
	    clcnt = CLCNT;
    }
    if (across) {
	if (clcnt == 1) {
	    ferrout("pr: -a flag requires multiple columns\n");
	    return(1);
	}
	if (merge) {
	    ferrout("pr: -m cannot be used with -a\n");
	    return(1);
	}
    }
    if (!wflag) {
	if (sflag)
	    pgwd = SPGWD;
	else
	    pgwd = PGWD;
    }
    if (cflag || merge) {
	if (!eflag) {
	    inchar = INCHAR;
	    ingap = INGAP;
	}
	if (!iflag) {
	    ochar = OCHAR;
	    ogap = OGAP;
	}
    }
    if (cflag) {
	if (merge) {
	    ferrout("pr: -m cannot be used with multiple columns\n");
	    return(1);
	}
	if (nmwd) {
	    colwd = (pgwd + 1 - (clcnt * (nmwd + 2)))/clcnt;
	    pgwd = ((colwd + nmwd + 2) * clcnt) - 1;
	} else {
	    colwd = (pgwd + 1 - clcnt)/clcnt;
	    pgwd = ((colwd + 1) * clcnt) - 1;
	}
	if (colwd < 1) {
	    ferrout("pr: page width is too small for %d columns\n",clcnt);
	    return(1);
	}
    }
    if (!lines)
	lines = LINES;

    /*
     * make sure long enough for headers. if not disable
     */
    if (lines <= HEADLEN + TAILLEN)
	nohead = 1;
    else if (!nohead)
	lines -= HEADLEN + TAILLEN;

    /*
     * adjust for double space on odd length pages
     */
    if (dspace) {
	if (lines == 1)
	    dspace = 0;
	else {
	    if (lines & 1)
		++addone;
	    lines /= 2;
	}
    }

    return(0);
}
