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