1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 #pragma ident "%Z%%M% %I% %E% SMI" 31 32 /* 33 * 34 * A simple program that can be used to filter jobs for PostScript 35 * printers. It's a cleaned up version of usg_iox, that I assume was 36 * written by Richard Flood. The most important addition includes some 37 * simple processing of printer status reports, usually obtained when 38 * \024 is sent to the printer. The returned status lines look like: 39 * 40 * 41 * %%[ status: idle; source serial 25 ]%% 42 * %%[ status: waiting; source serial 25 ]%% 43 * %%[ status: initializing; source serial 25 ]%% 44 * %%[ status: busy; source serial 25 ]%% 45 * %%[ status: printing; source serial 25 ]%% 46 * %%[ status: PrinterError: out of paper; source serial 25 ]%% 47 * %%[ status: PrinterError: no paper tray; source serial 25 ]%% 48 * 49 * 50 * although the list isn't meant to be complete. 51 * 52 * Other changes to the original program include the addition of 53 * options that let you select the tty line, baud rate, and printer 54 * log file. The program seems to work reasonably well, at least for 55 * our QMS PS-800 printer, but could still use some work. 56 * 57 * There were a couple of serious mistakes in the first few versions of 58 * postcomm. Both were made in setting up flow control in routine 59 * initialize(). Setting the IXANY flag in c_iflag was wrong, and 60 * often caused problems when the printer transmitted a spontaneous 61 * status report, which always happens when the paper runs out. 62 * Things were kludged up to get around the problems, but they were 63 * never exactly right, and probably could never be guaranteed to work 64 * 100%. 65 * 66 * The other mistake was setting the IXOFF flag, again in c_iflag. 67 * Although I never saw deadlock in the original versions of postcomm, 68 * it could happen. Apparently the IXANY and IXOFF flags combined to 69 * make that an unlikely event. Anyway both flags should normally be 70 * turned off to ensure reliable transmission of jobs. 71 * 72 * The implications of only setting IXON are obvious. Job transmission 73 * should be reliable, but data returned by the printer over the tty 74 * line may get lost. That won't cause problems in postcomm, but there 75 * may be occasions when you want to run a job and recover data 76 * generated by the printer. The -t option sets the IXOFF, IXANY, and 77 * IXON flags in c_iflag and causes send() to be far more careful about 78 * when data is sent to the printer. In addition anything not 79 * recognized as a status report is written on stdout. It seems to 80 * work reasonably well, but it's slow and may hang or have flow 81 * control problems. Only use the -t option when it's absolutely 82 * necessary. A small block size, like 512, should also help. 83 * 84 * Using two processes, one for reads and the other for writes, may 85 * eventually be needed. For now postcomm seems to do a good job 86 * transmitting data, and the -t option is probably acceptable for 87 * those few jobs that need to recover data from the printer. 88 * 89 * A typical command line might be: 90 * 91 * postcomm -L log -t <file1 > device 92 * 93 * where -L selects the printer log file and -t sends data from the 94 * printer out to the printer. If you don't choose a log file stderr 95 * will be used and the information mailed or written to you. 96 * 97 */ 98 99 #include <stdio.h> 100 #include <stdarg.h> 101 #include <ctype.h> 102 #include <fcntl.h> 103 #include <signal.h> 104 #include <sys/types.h> 105 106 107 # define OFF 0 108 # define ON 1 109 # define TRUE 1 110 # define FALSE 0 111 # define FATAL 1 112 # define NON_FATAL 0 113 114 #include "postcomm.h" /* some special definitions */ 115 116 117 char *prog_name = "postcomm"; /* just for error messages */ 118 119 int debug = OFF; /* debug flag */ 120 int ignore = OFF; /* what's done for FATAL errors */ 121 122 123 char *block = NULL; /* input file buffer */ 124 int blocksize = BLOCKSIZE; /* and its size in bytes */ 125 int head = 0; /* block[head] is the next character */ 126 int tail = 0; /* one past the last byte in block[] */ 127 128 char mesg[BUFSIZE]; /* exactly what came back on ttyi */ 129 char sbuf[BUFSIZE]; /* for parsing the message */ 130 int next = 0; /* next character goes in sbuf[next] */ 131 Status status[] = STATUS; /* for converting status strings */ 132 133 int stopbits = 1; /* number of stop bits */ 134 int tostdout = FALSE; /* non-status stuff goes to stdout? */ 135 int curfile = 0; /* only needed when tostdout is TRUE */ 136 137 char *postbegin = POSTBEGIN; /* preceeds all the input files */ 138 139 int ttyi; /* input */ 140 int ttyo = 2; /* and output file descriptors */ 141 142 FILE *fp_log = stderr; /* log file for data from the printer */ 143 144 145 146 static void filter(void); 147 static int getstatus(int); 148 static void initialize(void); 149 static void options(int, char *[]); 150 static int readblock(int); 151 static int readline(void); 152 static void reset(void); 153 static int writeblock(void); 154 155 void 156 logit(char *mesg, ...) 157 { 158 159 /* 160 * 161 * Simple routine that's used to write a message to the log file. 162 * 163 */ 164 165 166 if (mesg != NULL) 167 { 168 va_list ap; 169 170 va_start(ap, mesg); 171 vfprintf(fp_log, mesg, ap); 172 va_end(ap); 173 fflush(fp_log); 174 } 175 176 } /* End of logit */ 177 178 179 180 181 182 void 183 error(int kind, char *mesg, ...) 184 { 185 186 187 /* 188 * 189 * Called when we've run into some kind of program error. First *mesg is 190 * printed using the control string arguments a?. Then if kind is FATAL 191 * and we're not ignoring errors the program will be terminated. 192 * 193 * If mesg is NULL or *mesg is the NULL string nothing will be printed. 194 * 195 */ 196 197 198 if ( mesg != NULL && *mesg != '\0' ) { 199 va_list ap; 200 201 fprintf(fp_log, "%s: ", prog_name); 202 va_start(ap, mesg); 203 vfprintf(fp_log, mesg, ap); 204 va_end(ap); 205 putc('\n', fp_log); 206 } /* End if */ 207 208 if ( kind == FATAL && ignore == OFF ) { 209 write(ttyo, "\003\004", 2); 210 exit(1); 211 } /* End if */ 212 213 } /* End of error */ 214 215 216 217 218 219 int 220 main(int argc, char *argv[]) 221 { 222 223 /* 224 * 225 * A simple program that manages input and output for PostScript 226 * printers. If you're sending a PostScript program that will be 227 * returning useful information add the -ot option to the lp(1) command 228 * line. Everything not recognized as a printer status report will go 229 * to stdout. The -ot option should only be used when needed! It's slow 230 * and doesn't use flow control properly, but it's probably the best 231 * that can be done using a single process for reading and writing. 232 */ 233 234 prog_name = argv[0]; /* really just for error messages */ 235 236 options(argc, argv); 237 238 initialize(); /* Set printer up for printing */ 239 240 filter(); 241 242 reset(); /* wait 'til it's finished & reset it*/ 243 244 return (0); /* everything probably went OK */ 245 246 } /* End of main */ 247 248 249 250 251 252 static void 253 options(int argc, char *argv[]) 254 { 255 256 257 int ch; /* return value from getopt() */ 258 char *names = "tB:L:P:DI"; 259 260 extern char *optarg; /* used by getopt() */ 261 262 /* 263 * 264 * Reads and processes the command line options. The -t option should 265 * only be used when absolutely necessary. It's slow and doesn't do 266 * flow control properly. Selecting a small block size (eg. 512 or 267 * less) with with the -B option may help when you need the -t option. 268 * 269 */ 270 271 272 while ( (ch = getopt(argc, argv, names)) != EOF ) 273 { 274 switch ( ch ) 275 { 276 case 't': /* non-status stuff goes to stdout */ 277 tostdout = TRUE; 278 break; 279 280 case 'B': /* set the job buffer size */ 281 if ((blocksize = atoi(optarg)) <= 0) 282 blocksize = BLOCKSIZE; 283 break; 284 285 case 'L': /* printer log file */ 286 if ((fp_log = fopen(optarg, "w")) == NULL) 287 { 288 fp_log = stderr; 289 error(NON_FATAL, "can't open log file %s", 290 optarg); 291 } /* End if */ 292 break; 293 294 case 'P': /* initial PostScript program */ 295 postbegin = optarg; 296 break; 297 298 case 'D': /* debug flag */ 299 debug = ON; 300 break; 301 302 case 'I': /* ignore FATAL errors */ 303 ignore = ON; 304 break; 305 306 case '?': /* don't understand the option */ 307 error(FATAL, ""); 308 break; 309 310 default: /* don't know what to do for ch */ 311 error(FATAL, "missing case for option %c\n", ch); 312 break; 313 314 } /* End switch */ 315 316 } /* End while */ 317 } /* End of options */ 318 319 320 321 322 323 static void 324 initialize(void) 325 { 326 if ((block = malloc(blocksize)) == NULL) 327 error(FATAL, "no memory"); 328 329 ttyi = fileno(stdout); 330 331 if ((ttyo = dup(ttyi)) == -1) 332 error(FATAL, "can't dup file descriptor for stdout"); 333 334 /* 335 * 336 * Makes sure the printer is in the 337 * IDLE state before any real data is sent. 338 * 339 */ 340 341 342 logit("printer startup\n"); 343 344 while ( 1 ) 345 switch (getstatus(1)) 346 { 347 case IDLE: 348 if (postbegin != NULL) 349 write(ttyo, postbegin, strlen(postbegin)); 350 else 351 write(ttyo, "\n", 1); 352 return; 353 354 case WAITING: 355 case BUSY: 356 case ERROR: 357 write(ttyo, "\003\004", 2); 358 sleep(1); 359 break; 360 361 case FLUSHING: 362 write(ttyo, "\004", 1); 363 sleep(1); 364 break; 365 366 case PRINTERERROR: 367 case INITIALIZING: 368 sleep(15); 369 break; 370 371 case DISCONNECT: 372 /* talk to spooler w/S_FAULT_ALERT */ 373 error(FATAL, "printer appears to be offline"); 374 break; 375 376 default: 377 sleep(1); 378 break; 379 380 } /* End switch */ 381 382 } /* End of initialize */ 383 384 385 386 387 388 static void 389 filter(void) 390 { 391 static int wflag = 0; /* nonzero if we've written a block */ 392 int fd_in = fileno(stdin); 393 394 /* 395 * 396 * Responsible for sending the next file to the printer. 397 * Most of the hard stuff is done in getstatus() and readline(). 398 * All this routine really does is control what happens for the 399 * different printer states. 400 * 401 */ 402 403 404 logit("sending file\n"); 405 406 curfile++; 407 408 while (readblock(fd_in)) 409 switch (getstatus(0)) 410 { 411 case WAITING: 412 writeblock(); 413 wflag = 1; 414 break; 415 416 case BUSY: 417 case PRINTING: 418 case PRINTERERROR: 419 if (tostdout == FALSE) 420 { 421 writeblock(); 422 wflag = 1; 423 } 424 else 425 sleep(1); 426 break; 427 428 case UNKNOWN: 429 if (tostdout == FALSE) 430 { 431 writeblock(); 432 wflag = 1; 433 } 434 break; 435 436 case NOSTATUS: 437 if (tostdout == FALSE) 438 { 439 if (wflag) 440 writeblock(); 441 } 442 else 443 sleep(1); 444 break; 445 446 case IDLE: 447 if (wflag) 448 error(FATAL, "printer is idle"); 449 write(ttyo, "\n", 1); 450 break; 451 452 case ERROR: 453 fprintf(stderr, "%s", mesg); /* for csw */ 454 error(FATAL, "PostScript error"); 455 break; 456 457 case FLUSHING: 458 error(FATAL, "PostScript error"); 459 break; 460 461 case INITIALIZING: 462 error(FATAL, "printer booting"); 463 break; 464 465 case DISCONNECT: 466 error(FATAL, "printer appears to be offline"); 467 break; 468 469 } /* End switch */ 470 471 } /* End of print */ 472 473 474 475 476 477 static int 478 readblock(int fd_in) 479 /* current input file */ 480 { 481 482 /* 483 * 484 * Fills the input buffer with the next block, provided we're all done 485 * with the last one. Blocks from fd_in are stored in array block[]. 486 * Head is the index of the next byte in block[] that's supposed to go 487 * to the printer. tail points one past the last byte in the current 488 * block. head is adjusted in writeblock() after each successful 489 * write, while head and tail are reset here each time a new block is 490 * read. Returns the number of bytes left in the current block. Read 491 * errors cause the program to abort. 492 * 493 */ 494 495 if (head >= tail) 496 { /* done with the last block */ 497 if ((tail = read(fd_in, block, blocksize)) == -1) 498 error(FATAL, "error reading input file"); 499 head = 0; 500 } 501 502 return(tail - head); 503 504 } /* End of readblock */ 505 506 507 508 509 510 static int 511 writeblock(void) 512 { 513 int count; /* bytes successfully written */ 514 515 /* 516 * 517 * Called from send() when it's OK to send the next block to the 518 * printer. head is adjusted after the write, and the number of bytes 519 * that were successfully written is returned to the caller. 520 * 521 */ 522 523 524 if ((count = write(ttyo, &block[head], tail - head)) == -1) 525 error(FATAL, "error writing to stdout"); 526 else 527 if (count == 0) 528 error(FATAL, "printer appears to be offline"); 529 530 head += count; 531 return(count); 532 } /* End of writeblock */ 533 534 535 536 537 538 static int 539 getstatus(int t) 540 /* sleep time after sending '\024' */ 541 { 542 char *state; /* new printer state - from sbuf[] */ 543 int i; /* index of new state in status[] */ 544 static int laststate = NOSTATUS; 545 /* last state we found out about */ 546 547 /* 548 * 549 * Sends a status request to the printer and tries to read the response. 550 * If an entire line is available readline() returns TRUE and the 551 * string in sbuf[] is parsed and converted into an integer code that 552 * represents the printer's state. If readline() returns FALSE, 553 * meaning an entire line wasn't available, NOSTATUS is returned. 554 * 555 */ 556 557 if (readline() == TRUE) 558 { 559 state = sbuf; 560 561 if (strncmp(sbuf, "%%[", 3) == 0) 562 { 563 strtok(sbuf, " "); /* skip the leading "%%[ " */ 564 if (strcmp(state = strtok(NULL, " :;"), "status") == 0) 565 state = strtok(NULL, " :;"); 566 } 567 568 for (i = 0; status[i].state != NULL; i++) 569 if (strcmp(state, status[i].state) == 0) 570 break; 571 572 if (status[i].val != laststate || debug == ON) 573 logit("%s", mesg); 574 575 if (tostdout == TRUE && status[i].val == UNKNOWN && curfile > 0) 576 fprintf(stdout, "%s", mesg); 577 578 return(laststate = status[i].val); 579 } /* End if */ 580 581 if ( write(ttyo, "\024", 1) != 1 ) 582 error(FATAL, "printer appears to be offline"); 583 584 if ( t > 0 ) 585 sleep(t); 586 587 return(NOSTATUS); 588 589 } /* End of getstatus */ 590 591 592 593 594 595 static void 596 reset(void) 597 { 598 int sleeptime = 15; /* for 'out of paper' etc. */ 599 int senteof = FALSE; 600 601 /* 602 * 603 * We're all done sending the input files, so we'll send an EOF to the 604 * printer and wait until it tells us it's done. 605 * 606 */ 607 608 609 logit("waiting for end of job\n"); 610 611 while (1) 612 { 613 switch (getstatus(2)) 614 { 615 case WAITING: 616 write(ttyo, "\004", 1); 617 senteof = TRUE; 618 sleeptime = 15; 619 break; 620 621 case ENDOFJOB: 622 if (senteof == TRUE) 623 { 624 logit("job complete\n"); 625 return; 626 } 627 sleeptime = 15; 628 break; 629 630 case BUSY: 631 case PRINTING: 632 sleeptime = 15; 633 sleep(1); 634 break; 635 636 case PRINTERERROR: 637 sleep(sleeptime++); 638 break; 639 640 case ERROR: 641 fprintf(stderr, "%s", mesg); /* for csw */ 642 error(FATAL, "PostScript error"); 643 return; 644 645 case FLUSHING: 646 error(FATAL, "PostScript error"); 647 return; 648 649 case IDLE: 650 error(FATAL, "printer is idle"); 651 return; 652 653 case INITIALIZING: 654 error(FATAL, "printer booting"); 655 return; 656 657 case DISCONNECT: 658 error(FATAL, "printer appears to be offline"); 659 return; 660 661 default: 662 sleep(1); 663 break; 664 665 } /* End switch */ 666 667 if (sleeptime > 60) 668 sleeptime = 60; 669 670 } /* End while */ 671 672 } /* End of reset */ 673 674 675 676 677 678 679 680 681 682 static int 683 readline(void) 684 { 685 char ch; /* next character from ttyi */ 686 int n; /* read() return value */ 687 688 /* 689 * 690 * Reads the printer's tty line up to a newline (or EOF) or until no 691 * more characters are available. As characters are read they're 692 * converted to lower case and put in sbuf[next] until a newline (or 693 * EOF) are found. The string is then terminated with '\0', next is 694 * reset to zero, and TRUE is returned. 695 * 696 */ 697 698 699 while ((n = read(ttyi, &ch, 1)) != 0) 700 { 701 if (n < 0) 702 error(FATAL, "error reading stdout"); 703 mesg[next] = ch; 704 sbuf[next++] = tolower(ch); 705 if (ch == '\n' || ch == '\004') 706 { 707 mesg[next] = sbuf[next] = '\0'; 708 if (ch == '\004') 709 sprintf(sbuf, "%%%%[ status: endofjob ]%%%%\n"); 710 next = 0; 711 return(TRUE); 712 } /* End if */ 713 } /* End while */ 714 715 return(FALSE); 716 717 } /* End of readline */ 718