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