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 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 23 /* All Rights Reserved */ 24 25 26 /* 27 * Copyright 2002 Sun Microsystems, Inc. All rights reserved. 28 * Use is subject to license terms. 29 */ 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 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 void 147 logit(char *mesg, ...) 148 { 149 150 /* 151 * 152 * Simple routine that's used to write a message to the log file. 153 * 154 */ 155 156 157 if (mesg != NULL) 158 { 159 va_list ap; 160 161 va_start(ap, mesg); 162 vfprintf(fp_log, mesg, ap); 163 va_end(ap); 164 fflush(fp_log); 165 } 166 167 } /* End of logit */ 168 169 170 171 172 173 void 174 error(int kind, char *mesg, ...) 175 { 176 177 178 /* 179 * 180 * Called when we've run into some kind of program error. First *mesg is 181 * printed using the control string arguments a?. Then if kind is FATAL 182 * and we're not ignoring errors the program will be terminated. 183 * 184 * If mesg is NULL or *mesg is the NULL string nothing will be printed. 185 * 186 */ 187 188 189 if ( mesg != NULL && *mesg != '\0' ) { 190 va_list ap; 191 192 fprintf(fp_log, "%s: ", prog_name); 193 va_start(ap, mesg); 194 vfprintf(fp_log, mesg, ap); 195 va_end(ap); 196 putc('\n', fp_log); 197 } /* End if */ 198 199 if ( kind == FATAL && ignore == OFF ) { 200 write(ttyo, "\003\004", 2); 201 exit(1); 202 } /* End if */ 203 204 } /* End of error */ 205 206 207 208 209 210 main(argc, argv) 211 int argc; 212 char *argv[]; 213 { 214 215 /* 216 * 217 * A simple program that manages input and output for PostScript 218 * printers. If you're sending a PostScript program that will be 219 * returning useful information add the -ot option to the lp(1) command 220 * line. Everything not recognized as a printer status report will go 221 * to stdout. The -ot option should only be used when needed! It's slow 222 * and doesn't use flow control properly, but it's probably the best 223 * that can be done using a single process for reading and writing. 224 */ 225 226 prog_name = argv[0]; /* really just for error messages */ 227 228 options(argv, argc); 229 230 initialize(); /* Set printer up for printing */ 231 232 filter(); 233 234 reset(); /* wait 'til it's finished & reset it*/ 235 236 exit(0); /* everything probably went OK */ 237 238 } /* End of main */ 239 240 241 242 243 244 245 options(argc, argv) 246 int argc; 247 char *argv[]; 248 { 249 250 251 int ch; /* return value from getopt() */ 252 char *names = "tB:L:P:DI"; 253 254 extern char *optarg; /* used by getopt() */ 255 256 /* 257 * 258 * Reads and processes the command line options. The -t option should 259 * only be used when absolutely necessary. It's slow and doesn't do 260 * flow control properly. Selecting a small block size (eg. 512 or 261 * less) with with the -B option may help when you need the -t option. 262 * 263 */ 264 265 266 while ( (ch = getopt(argc, argv, names)) != EOF ) 267 { 268 switch ( ch ) 269 { 270 case 't': /* non-status stuff goes to stdout */ 271 tostdout = TRUE; 272 break; 273 274 case 'B': /* set the job buffer size */ 275 if ((blocksize = atoi(optarg)) <= 0) 276 blocksize = BLOCKSIZE; 277 break; 278 279 case 'L': /* printer log file */ 280 if ((fp_log = fopen(optarg, "w")) == NULL) 281 { 282 fp_log = stderr; 283 error(NON_FATAL, "can't open log file %s", 284 optarg); 285 } /* End if */ 286 break; 287 288 case 'P': /* initial PostScript program */ 289 postbegin = optarg; 290 break; 291 292 case 'D': /* debug flag */ 293 debug = ON; 294 break; 295 296 case 'I': /* ignore FATAL errors */ 297 ignore = ON; 298 break; 299 300 case '?': /* don't understand the option */ 301 error(FATAL, ""); 302 break; 303 304 default: /* don't know what to do for ch */ 305 error(FATAL, "missing case for option %c\n", ch); 306 break; 307 308 } /* End switch */ 309 310 } /* End while */ 311 } /* End of options */ 312 313 314 315 316 317 318 initialize() 319 { 320 if ((block = malloc(blocksize)) == NULL) 321 error(FATAL, "no memory"); 322 323 ttyi = fileno(stdout); 324 325 if ((ttyo = dup(ttyi)) == -1) 326 error(FATAL, "can't dup file descriptor for stdout"); 327 328 /* 329 * 330 * Makes sure the printer is in the 331 * IDLE state before any real data is sent. 332 * 333 */ 334 335 336 logit("printer startup\n"); 337 338 while ( 1 ) 339 switch (getstatus(1)) 340 { 341 case IDLE: 342 if (postbegin != NULL) 343 write(ttyo, postbegin, strlen(postbegin)); 344 else 345 write(ttyo, "\n", 1); 346 return; 347 348 case WAITING: 349 case BUSY: 350 case ERROR: 351 write(ttyo, "\003\004", 2); 352 sleep(1); 353 break; 354 355 case FLUSHING: 356 write(ttyo, "\004", 1); 357 sleep(1); 358 break; 359 360 case PRINTERERROR: 361 case INITIALIZING: 362 sleep(15); 363 break; 364 365 case DISCONNECT: 366 /* talk to spooler w/S_FAULT_ALERT */ 367 error(FATAL, "printer appears to be offline"); 368 break; 369 370 default: 371 sleep(1); 372 break; 373 374 } /* End switch */ 375 376 } /* End of initialize */ 377 378 379 380 381 382 383 filter() 384 { 385 static int wflag = 0; /* nonzero if we've written a block */ 386 int fd_in = fileno(stdin); 387 388 /* 389 * 390 * Responsible for sending the next file to the printer. 391 * Most of the hard stuff is done in getstatus() and readline(). 392 * All this routine really does is control what happens for the 393 * different printer states. 394 * 395 */ 396 397 398 logit("sending file\n"); 399 400 curfile++; 401 402 while (readblock(fd_in)) 403 switch (getstatus(0)) 404 { 405 case WAITING: 406 writeblock(); 407 wflag = 1; 408 break; 409 410 case BUSY: 411 case PRINTING: 412 case PRINTERERROR: 413 if (tostdout == FALSE) 414 { 415 writeblock(); 416 wflag = 1; 417 } 418 else 419 sleep(1); 420 break; 421 422 case UNKNOWN: 423 if (tostdout == FALSE) 424 { 425 writeblock(); 426 wflag = 1; 427 } 428 break; 429 430 case NOSTATUS: 431 if (tostdout == FALSE) 432 { 433 if (wflag) 434 writeblock(); 435 } 436 else 437 sleep(1); 438 break; 439 440 case IDLE: 441 if (wflag) 442 error(FATAL, "printer is idle"); 443 write(ttyo, "\n", 1); 444 break; 445 446 case ERROR: 447 fprintf(stderr, "%s", mesg); /* for csw */ 448 error(FATAL, "PostScript error"); 449 break; 450 451 case FLUSHING: 452 error(FATAL, "PostScript error"); 453 break; 454 455 case INITIALIZING: 456 error(FATAL, "printer booting"); 457 break; 458 459 case DISCONNECT: 460 error(FATAL, "printer appears to be offline"); 461 break; 462 463 } /* End switch */ 464 465 } /* End of print */ 466 467 468 469 470 471 472 readblock(fd_in) 473 int fd_in; /* current input file */ 474 { 475 476 /* 477 * 478 * Fills the input buffer with the next block, provided we're all done 479 * with the last one. Blocks from fd_in are stored in array block[]. 480 * Head is the index of the next byte in block[] that's supposed to go 481 * to the printer. tail points one past the last byte in the current 482 * block. head is adjusted in writeblock() after each successful 483 * write, while head and tail are reset here each time a new block is 484 * read. Returns the number of bytes left in the current block. Read 485 * errors cause the program to abort. 486 * 487 */ 488 489 if (head >= tail) 490 { /* done with the last block */ 491 if ((tail = read(fd_in, block, blocksize)) == -1) 492 error(FATAL, "error reading input file"); 493 head = 0; 494 } 495 496 return(tail - head); 497 498 } /* End of readblock */ 499 500 501 502 503 504 505 writeblock() 506 { 507 int count; /* bytes successfully written */ 508 509 /* 510 * 511 * Called from send() when it's OK to send the next block to the 512 * printer. head is adjusted after the write, and the number of bytes 513 * that were successfully written is returned to the caller. 514 * 515 */ 516 517 518 if ((count = write(ttyo, &block[head], tail - head)) == -1) 519 error(FATAL, "error writing to stdout"); 520 else 521 if (count == 0) 522 error(FATAL, "printer appears to be offline"); 523 524 head += count; 525 return(count); 526 } /* End of writeblock */ 527 528 529 530 531 532 533 getstatus(t) 534 int t; /* sleep time after sending '\024' */ 535 { 536 char *state; /* new printer state - from sbuf[] */ 537 int i; /* index of new state in status[] */ 538 static int laststate = NOSTATUS; 539 /* last state we found out about */ 540 541 542 /* 543 * 544 * Sends a status request to the printer and tries to read the response. 545 * If an entire line is available readline() returns TRUE and the 546 * string in sbuf[] is parsed and converted into an integer code that 547 * represents the printer's state. If readline() returns FALSE, 548 * meaning an entire line wasn't available, NOSTATUS is returned. 549 * 550 */ 551 552 if (readline() == TRUE) 553 { 554 state = sbuf; 555 556 if (strncmp(sbuf, "%%[", 3) == 0) 557 { 558 strtok(sbuf, " "); /* skip the leading "%%[ " */ 559 if (strcmp(state = strtok(NULL, " :;"), "status") == 0) 560 state = strtok(NULL, " :;"); 561 } 562 563 for (i = 0; status[i].state != NULL; i++) 564 if (strcmp(state, status[i].state) == 0) 565 break; 566 567 if (status[i].val != laststate || debug == ON) 568 logit("%s", mesg); 569 570 if (tostdout == TRUE && status[i].val == UNKNOWN && curfile > 0) 571 fprintf(stdout, "%s", mesg); 572 573 return(laststate = status[i].val); 574 } /* End if */ 575 576 if ( write(ttyo, "\024", 1) != 1 ) 577 error(FATAL, "printer appears to be offline"); 578 579 if ( t > 0 ) 580 sleep(t); 581 582 return(NOSTATUS); 583 584 } /* End of getstatus */ 585 586 587 588 589 590 591 reset() 592 { 593 int sleeptime = 15; /* for 'out of paper' etc. */ 594 int senteof = FALSE; 595 596 /* 597 * 598 * We're all done sending the input files, so we'll send an EOF to the 599 * printer and wait until it tells us it's done. 600 * 601 */ 602 603 604 logit("waiting for end of job\n"); 605 606 while (1) 607 { 608 switch (getstatus(2)) 609 { 610 case WAITING: 611 write(ttyo, "\004", 1); 612 senteof = TRUE; 613 sleeptime = 15; 614 break; 615 616 case ENDOFJOB: 617 if (senteof == TRUE) 618 { 619 logit("job complete\n"); 620 return; 621 } 622 sleeptime = 15; 623 break; 624 625 case BUSY: 626 case PRINTING: 627 sleeptime = 15; 628 sleep(1); 629 break; 630 631 case PRINTERERROR: 632 sleep(sleeptime++); 633 break; 634 635 case ERROR: 636 fprintf(stderr, "%s", mesg); /* for csw */ 637 error(FATAL, "PostScript error"); 638 return; 639 640 case FLUSHING: 641 error(FATAL, "PostScript error"); 642 return; 643 644 case IDLE: 645 error(FATAL, "printer is idle"); 646 return; 647 648 case INITIALIZING: 649 error(FATAL, "printer booting"); 650 return; 651 652 case DISCONNECT: 653 error(FATAL, "printer appears to be offline"); 654 return; 655 656 default: 657 sleep(1); 658 break; 659 660 } /* End switch */ 661 662 if (sleeptime > 60) 663 sleeptime = 60; 664 665 } /* End while */ 666 667 } /* End of reset */ 668 669 670 671 672 673 674 675 676 677 678 readline() 679 { 680 char ch; /* next character from ttyi */ 681 int n; /* read() return value */ 682 683 /* 684 * 685 * Reads the printer's tty line up to a newline (or EOF) or until no 686 * more characters are available. As characters are read they're 687 * converted to lower case and put in sbuf[next] until a newline (or 688 * EOF) are found. The string is then terminated with '\0', next is 689 * reset to zero, and TRUE is returned. 690 * 691 */ 692 693 694 while ((n = read(ttyi, &ch, 1)) != 0) 695 { 696 if (n < 0) 697 error(FATAL, "error reading stdout"); 698 mesg[next] = ch; 699 sbuf[next++] = tolower(ch); 700 if (ch == '\n' || ch == '\004') 701 { 702 mesg[next] = sbuf[next] = '\0'; 703 if (ch == '\004') 704 sprintf(sbuf, "%%%%[ status: endofjob ]%%%%\n"); 705 next = 0; 706 return(TRUE); 707 } /* End if */ 708 } /* End while */ 709 710 return(FALSE); 711 712 } /* End of readline */ 713