pr.c revision 1591
1/*-
2 * Copyright (c) 1991 Keith Muller.
3 * Copyright (c) 1993
4 *	The Regents of the University of California.  All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * Keith Muller of the University of California, San Diego.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 *    must display the following acknowledgement:
19 *	This product includes software developed by the University of
20 *	California, Berkeley and its contributors.
21 * 4. Neither the name of the University nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38#ifndef lint
39static char copyright[] =
40"@(#) Copyright (c) 1993\n\
41	The Regents of the University of California.  All rights reserved.\n";
42#endif /* not lint */
43
44#ifndef lint
45static char sccsid[] = "@(#)pr.c	8.2 (Berkeley) 4/16/94";
46#endif /* not lint */
47
48#include <sys/types.h>
49#include <sys/time.h>
50#include <sys/stat.h>
51
52#include <ctype.h>
53#include <errno.h>
54#include <signal.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59
60#include "pr.h"
61#include "extern.h"
62
63/*
64 * pr:	a printing and pagination filter. If multiple input files
65 *	are specified, each is read, formatted, and written to standard
66 *	output. By default, input is seperated into 66-line pages, each
67 *	with a header that includes the page number, date, time and the
68 *	files pathname.
69 *
70 *	Complies with posix P1003.2/D11
71 */
72
73/*
74 * parameter variables
75 */
76int	pgnm;			/* starting page number */
77int	clcnt;			/* number of columns */
78int	colwd;			/* column data width - multiple columns */
79int	across;			/* mult col flag; write across page */
80int	dspace;			/* double space flag */
81char	inchar;			/* expand input char */
82int	ingap;			/* expand input gap */
83int	formfeed;		/* use formfeed as trailer */
84char	*header;		/* header name instead of file name */
85char	ochar;			/* contract output char */
86int	ogap;			/* contract output gap */
87int	lines;			/* number of lines per page */
88int	merge;			/* merge multiple files in output */
89char	nmchar;			/* line numbering append char */
90int	nmwd;			/* width of line number field */
91int	offst;			/* number of page offset spaces */
92int	nodiag;			/* do not report file open errors */
93char	schar;			/* text column separation character */
94int	sflag;			/* -s option for multiple columns */
95int	nohead;			/* do not write head and trailer */
96int	pgwd;			/* page width with multiple col output */
97char	*timefrmt;		/* time conversion string */
98
99/*
100 * misc globals
101 */
102FILE	*err;			/* error message file pointer */
103int	addone;			/* page length is odd with double space */
104int	errcnt;			/* error count on file processing */
105char	digs[] = "0123456789";	/* page number translation map */
106
107int
108main(argc, argv)
109        int argc;
110        char *argv[];
111{
112	int ret_val;
113
114	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
115		(void)signal(SIGINT, terminate);
116	ret_val = setup(argc, argv);
117	if (!ret_val) {
118		/*
119		 * select the output format based on options
120		 */
121		if (merge)
122			ret_val = mulfile(argc, argv);
123		else if (clcnt == 1)
124			ret_val = onecol(argc, argv);
125		else if (across)
126			ret_val = horzcol(argc, argv);
127		else
128			ret_val = vertcol(argc, argv);
129	} else
130		usage();
131	flsh_errs();
132	if (errcnt || ret_val)
133		exit(1);
134	return(0);
135}
136
137/*
138 * onecol:	print files with only one column of output.
139 *		Line length is unlimited.
140 */
141int
142onecol(argc, argv)
143        int argc;
144        char *argv[];
145{
146	register int cnt = -1;
147	register int off;
148	register int lrgln;
149	register int linecnt;
150	register int num;
151	int lncnt;
152	int pagecnt;
153	int ips;
154	int ops;
155	int cps;
156	char *obuf;
157	char *lbuf;
158	char *nbuf;
159	char *hbuf;
160	char *ohbuf;
161	FILE *inf;
162	char *fname;
163	int mor;
164
165	if (nmwd)
166		num = nmwd + 1;
167	else
168		num = 0;
169	off = num + offst;
170
171	/*
172	 * allocate line buffer
173	 */
174	if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL) {
175		mfail();
176		return(1);
177	}
178	/*
179	 * allocate header buffer
180	 */
181	if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
182		mfail();
183		return(1);
184	}
185
186	ohbuf = hbuf + offst;
187	nbuf = obuf + offst;
188	lbuf = nbuf + num;
189	if (num)
190		nbuf[--num] = nmchar;
191	if (offst) {
192		(void)memset(obuf, (int)' ', offst);
193		(void)memset(hbuf, (int)' ', offst);
194	}
195
196	/*
197	 * loop by file
198	 */
199	while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
200		if (pgnm) {
201			/*
202			 * skip to specified page
203			 */
204			if (inskip(inf, pgnm, lines))
205				continue;
206			pagecnt = pgnm;
207		} else
208			pagecnt = 1;
209		lncnt = 0;
210
211		/*
212		 * loop by page
213		 */
214		for(;;) {
215			linecnt = 0;
216			lrgln = 0;
217			ops = 0;
218			ips = 0;
219			cps = 0;
220
221			/*
222			 * loop by line
223			 */
224			while (linecnt < lines) {
225				/*
226				 * input next line
227				 */
228				if ((cnt = inln(inf,lbuf,LBUF,&cps,0,&mor)) < 0)
229					break;
230				if (!linecnt && !nohead &&
231					prhead(hbuf, fname, pagecnt))
232					return(1);
233
234				/*
235				 * start of new line.
236				 */
237				if (!lrgln) {
238					if (num)
239						addnum(nbuf, num, ++lncnt);
240					if (otln(obuf,cnt+off, &ips, &ops, mor))
241						return(1);
242				} else if (otln(lbuf, cnt, &ips, &ops, mor))
243					return(1);
244
245				/*
246				 * if line bigger than buffer, get more
247				 */
248				if (mor) {
249					lrgln = 1;
250					continue;
251				}
252
253				/*
254				 * whole line rcvd. reset tab proc. state
255				 */
256				++linecnt;
257				lrgln = 0;
258				ops = 0;
259				ips = 0;
260			}
261
262			/*
263			 * fill to end of page
264			 */
265			if (linecnt && prtail(lines-linecnt-lrgln, lrgln))
266				return(1);
267
268			/*
269			 * On EOF go to next file
270			 */
271			if (cnt < 0)
272				break;
273			++pagecnt;
274		}
275		if (inf != stdin)
276			(void)fclose(inf);
277	}
278	if (eoptind < argc)
279		return(1);
280	return(0);
281}
282
283/*
284 * vertcol:	print files with more than one column of output down a page
285 */
286int
287vertcol(argc, argv)
288        int argc;
289        char *argv[];
290{
291	register char *ptbf;
292	register char **lstdat;
293	register int i;
294	register int j;
295	register int cnt = -1;
296	register int pln;
297	register int *indy;
298	int cvc;
299	int *lindy;
300	int lncnt;
301	int stp;
302	int pagecnt;
303	int col = colwd + 1;
304	int mxlen = pgwd + offst + 1;
305	int mclcnt = clcnt - 1;
306	struct vcol *vc;
307	int mvc;
308	int tvc;
309	int cw = nmwd + 1;
310	int fullcol;
311	char *buf;
312	char *hbuf;
313	char *ohbuf;
314	char *fname;
315	FILE *inf;
316	int ips = 0;
317	int cps = 0;
318	int ops = 0;
319	int mor = 0;
320
321	/*
322	 * allocate page buffer
323	 */
324	if ((buf = malloc((unsigned)lines*mxlen*sizeof(char))) == NULL) {
325		mfail();
326		return(1);
327	}
328
329	/*
330	 * allocate page header
331	 */
332	if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
333		mfail();
334		return(1);
335	}
336	ohbuf = hbuf + offst;
337	if (offst)
338		(void)memset(hbuf, (int)' ', offst);
339
340	/*
341	 * col pointers when no headers
342	 */
343	mvc = lines * clcnt;
344	if ((vc =
345	    (struct vcol *)malloc((unsigned)mvc*sizeof(struct vcol))) == NULL) {
346		mfail();
347		return(1);
348	}
349
350	/*
351	 * pointer into page where last data per line is located
352	 */
353	if ((lstdat = (char **)malloc((unsigned)lines*sizeof(char *))) == NULL){
354		mfail();
355		return(1);
356	}
357
358	/*
359	 * fast index lookups to locate start of lines
360	 */
361	if ((indy = (int *)malloc((unsigned)lines*sizeof(int))) == NULL) {
362		mfail();
363		return(1);
364	}
365	if ((lindy = (int *)malloc((unsigned)lines*sizeof(int))) == NULL) {
366		mfail();
367		return(1);
368	}
369
370	if (nmwd)
371		fullcol = col + cw;
372	else
373		fullcol = col;
374
375	/*
376	 * initialize buffer lookup indexes and offset area
377	 */
378	for (j = 0; j < lines; ++j) {
379		lindy[j] = j * mxlen;
380		indy[j] = lindy[j] + offst;
381		if (offst) {
382			ptbf = buf + lindy[j];
383			(void)memset(ptbf, (int)' ', offst);
384			ptbf += offst;
385		} else
386			ptbf = buf + indy[j];
387		lstdat[j] = ptbf;
388	}
389
390	/*
391	 * loop by file
392	 */
393	while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
394		if (pgnm) {
395			/*
396			 * skip to requested page
397			 */
398			if (inskip(inf, pgnm, lines))
399				continue;
400			pagecnt = pgnm;
401		} else
402			pagecnt = 1;
403		lncnt = 0;
404
405		/*
406		 * loop by page
407		 */
408		for(;;) {
409			/*
410			 * loop by column
411			 */
412			cvc = 0;
413			for (i = 0; i < clcnt; ++i) {
414				j = 0;
415				/*
416				 * if last column, do not pad
417				 */
418				if (i == mclcnt)
419					stp = 1;
420				else
421					stp = 0;
422				/*
423				 * loop by line
424				 */
425				for(;;) {
426					/*
427					 * is this first column
428					 */
429					if (!i) {
430						ptbf = buf + indy[j];
431						lstdat[j] = ptbf;
432					} else
433						ptbf = lstdat[j];
434					vc[cvc].pt = ptbf;
435
436					/*
437					 * add number
438					 */
439					if (nmwd) {
440						addnum(ptbf, nmwd, ++lncnt);
441						ptbf += nmwd;
442						*ptbf++ = nmchar;
443					}
444
445					/*
446					 * input next line
447					 */
448					cnt = inln(inf,ptbf,colwd,&cps,1,&mor);
449					vc[cvc++].cnt = cnt;
450					if (cnt < 0)
451						break;
452					ptbf += cnt;
453
454					/*
455					 * pad all but last column on page
456					 */
457					if (!stp) {
458						/*
459						 * pad to end of column
460						 */
461						if (sflag)
462							*ptbf++ = schar;
463						else if ((pln = col-cnt) > 0) {
464							(void)memset(ptbf,
465								(int)' ',pln);
466							ptbf += pln;
467						}
468					}
469					/*
470					 * remember last char in line
471					 */
472					lstdat[j] = ptbf;
473					if (++j >= lines)
474						break;
475				}
476				if (cnt < 0)
477					break;
478			}
479
480			/*
481			 * when -t (no header) is specified the spec requires
482			 * the min number of lines. The last page may not have
483			 * balanced length columns. To fix this we must reorder
484			 * the columns. This is a very slow technique so it is
485			 * only used under limited conditions. Without -t, the
486			 * balancing of text columns is unspecified. To NOT
487			 * balance the last page, add the global variable
488			 * nohead to the if statement below e.g.
489			 *
490			 * if ((cnt < 0) && nohead && cvc ......
491			 */
492			--cvc;
493
494			/*
495			 * check to see if last page needs to be reordered
496			 */
497			if ((cnt < 0) && cvc && ((mvc-cvc) >= clcnt)){
498				pln = cvc/clcnt;
499				if (cvc % clcnt)
500					++pln;
501
502				/*
503				 * print header
504				 */
505				if (!nohead && prhead(hbuf, fname, pagecnt))
506					return(1);
507				for (i = 0; i < pln; ++i) {
508					ips = 0;
509					ops = 0;
510					if (offst&& otln(buf,offst,&ips,&ops,1))
511						return(1);
512					tvc = i;
513
514					for (j = 0; j < clcnt; ++j) {
515						/*
516						 * determine column length
517						 */
518						if (j == mclcnt) {
519							/*
520							 * last column
521							 */
522							cnt = vc[tvc].cnt;
523							if (nmwd)
524								cnt += cw;
525						} else if (sflag) {
526							/*
527							 * single ch between
528							 */
529							cnt = vc[tvc].cnt + 1;
530							if (nmwd)
531								cnt += cw;
532						} else
533							cnt = fullcol;
534						if (otln(vc[tvc].pt, cnt, &ips,
535								&ops, 1))
536							return(1);
537						tvc += pln;
538						if (tvc >= cvc)
539							break;
540					}
541					/*
542					 * terminate line
543					 */
544					if (otln(buf, 0, &ips, &ops, 0))
545						return(1);
546				}
547				/*
548				 * pad to end of page
549				 */
550				if (prtail((lines - pln), 0))
551					return(1);
552				/*
553				 * done with output, go to next file
554				 */
555				break;
556			}
557
558			/*
559			 * determine how many lines to output
560			 */
561			if (i > 0)
562				pln = lines;
563			else
564				pln = j;
565
566			/*
567			 * print header
568			 */
569			if (pln && !nohead && prhead(hbuf, fname, pagecnt))
570				return(1);
571
572			/*
573			 * output each line
574			 */
575			for (i = 0; i < pln; ++i) {
576				ptbf = buf + lindy[i];
577				if ((j = lstdat[i] - ptbf) <= offst)
578					break;
579				if (otln(ptbf, j, &ips, &ops, 0))
580					return(1);
581			}
582
583			/*
584			 * pad to end of page
585			 */
586			if (pln && prtail((lines - pln), 0))
587				return(1);
588
589			/*
590			 * if EOF go to next file
591			 */
592			if (cnt < 0)
593				break;
594			++pagecnt;
595		}
596		if (inf != stdin)
597			(void)fclose(inf);
598	}
599	if (eoptind < argc)
600		return(1);
601	return(0);
602}
603
604/*
605 * horzcol:	print files with more than one column of output across a page
606 */
607int
608horzcol(argc, argv)
609        int argc;
610        char *argv[];
611{
612	register char *ptbf;
613	register int pln;
614	register int cnt = -1;
615	register char *lstdat;
616	register int col = colwd + 1;
617	register int j;
618	register int i;
619	int lncnt;
620	int pagecnt;
621	char *buf;
622	char *hbuf;
623	char *ohbuf;
624	char *fname;
625	FILE *inf;
626	int ips = 0;
627	int cps = 0;
628	int ops = 0;
629	int mor = 0;
630
631	if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) {
632		mfail();
633		return(1);
634	}
635
636	/*
637	 * page header
638	 */
639	if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
640		mfail();
641		return(1);
642	}
643	ohbuf = hbuf + offst;
644	if (offst) {
645		(void)memset(buf, (int)' ', offst);
646		(void)memset(hbuf, (int)' ', offst);
647	}
648
649	/*
650	 * loop by file
651	 */
652	while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) {
653		if (pgnm) {
654			if (inskip(inf, pgnm, lines))
655				continue;
656			pagecnt = pgnm;
657		} else
658			pagecnt = 1;
659		lncnt = 0;
660
661		/*
662		 * loop by page
663		 */
664		for(;;) {
665			/*
666			 * loop by line
667			 */
668			for (i = 0; i < lines; ++i) {
669				ptbf = buf + offst;
670				lstdat = ptbf;
671				j = 0;
672				/*
673				 * loop by col
674				 */
675				for(;;) {
676					if (nmwd) {
677						/*
678						 * add number to column
679						 */
680						addnum(ptbf, nmwd, ++lncnt);
681						ptbf += nmwd;
682						*ptbf++ = nmchar;
683					}
684					/*
685					 * input line
686					 */
687					if ((cnt = inln(inf,ptbf,colwd,&cps,1,
688							&mor)) < 0)
689						break;
690					ptbf += cnt;
691					lstdat = ptbf;
692
693					/*
694					 * if last line skip padding
695					 */
696					if (++j >= clcnt)
697						break;
698
699					/*
700					 * pad to end of column
701					 */
702					if (sflag)
703						*ptbf++ = schar;
704					else if ((pln = col - cnt) > 0) {
705						(void)memset(ptbf,(int)' ',pln);
706						ptbf += pln;
707					}
708				}
709
710				/*
711				 * determine line length
712				 */
713				if ((j = lstdat - buf) <= offst)
714					break;
715				if (!i && !nohead &&
716					prhead(hbuf, fname, pagecnt))
717					return(1);
718				/*
719				 * output line
720				 */
721				if (otln(buf, j, &ips, &ops, 0))
722					return(1);
723			}
724
725			/*
726			 * pad to end of page
727			 */
728			if (i && prtail(lines-i, 0))
729				return(1);
730
731			/*
732			 * if EOF go to next file
733			 */
734			if (cnt < 0)
735				break;
736			++pagecnt;
737		}
738		if (inf != stdin)
739			(void)fclose(inf);
740	}
741	if (eoptind < argc)
742		return(1);
743	return(0);
744}
745
746/*
747 * mulfile:	print files with more than one column of output and
748 *		more than one file concurrently
749 */
750int
751mulfile(argc, argv)
752        int argc;
753        char *argv[];
754{
755	register char *ptbf;
756	register int j;
757	register int pln;
758	register int cnt;
759	register char *lstdat;
760	register int i;
761	FILE **fbuf;
762	int actf;
763	int lncnt;
764	int col;
765	int pagecnt;
766	int fproc;
767	char *buf;
768	char *hbuf;
769	char *ohbuf;
770	char *fname;
771	int ips = 0;
772	int cps = 0;
773	int ops = 0;
774	int mor = 0;
775
776	/*
777	 * array of FILE *, one for each operand
778	 */
779	if ((fbuf = (FILE **)malloc((unsigned)clcnt*sizeof(FILE *))) == NULL) {
780		mfail();
781		return(1);
782	}
783
784	/*
785	 * page header
786	 */
787	if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) {
788		mfail();
789		return(1);
790	}
791	ohbuf = hbuf + offst;
792
793	/*
794	 * do not know how many columns yet. The number of operands provide an
795	 * upper bound on the number of columns. We use the number of files
796	 * we can open successfully to set the number of columns. The operation
797	 * of the merge operation (-m) in relation to unsuccesful file opens
798	 * is unspecified by posix.
799	 */
800	j = 0;
801	while (j < clcnt) {
802		if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) == NULL)
803			break;
804		if (pgnm && (inskip(fbuf[j], pgnm, lines)))
805			fbuf[j] = NULL;
806		++j;
807	}
808
809	/*
810	 * if no files, exit
811	 */
812	if (!j)
813		return(1);
814
815	/*
816	 * calculate page boundries based on open file count
817	 */
818	clcnt = j;
819	if (nmwd) {
820		colwd = (pgwd - clcnt - nmwd)/clcnt;
821		pgwd = ((colwd + 1) * clcnt) - nmwd - 2;
822	} else {
823		colwd = (pgwd + 1 - clcnt)/clcnt;
824		pgwd = ((colwd + 1) * clcnt) - 1;
825	}
826	if (colwd < 1) {
827		(void)fprintf(err,
828		  "pr: page width too small for %d columns\n", clcnt);
829		return(1);
830	}
831	actf = clcnt;
832	col = colwd + 1;
833
834	/*
835	 * line buffer
836	 */
837	if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) {
838		mfail();
839		return(1);
840	}
841	if (offst) {
842		(void)memset(buf, (int)' ', offst);
843		(void)memset(hbuf, (int)' ', offst);
844	}
845	if (pgnm)
846		pagecnt = pgnm;
847	else
848		pagecnt = 1;
849	lncnt = 0;
850
851	/*
852	 * continue to loop while any file still has data
853	 */
854	while (actf > 0) {
855		/*
856		 * loop by line
857		 */
858		for (i = 0; i < lines; ++i) {
859			ptbf = buf + offst;
860			lstdat = ptbf;
861			if (nmwd) {
862				/*
863				 * add line number to line
864				 */
865				addnum(ptbf, nmwd, ++lncnt);
866				ptbf += nmwd;
867				*ptbf++ = nmchar;
868			}
869			j = 0;
870			fproc = 0;
871
872			/*
873			 * loop by column
874			 */
875			for (j = 0; j < clcnt; ++j) {
876				if (fbuf[j] == NULL) {
877					/*
878					 * empty column; EOF
879					 */
880					cnt = 0;
881				} else if ((cnt = inln(fbuf[j], ptbf, colwd,
882							&cps, 1, &mor)) < 0) {
883					/*
884					 * EOF hit; no data
885					 */
886					if (fbuf[j] != stdin)
887						(void)fclose(fbuf[j]);
888					fbuf[j] = NULL;
889					--actf;
890					cnt = 0;
891				} else {
892					/*
893					 * process file data
894					 */
895					ptbf += cnt;
896					lstdat = ptbf;
897					fproc++;
898				}
899
900				/*
901				 * if last ACTIVE column, done with line
902				 */
903				if (fproc >= actf)
904					break;
905
906				/*
907				 * pad to end of column
908				 */
909				if (sflag) {
910					*ptbf++ = schar;
911				} else if ((pln = col - cnt) > 0) {
912					(void)memset(ptbf, (int)' ', pln);
913					ptbf += pln;
914				}
915			}
916
917			/*
918			 * calculate data in line
919			 */
920			if ((j = lstdat - buf) <= offst)
921				break;
922
923			if (!i && !nohead && prhead(hbuf, fname, pagecnt))
924				return(1);
925
926			/*
927			 * output line
928			 */
929			if (otln(buf, j, &ips, &ops, 0))
930				return(1);
931
932			/*
933			 * if no more active files, done
934			 */
935			if (actf <= 0) {
936				++i;
937				break;
938			}
939		}
940
941		/*
942		 * pad to end of page
943		 */
944		if (i && prtail(lines-i, 0))
945			return(1);
946		++pagecnt;
947	}
948	if (eoptind < argc)
949		return(1);
950	return(0);
951}
952
953/*
954 * inln():	input a line of data (unlimited length lines supported)
955 *		Input is optionally expanded to spaces
956 *
957 *	inf:	file
958 *	buf:	buffer
959 *	lim:	buffer length
960 *	cps:	column positon 1st char in buffer (large line support)
961 *	trnc:	throw away data more than lim up to \n
962 *	mor:	set if more data in line (not truncated)
963 */
964int
965inln(inf, buf, lim, cps, trnc, mor)
966	FILE *inf;
967	char *buf;
968	register int lim;
969	int *cps;
970	int trnc;
971	int *mor;
972{
973	register int col;
974	register int gap = ingap;
975	register int ch = EOF;
976	register char *ptbuf;
977	register int chk = (int)inchar;
978
979	ptbuf = buf;
980
981	if (gap) {
982		/*
983		 * expanding input option
984		 */
985		while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
986			/*
987			 * is this the input "tab" char
988			 */
989			if (ch == chk) {
990				/*
991				 * expand to number of spaces
992				 */
993				col = (ptbuf - buf) + *cps;
994				col = gap - (col % gap);
995
996				/*
997				 * if more than this line, push back
998				 */
999				if ((col > lim) && (ungetc(ch, inf) == EOF))
1000					return(1);
1001
1002				/*
1003				 * expand to spaces
1004				 */
1005				while ((--col >= 0) && (--lim >= 0))
1006					*ptbuf++ = ' ';
1007				continue;
1008			}
1009			if (ch == '\n')
1010				break;
1011			*ptbuf++ = ch;
1012		}
1013	} else {
1014		/*
1015		 * no expansion
1016		 */
1017		while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) {
1018			if (ch == '\n')
1019				break;
1020			*ptbuf++ = ch;
1021		}
1022	}
1023	col = ptbuf - buf;
1024	if (ch == EOF) {
1025		*mor = 0;
1026		*cps = 0;
1027		if (!col)
1028			return(-1);
1029		return(col);
1030	}
1031	if (ch == '\n') {
1032		/*
1033		 * entire line processed
1034		 */
1035		*mor = 0;
1036		*cps = 0;
1037		return(col);
1038	}
1039
1040	/*
1041	 * line was larger than limit
1042	 */
1043	if (trnc) {
1044		/*
1045		 * throw away rest of line
1046		 */
1047		while ((ch = getc(inf)) != EOF) {
1048			if (ch == '\n')
1049				break;
1050		}
1051		*cps = 0;
1052		*mor = 0;
1053	} else {
1054		/*
1055		 * save column offset if not truncated
1056		 */
1057		*cps += col;
1058		*mor = 1;
1059	}
1060
1061	return(col);
1062}
1063
1064/*
1065 * otln():	output a line of data. (Supports unlimited length lines)
1066 *		output is optionally contracted to tabs
1067 *
1068 *	buf:	output buffer with data
1069 *	cnt:	number of chars of valid data in buf
1070 *	svips:	buffer input column position (for large lines)
1071 *	svops:	buffer output column position (for large lines)
1072 *	mor:	output line not complete in this buf; more data to come.
1073 *		1 is more, 0 is complete, -1 is no \n's
1074 */
1075int
1076otln(buf, cnt, svips, svops, mor)
1077	register char *buf;
1078	int cnt;
1079	int *svops;
1080	int *svips;
1081	int mor;
1082{
1083	register int ops;		/* last col output */
1084	register int ips;		/* last col in buf examined */
1085	register int gap = ogap;
1086	register int tbps;
1087	register char *endbuf;
1088
1089	if (ogap) {
1090		/*
1091		 * contracting on output
1092		 */
1093		endbuf = buf + cnt;
1094		ops = *svops;
1095		ips = *svips;
1096		while (buf < endbuf) {
1097			/*
1098			 * count number of spaces and ochar in buffer
1099			 */
1100			if (*buf == ' ') {
1101				++ips;
1102				++buf;
1103				continue;
1104			}
1105
1106			/*
1107			 * simulate ochar processing
1108			 */
1109			if (*buf == ochar) {
1110				ips += gap - (ips % gap);
1111				++buf;
1112				continue;
1113			}
1114
1115			/*
1116			 * got a non space char; contract out spaces
1117			 */
1118			while (ops < ips) {
1119				/*
1120				 * use as many ochar as will fit
1121				 */
1122				if ((tbps = ops + gap - (ops % gap)) > ips)
1123					break;
1124				if (putchar(ochar) == EOF) {
1125					pfail();
1126					return(1);
1127				}
1128				ops = tbps;
1129			}
1130
1131			while (ops < ips) {
1132				/*
1133				 * finish off with spaces
1134				 */
1135				if (putchar(' ') == EOF) {
1136					pfail();
1137					return(1);
1138				}
1139				++ops;
1140			}
1141
1142			/*
1143			 * output non space char
1144			 */
1145			if (putchar(*buf++) == EOF) {
1146				pfail();
1147				return(1);
1148			}
1149			++ips;
1150			++ops;
1151		}
1152
1153		if (mor > 0) {
1154			/*
1155			 * if incomplete line, save position counts
1156			 */
1157			*svops = ops;
1158			*svips = ips;
1159			return(0);
1160		}
1161
1162		if (mor < 0) {
1163			while (ops < ips) {
1164				/*
1165				 * use as many ochar as will fit
1166				 */
1167				if ((tbps = ops + gap - (ops % gap)) > ips)
1168					break;
1169				if (putchar(ochar) == EOF) {
1170					pfail();
1171					return(1);
1172				}
1173				ops = tbps;
1174			}
1175			while (ops < ips) {
1176				/*
1177				 * finish off with spaces
1178				 */
1179				if (putchar(' ') == EOF) {
1180					pfail();
1181					return(1);
1182				}
1183				++ops;
1184			}
1185			return(0);
1186		}
1187	} else {
1188		/*
1189		 * output is not contracted
1190		 */
1191		if (cnt && (fwrite(buf, sizeof(char), cnt, stdout) <= 0)) {
1192			pfail();
1193			return(1);
1194		}
1195		if (mor != 0)
1196			return(0);
1197	}
1198
1199	/*
1200	 * process line end and double space as required
1201	 */
1202	if ((putchar('\n') == EOF) || (dspace && (putchar('\n') == EOF))) {
1203		pfail();
1204		return(1);
1205	}
1206	return(0);
1207}
1208
1209/*
1210 * inskip():	skip over pgcnt pages with lncnt lines per page
1211 *		file is closed at EOF (if not stdin).
1212 *
1213 *	inf	FILE * to read from
1214 *	pgcnt	number of pages to skip
1215 *	lncnt	number of lines per page
1216 */
1217int
1218inskip(inf, pgcnt, lncnt)
1219	FILE *inf;
1220	register int pgcnt;
1221	register int lncnt;
1222{
1223	register int c;
1224	register int cnt;
1225
1226	while(--pgcnt > 0) {
1227		cnt = lncnt;
1228		while ((c = getc(inf)) != EOF) {
1229			if ((c == '\n') && (--cnt == 0))
1230				break;
1231		}
1232		if (c == EOF) {
1233			if (inf != stdin)
1234				(void)fclose(inf);
1235			return(1);
1236		}
1237	}
1238	return(0);
1239}
1240
1241/*
1242 * nxtfile:	returns a FILE * to next file in arg list and sets the
1243 *		time field for this file (or current date).
1244 *
1245 *	buf	array to store proper date for the header.
1246 *	dt	if set skips the date processing (used with -m)
1247 */
1248FILE *
1249nxtfile(argc, argv, fname, buf, dt)
1250	int argc;
1251	char **argv;
1252	char **fname;
1253	char *buf;
1254	int dt;
1255{
1256	FILE *inf = NULL;
1257	struct timeval tv;
1258	struct timezone tz;
1259	struct tm *timeptr = NULL;
1260	struct stat statbuf;
1261	static int twice = -1;
1262
1263	++twice;
1264	if (eoptind >= argc) {
1265		/*
1266		 * no file listed; default, use standard input
1267		 */
1268		if (twice)
1269			return(NULL);
1270		clearerr(stdin);
1271		inf = stdin;
1272		if (header != NULL)
1273			*fname = header;
1274		else
1275			*fname = FNAME;
1276		if (nohead)
1277			return(inf);
1278		if (gettimeofday(&tv, &tz) < 0) {
1279			++errcnt;
1280			(void)fprintf(err, "pr: cannot get time of day, %s\n",
1281				strerror(errno));
1282			eoptind = argc - 1;
1283			return(NULL);
1284		}
1285		timeptr = localtime(&(tv.tv_sec));
1286	}
1287	for (; eoptind < argc; ++eoptind) {
1288		if (strcmp(argv[eoptind], "-") == 0) {
1289			/*
1290			 * process a "-" for filename
1291			 */
1292			clearerr(stdin);
1293			inf = stdin;
1294			if (header != NULL)
1295				*fname = header;
1296			else
1297				*fname = FNAME;
1298			++eoptind;
1299			if (nohead || (dt && twice))
1300				return(inf);
1301			if (gettimeofday(&tv, &tz) < 0) {
1302				++errcnt;
1303				(void)fprintf(err,
1304					"pr: cannot get time of day, %s\n",
1305					strerror(errno));
1306				return(NULL);
1307			}
1308			timeptr = localtime(&(tv.tv_sec));
1309		} else {
1310			/*
1311			 * normal file processing
1312			 */
1313			if ((inf = fopen(argv[eoptind], "r")) == NULL) {
1314				++errcnt;
1315				if (nodiag)
1316					continue;
1317				(void)fprintf(err, "pr: Cannot open %s, %s\n",
1318					argv[eoptind], strerror(errno));
1319				continue;
1320			}
1321			if (header != NULL)
1322				*fname = header;
1323			else if (dt)
1324				*fname = FNAME;
1325			else
1326				*fname = argv[eoptind];
1327			++eoptind;
1328			if (nohead || (dt && twice))
1329				return(inf);
1330
1331			if (dt) {
1332				if (gettimeofday(&tv, &tz) < 0) {
1333					++errcnt;
1334					(void)fprintf(err,
1335					     "pr: cannot get time of day, %s\n",
1336					     strerror(errno));
1337					return(NULL);
1338				}
1339				timeptr = localtime(&(tv.tv_sec));
1340			} else {
1341				if (fstat(fileno(inf), &statbuf) < 0) {
1342					++errcnt;
1343					(void)fclose(inf);
1344					(void)fprintf(err,
1345						"pr: Cannot stat %s, %s\n",
1346						argv[eoptind], strerror(errno));
1347					return(NULL);
1348				}
1349				timeptr = localtime(&(statbuf.st_mtime));
1350			}
1351		}
1352		break;
1353	}
1354	if (inf == NULL)
1355		return(NULL);
1356
1357	/*
1358	 * set up time field used in header
1359	 */
1360	if (strftime(buf, HDBUF, timefrmt, timeptr) <= 0) {
1361		++errcnt;
1362		if (inf != stdin)
1363			(void)fclose(inf);
1364		(void)fputs("pr: time conversion failed\n", err);
1365		return(NULL);
1366	}
1367	return(inf);
1368}
1369
1370/*
1371 * addnum():	adds the line number to the column
1372 *		Truncates from the front or pads with spaces as required.
1373 *		Numbers are right justified.
1374 *
1375 *	buf	buffer to store the number
1376 *	wdth	width of buffer to fill
1377 *	line	line number
1378 *
1379 *		NOTE: numbers occupy part of the column. The posix
1380 *		spec does not specify if -i processing should or should not
1381 *		occur on number padding. The spec does say it occupies
1382 *		part of the column. The usage of addnum	currently treats
1383 *		numbers as part of the column so spaces may be replaced.
1384 */
1385void
1386addnum(buf, wdth, line)
1387	register char *buf;
1388	register int wdth;
1389	register int line;
1390{
1391	register char *pt = buf + wdth;
1392
1393	do {
1394		*--pt = digs[line % 10];
1395		line /= 10;
1396	} while (line && (pt > buf));
1397
1398	/*
1399	 * pad with space as required
1400	 */
1401	while (pt > buf)
1402		*--pt = ' ';
1403}
1404
1405/*
1406 * prhead():	prints the top of page header
1407 *
1408 *	buf	buffer with time field (and offset)
1409 *	cnt	number of chars in buf
1410 *	fname	fname field for header
1411 *	pagcnt	page number
1412 */
1413int
1414prhead(buf, fname, pagcnt)
1415	char *buf;
1416	char *fname;
1417	int pagcnt;
1418{
1419	int ips = 0;
1420	int ops = 0;
1421
1422	if ((putchar('\n') == EOF) || (putchar('\n') == EOF)) {
1423		pfail();
1424		return(1);
1425	}
1426	/*
1427	 * posix is not clear if the header is subject to line length
1428	 * restrictions. The specification for header line format
1429	 * in the spec clearly does not limit length. No pr currently
1430	 * restricts header length. However if we need to truncate in
1431	 * an reasonable way, adjust the length of the printf by
1432	 * changing HDFMT to allow a length max as an arguement printf.
1433	 * buf (which contains the offset spaces and time field could
1434	 * also be trimmed
1435	 *
1436	 * note only the offset (if any) is processed for tab expansion
1437	 */
1438	if (offst && otln(buf, offst, &ips, &ops, -1))
1439		return(1);
1440	(void)printf(HDFMT,buf+offst, fname, pagcnt);
1441	return(0);
1442}
1443
1444/*
1445 * prtail():	pad page with empty lines (if required) and print page trailer
1446 *		if requested
1447 *
1448 *	cnt	number of lines of padding needed
1449 *	incomp	was a '\n' missing from last line output
1450 */
1451int
1452prtail(cnt, incomp)
1453	register int cnt;
1454	int incomp;
1455{
1456	if (nohead) {
1457		/*
1458		 * only pad with no headers when incomplete last line
1459		 */
1460		if (!incomp)
1461			return(0);
1462		if ((dspace && (putchar('\n') == EOF)) ||
1463		    (putchar('\n') == EOF)) {
1464			pfail();
1465			return(1);
1466		}
1467		return(0);
1468	}
1469
1470	/*
1471	 * if double space output two \n
1472	 */
1473	if (dspace)
1474		cnt *= 2;
1475
1476	/*
1477	 * if an odd number of lines per page, add an extra \n
1478	 */
1479	if (addone)
1480		++cnt;
1481
1482	/*
1483	 * pad page
1484	 */
1485	if (formfeed) {
1486		if ((incomp && (putchar('\n') == EOF)) ||
1487		    (putchar('\f') == EOF)) {
1488			pfail();
1489			return(1);
1490		}
1491		return(0);
1492	}
1493	cnt += TAILLEN;
1494	while (--cnt >= 0) {
1495		if (putchar('\n') == EOF) {
1496			pfail();
1497			return(1);
1498		}
1499	}
1500	return(0);
1501}
1502
1503/*
1504 * terminate():	when a SIGINT is recvd
1505 */
1506void
1507terminate(which_sig)
1508	int which_sig;
1509{
1510	flsh_errs();
1511	exit(1);
1512}
1513
1514
1515/*
1516 * flsh_errs():	output saved up diagnostic messages after all normal
1517 *		processing has completed
1518 */
1519void
1520flsh_errs()
1521{
1522	char buf[BUFSIZ];
1523
1524	(void)fflush(stdout);
1525	(void)fflush(err);
1526	if (err == stderr)
1527		return;
1528	rewind(err);
1529	while (fgets(buf, BUFSIZ, err) != NULL)
1530		(void)fputs(buf, stderr);
1531}
1532
1533void
1534mfail()
1535{
1536	(void)fputs("pr: memory allocation failed\n", err);
1537}
1538
1539void
1540pfail()
1541{
1542	(void)fprintf(err, "pr: write failure, %s\n", strerror(errno));
1543}
1544
1545void
1546usage()
1547{
1548	(void)fputs(
1549	 "usage: pr [+page] [-col] [-adFmrt] [-e[ch][gap]] [-h header]\n",err);
1550	(void)fputs(
1551	 "          [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n",err);
1552	(void)fputs(
1553	 "          [-s[ch]] [-w width] [-] [file ...]\n", err);
1554}
1555
1556/*
1557 * setup:	Validate command args, initialize and perform sanity
1558 *		checks on options
1559 */
1560int
1561setup(argc, argv)
1562	register int argc;
1563	register char **argv;
1564{
1565	register int c;
1566	int eflag = 0;
1567	int iflag = 0;
1568	int wflag = 0;
1569	int cflag = 0;
1570
1571	if (isatty(fileno(stdout))) {
1572		/*
1573		 * defer diagnostics until processing is done
1574		 */
1575		if ((err = tmpfile()) == NULL) {
1576		       (void)fputs("Cannot defer diagnostic messages\n",stderr);
1577		       return(1);
1578		}
1579	} else
1580		err = stderr;
1581	while ((c = egetopt(argc, argv, "#adFmrte?h:i?l:n?o:s?w:")) != EOF) {
1582		switch (c) {
1583		case '+':
1584			if ((pgnm = atoi(eoptarg)) < 1) {
1585			    (void)fputs("pr: +page number must be 1 or more\n",
1586				err);
1587			    return(1);
1588			}
1589			break;
1590		case '-':
1591			if ((clcnt = atoi(eoptarg)) < 1) {
1592			    (void)fputs("pr: -columns must be 1 or more\n",err);
1593			    return(1);
1594			}
1595			if (clcnt > 1)
1596				++cflag;
1597			break;
1598		case 'a':
1599			++across;
1600			break;
1601		case 'd':
1602			++dspace;
1603			break;
1604		case 'e':
1605			++eflag;
1606			if ((eoptarg != NULL) && !isdigit(*eoptarg))
1607				inchar = *eoptarg++;
1608			else
1609				inchar = INCHAR;
1610			if ((eoptarg != NULL) && isdigit(*eoptarg)) {
1611				if ((ingap = atoi(eoptarg)) < 0) {
1612					(void)fputs(
1613					"pr: -e gap must be 0 or more\n", err);
1614					return(1);
1615				}
1616				if (ingap == 0)
1617					ingap = INGAP;
1618			} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
1619				(void)fprintf(err,
1620				      "pr: invalid value for -e %s\n", eoptarg);
1621				return(1);
1622			} else
1623				ingap = INGAP;
1624			break;
1625		case 'F':
1626			++formfeed;
1627			break;
1628		case 'h':
1629			header = eoptarg;
1630			break;
1631		case 'i':
1632			++iflag;
1633			if ((eoptarg != NULL) && !isdigit(*eoptarg))
1634				ochar = *eoptarg++;
1635			else
1636				ochar = OCHAR;
1637			if ((eoptarg != NULL) && isdigit(*eoptarg)) {
1638				if ((ogap = atoi(eoptarg)) < 0) {
1639					(void)fputs(
1640					"pr: -i gap must be 0 or more\n", err);
1641					return(1);
1642				}
1643				if (ogap == 0)
1644					ogap = OGAP;
1645			} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
1646				(void)fprintf(err,
1647				      "pr: invalid value for -i %s\n", eoptarg);
1648				return(1);
1649			} else
1650				ogap = OGAP;
1651			break;
1652		case 'l':
1653			if (!isdigit(*eoptarg) || ((lines=atoi(eoptarg)) < 1)) {
1654				(void)fputs(
1655				 "pr: Number of lines must be 1 or more\n",err);
1656				return(1);
1657			}
1658			break;
1659		case 'm':
1660			++merge;
1661			break;
1662		case 'n':
1663			if ((eoptarg != NULL) && !isdigit(*eoptarg))
1664				nmchar = *eoptarg++;
1665			else
1666				nmchar = NMCHAR;
1667			if ((eoptarg != NULL) && isdigit(*eoptarg)) {
1668				if ((nmwd = atoi(eoptarg)) < 1) {
1669					(void)fputs(
1670					"pr: -n width must be 1 or more\n",err);
1671					return(1);
1672				}
1673			} else if ((eoptarg != NULL) && (*eoptarg != '\0')) {
1674				(void)fprintf(err,
1675				      "pr: invalid value for -n %s\n", eoptarg);
1676				return(1);
1677			} else
1678				nmwd = NMWD;
1679			break;
1680		case 'o':
1681			if (!isdigit(*eoptarg) || ((offst = atoi(eoptarg))< 1)){
1682				(void)fputs("pr: -o offset must be 1 or more\n",
1683					err);
1684				return(1);
1685			}
1686			break;
1687		case 'r':
1688			++nodiag;
1689			break;
1690		case 's':
1691			++sflag;
1692			if (eoptarg == NULL)
1693				schar = SCHAR;
1694			else
1695				schar = *eoptarg++;
1696			if (*eoptarg != '\0') {
1697				(void)fprintf(err,
1698				      "pr: invalid value for -s %s\n", eoptarg);
1699				return(1);
1700			}
1701			break;
1702		case 't':
1703			++nohead;
1704			break;
1705		case 'w':
1706			++wflag;
1707			if (!isdigit(*eoptarg) || ((pgwd = atoi(eoptarg)) < 1)){
1708				(void)fputs(
1709				   "pr: -w width must be 1 or more \n",err);
1710				return(1);
1711			}
1712			break;
1713		case '?':
1714		default:
1715			return(1);
1716		}
1717	}
1718
1719	/*
1720	 * default and sanity checks
1721	 */
1722	if (!clcnt) {
1723		if (merge) {
1724			if ((clcnt = argc - eoptind) <= 1) {
1725				clcnt = CLCNT;
1726				merge = 0;
1727			}
1728		} else
1729			clcnt = CLCNT;
1730	}
1731	if (across) {
1732		if (clcnt == 1) {
1733			(void)fputs("pr: -a flag requires multiple columns\n",
1734				err);
1735			return(1);
1736		}
1737		if (merge) {
1738			(void)fputs("pr: -m cannot be used with -a\n", err);
1739			return(1);
1740		}
1741	}
1742	if (!wflag) {
1743		if (sflag)
1744			pgwd = SPGWD;
1745		else
1746			pgwd = PGWD;
1747	}
1748	if (cflag || merge) {
1749		if (!eflag) {
1750			inchar = INCHAR;
1751			ingap = INGAP;
1752		}
1753		if (!iflag) {
1754			ochar = OCHAR;
1755			ogap = OGAP;
1756		}
1757	}
1758	if (cflag) {
1759		if (merge) {
1760			(void)fputs(
1761			  "pr: -m cannot be used with multiple columns\n", err);
1762			return(1);
1763		}
1764		if (nmwd) {
1765			colwd = (pgwd + 1 - (clcnt * (nmwd + 2)))/clcnt;
1766			pgwd = ((colwd + nmwd + 2) * clcnt) - 1;
1767		} else {
1768			colwd = (pgwd + 1 - clcnt)/clcnt;
1769			pgwd = ((colwd + 1) * clcnt) - 1;
1770		}
1771		if (colwd < 1) {
1772			(void)fprintf(err,
1773			  "pr: page width is too small for %d columns\n",clcnt);
1774			return(1);
1775		}
1776	}
1777	if (!lines)
1778		lines = LINES;
1779
1780	/*
1781	 * make sure long enough for headers. if not disable
1782	 */
1783	if (lines <= HEADLEN + TAILLEN)
1784		++nohead;
1785	else if (!nohead)
1786		lines -= HEADLEN + TAILLEN;
1787
1788	/*
1789	 * adjust for double space on odd length pages
1790	 */
1791	if (dspace) {
1792		if (lines == 1)
1793			dspace = 0;
1794		else {
1795			if (lines & 1)
1796				++addone;
1797			lines /= 2;
1798		}
1799	}
1800
1801	if ((timefrmt = getenv("LC_TIME")) == NULL)
1802		timefrmt = TIMEFMT;
1803	return(0);
1804}
1805