freebsd-src/usr.bin/pr/pr.c
Warner Losh 0b8224d1cc Remove copyright strings ifdef'd out
We've ifdef'd out the copyright strings for some time now. Go ahead and
remove the ifdefs. Plus whatever other detritis was left over from other
recent removals. These copyright strings are present in the comments and
are largely from CSRG's attempt at adding their copyright to every
binary file (which modern interpretations of the license doesn't
require).

Sponsored by:		Netflix
2023-11-26 22:23:58 -07:00

1850 lines
35 KiB
C

/*-
* SPDX-License-Identifier: BSD-4-Clause
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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/time.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <langinfo.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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
*/
/*
* parameter variables
*/
static int pgnm; /* starting page number */
static int clcnt; /* number of columns */
static int colwd; /* column data width - multiple columns */
static int across; /* mult col flag; write across page */
static int dspace; /* double space flag */
static char inchar; /* expand input char */
static int ingap; /* expand input gap */
static int pausefst; /* Pause before first page */
static int pauseall; /* Pause before each page */
static int formfeed; /* use formfeed as trailer */
static char *header; /* header name instead of file name */
static char ochar; /* contract output char */
static int ogap; /* contract output gap */
static int lines; /* number of lines per page */
static int merge; /* merge multiple files in output */
static char nmchar; /* line numbering append char */
static int nmwd; /* width of line number field */
static int offst; /* number of page offset spaces */
static int nodiag; /* do not report file open errors */
static char schar; /* text column separation character */
static int sflag; /* -s option for multiple columns */
static int nohead; /* do not write head and trailer */
static int pgwd; /* page width with multiple col output */
static char *timefrmt; /* time conversion string */
/*
* misc globals
*/
static FILE *err; /* error message file pointer */
static int addone; /* page length is odd with double space */
static int errcnt; /* error count on file processing */
static char digs[] = "0123456789"; /* page number translation map */
static char fnamedefault[] = FNAME;
int
main(int argc, char *argv[])
{
int ret_val;
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);
free(timefrmt);
} else
usage();
flsh_errs();
if (errcnt || ret_val)
exit(1);
return(0);
}
/*
* Check if we should pause and write an alert character and wait for a
* carriage return on /dev/tty.
*/
static void
ttypause(int pagecnt)
{
int pch;
FILE *ttyfp;
if ((pauseall || (pausefst && pagecnt == 1)) &&
isatty(STDOUT_FILENO)) {
if ((ttyfp = fopen("/dev/tty", "r")) != NULL) {
(void)putc('\a', stderr);
while ((pch = getc(ttyfp)) != '\n' && pch != EOF)
;
(void)fclose(ttyfp);
}
}
}
/*
* onecol: print files with only one column of output.
* Line length is unlimited.
*/
int
onecol(int argc, char *argv[])
{
int cnt = -1;
int off;
int lrgln;
int linecnt;
int num;
int lncnt;
int pagecnt;
int ips;
int ops;
int cps;
char *obuf;
char *lbuf;
char *nbuf;
char *hbuf;
char *ohbuf;
FILE *inf;
const char *fname;
int mor;
if (nmwd)
num = nmwd + 1;
else
num = 0;
off = num + offst;
/*
* allocate line buffer
*/
if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL) {
mfail();
return(1);
}
/*
* allocate header buffer
*/
if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
free(obuf);
mfail();
return(1);
}
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) {
if (pgnm) {
/*
* skip to specified page
*/
if (inskip(inf, pgnm, lines))
continue;
pagecnt = pgnm;
} else
pagecnt = 1;
lncnt = 0;
/*
* loop by page
*/
for(;;) {
linecnt = 0;
lrgln = 0;
ops = 0;
ips = 0;
cps = 0;
ttypause(pagecnt);
/*
* loop by line
*/
while (linecnt < lines) {
/*
* input next line
*/
if ((cnt = inln(inf,lbuf,LBUF,&cps,0,&mor)) < 0)
break;
if (!linecnt && !nohead &&
prhead(hbuf, fname, pagecnt))
goto err;
/*
* start of new line.
*/
if (!lrgln) {
if (num)
addnum(nbuf, num, ++lncnt);
if (otln(obuf,cnt+off, &ips, &ops, mor))
goto err;
} else if (otln(lbuf, cnt, &ips, &ops, mor))
goto err;
/*
* if line bigger than buffer, get more
*/
if (mor) {
lrgln = 1;
continue;
}
/*
* whole line rcvd. reset tab proc. state
*/
++linecnt;
lrgln = 0;
ops = 0;
ips = 0;
}
/*
* fill to end of page
*/
if (linecnt && prtail(lines-linecnt-lrgln, lrgln))
goto err;
/*
* On EOF go to next file
*/
if (cnt < 0)
break;
++pagecnt;
}
if (inf != stdin)
(void)fclose(inf);
}
if (eoptind < argc)
goto err;
free(hbuf);
free(obuf);
return(0);
err:
free(hbuf);
free(obuf);
return(1);
}
/*
* vertcol: print files with more than one column of output down a page
*/
int
vertcol(int argc, char *argv[])
{
char *ptbf;
char **lstdat = NULL;
int i;
int j;
int cnt = -1;
int pln;
int *indy = NULL;
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;
const char *fname;
FILE *inf;
int ips = 0;
int cps = 0;
int ops = 0;
int mor = 0;
int retval = 1;
/*
* allocate page buffer
*/
if ((buf = malloc((unsigned)lines*mxlen*sizeof(char))) == NULL) {
mfail();
return(1);
}
/*
* allocate page header
*/
if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
mfail();
goto out;
}
ohbuf = hbuf + offst;
if (offst)
(void)memset(hbuf, (int)' ', offst);
/*
* col pointers when no headers
*/
mvc = lines * clcnt;
if ((vc =
(struct vcol *)malloc((unsigned)mvc*sizeof(struct vcol))) == NULL) {
mfail();
goto out;
}
/*
* pointer into page where last data per line is located
*/
if ((lstdat = (char **)malloc((unsigned)lines*sizeof(char *))) == NULL){
mfail();
goto out;
}
/*
* fast index lookups to locate start of lines
*/
if ((indy = (int *)malloc((unsigned)lines*sizeof(int))) == NULL) {
mfail();
goto out;
}
if ((lindy = (int *)malloc((unsigned)lines*sizeof(int))) == NULL) {
mfail();
goto out;
}
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) {
if (pgnm) {
/*
* skip to requested page
*/
if (inskip(inf, pgnm, lines))
continue;
pagecnt = pgnm;
} else
pagecnt = 1;
lncnt = 0;
/*
* loop by page
*/
for(;;) {
ttypause(pagecnt);
/*
* 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
*/
cnt = inln(inf,ptbf,colwd,&cps,1,&mor);
vc[cvc++].cnt = cnt;
if (cnt < 0)
break;
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;
}
if (cnt < 0)
break;
}
/*
* 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.
*
* if ((cnt < 0) && nohead && cvc ......
*/
--cvc;
/*
* check to see if last page needs to be reordered
*/
if ((cnt < 0) && cvc && ((mvc-cvc) >= clcnt)){
pln = cvc/clcnt;
if (cvc % clcnt)
++pln;
/*
* print header
*/
if (!nohead && prhead(hbuf, fname, pagecnt))
goto out;
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;
}
/*
* pad to end of page
*/
if (prtail((lines - pln), 0))
goto out;
/*
* done with output, go to next file
*/
break;
}
/*
* determine how many lines to output
*/
if (i > 0)
pln = lines;
else
pln = j;
/*
* print header
*/
if (pln && !nohead && prhead(hbuf, fname, pagecnt))
goto out;
/*
* output each line
*/
for (i = 0; i < pln; ++i) {
ptbf = buf + lindy[i];
if ((j = lstdat[i] - ptbf) <= offst)
break;
if (otln(ptbf, j, &ips, &ops, 0))
goto out;
}
/*
* pad to end of page
*/
if (pln && prtail((lines - pln), 0))
goto out;
/*
* if EOF go to next file
*/
if (cnt < 0)
break;
++pagecnt;
}
if (inf != stdin)
(void)fclose(inf);
}
if (eoptind < argc)
goto out;
retval = 0;
out:
free(lindy);
free(indy);
free(lstdat);
free(vc);
free(hbuf);
free(buf);
return(retval);
}
/*
* horzcol: print files with more than one column of output across a page
*/
int
horzcol(int argc, char *argv[])
{
char *ptbf;
int pln;
int cnt = -1;
char *lstdat;
int col = colwd + 1;
int j;
int i;
int lncnt;
int pagecnt;
char *buf;
char *hbuf;
char *ohbuf;
const char *fname;
FILE *inf;
int ips = 0;
int cps = 0;
int ops = 0;
int mor = 0;
if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) {
mfail();
return(1);
}
/*
* page header
*/
if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
free(buf);
mfail();
return(1);
}
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) {
if (pgnm) {
if (inskip(inf, pgnm, lines))
continue;
pagecnt = pgnm;
} else
pagecnt = 1;
lncnt = 0;
/*
* loop by page
*/
for(;;) {
ttypause(pagecnt);
/*
* 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
*/
if ((cnt = inln(inf,ptbf,colwd,&cps,1,
&mor)) < 0)
break;
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;
}
}
/*
* determine line length
*/
if ((j = lstdat - buf) <= offst)
break;
if (!i && !nohead &&
prhead(hbuf, fname, pagecnt))
goto err;
/*
* output line
*/
if (otln(buf, j, &ips, &ops, 0))
goto err;
}
/*
* pad to end of page
*/
if (i && prtail(lines-i, 0))
goto err;
/*
* if EOF go to next file
*/
if (cnt < 0)
break;
++pagecnt;
}
if (inf != stdin)
(void)fclose(inf);
}
if (eoptind < argc)
goto err;
free(hbuf);
free(buf);
return(0);
err:
free(hbuf);
free(buf);
return(1);
}
/*
* 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 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;
const char *fname;
int ips = 0;
int cps = 0;
int ops = 0;
int mor = 0;
int retval = 1;
/*
* array of FILE *, one for each operand
*/
if ((fbuf = (FILE **)malloc((unsigned)clcnt*sizeof(FILE *))) == NULL) {
mfail();
goto out;
}
/*
* page header
*/
if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
mfail();
goto out;
}
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.
*/
j = 0;
while (j < clcnt) {
if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) == NULL)
break;
if (pgnm && (inskip(fbuf[j], pgnm, lines)))
fbuf[j] = NULL;
++j;
}
/*
* if no files, exit
*/
if (!j)
goto out;
/*
* calculate page boundaries based on open file count
*/
clcnt = j;
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) {
(void)fprintf(err,
"pr: page width too small for %d columns\n", clcnt);
goto out;
}
actf = clcnt;
col = colwd + 1;
/*
* line buffer
*/
if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) {
mfail();
goto out;
}
if (offst) {
(void)memset(buf, (int)' ', offst);
(void)memset(hbuf, (int)' ', offst);
}
if (pgnm)
pagecnt = pgnm;
else
pagecnt = 1;
lncnt = 0;
/*
* continue to loop while any file still has data
*/
while (actf > 0) {
ttypause(pagecnt);
/*
* 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;
}
j = 0;
fproc = 0;
/*
* loop by column
*/
for (j = 0; j < clcnt; ++j) {
if (fbuf[j] == NULL) {
/*
* empty column; EOF
*/
cnt = 0;
} else if ((cnt = inln(fbuf[j], ptbf, colwd,
&cps, 1, &mor)) < 0) {
/*
* EOF hit; no data
*/
if (fbuf[j] != stdin)
(void)fclose(fbuf[j]);
fbuf[j] = NULL;
--actf;
cnt = 0;
} else {
/*
* process file data
*/
ptbf += cnt;
lstdat = ptbf;
fproc++;
}
/*
* if last ACTIVE column, done with line
*/
if (fproc >= actf)
break;
/*
* pad to end of column
*/
if (sflag) {
*ptbf++ = schar;
} else if ((pln = col - cnt) > 0) {
(void)memset(ptbf, (int)' ', pln);
ptbf += pln;
}
}
/*
* calculate data in line
*/
if ((j = lstdat - buf) <= offst)
break;
if (!i && !nohead && prhead(hbuf, fname, pagecnt))
goto out;
/*
* output line
*/
if (otln(buf, j, &ips, &ops, 0))
goto out;
/*
* if no more active files, done
*/
if (actf <= 0) {
++i;
break;
}
}
/*
* pad to end of page
*/
if (i && prtail(lines-i, 0))
goto out;
++pagecnt;
}
if (eoptind < argc)
goto out;
retval = 0;
out:
free(buf);
free(hbuf);
free(fbuf);
return(retval);
}
/*
* inln(): input a line of data (unlimited length lines supported)
* Input is optionally expanded to spaces
*
* inf: file
* buf: buffer
* lim: buffer length
* 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 *cps, int trnc, int *mor)
{
int col;
int gap = ingap;
int ch = EOF;
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))
return(1);
/*
* expand to spaces
*/
while ((--col >= 0) && (--lim >= 0))
*ptbuf++ = ' ';
continue;
}
if (ch == '\n')
break;
*ptbuf++ = ch;
}
} else {
/*
* no expansion
*/
while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
if (ch == '\n')
break;
*ptbuf++ = ch;
}
}
col = ptbuf - buf;
if (ch == EOF) {
*mor = 0;
*cps = 0;
if (!col)
return(-1);
return(col);
}
if (ch == '\n') {
/*
* entire line processed
*/
*mor = 0;
*cps = 0;
return(col);
}
/*
* 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;
}
return(col);
}
/*
* 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;
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 (ips - ops > 1) {
/*
* 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 (ips - ops > 1) {
/*
* 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) <= 0)) {
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);
}
/*
* 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);
}
/*
* 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, const char **fname, char *buf, int dt)
{
FILE *inf = NULL;
time_t tv_sec;
struct tm *timeptr = NULL;
struct stat statbuf;
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 = fnamedefault;
if (nohead)
return(inf);
if ((tv_sec = time(NULL)) == -1) {
++errcnt;
(void)fprintf(err, "pr: cannot get time of day, %s\n",
strerror(errno));
eoptind = argc - 1;
return(NULL);
}
timeptr = localtime(&tv_sec);
}
for (; eoptind < argc; ++eoptind) {
if (strcmp(argv[eoptind], "-") == 0) {
/*
* process a "-" for filename
*/
clearerr(stdin);
inf = stdin;
if (header != NULL)
*fname = header;
else
*fname = fnamedefault;
++eoptind;
if (nohead || (dt && twice))
return(inf);
if ((tv_sec = time(NULL)) == -1) {
++errcnt;
(void)fprintf(err,
"pr: cannot get time of day, %s\n",
strerror(errno));
return(NULL);
}
timeptr = localtime(&tv_sec);
} else {
/*
* normal file processing
*/
if ((inf = fopen(argv[eoptind], "r")) == NULL) {
++errcnt;
if (nodiag)
continue;
(void)fprintf(err, "pr: cannot open %s, %s\n",
argv[eoptind], strerror(errno));
continue;
}
if (header != NULL)
*fname = header;
else if (dt)
*fname = fnamedefault;
else
*fname = argv[eoptind];
++eoptind;
if (nohead || (dt && twice))
return(inf);
if (dt) {
if ((tv_sec = time(NULL)) == -1) {
++errcnt;
(void)fprintf(err,
"pr: cannot get time of day, %s\n",
strerror(errno));
fclose(inf);
return(NULL);
}
timeptr = localtime(&tv_sec);
} else {
if (fstat(fileno(inf), &statbuf) < 0) {
++errcnt;
(void)fclose(inf);
(void)fprintf(err,
"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, timefrmt, timeptr) <= 0) {
++errcnt;
if (inf != stdin)
(void)fclose(inf);
(void)fputs("pr: time conversion failed\n", err);
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
*/
int
prhead(char *buf, const char *fname, int pagcnt)
{
int ips = 0;
int ops = 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
* a reasonable way, adjust the length of the printf by
* changing HDFMT to allow a length max as an argument to 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
*/
int
prtail(int cnt, int incomp)
{
if (nohead) {
/*
* only pad with no headers when incomplete last line
*/
if (incomp &&
((dspace && (putchar('\n') == EOF)) ||
(putchar('\n') == EOF))) {
pfail();
return(1);
}
/*
* but honor the formfeed request
*/
if (formfeed) {
if (putchar('\f') == EOF) {
pfail();
return(1);
}
}
return(0);
}
/*
* if double space output two \n
*/
if (dspace)
cnt *= 2;
/*
* if an odd number of lines per page, add an extra \n
*/
if (addone)
++cnt;
/*
* pad page
*/
if (formfeed) {
if ((incomp && (putchar('\n') == EOF)) ||
(putchar('\f') == EOF)) {
pfail();
return(1);
}
return(0);
}
cnt += TAILLEN;
while (--cnt >= 0) {
if (putchar('\n') == EOF) {
pfail();
return(1);
}
}
return(0);
}
/*
* terminate(): when a SIGINT is recvd
*/
void
terminate(int which_sig __unused)
{
flsh_errs();
exit(1);
}
/*
* flsh_errs(): output saved up diagnostic messages after all normal
* processing has completed
*/
void
flsh_errs(void)
{
char buf[BUFSIZ];
(void)fflush(stdout);
(void)fflush(err);
if (err == stderr)
return;
rewind(err);
while (fgets(buf, BUFSIZ, err) != NULL)
(void)fputs(buf, stderr);
}
void
mfail(void)
{
(void)fputs("pr: memory allocation failed\n", err);
}
void
pfail(void)
{
(void)fprintf(err, "pr: write failure, %s\n", strerror(errno));
}
void
usage(void)
{
(void)fputs(
"usage: pr [+page] [-col] [-adFfmprt] [-e[ch][gap]] [-h header]\n",
err);
(void)fputs(
" [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n",err);
(void)fputs(
" [-L locale] [-s[ch]] [-w width] [-] [file ...]\n", err);
}
/*
* setup: Validate command args, initialize and perform sanity
* checks on options
*/
int
setup(int argc, char *argv[])
{
int c;
int d_first;
int eflag = 0;
int iflag = 0;
int wflag = 0;
int cflag = 0;
char *Lflag = NULL;
if (isatty(fileno(stdout))) {
/*
* defer diagnostics until processing is done
*/
if ((err = tmpfile()) == NULL) {
err = stderr;
(void)fputs("Cannot defer diagnostic messages\n",stderr);
return(1);
}
} else
err = stderr;
while ((c = egetopt(argc, argv, "#adFfmrte?h:i?L:l:n?o:ps?w:")) != -1) {
switch (c) {
case '+':
if ((pgnm = atoi(eoptarg)) < 1) {
(void)fputs("pr: +page number must be 1 or more\n",
err);
return(1);
}
break;
case '-':
if ((clcnt = atoi(eoptarg)) < 1) {
(void)fputs("pr: -columns must be 1 or more\n",err);
return(1);
}
if (clcnt > 1)
++cflag;
break;
case 'a':
++across;
break;
case 'd':
++dspace;
break;
case 'e':
++eflag;
if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
inchar = *eoptarg++;
else
inchar = INCHAR;
if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
if ((ingap = atoi(eoptarg)) < 0) {
(void)fputs(
"pr: -e gap must be 0 or more\n", err);
return(1);
}
if (ingap == 0)
ingap = INGAP;
} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
(void)fprintf(err,
"pr: invalid value for -e %s\n", eoptarg);
return(1);
} else
ingap = INGAP;
break;
case 'f':
++pausefst;
/*FALLTHROUGH*/
case 'F':
++formfeed;
break;
case 'h':
header = eoptarg;
break;
case 'i':
++iflag;
if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
ochar = *eoptarg++;
else
ochar = OCHAR;
if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
if ((ogap = atoi(eoptarg)) < 0) {
(void)fputs(
"pr: -i gap must be 0 or more\n", err);
return(1);
}
if (ogap == 0)
ogap = OGAP;
} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
(void)fprintf(err,
"pr: invalid value for -i %s\n", eoptarg);
return(1);
} else
ogap = OGAP;
break;
case 'L':
Lflag = eoptarg;
break;
case 'l':
if (!isdigit((unsigned char)*eoptarg) || ((lines=atoi(eoptarg)) < 1)) {
(void)fputs(
"pr: number of lines must be 1 or more\n",err);
return(1);
}
break;
case 'm':
++merge;
break;
case 'n':
if ((eoptarg != NULL) && !isdigit((unsigned char)*eoptarg))
nmchar = *eoptarg++;
else
nmchar = NMCHAR;
if ((eoptarg != NULL) && isdigit((unsigned char)*eoptarg)) {
if ((nmwd = atoi(eoptarg)) < 1) {
(void)fputs(
"pr: -n width must be 1 or more\n",err);
return(1);
}
} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
(void)fprintf(err,
"pr: invalid value for -n %s\n", eoptarg);
return(1);
} else
nmwd = NMWD;
break;
case 'o':
if (!isdigit((unsigned char)*eoptarg) || ((offst = atoi(eoptarg))< 1)){
(void)fputs("pr: -o offset must be 1 or more\n",
err);
return(1);
}
break;
case 'p':
++pauseall;
break;
case 'r':
++nodiag;
break;
case 's':
++sflag;
if (eoptarg == NULL)
schar = SCHAR;
else {
schar = *eoptarg++;
if (*eoptarg != '\0') {
(void)fprintf(err,
"pr: invalid value for -s %s\n",
eoptarg);
return(1);
}
}
break;
case 't':
++nohead;
break;
case 'w':
++wflag;
if ((eoptarg == NULL ) ||
!isdigit((unsigned char)*eoptarg) ||
((pgwd = atoi(eoptarg)) < 1)){
(void)fputs(
"pr: -w width must be 1 or more \n",err);
return(1);
}
break;
case '?':
default:
return(1);
}
}
/*
* default and sanity checks
*/
if (!clcnt) {
if (merge) {
if ((clcnt = argc - eoptind) <= 1) {
clcnt = CLCNT;
merge = 0;
}
} else
clcnt = CLCNT;
}
if (across) {
if (clcnt == 1) {
(void)fputs("pr: -a flag requires multiple columns\n",
err);
return(1);
}
if (merge) {
(void)fputs("pr: -m cannot be used with -a\n", err);
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) {
(void)fputs(
"pr: -m cannot be used with multiple columns\n", err);
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) {
(void)fprintf(err,
"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;
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;
}
}
(void) setlocale(LC_TIME, (Lflag != NULL) ? Lflag : "");
d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
timefrmt = strdup(d_first ? TIMEFMTD : TIMEFMTM);
return(0);
}