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