1 /*
2 *
3 * postio - RS-232 serial interface for PostScript printers
4 *
5 * A simple program that manages input and output for PostScript printers. Much
6 * has been added and changed from early versions of the program, but the basic
7 * philosophy is still the same. Don't send real data until we're certain we've
8 * connected to a PostScript printer that's in the idle state and try to hold the
9 * connection until the job is completely done. It's more work than you might
10 * expect is necessary, but should provide a reasonably reliable spooler interface
11 * that can return error indications to the caller via the program's exit status.
12 *
13 * I've added code that will let you split the program into separate read/write
14 * processes. Although it's not the default it should be useful if you have a file
15 * that will be returning useful data from the printer. The two process stuff was
16 * laid down on top of the single process code and both methods still work. The
17 * implementation isn't as good as it could be, but didn't require many changes
18 * to the original program (despite the fact that there are now many differences).
19 *
20 * By default the program still runs as a single process. The -R2 option forces
21 * separate read and write processes after the intial connection is made. If you
22 * want that as the default initialize splitme (below) to TRUE. In addition the
23 * -t option that's used to force stuff not recognized as status reports to stdout
24 * also tries to run as two processes (by setting splitme to TRUE). It will only
25 * work if the required code (ie. resetline() in ifdef.c) has been implemented
26 * for your Unix system. I've only tested the System V code.
27 *
28 * Code needed to support interactive mode has also been added, although again it's
29 * not as efficient as it could be. It depends on the system dependent procedures
30 * resetline() and setupstdin() (file ifdef.c) and for now is only guaranteed to
31 * work on System V. Can be requested using the -i option.
32 *
33 * Quiet mode (-q option) is also new, but was needed for some printers connected
34 * to RADIAN. If you're running in quiet mode no status requests will be sent to
35 * the printer while files are being transmitted (ie. in send()).
36 *
37 * The program expects to receive printer status lines that look like,
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 * although this list isn't complete. Sending a '\024' (control T) character forces
48 * the return of a status report. PostScript errors detected on the printer result
49 * in the immediate transmission of special error messages that look like,
50 *
51 * %%[ Error: undefined; OffendingCommand: xxx ]%%
52 * %%[ Flushing: rest of job (to end-of-file) will be ignored ]%%
53 *
54 * although we only use the Error and Flushing keywords. Finally conditions, like
55 * being out of paper, result in other messages being sent back from the printer
56 * over the communications line. Typical PrinterError messages look like,
57 *
58 * %%[ PrinterError: out of paper; source: serial 25 ]%%
59 * %%[ PrinterError: paper jam; source: serial 25 ]%%
60 *
61 * although we only use the PrinterError keyword rather than trying to recognize
62 * all possible printer errors.
63 *
64 * The implications of using one process and only flow controlling data going to
65 * the printer are obvious. Job transmission should be reliable, but there can be
66 * data loss in stuff sent back from the printer. Usually that only caused problems
67 * with jobs designed to run on the printer and return useful data back over the
68 * communications line. If that's the kind of job you're sending call postio with
69 * the -t option. That should force the program to split into separate read and
70 * write processes and everything not bracketed by "%%[ " and " ]%%" strings goes
71 * to stdout. In otherwords the data you're expecting should be separated from the
72 * status stuff that goes to the log file (or stderr). The -R2 option does almost
73 * the same thing (ie. separate read and write processes), but everything that
74 * comes back from the printer goes to the log file (stderr by default) and you'll
75 * have to separate your data from any printer messages.
76 *
77 * A typical command line might be,
78 *
79 * postio -l /dev/tty01 -b 9600 -L log file1 file2
80 *
81 * where -l selects the line, -b sets the baud rate, and -L selects the printer
82 * log file. Since there's no default line, at least not right now, you'll always
83 * need to use the -l option, and if you don't choose a log file stderr will be
84 * used. If you have a program that will be returning data the command line might
85 * look like,
86 *
87 * postio -t -l/dev/tty01 -b9600 -Llog file >results
88 *
89 * Status stuff goes to file log while the data you're expecting back from the
90 * printer gets put in file results.
91 *
92 */
93
94 #include <stdio.h>
95 #include <ctype.h>
96 #include <fcntl.h>
97 #include <signal.h>
98 #include <sys/types.h>
99 #include <errno.h>
100
101 #include "ifdef.h" /* conditional compilation stuff */
102 #include "gen.h" /* general purpose definitions */
103 #include "postio.h" /* some special definitions */
104
105 char **argv; /* global so everyone can use them */
106 int argc;
107
108 char *prog_name = ""; /* really just for error messages */
109 int x_stat = 0; /* program exit status */
110 int debug = OFF; /* debug flag */
111 int ignore = OFF; /* what's done for FATAL errors */
112
113 char *line = NULL; /* printer is on this tty line */
114 short baudrate = BAUDRATE; /* and running at this baud rate */
115 Baud baudtable[] = BAUDTABLE; /* converts strings to termio values */
116
117 int stopbits = 1; /* number of stop bits */
118 int tostdout = FALSE; /* non-status stuff goes to stdout? */
119 int quiet = FALSE; /* no status queries in send() if TRUE */
120 int interactive = FALSE; /* interactive mode */
121 char *postbegin = POSTBEGIN; /* preceeds all the input files */
122 int useslowsend = FALSE; /* not recommended! */
123 int sendctrlC = TRUE; /* interrupt with ctrl-C when BUSY */
124 int window_size = -1; /* for Datakit - use -w */
125
126 char *block = NULL; /* input file buffer */
127 int blocksize = BLOCKSIZE; /* and its size in bytes */
128 int head = 0; /* block[head] is the next character */
129 int tail = 0; /* one past the last byte in block[] */
130
131 int splitme = FALSE; /* into READ and WRITE processes if TRUE */
132 int whatami = READWRITE; /* a READ or WRITE process - or both */
133 int canread = TRUE; /* allow reads */
134 int canwrite = TRUE; /* and writes if TRUE */
135 int otherpid = -1; /* who gets signals if greater than 1 */
136 int joinsig = SIGTRAP; /* reader gets this when writing is done */
137 int writedone = FALSE; /* and then sets this to TRUE */
138
139 char mesg[MESGSIZE]; /* exactly what came back on ttyi */
140 char sbuf[MESGSIZE]; /* for parsing the message */
141 int next = 0; /* next character goes in mesg[next] */
142 char *mesgptr = NULL; /* printer message starts here in mesg[] */
143 char *endmesg = NULL; /* as far as readline() can go in mesg[] */
144
145 Status status[] = STATUS; /* for converting status strings */
146 int nostatus = NOSTATUS; /* default getstatus() return value */
147
148 int currentstate = NOTCONNECTED; /* what's happening START, SEND, or DONE */
149
150 int ttyi = 0; /* input */
151 int ttyo = 2; /* and output file descriptors */
152
153 FILE *fp_log = stderr; /* log file for stuff from the printer */
154
155 /*****************************************************************************/
156
main(agc,agv)157 main(agc, agv)
158
159 int agc;
160 char *agv[];
161
162 {
163
164 /*
165 *
166 * A simple program that manages input and output for PostScript printers. Can run
167 * as a single process or as separate read/write processes. What's done depends on
168 * the value assigned to splitme when split() is called.
169 *
170 */
171
172 argc = agc; /* other routines may want them */
173 argv = agv;
174
175 prog_name = argv[0]; /* really just for error messages */
176
177 init_signals(); /* sets up interrupt handling */
178 options(); /* get command line options */
179 initialize(); /* must be done after options() */
180 start(); /* make sure the printer is ready */
181 split(); /* into read/write processes - maybe */
182 arguments(); /* then send each input file */
183 done(); /* wait until the printer is finished */
184 cleanup(); /* make sure the write process stops */
185
186 exit(x_stat); /* everything probably went OK */
187
188 } /* End of main */
189
190 /*****************************************************************************/
191
init_signals()192 init_signals()
193
194 {
195
196 void interrupt(); /* handles them if we catch signals */
197
198 /*
199 *
200 * Makes sure we handle interrupts. The proper way to kill the program, if
201 * necessary, is to do a kill -15. That forces a call to interrupt(), which in
202 * turn tries to reset the printer and then exits with a non-zero status. If the
203 * program is running as two processes, sending SIGTERM to either the parent or
204 * child should clean things up.
205 *
206 */
207
208 if ( signal(SIGINT, interrupt) == SIG_IGN ) {
209 signal(SIGINT, SIG_IGN);
210 signal(SIGQUIT, SIG_IGN);
211 signal(SIGHUP, SIG_IGN);
212 } else {
213 signal(SIGHUP, interrupt);
214 signal(SIGQUIT, interrupt);
215 } /* End else */
216
217 signal(SIGTERM, interrupt);
218
219 } /* End of init_sig */
220
221 /*****************************************************************************/
222
options()223 options()
224
225 {
226
227 int ch; /* return value from getopt() */
228 char *optnames = "b:cil:qs:tw:B:L:P:R:SDI";
229
230 extern char *optarg; /* used by getopt() */
231 extern int optind;
232
233 /*
234 *
235 * Reads and processes the command line options. The -R2, -t, and -i options all
236 * force separate read and write processes by eventually setting splitme to TRUE
237 * (check initialize()). The -S option is not recommended and should only be used
238 * as a last resort!
239 *
240 */
241
242 while ( (ch = getopt(argc, argv, optnames)) != EOF ) {
243 switch ( ch ) {
244 case 'b': /* baud rate string */
245 baudrate = getbaud(optarg);
246 break;
247
248 case 'c': /* no ctrl-C's */
249 sendctrlC = FALSE;
250 break;
251
252 case 'i': /* interactive mode */
253 interactive = TRUE;
254 break;
255
256 case 'l': /* printer line */
257 line = optarg;
258 break;
259
260 case 'q': /* no status queries - for RADIAN? */
261 quiet = TRUE;
262 break;
263
264 case 's': /* use 2 stop bits - for UNISON? */
265 if ( (stopbits = atoi(optarg)) < 1 || stopbits > 2 )
266 stopbits = 1;
267 break;
268
269 case 't': /* non-status stuff goes to stdout */
270 tostdout = TRUE;
271 break;
272
273 case 'w': /* Datakit window size */
274 window_size = atoi(optarg);
275 break;
276
277 case 'B': /* set the job buffer size */
278 if ( (blocksize = atoi(optarg)) <= 0 )
279 blocksize = BLOCKSIZE;
280 break;
281
282 case 'L': /* printer log file */
283 if ( (fp_log = fopen(optarg, "w")) == NULL ) {
284 fp_log = stderr;
285 error(NON_FATAL, "can't open log file %s", optarg);
286 } /* End if */
287 break;
288
289 case 'P': /* initial PostScript code */
290 postbegin = optarg;
291 break;
292
293 case 'R': /* run as one or two processes */
294 if ( atoi(optarg) == 2 )
295 splitme = TRUE;
296 else splitme = FALSE;
297 break;
298
299 case 'S': /* slow and kludged up version of send */
300 useslowsend = TRUE;
301 break;
302
303 case 'D': /* debug flag */
304 debug = ON;
305 break;
306
307 case 'I': /* ignore FATAL errors */
308 ignore = ON;
309 break;
310
311 case '?': /* don't understand the option */
312 error(FATAL, "");
313 break;
314
315 default: /* don't know what to do for ch */
316 error(FATAL, "missing case for option %c\n", ch);
317 break;
318 } /* End switch */
319 } /* End while */
320
321 argc -= optind; /* get ready for non-option args */
322 argv += optind;
323
324 } /* End of options */
325
326 /*****************************************************************************/
327
getbaud(rate)328 getbaud(rate)
329
330 char *rate; /* string representing the baud rate */
331
332 {
333
334 int i; /* for looking through baudtable[] */
335
336 /*
337 *
338 * Called from options() to convert a baud rate string into an appropriate termio
339 * value. *rate is looked up in baudtable[] and if it's found, the corresponding
340 * value is returned to the caller.
341 *
342 */
343
344 for ( i = 0; baudtable[i].rate != NULL; i++ )
345 if ( strcmp(rate, baudtable[i].rate) == 0 )
346 return(baudtable[i].val);
347
348 error(FATAL, "don't recognize baud rate %s", rate);
349
350 } /* End of getbaud */
351
352 /*****************************************************************************/
353
initialize()354 initialize()
355
356 {
357
358 /*
359 *
360 * Initialization, a few checks, and a call to setupline() (file ifdef.c) to open
361 * and configure the communications line. Settings for interactive mode always
362 * take precedence. The setupstdin() call with an argument of 0 saves the current
363 * terminal settings if interactive mode has been requested - otherwise nothing's
364 * done. Unbuffering stdout (via the setbuf() call) isn't really needed on System V
365 * since it's flushed whenever terminal input is requested. It's more efficient if
366 * we buffer the stdout (on System V) but safer (for other versions of Unix) if we
367 * include the setbuf() call.
368 *
369 */
370
371 whatami = READWRITE; /* always run start() as one process */
372 canread = canwrite = TRUE;
373
374 if ( tostdout == TRUE ) /* force separate read/write processes */
375 splitme = TRUE;
376
377 if ( interactive == TRUE ) { /* interactive mode settings always win */
378 quiet = FALSE;
379 tostdout = FALSE;
380 splitme = TRUE;
381 blocksize = 1;
382 postbegin = NULL;
383 useslowsend = FALSE;
384 nostatus = INTERACTIVE;
385 setbuf(stdout, NULL);
386 } /* End if */
387
388 if ( useslowsend == TRUE ) { /* last resort only - not recommended */
389 quiet = FALSE;
390 splitme = FALSE;
391 if ( blocksize > 1024 ) /* don't send too much all at once */
392 blocksize = 1024;
393 } /* End if */
394
395 if ( tostdout == TRUE && fp_log == stderr )
396 fp_log = NULL;
397
398 if ( line == NULL && (interactive == TRUE || tostdout == TRUE) )
399 error(FATAL, "a printer line must be supplied - use the -l option");
400
401 if ( (block = malloc(blocksize)) == NULL )
402 error(FATAL, "no memory");
403
404 endmesg = mesg + sizeof mesg - 2; /* one byte from last position in mesg */
405
406 setupline(); /* configure the communications line */
407 setupstdin(0); /* save current stdin terminal settings */
408
409 } /* End of initialize */
410
411 /*****************************************************************************/
412
start()413 start()
414
415 {
416
417 /*
418 *
419 * Tries to put the printer in the IDLE state before anything important is sent.
420 * Run as a single process no matter what has been assigned to splitme. Separate
421 * read and write processes, if requested, will be created after we're done here.
422 *
423 */
424
425 logit("printer startup\n");
426
427 currentstate = START;
428 clearline();
429
430 while ( 1 )
431 switch ( getstatus(1) ) {
432 case IDLE:
433 case INTERACTIVE:
434 if ( postbegin != NULL && *postbegin != '\0' )
435 Write(ttyo, postbegin, strlen(postbegin));
436 clearline();
437 return;
438
439 case BUSY:
440 if ( sendctrlC == TRUE ) {
441 Write(ttyo, "\003", 1);
442 Rest(1);
443 } /* End if */
444 break;
445
446 case WAITING:
447 case ERROR:
448 case FLUSHING:
449 Write(ttyo, "\004", 1);
450 Rest(1);
451 break;
452
453 case PRINTERERROR:
454 Rest(15);
455 break;
456
457 case DISCONNECT:
458 error(FATAL, "Disconnected - printer may be offline");
459 break;
460
461 case ENDOFJOB:
462 case UNKNOWN:
463 clearline();
464 break;
465
466 default:
467 Rest(1);
468 break;
469 } /* End switch */
470
471 } /* End of start */
472
473 /*****************************************************************************/
474
split()475 split()
476
477 {
478
479 int pid;
480 void interrupt();
481
482 /*
483 *
484 * If splitme is TRUE we fork a process, make the parent handle reading, and let
485 * the child take care of writing. resetline() (file ifdef.c) contains all the
486 * system dependent code needed to reset the communications line for separate
487 * read and write processes. For now it's expected to return TRUE or FALSE and
488 * that value controls whether we try the fork. I've only tested the two process
489 * stuff for System V. Other versions of resetline() may just be dummy procedures
490 * that always return FALSE. If the fork() failed previous versions continued as
491 * a single process, although the implementation wasn't quite right, but I've now
492 * decided to quit. The main reason is a Datakit channel may be configured to
493 * flow control data in both directions, and if we run postio over that channel
494 * as a single process we likely will end up in deadlock.
495 *
496 */
497
498 if ( splitme == TRUE )
499 if ( resetline() == TRUE ) {
500 pid = getpid();
501 signal(joinsig, interrupt);
502 if ( (otherpid = fork()) == -1 )
503 error(FATAL, "can't fork");
504 else if ( otherpid == 0 ) {
505 whatami = WRITE;
506 nostatus = WRITEPROCESS;
507 otherpid = pid;
508 setupstdin(1);
509 } else whatami = READ;
510 } else if ( interactive == TRUE || tostdout == TRUE )
511 error(FATAL, "can't create two process - check resetline()");
512 else error(NON_FATAL, "running as a single process - check resetline()");
513
514 canread = (whatami & READ) ? TRUE : FALSE;
515 canwrite = (whatami & WRITE) ? TRUE : FALSE;
516
517 } /* End of split */
518
519 /*****************************************************************************/
520
arguments()521 arguments()
522
523 {
524
525 int fd_in; /* next input file */
526
527 /*
528 *
529 * Makes sure all the non-option command line arguments are processed. If there
530 * aren't any arguments left when we get here we'll send stdin. Input files are
531 * only read and sent to the printer if canwrite is TRUE. Checking it here means
532 * we won't have to do it in send(). If interactive mode is TRUE we'll stay here
533 * forever sending stdin when we run out of files - exit with a break. Actually
534 * the loop is bogus and used at most once when we're in interactive mode because
535 * stdin is in a pseudo raw mode and the read() in readblock() should never see
536 * the end of file.
537 *
538 */
539
540 if ( canwrite == TRUE )
541 do /* loop is for interactive mode */
542 if ( argc < 1 )
543 send(fileno(stdin), "pipe.end");
544 else {
545 while ( argc > 0 ) {
546 if ( (fd_in = open(*argv, O_RDONLY)) == -1 )
547 error(FATAL, "can't open %s", *argv);
548 send(fd_in, *argv);
549 close(fd_in);
550 argc--;
551 argv++;
552 } /* End while */
553 } /* End else */
554 while ( interactive == TRUE );
555
556 } /* End of arguments */
557
558 /*****************************************************************************/
559
send(fd_in,name)560 send(fd_in, name)
561
562 int fd_in; /* next input file */
563 char *name; /* and it's pathname */
564
565 {
566
567 /*
568 *
569 * Sends file *name to the printer. There's nothing left here that depends on
570 * sending and receiving status reports, although it can be reassuring to know
571 * the printer is responding and processing our job. Only the writer gets here
572 * in the two process implementation, and in that case split() has reset nostatus
573 * to WRITEPROCESS and that's what getstatus() always returns. For now we accept
574 * the IDLE state and ENDOFJOB as legitimate and ignore the INITIALIZING state.
575 *
576 */
577
578 if ( interactive == FALSE )
579 logit("sending file %s\n", name);
580
581 currentstate = SEND;
582
583 if ( useslowsend == TRUE ) {
584 slowsend(fd_in);
585 return;
586 } /* End if */
587
588 while ( readblock(fd_in) )
589 switch ( getstatus(0) ) {
590 case IDLE:
591 case BUSY:
592 case WAITING:
593 case PRINTING:
594 case ENDOFJOB:
595 case PRINTERERROR:
596 case UNKNOWN:
597 case NOSTATUS:
598 case WRITEPROCESS:
599 case INTERACTIVE:
600 writeblock();
601 break;
602
603 case ERROR:
604 fprintf(stderr, "%s", mesg); /* for csw */
605 error(USER_FATAL, "PostScript Error");
606 break;
607
608 case FLUSHING:
609 error(USER_FATAL, "Flushing Job");
610 break;
611
612 case DISCONNECT:
613 error(FATAL, "Disconnected - printer may be offline");
614 break;
615 } /* End switch */
616
617 } /* End of send */
618
619 /*****************************************************************************/
620
done()621 done()
622
623 {
624
625 int sleeptime = 15; /* for 'out of paper' etc. */
626
627 /*
628 *
629 * Tries to stay connected to the printer until we're reasonably sure the job is
630 * complete. It's the only way we can recover error messages or data generated by
631 * the PostScript program and returned over the communication line. Actually doing
632 * it correctly for all possible PostScript jobs is more difficult that it might
633 * seem. For example if we've sent several jobs, each with their own EOF mark, then
634 * waiting for ENDOFJOB won't guarantee all the jobs have completed. Even waiting
635 * for IDLE isn't good enough. Checking for the WAITING state after all the files
636 * have been sent and then sending an EOF may be the best approach, but even that
637 * won't work all the time - we could miss it or might not get there. Even sending
638 * our own special PostScript job after all the input files has it's own different
639 * set of problems, but probably could work (perhaps by printing a fake status
640 * message or just not timing out). Anyway it's probably not worth the trouble so
641 * for now we'll quit if writedone is TRUE and we get ENDOFJOB or IDLE.
642 *
643 * If we're running separate read and write processes the reader gets here after
644 * after split() while the writer goes to send() and only gets here after all the
645 * input files have been transmitted. When they're both here the writer sends the
646 * reader signal joinsig and that forces writedone to TRUE in the reader. At that
647 * point the reader can begin looking for an indication of the end of the job.
648 * The writer hangs around until the reader kills it (usually in cleanup()) sending
649 * occasional status requests.
650 *
651 */
652
653 if ( canwrite == TRUE )
654 logit("waiting for end of job\n");
655
656 currentstate = DONE;
657 writedone = (whatami == READWRITE) ? TRUE : FALSE;
658
659 while ( 1 ) {
660 switch ( getstatus(1) ) {
661
662 case WRITEPROCESS:
663 if ( writedone == FALSE ) {
664 sendsignal(joinsig);
665 Write(ttyo, "\004", 1);
666 writedone = TRUE;
667 sleeptime = 1;
668 } /* End if */
669 Rest(sleeptime++);
670 break;
671
672 case WAITING:
673 Write(ttyo, "\004", 1);
674 Rest(1);
675 sleeptime = 15;
676 break;
677
678 case IDLE:
679 case ENDOFJOB:
680 if ( writedone == TRUE ) {
681 logit("job complete\n");
682 return;
683 } /* End if */
684 break;
685
686 case BUSY:
687 case PRINTING:
688 case INTERACTIVE:
689 sleeptime = 15;
690 break;
691
692 case PRINTERERROR:
693 Rest(sleeptime++);
694 break;
695
696 case ERROR:
697 fprintf(stderr, "%s", mesg); /* for csw */
698 error(USER_FATAL, "PostScript Error");
699 return;
700
701 case FLUSHING:
702 error(USER_FATAL, "Flushing Job");
703 return;
704
705 case DISCONNECT:
706 error(FATAL, "Disconnected - printer may be offline");
707 return;
708
709 default:
710 Rest(1);
711 break;
712 } /* End switch */
713
714 if ( sleeptime > 60 )
715 sleeptime = 60;
716 } /* End while */
717
718 } /* End of done */
719
720 /*****************************************************************************/
721
cleanup()722 cleanup()
723
724 {
725
726 int w;
727
728 /*
729 *
730 * Only needed if we're running separate read and write processes. Makes sure the
731 * write process is killed after the read process has successfully finished with
732 * all the jobs. sendsignal() returns a -1 if there's nobody to signal so things
733 * work when we're running a single process.
734 *
735 */
736
737 while ( sendsignal(SIGKILL) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
738
739 } /* End of cleanup */
740
741 /*****************************************************************************/
742
readblock(fd_in)743 readblock(fd_in)
744
745 int fd_in; /* current input file */
746
747 {
748
749 static long blocknum = 1;
750
751 /*
752 *
753 * Fills the input buffer with the next block, provided we're all done with the
754 * last one. Blocks from fd_in are stored in array block[]. head is the index
755 * of the next byte in block[] that's supposed to go to the printer. tail points
756 * one past the last byte in the current block. head is adjusted in writeblock()
757 * after each successful write, while head and tail are reset here each time
758 * a new block is read. Returns the number of bytes left in the current block.
759 * Read errors cause the program to abort. The fake status message that's put out
760 * in quiet mode is only so you can look at the log file and know something's
761 * happening - take it out if you want.
762 *
763 */
764
765 if ( head >= tail ) { /* done with the last block */
766 if ( (tail = read(fd_in, block, blocksize)) == -1 )
767 error(FATAL, "error reading input file");
768 if ( quiet == TRUE && tail > 0 ) /* put out a fake message? */
769 logit("%%%%[ status: busy; block: %d ]%%%%\n", blocknum++);
770 head = 0;
771 } /* End if */
772
773 return(tail - head);
774
775 } /* End of readblock */
776
777 /*****************************************************************************/
778
writeblock()779 writeblock()
780
781 {
782
783 int count; /* bytes successfully written */
784
785 /*
786 *
787 * Called from send() when it's OK to send the next block to the printer. head
788 * is adjusted after the write, and the number of bytes that were successfully
789 * written is returned to the caller.
790 *
791 */
792
793 if ( (count = write(ttyo, &block[head], tail - head)) == -1 )
794 error(FATAL, "error writing to %s", line);
795 else if ( count == 0 )
796 error(FATAL, "printer appears to be offline");
797
798 head += count;
799 return(count);
800
801 } /* End of writeblock */
802
803 /*****************************************************************************/
804
getstatus(t)805 getstatus(t)
806
807 int t; /* sleep time after sending '\024' */
808
809 {
810
811 int gotline = FALSE; /* value returned by readline() */
812 int state = nostatus; /* representation of the current state */
813 int mesgch; /* to restore mesg[] when tostdout == TRUE */
814
815 static int laststate = NOSTATUS; /* last state recognized */
816
817 /*
818 *
819 * Looks for things coming back from the printer on the communications line, parses
820 * complete lines retrieved by readline(), and returns an integer representation
821 * of the current printer status to the caller. If nothing was available a status
822 * request (control T) is sent to the printer and nostatus is returned to the
823 * caller (provided quiet isn't TRUE). Interactive mode either never returns from
824 * readline() or returns FALSE.
825 *
826 */
827
828 if ( canread == TRUE && (gotline = readline()) == TRUE ) {
829 state = parsemesg();
830 if ( state != laststate || state == UNKNOWN || mesgptr != mesg || debug == ON )
831 logit("%s", mesg);
832
833 if ( tostdout == TRUE && currentstate != START ) {
834 mesgch = *mesgptr;
835 *mesgptr = '\0';
836 fprintf(stdout, "%s", mesg);
837 fflush(stdout);
838 *mesgptr = mesgch; /* for ERROR in send() and done() */
839 } /* End if */
840 return(laststate = state);
841 } /* End if */
842
843 if ( (quiet == FALSE || currentstate != SEND) &&
844 (tostdout == FALSE || currentstate == START) && interactive == FALSE ) {
845 if ( Write(ttyo, "\024", 1) != 1 )
846 error(FATAL, "printer appears to be offline");
847 if ( t > 0 ) Rest(t);
848 } /* End if */
849
850 return(nostatus);
851
852 } /* End of getstatus */
853
854 /*****************************************************************************/
855
parsemesg()856 parsemesg()
857
858 {
859
860 char *e; /* end of printer message in mesg[] */
861 char *key, *val; /* keyword/value strings in sbuf[] */
862 char *p; /* for converting to lower case etc. */
863 int i; /* where *key was found in status[] */
864
865 /*
866 *
867 * Parsing the lines that readline() stores in mesg[] is messy, and what's done
868 * here isn't completely correct nor as fast as it could be. The general format
869 * of lines that come back from the printer (assuming no data loss) is:
870 *
871 * str%%[ key: val; key: val; key: val ]%%\n
872 *
873 * where str can be most anything not containing a newline and printer reports
874 * (eg. status or error messages) are bracketed by "%%[ " and " ]%%" strings and
875 * end with a newline. Usually we'll have the string or printer report but not
876 * both. For most jobs the leading string will be empty, but could be anything
877 * generated on a printer and returned over the communications line using the
878 * PostScript print operator. I'll assume PostScript jobs are well behaved and
879 * never bracket their messages with "%%[ " and " ]%%" strings that delimit status
880 * or error messages.
881 *
882 * Printer reports consist of one or more key/val pairs, and what we're interested
883 * in (status or error indications) may not be the first pair in the list. In
884 * addition we'll sometimes want the value associated with a keyword (eg. when
885 * key = status) and other times we'll want the keyword (eg. when key = Error or
886 * Flushing). The last pair isn't terminated by a semicolon and a value string
887 * often contains many space separated words and it can even include colons in
888 * meaningful places. I've also decided to continue converting things to lower
889 * case before doing the lookup in status[]. The isupper() test is for Berkeley
890 * systems.
891 *
892 */
893
894 if ( *(mesgptr = find("%%[ ", mesg)) != '\0' && *(e = find(" ]%%", mesgptr+4)) != '\0' ) {
895 strcpy(sbuf, mesgptr+4); /* don't change mesg[] */
896 sbuf[e-mesgptr-4] = '\0'; /* ignore the trailing " ]%%" */
897
898 for ( key = strtok(sbuf, " :"); key != NULL; key = strtok(NULL, " :") ) {
899 if ( (val = strtok(NULL, ";")) != NULL && strcmp(key, "status") == 0 )
900 key = val;
901
902 for ( ; *key == ' '; key++ ) ; /* skip any leading spaces */
903 for ( p = key; *p; p++ ) /* convert to lower case */
904 if ( *p == ':' ) {
905 *p = '\0';
906 break;
907 } else if ( isupper(*p) ) *p = tolower(*p);
908
909 for ( i = 0; status[i].state != NULL; i++ )
910 if ( strcmp(status[i].state, key) == 0 )
911 return(status[i].val);
912 } /* End for */
913 } else if ( strcmp(mesg, "CONVERSATION ENDED.\n") == 0 )
914 return(DISCONNECT);
915
916 return(mesgptr == '\0' ? nostatus : UNKNOWN);
917
918 } /* End of parsemesg */
919
920 /*****************************************************************************/
921
find(str1,str2)922 char *find(str1, str2)
923
924 char *str1; /* look for this string */
925 char *str2; /* in this one */
926
927 {
928
929 char *s1, *s2; /* can't change str1 or str2 too fast */
930
931 /*
932 *
933 * Looks for *str1 in string *str2. Returns a pointer to the start of the substring
934 * if it's found or to the end of string str2 otherwise.
935 *
936 */
937
938 for ( ; *str2 != '\0'; str2++ ) {
939 for ( s1 = str1, s2 = str2; *s1 != '\0' && *s1 == *s2; s1++, s2++ ) ;
940 if ( *s1 == '\0' )
941 break;
942 } /* End for */
943
944 return(str2);
945
946 } /* End of find */
947
948 /*****************************************************************************/
949
clearline()950 clearline()
951
952 {
953
954 /*
955 *
956 * Reads characters from the input line until nothing's left. Don't do anything if
957 * we're currently running separate read and write processes.
958 *
959 */
960
961 if ( whatami == READWRITE )
962 while ( readline() != FALSE ) ;
963
964 } /* End of clearline */
965
966 /*****************************************************************************/
967
sendsignal(sig)968 sendsignal(sig)
969
970 int sig; /* this goes to the other process */
971
972 {
973
974 /*
975 *
976 * Sends signal sig to the other process if we're running as separate read and
977 * write processes. Returns the result of the kill if there's someone else to
978 * signal or -1 if we're running alone.
979 *
980 */
981
982 if ( whatami != READWRITE && otherpid > 1 )
983 return(kill(otherpid, sig));
984
985 return(-1);
986
987 } /* End of sendsignal */
988
989 /*****************************************************************************/
990
interrupt(sig)991 void interrupt(sig)
992
993 int sig; /* signal that we caught */
994
995 {
996
997 /*
998 *
999 * Caught a signal - all except joinsig cause the program to quit. joinsig is the
1000 * signal sent by the writer to the reader after all the jobs have been transmitted.
1001 * Used to tell the read process when it can start looking for the end of the job.
1002 *
1003 */
1004
1005 signal(sig, SIG_IGN);
1006
1007 if ( sig != joinsig ) {
1008 x_stat |= FATAL;
1009 if ( canread == TRUE )
1010 if ( interactive == FALSE )
1011 error(NON_FATAL, "signal %d abort", sig);
1012 else error(NON_FATAL, "quitting");
1013 quit(sig);
1014 } /* End if */
1015
1016 writedone = TRUE;
1017 signal(joinsig, interrupt);
1018
1019 } /* End of interrupt */
1020
1021 /*****************************************************************************/
1022
logit(mesg,a1,a2,a3)1023 logit(mesg, a1, a2, a3)
1024
1025 char *mesg; /* control string */
1026 unsigned a1, a2, a3; /* and possible arguments */
1027
1028 {
1029
1030 /*
1031 *
1032 * Simple routine that's used to write a message to the log file.
1033 *
1034 */
1035
1036 if ( mesg != NULL && fp_log != NULL ) {
1037 fprintf(fp_log, mesg, a1, a2, a3);
1038 fflush(fp_log);
1039 } /* End if */
1040
1041 } /* End of logit */
1042
1043 /*****************************************************************************/
1044
error(kind,mesg,a1,a2,a3)1045 error(kind, mesg, a1, a2, a3)
1046
1047 int kind; /* FATAL or NON_FATAL error */
1048 char *mesg; /* error message control string */
1049 unsigned a1, a2, a3; /* control string arguments */
1050
1051 {
1052
1053 FILE *fp_err;
1054
1055 /*
1056 *
1057 * Called when we've run into some kind of program error. First *mesg is printed
1058 * using the control string arguments a?. If kind is FATAL and we're not ignoring
1059 * errors the program will be terminated. If mesg is NULL or *mesg is the NULL
1060 * string nothing will be printed.
1061 *
1062 */
1063
1064 fp_err = (fp_log != NULL) ? fp_log : stderr;
1065
1066 if ( mesg != NULL && *mesg != '\0' ) {
1067 fprintf(fp_err, "%s: ", prog_name);
1068 fprintf(fp_err, mesg, a1, a2, a3);
1069 putc('\n', fp_err);
1070 } /* End if */
1071
1072 x_stat |= kind;
1073
1074 if ( kind != NON_FATAL && ignore == OFF )
1075 quit(SIGTERM);
1076
1077 } /* End of error */
1078
1079 /*****************************************************************************/
1080
quit(sig)1081 quit(sig)
1082
1083 int sig;
1084
1085 {
1086
1087 int w;
1088
1089 /*
1090 *
1091 * Makes sure everything is properly cleaned up if there's a signal or FATAL error
1092 * that should cause the program to terminate. The sleep by the write process is
1093 * to help give the reset sequence a chance to reach the printer before we break
1094 * the connection - primarily for printers connected to Datakit. There's a very
1095 * slight chance the reset sequence that's sent to the printer could get us stuck
1096 * here. Simplest solution is don't bother to send it - everything works without it.
1097 * Flushing ttyo would be better, but means yet another system dependent procedure
1098 * in ifdef.c! I'll leave things be for now.
1099 *
1100 * Obscure problem on PS-810 turbos says wait a bit after sending an interrupt.
1101 * Seem to remember the printer getting into a bad state immediately after the
1102 * top was opened when the toner light was on. A sleep after sending the ctrl-C
1103 * seemed to fix things.
1104 *
1105 */
1106
1107 signal(sig, SIG_IGN);
1108 ignore = ON;
1109
1110 while ( sendsignal(sig) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
1111
1112 setupstdin(2);
1113
1114 if ( currentstate != NOTCONNECTED ) {
1115 if ( sendctrlC == TRUE ) {
1116 Write(ttyo, "\003", 1);
1117 Rest(1); /* PS-810 turbo problem?? */
1118 } /* End if */
1119 Write(ttyo, "\004", 1);
1120 } /* End if */
1121
1122 alarm(0); /* prevents sleep() loop on V9 systems */
1123 Rest(2);
1124
1125 exit(x_stat);
1126
1127 } /* End of quit */
1128
1129 /*****************************************************************************/
1130
Rest(t)1131 Rest(t)
1132
1133 int t;
1134
1135 {
1136
1137 /*
1138 *
1139 * Used to replace sleep() calls. Only needed if we're running the program as
1140 * a read and write process and don't want to have the read process sleep. Most
1141 * sleeps are in the code because of the non-blocking read used by the single
1142 * process implementation. Probably should be a macro.
1143 *
1144 */
1145
1146 if ( t > 0 && canwrite == TRUE )
1147 sleep(t);
1148
1149 } /* End of Rest */
1150
1151 /*****************************************************************************/
1152
Read(fd,buf,n)1153 Read(fd, buf, n)
1154
1155 int fd;
1156 char *buf;
1157 int n;
1158
1159 {
1160
1161 int count;
1162
1163 /*
1164 *
1165 * Used to replace some of the read() calls. Only needed if we're running separate
1166 * read and write processes. Should only be used to replace read calls on ttyi.
1167 * Always returns 0 to the caller if the process doesn't have its READ flag set.
1168 * Probably should be a macro.
1169 *
1170 */
1171
1172 if ( canread == TRUE ) {
1173 if ( (count = read(fd, buf, n)) == -1 && errno == EINTR )
1174 count = 0;
1175 } else count = 0;
1176
1177 return(count);
1178
1179 } /* End of Read */
1180
1181 /*****************************************************************************/
1182
Write(fd,buf,n)1183 Write(fd, buf, n)
1184
1185 int fd;
1186 char *buf;
1187 int n;
1188
1189 {
1190
1191 int count;
1192
1193 /*
1194 *
1195 * Used to replace some of the write() calls. Again only needed if we're running
1196 * separate read and write processes. Should only be used to replace write calls
1197 * on ttyo. Always returns n to the caller if the process doesn't have its WRITE
1198 * flag set. Should also probably be a macro.
1199 *
1200 */
1201
1202 if ( canwrite == TRUE ) {
1203 if ( (count = write(fd, buf, n)) == -1 && errno == EINTR )
1204 count = n;
1205 } else count = n;
1206
1207 return(count);
1208
1209 } /* End of Write */
1210
1211 /*****************************************************************************/
1212
1213