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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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