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
main(argc,argv)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" */
SendBanner()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 */
FindPattern(p,start,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
GotChar(c,pbuf)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
BackupStatus(file1,file2)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 */
RestoreStatus()1070 private RestoreStatus() {
1071 BackupStatus("status",".status");
1072 }
1073
1074 /* report PrinterError via "status" message file */
Status(msg)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
salarm()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
walarm()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
falarm()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 */
intinit()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
intsend()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
intwait()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
reverseready()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
readynow()1212 private VOID readynow() {
1213 goahead = TRUE;
1214 longjmp(startstatus, 0);
1215 }
1216
1217 /* EMT on sending phase, hard EOF printer died! */
emtdead()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
emtdone()1225 private VOID emtdone() {
1226 VOIDC alarm(0);
1227 gotemt = TRUE;
1228 longjmp(dwait, 0);
1229 }
1230