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
117char	*prog_name = "postcomm";	/* just for error messages */
118
119int	debug = OFF;		/* debug flag */
120int	ignore = OFF;		/* what's done for FATAL errors */
121
122
123char	*block = NULL;		/* input file buffer */
124int	blocksize = BLOCKSIZE;	/* and its size in bytes */
125int	head = 0;		/* block[head] is the next character */
126int	tail = 0;		/* one past the last byte in block[] */
127
128char	mesg[BUFSIZE];		/* exactly what came back on ttyi */
129char	sbuf[BUFSIZE];		/* for parsing the message */
130int	next = 0;		/* next character goes in sbuf[next] */
131Status	status[] = STATUS;	/* for converting status strings */
132
133int	stopbits = 1;		/* number of stop bits */
134int	tostdout = FALSE;	/* non-status stuff goes to stdout? */
135int	curfile = 0;		/* only needed when tostdout is TRUE */
136
137char	*postbegin = POSTBEGIN;	/* preceeds all the input files */
138
139int	ttyi;			/* input */
140int	ttyo = 2;		/* and output file descriptors */
141
142FILE	*fp_log = stderr;	/* log file for data from the printer */
143
144
145
146static void filter(void);
147static int getstatus(int);
148static void initialize(void);
149static void options(int, char *[]);
150static int readblock(int);
151static int readline(void);
152static void reset(void);
153static int writeblock(void);
154
155void
156logit(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
182void
183error(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
219int
220main(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
252static void
253options(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
323static void
324initialize(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
388static void
389filter(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
477static int
478readblock(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
510static int
511writeblock(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
538static int
539getstatus(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
595static void
596reset(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
682static int
683readline(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