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