Merge commit 'bb1f424574ac8e08069d0ba993c2a41ffe796794'
[unleashed.git] / usr / src / cmd / pr / pr.c
blob288cd86bdb8ee3dc64be74667675880de30ad5ac
1 /*
2 * CDDL HEADER START
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.
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.
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]
20 * CDDL HEADER END
23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 * Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
29 * All Rights Reserved
33 * University Copyright- Copyright (c) 1982, 1986, 1988
34 * The Regents of the University of California
35 * All Rights Reserved
37 * University Acknowledgment- Portions of this document are derived from
38 * software developed by the University of California, Berkeley, and its
39 * contributors.
43 * PR command (print files in pages and columns, with headings)
44 * 2+head+2+page[56]+5
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>
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--; \
86 #define SQUEEZE_ARG(argp, ind, n) \
87 { \
88 int i; \
89 for (i = ind; argp[i]; i++) \
90 argp[i] = argp[i + n]; \
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
101 #define FORMAT "%b %e %H:%M %Y"
103 typedef int ANY;
104 typedef unsigned int UNS;
105 typedef struct { FILE *f_f; char *f_name; wchar_t f_nextc; } FILS;
106 typedef struct {int fold; int skip; int eof; } foldinf;
107 typedef struct { wchar_t *c_ptr, *c_ptr0; long c_lno; int c_skip; } *COLP;
108 typedef struct err { struct err *e_nextp; char *e_mess; } ERR;
111 * Global data.
113 static FILS *Files;
114 static mode_t Mode;
115 static int Multi = 0;
116 static int Nfiles = 0;
117 static int Error = 0;
118 static char nulls[] = "";
119 static char *Ttyout;
120 static char obuf[BUFSIZ];
121 static char time_buf[50]; /* array to hold the time and date */
122 static long Lnumb = 0;
123 static FILE *Ttyin;
124 static int Dblspace = 1;
125 static int Fpage = 1;
126 static int Formfeed = 0;
127 static int Length = LENGTH;
128 static int Linew = 0;
129 static int Offset = 0;
130 static int Ncols = 0;
131 static int Pause = 0;
132 static wchar_t Sepc = 0;
133 static int Colw;
134 static int Plength;
135 static int Margin = MARGIN;
136 static int Numw;
137 static int Nsepc = NSEPC;
138 static int Report = 1;
139 static int Etabn = 0;
140 static wchar_t Etabc = '\t';
141 static int Itabn = 0;
142 static wchar_t Itabc = '\t';
143 static int fold = 0;
144 static int foldcol = 0;
145 static int alleof = 0;
146 static char *Head = NULL;
147 static wchar_t *Buffer = NULL, *Bufend, *Bufptr;
148 static UNS Buflen;
149 static COLP Colpts;
150 static foldinf *Fcol;
151 static int Page;
152 static wchar_t C = '\0';
153 static int Nspace;
154 static int Inpos;
155 static int Outpos;
156 static int Lcolpos;
157 static int Pcolpos;
158 static int Line;
159 static ERR *Err = NULL;
160 static ERR *Lasterr = (ERR *)&Err;
161 static int mbcurmax = 1;
164 * Function prototypes.
166 static void onintr();
167 static ANY *getspace();
168 static int findopt(int, char **);
169 static void fixtty();
170 static char *GETDATE();
171 static char *ffiler(char *);
172 static int print(char *);
173 static void putpage();
174 static void foldpage();
175 static void nexbuf();
176 static void foldbuf();
177 static void balance(int);
178 static int readbuf(wchar_t **, int, COLP);
179 static wint_t get(int);
180 static int put(wchar_t);
181 static void putspace();
182 static void unget(int);
183 static FILE *mustopen(char *, FILS *);
184 static void die(char *);
185 static void errprint();
186 static void usage(int);
187 static wint_t _fgetwc_pr(FILE *, int *);
188 static size_t freadw(wchar_t *, size_t, FILE *);
192 main(int argc, char **argv)
194 FILS fstr[NFILES];
195 int nfdone = 0;
197 Ttyin = stdin;
199 /* Get locale variables for environment */
200 (void) setlocale(LC_ALL, "");
202 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
203 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
204 #endif
205 (void) textdomain(TEXT_DOMAIN);
207 mbcurmax = MB_CUR_MAX;
208 Files = fstr;
209 for (argc = findopt(argc, argv); argc > 0; --argc, ++argv) {
210 if (Multi == 'm') {
211 if (Nfiles >= NFILES - 1) die("too many files");
212 if (mustopen(*argv, &Files[Nfiles++]) == NULL)
213 ++nfdone; /* suppress printing */
214 } else {
215 if (print(*argv))
216 (void) fclose(Files->f_f);
217 ++nfdone;
220 if (!nfdone) /* no files named, use stdin */
221 (void) print(NOFILE); /* on GCOS, use current file, if any */
223 if (Report) {
224 errprint(); /* print accumulated error reports */
225 exit(Error);
228 return (Error);
233 * findopt() returns argc modified to be the number of explicitly supplied
234 * filenames, including '-', the explicit request to use stdin.
235 * argc == 0 implies that no filenames were supplied and stdin should be used.
236 * Options are striped from argv and only file names are returned.
239 static int
240 findopt(int argc, char **argv)
242 int eargc = 0;
243 int c;
244 int mflg = 0;
245 int aflg = 0;
246 int optnum;
247 int argv_ind;
248 int end_opt;
249 int i;
250 int isarg = 0;
252 fixtty();
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);
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);
272 REMOVE_ARG(argc, &argv[optnum]);
273 optnum--;
274 break;
276 case '-':
277 /* Check for end of options */
278 if (argv[optnum][1] == '-') {
279 end_opt++;
280 break;
283 if (argv[optnum][1] == 'h' || argv[optnum][1] == 'l' ||
284 argv[optnum][1] == 'o' || argv[optnum][1] == 'w')
285 isarg = 1;
286 else
287 isarg = 0;
289 break;
291 default:
292 if (isarg == 0)
293 end_opt++;
294 else
295 isarg = 0;
296 break;
301 * Handle options with optional arguments.
302 * If optional arguments are present they may not be separated
303 * from the option letter.
306 for (optnum = 1; optnum < argc; optnum++) {
307 if (argv[optnum][0] == '-' && argv[optnum][1] == '-')
308 /* End of options */
309 break;
311 if (argv[optnum][0] == '-' && argv[optnum][1] == '\0')
312 /* stdin file name */
313 continue;
315 if (argv[optnum][0] != '-')
316 /* not option */
317 continue;
319 for (argv_ind = 1; argv[optnum][argv_ind] != '\0'; argv_ind++) {
320 switch (argv[optnum][argv_ind]) {
321 case 'e':
322 SQUEEZE_ARG(argv[optnum], argv_ind, 1);
323 if ((c = argv[optnum][argv_ind]) != '\0' &&
324 !isdigit(c)) {
325 int r;
326 wchar_t wc;
327 r = mbtowc(&wc, &argv[optnum][argv_ind],
328 mbcurmax);
329 if (r == -1) {
330 (void) fprintf(stderr, gettext(
331 "pr: Illegal character in -e option\n"));
332 exit(1);
334 Etabc = wc;
335 SQUEEZE_ARG(argv[optnum], argv_ind, r);
337 if (isdigit(argv[optnum][argv_ind])) {
338 Etabn = (int)strtol(&argv[optnum]
339 [argv_ind], (char **)NULL, 10);
340 while (isdigit(argv[optnum][argv_ind]))
341 SQUEEZE_ARG(argv[optnum],
342 argv_ind, 1);
344 if (Etabn <= 0)
345 Etabn = DEFTAB;
346 argv_ind--;
347 break;
349 case 'i':
350 SQUEEZE_ARG(argv[optnum], argv_ind, 1);
351 if ((c = argv[optnum][argv_ind]) != '\0' &&
352 !isdigit(c)) {
353 int r;
354 wchar_t wc;
355 r = mbtowc(&wc, &argv[optnum][argv_ind],
356 mbcurmax);
357 if (r == -1) {
358 (void) fprintf(stderr, gettext(
359 "pr: Illegal character in -i option\n"));
360 exit(1);
362 Itabc = wc;
363 SQUEEZE_ARG(argv[optnum], argv_ind, r);
365 if (isdigit(argv[optnum][argv_ind])) {
366 Itabn = (int)strtol(&argv[optnum]
367 [argv_ind], (char **)NULL, 10);
368 while (isdigit(argv[optnum][argv_ind]))
369 SQUEEZE_ARG(argv[optnum],
370 argv_ind, 1);
372 if (Itabn <= 0)
373 Itabn = DEFTAB;
374 argv_ind--;
375 break;
378 case 'n':
379 ++Lnumb;
380 SQUEEZE_ARG(argv[optnum], argv_ind, 1);
381 if ((c = argv[optnum][argv_ind]) != '\0' &&
382 !isdigit(c)) {
383 int r;
384 wchar_t wc;
385 r = mbtowc(&wc, &argv[optnum][argv_ind],
386 mbcurmax);
387 if (r == -1) {
388 (void) fprintf(stderr, gettext(
389 "pr: Illegal character in -n option\n"));
390 exit(1);
392 Nsepc = wc;
393 SQUEEZE_ARG(argv[optnum], argv_ind, r);
395 if (isdigit(argv[optnum][argv_ind])) {
396 Numw = (int)strtol(&argv[optnum]
397 [argv_ind], (char **)NULL, 10);
398 while (isdigit(argv[optnum][argv_ind]))
399 SQUEEZE_ARG(argv[optnum],
400 argv_ind, 1);
402 argv_ind--;
403 if (!Numw)
404 Numw = NUMW;
405 break;
407 case 's':
408 SQUEEZE_ARG(argv[optnum], argv_ind, 1);
409 if ((Sepc = argv[optnum][argv_ind]) == '\0')
410 Sepc = '\t';
411 else {
412 int r;
413 wchar_t wc;
414 r = mbtowc(&wc, &argv[optnum][argv_ind],
415 mbcurmax);
416 if (r == -1) {
417 (void) fprintf(stderr, gettext(
418 "pr: Illegal character in -s option\n"));
419 exit(1);
421 Sepc = wc;
422 SQUEEZE_ARG(argv[optnum], argv_ind, r);
424 argv_ind--;
425 break;
427 default:
428 break;
431 if (argv[optnum][0] == '-' && argv[optnum][1] == '\0') {
432 REMOVE_ARG(argc, &argv[optnum]);
433 optnum--;
437 /* Now get the other options */
438 while ((c = getopt(argc, argv, "0123456789adfFh:l:mo:prtw:"))
439 != EOF) {
440 switch (c) {
441 case '0':
442 case '1':
443 case '2':
444 case '3':
445 case '4':
446 case '5':
447 case '6':
448 case '7':
449 case '8':
450 case '9':
451 Ncols *= 10;
452 Ncols += c - '0';
453 break;
455 case 'a':
456 aflg++;
457 if (!Multi)
458 Multi = c;
459 break;
461 case 'd':
462 Dblspace = 2;
463 break;
465 case 'f':
466 ++Formfeed;
467 ++Pause;
468 break;
470 case 'h':
471 Head = optarg;
472 break;
474 case 'l':
475 if (strlen(optarg) != strspn(optarg, "0123456789"))
476 usage(1);
477 Length = (int)strtol(optarg, (char **)NULL, 10);
478 break;
480 case 'm':
481 mflg++;
482 Multi = c;
483 break;
485 case 'o':
486 if (strlen(optarg) != strspn(optarg, "0123456789"))
487 usage(1);
488 Offset = (int)strtol(optarg, (char **)NULL, 10);
489 break;
491 case 'p':
492 ++Pause;
493 break;
495 case 'r':
496 Report = 0;
497 break;
499 case 't':
500 Margin = 0;
501 break;
503 case 'w':
504 if (strlen(optarg) != strspn(optarg, "0123456789"))
505 usage(1);
506 Linew = (int)strtol(optarg, (char **)NULL, 10);
507 break;
509 case 'F':
510 #ifdef XPG4
511 ++Formfeed;
512 #else
513 fold++;
514 #endif
515 break;
517 case '?':
518 usage(2);
519 break;
521 default :
522 usage(2);
526 /* Count the file names and strip options */
527 for (i = 1; i < argc; i++) {
528 /* Check for explicit stdin */
529 if ((argv[i][0] == '-') && (argv[i][1] == '\0')) {
530 argv[eargc++][0] = '\0';
531 REMOVE_ARG(argc, &argv[i]);
532 if (i < optind)
533 optind--;
536 for (i = eargc; optind < argc; i++, optind++) {
537 argv[i] = argv[optind];
538 eargc++;
541 /* Check options */
542 if (Ncols == 0)
543 Ncols = 1;
545 if (mflg && (Ncols > 1)) {
546 (void) fprintf(stderr,
547 gettext("pr: only one of either -m or -column allowed\n"));
548 usage(1);
551 if (Ncols == 1 && fold)
552 Multi = 'm';
554 if (Length <= 0)
555 Length = LENGTH;
557 if (Length <= Margin)
558 Margin = 0;
560 Plength = Length - Margin/2;
562 if (Multi == 'm')
563 Ncols = eargc;
565 switch (Ncols) {
566 case 0:
567 Ncols = 1;
568 break;
570 case 1:
571 break;
573 default:
574 if (Etabn == 0) /* respect explicit tab specification */
575 Etabn = DEFTAB;
576 if (Itabn == 0)
577 Itabn = DEFTAB;
580 if ((Fcol = (foldinf *) malloc(sizeof (foldinf) * Ncols)) == NULL) {
581 (void) fprintf(stderr, gettext("pr: malloc failed\n"));
582 exit(1);
584 for (i = 0; i < Ncols; i++)
585 Fcol[i].fold = Fcol[i].skip = 0;
587 if (Linew == 0)
588 Linew = Ncols != 1 && Sepc == 0 ? LINEW : 512;
590 if (Lnumb) {
591 int numw;
593 if (Nsepc == '\t') {
594 if (Itabn == 0)
595 numw = Numw + DEFTAB - (Numw % DEFTAB);
596 else
597 numw = Numw + Itabn - (Numw % Itabn);
598 } else {
599 numw = Numw + ((iswprint(Nsepc)) ? 1 : 0);
601 Linew -= (Multi == 'm') ? numw : numw * Ncols;
604 if ((Colw = (Linew - Ncols + 1)/Ncols) < 1)
605 die("width too small");
607 if (Ncols != 1 && Multi == 0) {
608 /* Buflen should take the number of wide characters */
609 /* Not the size for Buffer */
610 Buflen = ((UNS) (Plength / Dblspace + 1)) *
611 2 * (Linew + 1);
612 /* Should allocate Buflen * sizeof (wchar_t) */
613 Buffer = (wchar_t *)getspace(Buflen * sizeof (wchar_t));
614 Bufptr = Bufend = &Buffer[Buflen];
615 Colpts = (COLP) getspace((UNS) ((Ncols + 1) *
616 sizeof (*Colpts)));
617 Colpts[0].c_lno = 0;
620 /* is stdin not a tty? */
621 if (Ttyout && (Pause || Formfeed) && !ttyname(fileno(stdin)))
622 Ttyin = fopen("/dev/tty", "r");
624 return (eargc);
628 static int
629 print(char *name)
631 static int notfirst = 0;
632 char *date = NULL;
633 char *head = NULL;
634 int c;
636 if (Multi != 'm' && mustopen(name, &Files[0]) == NULL)
637 return (0);
638 if (Multi == 'm' && Nfiles == 0 && mustopen(name, &Files[0]) == NULL)
639 die("cannot open stdin");
640 if (Buffer)
641 (void) ungetwc(Files->f_nextc, Files->f_f);
642 if (Lnumb)
643 Lnumb = 1;
644 for (Page = 0; ; putpage()) {
645 if (C == WEOF && !(fold && Buffer))
646 break;
647 if (Buffer)
648 nexbuf();
649 Inpos = 0;
650 if (get(0) == WEOF)
651 break;
652 (void) fflush(stdout);
653 if (++Page >= Fpage) {
654 /* Pause if -p and not first page */
655 if (Ttyout && Pause && !notfirst++) {
656 PROMPT(); /* prompt with bell and pause */
657 while ((c = getc(Ttyin)) != EOF && c != '\n')
660 if (Margin == 0)
661 continue;
662 if (date == NULL)
663 date = GETDATE();
664 if (head == NULL)
665 head = Head != NULL ? Head :
666 Nfiles < 2 ? Files->f_name : nulls;
667 (void) printf("\n\n");
668 Nspace = Offset;
669 putspace();
670 (void) printf(HEAD);
673 C = '\0';
674 return (1);
678 static void
679 putpage()
681 int colno;
683 if (fold) {
684 foldpage();
685 return;
687 for (Line = Margin / 2; ; (void) get(0)) {
688 for (Nspace = Offset, colno = 0, Outpos = 0; C != '\f'; ) {
689 if (Lnumb && (C != WEOF) &&
690 (((colno == 0) && (Multi == 'm')) ||
691 (Multi != 'm'))) {
692 if (Page >= Fpage) {
693 putspace();
694 (void) printf("%*ld%wc", Numw, Buffer ?
695 Colpts[colno].c_lno++ :
696 Lnumb, Nsepc);
698 /* Move Outpos for number field */
699 Outpos += Numw;
700 if (Nsepc == '\t')
701 Outpos +=
702 DEFTAB - (Outpos % DEFTAB);
703 else
704 Outpos++;
706 ++Lnumb;
708 for (Lcolpos = 0, Pcolpos = 0;
709 C != '\n' && C != '\f' && C != WEOF;
710 (void) get(colno))
711 (void) put(C);
713 if ((C == WEOF) || (++colno == Ncols) ||
714 ((C == '\n') && (get(colno) == WEOF)))
715 break;
717 if (Sepc)
718 (void) put(Sepc);
719 else if ((Nspace += Colw - Lcolpos + 1) < 1)
720 Nspace = 1;
723 if (C == WEOF) {
724 if (Margin != 0)
725 break;
726 if (colno != 0)
727 (void) put('\n');
728 return;
730 if (C == '\f')
731 break;
732 (void) put('\n');
733 if (Dblspace == 2 && Line < Plength)
734 (void) put('\n');
735 if (Line >= Plength)
736 break;
738 if (Formfeed)
739 (void) put('\f');
740 else
741 while (Line < Length)
742 (void) put('\n');
746 static void
747 foldpage()
749 int colno;
750 int keep;
751 int i;
752 int pLcolpos;
753 static int sl;
755 for (Line = Margin / 2; ; (void) get(0)) {
756 for (Nspace = Offset, colno = 0, Outpos = 0; C != '\f'; ) {
757 if (Lnumb && Multi == 'm' && foldcol) {
758 if (!Fcol[colno].skip) {
759 unget(colno);
760 putspace();
761 if (!colno) {
762 for (i = 0; i <= Numw; i++)
763 (void) printf(" ");
764 (void) printf("%wc", Nsepc);
766 for (i = 0; i <= Colw; i++)
767 (void) printf(" ");
768 (void) put(Sepc);
769 if (++colno == Ncols)
770 break;
771 (void) get(colno);
772 continue;
773 } else if (!colno)
774 Lnumb = sl;
777 if (Lnumb && (C != WEOF) &&
778 ((colno == 0 && Multi == 'm') || (Multi != 'm'))) {
779 if (Page >= Fpage) {
780 putspace();
781 if ((foldcol &&
782 Fcol[colno].skip && Multi != 'a') ||
783 (Fcol[0].fold && Multi == 'a') ||
784 (Buffer && Colpts[colno].c_skip)) {
785 for (i = 0; i < Numw; i++)
786 (void) printf(" ");
787 (void) printf("%wc", Nsepc);
788 if (Buffer) {
789 Colpts[colno].c_lno++;
790 Colpts[colno].c_skip =
794 else
795 (void) printf("%*ld%wc", Numw, Buffer ?
796 Colpts[colno].c_lno++ :
797 Lnumb, Nsepc);
799 sl = Lnumb++;
801 pLcolpos = 0;
802 for (Lcolpos = 0, Pcolpos = 0;
803 C != '\n' && C != '\f' && C != WEOF;
804 (void) get(colno)) {
805 if (put(C)) {
806 unget(colno);
807 Fcol[(Multi == 'a') ? 0 : colno].fold
808 = 1;
809 break;
810 } else if (Multi == 'a') {
811 Fcol[0].fold = 0;
813 pLcolpos = Lcolpos;
815 if (Buffer) {
816 alleof = 1;
817 for (i = 0; i < Ncols; i++)
818 if (!Fcol[i].eof)
819 alleof = 0;
820 if (alleof || ++colno == Ncols)
821 break;
822 } else if (C == EOF || ++colno == Ncols)
823 break;
824 keep = C;
825 (void) get(colno);
826 if (keep == '\n' && C == WEOF)
827 break;
828 if (Sepc)
829 (void) put(Sepc);
830 else if ((Nspace += Colw - pLcolpos + 1) < 1)
831 Nspace = 1;
833 foldcol = 0;
834 if (Lnumb && Multi != 'a') {
835 for (i = 0; i < Ncols; i++) {
836 Fcol[i].skip = Fcol[i].fold;
837 foldcol += Fcol[i].fold;
838 Fcol[i].fold = 0;
841 if (C == WEOF) {
842 if (Margin != 0)
843 break;
844 if (colno != 0)
845 (void) put('\n');
846 return;
848 if (C == '\f')
849 break;
850 (void) put('\n');
851 (void) fflush(stdout);
852 if (Dblspace == 2 && Line < Plength)
853 (void) put('\n');
854 if (Line >= Plength)
855 break;
857 if (Formfeed)
858 (void) put('\f');
859 else while (Line < Length)
860 (void) put('\n');
864 static void
865 nexbuf()
867 wchar_t *s = Buffer;
868 COLP p = Colpts;
869 int j;
870 int c;
871 int bline = 0;
872 wchar_t wc;
874 if (fold) {
875 foldbuf();
876 return;
878 for (; ; ) {
879 p->c_ptr0 = p->c_ptr = s;
880 if (p == &Colpts[Ncols])
881 return;
882 (p++)->c_lno = Lnumb + bline;
883 for (j = (Length - Margin)/Dblspace; --j >= 0; ++bline) {
884 for (Inpos = 0; ; ) {
885 errno = 0;
886 wc = _fgetwc_pr(Files->f_f, &c);
887 if (wc == WEOF) {
888 /* If there is an illegal character, */
889 /* handle it as a byte sequence. */
890 if (errno == EILSEQ) {
891 if (Inpos < Colw - 1) {
892 *s = c;
893 if (++s >= Bufend)
894 die("page-buffer overflow");
896 Inpos++;
897 Error++;
898 return;
899 } else {
900 /* Real EOF */
901 for (*s = WEOF; p <= &Colpts[Ncols]; ++p)
902 p->c_ptr0 = p->c_ptr = s;
903 balance(bline);
904 return;
908 if (isascii(wc)) {
909 if (isprint(wc))
910 Inpos++;
911 } else if (iswprint(wc)) {
912 Inpos += wcwidth(wc);
915 if (Inpos <= Colw || wc == '\n') {
916 *s = wc;
917 if (++s >= Bufend)
918 die("page-buffer overflow");
920 if (wc == '\n')
921 break;
922 switch (wc) {
923 case '\b':
924 if (Inpos == 0)
925 --s;
927 /*FALLTHROUGH*/
929 case ESC:
930 if (Inpos > 0)
931 --Inpos;
939 static void
940 foldbuf()
942 int num;
943 int i;
944 int colno = 0;
945 int size = Buflen;
946 wchar_t *s;
947 wchar_t *d;
948 COLP p = Colpts;
950 for (i = 0; i < Ncols; i++)
951 Fcol[i].eof = 0;
952 d = Buffer;
953 if (Bufptr != Bufend) {
954 s = Bufptr;
955 while (s < Bufend)
956 *d++ = *s++;
957 size -= (Bufend - Bufptr);
959 Bufptr = Buffer;
960 p->c_ptr0 = p->c_ptr = Buffer;
961 if (p->c_lno == 0) {
962 p->c_lno = Lnumb;
963 p->c_skip = 0;
964 } else {
965 p->c_lno = Colpts[Ncols-1].c_lno;
966 p->c_skip = Colpts[Ncols].c_skip;
967 if (p->c_skip)
968 p->c_lno--;
970 if ((num = freadw(d, size, Files->f_f)) != size) {
971 for (*(d+num) = WEOF; (++p) <= &Colpts[Ncols]; ) {
972 p->c_ptr0 = p->c_ptr = (d+num);
974 balance(0);
975 return;
977 i = (Length - Margin) / Dblspace;
978 do {
979 (void) readbuf(&Bufptr, i, p++);
980 } while (++colno < Ncols);
984 static void
985 balance(int bline) /* line balancing for last page */
987 wchar_t *s = Buffer;
988 COLP p = Colpts;
989 int colno = 0;
990 int j;
991 int c;
992 int l;
993 int lines;
995 if (!fold) {
996 c = bline % Ncols;
997 l = (bline + Ncols - 1)/Ncols;
998 bline = 0;
999 do {
1000 for (j = 0; j < l; ++j)
1001 while (*s++ != '\n')
1003 (++p)->c_lno = Lnumb + (bline += l);
1004 p->c_ptr0 = p->c_ptr = s;
1005 if (++colno == c)
1006 --l;
1007 } while (colno < Ncols - 1);
1008 } else {
1009 lines = readbuf(&s, 0, 0);
1010 l = (lines + Ncols - 1)/Ncols;
1011 if (l > ((Length - Margin) / Dblspace)) {
1012 l = (Length - Margin) / Dblspace;
1013 c = Ncols;
1014 } else {
1015 c = lines % Ncols;
1017 s = Buffer;
1018 do {
1019 (void) readbuf(&s, l, p++);
1020 if (++colno == c)
1021 --l;
1022 } while (colno < Ncols);
1023 Bufptr = s;
1028 static int
1029 readbuf(wchar_t **s, int lincol, COLP p)
1031 int lines = 0;
1032 int chars = 0;
1033 int width;
1034 int nls = 0;
1035 int move;
1036 int skip = 0;
1037 int decr = 0;
1039 width = (Ncols == 1) ? Linew : Colw;
1040 while (**s != WEOF) {
1041 switch (**s) {
1042 case '\n':
1043 lines++; nls++; chars = 0; skip = 0;
1044 break;
1046 case '\b':
1047 case ESC:
1048 if (chars) chars--;
1049 break;
1051 case '\t':
1052 move = Itabn - ((chars + Itabn) % Itabn);
1053 move = (move < width-chars) ? move :
1054 width-chars;
1055 chars += move;
1056 /* FALLTHROUGH */
1058 default:
1059 if (isascii(**s)) {
1060 if (isprint(**s))
1061 chars++;
1062 } else if (iswprint(**s)) {
1063 chars += wcwidth(**s);
1066 if (chars > width) {
1067 lines++;
1068 skip++;
1069 decr++;
1070 chars = 0;
1072 if (lincol && lines == lincol) {
1073 (p+1)->c_lno = p->c_lno + nls;
1074 (++p)->c_skip = skip;
1075 if (**s == '\n') (*s)++;
1076 p->c_ptr0 = p->c_ptr = (wchar_t *)*s;
1077 return (0);
1079 if (decr)
1080 decr = 0;
1081 else
1082 (*s)++;
1084 return (lines);
1088 static wint_t
1089 get(int colno)
1091 static int peekc = 0;
1092 COLP p;
1093 FILS *q;
1094 int c;
1095 wchar_t wc, w;
1097 if (peekc) {
1098 peekc = 0;
1099 wc = Etabc;
1100 } else if (Buffer) {
1101 p = &Colpts[colno];
1102 if (p->c_ptr >= (p+1)->c_ptr0)
1103 wc = WEOF;
1104 else if ((wc = *p->c_ptr) != WEOF)
1105 ++p->c_ptr;
1106 if (fold && wc == WEOF)
1107 Fcol[colno].eof = 1;
1108 } else if ((wc =
1109 (q = &Files[Multi == 'a' ? 0 : colno])->f_nextc) == WEOF) {
1110 for (q = &Files[Nfiles]; --q >= Files && q->f_nextc == WEOF; )
1112 if (q >= Files)
1113 wc = '\n';
1114 } else {
1115 errno = 0;
1116 w = _fgetwc_pr(q->f_f, &c);
1117 if (w == WEOF && errno == EILSEQ) {
1118 q->f_nextc = (wchar_t)c;
1119 } else {
1120 q->f_nextc = w;
1124 if (Etabn != 0 && wc == Etabc) {
1125 ++Inpos;
1126 peekc = ETABS;
1127 wc = ' ';
1128 return (C = wc);
1131 if (wc == WEOF)
1132 return (C = wc);
1134 if (isascii(wc)) {
1135 if (isprint(wc)) {
1136 Inpos++;
1137 return (C = wc);
1139 } else if (iswprint(wc)) {
1140 Inpos += wcwidth(wc);
1141 return (C = wc);
1144 switch (wc) {
1145 case '\b':
1146 case ESC:
1147 if (Inpos > 0)
1148 --Inpos;
1149 break;
1150 case '\f':
1151 if (Ncols == 1)
1152 break;
1153 wc = '\n';
1154 /* FALLTHROUGH */
1155 case '\n':
1156 case '\r':
1157 Inpos = 0;
1158 break;
1160 return (C = wc);
1164 static int
1165 put(wchar_t wc)
1167 int move = 0;
1168 int width = Colw;
1169 int sp = Lcolpos;
1171 if (fold && Ncols == 1)
1172 width = Linew;
1174 switch (wc) {
1175 case ' ':
1176 /* If column not full or this is separator char */
1177 if ((!fold && Ncols < 2) || (Lcolpos < width) ||
1178 ((Sepc == wc) && (Lcolpos == width))) {
1179 ++Nspace;
1180 ++Lcolpos;
1182 if (fold && sp == Lcolpos)
1183 if (Lcolpos >= width)
1184 return (1);
1186 return (0);
1188 case '\t':
1189 if (Itabn == 0)
1190 break;
1192 /* If column not full or this is separator char */
1193 if ((Lcolpos < width) ||
1194 ((Sepc == wc) && (Lcolpos == width))) {
1195 move = Itabn - ((Lcolpos + Itabn) % Itabn);
1196 move = (move < width-Lcolpos) ? move : width-Lcolpos;
1197 Nspace += move;
1198 Lcolpos += move;
1200 if (fold && sp == Lcolpos)
1201 if (Lcolpos >= width)
1202 return (1);
1203 return (0);
1205 case '\b':
1206 if (Lcolpos == 0)
1207 return (0);
1208 if (Nspace > 0) {
1209 --Nspace;
1210 --Lcolpos;
1211 return (0);
1213 if (Lcolpos > Pcolpos) {
1214 --Lcolpos;
1215 return (0);
1218 /*FALLTHROUGH*/
1220 case ESC:
1221 move = -1;
1222 break;
1224 case '\n':
1225 ++Line;
1227 /*FALLTHROUGH*/
1229 case '\r':
1230 case '\f':
1231 Pcolpos = 0;
1232 Lcolpos = 0;
1233 Nspace = 0;
1234 Outpos = 0;
1235 /* FALLTHROUGH */
1236 default:
1237 if (isascii(wc)) {
1238 if (isprint(wc))
1239 move = 1;
1240 else
1241 move = 0;
1242 } else if (iswprint(wc)) {
1243 move = wcwidth(wc);
1244 } else {
1245 move = 0;
1247 break;
1249 if (Page < Fpage)
1250 return (0);
1251 if (Lcolpos > 0 || move > 0)
1252 Lcolpos += move;
1254 putspace();
1256 /* If column not full or this is separator char */
1257 if ((!fold && Ncols < 2) || (Lcolpos <= width) ||
1258 ((Sepc == wc) && (Lcolpos > width))) {
1259 (void) fputwc(wc, stdout);
1260 Outpos += move;
1261 Pcolpos = Lcolpos;
1264 if (fold && Lcolpos > width)
1265 return (1);
1267 return (0);
1271 static void
1272 putspace(void)
1274 int nc = 0;
1276 for (; Nspace > 0; Outpos += nc, Nspace -= nc) {
1277 #ifdef XPG4
1278 /* XPG4: -i: replace multiple SPACE chars with tab chars */
1279 if ((Nspace >= 2 && Itabn > 0 &&
1280 Nspace >= (nc = Itabn - Outpos % Itabn)) && !fold) {
1281 #else
1282 /* Solaris: -i: replace white space with tab chars */
1283 if ((Itabn > 0 && Nspace >= (nc = Itabn - Outpos % Itabn)) &&
1284 !fold) {
1285 #endif
1286 (void) fputwc(Itabc, stdout);
1287 } else {
1288 nc = 1;
1289 (void) putchar(' ');
1295 static void
1296 unget(int colno)
1298 if (Buffer) {
1299 if (*(Colpts[colno].c_ptr-1) != '\t')
1300 --(Colpts[colno].c_ptr);
1301 if (Colpts[colno].c_lno)
1302 Colpts[colno].c_lno--;
1303 } else {
1304 if ((Multi == 'm' && colno == 0) || Multi != 'm')
1305 if (Lnumb && !foldcol)
1306 Lnumb--;
1307 colno = (Multi == 'a') ? 0 : colno;
1308 (void) ungetwc(Files[colno].f_nextc, Files[colno].f_f);
1309 Files[colno].f_nextc = C;
1315 * Defer message about failure to open file to prevent messing up
1316 * alignment of page with tear perforations or form markers.
1317 * Treat empty file as special case and report as diagnostic.
1320 static FILE *
1321 mustopen(char *s, FILS *f)
1323 char *empty_file_msg = gettext("%s -- empty file");
1324 int c;
1326 if (*s == '\0') {
1327 f->f_name = STDINNAME();
1328 f->f_f = stdin;
1329 } else if ((f->f_f = fopen(f->f_name = s, "r")) == NULL) {
1330 s = ffiler(f->f_name);
1331 s = strcpy((char *)getspace((UNS) strlen(s) + 1), s);
1333 if (f->f_f != NULL) {
1334 errno = 0;
1335 f->f_nextc = _fgetwc_pr(f->f_f, &c);
1336 if (f->f_nextc != WEOF) {
1337 return (f->f_f);
1338 } else { /* WEOF */
1339 if (errno == EILSEQ) {
1340 f->f_nextc = (wchar_t)c;
1341 return (f->f_f);
1343 if (Multi == 'm')
1344 return (f->f_f);
1346 (void) sprintf(s = (char *)getspace((UNS) strlen(f->f_name)
1347 + 1 + (UNS) strlen(empty_file_msg)),
1348 empty_file_msg, f->f_name);
1349 (void) fclose(f->f_f);
1351 Error = 1;
1352 if (Report)
1353 if (Ttyout) { /* accumulate error reports */
1354 Lasterr = Lasterr->e_nextp =
1355 (ERR *) getspace((UNS) sizeof (ERR));
1356 Lasterr->e_nextp = NULL;
1357 Lasterr->e_mess = s;
1358 } else { /* ok to print error report now */
1359 cerror(s);
1360 (void) putc('\n', stderr);
1362 return (NULL);
1366 static ANY *
1367 getspace(UNS n)
1369 ANY *t;
1371 if ((t = (ANY *) malloc(n)) == NULL)
1372 die("out of space");
1373 return (t);
1377 static void
1378 die(char *s)
1380 ++Error;
1381 errprint();
1382 cerror(s);
1383 (void) putc('\n', stderr);
1384 exit(1);
1386 /*NOTREACHED*/
1390 static void
1391 errprint() /* print accumulated error reports */
1393 (void) fflush(stdout);
1394 for (; Err != NULL; Err = Err->e_nextp) {
1395 cerror(Err->e_mess);
1396 (void) putc('\n', stderr);
1398 done();
1402 static void
1403 fixtty()
1405 struct stat sbuf;
1407 setbuf(stdout, obuf);
1408 if (signal(SIGINT, SIG_IGN) != SIG_IGN)
1409 (void) signal(SIGINT, onintr);
1410 if (Ttyout = ttyname(fileno(stdout))) { /* is stdout a tty? */
1411 (void) stat(Ttyout, &sbuf);
1412 Mode = sbuf.st_mode; /* save permissions */
1413 (void) chmod(Ttyout, (S_IREAD|S_IWRITE));
1418 static void
1419 onintr()
1421 ++Error;
1422 errprint();
1423 _exit(1);
1427 static char *
1428 GETDATE() /* return date file was last modified */
1430 static char *now = NULL;
1431 static struct stat sbuf;
1432 static struct stat nbuf;
1434 if (Nfiles > 1 || Files->f_name == nulls) {
1435 if (now == NULL) {
1436 (void) time(&nbuf.st_mtime);
1437 (void) cftime(time_buf,
1438 dcgettext(NULL, FORMAT, LC_TIME),
1439 &nbuf.st_mtime);
1440 now = time_buf;
1442 return (now);
1443 } else {
1444 (void) stat(Files->f_name, &sbuf);
1445 (void) cftime(time_buf, dcgettext(NULL, FORMAT, LC_TIME),
1446 &sbuf.st_mtime);
1447 return (time_buf);
1452 static char *
1453 ffiler(char *s)
1455 static char buf[100];
1457 (void) sprintf(buf, gettext("can't open %s"), s);
1458 return (buf);
1462 static void
1463 usage(int rc)
1465 (void) fprintf(stderr, gettext(
1466 "usage: pr [-# [-w #] [-a]] [-e[c][#]] [-i[c][#]] [-drtfp] [-n[c][#]] \\\n"
1467 " [-o #] [-l #] [-s[char]] [-h header] [-F] [+#] [file ...]\n\n"
1468 " pr [-m [-w #]] [-e[c][#]] [-i[c][#]] [-drtfp] [-n[c][#]] [-0 #] \\\n"
1469 " [-l #] [-s[char]] [-h header] [-F] [+#] file1 file2 ...\n"
1471 exit(rc);
1474 static wint_t
1475 _fgetwc_pr(FILE *f, int *ic)
1477 int i;
1478 int len;
1479 char mbuf[MB_LEN_MAX];
1480 int c;
1481 wchar_t wc;
1483 c = getc(f);
1485 if (c == EOF)
1486 return (WEOF);
1487 if (mbcurmax == 1 || isascii(c)) {
1488 return ((wint_t)c);
1490 mbuf[0] = (char)c;
1491 for (i = 1; i < mbcurmax; i++) {
1492 c = getc(f);
1493 if (c == EOF) {
1494 break;
1495 } else {
1496 mbuf[i] = (char)c;
1499 mbuf[i] = 0;
1501 len = mbtowc(&wc, mbuf, i);
1502 if (len == -1) {
1503 /* Illegal character */
1504 /* Set the first byte to *ic */
1505 *ic = mbuf[0];
1506 /* Push back remaining characters */
1507 for (i--; i > 0; i--) {
1508 (void) ungetc(mbuf[i], f);
1510 errno = EILSEQ;
1511 return (WEOF);
1512 } else {
1513 /* Push back over-read characters */
1514 for (i--; i >= len; i--) {
1515 (void) ungetc(mbuf[i], f);
1517 return ((wint_t)wc);
1521 static size_t
1522 freadw(wchar_t *ptr, size_t nitems, FILE *f)
1524 size_t i;
1525 size_t ret;
1526 int c;
1527 wchar_t *p;
1528 wint_t wc;
1530 if (feof(f)) {
1531 return (0);
1534 p = ptr;
1535 ret = 0;
1536 for (i = 0; i < nitems; i++) {
1537 errno = 0;
1538 wc = _fgetwc_pr(f, &c);
1539 if (wc == WEOF) {
1540 if (errno == EILSEQ) {
1541 *p++ = (wchar_t)c;
1542 ret++;
1543 } else {
1544 return (ret);
1546 } else {
1547 *p++ = (wchar_t)wc;
1548 ret++;
1551 return (ret);