1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 *	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
29 *	All Rights Reserved
30 */
31
32/*
33 * University Copyright- Copyright (c) 1982, 1986, 1988
34 * The Regents of the University of California
35 * All Rights Reserved
36 *
37 * University Acknowledgment- Portions of this document are derived from
38 * software developed by the University of California, Berkeley, and its
39 * contributors.
40 */
41
42/*
43 *	PR command (print files in pages and columns, with headings)
44 *	2+head+2+page[56]+5
45 */
46
47#include <stdio.h>
48#include <signal.h>
49#include <ctype.h>
50#include <sys/types.h>
51#include <sys/stat.h>
52#include <unistd.h>
53#include <stdlib.h>
54#include <locale.h>
55#include <string.h>
56#include <limits.h>
57#include <wchar.h>
58#include <errno.h>
59
60#define	ESC		'\033'
61#define	LENGTH		66
62#define	LINEW		72
63#define	NUMW		5
64#define	MARGIN		10
65#define	DEFTAB		8
66#define	NFILES		10
67#define	STDINNAME()	nulls
68#define	PROMPT()	(void) putc('\7', stderr) /* BEL */
69#define	NOFILE		nulls
70#define	ETABS		(Inpos % Etabn)
71#define	NSEPC		'\t'
72#define	HEAD		gettext("%s  %s Page %d\n\n\n"), date, head, Page
73#define	cerror(S)	(void) fprintf(stderr, "pr: %s", gettext(S))
74#define	done()		if (Ttyout) (void) chmod(Ttyout, Mode)
75#define	ALL_NUMS(s)	(strspn(s, "0123456789") == strlen(s))
76#define	REMOVE_ARG(argc, argp)					\
77			{					\
78				char	**p = argp;		\
79				while (*p != NULL)		\
80				{				\
81					*p = *(p + 1);		\
82					p++;			\
83				}				\
84				argc--;				\
85			}
86#define	SQUEEZE_ARG(argp, ind, n)					\
87			{					\
88				int	i;			\
89				for (i = ind; argp[i]; i++)	\
90					argp[i] = argp[i + n];	\
91			}
92
93/*
94 *   ---date time format---
95 *   b -- abbreviated month name
96 *   e -- day of month
97 *   H -- Hour (24 hour version)
98 *   M -- Minute
99 *   Y -- Year in the form ccyy
100 */
101#define	FORMAT		"%b %e %H:%M %Y"
102
103typedef	int	ANY;
104typedef	unsigned	int	UNS;
105typedef	struct	{ FILE *f_f; char *f_name; wchar_t f_nextc; } FILS;
106typedef	struct	{int fold; int skip; int eof; } foldinf;
107typedef	struct	{ wchar_t *c_ptr, *c_ptr0; long c_lno; int c_skip; } *COLP;
108typedef struct	err { struct err *e_nextp; char *e_mess; } ERR;
109
110/*
111 * Global data.
112 */
113static	FILS	*Files;
114static	mode_t	Mode;
115static	int	Multi = 0;
116static	int	Nfiles = 0;
117static	int	Error = 0;
118static	char	nulls[] = "";
119static	char	*Ttyout;
120static	char	obuf[BUFSIZ];
121static	char	time_buf[50];	/* array to hold the time and date */
122static	long	Lnumb = 0;
123static	FILE	*Ttyin = stdin;
124static	int	Dblspace = 1;
125static	int	Fpage = 1;
126static	int	Formfeed = 0;
127static	int	Length = LENGTH;
128static	int	Linew = 0;
129static	int	Offset = 0;
130static	int	Ncols = 0;
131static	int	Pause = 0;
132static	wchar_t	Sepc = 0;
133static	int	Colw;
134static	int	Plength;
135static	int	Margin = MARGIN;
136static	int	Numw;
137static	int	Nsepc = NSEPC;
138static	int	Report = 1;
139static	int	Etabn = 0;
140static	wchar_t	Etabc = '\t';
141static	int	Itabn = 0;
142static	wchar_t	Itabc = '\t';
143static	int	fold = 0;
144static	int	foldcol = 0;
145static	int	alleof = 0;
146static	char	*Head = NULL;
147static	wchar_t *Buffer = NULL, *Bufend, *Bufptr;
148static	UNS	Buflen;
149static	COLP	Colpts;
150static	foldinf	*Fcol;
151static	int	Page;
152static	wchar_t	C = '\0';
153static	int	Nspace;
154static	int	Inpos;
155static	int	Outpos;
156static	int	Lcolpos;
157static	int	Pcolpos;
158static	int	Line;
159static	ERR	*Err = NULL;
160static	ERR	*Lasterr = (ERR *)&Err;
161static	int	mbcurmax = 1;
162
163/*
164 * Function prototypes.
165 */
166static	void	onintr();
167static	ANY	*getspace();
168static	int	findopt(int, char **);
169static	void	fixtty();
170static	char 	*GETDATE();
171static	char	*ffiler(char *);
172static	int	print(char *);
173static	void	putpage();
174static	void	foldpage();
175static	void	nexbuf();
176static	void	foldbuf();
177static	void	balance(int);
178static	int	readbuf(wchar_t **, int, COLP);
179static	wint_t	get(int);
180static	int	put(wchar_t);
181static	void	putspace();
182static	void	unget(int);
183static	FILE	*mustopen(char *, FILS *);
184static	void	die(char *);
185static	void	errprint();
186static	void	usage(int);
187static wint_t	_fgetwc_pr(FILE *, int *);
188static size_t	freadw(wchar_t *, size_t, FILE *);
189
190
191int
192main(int argc, char **argv)
193{
194	FILS	fstr[NFILES];
195	int	nfdone = 0;
196
197
198	/* Get locale variables for environment */
199	(void) setlocale(LC_ALL, "");
200
201#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
202#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
203#endif
204	(void) textdomain(TEXT_DOMAIN);
205
206	mbcurmax = MB_CUR_MAX;
207	Files = fstr;
208	for (argc = findopt(argc, argv); argc > 0; --argc, ++argv) {
209		if (Multi == 'm') {
210			if (Nfiles >= NFILES - 1) die("too many files");
211			if (mustopen(*argv, &Files[Nfiles++]) == NULL)
212				++nfdone;	/* suppress printing */
213		} else {
214			if (print(*argv))
215				(void) fclose(Files->f_f);
216			++nfdone;
217		}
218	}
219	if (!nfdone)	/* no files named, use stdin */
220		(void) print(NOFILE);	/* on GCOS, use current file, if any */
221
222	if (Report) {
223		errprint();	/* print accumulated error reports */
224		exit(Error);
225	}
226
227	return (Error);
228}
229
230
231/*
232 * findopt() returns argc modified to be the number of explicitly supplied
233 * filenames, including '-', the explicit request to use stdin.
234 * argc == 0 implies that no filenames were supplied and stdin should be used.
235 * Options are striped from argv and only file names are returned.
236 */
237
238static	int
239findopt(int argc, char **argv)
240{
241	int	eargc = 0;
242	int	c;
243	int	mflg = 0;
244	int	aflg = 0;
245	int	optnum;
246	int	argv_ind;
247	int	end_opt;
248	int	i;
249	int	isarg = 0;
250
251	fixtty();
252
253	/* Handle page number option */
254	for (optnum = 1, end_opt = 0; optnum < argc && !end_opt; optnum++) {
255		switch (*argv[optnum]) {
256		case '+':
257			/* check for all digits */
258			if (strlen(&argv[optnum][1]) !=
259			    strspn(&argv[optnum][1], "0123456789")) {
260				(void) fprintf(stderr, gettext(
261				    "pr: Badly formed number\n"));
262				exit(1);
263			}
264
265			if ((Fpage = (int)strtol(&argv[optnum][1],
266			    (char **)NULL, 10)) < 0) {
267				(void) fprintf(stderr, gettext(
268				    "pr: Badly formed number\n"));
269				exit(1);
270			}
271			REMOVE_ARG(argc, &argv[optnum]);
272			optnum--;
273			break;
274
275		case '-':
276			/* Check for end of options */
277			if (argv[optnum][1] == '-') {
278				end_opt++;
279				break;
280			}
281
282			if (argv[optnum][1] == 'h' || argv[optnum][1] == 'l' ||
283			    argv[optnum][1] == 'o' || argv[optnum][1] == 'w')
284				isarg = 1;
285			else
286				isarg = 0;
287
288			break;
289
290		default:
291			if (isarg == 0)
292				end_opt++;
293			else
294				isarg = 0;
295			break;
296		}
297	}
298
299	/*
300	 * Handle options with optional arguments.
301	 * If optional arguments are present they may not be separated
302	 * from the option letter.
303	 */
304
305	for (optnum = 1; optnum < argc; optnum++) {
306		if (argv[optnum][0] == '-' && argv[optnum][1] == '-')
307			/* End of options */
308			break;
309
310		if (argv[optnum][0] == '-' && argv[optnum][1] == '\0')
311			/* stdin file name */
312			continue;
313
314		if (argv[optnum][0] != '-')
315			/* not option */
316			continue;
317
318		for (argv_ind = 1; argv[optnum][argv_ind] != '\0'; argv_ind++) {
319			switch (argv[optnum][argv_ind]) {
320			case 'e':
321				SQUEEZE_ARG(argv[optnum], argv_ind, 1);
322				if ((c = argv[optnum][argv_ind]) != '\0' &&
323				    !isdigit(c)) {
324					int	r;
325					wchar_t	wc;
326					r = mbtowc(&wc, &argv[optnum][argv_ind],
327						mbcurmax);
328					if (r == -1) {
329						(void) fprintf(stderr, gettext(
330"pr: Illegal character in -e option\n"));
331						exit(1);
332					}
333					Etabc = wc;
334					SQUEEZE_ARG(argv[optnum], argv_ind, r);
335				}
336				if (isdigit(argv[optnum][argv_ind])) {
337					Etabn = (int)strtol(&argv[optnum]
338					    [argv_ind], (char **)NULL, 10);
339					while (isdigit(argv[optnum][argv_ind]))
340					    SQUEEZE_ARG(argv[optnum],
341							argv_ind, 1);
342				}
343				if (Etabn <= 0)
344					Etabn = DEFTAB;
345				argv_ind--;
346				break;
347
348			case 'i':
349				SQUEEZE_ARG(argv[optnum], argv_ind, 1);
350				if ((c = argv[optnum][argv_ind]) != '\0' &&
351				    !isdigit(c)) {
352					int	r;
353					wchar_t	wc;
354					r = mbtowc(&wc, &argv[optnum][argv_ind],
355						mbcurmax);
356					if (r == -1) {
357						(void) fprintf(stderr, gettext(
358"pr: Illegal character in -i option\n"));
359						exit(1);
360					}
361					Itabc = wc;
362					SQUEEZE_ARG(argv[optnum], argv_ind, r);
363				}
364				if (isdigit(argv[optnum][argv_ind])) {
365					Itabn = (int)strtol(&argv[optnum]
366					    [argv_ind], (char **)NULL, 10);
367					while (isdigit(argv[optnum][argv_ind]))
368					    SQUEEZE_ARG(argv[optnum],
369							argv_ind, 1);
370				}
371				if (Itabn <= 0)
372					Itabn = DEFTAB;
373				argv_ind--;
374				break;
375
376
377			case 'n':
378				++Lnumb;
379				SQUEEZE_ARG(argv[optnum], argv_ind, 1);
380				if ((c = argv[optnum][argv_ind]) != '\0' &&
381				    !isdigit(c)) {
382					int	r;
383					wchar_t	wc;
384					r = mbtowc(&wc, &argv[optnum][argv_ind],
385						mbcurmax);
386					if (r == -1) {
387						(void) fprintf(stderr, gettext(
388"pr: Illegal character in -n option\n"));
389						exit(1);
390					}
391					Nsepc = wc;
392					SQUEEZE_ARG(argv[optnum], argv_ind, r);
393				}
394				if (isdigit(argv[optnum][argv_ind])) {
395					Numw = (int)strtol(&argv[optnum]
396					    [argv_ind], (char **)NULL, 10);
397					while (isdigit(argv[optnum][argv_ind]))
398					    SQUEEZE_ARG(argv[optnum],
399							argv_ind, 1);
400				}
401				argv_ind--;
402				if (!Numw)
403					Numw = NUMW;
404				break;
405
406			case 's':
407				SQUEEZE_ARG(argv[optnum], argv_ind, 1);
408				if ((Sepc = argv[optnum][argv_ind]) == '\0')
409					Sepc = '\t';
410				else {
411					int	r;
412					wchar_t	wc;
413					r = mbtowc(&wc, &argv[optnum][argv_ind],
414						mbcurmax);
415					if (r == -1) {
416						(void) fprintf(stderr, gettext(
417"pr: Illegal character in -s option\n"));
418						exit(1);
419					}
420					Sepc = wc;
421					SQUEEZE_ARG(argv[optnum], argv_ind, r);
422				}
423				argv_ind--;
424				break;
425
426			default:
427				break;
428			}
429		}
430		if (argv[optnum][0] == '-' && argv[optnum][1] == '\0') {
431			REMOVE_ARG(argc, &argv[optnum]);
432			optnum--;
433		}
434	}
435
436	/* Now get the other options */
437	while ((c = getopt(argc, argv, "0123456789adfFh:l:mo:prtw:"))
438	    != EOF) {
439		switch (c) {
440		case '0':
441		case '1':
442		case '2':
443		case '3':
444		case '4':
445		case '5':
446		case '6':
447		case '7':
448		case '8':
449		case '9':
450			Ncols *= 10;
451			Ncols += c - '0';
452			break;
453
454		case 'a':
455			aflg++;
456			if (!Multi)
457				Multi = c;
458			break;
459
460		case 'd':
461			Dblspace = 2;
462			break;
463
464		case 'f':
465			++Formfeed;
466			++Pause;
467			break;
468
469		case 'h':
470			Head = optarg;
471			break;
472
473		case 'l':
474			if (strlen(optarg) != strspn(optarg, "0123456789"))
475				usage(1);
476			Length = (int)strtol(optarg, (char **)NULL, 10);
477			break;
478
479		case 'm':
480			mflg++;
481			Multi = c;
482			break;
483
484		case 'o':
485			if (strlen(optarg) != strspn(optarg, "0123456789"))
486				usage(1);
487			Offset = (int)strtol(optarg, (char **)NULL, 10);
488			break;
489
490		case 'p':
491			++Pause;
492			break;
493
494		case 'r':
495			Report = 0;
496			break;
497
498		case 't':
499			Margin = 0;
500			break;
501
502		case 'w':
503			if (strlen(optarg) != strspn(optarg, "0123456789"))
504				usage(1);
505			Linew = (int)strtol(optarg, (char **)NULL, 10);
506			break;
507
508		case 'F':
509#ifdef	XPG4
510			++Formfeed;
511#else
512			fold++;
513#endif
514			break;
515
516		case '?':
517			usage(2);
518			break;
519
520		default :
521			usage(2);
522		}
523	}
524
525	/* Count the file names and strip options */
526	for (i = 1; i < argc; i++) {
527		/* Check for explicit stdin */
528		if ((argv[i][0] == '-') && (argv[i][1] == '\0')) {
529			argv[eargc++][0] = '\0';
530			REMOVE_ARG(argc, &argv[i]);
531			if (i < optind)
532				optind--;
533		}
534	}
535	for (i = eargc; optind < argc; i++, optind++) {
536		argv[i] = argv[optind];
537		eargc++;
538	}
539
540	/* Check options */
541	if (Ncols == 0)
542		Ncols = 1;
543
544	if (mflg && (Ncols > 1)) {
545		(void) fprintf(stderr,
546		    gettext("pr: only one of either -m or -column allowed\n"));
547		usage(1);
548	}
549
550	if (Ncols == 1 && fold)
551		Multi = 'm';
552
553	if (Length <= 0)
554		Length = LENGTH;
555
556	if (Length <= Margin)
557		Margin = 0;
558
559	Plength = Length - Margin/2;
560
561	if (Multi == 'm')
562		Ncols = eargc;
563
564	switch (Ncols) {
565	case 0:
566		Ncols = 1;
567		break;
568
569	case 1:
570		break;
571
572	default:
573		if (Etabn == 0)	/* respect explicit tab specification */
574			Etabn = DEFTAB;
575		if (Itabn == 0)
576			Itabn = DEFTAB;
577	}
578
579	if ((Fcol = (foldinf *) malloc(sizeof (foldinf) * Ncols)) == NULL) {
580		(void) fprintf(stderr, gettext("pr: malloc failed\n"));
581		exit(1);
582	}
583	for (i = 0; i < Ncols; i++)
584		Fcol[i].fold = Fcol[i].skip = 0;
585
586	if (Linew == 0)
587		Linew = Ncols != 1 && Sepc == 0 ? LINEW : 512;
588
589	if (Lnumb) {
590		int numw;
591
592		if (Nsepc == '\t') {
593			if (Itabn == 0)
594				numw = Numw + DEFTAB - (Numw % DEFTAB);
595			else
596				numw = Numw + Itabn - (Numw % Itabn);
597		} else {
598			numw = Numw + ((iswprint(Nsepc)) ? 1 : 0);
599		}
600		Linew -= (Multi == 'm') ? numw : numw * Ncols;
601	}
602
603	if ((Colw = (Linew - Ncols + 1)/Ncols) < 1)
604		die("width too small");
605
606	if (Ncols != 1 && Multi == 0) {
607		/* Buflen should take the number of wide characters */
608		/* Not the size for Buffer */
609		Buflen = ((UNS) (Plength / Dblspace + 1)) *
610		    2 * (Linew + 1);
611		/* Should allocate Buflen * sizeof (wchar_t) */
612		Buffer = (wchar_t *)getspace(Buflen * sizeof (wchar_t));
613		Bufptr = Bufend = &Buffer[Buflen];
614		Colpts = (COLP) getspace((UNS) ((Ncols + 1) *
615		    sizeof (*Colpts)));
616		Colpts[0].c_lno = 0;
617	}
618
619	/* is stdin not a tty? */
620	if (Ttyout && (Pause || Formfeed) && !ttyname(fileno(stdin)))
621		Ttyin = fopen("/dev/tty", "r");
622
623	return (eargc);
624}
625
626
627static	int
628print(char *name)
629{
630	static	int	notfirst = 0;
631	char	*date = NULL;
632	char	*head = NULL;
633	int	c;
634
635	if (Multi != 'm' && mustopen(name, &Files[0]) == NULL)
636		return (0);
637	if (Multi == 'm' && Nfiles == 0 && mustopen(name, &Files[0]) == NULL)
638		die("cannot open stdin");
639	if (Buffer)
640		(void) ungetwc(Files->f_nextc, Files->f_f);
641	if (Lnumb)
642		Lnumb = 1;
643	for (Page = 0; ; putpage()) {
644		if (C == WEOF && !(fold && Buffer))
645			break;
646		if (Buffer)
647			nexbuf();
648		Inpos = 0;
649		if (get(0) == WEOF)
650			break;
651		(void) fflush(stdout);
652		if (++Page >= Fpage) {
653			/* Pause if -p and not first page */
654			if (Ttyout && Pause && !notfirst++) {
655				PROMPT();	/* prompt with bell and pause */
656				while ((c = getc(Ttyin)) != EOF && c != '\n')
657					;
658			}
659			if (Margin == 0)
660				continue;
661			if (date == NULL)
662				date = GETDATE();
663			if (head == NULL)
664				head = Head != NULL ? Head :
665				    Nfiles < 2 ? Files->f_name : nulls;
666			(void) printf("\n\n");
667			Nspace = Offset;
668			putspace();
669			(void) printf(HEAD);
670		}
671	}
672	C = '\0';
673	return (1);
674}
675
676
677static	void
678putpage()
679{
680	int	colno;
681
682	if (fold) {
683		foldpage();
684		return;
685	}
686	for (Line = Margin / 2; ; (void) get(0)) {
687		for (Nspace = Offset, colno = 0, Outpos = 0; C != '\f'; ) {
688			if (Lnumb && (C != WEOF) &&
689			    (((colno == 0) && (Multi == 'm')) ||
690			    (Multi != 'm'))) {
691				if (Page >= Fpage) {
692					putspace();
693					(void) printf("%*ld%wc", Numw, Buffer ?
694					    Colpts[colno].c_lno++ :
695					    Lnumb, Nsepc);
696
697					/* Move Outpos for number field */
698					Outpos += Numw;
699					if (Nsepc == '\t')
700						Outpos +=
701						    DEFTAB - (Outpos % DEFTAB);
702					else
703						Outpos++;
704				}
705				++Lnumb;
706			}
707			for (Lcolpos = 0, Pcolpos = 0;
708			    C != '\n' && C != '\f' && C != WEOF;
709			    (void) get(colno))
710				(void) put(C);
711
712			if ((C == WEOF) || (++colno == Ncols) ||
713			    ((C == '\n') && (get(colno) == WEOF)))
714				break;
715
716			if (Sepc)
717				(void) put(Sepc);
718			else if ((Nspace += Colw - Lcolpos + 1) < 1)
719				Nspace = 1;
720		}
721
722		if (C == WEOF) {
723			if (Margin != 0)
724				break;
725			if (colno != 0)
726				(void) put('\n');
727			return;
728		}
729		if (C == '\f')
730			break;
731		(void) put('\n');
732		if (Dblspace == 2 && Line < Plength)
733			(void) put('\n');
734		if (Line >= Plength)
735			break;
736	}
737	if (Formfeed)
738		(void) put('\f');
739	else
740		while (Line < Length)
741			(void) put('\n');
742}
743
744
745static	void
746foldpage()
747{
748	int	colno;
749	int	keep;
750	int	i;
751	int	pLcolpos;
752	static	int	sl;
753
754	for (Line = Margin / 2; ; (void) get(0)) {
755		for (Nspace = Offset, colno = 0, Outpos = 0; C != '\f'; ) {
756			if (Lnumb && Multi == 'm' && foldcol) {
757				if (!Fcol[colno].skip) {
758					unget(colno);
759					putspace();
760					if (!colno) {
761						for (i = 0; i <= Numw; i++)
762							(void) printf(" ");
763						(void) printf("%wc", Nsepc);
764					}
765					for (i = 0; i <= Colw; i++)
766						(void) printf(" ");
767					(void) put(Sepc);
768					if (++colno == Ncols)
769						break;
770					(void) get(colno);
771					continue;
772				} else if (!colno)
773					Lnumb = sl;
774			}
775
776			if (Lnumb && (C != WEOF) &&
777			    ((colno == 0 && Multi == 'm') || (Multi != 'm'))) {
778				if (Page >= Fpage) {
779					putspace();
780					if ((foldcol &&
781					    Fcol[colno].skip && Multi != 'a') ||
782					    (Fcol[0].fold && Multi == 'a') ||
783					    (Buffer && Colpts[colno].c_skip)) {
784						for (i = 0; i < Numw; i++)
785							(void) printf(" ");
786						(void) printf("%wc", Nsepc);
787						if (Buffer) {
788							Colpts[colno].c_lno++;
789							Colpts[colno].c_skip =
790							    0;
791						}
792					}
793					else
794					(void) printf("%*ld%wc", Numw, Buffer ?
795					    Colpts[colno].c_lno++ :
796					    Lnumb, Nsepc);
797				}
798				sl = Lnumb++;
799			}
800			pLcolpos = 0;
801			for (Lcolpos = 0, Pcolpos = 0;
802			    C != '\n' && C != '\f' && C != WEOF;
803			    (void) get(colno)) {
804				if (put(C)) {
805					unget(colno);
806					Fcol[(Multi == 'a') ? 0 : colno].fold
807					    = 1;
808					break;
809				} else if (Multi == 'a') {
810					Fcol[0].fold = 0;
811				}
812				pLcolpos = Lcolpos;
813			}
814			if (Buffer) {
815				alleof = 1;
816				for (i = 0; i < Ncols; i++)
817					if (!Fcol[i].eof)
818						alleof = 0;
819				if (alleof || ++colno == Ncols)
820					break;
821			} else if (C == EOF || ++colno == Ncols)
822				break;
823			keep = C;
824			(void) get(colno);
825			if (keep == '\n' && C == WEOF)
826				break;
827			if (Sepc)
828				(void) put(Sepc);
829			else if ((Nspace += Colw - pLcolpos + 1) < 1)
830				Nspace = 1;
831		}
832		foldcol = 0;
833		if (Lnumb && Multi != 'a') {
834			for (i = 0; i < Ncols; i++) {
835				Fcol[i].skip = Fcol[i].fold;
836				foldcol +=  Fcol[i].fold;
837				Fcol[i].fold = 0;
838			}
839		}
840		if (C == WEOF) {
841			if (Margin != 0)
842				break;
843			if (colno != 0)
844				(void) put('\n');
845			return;
846		}
847		if (C == '\f')
848			break;
849		(void) put('\n');
850		(void) fflush(stdout);
851		if (Dblspace == 2 && Line < Plength)
852			(void) put('\n');
853		if (Line >= Plength)
854			break;
855	}
856	if (Formfeed)
857		(void) put('\f');
858	else while (Line < Length)
859		(void) put('\n');
860}
861
862
863static	void
864nexbuf()
865{
866	wchar_t	*s = Buffer;
867	COLP	p = Colpts;
868	int	j;
869	int	c;
870	int	bline = 0;
871	wchar_t	wc;
872
873	if (fold) {
874		foldbuf();
875		return;
876	}
877	for (; ; ) {
878		p->c_ptr0 = p->c_ptr = s;
879		if (p == &Colpts[Ncols])
880			return;
881		(p++)->c_lno = Lnumb + bline;
882		for (j = (Length - Margin)/Dblspace; --j >= 0; ++bline) {
883			for (Inpos = 0; ; ) {
884				errno = 0;
885				wc = _fgetwc_pr(Files->f_f, &c);
886				if (wc == WEOF) {
887					/* If there is an illegal character, */
888					/* handle it as a byte sequence. */
889					if (errno == EILSEQ) {
890						if (Inpos < Colw - 1) {
891							*s = c;
892							if (++s >= Bufend)
893die("page-buffer overflow");
894						}
895						Inpos++;
896						Error++;
897						return;
898					} else {
899						/* Real EOF */
900for (*s = WEOF; p <= &Colpts[Ncols]; ++p)
901	p->c_ptr0 = p->c_ptr = s;
902						balance(bline);
903						return;
904					}
905				}
906
907				if (isascii(wc)) {
908					if (isprint(wc))
909						Inpos++;
910				} else if (iswprint(wc)) {
911					Inpos += wcwidth(wc);
912				}
913
914				if (Inpos <= Colw || wc == '\n') {
915					*s = wc;
916					if (++s >= Bufend)
917						die("page-buffer overflow");
918				}
919				if (wc == '\n')
920					break;
921				switch (wc) {
922				case '\b':
923					if (Inpos == 0)
924						--s;
925
926					/*FALLTHROUGH*/
927
928				case ESC:
929					if (Inpos > 0)
930						--Inpos;
931				}
932			}
933		}
934	}
935}
936
937
938static	void
939foldbuf()
940{
941	int	num;
942	int	i;
943	int	colno = 0;
944	int	size = Buflen;
945	wchar_t	*s;
946	wchar_t	*d;
947	COLP	p = Colpts;
948
949	for (i = 0; i < Ncols; i++)
950		Fcol[i].eof = 0;
951	d = Buffer;
952	if (Bufptr != Bufend) {
953		s = Bufptr;
954		while (s < Bufend)
955			*d++ = *s++;
956		size -= (Bufend - Bufptr);
957	}
958	Bufptr = Buffer;
959	p->c_ptr0 = p->c_ptr = Buffer;
960	if (p->c_lno == 0) {
961		p->c_lno = Lnumb;
962		p->c_skip = 0;
963	} else {
964		p->c_lno = Colpts[Ncols-1].c_lno;
965		p->c_skip = Colpts[Ncols].c_skip;
966		if (p->c_skip)
967			p->c_lno--;
968	}
969	if ((num = freadw(d, size, Files->f_f)) != size) {
970		for (*(d+num) = WEOF; (++p) <= &Colpts[Ncols]; ) {
971			p->c_ptr0 = p->c_ptr = (d+num);
972		}
973		balance(0);
974		return;
975	}
976	i = (Length - Margin) / Dblspace;
977	do {
978		(void) readbuf(&Bufptr, i, p++);
979	} while (++colno < Ncols);
980}
981
982
983static	void
984balance(int bline)	/* line balancing for last page */
985{
986	wchar_t	*s = Buffer;
987	COLP	p = Colpts;
988	int	colno = 0;
989	int	j;
990	int	c;
991	int	l;
992	int	lines;
993
994	if (!fold) {
995		c = bline % Ncols;
996		l = (bline + Ncols - 1)/Ncols;
997		bline = 0;
998		do {
999			for (j = 0; j < l; ++j)
1000				while (*s++ != '\n')
1001					;
1002			(++p)->c_lno = Lnumb + (bline += l);
1003			p->c_ptr0 = p->c_ptr = s;
1004			if (++colno == c)
1005				--l;
1006		} while (colno < Ncols - 1);
1007	} else {
1008		lines = readbuf(&s, 0, 0);
1009		l = (lines + Ncols - 1)/Ncols;
1010		if (l > ((Length - Margin) / Dblspace)) {
1011			l = (Length - Margin) / Dblspace;
1012			c = Ncols;
1013		} else {
1014			c = lines % Ncols;
1015		}
1016		s = Buffer;
1017		do {
1018			(void) readbuf(&s, l, p++);
1019			if (++colno == c)
1020				--l;
1021		} while (colno < Ncols);
1022		Bufptr = s;
1023	}
1024}
1025
1026
1027static	int
1028readbuf(wchar_t **s, int lincol, COLP p)
1029{
1030	int	lines = 0;
1031	int	chars = 0;
1032	int	width;
1033	int	nls = 0;
1034	int	move;
1035	int	skip = 0;
1036	int	decr = 0;
1037
1038	width = (Ncols == 1) ? Linew : Colw;
1039	while (**s != WEOF) {
1040		switch (**s) {
1041			case '\n':
1042				lines++; nls++; chars = 0; skip = 0;
1043				break;
1044
1045			case '\b':
1046			case ESC:
1047				if (chars) chars--;
1048				break;
1049
1050			case '\t':
1051				move = Itabn - ((chars + Itabn) % Itabn);
1052				move = (move < width-chars) ? move :
1053				    width-chars;
1054				chars += move;
1055				/* FALLTHROUGH */
1056
1057			default:
1058				if (isascii(**s)) {
1059					if (isprint(**s))
1060						chars++;
1061				} else if (iswprint(**s)) {
1062					chars += wcwidth(**s);
1063				}
1064		}
1065		if (chars > width) {
1066			lines++;
1067			skip++;
1068			decr++;
1069			chars = 0;
1070		}
1071		if (lincol && lines == lincol) {
1072			(p+1)->c_lno = p->c_lno + nls;
1073			(++p)->c_skip = skip;
1074			if (**s == '\n') (*s)++;
1075			p->c_ptr0 = p->c_ptr = (wchar_t *)*s;
1076			return (0);
1077		}
1078		if (decr)
1079			decr = 0;
1080		else
1081			(*s)++;
1082	}
1083	return (lines);
1084}
1085
1086
1087static	wint_t
1088get(int colno)
1089{
1090	static	int	peekc = 0;
1091	COLP	p;
1092	FILS	*q;
1093	int	c;
1094	wchar_t		wc, w;
1095
1096	if (peekc) {
1097		peekc = 0;
1098		wc = Etabc;
1099	} else if (Buffer) {
1100		p = &Colpts[colno];
1101		if (p->c_ptr >= (p+1)->c_ptr0)
1102			wc = WEOF;
1103		else if ((wc = *p->c_ptr) != WEOF)
1104			++p->c_ptr;
1105		if (fold && wc == WEOF)
1106			Fcol[colno].eof = 1;
1107	} else if ((wc =
1108		(q = &Files[Multi == 'a' ? 0 : colno])->f_nextc) == WEOF) {
1109		for (q = &Files[Nfiles]; --q >= Files && q->f_nextc == WEOF; )
1110			;
1111		if (q >= Files)
1112			wc = '\n';
1113	} else {
1114		errno = 0;
1115		w = _fgetwc_pr(q->f_f, &c);
1116		if (w == WEOF && errno == EILSEQ) {
1117			q->f_nextc = (wchar_t)c;
1118		} else {
1119			q->f_nextc = w;
1120		}
1121	}
1122
1123	if (Etabn != 0 && wc == Etabc) {
1124		++Inpos;
1125		peekc = ETABS;
1126		wc = ' ';
1127		return (C = wc);
1128	}
1129
1130	if (wc == WEOF)
1131		return (C = wc);
1132
1133	if (isascii(wc)) {
1134		if (isprint(wc)) {
1135			Inpos++;
1136			return (C = wc);
1137		}
1138	} else if (iswprint(wc)) {
1139		Inpos += wcwidth(wc);
1140		return (C = wc);
1141	}
1142
1143	switch (wc) {
1144	case '\b':
1145	case ESC:
1146		if (Inpos > 0)
1147			--Inpos;
1148		break;
1149	case '\f':
1150		if (Ncols == 1)
1151			break;
1152		wc = '\n';
1153		/* FALLTHROUGH */
1154	case '\n':
1155	case '\r':
1156		Inpos = 0;
1157		break;
1158	}
1159	return (C = wc);
1160}
1161
1162
1163static	int
1164put(wchar_t wc)
1165{
1166	int	move = 0;
1167	int	width = Colw;
1168	int	sp = Lcolpos;
1169
1170	if (fold && Ncols == 1)
1171		width = Linew;
1172
1173	switch (wc) {
1174	case ' ':
1175		/* If column not full or this is separator char */
1176		if ((!fold && Ncols < 2) || (Lcolpos < width) ||
1177		    ((Sepc == wc) && (Lcolpos == width))) {
1178			++Nspace;
1179			++Lcolpos;
1180		}
1181		if (fold && sp == Lcolpos)
1182			if (Lcolpos >= width)
1183				return (1);
1184
1185		return (0);
1186
1187	case '\t':
1188		if (Itabn == 0)
1189			break;
1190
1191		/* If column not full or this is separator char */
1192		if ((Lcolpos < width) ||
1193		    ((Sepc == wc) && (Lcolpos == width))) {
1194			move = Itabn - ((Lcolpos + Itabn) % Itabn);
1195			move = (move < width-Lcolpos) ? move : width-Lcolpos;
1196			Nspace += move;
1197			Lcolpos += move;
1198		}
1199		if (fold && sp == Lcolpos)
1200			if (Lcolpos >= width)
1201				return (1);
1202		return (0);
1203
1204	case '\b':
1205		if (Lcolpos == 0)
1206			return (0);
1207		if (Nspace > 0) {
1208			--Nspace;
1209			--Lcolpos;
1210			return (0);
1211		}
1212		if (Lcolpos > Pcolpos) {
1213			--Lcolpos;
1214			return (0);
1215		}
1216
1217		/*FALLTHROUGH*/
1218
1219	case ESC:
1220		move = -1;
1221		break;
1222
1223	case '\n':
1224		++Line;
1225
1226		/*FALLTHROUGH*/
1227
1228	case '\r':
1229	case '\f':
1230		Pcolpos = 0;
1231		Lcolpos = 0;
1232		Nspace = 0;
1233		Outpos = 0;
1234		/* FALLTHROUGH */
1235	default:
1236		if (isascii(wc)) {
1237			if (isprint(wc))
1238				move = 1;
1239			else
1240				move = 0;
1241		} else if (iswprint(wc)) {
1242			move = wcwidth(wc);
1243		} else {
1244			move = 0;
1245		}
1246		break;
1247	}
1248	if (Page < Fpage)
1249		return (0);
1250	if (Lcolpos > 0 || move > 0)
1251		Lcolpos += move;
1252
1253	putspace();
1254
1255	/* If column not full or this is separator char */
1256	if ((!fold && Ncols < 2) || (Lcolpos <= width) ||
1257	    ((Sepc == wc) && (Lcolpos > width))) {
1258		(void) fputwc(wc, stdout);
1259		Outpos += move;
1260		Pcolpos = Lcolpos;
1261	}
1262
1263	if (fold && Lcolpos > width)
1264		return (1);
1265
1266	return (0);
1267}
1268
1269
1270static	void
1271putspace(void)
1272{
1273	int nc = 0;
1274
1275	for (; Nspace > 0; Outpos += nc, Nspace -= nc) {
1276#ifdef XPG4
1277		/* XPG4:  -i:  replace multiple SPACE chars with tab chars */
1278		if ((Nspace >= 2 && Itabn > 0 &&
1279			Nspace >= (nc = Itabn - Outpos % Itabn)) && !fold) {
1280#else
1281		/* Solaris:  -i:  replace white space with tab chars */
1282		if ((Itabn > 0 && Nspace >= (nc = Itabn - Outpos % Itabn)) &&
1283			!fold) {
1284#endif
1285			(void) fputwc(Itabc, stdout);
1286		} else {
1287			nc = 1;
1288			(void) putchar(' ');
1289		}
1290	}
1291}
1292
1293
1294static	void
1295unget(int colno)
1296{
1297	if (Buffer) {
1298		if (*(Colpts[colno].c_ptr-1) != '\t')
1299			--(Colpts[colno].c_ptr);
1300		if (Colpts[colno].c_lno)
1301			Colpts[colno].c_lno--;
1302	} else {
1303		if ((Multi == 'm' && colno == 0) || Multi != 'm')
1304			if (Lnumb && !foldcol)
1305				Lnumb--;
1306		colno = (Multi == 'a') ? 0 : colno;
1307		(void) ungetwc(Files[colno].f_nextc, Files[colno].f_f);
1308		Files[colno].f_nextc = C;
1309	}
1310}
1311
1312
1313/*
1314 * Defer message about failure to open file to prevent messing up
1315 * alignment of page with tear perforations or form markers.
1316 * Treat empty file as special case and report as diagnostic.
1317 */
1318
1319static	FILE *
1320mustopen(char *s, FILS *f)
1321{
1322	char	*empty_file_msg = gettext("%s -- empty file");
1323	int	c;
1324
1325	if (*s == '\0') {
1326		f->f_name = STDINNAME();
1327		f->f_f = stdin;
1328	} else if ((f->f_f = fopen(f->f_name = s, "r")) == NULL) {
1329		s = ffiler(f->f_name);
1330		s = strcpy((char *)getspace((UNS) strlen(s) + 1), s);
1331	}
1332	if (f->f_f != NULL) {
1333		errno = 0;
1334		f->f_nextc = _fgetwc_pr(f->f_f, &c);
1335		if (f->f_nextc != WEOF) {
1336			return (f->f_f);
1337		} else {	/* WEOF */
1338			if (errno == EILSEQ) {
1339				f->f_nextc = (wchar_t)c;
1340				return (f->f_f);
1341			}
1342			if (Multi == 'm')
1343				return (f->f_f);
1344		}
1345		(void) sprintf(s = (char *)getspace((UNS) strlen(f->f_name)
1346		    + 1 + (UNS) strlen(empty_file_msg)),
1347		    empty_file_msg, f->f_name);
1348		(void) fclose(f->f_f);
1349	}
1350	Error = 1;
1351	if (Report)
1352		if (Ttyout) {	/* accumulate error reports */
1353			Lasterr = Lasterr->e_nextp =
1354			    (ERR *) getspace((UNS) sizeof (ERR));
1355			Lasterr->e_nextp = NULL;
1356			Lasterr->e_mess = s;
1357		} else {	/* ok to print error report now */
1358			cerror(s);
1359			(void) putc('\n', stderr);
1360		}
1361	return ((FILE *)NULL);
1362}
1363
1364
1365static	ANY *
1366getspace(UNS n)
1367{
1368	ANY *t;
1369
1370	if ((t = (ANY *) malloc(n)) == NULL)
1371		die("out of space");
1372	return (t);
1373}
1374
1375
1376static	void
1377die(char *s)
1378{
1379	++Error;
1380	errprint();
1381	cerror(s);
1382	(void) putc('\n', stderr);
1383	exit(1);
1384
1385	/*NOTREACHED*/
1386}
1387
1388
1389static	void
1390errprint()	/* print accumulated error reports */
1391{
1392	(void) fflush(stdout);
1393	for (; Err != NULL; Err = Err->e_nextp) {
1394		cerror(Err->e_mess);
1395		(void) putc('\n', stderr);
1396	}
1397	done();
1398}
1399
1400
1401static	void
1402fixtty()
1403{
1404	struct stat sbuf;
1405
1406	setbuf(stdout, obuf);
1407	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1408		(void) signal(SIGINT, onintr);
1409	if (Ttyout = ttyname(fileno(stdout))) {	/* is stdout a tty? */
1410		(void) stat(Ttyout, &sbuf);
1411		Mode = sbuf.st_mode;		/* save permissions */
1412		(void) chmod(Ttyout, (S_IREAD|S_IWRITE));
1413	}
1414}
1415
1416
1417static	void
1418onintr()
1419{
1420	++Error;
1421	errprint();
1422	_exit(1);
1423}
1424
1425
1426static	char *
1427GETDATE()	/* return date file was last modified */
1428{
1429	static	char	*now = NULL;
1430	static	struct	stat	sbuf;
1431	static	struct	stat	nbuf;
1432
1433	if (Nfiles > 1 || Files->f_name == nulls) {
1434		if (now == NULL) {
1435			(void) time(&nbuf.st_mtime);
1436			(void) cftime(time_buf,
1437				dcgettext(NULL, FORMAT, LC_TIME),
1438			    &nbuf.st_mtime);
1439			now = time_buf;
1440		}
1441		return (now);
1442	} else {
1443		(void) stat(Files->f_name, &sbuf);
1444		(void) cftime(time_buf, dcgettext(NULL, FORMAT, LC_TIME),
1445			&sbuf.st_mtime);
1446		return (time_buf);
1447	}
1448}
1449
1450
1451static	char *
1452ffiler(char *s)
1453{
1454	static char buf[100];
1455
1456	(void) sprintf(buf, gettext("can't open %s"), s);
1457	return (buf);
1458}
1459
1460
1461static	void
1462usage(int rc)
1463{
1464	(void) fprintf(stderr, gettext(
1465"usage: pr [-# [-w #] [-a]] [-e[c][#]] [-i[c][#]] [-drtfp] [-n[c][#]]  \\\n"
1466"          [-o #] [-l #] [-s[char]] [-h header] [-F] [+#] [file ...]\n\n"
1467"       pr [-m [-w #]] [-e[c][#]] [-i[c][#]] [-drtfp] [-n[c][#]] [-0 #] \\\n"
1468"          [-l #] [-s[char]] [-h header] [-F] [+#] file1 file2 ...\n"
1469));
1470	exit(rc);
1471}
1472
1473static wint_t
1474_fgetwc_pr(FILE *f, int *ic)
1475{
1476	int	i;
1477	int	len;
1478	char	mbuf[MB_LEN_MAX];
1479	int	c;
1480	wchar_t	wc;
1481
1482	c = getc(f);
1483
1484	if (c == EOF)
1485		return (WEOF);
1486	if (mbcurmax == 1 || isascii(c)) {
1487		return ((wint_t)c);
1488	}
1489	mbuf[0] = (char)c;
1490	for (i = 1; i < mbcurmax; i++) {
1491		c = getc(f);
1492		if (c == EOF) {
1493			break;
1494		} else {
1495			mbuf[i] = (char)c;
1496		}
1497	}
1498	mbuf[i] = 0;
1499
1500	len = mbtowc(&wc, mbuf, i);
1501	if (len == -1) {
1502		/* Illegal character */
1503		/* Set the first byte to *ic */
1504		*ic = mbuf[0];
1505		/* Push back remaining characters */
1506		for (i--; i > 0; i--) {
1507			(void) ungetc(mbuf[i], f);
1508		}
1509		errno = EILSEQ;
1510		return (WEOF);
1511	} else {
1512		/* Push back over-read characters */
1513		for (i--; i >= len; i--) {
1514			(void) ungetc(mbuf[i], f);
1515		}
1516		return ((wint_t)wc);
1517	}
1518}
1519
1520static size_t
1521freadw(wchar_t *ptr, size_t nitems, FILE *f)
1522{
1523	size_t	i;
1524	size_t	ret;
1525	int	c;
1526	wchar_t	*p;
1527	wint_t	wc;
1528
1529	if (feof(f)) {
1530		return (0);
1531	}
1532
1533	p = ptr;
1534	ret = 0;
1535	for (i = 0; i < nitems; i++) {
1536		errno = 0;
1537		wc = _fgetwc_pr(f, &c);
1538		if (wc == WEOF) {
1539			if (errno == EILSEQ) {
1540				*p++ = (wchar_t)c;
1541				ret++;
1542			} else {
1543				return (ret);
1544			}
1545		} else {
1546			*p++ = (wchar_t)wc;
1547			ret++;
1548		}
1549	}
1550	return (ret);
1551}
1552