xref: /original-bsd/local/transcript/src/pscomm.c (revision 6b4229fb)
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