xref: /plan9/sys/src/cmd/postscript/postio/postio.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 /*
2  *
3  * postio - RS-232 serial interface for PostScript printers
4  *
5  * A simple program that manages input and output for PostScript printers. Much
6  * has been added and changed from early versions of the program, but the basic
7  * philosophy is still the same. Don't send real data until we're certain we've
8  * connected to a PostScript printer that's in the idle state and try to hold the
9  * connection until the job is completely done. It's more work than you might
10  * expect is necessary, but should provide a reasonably reliable spooler interface
11  * that can return error indications to the caller via the program's exit status.
12  *
13  * I've added code that will let you split the program into separate read/write
14  * processes. Although it's not the default it should be useful if you have a file
15  * that will be returning useful data from the printer. The two process stuff was
16  * laid down on top of the single process code and both methods still work. The
17  * implementation isn't as good as it could be, but didn't require many changes
18  * to the original program (despite the fact that there are now many differences).
19  *
20  * By default the program still runs as a single process. The -R2 option forces
21  * separate read and write processes after the intial connection is made. If you
22  * want that as the default initialize splitme (below) to TRUE. In addition the
23  * -t option that's used to force stuff not recognized as status reports to stdout
24  * also tries to run as two processes (by setting splitme to TRUE). It will only
25  * work if the required code (ie. resetline() in ifdef.c) has been implemented
26  * for your Unix system. I've only tested the System V code.
27  *
28  * Code needed to support interactive mode has also been added, although again it's
29  * not as efficient as it could be. It depends on the system dependent procedures
30  * resetline() and setupstdin() (file ifdef.c) and for now is only guaranteed to
31  * work on System V. Can be requested using the -i option.
32  *
33  * Quiet mode (-q option) is also new, but was needed for some printers connected
34  * to RADIAN. If you're running in quiet mode no status requests will be sent to
35  * the printer while files are being transmitted (ie. in send()).
36  *
37  * The program expects to receive printer status lines that look like,
38  *
39  *	%%[ status: idle; source: serial 25 ]%%
40  *	%%[ status: waiting; source: serial 25 ]%%
41  *	%%[ status: initializing; source: serial 25 ]%%
42  *	%%[ status: busy; source: serial 25 ]%%
43  *	%%[ status: printing; source: serial 25 ]%%
44  *	%%[ status: PrinterError: out of paper; source: serial 25 ]%%
45  *	%%[ status: PrinterError: no paper tray; source: serial 25 ]%%
46  *
47  * although this list isn't complete. Sending a '\024' (control T) character forces
48  * the return of a status report. PostScript errors detected on the printer result
49  * in the immediate transmission of special error messages that look like,
50  *
51  *	%%[ Error: undefined; OffendingCommand: xxx ]%%
52  *	%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%
53  *
54  * although we only use the Error and Flushing keywords. Finally conditions, like
55  * being out of paper, result in other messages being sent back from the printer
56  * over the communications line. Typical PrinterError messages look like,
57  *
58  *	%%[ PrinterError: out of paper; source: serial 25 ]%%
59  *	%%[ PrinterError: paper jam; source: serial 25 ]%%
60  *
61  * although we only use the PrinterError keyword rather than trying to recognize
62  * all possible printer errors.
63  *
64  * The implications of using one process and only flow controlling data going to
65  * the printer are obvious. Job transmission should be reliable, but there can be
66  * data loss in stuff sent back from the printer. Usually that only caused problems
67  * with jobs designed to run on the printer and return useful data back over the
68  * communications line. If that's the kind of job you're sending call postio with
69  * the -t option. That should force the program to split into separate read and
70  * write processes and everything not bracketed by "%%[ " and " ]%%" strings goes
71  * to stdout. In otherwords the data you're expecting should be separated from the
72  * status stuff that goes to the log file (or stderr). The -R2 option does almost
73  * the same thing (ie. separate read and write processes), but everything that
74  * comes back from the printer goes to the log file (stderr by default) and you'll
75  * have to separate your data from any printer messages.
76  *
77  * A typical command line might be,
78  *
79  *	postio -l /dev/tty01 -b 9600 -L log file1 file2
80  *
81  * where -l selects the line, -b sets the baud rate, and -L selects the printer
82  * log file. Since there's no default line, at least not right now, you'll always
83  * need to use the -l option, and if you don't choose a log file stderr will be
84  * used. If you have a program that will be returning data the command line might
85  * look like,
86  *
87  *	postio -t -l/dev/tty01 -b9600 -Llog file >results
88  *
89  * Status stuff goes to file log while the data you're expecting back from the
90  * printer gets put in file results.
91  *
92  */
93 
94 #include <stdio.h>
95 #include <ctype.h>
96 #include <fcntl.h>
97 #include <signal.h>
98 #include <sys/types.h>
99 #include <errno.h>
100 
101 #include "ifdef.h"			/* conditional compilation stuff */
102 #include "gen.h"			/* general purpose definitions */
103 #include "postio.h"			/* some special definitions */
104 
105 char	**argv;				/* global so everyone can use them */
106 int	argc;
107 
108 char	*prog_name = "";		/* really just for error messages */
109 int	x_stat = 0;			/* program exit status */
110 int	debug = OFF;			/* debug flag */
111 int	ignore = OFF;			/* what's done for FATAL errors */
112 
113 char	*line = NULL;			/* printer is on this tty line */
114 short	baudrate = BAUDRATE;		/* and running at this baud rate */
115 Baud	baudtable[] = BAUDTABLE;	/* converts strings to termio values */
116 
117 int	stopbits = 1;			/* number of stop bits */
118 int	tostdout = FALSE;		/* non-status stuff goes to stdout? */
119 int	quiet = FALSE;			/* no status queries in send() if TRUE */
120 int	interactive = FALSE;		/* interactive mode */
121 char	*postbegin = POSTBEGIN;		/* preceeds all the input files */
122 int	useslowsend = FALSE;		/* not recommended! */
123 int	sendctrlC = TRUE;		/* interrupt with ctrl-C when BUSY */
124 int	window_size = -1;		/* for Datakit - use -w */
125 
126 char	*block = NULL;			/* input file buffer */
127 int	blocksize = BLOCKSIZE;		/* and its size in bytes */
128 int	head = 0;			/* block[head] is the next character */
129 int	tail = 0;			/* one past the last byte in block[] */
130 
131 int	splitme = FALSE;		/* into READ and WRITE processes if TRUE */
132 int	whatami = READWRITE;		/* a READ or WRITE process - or both */
133 int	canread = TRUE;			/* allow reads */
134 int	canwrite = TRUE;		/* and writes if TRUE */
135 int	otherpid = -1;			/* who gets signals if greater than 1 */
136 int	joinsig = SIGTRAP;		/* reader gets this when writing is done */
137 int	writedone = FALSE;		/* and then sets this to TRUE */
138 
139 char	mesg[MESGSIZE];			/* exactly what came back on ttyi */
140 char	sbuf[MESGSIZE];			/* for parsing the message */
141 int	next = 0;			/* next character goes in mesg[next] */
142 char	*mesgptr = NULL;		/* printer message starts here in mesg[] */
143 char	*endmesg = NULL;		/* as far as readline() can go in mesg[] */
144 
145 Status	status[] = STATUS;		/* for converting status strings */
146 int	nostatus = NOSTATUS;		/* default getstatus() return value */
147 
148 int	currentstate = NOTCONNECTED;	/* what's happening START, SEND, or DONE */
149 
150 int	ttyi = 0;			/* input */
151 int	ttyo = 2;			/* and output file descriptors */
152 
153 FILE	*fp_log = stderr;		/* log file for stuff from the printer */
154 
155 /*****************************************************************************/
156 
main(agc,agv)157 main(agc, agv)
158 
159     int		agc;
160     char	*agv[];
161 
162 {
163 
164 /*
165  *
166  * A simple program that manages input and output for PostScript printers. Can run
167  * as a single process or as separate read/write processes. What's done depends on
168  * the value assigned to splitme when split() is called.
169  *
170  */
171 
172     argc = agc;				/* other routines may want them */
173     argv = agv;
174 
175     prog_name = argv[0];		/* really just for error messages */
176 
177     init_signals();			/* sets up interrupt handling */
178     options();				/* get command line options */
179     initialize();			/* must be done after options() */
180     start();				/* make sure the printer is ready */
181     split();				/* into read/write processes - maybe */
182     arguments();			/* then send each input file */
183     done();				/* wait until the printer is finished */
184     cleanup();				/* make sure the write process stops */
185 
186     exit(x_stat);			/* everything probably went OK */
187 
188 }   /* End of main */
189 
190 /*****************************************************************************/
191 
init_signals()192 init_signals()
193 
194 {
195 
196     void	interrupt();		/* handles them if we catch signals */
197 
198 /*
199  *
200  * Makes sure we handle interrupts. The proper way to kill the program, if
201  * necessary, is to do a kill -15. That forces a call to interrupt(), which in
202  * turn tries to reset the printer and then exits with a non-zero status. If the
203  * program is running as two processes, sending SIGTERM to either the parent or
204  * child should clean things up.
205  *
206  */
207 
208     if ( signal(SIGINT, interrupt) == SIG_IGN )  {
209 	signal(SIGINT, SIG_IGN);
210 	signal(SIGQUIT, SIG_IGN);
211 	signal(SIGHUP, SIG_IGN);
212     } else {
213 	signal(SIGHUP, interrupt);
214 	signal(SIGQUIT, interrupt);
215     }	/* End else */
216 
217     signal(SIGTERM, interrupt);
218 
219 }   /* End of init_sig */
220 
221 /*****************************************************************************/
222 
options()223 options()
224 
225 {
226 
227     int		ch;			/* return value from getopt() */
228     char	*optnames = "b:cil:qs:tw:B:L:P:R:SDI";
229 
230     extern char	*optarg;		/* used by getopt() */
231     extern int	optind;
232 
233 /*
234  *
235  * Reads and processes the command line options. The -R2, -t, and -i options all
236  * force separate read and write processes by eventually setting splitme to TRUE
237  * (check initialize()). The -S option is not recommended and should only be used
238  * as a last resort!
239  *
240  */
241 
242     while ( (ch = getopt(argc, argv, optnames)) != EOF )  {
243 	switch ( ch )  {
244 	    case 'b':			/* baud rate string */
245 		    baudrate = getbaud(optarg);
246 		    break;
247 
248 	    case 'c':			/* no ctrl-C's */
249 		    sendctrlC = FALSE;
250 		    break;
251 
252 	    case 'i':			/* interactive mode */
253 		    interactive = TRUE;
254 		    break;
255 
256 	    case 'l':			/* printer line */
257 		    line = optarg;
258 		    break;
259 
260 	    case 'q':			/* no status queries - for RADIAN? */
261 		    quiet = TRUE;
262 		    break;
263 
264 	    case 's':			/* use 2 stop bits - for UNISON? */
265 		    if ( (stopbits = atoi(optarg)) < 1 || stopbits > 2 )
266 			stopbits = 1;
267 		    break;
268 
269 	    case 't':			/* non-status stuff goes to stdout */
270 		    tostdout = TRUE;
271 		    break;
272 
273 	    case 'w':			/* Datakit window size */
274 		    window_size = atoi(optarg);
275 		    break;
276 
277 	    case 'B':			/* set the job buffer size */
278 		    if ( (blocksize = atoi(optarg)) <= 0 )
279 			blocksize = BLOCKSIZE;
280 		    break;
281 
282 	    case 'L':			/* printer log file */
283 		    if ( (fp_log = fopen(optarg, "w")) == NULL )  {
284 			fp_log = stderr;
285 			error(NON_FATAL, "can't open log file %s", optarg);
286 		    }	/* End if */
287 		    break;
288 
289 	    case 'P':			/* initial PostScript code */
290 		    postbegin = optarg;
291 		    break;
292 
293 	    case 'R':			/* run as one or two processes */
294 		    if ( atoi(optarg) == 2 )
295 			splitme = TRUE;
296 		    else splitme = FALSE;
297 		    break;
298 
299 	    case 'S':			/* slow and kludged up version of send */
300 		    useslowsend = TRUE;
301 		    break;
302 
303 	    case 'D':			/* debug flag */
304 		    debug = ON;
305 		    break;
306 
307 	    case 'I':			/* ignore FATAL errors */
308 		    ignore = ON;
309 		    break;
310 
311 	    case '?':			/* don't understand the option */
312 		    error(FATAL, "");
313 		    break;
314 
315 	    default:			/* don't know what to do for ch */
316 		    error(FATAL, "missing case for option %c\n", ch);
317 		    break;
318 	}   /* End switch */
319     }   /* End while */
320 
321     argc -= optind;			/* get ready for non-option args */
322     argv += optind;
323 
324 }   /* End of options */
325 
326 /*****************************************************************************/
327 
getbaud(rate)328 getbaud(rate)
329 
330     char	*rate;			/* string representing the baud rate */
331 
332 {
333 
334     int		i;			/* for looking through baudtable[] */
335 
336 /*
337  *
338  * Called from options() to convert a baud rate string into an appropriate termio
339  * value. *rate is looked up in baudtable[] and if it's found, the corresponding
340  * value is returned to the caller.
341  *
342  */
343 
344     for ( i = 0; baudtable[i].rate != NULL; i++ )
345 	if ( strcmp(rate, baudtable[i].rate) == 0 )
346 	    return(baudtable[i].val);
347 
348     error(FATAL, "don't recognize baud rate %s", rate);
349 
350 }   /* End of getbaud */
351 
352 /*****************************************************************************/
353 
initialize()354 initialize()
355 
356 {
357 
358 /*
359  *
360  * Initialization, a few checks, and a call to setupline() (file ifdef.c) to open
361  * and configure the communications line. Settings for interactive mode always
362  * take precedence. The setupstdin() call with an argument of 0 saves the current
363  * terminal settings if interactive mode has been requested - otherwise nothing's
364  * done. Unbuffering stdout (via the setbuf() call) isn't really needed on System V
365  * since it's flushed whenever terminal input is requested. It's more efficient if
366  * we buffer the stdout (on System V) but safer (for other versions of Unix) if we
367  * include the setbuf() call.
368  *
369  */
370 
371     whatami = READWRITE;		/* always run start() as one process */
372     canread = canwrite = TRUE;
373 
374     if ( tostdout == TRUE )		/* force separate read/write processes */
375 	splitme = TRUE;
376 
377     if ( interactive == TRUE )  {	/* interactive mode settings always win */
378 	quiet = FALSE;
379 	tostdout = FALSE;
380 	splitme = TRUE;
381 	blocksize = 1;
382 	postbegin = NULL;
383 	useslowsend = FALSE;
384 	nostatus = INTERACTIVE;
385 	setbuf(stdout, NULL);
386     }	/* End if */
387 
388     if ( useslowsend == TRUE )  {	/* last resort only - not recommended */
389 	quiet = FALSE;
390 	splitme = FALSE;
391 	if ( blocksize > 1024 )		/* don't send too much all at once */
392 	    blocksize = 1024;
393     }	/* End if */
394 
395     if ( tostdout == TRUE && fp_log == stderr )
396 	fp_log = NULL;
397 
398     if ( line == NULL && (interactive == TRUE || tostdout == TRUE) )
399 	error(FATAL, "a printer line must be supplied - use the -l option");
400 
401     if ( (block = malloc(blocksize)) == NULL )
402 	error(FATAL, "no memory");
403 
404     endmesg = mesg + sizeof mesg - 2;	/* one byte from last position in mesg */
405 
406     setupline();			/* configure the communications line */
407     setupstdin(0);			/* save current stdin terminal settings */
408 
409 }   /* End of initialize */
410 
411 /*****************************************************************************/
412 
start()413 start()
414 
415 {
416 
417 /*
418  *
419  * Tries to put the printer in the IDLE state before anything important is sent.
420  * Run as a single process no matter what has been assigned to splitme. Separate
421  * read and write processes, if requested, will be created after we're done here.
422  *
423  */
424 
425     logit("printer startup\n");
426 
427     currentstate = START;
428     clearline();
429 
430     while ( 1 )
431 	switch ( getstatus(1) )  {
432 	    case IDLE:
433 	    case INTERACTIVE:
434 		    if ( postbegin != NULL && *postbegin != '\0' )
435 			Write(ttyo, postbegin, strlen(postbegin));
436 		    clearline();
437 		    return;
438 
439 	    case BUSY:
440 		    if ( sendctrlC == TRUE ) {
441 			Write(ttyo, "\003", 1);
442 			Rest(1);
443 		    }	/* End if */
444 		    break;
445 
446 	    case WAITING:
447 	    case ERROR:
448 	    case FLUSHING:
449 		    Write(ttyo, "\004", 1);
450 		    Rest(1);
451 		    break;
452 
453 	    case PRINTERERROR:
454 		    Rest(15);
455 		    break;
456 
457 	    case DISCONNECT:
458 		    error(FATAL, "Disconnected - printer may be offline");
459 		    break;
460 
461 	    case ENDOFJOB:
462 	    case UNKNOWN:
463 		    clearline();
464 		    break;
465 
466 	    default:
467 		    Rest(1);
468 		    break;
469 	}   /* End switch */
470 
471 }   /* End of start */
472 
473 /*****************************************************************************/
474 
split()475 split()
476 
477 {
478 
479     int		pid;
480     void	interrupt();
481 
482 /*
483  *
484  * If splitme is TRUE we fork a process, make the parent handle reading, and let
485  * the child take care of writing. resetline() (file ifdef.c) contains all the
486  * system dependent code needed to reset the communications line for separate
487  * read and write processes. For now it's expected to return TRUE or FALSE and
488  * that value controls whether we try the fork. I've only tested the two process
489  * stuff for System V. Other versions of resetline() may just be dummy procedures
490  * that always return FALSE. If the fork() failed previous versions continued as
491  * a single process, although the implementation wasn't quite right, but I've now
492  * decided to quit. The main reason is a Datakit channel may be configured to
493  * flow control data in both directions, and if we run postio over that channel
494  * as a single process we likely will end up in deadlock.
495  *
496  */
497 
498     if ( splitme == TRUE )
499 	if ( resetline() == TRUE )  {
500 	    pid = getpid();
501 	    signal(joinsig, interrupt);
502 	    if ( (otherpid = fork()) == -1 )
503 		error(FATAL, "can't fork");
504 	    else if ( otherpid == 0 )  {
505 		whatami = WRITE;
506 		nostatus = WRITEPROCESS;
507 		otherpid = pid;
508 		setupstdin(1);
509 	    } else whatami = READ;
510 	} else if ( interactive == TRUE || tostdout == TRUE )
511 	    error(FATAL, "can't create two process - check resetline()");
512  	else error(NON_FATAL, "running as a single process - check resetline()");
513 
514     canread = (whatami & READ) ? TRUE : FALSE;
515     canwrite = (whatami & WRITE) ? TRUE : FALSE;
516 
517 }   /* End of split */
518 
519 /*****************************************************************************/
520 
arguments()521 arguments()
522 
523 {
524 
525     int		fd_in;			/* next input file */
526 
527 /*
528  *
529  * Makes sure all the non-option command line arguments are processed. If there
530  * aren't any arguments left when we get here we'll send stdin. Input files are
531  * only read and sent to the printer if canwrite is TRUE. Checking it here means
532  * we won't have to do it in send(). If interactive mode is TRUE we'll stay here
533  * forever sending stdin when we run out of files - exit with a break. Actually
534  * the loop is bogus and used at most once when we're in interactive mode because
535  * stdin is in a pseudo raw mode and the read() in readblock() should never see
536  * the end of file.
537  *
538  */
539 
540     if ( canwrite == TRUE )
541 	do				/* loop is for interactive mode */
542 	    if ( argc < 1 )
543 		send(fileno(stdin), "pipe.end");
544 	    else  {
545 		while ( argc > 0 )  {
546 		    if ( (fd_in = open(*argv, O_RDONLY)) == -1 )
547 			error(FATAL, "can't open %s", *argv);
548 		    send(fd_in, *argv);
549 		    close(fd_in);
550 		    argc--;
551 		    argv++;
552 		}   /* End while */
553 	    }	/* End else */
554 	while ( interactive == TRUE );
555 
556 }   /* End of arguments */
557 
558 /*****************************************************************************/
559 
send(fd_in,name)560 send(fd_in, name)
561 
562     int		fd_in;			/* next input file */
563     char	*name;			/* and it's pathname */
564 
565 {
566 
567 /*
568  *
569  * Sends file *name to the printer. There's nothing left here that depends on
570  * sending and receiving status reports, although it can be reassuring to know
571  * the printer is responding and processing our job. Only the writer gets here
572  * in the two process implementation, and in that case split() has reset nostatus
573  * to WRITEPROCESS and that's what getstatus() always returns. For now we accept
574  * the IDLE state and ENDOFJOB as legitimate and ignore the INITIALIZING state.
575  *
576  */
577 
578     if ( interactive == FALSE )
579 	logit("sending file %s\n", name);
580 
581     currentstate = SEND;
582 
583     if ( useslowsend == TRUE )  {
584 	slowsend(fd_in);
585 	return;
586     }	/* End if */
587 
588     while ( readblock(fd_in) )
589 	switch ( getstatus(0) )  {
590 	    case IDLE:
591 	    case BUSY:
592 	    case WAITING:
593 	    case PRINTING:
594 	    case ENDOFJOB:
595 	    case PRINTERERROR:
596 	    case UNKNOWN:
597 	    case NOSTATUS:
598 	    case WRITEPROCESS:
599 	    case INTERACTIVE:
600 		    writeblock();
601 		    break;
602 
603 	    case ERROR:
604 		    fprintf(stderr, "%s", mesg);	/* for csw */
605 		    error(USER_FATAL, "PostScript Error");
606 		    break;
607 
608 	    case FLUSHING:
609 		    error(USER_FATAL, "Flushing Job");
610 		    break;
611 
612 	    case DISCONNECT:
613 		    error(FATAL, "Disconnected - printer may be offline");
614 		    break;
615 	}   /* End switch */
616 
617 }   /* End of send */
618 
619 /*****************************************************************************/
620 
done()621 done()
622 
623 {
624 
625     int		sleeptime = 15;		/* for 'out of paper' etc. */
626 
627 /*
628  *
629  * Tries to stay connected to the printer until we're reasonably sure the job is
630  * complete. It's the only way we can recover error messages or data generated by
631  * the PostScript program and returned over the communication line. Actually doing
632  * it correctly for all possible PostScript jobs is more difficult that it might
633  * seem. For example if we've sent several jobs, each with their own EOF mark, then
634  * waiting for ENDOFJOB won't guarantee all the jobs have completed. Even waiting
635  * for IDLE isn't good enough. Checking for the WAITING state after all the files
636  * have been sent and then sending an EOF may be the best approach, but even that
637  * won't work all the time - we could miss it or might not get there. Even sending
638  * our own special PostScript job after all the input files has it's own different
639  * set of problems, but probably could work (perhaps by printing a fake status
640  * message or just not timing out). Anyway it's probably not worth the trouble so
641  * for now we'll quit if writedone is TRUE and we get ENDOFJOB or IDLE.
642  *
643  * If we're running separate read and write processes the reader gets here after
644  * after split() while the writer goes to send() and only gets here after all the
645  * input files have been transmitted. When they're both here the writer sends the
646  * reader signal joinsig and that forces writedone to TRUE in the reader. At that
647  * point the reader can begin looking for an indication of the end of the job.
648  * The writer hangs around until the reader kills it (usually in cleanup()) sending
649  * occasional status requests.
650  *
651  */
652 
653     if ( canwrite == TRUE )
654 	logit("waiting for end of job\n");
655 
656     currentstate = DONE;
657     writedone = (whatami == READWRITE) ? TRUE : FALSE;
658 
659     while ( 1 )  {
660 	switch ( getstatus(1) )  {
661 
662 	    case WRITEPROCESS:
663 		    if ( writedone == FALSE )  {
664 			sendsignal(joinsig);
665 			Write(ttyo, "\004", 1);
666 			writedone = TRUE;
667 			sleeptime = 1;
668 		    }	/* End if */
669 		    Rest(sleeptime++);
670 		    break;
671 
672 	    case WAITING:
673 		    Write(ttyo, "\004", 1);
674 		    Rest(1);
675 		    sleeptime = 15;
676 		    break;
677 
678 	    case IDLE:
679 	    case ENDOFJOB:
680 		    if ( writedone == TRUE )  {
681 			logit("job complete\n");
682 			return;
683 		    }	/* End if */
684 		    break;
685 
686 	    case BUSY:
687 	    case PRINTING:
688 	    case INTERACTIVE:
689 		    sleeptime = 15;
690 		    break;
691 
692 	    case PRINTERERROR:
693 		    Rest(sleeptime++);
694 		    break;
695 
696 	    case ERROR:
697 		    fprintf(stderr, "%s", mesg);	/* for csw */
698 		    error(USER_FATAL, "PostScript Error");
699 		    return;
700 
701 	    case FLUSHING:
702 		    error(USER_FATAL, "Flushing Job");
703 		    return;
704 
705 	    case DISCONNECT:
706 		    error(FATAL, "Disconnected - printer may be offline");
707 		    return;
708 
709 	    default:
710 		    Rest(1);
711 		    break;
712 	}   /* End switch */
713 
714 	if ( sleeptime > 60 )
715 	    sleeptime = 60;
716     }	/* End while */
717 
718 }   /* End of done */
719 
720 /*****************************************************************************/
721 
cleanup()722 cleanup()
723 
724 {
725 
726     int		w;
727 
728 /*
729  *
730  * Only needed if we're running separate read and write processes. Makes sure the
731  * write process is killed after the read process has successfully finished with
732  * all the jobs. sendsignal() returns a -1 if there's nobody to signal so things
733  * work when we're running a single process.
734  *
735  */
736 
737     while ( sendsignal(SIGKILL) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
738 
739 }   /* End of cleanup */
740 
741 /*****************************************************************************/
742 
readblock(fd_in)743 readblock(fd_in)
744 
745     int		fd_in;			/* current input file */
746 
747 {
748 
749     static long	blocknum = 1;
750 
751 /*
752  *
753  * Fills the input buffer with the next block, provided we're all done with the
754  * last one. Blocks from fd_in are stored in array block[]. head is the index
755  * of the next byte in block[] that's supposed to go to the printer. tail points
756  * one past the last byte in the current block. head is adjusted in writeblock()
757  * after each successful write, while head and tail are reset here each time
758  * a new block is read. Returns the number of bytes left in the current block.
759  * Read errors cause the program to abort. The fake status message that's put out
760  * in quiet mode is only so you can look at the log file and know something's
761  * happening - take it out if you want.
762  *
763  */
764 
765     if ( head >= tail )  {		/* done with the last block */
766 	if ( (tail = read(fd_in, block, blocksize)) == -1 )
767 	    error(FATAL, "error reading input file");
768 	if ( quiet == TRUE && tail > 0 )	/* put out a fake message? */
769 	    logit("%%%%[ status: busy; block: %d ]%%%%\n", blocknum++);
770 	head = 0;
771     }	/* End if */
772 
773     return(tail - head);
774 
775 }   /* End of readblock */
776 
777 /*****************************************************************************/
778 
writeblock()779 writeblock()
780 
781 {
782 
783     int		count;			/* bytes successfully written */
784 
785 /*
786  *
787  * Called from send() when it's OK to send the next block to the printer. head
788  * is adjusted after the write, and the number of bytes that were successfully
789  * written is returned to the caller.
790  *
791  */
792 
793     if ( (count = write(ttyo, &block[head], tail - head)) == -1 )
794 	error(FATAL, "error writing to %s", line);
795     else if ( count == 0 )
796 	error(FATAL, "printer appears to be offline");
797 
798     head += count;
799     return(count);
800 
801 }   /* End of writeblock */
802 
803 /*****************************************************************************/
804 
getstatus(t)805 getstatus(t)
806 
807     int		t;			/* sleep time after sending '\024' */
808 
809 {
810 
811     int		gotline = FALSE;	/* value returned by readline() */
812     int		state = nostatus;	/* representation of the current state */
813     int		mesgch;			/* to restore mesg[] when tostdout == TRUE */
814 
815     static int	laststate = NOSTATUS;	/* last state recognized */
816 
817 /*
818  *
819  * Looks for things coming back from the printer on the communications line, parses
820  * complete lines retrieved by readline(), and returns an integer representation
821  * of the current printer status to the caller. If nothing was available a status
822  * request (control T) is sent to the printer and nostatus is returned to the
823  * caller (provided quiet isn't TRUE). Interactive mode either never returns from
824  * readline() or returns FALSE.
825  *
826  */
827 
828     if ( canread == TRUE && (gotline = readline()) == TRUE )  {
829 	state = parsemesg();
830 	if ( state != laststate || state == UNKNOWN || mesgptr != mesg || debug == ON )
831 	    logit("%s", mesg);
832 
833 	if ( tostdout == TRUE && currentstate != START )  {
834 	    mesgch = *mesgptr;
835 	    *mesgptr = '\0';
836 	    fprintf(stdout, "%s", mesg);
837 	    fflush(stdout);
838 	    *mesgptr = mesgch;		/* for ERROR in send() and done() */
839 	}   /* End if */
840 	return(laststate = state);
841     }	/* End if */
842 
843     if ( (quiet == FALSE || currentstate != SEND) &&
844 	 (tostdout == FALSE || currentstate == START) && interactive == FALSE )  {
845 	if ( Write(ttyo, "\024", 1) != 1 )
846 	    error(FATAL, "printer appears to be offline");
847 	if ( t > 0 ) Rest(t);
848     }	/* End if */
849 
850     return(nostatus);
851 
852 }   /* End of getstatus */
853 
854 /*****************************************************************************/
855 
parsemesg()856 parsemesg()
857 
858 {
859 
860     char	*e;			/* end of printer message in mesg[] */
861     char	*key, *val;		/* keyword/value strings in sbuf[] */
862     char	*p;			/* for converting to lower case etc. */
863     int		i;			/* where *key was found in status[] */
864 
865 /*
866  *
867  * Parsing the lines that readline() stores in mesg[] is messy, and what's done
868  * here isn't completely correct nor as fast as it could be. The general format
869  * of lines that come back from the printer (assuming no data loss) is:
870  *
871  *		str%%[ key: val; key: val; key: val ]%%\n
872  *
873  * where str can be most anything not containing a newline and printer reports
874  * (eg. status or error messages) are bracketed by "%%[ " and " ]%%" strings and
875  * end with a newline. Usually we'll have the string or printer report but not
876  * both. For most jobs the leading string will be empty, but could be anything
877  * generated on a printer and returned over the communications line using the
878  * PostScript print operator. I'll assume PostScript jobs are well behaved and
879  * never bracket their messages with "%%[ " and " ]%%" strings that delimit status
880  * or error messages.
881  *
882  * Printer reports consist of one or more key/val pairs, and what we're interested
883  * in (status or error indications) may not be the first pair in the list. In
884  * addition we'll sometimes want the value associated with a keyword (eg. when
885  * key = status) and other times we'll want the keyword (eg. when key = Error or
886  * Flushing). The last pair isn't terminated by a semicolon and a value string
887  * often contains many space separated words and it can even include colons in
888  * meaningful places. I've also decided to continue converting things to lower
889  * case before doing the lookup in status[]. The isupper() test is for Berkeley
890  * systems.
891  *
892  */
893 
894     if ( *(mesgptr = find("%%[ ", mesg)) != '\0' && *(e = find(" ]%%", mesgptr+4)) != '\0' )  {
895 	strcpy(sbuf, mesgptr+4);		/* don't change mesg[] */
896 	sbuf[e-mesgptr-4] = '\0';		/* ignore the trailing " ]%%" */
897 
898 	for ( key = strtok(sbuf, " :"); key != NULL; key = strtok(NULL, " :") )  {
899 	    if ( (val = strtok(NULL, ";")) != NULL && strcmp(key, "status") == 0 )
900 		key = val;
901 
902 	    for ( ; *key == ' '; key++ ) ;	/* skip any leading spaces */
903 	    for ( p = key; *p; p++ )		/* convert to lower case */
904 		if ( *p == ':' )  {
905 		    *p = '\0';
906 		    break;
907 		} else if ( isupper(*p) ) *p = tolower(*p);
908 
909 	    for ( i = 0; status[i].state != NULL; i++ )
910 		if ( strcmp(status[i].state, key) == 0 )
911 		    return(status[i].val);
912 	}   /* End for */
913     } else if ( strcmp(mesg, "CONVERSATION ENDED.\n") == 0 )
914 	return(DISCONNECT);
915 
916     return(mesgptr == '\0' ? nostatus : UNKNOWN);
917 
918 }   /* End of parsemesg */
919 
920 /*****************************************************************************/
921 
find(str1,str2)922 char *find(str1, str2)
923 
924     char	*str1;			/* look for this string */
925     char	*str2;			/* in this one */
926 
927 {
928 
929     char	*s1, *s2;		/* can't change str1 or str2 too fast */
930 
931 /*
932  *
933  * Looks for *str1 in string *str2. Returns a pointer to the start of the substring
934  * if it's found or to the end of string str2 otherwise.
935  *
936  */
937 
938     for ( ; *str2 != '\0'; str2++ )  {
939 	for ( s1 = str1, s2 = str2; *s1 != '\0' && *s1 == *s2; s1++, s2++ ) ;
940 	if ( *s1 == '\0' )
941 	    break;
942     }	/* End for */
943 
944     return(str2);
945 
946 }   /* End of find */
947 
948 /*****************************************************************************/
949 
clearline()950 clearline()
951 
952 {
953 
954 /*
955  *
956  * Reads characters from the input line until nothing's left. Don't do anything if
957  * we're currently running separate read and write processes.
958  *
959  */
960 
961     if ( whatami == READWRITE )
962 	while ( readline() != FALSE ) ;
963 
964 }   /* End of clearline */
965 
966 /*****************************************************************************/
967 
sendsignal(sig)968 sendsignal(sig)
969 
970     int		sig;			/* this goes to the other process */
971 
972 {
973 
974 /*
975  *
976  * Sends signal sig to the other process if we're running as separate read and
977  * write processes. Returns the result of the kill if there's someone else to
978  * signal or -1 if we're running alone.
979  *
980  */
981 
982     if ( whatami != READWRITE && otherpid > 1 )
983 	return(kill(otherpid, sig));
984 
985     return(-1);
986 
987 }   /* End of sendsignal */
988 
989 /*****************************************************************************/
990 
interrupt(sig)991 void interrupt(sig)
992 
993     int		sig;			/* signal that we caught */
994 
995 {
996 
997 /*
998  *
999  * Caught a signal - all except joinsig cause the program to quit. joinsig is the
1000  * signal sent by the writer to the reader after all the jobs have been transmitted.
1001  * Used to tell the read process when it can start looking for the end of the job.
1002  *
1003  */
1004 
1005     signal(sig, SIG_IGN);
1006 
1007     if ( sig != joinsig )  {
1008 	x_stat |= FATAL;
1009 	if ( canread == TRUE )
1010 	    if ( interactive == FALSE )
1011 		error(NON_FATAL, "signal %d abort", sig);
1012 	    else error(NON_FATAL, "quitting");
1013 	quit(sig);
1014     }	/* End if */
1015 
1016     writedone = TRUE;
1017     signal(joinsig, interrupt);
1018 
1019 }   /* End of interrupt */
1020 
1021 /*****************************************************************************/
1022 
logit(mesg,a1,a2,a3)1023 logit(mesg, a1, a2, a3)
1024 
1025     char	*mesg;			/* control string */
1026     unsigned	a1, a2, a3;		/* and possible arguments */
1027 
1028 {
1029 
1030 /*
1031  *
1032  * Simple routine that's used to write a message to the log file.
1033  *
1034  */
1035 
1036     if ( mesg != NULL && fp_log != NULL )  {
1037 	fprintf(fp_log, mesg, a1, a2, a3);
1038 	fflush(fp_log);
1039     }	/* End if */
1040 
1041 }   /* End of logit */
1042 
1043 /*****************************************************************************/
1044 
error(kind,mesg,a1,a2,a3)1045 error(kind, mesg, a1, a2, a3)
1046 
1047     int		kind;			/* FATAL or NON_FATAL error */
1048     char	*mesg;			/* error message control string */
1049     unsigned	a1, a2, a3;		/* control string arguments */
1050 
1051 {
1052 
1053     FILE	*fp_err;
1054 
1055 /*
1056  *
1057  * Called when we've run into some kind of program error. First *mesg is printed
1058  * using the control string arguments a?. If kind is FATAL and we're not ignoring
1059  * errors the program will be terminated. If mesg is NULL or *mesg is the NULL
1060  * string nothing will be printed.
1061  *
1062  */
1063 
1064     fp_err = (fp_log != NULL) ? fp_log : stderr;
1065 
1066     if ( mesg != NULL && *mesg != '\0' )  {
1067 	fprintf(fp_err, "%s: ", prog_name);
1068 	fprintf(fp_err, mesg, a1, a2, a3);
1069 	putc('\n', fp_err);
1070     }	/* End if */
1071 
1072     x_stat |= kind;
1073 
1074     if ( kind != NON_FATAL && ignore == OFF )
1075 	quit(SIGTERM);
1076 
1077 }   /* End of error */
1078 
1079 /*****************************************************************************/
1080 
quit(sig)1081 quit(sig)
1082 
1083     int		sig;
1084 
1085 {
1086 
1087     int		w;
1088 
1089 /*
1090  *
1091  * Makes sure everything is properly cleaned up if there's a signal or FATAL error
1092  * that should cause the program to terminate. The sleep by the write process is
1093  * to help give the reset sequence a chance to reach the printer before we break
1094  * the connection - primarily for printers connected to Datakit. There's a very
1095  * slight chance the reset sequence that's sent to the printer could get us stuck
1096  * here. Simplest solution is don't bother to send it - everything works without it.
1097  * Flushing ttyo would be better, but means yet another system dependent procedure
1098  * in ifdef.c! I'll leave things be for now.
1099  *
1100  * Obscure problem on PS-810 turbos says wait a bit after sending an interrupt.
1101  * Seem to remember the printer getting into a bad state immediately after the
1102  * top was opened when the toner light was on. A sleep after sending the ctrl-C
1103  * seemed to fix things.
1104  *
1105  */
1106 
1107     signal(sig, SIG_IGN);
1108     ignore = ON;
1109 
1110     while ( sendsignal(sig) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
1111 
1112     setupstdin(2);
1113 
1114     if ( currentstate != NOTCONNECTED ) {
1115 	if ( sendctrlC == TRUE ) {
1116 	    Write(ttyo, "\003", 1);
1117 	    Rest(1);			/* PS-810 turbo problem?? */
1118 	}   /* End if */
1119 	Write(ttyo, "\004", 1);
1120     }	/* End if */
1121 
1122     alarm(0);				/* prevents sleep() loop on V9 systems */
1123     Rest(2);
1124 
1125     exit(x_stat);
1126 
1127 }   /* End of quit */
1128 
1129 /*****************************************************************************/
1130 
Rest(t)1131 Rest(t)
1132 
1133     int		t;
1134 
1135 {
1136 
1137 /*
1138  *
1139  * Used to replace sleep() calls. Only needed if we're running the program as
1140  * a read and write process and don't want to have the read process sleep. Most
1141  * sleeps are in the code because of the non-blocking read used by the single
1142  * process implementation. Probably should be a macro.
1143  *
1144  */
1145 
1146     if ( t > 0 && canwrite == TRUE )
1147 	sleep(t);
1148 
1149 }   /* End of Rest */
1150 
1151 /*****************************************************************************/
1152 
Read(fd,buf,n)1153 Read(fd, buf, n)
1154 
1155     int		fd;
1156     char	*buf;
1157     int		n;
1158 
1159 {
1160 
1161     int		count;
1162 
1163 /*
1164  *
1165  * Used to replace some of the read() calls. Only needed if we're running separate
1166  * read and write processes. Should only be used to replace read calls on ttyi.
1167  * Always returns 0 to the caller if the process doesn't have its READ flag set.
1168  * Probably should be a macro.
1169  *
1170  */
1171 
1172     if ( canread == TRUE )  {
1173 	if ( (count = read(fd, buf, n)) == -1 && errno == EINTR )
1174 	    count = 0;
1175     } else count = 0;
1176 
1177     return(count);
1178 
1179 }   /* End of Read */
1180 
1181 /*****************************************************************************/
1182 
Write(fd,buf,n)1183 Write(fd, buf, n)
1184 
1185     int		fd;
1186     char	*buf;
1187     int		n;
1188 
1189 {
1190 
1191     int		count;
1192 
1193 /*
1194  *
1195  * Used to replace some of the write() calls. Again only needed if we're running
1196  * separate read and write processes. Should only be used to replace write calls
1197  * on ttyo. Always returns n to the caller if the process doesn't have its WRITE
1198  * flag set. Should also probably be a macro.
1199  *
1200  */
1201 
1202     if ( canwrite == TRUE )  {
1203 	if ( (count = write(fd, buf, n)) == -1 && errno == EINTR )
1204 	    count = n;
1205     } else count = n;
1206 
1207     return(count);
1208 
1209 }   /* End of Write */
1210 
1211 /*****************************************************************************/
1212 
1213