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
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;
109
110 /*
111 * Global data.
112 */
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 = stdin;
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;
162
163 /*
164 * Function prototypes.
165 */
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 *);
189
190
191 int
main(int argc,char ** argv)192 main(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
238 static int
findopt(int argc,char ** argv)239 findopt(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
627 static int
print(char * name)628 print(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
677 static void
putpage()678 putpage()
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
745 static void
foldpage()746 foldpage()
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
863 static void
nexbuf()864 nexbuf()
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)
893 die("page-buffer overflow");
894 }
895 Inpos++;
896 Error++;
897 return;
898 } else {
899 /* Real EOF */
900 for (*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
938 static void
foldbuf()939 foldbuf()
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
983 static void
balance(int bline)984 balance(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
1027 static int
readbuf(wchar_t ** s,int lincol,COLP p)1028 readbuf(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
1087 static wint_t
get(int colno)1088 get(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
1163 static int
put(wchar_t wc)1164 put(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
1270 static void
putspace(void)1271 putspace(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
1294 static void
1295 unget(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
1319 static FILE *
1320 mustopen(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
1365 static ANY *
1366 getspace(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
1376 static void
1377 die(char *s)
1378 {
1379 ++Error;
1380 errprint();
1381 cerror(s);
1382 (void) putc('\n', stderr);
1383 exit(1);
1384
1385 /*NOTREACHED*/
1386 }
1387
1388
1389 static void
1390 errprint() /* 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
1401 static void
1402 fixtty()
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
1417 static void
1418 onintr()
1419 {
1420 ++Error;
1421 errprint();
1422 _exit(1);
1423 }
1424
1425
1426 static char *
1427 GETDATE() /* 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
1451 static char *
1452 ffiler(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
1461 static void
1462 usage(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
1473 static 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
1520 static size_t
1521 freadw(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