xref: /illumos-gate/usr/src/cmd/lp/filter/postscript/postcomm/postcomm.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 /*
33  *
34  * A simple program that can be used to filter jobs for PostScript
35  * printers. It's a cleaned up version of usg_iox, that I assume was
36  * written by Richard Flood.  The most important addition includes some
37  * simple processing of printer status reports, usually obtained when
38  * \024 is sent to the printer. The returned status lines look like:
39  *
40  *
41  *	%%[ status: idle; source serial 25 ]%%
42  *	%%[ status: waiting; source serial 25 ]%%
43  *	%%[ status: initializing; source serial 25 ]%%
44  *	%%[ status: busy; source serial 25 ]%%
45  *	%%[ status: printing; source serial 25 ]%%
46  *	%%[ status: PrinterError: out of paper; source serial 25 ]%%
47  *	%%[ status: PrinterError: no paper tray; source serial 25 ]%%
48  *
49  *
50  * although the list isn't meant to be complete.
51  *
52  * Other changes to the original program include the addition of
53  * options that let you select the tty line, baud rate, and printer
54  * log file. The program seems to work reasonably well, at least for
55  * our QMS PS-800 printer, but could still use some work.
56  *
57  * There were a couple of serious mistakes in the first few versions of
58  * postcomm.  Both were made in setting up flow control in routine
59  * initialize(). Setting the IXANY flag in c_iflag was wrong, and
60  * often caused problems when the printer transmitted a spontaneous
61  * status report, which always happens when the paper runs out.
62  * Things were kludged up to get around the problems, but they were
63  * never exactly right, and probably could never be guaranteed to work
64  * 100%.
65  *
66  * The other mistake was setting the IXOFF flag, again in c_iflag.
67  * Although I never saw deadlock in the original versions of postcomm,
68  * it could happen.  Apparently the IXANY and IXOFF flags combined to
69  * make that an unlikely event.  Anyway both flags should normally be
70  * turned off to ensure reliable transmission of jobs.
71  *
72  * The implications of only setting IXON are obvious. Job transmission
73  * should be reliable, but data returned by the printer over the tty
74  * line may get lost. That won't cause problems in postcomm, but there
75  * may be occasions when you want to run a job and recover data
76  * generated by the printer. The -t option sets the IXOFF, IXANY, and
77  * IXON flags in c_iflag and causes send() to be far more careful about
78  * when data is sent to the printer. In addition anything not
79  * recognized as a status report is written on stdout. It seems to
80  * work reasonably well, but it's slow and may hang or have flow
81  * control problems. Only use the -t option when it's absolutely
82  * necessary. A small block size, like 512, should also help.
83  *
84  * Using two processes, one for reads and the other for writes, may
85  * eventually be needed. For now postcomm seems to do a good job
86  * transmitting data, and the -t option is probably acceptable for
87  * those few jobs that need to recover data from the printer.
88  *
89  * A typical command line might be:
90  *
91  *	postcomm -L log -t <file1 > device
92  *
93  * where -L selects the printer log file and -t sends data from the
94  * printer out to the printer.  If you don't choose a log file stderr
95  * will be used and the information mailed or written to you.
96  *
97  */
98 
99 #include <stdio.h>
100 #include <stdarg.h>
101 #include <ctype.h>
102 #include <fcntl.h>
103 #include <signal.h>
104 #include <sys/types.h>
105 
106 
107 # define	OFF		0
108 # define	ON		1
109 # define	TRUE		1
110 # define	FALSE		0
111 # define	FATAL		1
112 # define	NON_FATAL	0
113 
114 #include "postcomm.h"		/* some special definitions */
115 
116 
117 char	*prog_name = "postcomm";	/* just for error messages */
118 
119 int	debug = OFF;		/* debug flag */
120 int	ignore = OFF;		/* what's done for FATAL errors */
121 
122 
123 char	*block = NULL;		/* input file buffer */
124 int	blocksize = BLOCKSIZE;	/* and its size in bytes */
125 int	head = 0;		/* block[head] is the next character */
126 int	tail = 0;		/* one past the last byte in block[] */
127 
128 char	mesg[BUFSIZE];		/* exactly what came back on ttyi */
129 char	sbuf[BUFSIZE];		/* for parsing the message */
130 int	next = 0;		/* next character goes in sbuf[next] */
131 Status	status[] = STATUS;	/* for converting status strings */
132 
133 int	stopbits = 1;		/* number of stop bits */
134 int	tostdout = FALSE;	/* non-status stuff goes to stdout? */
135 int	curfile = 0;		/* only needed when tostdout is TRUE */
136 
137 char	*postbegin = POSTBEGIN;	/* preceeds all the input files */
138 
139 int	ttyi;			/* input */
140 int	ttyo = 2;		/* and output file descriptors */
141 
142 FILE	*fp_log = stderr;	/* log file for data from the printer */
143 
144 
145 
146 void
147 logit(char *mesg, ...)
148 {
149 
150 /*
151  *
152  * Simple routine that's used to write a message to the log file.
153  *
154  */
155 
156 
157     if (mesg != NULL)
158     {
159 	va_list ap;
160 
161 	va_start(ap, mesg);
162 	vfprintf(fp_log, mesg, ap);
163 	va_end(ap);
164 	fflush(fp_log);
165     }
166 
167 }   /* End of logit */
168 
169 
170 
171 
172 
173 void
174 error(int kind, char *mesg, ...)
175 {
176 
177 
178 /*
179  *
180  * Called when we've run into some kind of program error. First *mesg is
181  * printed using the control string arguments a?. Then if kind is FATAL
182  * and we're not ignoring errors the program will be terminated.
183  *
184  * If mesg is NULL or *mesg is the NULL string nothing will be printed.
185  *
186  */
187 
188 
189     if ( mesg != NULL  &&  *mesg != '\0' )  {
190 	va_list ap;
191 
192 	fprintf(fp_log, "%s: ", prog_name);
193 	va_start(ap, mesg);
194 	vfprintf(fp_log, mesg, ap);
195 	va_end(ap);
196 	putc('\n', fp_log);
197     }	/* End if */
198 
199     if ( kind == FATAL  &&  ignore == OFF )  {
200 	write(ttyo, "\003\004", 2);
201 	exit(1);
202     }	/* End if */
203 
204 }   /* End of error */
205 
206 
207 
208 
209 
210 main(argc, argv)
211 int	argc;
212 char	*argv[];
213 {
214 
215 /*
216  *
217  * A simple program that manages input and output for PostScript
218  * printers. If you're sending a PostScript program that will be
219  * returning useful information add the -ot option to the lp(1) command
220  * line. Everything not recognized as a printer status report will go
221  * to stdout. The -ot option should only be used when needed! It's slow
222  * and doesn't use flow control properly, but it's probably the best
223  * that can be done using a single process for reading and writing.
224  */
225 
226     prog_name = argv[0];	/* really just for error messages */
227 
228     options(argv, argc);
229 
230     initialize();		/* Set printer up for printing */
231 
232     filter();
233 
234     reset();			/* wait 'til it's finished & reset it*/
235 
236     exit(0);		/* everything probably went OK */
237 
238 }   /* End of main */
239 
240 
241 
242 
243 
244 
245 options(argc, argv)
246 int	argc;
247 char	*argv[];
248 {
249 
250 
251     int		ch;			/* return value from getopt() */
252     char	*names = "tB:L:P:DI";
253 
254     extern char	*optarg;		/* used by getopt() */
255 
256 /*
257  *
258  * Reads and processes the command line options.  The -t option should
259  * only be used when absolutely necessary.  It's slow and doesn't do
260  * flow control properly.  Selecting a small block size (eg. 512 or
261  * less) with with the -B option may help when you need the -t option.
262  *
263  */
264 
265 
266     while ( (ch = getopt(argc, argv, names)) != EOF )
267     {
268 	switch ( ch )
269 	{
270 	    case 't':		/* non-status stuff goes to stdout */
271 		    tostdout = TRUE;
272 		    break;
273 
274 	    case 'B':		/* set the job buffer size */
275 		    if ((blocksize = atoi(optarg)) <= 0)
276 			blocksize = BLOCKSIZE;
277 		    break;
278 
279 	    case 'L':			/* printer log file */
280 		    if ((fp_log = fopen(optarg, "w")) == NULL)
281 		    {
282 			fp_log = stderr;
283 			error(NON_FATAL, "can't open log file %s",
284 								optarg);
285 		    }	/* End if */
286 		    break;
287 
288 	    case 'P':			/* initial PostScript program */
289 		    postbegin = optarg;
290 		    break;
291 
292 	    case 'D':			/* debug flag */
293 		    debug = ON;
294 		    break;
295 
296 	    case 'I':			/* ignore FATAL errors */
297 		    ignore = ON;
298 		    break;
299 
300 	    case '?':			/* don't understand the option */
301 		    error(FATAL, "");
302 		    break;
303 
304 	    default:			/* don't know what to do for ch */
305 		    error(FATAL, "missing case for option %c\n", ch);
306 		    break;
307 
308 	}   /* End switch */
309 
310     }   /* End while */
311 }   /* End of options */
312 
313 
314 
315 
316 
317 
318 initialize()
319 {
320     if ((block = malloc(blocksize)) == NULL)
321 	error(FATAL, "no memory");
322 
323     ttyi = fileno(stdout);
324 
325     if ((ttyo = dup(ttyi)) == -1)
326 	error(FATAL, "can't dup file descriptor for stdout");
327 
328 /*
329  *
330  * Makes sure the printer is in the
331  * IDLE state before any real data is sent.
332  *
333  */
334 
335 
336     logit("printer startup\n");
337 
338     while ( 1 )
339 	switch (getstatus(1))
340 	{
341 	    case IDLE:
342 		    if (postbegin != NULL)
343 			write(ttyo, postbegin, strlen(postbegin));
344 		    else
345 			write(ttyo, "\n", 1);
346 		    return;
347 
348 	    case WAITING:
349 	    case BUSY:
350 	    case ERROR:
351 		    write(ttyo, "\003\004", 2);
352 		    sleep(1);
353 		    break;
354 
355 	    case FLUSHING:
356 		    write(ttyo, "\004", 1);
357 		    sleep(1);
358 		    break;
359 
360 	    case PRINTERERROR:
361 	    case INITIALIZING:
362 		    sleep(15);
363 		    break;
364 
365 	    case DISCONNECT:
366 		    /* talk to spooler w/S_FAULT_ALERT */
367 		    error(FATAL, "printer appears to be offline");
368 		    break;
369 
370 	    default:
371 		    sleep(1);
372 		    break;
373 
374 	}   /* End switch */
375 
376 }   /* End of initialize */
377 
378 
379 
380 
381 
382 
383 filter()
384 {
385     static int	wflag = 0;	/* nonzero if we've written a block */
386     int		fd_in = fileno(stdin);
387 
388 /*
389  *
390  * Responsible for sending the next file to the printer.
391  * Most of the hard stuff is done in getstatus() and readline().
392  * All this routine really does is control what happens for the
393  * different printer states.
394  *
395  */
396 
397 
398     logit("sending file\n");
399 
400     curfile++;
401 
402     while (readblock(fd_in))
403 	switch (getstatus(0))
404 	{
405 	    case WAITING:
406 		    writeblock();
407 		    wflag = 1;
408 		    break;
409 
410 	    case BUSY:
411 	    case PRINTING:
412 	    case PRINTERERROR:
413 		    if (tostdout == FALSE)
414 		    {
415 			writeblock();
416 			wflag = 1;
417 		    }
418 		    else
419 			sleep(1);
420 		    break;
421 
422 	    case UNKNOWN:
423 		    if (tostdout == FALSE)
424 		    {
425 			writeblock();
426 			wflag = 1;
427 		    }
428 		    break;
429 
430 	    case NOSTATUS:
431 		    if (tostdout == FALSE)
432 		    {
433 			if (wflag)
434 			    writeblock();
435 		    }
436 		    else
437 			sleep(1);
438 		    break;
439 
440 	    case IDLE:
441 		    if (wflag)
442 			error(FATAL, "printer is idle");
443 		    write(ttyo, "\n", 1);
444 		    break;
445 
446 	    case ERROR:
447 		    fprintf(stderr, "%s", mesg);	/* for csw */
448 		    error(FATAL, "PostScript error");
449 		    break;
450 
451 	    case FLUSHING:
452 		    error(FATAL, "PostScript error");
453 		    break;
454 
455 	    case INITIALIZING:
456 		    error(FATAL, "printer booting");
457 		    break;
458 
459 	    case DISCONNECT:
460 		    error(FATAL, "printer appears to be offline");
461 		    break;
462 
463 	}   /* End switch */
464 
465 }   /* End of print */
466 
467 
468 
469 
470 
471 
472 readblock(fd_in)
473 int	fd_in;			/* current input file */
474 {
475 
476 /*
477  *
478  * Fills the input buffer with the next block, provided we're all done
479  * with the last one.  Blocks from fd_in are stored in array block[].
480  * Head is the index of the next byte in block[] that's supposed to go
481  * to the printer.   tail points one past the last byte in the current
482  * block.  head is adjusted in writeblock() after each successful
483  * write, while head and tail are reset here each time a new block is
484  * read.  Returns the number of bytes left in the current block.   Read
485  * errors cause the program to abort.
486  *
487  */
488 
489     if (head >= tail)
490     {		/* done with the last block */
491 	if ((tail = read(fd_in, block, blocksize)) == -1)
492 	    error(FATAL, "error reading input file");
493 	head = 0;
494     }
495 
496     return(tail - head);
497 
498 }   /* End of readblock */
499 
500 
501 
502 
503 
504 
505 writeblock()
506 {
507     int		count;		/* bytes successfully written */
508 
509 /*
510  *
511  * Called from send() when it's OK to send the next block to the
512  * printer. head is adjusted after the write, and the number of bytes
513  * that were successfully written is returned to the caller.
514  *
515  */
516 
517 
518     if ((count = write(ttyo, &block[head], tail - head)) == -1)
519 	error(FATAL, "error writing to stdout");
520     else
521 	if (count == 0)
522 	    error(FATAL, "printer appears to be offline");
523 
524     head += count;
525     return(count);
526 }   /* End of writeblock */
527 
528 
529 
530 
531 
532 
533 getstatus(t)
534 int	t;			/* sleep time after sending '\024' */
535 {
536     char	*state;		/* new printer state - from sbuf[] */
537     int		i;		/* index of new state in status[] */
538     static int	laststate = NOSTATUS;
539 				/* last state we found out about */
540 
541 
542 /*
543  *
544  * Sends a status request to the printer and tries to read the response.
545  * If an entire line is available readline() returns TRUE and the
546  * string in sbuf[] is parsed and converted into an integer code that
547  * represents the printer's state.  If readline() returns FALSE,
548  * meaning an entire line wasn't available, NOSTATUS is returned.
549  *
550  */
551 
552     if (readline() == TRUE)
553     {
554 	state = sbuf;
555 
556 	if (strncmp(sbuf, "%%[", 3) == 0)
557 	{
558 	    strtok(sbuf, " ");		/* skip the leading "%%[ " */
559 	    if (strcmp(state = strtok(NULL, " :;"), "status") == 0)
560 		state = strtok(NULL, " :;");
561 	}
562 
563 	for (i = 0; status[i].state != NULL; i++)
564 	    if (strcmp(state, status[i].state) == 0)
565 		break;
566 
567 	if (status[i].val != laststate || debug == ON)
568 	    logit("%s", mesg);
569 
570 	if (tostdout == TRUE && status[i].val == UNKNOWN && curfile > 0)
571 	    fprintf(stdout, "%s", mesg);
572 
573 	return(laststate = status[i].val);
574     }	/* End if */
575 
576     if ( write(ttyo, "\024", 1) != 1 )
577 	error(FATAL, "printer appears to be offline");
578 
579     if ( t > 0 )
580 	sleep(t);
581 
582     return(NOSTATUS);
583 
584 }   /* End of getstatus */
585 
586 
587 
588 
589 
590 
591 reset()
592 {
593     int		sleeptime = 15;		/* for 'out of paper' etc. */
594     int		senteof = FALSE;
595 
596 /*
597  *
598  * We're all done sending the input files, so we'll send an EOF to the
599  * printer and wait until it tells us it's done.
600  *
601  */
602 
603 
604     logit("waiting for end of job\n");
605 
606     while (1)
607     {
608 	switch (getstatus(2))
609 	{
610 	    case WAITING:
611 		write(ttyo, "\004", 1);
612 		senteof = TRUE;
613 		sleeptime = 15;
614 		break;
615 
616 	    case ENDOFJOB:
617 		if (senteof == TRUE)
618 		{
619 		    logit("job complete\n");
620 		    return;
621 		}
622 		sleeptime = 15;
623 		break;
624 
625 	    case BUSY:
626 	    case PRINTING:
627 		sleeptime = 15;
628 		sleep(1);
629 		break;
630 
631 	    case PRINTERERROR:
632 		sleep(sleeptime++);
633 		break;
634 
635 	    case ERROR:
636 		fprintf(stderr, "%s", mesg);	/* for csw */
637 		error(FATAL, "PostScript error");
638 		return;
639 
640 	    case FLUSHING:
641 		error(FATAL, "PostScript error");
642 		return;
643 
644 	    case IDLE:
645 		error(FATAL, "printer is idle");
646 		return;
647 
648 	    case INITIALIZING:
649 		error(FATAL, "printer booting");
650 		return;
651 
652 	    case DISCONNECT:
653 		error(FATAL, "printer appears to be offline");
654 		return;
655 
656 	    default:
657 		sleep(1);
658 		break;
659 
660 	}   /* End switch */
661 
662 	if (sleeptime > 60)
663 	    sleeptime = 60;
664 
665     }	/* End while */
666 
667 }   /* End of reset */
668 
669 
670 
671 
672 
673 
674 
675 
676 
677 
678 readline()
679 {
680     char	ch;			/* next character from ttyi */
681     int		n;			/* read() return value */
682 
683 /*
684  *
685  * Reads the printer's tty line up to a newline (or EOF) or until no
686  * more characters are available. As characters are read they're
687  * converted to lower case and put in sbuf[next] until a newline (or
688  * EOF) are found. The string is then terminated with '\0', next is
689  * reset to zero, and TRUE is returned.
690  *
691  */
692 
693 
694     while ((n = read(ttyi, &ch, 1)) != 0)
695     {
696 	if (n < 0)
697 	    error(FATAL, "error reading stdout");
698 	mesg[next] = ch;
699 	sbuf[next++] = tolower(ch);
700 	if (ch == '\n'  ||  ch == '\004')
701 	{
702 	    mesg[next] = sbuf[next] = '\0';
703 	    if (ch == '\004')
704 		sprintf(sbuf, "%%%%[ status: endofjob ]%%%%\n");
705 	    next = 0;
706 	    return(TRUE);
707 	}   /* End if */
708     }	/* End while */
709 
710     return(FALSE);
711 
712 }   /* End of readline */
713