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