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