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