* Copyright (c) 1991 Keith Muller.
* 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
* 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
static char copyright
[] =
"@(#) Copyright (c) 1993\n\
The Regents of the University of California. All rights reserved.\n";
static char sccsid
[] = "@(#)pr.c 8.3 (Berkeley) 10/9/94";
* 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 seperated into 66-line pages, each
* with a header that includes the page number, date, time and the
* Complies with posix P1003.2/D11
int pgnm
; /* starting page number */
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 */
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 */
char *timefrmt
; /* time conversion string */
FILE *errf
; /* error message file pointer */
int addone
; /* page length is odd with double space */
int errcnt
; /* error count on file processing */
char digs
[] = "0123456789"; /* page number translation map */
if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
)
(void)signal(SIGINT
, terminate
);
ret_val
= setup(argc
, argv
);
* select the output format based on options
ret_val
= mulfile(argc
, argv
);
ret_val
= onecol(argc
, argv
);
ret_val
= horzcol(argc
, argv
);
ret_val
= vertcol(argc
, argv
);
* onecol: print files with only one column of output.
* Line length is unlimited.
if ((obuf
= malloc((unsigned)(LBUF
+ off
)*sizeof(char))) == NULL
) {
if ((hbuf
= malloc((unsigned)(HDBUF
+ offst
)*sizeof(char))) == NULL
) {
(void)memset(obuf
, (int)' ', offst
);
(void)memset(hbuf
, (int)' ', offst
);
while ((inf
= nxtfile(argc
, argv
, &fname
, ohbuf
, 0)) != NULL
) {
if (inskip(inf
, pgnm
, lines
))
while (linecnt
< lines
) {
if ((cnt
= inln(inf
,lbuf
,LBUF
,&cps
,0,&mor
)) < 0)
if (!linecnt
&& !nohead
&&
prhead(hbuf
, fname
, pagecnt
))
addnum(nbuf
, num
, ++lncnt
);
if (otln(obuf
,cnt
+off
, &ips
, &ops
, mor
))
} else if (otln(lbuf
, cnt
, &ips
, &ops
, mor
))
* if line bigger than buffer, get more
* whole line rcvd. reset tab proc. state
if (linecnt
&& prtail(lines
-linecnt
-lrgln
, lrgln
))
* vertcol: print files with more than one column of output down a page
int mxlen
= pgwd
+ offst
+ 1;
if ((buf
= malloc((unsigned)lines
*mxlen
*sizeof(char))) == NULL
) {
if ((hbuf
= malloc((unsigned)(HDBUF
+ offst
)*sizeof(char))) == NULL
) {
(void)memset(hbuf
, (int)' ', offst
);
* col pointers when no headers
(struct vcol
*)malloc((unsigned)mvc
*sizeof(struct vcol
))) == NULL
) {
* pointer into page where last data per line is located
if ((lstdat
= (char **)malloc((unsigned)lines
*sizeof(char *))) == NULL
){
* fast index lookups to locate start of lines
if ((indy
= (int *)malloc((unsigned)lines
*sizeof(int))) == NULL
) {
if ((lindy
= (int *)malloc((unsigned)lines
*sizeof(int))) == NULL
) {
* initialize buffer lookup indexes and offset area
for (j
= 0; j
< lines
; ++j
) {
indy
[j
] = lindy
[j
] + offst
;
(void)memset(ptbf
, (int)' ', offst
);
while ((inf
= nxtfile(argc
, argv
, &fname
, ohbuf
, 0)) != NULL
) {
if (inskip(inf
, pgnm
, lines
))
for (i
= 0; i
< clcnt
; ++i
) {
* if last column, do not pad
addnum(ptbf
, nmwd
, ++lncnt
);
cnt
= inln(inf
,ptbf
,colwd
,&cps
,1,&mor
);
* pad all but last column on page
else if ((pln
= col
-cnt
) > 0) {
* remember last char in line
* 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 ......
* check to see if last page needs to be reordered
if ((cnt
< 0) && cvc
&& ((mvc
-cvc
) >= clcnt
)){
if (!nohead
&& prhead(hbuf
, fname
, pagecnt
))
for (i
= 0; i
< pln
; ++i
) {
if (offst
&& otln(buf
,offst
,&ips
,&ops
,1))
for (j
= 0; j
< clcnt
; ++j
) {
* determine column length
if (otln(vc
[tvc
].pt
, cnt
, &ips
,
if (otln(buf
, 0, &ips
, &ops
, 0))
if (prtail((lines
- pln
), 0))
* done with output, go to next file
* determine how many lines to output
if (pln
&& !nohead
&& prhead(hbuf
, fname
, pagecnt
))
for (i
= 0; i
< pln
; ++i
) {
if ((j
= lstdat
[i
] - ptbf
) <= offst
)
if (otln(ptbf
, j
, &ips
, &ops
, 0))
if (pln
&& prtail((lines
- pln
), 0))
* horzcol: print files with more than one column of output across a page
register int col
= colwd
+ 1;
if ((buf
= malloc((unsigned)(pgwd
+offst
+1)*sizeof(char))) == NULL
) {
if ((hbuf
= malloc((unsigned)(HDBUF
+ offst
)*sizeof(char))) == NULL
) {
(void)memset(buf
, (int)' ', offst
);
(void)memset(hbuf
, (int)' ', offst
);
while ((inf
= nxtfile(argc
, argv
, &fname
, ohbuf
, 0)) != NULL
) {
if (inskip(inf
, pgnm
, lines
))
for (i
= 0; i
< lines
; ++i
) {
addnum(ptbf
, nmwd
, ++lncnt
);
if ((cnt
= inln(inf
,ptbf
,colwd
,&cps
,1,
* if last line skip padding
else if ((pln
= col
- cnt
) > 0) {
(void)memset(ptbf
,(int)' ',pln
);
if ((j
= lstdat
- buf
) <= offst
)
prhead(hbuf
, fname
, pagecnt
))
if (otln(buf
, j
, &ips
, &ops
, 0))
if (i
&& prtail(lines
-i
, 0))
* mulfile: print files with more than one column of output and
* more than one file concurrently
* array of FILE *, one for each operand
if ((fbuf
= (FILE **)malloc((unsigned)clcnt
*sizeof(FILE *))) == NULL
) {
if ((hbuf
= malloc((unsigned)(HDBUF
+ offst
)*sizeof(char))) == NULL
) {
* 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 unsuccesful file opens
* is unspecified by posix.
if ((fbuf
[j
] = nxtfile(argc
, argv
, &fname
, ohbuf
, 1)) == NULL
)
if (pgnm
&& (inskip(fbuf
[j
], pgnm
, lines
)))
* calculate page boundries based on open file count
colwd
= (pgwd
- clcnt
- nmwd
)/clcnt
;
pgwd
= ((colwd
+ 1) * clcnt
) - nmwd
- 2;
colwd
= (pgwd
+ 1 - clcnt
)/clcnt
;
pgwd
= ((colwd
+ 1) * clcnt
) - 1;
"pr: page width too small for %d columns\n", clcnt
);
if ((buf
= malloc((unsigned)(pgwd
+offst
+1)*sizeof(char))) == NULL
) {
(void)memset(buf
, (int)' ', offst
);
(void)memset(hbuf
, (int)' ', offst
);
* continue to loop while any file still has data
for (i
= 0; i
< lines
; ++i
) {
* add line number to line
addnum(ptbf
, nmwd
, ++lncnt
);
for (j
= 0; j
< clcnt
; ++j
) {
} else if ((cnt
= inln(fbuf
[j
], ptbf
, colwd
,
* if last ACTIVE column, done with line
} else if ((pln
= col
- cnt
) > 0) {
(void)memset(ptbf
, (int)' ', pln
);
if ((j
= lstdat
- buf
) <= offst
)
if (!i
&& !nohead
&& prhead(hbuf
, fname
, pagecnt
))
if (otln(buf
, j
, &ips
, &ops
, 0))
* if no more active files, done
if (i
&& prtail(lines
-i
, 0))
* inln(): input a line of data (unlimited length lines supported)
* Input is optionally expanded to spaces
* cps: column positon 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)
inln(inf
, buf
, lim
, cps
, trnc
, mor
)
register int gap
= ingap
;
register int chk
= (int)inchar
;
while ((--lim
>= 0) && ((ch
= getc(inf
)) != EOF
)) {
* is this the input "tab" char
* expand to number of spaces
col
= (ptbuf
- buf
) + *cps
;
* if more than this line, push back
if ((col
> lim
) && (ungetc(ch
, inf
) == EOF
))
while ((--col
>= 0) && (--lim
>= 0))
while ((--lim
>= 0) && ((ch
= getc(inf
)) != EOF
)) {
* line was larger than limit
* throw away rest of line
while ((ch
= getc(inf
)) != EOF
) {
* save column offset if not truncated
* 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
otln(buf
, cnt
, svips
, svops
, mor
)
register int ops
; /* last col output */
register int ips
; /* last col in buf examined */
* count number of spaces and ochar in buffer
* simulate ochar processing
ips
+= gap
- (ips
% gap
);
* got a non space char; contract out spaces
* use as many ochar as will fit
if ((tbps
= ops
+ gap
- (ops
% gap
)) > ips
)
if (putchar(ochar
) == EOF
) {
if (putchar(' ') == EOF
) {
if (putchar(*buf
++) == EOF
) {
* if incomplete line, save position counts
* use as many ochar as will fit
if ((tbps
= ops
+ gap
- (ops
% gap
)) > ips
)
if (putchar(ochar
) == EOF
) {
if (putchar(' ') == EOF
) {
* output is not contracted
if (cnt
&& (fwrite(buf
, sizeof(char), cnt
, stdout
) <= 0)) {
* process line end and double space as required
if ((putchar('\n') == EOF
) || (dspace
&& (putchar('\n') == EOF
))) {
* 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
inskip(inf
, pgcnt
, lncnt
)
while ((c
= getc(inf
)) != EOF
) {
if ((c
== '\n') && (--cnt
== 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)
nxtfile(argc
, argv
, fname
, buf
, dt
)
struct tm
*timeptr
= NULL
;
* no file listed; default, use standard input
if (gettimeofday(&tv
, &tz
) < 0) {
(void)fprintf(errf
, "pr: cannot get time of day, %s\n",
timeptr
= localtime(&(tv
.tv_sec
));
for (; eoptind
< argc
; ++eoptind
) {
if (strcmp(argv
[eoptind
], "-") == 0) {
* process a "-" for filename
if (nohead
|| (dt
&& twice
))
if (gettimeofday(&tv
, &tz
) < 0) {
"pr: cannot get time of day, %s\n",
timeptr
= localtime(&(tv
.tv_sec
));
if ((inf
= fopen(argv
[eoptind
], "r")) == NULL
) {
(void)fprintf(errf
, "pr: Cannot open %s, %s\n",
argv
[eoptind
], strerror(errno
));
if (nohead
|| (dt
&& twice
))
if (gettimeofday(&tv
, &tz
) < 0) {
"pr: cannot get time of day, %s\n",
timeptr
= localtime(&(tv
.tv_sec
));
if (fstat(fileno(inf
), &statbuf
) < 0) {
"pr: Cannot stat %s, %s\n",
argv
[eoptind
], strerror(errno
));
timeptr
= localtime(&(statbuf
.st_mtime
));
* set up time field used in header
if (strftime(buf
, HDBUF
, timefrmt
, timeptr
) <= 0) {
(void)fputs("pr: time conversion failed\n", errf
);
* 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
* 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.
register char *pt
= buf
+ wdth
;
} while (line
&& (pt
> buf
));
* pad with space as required
* 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
prhead(buf
, fname
, pagcnt
)
if ((putchar('\n') == EOF
) || (putchar('\n') == EOF
)) {
* 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 arguement printf.
* buf (which contains the offset spaces and time field could
* note only the offset (if any) is processed for tab expansion
if (offst
&& otln(buf
, offst
, &ips
, &ops
, -1))
(void)printf(HDFMT
,buf
+offst
, fname
, pagcnt
);
* prtail(): pad page with empty lines (if required) and print page trailer
* cnt number of lines of padding needed
* incomp was a '\n' missing from last line output
* only pad with no headers when incomplete last line
if ((dspace
&& (putchar('\n') == EOF
)) ||
(putchar('\n') == EOF
)) {
* if double space output two \n
* if an odd number of lines per page, add an extra \n
if ((incomp
&& (putchar('\n') == EOF
)) ||
(putchar('\f') == EOF
)) {
if (putchar('\n') == EOF
) {
* terminate(): when a SIGINT is recvd
* flsh_errs(): output saved up diagnostic messages after all normal
* processing has completed
while (fgets(buf
, BUFSIZ
, errf
) != NULL
)
(void)fputs(buf
, stderr
);
(void)fputs("pr: memory allocation failed\n", errf
);
(void)fprintf(errf
, "pr: write failure, %s\n", strerror(errno
));
"usage: pr [+page] [-col] [-adFmrt] [-e[ch][gap]] [-h header]\n",
" [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n",
" [-s[ch]] [-w width] [-] [file ...]\n", errf
);
* setup: Validate command args, initialize and perform sanity
if (isatty(fileno(stdout
))) {
* defer diagnostics until processing is done
if ((errf
= tmpfile()) == NULL
) {
(void)fputs("Cannot defer diagnostic messages\n",stderr
);
while ((c
= egetopt(argc
, argv
, "#adFmrte?h:i?l:n?o:s?w:")) != EOF
) {
if ((pgnm
= atoi(eoptarg
)) < 1) {
(void)fputs("pr: +page number must be 1 or more\n",
if ((clcnt
= atoi(eoptarg
)) < 1) {
(void)fputs("pr: -columns must be 1 or more\n",
if ((eoptarg
!= NULL
) && !isdigit(*eoptarg
))
if ((eoptarg
!= NULL
) && isdigit(*eoptarg
)) {
if ((ingap
= atoi(eoptarg
)) < 0) {
"pr: -e gap must be 0 or more\n", errf
);
} else if ((eoptarg
!= NULL
) && (*eoptarg
!= '\0')) {
"pr: invalid value for -e %s\n", eoptarg
);
if ((eoptarg
!= NULL
) && !isdigit(*eoptarg
))
if ((eoptarg
!= NULL
) && isdigit(*eoptarg
)) {
if ((ogap
= atoi(eoptarg
)) < 0) {
"pr: -i gap must be 0 or more\n", errf
);
} else if ((eoptarg
!= NULL
) && (*eoptarg
!= '\0')) {
"pr: invalid value for -i %s\n", eoptarg
);
if (!isdigit(*eoptarg
) || ((lines
=atoi(eoptarg
)) < 1)) {
"pr: Number of lines must be 1 or more\n",errf
);
if ((eoptarg
!= NULL
) && !isdigit(*eoptarg
))
if ((eoptarg
!= NULL
) && isdigit(*eoptarg
)) {
if ((nmwd
= atoi(eoptarg
)) < 1) {
"pr: -n width must be 1 or more\n",errf
);
} else if ((eoptarg
!= NULL
) && (*eoptarg
!= '\0')) {
"pr: invalid value for -n %s\n", eoptarg
);
if (!isdigit(*eoptarg
) || ((offst
= atoi(eoptarg
))< 1)){
(void)fputs("pr: -o offset must be 1 or more\n",
"pr: invalid value for -s %s\n", eoptarg
);
if (!isdigit(*eoptarg
) || ((pgwd
= atoi(eoptarg
)) < 1)){
"pr: -w width must be 1 or more \n",errf
);
* default and sanity checks
if ((clcnt
= argc
- eoptind
) <= 1) {
(void)fputs("pr: -a flag requires multiple columns\n",
(void)fputs("pr: -m cannot be used with -a\n", errf
);
"pr: -m cannot be used with multiple columns\n", errf
);
colwd
= (pgwd
+ 1 - (clcnt
* (nmwd
+ 2)))/clcnt
;
pgwd
= ((colwd
+ nmwd
+ 2) * clcnt
) - 1;
colwd
= (pgwd
+ 1 - clcnt
)/clcnt
;
pgwd
= ((colwd
+ 1) * clcnt
) - 1;
"pr: page width is too small for %d columns\n",clcnt
);
* make sure long enough for headers. if not disable
if (lines
<= HEADLEN
+ TAILLEN
)
lines
-= HEADLEN
+ TAILLEN
;
* adjust for double space on odd length pages
if ((timefrmt
= getenv("LC_TIME")) == NULL
)