1 #ifndef lint 2 static char Notice[] = "Copyright (c) 1985 Adobe Systems Incorporated"; 3 static char *ORCSID="$Header: pscomm.bsd,v 2.1 85/11/24 11:50:16 shore Rel $"; 4 static char *RCSID="$Header: pscomm.c,v 1.4 87/10/31 20:42:02 cuong Exp $"; 5 #endif 6 /* pscomm.c 7 * 8 * Copyright (C) 1985 Adobe Systems Incorporated 9 * 10 * 4.2BSD lpr/lpd communications filter for PostScript printers 11 * (formerly "psif" in TranScript release 1.0) 12 * 13 * pscomm is the general communications filter for 14 * sending files to a PostScript printer (e.g., an Apple LaserWriter, 15 * QMS PostScript printer, or Linotype PostScript typesetter) 16 * via RS232 lines. It does page accounting, error handling/reporting, 17 * job logging, banner page printing, etc. 18 * It observes (parts of) the PostScript file structuring conventions. 19 * In particular, it distinguishes between PostScript files (beginning 20 * with the "%!" magic number) -- which are shipped to the printer -- 21 * and text files (no magic number) which are formatted and listed 22 * on the printer. Files which begin with "%!PS-Adobe-" may be 23 * page-reversed if the target printer has that option specified. 24 * 25 * depending on the values of BANNERFIRST and BANNERLAST, 26 * pscomm looks for a file named ".banner", (created by the "of" filter) 27 * in the current working directory and ships it to the printer also. 28 * 29 * pscomm gets called with: 30 * stdin == the file to print (may be a pipe!) 31 * stdout == the printer 32 * stderr == the printer log file 33 * cwd == the spool directory 34 * argv == set up by interface shell script: 35 * filtername -P printer 36 * -p filtername 37 * [-r] (don't ever reverse) 38 * -n login 39 * -h host 40 * [accntfile] 41 * 42 * environ == various environment variable effect behavior 43 * VERBOSELOG - do verbose log file output 44 * BANNERFIRST - print .banner before job 45 * BANNERLAST - print .banner after job 46 * REVERSE - page reversal filter program 47 * (no reversal if null or missing) 48 * PSLIBDIR - transcript library directory 49 * PSTEXT - simple text formatting filter 50 * JOBOUTPUT - file for actual printer stream 51 * output (if defined) 52 * 53 * pscomm depends on certain additional features of the 4.2BSD spooling 54 * architecture. In particular it assumes that the printer status file 55 * has the default name (./status) and it uses this file to communicate 56 * printer error status information to the user -- the contents of the 57 * status file gets incorporated in "lpq" and "lpc status" messages. 58 * 59 * Edit History: 60 * Andrew Shore: Sat Nov 16 11:59:58 1985 61 * End Edit History. 62 * 63 * RCSLOG: 64 * $Log: pscomm.c,v $ 65 * Revision 1.4 87/10/31 20:42:02 cuong 66 * Two changes: 67 * 1. Make sender wait for listener's signal when requesting initial 68 * pagecount. The problem is with short jobs & fast machines; 69 * the sender will merrily finish the job and send an asynchronous 70 * status request the response to which will clobber the listener's 71 * input stream. 72 * 2. Make sender sleep(1) just after sending the job-finish EOF to give 73 * the LaserWriter a chance to update its status. 74 * 75 * Revision 1.3 87/10/03 16:42:47 cuong 76 * Takes care of improper handling of abnormal exits when 77 * accounting is turned on. Pagecount information was again 78 * waited on abortively. Now, it's fixed, no? 79 * 80 * Revision 1.2 87/09/20 19:03:25 cuong 81 * Fixed bug: 82 * Symptom: pagecount accounting turned on, then pscomms will hang. 83 * Reason: old pscomm listener assumed the final pagecount information 84 * will come after the ctrl-d; this is not true. Hence it 85 * hangs waiting after the ctrl-d is received. 86 * Fix: while waiting for ctrl-d, the pscomm listener must also 87 * scan for the pattern %%[ pagecount: %d ]%%, and save 88 * this in the pbuf[] array if found. 89 * Cuong 90 * 91 * Revision 1.1 87/06/13 19:26:31 cuong 92 * Initial revision 93 * 94 * Revision 2.1 85/11/24 11:50:16 shore 95 * Product Release 2.0 96 * 97 * Revision 1.1 85/11/20 00:35:21 shore 98 * Initial revision 99 * 100 * Revision 1.2 85/05/14 11:25:29 shore 101 * better support for BANNERLAST, still buggy though 102 * 103 * 104 */ 105 106 #include <ctype.h> 107 #include <setjmp.h> 108 #include <sgtty.h> 109 #include <signal.h> 110 #include <stdio.h> 111 #include <strings.h> 112 113 #include <sys/file.h> 114 #include <sys/ioctl.h> 115 #include <sys/time.h> 116 #include <sys/resource.h> 117 #include <sys/wait.h> 118 #include <sys/types.h> 119 #include <sys/stat.h> 120 121 #include "transcript.h" 122 #include "psspool.h" 123 124 #ifdef BDEBUG 125 #define debugp(x) \ 126 { \ 127 fprintf(stderr, "(pid %d) ", getpid()); \ 128 fprintf x; \ 129 (void) fflush(stderr); \ 130 } 131 #else 132 #define debugp(x) 133 #endif BDEBUG 134 135 /* 136 * the following string is sent to the printer when we want it to 137 * report its current pagecount (for accounting) 138 */ 139 140 private char *getpages = "\n(%%%%[ pagecount: )print \ 141 statusdict/pagecount get exec( )cvs print( ]%%%%)= flush\n%s"; 142 143 private jmp_buf waitonreverse, startstatus, dwait, sendint; 144 145 private char *prog; /* invoking program name */ 146 private char *name; /* user login name */ 147 private char *host; /* host name */ 148 private char *pname; /* printer name */ 149 private char *accountingfile; /* file for printer accounting */ 150 private int doactng; /* true if we can do accounting */ 151 private int progress, oldprogress; /* finite progress counts */ 152 private int getstatus = FALSE; 153 private int revdone = FALSE; /* reverse done, send new */ 154 private int goahead = FALSE; /* got initial status back */ 155 private int gotemt = FALSE; /* got ^D ack from listener */ 156 private int sendend = TRUE; /* send an ^D */ 157 158 private char *bannerfirst; 159 private char *bannerlast; 160 private char *verboselog; 161 private char *reverse; 162 private int BannerFirst; 163 private int BannerLast; 164 private int VerboseLog; 165 166 private int fpid = 0; /* formatter pid */ 167 private int cpid = 0; /* listener pid */ 168 169 private int intrup = FALSE; /* interrupt flag */ 170 171 private char abortbuf[] = "\003"; /* ^C abort */ 172 private char statusbuf[] = "\024"; /* ^T status */ 173 private char eofbuf[] = "\004"; /* ^D end of file */ 174 175 private char EOFerr[] = "%s: unexpected EOF from printer (%s)!\n"; 176 177 /* global file descriptors (avoid stdio buffering!) */ 178 private int fdsend; /* to printer (from stdout) */ 179 private int fdlisten; /* from printer (same tty line) */ 180 private int fdinput; /* file to print (from stdin) */ 181 182 private FILE *jobout; /* special printer output log */ 183 184 private int flg = FREAD|FWRITE; /* ioctl FLUSH arg */ 185 186 187 extern char *getenv(); 188 189 private VOID intinit(); 190 private VOID intsend(); 191 private VOID intwait(); 192 private VOID salarm(); 193 private VOID walarm(); 194 private VOID falarm(); 195 private VOID reverseready(); 196 private VOID readynow(); 197 private VOID emtdead(); 198 private VOID emtdone(); 199 private char *FindPattern(); 200 201 #define SENDALARM 90 202 #define WAITALARM 30 203 204 main(argc,argv) 205 int argc; 206 char *argv[]; 207 { 208 register char *cp; 209 register int cnt, wc; 210 register char *mbp; 211 212 char **av; 213 long clock; /* for log timestamp */ 214 char magic[11]; /* first few bytes of stdin ?magic number and type */ 215 int noReverse = 0; /* flag if we should never page reverse */ 216 int canReverse = 0;/* flag if we can page-reverse the ps file */ 217 int reversing = 0; 218 FILE *streamin; 219 220 char mybuf[BUFSIZ]; 221 int wpid; 222 union wait status; 223 int fdpipe[2]; 224 int format = 0; 225 int i; 226 227 VOIDC signal(SIGINT, intinit); 228 VOIDC signal(SIGHUP, intinit); 229 VOIDC signal(SIGQUIT, intinit); 230 VOIDC signal(SIGTERM, intinit); 231 232 /* parse command-line arguments */ 233 /* the argv (see header comments) comes from the spooler daemon */ 234 /* itself, so it should be canonical, but at least one 4.2-based */ 235 /* system uses -nlogin -hhost (insead of -n login -h host) so I */ 236 /* check for both */ 237 238 av = argv; 239 prog = *av; 240 241 while (--argc) { 242 if (*(cp = *++av) == '-') { 243 switch (*(cp + 1)) { 244 case 'P': /* printer name */ 245 argc--; 246 pname = *(++av); 247 break; 248 249 case 'n': /* user name */ 250 argc--; 251 name = *(++av); 252 break; 253 254 case 'h': /* host */ 255 argc--; 256 host = *(++av); 257 break; 258 259 case 'p': /* prog */ 260 argc--; 261 prog = *(++av); 262 break; 263 264 case 'r': /* never reverse */ 265 argc--; 266 noReverse = 1; 267 break; 268 269 default: /* unknown */ 270 fprintf(stderr,"%s: unknown option: %s\n",prog,cp); 271 break; 272 } 273 } 274 else 275 accountingfile = cp; 276 } 277 278 debugp((stderr,"args: %s %s %s %s\n",prog,host,name,accountingfile)); 279 280 /* do printer-specific options processing */ 281 282 VerboseLog = 1; 283 BannerFirst = BannerLast = 0; 284 reverse = NULL; 285 if (bannerfirst=envget("BANNERFIRST")) { 286 BannerFirst=atoi(bannerfirst); 287 } 288 if (bannerlast=envget("BANNERLAST")) { 289 BannerLast=atoi(bannerlast); 290 } 291 if (verboselog=envget("VERBOSELOG")) { 292 VerboseLog=atoi(verboselog); 293 } 294 if (!noReverse) { 295 reverse=envget("REVERSE"); /* name of the filter itself */ 296 } 297 298 if (VerboseLog) { 299 fprintf(stderr, "%s: %s:%s %s start - %s", prog, host, name, pname, 300 (VOIDC time(&clock), ctime(&clock))); 301 VOIDC fflush(stderr); 302 } 303 debugp((stderr,"%s: pid %d ppid %d\n",prog,getpid(),getppid())); 304 debugp((stderr,"%s: options BF %d BL %d VL %d R %s\n",prog,BannerFirst, 305 BannerLast, VerboseLog, ((reverse == NULL) ? "norev": reverse))); 306 307 /* IMPORTANT: in the case of cascaded filters, */ 308 /* stdin may be a pipe! (and hence we cannot seek!) */ 309 310 if ((cnt = read(fileno(stdin),magic,11)) != 11) goto badfile; 311 debugp((stderr,"%s: magic number is %11.11s\n",prog,magic)); 312 streamin = stdin; 313 314 if (strncmp(magic,"%!PS-Adobe-",11) == 0) { 315 canReverse = TRUE; 316 goto go_ahead; 317 } 318 else if (strncmp(magic,"%!",2) == 0) { 319 canReverse = FALSE; 320 goto go_ahead; 321 } 322 323 /* here is where you might test for other file type 324 * e.g., PRESS, imPRESS, DVI, Mac-generated, etc. 325 */ 326 327 /* final sanity check on the text file, to guard 328 * against arbitrary binary data 329 */ 330 331 for (i = 0; i < 11; i++) { 332 if (!isascii(magic[i]) || (!isprint(magic[i]) && !isspace(magic[i]))){ 333 fprintf(stderr,"%s: spooled binary file rejected\n",prog); 334 VOIDC fflush(stderr); 335 sprintf(mybuf,"%s/bogusmsg.ps",envget("PSLIBDIR")); 336 if ((streamin = freopen(mybuf,"r",stdin)) == NULL) { 337 exit(THROW_AWAY); 338 } 339 format = 1; 340 goto lastchance; 341 } 342 } 343 344 goto format_text; 345 346 badfile: 347 fprintf(stderr,"%s: bad magic number, EOF\n", prog); 348 VOIDC fflush(stderr); 349 exit(THROW_AWAY); 350 351 format_text: 352 /* exec dumb formatter to make a listing */ 353 debugp((stderr,"formatting\n")); 354 format = 1; 355 VOIDC lseek(0,0L,0); 356 rewind(stdin); 357 if (pipe (fdpipe)) pexit2(prog, "format pipe",THROW_AWAY); 358 if ((fpid = fork()) < 0) pexit2(prog, "format fork",THROW_AWAY); 359 if (fpid == 0) { /* child */ 360 /* set up child stdout to feed parent stdin */ 361 if (close(1) || (dup(fdpipe[1]) != 1) 362 || close(fdpipe[1]) || close(fdpipe[0])) { 363 pexit2(prog, "format child",THROW_AWAY); 364 } 365 execl(envget("PSTEXT"), "pstext", pname, 0); 366 pexit2(prog,"format exec",THROW_AWAY); 367 } 368 /* parent continues */ 369 /* set up stdin to be pipe */ 370 if (close(0) || (dup(fdpipe[0]) != 0) 371 || close(fdpipe[0]) || close(fdpipe[1])) { 372 pexit2(prog, "format parent",THROW_AWAY); 373 } 374 375 /* fall through to spooler with new stdin */ 376 /* can't seek here but we should be at the right place */ 377 streamin = fdopen(0,"r"); 378 canReverse = TRUE; /* we know we can reverse pstext output */ 379 380 go_ahead: 381 382 /* do page reversal if specified */ 383 if (reversing = ((reverse != NULL) && canReverse)) { 384 debugp((stderr,"reversing\n")); 385 VOIDC setjmp(waitonreverse); 386 if (!revdone) { 387 VOIDC signal(SIGEMT, reverseready); 388 if (pipe (fdpipe)) pexit2(prog, "reverse pipe", THROW_AWAY); 389 if ((fpid = fork()) < 0) pexit2(prog, "reverse fork", THROW_AWAY); 390 if (fpid == 0) { /* child */ 391 /* set up child stdout to feed parent stdin */ 392 if (close(1) || (dup(fdpipe[1]) != 1) 393 || close(fdpipe[1]) || close(fdpipe[0])) { 394 pexit2(prog, "reverse child", THROW_AWAY); 395 } 396 execl(reverse, "psrv", pname, 0); 397 pexit2(prog,"reverse exec",THROW_AWAY); 398 } 399 /* parent continues */ 400 if (close(0) || (dup(fdpipe[0]) != 0) 401 || close(fdpipe[0]) || close(fdpipe[1])) { 402 pexit2(prog, "reverse parent", THROW_AWAY); 403 } 404 /* fall through to spooler with new stdin */ 405 /* VOIDC lseek(0,0L,0); */ 406 streamin = fdopen(0,"r"); 407 408 while (TRUE) { 409 if (revdone) break; 410 pause(); 411 } 412 } 413 VOIDC signal(SIGEMT, SIG_IGN); 414 debugp((stderr,"%s: reverse feeding\n",prog)); 415 } 416 417 lastchance:; 418 419 /* establish an input stream from the printer -- 420 * the printcap entry specifies "rw" and we get 421 * invoked with stdout == the device, so we 422 * dup stdout, and reopen it for reading; 423 * this seems to work fine... 424 */ 425 426 fdinput = fileno(streamin); /* the file to print */ 427 fdsend = fileno(stdout); /* the printer (write) */ 428 429 if ((fdlisten = dup(fdsend)) < 0) /* the printer (read) */ 430 pexit(prog, THROW_AWAY); 431 432 doactng = name && accountingfile && (access(accountingfile, W_OK) == 0); 433 434 /* get control of the "status" message file. 435 * we copy the current one to ".status" so we can restore it 436 * on exit (to be clean). 437 * Our ability to use this is publicized nowhere in the 438 * 4.2 lpr documentation, so things might go bad for us. 439 * We will use it to report that printer errors condition 440 * has been detected, and the printer should be checked. 441 * Unfortunately, this notice may persist through 442 * the end of the print job, but this is no big deal. 443 */ 444 BackupStatus(".status","status"); 445 446 if ((cpid = fork()) < 0) pexit(prog, THROW_AWAY); 447 else if (cpid) {/* parent - sender */ 448 VOIDC setjmp(sendint); 449 450 if (intrup) { 451 /* we only get here if there was an interrupt */ 452 453 fprintf(stderr,"%s: abort (sending)\n",prog); 454 VOIDC fflush(stderr); 455 456 /* flush and restart output to printer, 457 * send an abort (^C) request and wait for the job to end 458 */ 459 if (ioctl(fdsend, TIOCFLUSH,&flg) || ioctl(fdsend, TIOCSTART,&flg) 460 || (write(fdsend, abortbuf, 1) != 1)) { 461 RestoreStatus(); 462 pexit(prog,THROW_AWAY); 463 } 464 debugp((stderr,"%s: sent interrupt - waiting\n",prog)); 465 intrup = 0; 466 goto donefile; /* sorry ewd! */ 467 } 468 469 VOIDC signal(SIGINT, intsend); 470 VOIDC signal(SIGHUP, intsend); 471 VOIDC signal(SIGQUIT, intsend); 472 VOIDC signal(SIGTERM, intsend); 473 VOIDC signal(SIGEMT, readynow); 474 475 progress = oldprogress = 0; /* finite progress on sender */ 476 getstatus = FALSE; /* prime the pump for fun FALSE; */ 477 478 VOIDC signal(SIGALRM, salarm); /* sending phase alarm */ 479 VOIDC alarm(SENDALARM); /* schedule an alarm/timeout */ 480 481 /* loop, trying to send a ^T to get printer status 482 * We will hang here (and post a message) if the printer 483 * is unreachable. Eventually, we will succeed, the listener 484 * will see the status report, signal us, and we will proceed 485 */ 486 487 cnt = 1; 488 VOIDC setjmp(startstatus); 489 490 while (TRUE) { 491 if (goahead) break; 492 debugp((stderr,"%s: get start status\n",prog)); 493 VOIDC write(fdsend, statusbuf, 1); 494 pause(); 495 if (goahead) break; 496 /* if we get here, we got an alarm */ 497 ioctl(fdsend, TIOCFLUSH, &flg); 498 ioctl(fdsend, TIOCSTART, &flg); 499 sprintf(mybuf, "Not Responding for %d minutes", 500 (cnt * SENDALARM+30)/60); 501 Status(mybuf); 502 alarm(SENDALARM); 503 cnt++; 504 } 505 506 VOIDC signal(SIGEMT, emtdead); /* now EMTs mean printer died */ 507 508 RestoreStatus(); 509 debugp((stderr,"%s: printer responding\n",prog)); 510 511 /* initial page accounting (BEFORE break page) */ 512 if (doactng) { 513 sprintf(mybuf, getpages, "\004"); 514 VOIDC write(fdsend, mybuf, strlen(mybuf)); 515 debugp((stderr, "%s: sent pagecount request\n", prog)); 516 progress++; 517 518 /* Sat Oct 31 17:51:45 PST 1987 519 * loop, waiting for the listener to signal initial pagecount is 520 * received. The problem is with fast machines and short jobs; 521 * if we don't loop here, we may finish the job and send another 522 * CTRL-T before the initial pagecount ever came back. The way 523 * the laserwriter behaves, this may result in a mix of pagecount 524 * data and status information like this: 525 * %%[ pagecount: %%[ status: busy; source: serial 25 ]%% 24418 ]%% 526 * 527 * That is really silly - Cuong 528 */ 529 530 VOIDC signal(SIGINT, intsend); 531 VOIDC signal(SIGHUP, intsend); 532 VOIDC signal(SIGQUIT, intsend); 533 VOIDC signal(SIGTERM, intsend); 534 VOIDC signal(SIGEMT, readynow); 535 536 progress = oldprogress = 0; /* finite progress on sender */ 537 getstatus = FALSE; /* prime the pump for fun FALSE; */ 538 539 VOIDC signal(SIGALRM, salarm); /* sending phase alarm */ 540 VOIDC alarm(SENDALARM); /* schedule an alarm/timeout */ 541 542 cnt = 1; goahead = FALSE; 543 VOIDC setjmp(startstatus); 544 545 while (TRUE) { 546 if (goahead) break; 547 pause(); 548 if (goahead) break; 549 /* if we get here, we got an alarm */ 550 ioctl(fdsend, TIOCFLUSH, &flg); 551 ioctl(fdsend, TIOCSTART, &flg); 552 sprintf(mybuf, "Not Responding for %d minutes", 553 (cnt * SENDALARM+30)/60); 554 Status(mybuf); 555 alarm(SENDALARM); 556 cnt++; 557 } 558 559 VOIDC signal(SIGEMT, emtdead); /* now EMTs mean printer died */ 560 561 RestoreStatus(); 562 debugp((stderr,"%s: sender received EMT (goahead) from listener\n",prog)); 563 } /* if (doactng) */ 564 565 /* initial break page ? */ 566 if (BannerFirst) { 567 SendBanner(); 568 progress++; 569 } 570 571 /* ship the magic number! */ 572 if ((!format) && (!reversing)) { 573 VOIDC write(fdsend,magic,11); 574 progress++; 575 } 576 577 /* now ship the rest of the file */ 578 579 VOIDC alarm(SENDALARM); /* schedule an alarm */ 580 581 while ((cnt = read(fdinput, mybuf, sizeof mybuf)) > 0) { 582 /* VOIDC alarm(SENDALARM); /* we made progress, reset alarm */ 583 if (intrup == TRUE) break; 584 585 /* get status every other time */ 586 if (getstatus) { 587 debugp((stderr,"%s: get periodic status\n",prog)); 588 VOIDC write(fdsend, statusbuf, 1); 589 getstatus = FALSE; 590 progress++; 591 } 592 mbp = mybuf; 593 while ((cnt > 0) && ((wc = write(fdsend, mbp, cnt)) != cnt)) { 594 /* this seems necessary but not sure why */ 595 if (wc < 0) { 596 fprintf(stderr,"%s: error writing to printer:\n",prog); 597 perror(prog); 598 RestoreStatus(); 599 sleep(10); 600 exit(TRY_AGAIN); 601 } 602 mbp += wc; 603 cnt -= wc; 604 progress++; 605 } 606 progress++; 607 } 608 if (cnt < 0) { 609 fprintf(stderr,"%s: error reading from stdin: \n", prog); 610 perror(prog); 611 RestoreStatus(); 612 sleep(10); 613 exit(TRY_AGAIN); /* kill the listener? */ 614 } 615 616 /* final break page ? */ 617 if (BannerLast) { 618 SendBanner(); 619 progress++; 620 } 621 622 donefile:; 623 624 sendend = 1; 625 626 VOIDC setjmp(dwait); 627 628 if (sendend && !gotemt) { 629 630 VOIDC signal(SIGEMT, emtdone); 631 632 debugp((stderr,"%s: done sending\n",prog)); 633 634 /* now send the PostScript EOF character */ 635 debugp((stderr,"%s: sending PostScript EOF\n",prog)); 636 VOIDC write(fdsend, eofbuf, 1); 637 sendend = 0; 638 progress++; 639 640 VOIDC signal(SIGINT, intwait); 641 VOIDC signal(SIGHUP, intwait); 642 VOIDC signal(SIGQUIT, intwait); 643 VOIDC signal(SIGTERM, intwait); 644 645 VOIDC signal(SIGALRM, walarm); 646 VOIDC alarm(WAITALARM); 647 getstatus = TRUE; 648 } 649 650 /* for very short jobs and very fast machines, 651 * we've experienced that the whole job is sent 652 * before the LaserWriter has a chance to update 653 * its status. Hence we may get a false idle 654 * status if we immediately send the statusbuf. 655 * 656 * Keep in mind that the LaserWriter status response 657 * is asynchronous to the datastream. 658 */ 659 sleep(1); 660 661 /* wait to sync with listener EMT signal 662 * to indicate it got an EOF from the printer 663 */ 664 while (TRUE) { 665 if (gotemt) break; 666 if (getstatus) { 667 debugp((stderr,"%s: get final status\n",prog)); 668 VOIDC write(fdsend, statusbuf, 1); 669 getstatus = FALSE; 670 } 671 debugp((stderr,"waiting e%d i%d %d %d\n", 672 gotemt,intrup,wpid,status)); 673 wpid = wait(&status); 674 if (wpid == -1) break; 675 } 676 677 /* final page accounting */ 678 if (doactng) { 679 sprintf(mybuf, getpages, "\004"); 680 VOIDC write(fdsend, mybuf, strlen(mybuf)); 681 debugp((stderr, "%s: sent pagecount request\n", prog)); 682 progress++; 683 } 684 685 /* wait for listener to die */ 686 VOIDC setjmp(dwait); 687 while ((wpid = wait(&status)) > 0); 688 VOIDC alarm(0); 689 VOIDC signal(SIGINT, SIG_IGN); 690 VOIDC signal(SIGHUP, SIG_IGN); 691 VOIDC signal(SIGQUIT, SIG_IGN); 692 VOIDC signal(SIGTERM, SIG_IGN); 693 VOIDC signal(SIGEMT, SIG_IGN); 694 debugp((stderr,"w2: s%lo p%d = p%d\n", status, wpid, cpid)); 695 696 if (VerboseLog) { 697 fprintf(stderr,"%s: end - %s",prog, 698 (VOIDC time(&clock),ctime(&clock))); 699 VOIDC fflush(stderr); 700 } 701 RestoreStatus(); 702 exit(0); 703 } 704 else {/* child - listener */ 705 register FILE *psin; 706 register int r; 707 708 char pbuf[BUFSIZ]; /* buffer for pagecount info */ 709 char *pb; /* pointer for above */ 710 int pc1, pc2; /* page counts before and after job */ 711 int sc; /* pattern match count for sscanf */ 712 char *outname; /* file name for job output */ 713 int havejobout = FALSE; /* flag if jobout != stderr */ 714 int ppid; /* parent process id */ 715 716 VOIDC signal(SIGINT, SIG_IGN); 717 VOIDC signal(SIGHUP, SIG_IGN); 718 VOIDC signal(SIGQUIT, SIG_IGN); 719 VOIDC signal(SIGTERM, SIG_IGN); 720 VOIDC signal(SIGALRM, SIG_IGN); 721 722 ppid = getppid(); 723 724 /* get jobout from environment if there, otherwise use stderr */ 725 if (((outname = envget("JOBOUTPUT")) == NULL) 726 || ((jobout = fopen(outname,"w")) == NULL)) { 727 jobout = stderr; 728 } 729 else havejobout = TRUE; 730 731 pc1 = pc2 = -1; /* bogus initial values */ 732 if ((psin = fdopen(fdlisten, "r")) == NULL) { 733 RestoreStatus(); 734 pexit(prog, THROW_AWAY); 735 } 736 737 /* listen for first status (idle?) */ 738 pb = pbuf; 739 *pb = '\0'; 740 while (TRUE) { 741 r = getc(psin); 742 if (r == EOF) { 743 fprintf(stderr, EOFerr, prog, "startup"); 744 VOIDC fflush(stderr); 745 sleep(20); /* printer may be coming up */ 746 /* RestoreStatus(); */ 747 /* exit(TRY_AGAIN); */ 748 } 749 if ((r & 0377) == '\n') break; /* newline */ 750 *pb++ = r; 751 } 752 *pb = 0; 753 debugp((stderr,"%s: initial status - %s\n",prog,pbuf)); 754 if (strcmp(pbuf, "%%[ status: idle ]%%\r") != 0) { 755 fprintf(stderr,"%s: initial status - %s\n",prog,pbuf); 756 VOIDC fflush(stderr); 757 } 758 759 /* flush input state and signal sender that we heard something */ 760 ioctl(fdlisten, TIOCFLUSH, &flg); 761 762 VOIDC kill(ppid,SIGEMT); 763 764 /* listen for first pagecount */ 765 if (doactng) { 766 pb = pbuf; 767 *pb = '\0'; 768 while (TRUE) { 769 r = getc(psin); 770 if (r == EOF) { 771 fprintf(stderr, EOFerr, prog, "accounting1"); 772 VOIDC fflush(stderr); 773 RestoreStatus(); 774 sleep(10); /* give interface a chance */ 775 exit(TRY_AGAIN); 776 } 777 if ((r&0377) == 004) break; /* PS_EOF */ 778 *pb++ = r; 779 } 780 *pb = '\0'; 781 782 if (pb = FindPattern(pb, pbuf, "%%[ pagecount: ")) { 783 sc = sscanf(pb, "%%%%[ pagecount: %d ]%%%%\r", &pc1); 784 } 785 if ((pb == NULL) || (sc != 1)) { 786 fprintf(stderr, "%s: accounting error 1 (%s)\n", prog,pbuf); 787 VOIDC fflush(stderr); 788 } 789 debugp((stderr,"%s: accounting 1 (%s)\n",prog,pbuf)); 790 791 /* flush input state and signal sender that we heard something */ 792 ioctl(fdlisten, TIOCFLUSH, &flg); 793 794 VOIDC kill(ppid,SIGEMT); 795 796 /* 797 Sun Sep 20 18:32:28 PDT 1987 798 The previous bug was that it was assumed the ctrl-d comes 799 before the final pagecount. This doesn't happen, and the 800 listener waits forever after a ctrl-d for a pagecount. 801 The fix is to clear out the pbuf[] buffer, then check for it 802 when we get to looking for the final pagecount. If it is 803 non-empty, we know we *already* read the final pagecount 804 *before* the ctrl-d, and use it, without waiting for 805 anything to come back from the printer. 806 */ 807 pbuf[0] = '\0'; 808 } 809 810 /* listen for the user job */ 811 while (TRUE) { 812 r = getc(psin); 813 debugp((stderr, "%s: listener got character \\%o '%c'\n", prog, r, r)); 814 if ((r&0377) == 004) break; /* PS_EOF */ 815 else if (r == EOF) { 816 VOIDC fclose(psin); 817 fprintf(stderr, EOFerr, prog, "job"); 818 VOIDC fflush(stderr); 819 RestoreStatus(); 820 VOIDC kill(ppid,SIGEMT); 821 exit(THROW_AWAY); 822 } 823 /* 824 Sun Sep 20 18:37:01 PDT 1987 825 GotChar() takes an addition argument: the pointer to the 826 pbuf[] buffer, and fills it with the final pagecount 827 information if that is received from the printer. 828 */ 829 GotChar(r, pbuf); 830 } 831 832 /* let sender know we saw the end of the job */ 833 /* sync - wait for sender to restart us */ 834 835 debugp((stderr,"%s: listener saw eof, signaling\n",prog)); 836 837 VOIDC kill(ppid,SIGEMT); 838 839 /* now get final page count */ 840 if (doactng) { 841 /* 842 Sun Sep 20 18:48:35 PDT 1987 843 We attempt to wait for the final pagecount only if it has *not* 844 been sent by the printer. It is the case that the final pagecount 845 is sent before the ctrl-d above, hence if we wait, it'll be forever. 846 Final pagecount information 'prematurely' received has already 847 been stored in pbuf[] iff pbuf[0] is non-null. 848 */ 849 850 if (pbuf[0] == '\0') { 851 debugp((stderr, "%s: waiting for pagecount\n", prog)); 852 pb = pbuf; 853 *pb = '\0'; /* ignore the previous pagecount */ 854 while (TRUE) { 855 r = getc(psin); 856 if (r == EOF) { 857 fprintf(stderr, EOFerr, prog, "accounting2"); 858 VOIDC fflush(stderr); 859 RestoreStatus(); 860 sleep(10); 861 exit(THROW_AWAY); /* what else to do? */ 862 } 863 if ((r&0377) == 004) break; /* PS_EOF */ 864 *pb++ = r; 865 } 866 *pb = '\0'; 867 } else { 868 pb = pbuf + strlen(pbuf) - 1; 869 } 870 debugp((stderr,"%s: accounting 2 (%s)\n",prog,pbuf)); 871 if (pb = FindPattern(pb, pbuf, "%%[ pagecount: ")) { 872 sc = sscanf(pb, "%%%%[ pagecount: %d ]%%%%\r", &pc2); 873 } 874 if ((pb == NULL) || (sc != 1)) { 875 fprintf(stderr, "%s: accounting error 2 (%s)\n", prog,pbuf); 876 VOIDC fflush(stderr); 877 } 878 else if ((pc2 < pc1) || (pc1 < 0) || (pc2 < 0)) { 879 fprintf(stderr,"%s: accounting error 3 %d %d\n", prog,pc1,pc2); 880 VOIDC fflush(stderr); 881 } 882 else if (freopen(accountingfile, "a", stdout) != NULL) { 883 printf("%7.2f\t%s:%s\n", (float)(pc2 - pc1), host, name); 884 VOIDC fclose(stdout); 885 /* 886 Sun Sep 20 18:55:32 PDT 1987 887 File append failure report added for future use. 888 */ 889 } else { 890 debugp((stderr, "%s: can't append accounting file\n", prog)); 891 perror(accountingfile); 892 } 893 } 894 895 /* all done -- let sender know */ 896 if (havejobout) VOIDC fclose(jobout); 897 VOIDC fclose(psin); 898 exit(0); /* to parent */ 899 } 900 } 901 902 /* send the file ".banner" */ 903 private SendBanner() 904 { 905 register int banner; 906 int cnt; 907 char buf[BUFSIZ]; 908 909 if ((banner = open(".banner",O_RDONLY|O_NDELAY,0)) < 0) return; 910 while ((cnt = read(banner,buf,sizeof buf)) > 0) { 911 VOIDC write(fdsend,buf,cnt); 912 } 913 VOIDC close(banner); 914 VOIDC unlink(".banner"); 915 } 916 917 /* search backwards from p in start for patt */ 918 private char *FindPattern(p, start, patt) 919 char *p; 920 char *start; 921 char *patt; 922 { 923 int patlen; 924 patlen = strlen(patt); 925 926 p -= patlen; 927 for (; p >= start; p--) { 928 if (strncmp(p, patt, patlen) == 0) return(p); 929 } 930 return ((char *)NULL); 931 } 932 933 private GotChar(c, pbuf) 934 register int c; 935 char *pbuf; 936 { 937 static char linebuf[BUFSIZ]; 938 static char *cp = linebuf; 939 static enum State {normal, onep, twop, inmessage, 940 close1, close2, close3, close4} st = normal; 941 char *match, *last; 942 943 switch (st) { 944 case normal: 945 if (c == '%') { 946 st = onep; 947 cp = linebuf; 948 *cp++ = c; 949 break; 950 } 951 putc(c,jobout); 952 VOIDC fflush(jobout); 953 break; 954 case onep: 955 if (c == '%') { 956 st = twop; 957 *cp++ = c; 958 break; 959 } 960 putc('%',jobout); 961 putc(c,jobout); 962 VOIDC fflush(jobout); 963 st = normal; 964 break; 965 case twop: 966 if (c == '\[') { 967 st = inmessage; 968 *cp++ = c; 969 break; 970 } 971 if (c == '\%') { 972 putc('%',jobout); 973 VOIDC fflush(jobout); 974 /* don't do anything to cp */ 975 break; 976 } 977 putc('%',jobout); 978 putc('%',jobout); 979 VOIDC fflush(jobout); 980 st = normal; 981 break; 982 case inmessage: 983 *cp++ = c; 984 if (c == '\]') st = close1; 985 break; 986 case close1: 987 *cp++ = c; 988 switch (c) { 989 case '%': st = close2; break; 990 case '\]': st = close1; break; 991 default: st = inmessage; break; 992 } 993 break; 994 case close2: 995 *cp++ = c; 996 switch (c) { 997 case '%': st = close3; break; 998 case '\]': st = close1; break; 999 default: st = inmessage; break; 1000 } 1001 break; 1002 case close3: 1003 *cp++ = c; 1004 switch (c) { 1005 case '\r': st = close4; break; 1006 case '\]': st = close1; break; 1007 default: st = inmessage; break; 1008 } 1009 break; 1010 case close4: 1011 *cp++ = c; 1012 switch(c) { 1013 case '\n': st = normal; break; 1014 case '\]': st = close1; break; 1015 default: st = inmessage; break; 1016 } 1017 if (st == normal) { 1018 /* parse complete message */ 1019 last = cp; 1020 *cp = 0; 1021 debugp((stderr,">>%s",linebuf)); 1022 if (match = FindPattern(cp, linebuf, " PrinterError: ")) { 1023 if (*(match-1) != ':') { 1024 fprintf(stderr,"%s",linebuf); 1025 VOIDC fflush(stderr); 1026 *(last-6) = 0; 1027 Status(match+15); 1028 } 1029 else { 1030 last = index(match,';'); 1031 *last = 0; 1032 Status(match+15); 1033 } 1034 } 1035 else if (match = FindPattern(cp, linebuf, " status: ")) { 1036 match += 9; 1037 if (strncmp(match,"idle",4) == 0) { 1038 /* we are hopelessly lost, get everyone to quit */ 1039 fprintf(stderr,"%s: ERROR: printer is idle, giving up!\n",prog); 1040 VOIDC fflush(stderr); 1041 VOIDC kill(getppid(),SIGKILL); /* will this work */ 1042 exit(THROW_AWAY); 1043 } 1044 else { 1045 /* one of: busy, waiting, printing, initializing */ 1046 /* clear status message */ 1047 RestoreStatus(); 1048 } 1049 } 1050 /* 1051 Sun Sep 20 18:39:40 PDT 1987 1052 Additional else necessary: if we get the final pagecount 1053 information here from the printer, store it in the given 1054 array pbuf[]. 1055 */ 1056 else if (match = FindPattern(cp, linebuf, "%%[ pagecount: ")) { 1057 /* fill pbuf */ 1058 strcpy(pbuf, linebuf); 1059 debugp((stderr, "%s: 'premature' final pagecount read = '%s'\n", prog, pbuf)); 1060 } 1061 else { 1062 /* message not for us */ 1063 fprintf(jobout,"%s",linebuf); 1064 VOIDC fflush(jobout); 1065 st = normal; 1066 break; 1067 } 1068 } 1069 break; 1070 default: 1071 fprintf(stderr,"bad case;\n"); 1072 } 1073 return; 1074 } 1075 1076 /* backup "status" message file in ".status", 1077 * in case there is a PrinterError 1078 */ 1079 1080 private BackupStatus(file1, file2) 1081 char *file1, *file2; 1082 { 1083 register int fd1, fd2; 1084 char buf[BUFSIZ]; 1085 int cnt; 1086 1087 VOIDC umask(0); 1088 fd1 = open(file1, O_WRONLY|O_CREAT, 0664); 1089 if ((fd1 < 0) || (flock(fd1,LOCK_EX) < 0)) { 1090 VOIDC unlink(file1); 1091 VOIDC flock(fd1,LOCK_UN); 1092 VOIDC close(fd1); 1093 fd1 = open(file1, O_WRONLY|O_CREAT, 0664); 1094 } 1095 if ((fd1 < 0) || (flock(fd1,LOCK_EX) <0)) { 1096 fprintf(stderr, "%s: writing %s:\n",prog,file1); 1097 perror(prog); 1098 VOIDC close(fd1); 1099 return; 1100 } 1101 VOIDC ftruncate(fd1,0); 1102 if ((fd2 = open(file2, O_RDONLY,0)) < 0) { 1103 fprintf(stderr, "%s: error reading %s:\n", prog, file2); 1104 perror(prog); 1105 VOIDC close(fd1); 1106 return; 1107 } 1108 cnt = read(fd2,buf,BUFSIZ); 1109 VOIDC write(fd1,buf,cnt); 1110 VOIDC flock(fd1,LOCK_UN); 1111 VOIDC close(fd1); 1112 VOIDC close(fd2); 1113 } 1114 1115 /* restore the "status" message from the backed-up ".status" copy */ 1116 private RestoreStatus() { 1117 BackupStatus("status",".status"); 1118 } 1119 1120 /* report PrinterError via "status" message file */ 1121 private Status(msg) 1122 register char *msg; 1123 { 1124 register int fd; 1125 char msgbuf[100]; 1126 1127 if ((fd = open("status",O_WRONLY|O_CREAT,0664)) < 0) return; 1128 VOIDC ftruncate(fd,0); 1129 sprintf(msgbuf,"Printer Error: may need attention! (%s)\n\0",msg); 1130 VOIDC write(fd,msgbuf,strlen(msgbuf)); 1131 VOIDC close(fd); 1132 } 1133 1134 /* sending phase alarm handler for sender */ 1135 1136 private VOID salarm() { 1137 1138 debugp((stderr,"%s: AS %d %d %d\n",prog,oldprogress,progress,getstatus)); 1139 1140 /* if progress != oldprogress, we made some progress (sent something) 1141 * else, we had two alarms without sending anything... 1142 * It may be that a PrinterError has us stopped, or we are computing 1143 * for a long time (forever?) -- printer jobtimeout may help here 1144 * in any case, all we do is set the flag to get status... 1145 * this will help us clear printererror notification 1146 */ 1147 1148 oldprogress = progress; 1149 getstatus = TRUE; 1150 1151 /* reset the alarm and return */ 1152 VOIDC alarm(SENDALARM); 1153 return; 1154 } 1155 1156 /* waiting phase alarm handler for sender */ 1157 1158 private VOID walarm() { 1159 static int acount = 0; 1160 1161 debugp((stderr,"%s: WA %d %d %d %d\n", 1162 prog,acount,oldprogress,progress,getstatus)); 1163 1164 if ((oldprogress != progress) || (acount == 4)) { 1165 getstatus = TRUE; 1166 acount = 0; 1167 oldprogress = progress; 1168 } 1169 else acount++; 1170 1171 /* reset alarm */ 1172 VOIDC alarm(WAITALARM); 1173 1174 /* return to wait loop */ 1175 longjmp(dwait, 0); 1176 } 1177 1178 /* final phase alarm handler for sender */ 1179 1180 private VOID falarm() { 1181 1182 debugp((stderr,"%s: FA %d %d %d\n",prog,oldprogress,progress,getstatus)); 1183 1184 /* no reason to count progress, just get status */ 1185 if (!intrup) { 1186 VOIDC write(fdsend, statusbuf, 1); 1187 } 1188 getstatus = FALSE; 1189 1190 /* reset alarm */ 1191 VOIDC alarm(WAITALARM); 1192 return; 1193 } 1194 1195 /* initial interrupt handler - before communications begin, so 1196 * nothing to be sent to printer 1197 */ 1198 private VOID intinit() { 1199 long clock; 1200 1201 /* get rid of banner file */ 1202 VOIDC unlink(".banner"); 1203 1204 fprintf(stderr,"%s: abort (during setup)\n",prog); 1205 VOIDC fflush(stderr); 1206 1207 /* these next two may be too cautious */ 1208 VOIDC kill(0,SIGINT); 1209 while (wait((union wait *) 0) > 0); 1210 1211 if (VerboseLog) { 1212 fprintf (stderr, "%s: end - %s", prog, (time(&clock), ctime(&clock))); 1213 VOIDC fflush(stderr); 1214 } 1215 1216 exit(THROW_AWAY); 1217 } 1218 1219 /* interrupt during sending phase to sender process */ 1220 1221 private VOID intsend() { 1222 /* set flag */ 1223 intrup = TRUE; 1224 longjmp(sendint, 0); 1225 } 1226 1227 /* interrupt during waiting phase to sender process */ 1228 1229 private VOID intwait() { 1230 1231 intrup = TRUE; 1232 1233 fprintf(stderr,"%s: abort (waiting)\n",prog); 1234 VOIDC fflush(stderr); 1235 if (ioctl(fdsend, TIOCFLUSH, &flg) || ioctl(fdsend, TIOCSTART, &flg) 1236 || (write(fdsend, abortbuf, 1) != 1)) { 1237 fprintf(stderr, "%s: error in ioctl(fdsend):\n", prog); 1238 perror(prog); 1239 } 1240 1241 /* VOIDC alarm(2); /* force an alarm soon to get us out of wait! ? */ 1242 longjmp(dwait, 0); 1243 } 1244 1245 /* EMT for reverse filter, avoid printer timeout at the expense 1246 * of performance (sigh) 1247 */ 1248 1249 private VOID reverseready() { 1250 revdone = TRUE; 1251 longjmp(waitonreverse, 0); 1252 } 1253 1254 /* EMT on startup to sender -- signalled by listener after first status 1255 * message received 1256 */ 1257 1258 private VOID readynow() { 1259 goahead = TRUE; 1260 longjmp(startstatus, 0); 1261 } 1262 1263 /* EMT on sending phase, hard EOF printer died! */ 1264 private VOID emtdead() { 1265 VOIDC alarm(0); 1266 exit(THROW_AWAY); 1267 } 1268 1269 /* EMT during waiting phase -- listener saw an EOF (^D) from printer */ 1270 1271 private VOID emtdone() { 1272 VOIDC alarm(0); 1273 gotemt = TRUE; 1274 longjmp(dwait, 0); 1275 } 1276