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