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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
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 static void filter(void);
147 static int getstatus(int);
148 static void initialize(void);
149 static void options(int, char *[]);
150 static int readblock(int);
151 static int readline(void);
152 static void reset(void);
153 static int writeblock(void);
154 
155 void
156 logit(char *mesg, ...)
157 {
158 
159 /*
160  *
161  * Simple routine that's used to write a message to the log file.
162  *
163  */
164 
165 
166     if (mesg != NULL)
167     {
168 	va_list ap;
169 
170 	va_start(ap, mesg);
171 	vfprintf(fp_log, mesg, ap);
172 	va_end(ap);
173 	fflush(fp_log);
174     }
175 
176 }   /* End of logit */
177 
178 
179 
180 
181 
182 void
183 error(int kind, char *mesg, ...)
184 {
185 
186 
187 /*
188  *
189  * Called when we've run into some kind of program error. First *mesg is
190  * printed using the control string arguments a?. Then if kind is FATAL
191  * and we're not ignoring errors the program will be terminated.
192  *
193  * If mesg is NULL or *mesg is the NULL string nothing will be printed.
194  *
195  */
196 
197 
198     if ( mesg != NULL  &&  *mesg != '\0' )  {
199 	va_list ap;
200 
201 	fprintf(fp_log, "%s: ", prog_name);
202 	va_start(ap, mesg);
203 	vfprintf(fp_log, mesg, ap);
204 	va_end(ap);
205 	putc('\n', fp_log);
206     }	/* End if */
207 
208     if ( kind == FATAL  &&  ignore == OFF )  {
209 	write(ttyo, "\003\004", 2);
210 	exit(1);
211     }	/* End if */
212 
213 }   /* End of error */
214 
215 
216 
217 
218 
219 int
220 main(int argc, char *argv[])
221 {
222 
223 /*
224  *
225  * A simple program that manages input and output for PostScript
226  * printers. If you're sending a PostScript program that will be
227  * returning useful information add the -ot option to the lp(1) command
228  * line. Everything not recognized as a printer status report will go
229  * to stdout. The -ot option should only be used when needed! It's slow
230  * and doesn't use flow control properly, but it's probably the best
231  * that can be done using a single process for reading and writing.
232  */
233 
234     prog_name = argv[0];	/* really just for error messages */
235 
236     options(argc, argv);
237 
238     initialize();		/* Set printer up for printing */
239 
240     filter();
241 
242     reset();			/* wait 'til it's finished & reset it*/
243 
244     return (0);		/* everything probably went OK */
245 
246 }   /* End of main */
247 
248 
249 
250 
251 
252 static void
253 options(int argc, char *argv[])
254 {
255 
256 
257     int		ch;			/* return value from getopt() */
258     char	*names = "tB:L:P:DI";
259 
260     extern char	*optarg;		/* used by getopt() */
261 
262 /*
263  *
264  * Reads and processes the command line options.  The -t option should
265  * only be used when absolutely necessary.  It's slow and doesn't do
266  * flow control properly.  Selecting a small block size (eg. 512 or
267  * less) with with the -B option may help when you need the -t option.
268  *
269  */
270 
271 
272     while ( (ch = getopt(argc, argv, names)) != EOF )
273     {
274 	switch ( ch )
275 	{
276 	    case 't':		/* non-status stuff goes to stdout */
277 		    tostdout = TRUE;
278 		    break;
279 
280 	    case 'B':		/* set the job buffer size */
281 		    if ((blocksize = atoi(optarg)) <= 0)
282 			blocksize = BLOCKSIZE;
283 		    break;
284 
285 	    case 'L':			/* printer log file */
286 		    if ((fp_log = fopen(optarg, "w")) == NULL)
287 		    {
288 			fp_log = stderr;
289 			error(NON_FATAL, "can't open log file %s",
290 								optarg);
291 		    }	/* End if */
292 		    break;
293 
294 	    case 'P':			/* initial PostScript program */
295 		    postbegin = optarg;
296 		    break;
297 
298 	    case 'D':			/* debug flag */
299 		    debug = ON;
300 		    break;
301 
302 	    case 'I':			/* ignore FATAL errors */
303 		    ignore = ON;
304 		    break;
305 
306 	    case '?':			/* don't understand the option */
307 		    error(FATAL, "");
308 		    break;
309 
310 	    default:			/* don't know what to do for ch */
311 		    error(FATAL, "missing case for option %c\n", ch);
312 		    break;
313 
314 	}   /* End switch */
315 
316     }   /* End while */
317 }   /* End of options */
318 
319 
320 
321 
322 
323 static void
324 initialize(void)
325 {
326     if ((block = malloc(blocksize)) == NULL)
327 	error(FATAL, "no memory");
328 
329     ttyi = fileno(stdout);
330 
331     if ((ttyo = dup(ttyi)) == -1)
332 	error(FATAL, "can't dup file descriptor for stdout");
333 
334 /*
335  *
336  * Makes sure the printer is in the
337  * IDLE state before any real data is sent.
338  *
339  */
340 
341 
342     logit("printer startup\n");
343 
344     while ( 1 )
345 	switch (getstatus(1))
346 	{
347 	    case IDLE:
348 		    if (postbegin != NULL)
349 			write(ttyo, postbegin, strlen(postbegin));
350 		    else
351 			write(ttyo, "\n", 1);
352 		    return;
353 
354 	    case WAITING:
355 	    case BUSY:
356 	    case ERROR:
357 		    write(ttyo, "\003\004", 2);
358 		    sleep(1);
359 		    break;
360 
361 	    case FLUSHING:
362 		    write(ttyo, "\004", 1);
363 		    sleep(1);
364 		    break;
365 
366 	    case PRINTERERROR:
367 	    case INITIALIZING:
368 		    sleep(15);
369 		    break;
370 
371 	    case DISCONNECT:
372 		    /* talk to spooler w/S_FAULT_ALERT */
373 		    error(FATAL, "printer appears to be offline");
374 		    break;
375 
376 	    default:
377 		    sleep(1);
378 		    break;
379 
380 	}   /* End switch */
381 
382 }   /* End of initialize */
383 
384 
385 
386 
387 
388 static void
389 filter(void)
390 {
391     static int	wflag = 0;	/* nonzero if we've written a block */
392     int		fd_in = fileno(stdin);
393 
394 /*
395  *
396  * Responsible for sending the next file to the printer.
397  * Most of the hard stuff is done in getstatus() and readline().
398  * All this routine really does is control what happens for the
399  * different printer states.
400  *
401  */
402 
403 
404     logit("sending file\n");
405 
406     curfile++;
407 
408     while (readblock(fd_in))
409 	switch (getstatus(0))
410 	{
411 	    case WAITING:
412 		    writeblock();
413 		    wflag = 1;
414 		    break;
415 
416 	    case BUSY:
417 	    case PRINTING:
418 	    case PRINTERERROR:
419 		    if (tostdout == FALSE)
420 		    {
421 			writeblock();
422 			wflag = 1;
423 		    }
424 		    else
425 			sleep(1);
426 		    break;
427 
428 	    case UNKNOWN:
429 		    if (tostdout == FALSE)
430 		    {
431 			writeblock();
432 			wflag = 1;
433 		    }
434 		    break;
435 
436 	    case NOSTATUS:
437 		    if (tostdout == FALSE)
438 		    {
439 			if (wflag)
440 			    writeblock();
441 		    }
442 		    else
443 			sleep(1);
444 		    break;
445 
446 	    case IDLE:
447 		    if (wflag)
448 			error(FATAL, "printer is idle");
449 		    write(ttyo, "\n", 1);
450 		    break;
451 
452 	    case ERROR:
453 		    fprintf(stderr, "%s", mesg);	/* for csw */
454 		    error(FATAL, "PostScript error");
455 		    break;
456 
457 	    case FLUSHING:
458 		    error(FATAL, "PostScript error");
459 		    break;
460 
461 	    case INITIALIZING:
462 		    error(FATAL, "printer booting");
463 		    break;
464 
465 	    case DISCONNECT:
466 		    error(FATAL, "printer appears to be offline");
467 		    break;
468 
469 	}   /* End switch */
470 
471 }   /* End of print */
472 
473 
474 
475 
476 
477 static int
478 readblock(int fd_in)
479     /* current input file */
480 {
481 
482 /*
483  *
484  * Fills the input buffer with the next block, provided we're all done
485  * with the last one.  Blocks from fd_in are stored in array block[].
486  * Head is the index of the next byte in block[] that's supposed to go
487  * to the printer.   tail points one past the last byte in the current
488  * block.  head is adjusted in writeblock() after each successful
489  * write, while head and tail are reset here each time a new block is
490  * read.  Returns the number of bytes left in the current block.   Read
491  * errors cause the program to abort.
492  *
493  */
494 
495     if (head >= tail)
496     {		/* done with the last block */
497 	if ((tail = read(fd_in, block, blocksize)) == -1)
498 	    error(FATAL, "error reading input file");
499 	head = 0;
500     }
501 
502     return(tail - head);
503 
504 }   /* End of readblock */
505 
506 
507 
508 
509 
510 static int
511 writeblock(void)
512 {
513     int		count;		/* bytes successfully written */
514 
515 /*
516  *
517  * Called from send() when it's OK to send the next block to the
518  * printer. head is adjusted after the write, and the number of bytes
519  * that were successfully written is returned to the caller.
520  *
521  */
522 
523 
524     if ((count = write(ttyo, &block[head], tail - head)) == -1)
525 	error(FATAL, "error writing to stdout");
526     else
527 	if (count == 0)
528 	    error(FATAL, "printer appears to be offline");
529 
530     head += count;
531     return(count);
532 }   /* End of writeblock */
533 
534 
535 
536 
537 
538 static int
539 getstatus(int t)
540     /* sleep time after sending '\024' */
541 {
542     char	*state;		/* new printer state - from sbuf[] */
543     int		i;		/* index of new state in status[] */
544     static int	laststate = NOSTATUS;
545 				/* last state we found out about */
546 
547 /*
548  *
549  * Sends a status request to the printer and tries to read the response.
550  * If an entire line is available readline() returns TRUE and the
551  * string in sbuf[] is parsed and converted into an integer code that
552  * represents the printer's state.  If readline() returns FALSE,
553  * meaning an entire line wasn't available, NOSTATUS is returned.
554  *
555  */
556 
557     if (readline() == TRUE)
558     {
559 	state = sbuf;
560 
561 	if (strncmp(sbuf, "%%[", 3) == 0)
562 	{
563 	    strtok(sbuf, " ");		/* skip the leading "%%[ " */
564 	    if (strcmp(state = strtok(NULL, " :;"), "status") == 0)
565 		state = strtok(NULL, " :;");
566 	}
567 
568 	for (i = 0; status[i].state != NULL; i++)
569 	    if (strcmp(state, status[i].state) == 0)
570 		break;
571 
572 	if (status[i].val != laststate || debug == ON)
573 	    logit("%s", mesg);
574 
575 	if (tostdout == TRUE && status[i].val == UNKNOWN && curfile > 0)
576 	    fprintf(stdout, "%s", mesg);
577 
578 	return(laststate = status[i].val);
579     }	/* End if */
580 
581     if ( write(ttyo, "\024", 1) != 1 )
582 	error(FATAL, "printer appears to be offline");
583 
584     if ( t > 0 )
585 	sleep(t);
586 
587     return(NOSTATUS);
588 
589 }   /* End of getstatus */
590 
591 
592 
593 
594 
595 static void
596 reset(void)
597 {
598     int		sleeptime = 15;		/* for 'out of paper' etc. */
599     int		senteof = FALSE;
600 
601 /*
602  *
603  * We're all done sending the input files, so we'll send an EOF to the
604  * printer and wait until it tells us it's done.
605  *
606  */
607 
608 
609     logit("waiting for end of job\n");
610 
611     while (1)
612     {
613 	switch (getstatus(2))
614 	{
615 	    case WAITING:
616 		write(ttyo, "\004", 1);
617 		senteof = TRUE;
618 		sleeptime = 15;
619 		break;
620 
621 	    case ENDOFJOB:
622 		if (senteof == TRUE)
623 		{
624 		    logit("job complete\n");
625 		    return;
626 		}
627 		sleeptime = 15;
628 		break;
629 
630 	    case BUSY:
631 	    case PRINTING:
632 		sleeptime = 15;
633 		sleep(1);
634 		break;
635 
636 	    case PRINTERERROR:
637 		sleep(sleeptime++);
638 		break;
639 
640 	    case ERROR:
641 		fprintf(stderr, "%s", mesg);	/* for csw */
642 		error(FATAL, "PostScript error");
643 		return;
644 
645 	    case FLUSHING:
646 		error(FATAL, "PostScript error");
647 		return;
648 
649 	    case IDLE:
650 		error(FATAL, "printer is idle");
651 		return;
652 
653 	    case INITIALIZING:
654 		error(FATAL, "printer booting");
655 		return;
656 
657 	    case DISCONNECT:
658 		error(FATAL, "printer appears to be offline");
659 		return;
660 
661 	    default:
662 		sleep(1);
663 		break;
664 
665 	}   /* End switch */
666 
667 	if (sleeptime > 60)
668 	    sleeptime = 60;
669 
670     }	/* End while */
671 
672 }   /* End of reset */
673 
674 
675 
676 
677 
678 
679 
680 
681 
682 static int
683 readline(void)
684 {
685     char	ch;			/* next character from ttyi */
686     int		n;			/* read() return value */
687 
688 /*
689  *
690  * Reads the printer's tty line up to a newline (or EOF) or until no
691  * more characters are available. As characters are read they're
692  * converted to lower case and put in sbuf[next] until a newline (or
693  * EOF) are found. The string is then terminated with '\0', next is
694  * reset to zero, and TRUE is returned.
695  *
696  */
697 
698 
699     while ((n = read(ttyi, &ch, 1)) != 0)
700     {
701 	if (n < 0)
702 	    error(FATAL, "error reading stdout");
703 	mesg[next] = ch;
704 	sbuf[next++] = tolower(ch);
705 	if (ch == '\n'  ||  ch == '\004')
706 	{
707 	    mesg[next] = sbuf[next] = '\0';
708 	    if (ch == '\004')
709 		sprintf(sbuf, "%%%%[ status: endofjob ]%%%%\n");
710 	    next = 0;
711 	    return(TRUE);
712 	}   /* End if */
713     }	/* End while */
714 
715     return(FALSE);
716 
717 }   /* End of readline */
718