/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* * * A simple program that can be used to filter jobs for PostScript * printers. It's a cleaned up version of usg_iox, that I assume was * written by Richard Flood. The most important addition includes some * simple processing of printer status reports, usually obtained when * \024 is sent to the printer. The returned status lines look like: * * * %%[ status: idle; source serial 25 ]%% * %%[ status: waiting; source serial 25 ]%% * %%[ status: initializing; source serial 25 ]%% * %%[ status: busy; source serial 25 ]%% * %%[ status: printing; source serial 25 ]%% * %%[ status: PrinterError: out of paper; source serial 25 ]%% * %%[ status: PrinterError: no paper tray; source serial 25 ]%% * * * although the list isn't meant to be complete. * * Other changes to the original program include the addition of * options that let you select the tty line, baud rate, and printer * log file. The program seems to work reasonably well, at least for * our QMS PS-800 printer, but could still use some work. * * There were a couple of serious mistakes in the first few versions of * postcomm. Both were made in setting up flow control in routine * initialize(). Setting the IXANY flag in c_iflag was wrong, and * often caused problems when the printer transmitted a spontaneous * status report, which always happens when the paper runs out. * Things were kludged up to get around the problems, but they were * never exactly right, and probably could never be guaranteed to work * 100%. * * The other mistake was setting the IXOFF flag, again in c_iflag. * Although I never saw deadlock in the original versions of postcomm, * it could happen. Apparently the IXANY and IXOFF flags combined to * make that an unlikely event. Anyway both flags should normally be * turned off to ensure reliable transmission of jobs. * * The implications of only setting IXON are obvious. Job transmission * should be reliable, but data returned by the printer over the tty * line may get lost. That won't cause problems in postcomm, but there * may be occasions when you want to run a job and recover data * generated by the printer. The -t option sets the IXOFF, IXANY, and * IXON flags in c_iflag and causes send() to be far more careful about * when data is sent to the printer. In addition anything not * recognized as a status report is written on stdout. It seems to * work reasonably well, but it's slow and may hang or have flow * control problems. Only use the -t option when it's absolutely * necessary. A small block size, like 512, should also help. * * Using two processes, one for reads and the other for writes, may * eventually be needed. For now postcomm seems to do a good job * transmitting data, and the -t option is probably acceptable for * those few jobs that need to recover data from the printer. * * A typical command line might be: * * postcomm -L log -t device * * where -L selects the printer log file and -t sends data from the * printer out to the printer. If you don't choose a log file stderr * will be used and the information mailed or written to you. * */ #include #include #include #include #include #include # define OFF 0 # define ON 1 # define TRUE 1 # define FALSE 0 # define FATAL 1 # define NON_FATAL 0 #include "postcomm.h" /* some special definitions */ char *prog_name = "postcomm"; /* just for error messages */ int debug = OFF; /* debug flag */ int ignore = OFF; /* what's done for FATAL errors */ char *block = NULL; /* input file buffer */ int blocksize = BLOCKSIZE; /* and its size in bytes */ int head = 0; /* block[head] is the next character */ int tail = 0; /* one past the last byte in block[] */ char mesg[BUFSIZE]; /* exactly what came back on ttyi */ char sbuf[BUFSIZE]; /* for parsing the message */ int next = 0; /* next character goes in sbuf[next] */ Status status[] = STATUS; /* for converting status strings */ int stopbits = 1; /* number of stop bits */ int tostdout = FALSE; /* non-status stuff goes to stdout? */ int curfile = 0; /* only needed when tostdout is TRUE */ char *postbegin = POSTBEGIN; /* preceeds all the input files */ int ttyi; /* input */ int ttyo = 2; /* and output file descriptors */ FILE *fp_log = stderr; /* log file for data from the printer */ static void filter(void); static int getstatus(int); static void initialize(void); static void options(int, char *[]); static int readblock(int); static int readline(void); static void reset(void); static int writeblock(void); void logit(char *mesg, ...) { /* * * Simple routine that's used to write a message to the log file. * */ if (mesg != NULL) { va_list ap; va_start(ap, mesg); vfprintf(fp_log, mesg, ap); va_end(ap); fflush(fp_log); } } /* End of logit */ void error(int kind, char *mesg, ...) { /* * * Called when we've run into some kind of program error. First *mesg is * printed using the control string arguments a?. Then if kind is FATAL * and we're not ignoring errors the program will be terminated. * * If mesg is NULL or *mesg is the NULL string nothing will be printed. * */ if ( mesg != NULL && *mesg != '\0' ) { va_list ap; fprintf(fp_log, "%s: ", prog_name); va_start(ap, mesg); vfprintf(fp_log, mesg, ap); va_end(ap); putc('\n', fp_log); } /* End if */ if ( kind == FATAL && ignore == OFF ) { write(ttyo, "\003\004", 2); exit(1); } /* End if */ } /* End of error */ int main(int argc, char *argv[]) { /* * * A simple program that manages input and output for PostScript * printers. If you're sending a PostScript program that will be * returning useful information add the -ot option to the lp(1) command * line. Everything not recognized as a printer status report will go * to stdout. The -ot option should only be used when needed! It's slow * and doesn't use flow control properly, but it's probably the best * that can be done using a single process for reading and writing. */ prog_name = argv[0]; /* really just for error messages */ options(argc, argv); initialize(); /* Set printer up for printing */ filter(); reset(); /* wait 'til it's finished & reset it*/ return (0); /* everything probably went OK */ } /* End of main */ static void options(int argc, char *argv[]) { int ch; /* return value from getopt() */ char *names = "tB:L:P:DI"; extern char *optarg; /* used by getopt() */ /* * * Reads and processes the command line options. The -t option should * only be used when absolutely necessary. It's slow and doesn't do * flow control properly. Selecting a small block size (eg. 512 or * less) with with the -B option may help when you need the -t option. * */ while ( (ch = getopt(argc, argv, names)) != EOF ) { switch ( ch ) { case 't': /* non-status stuff goes to stdout */ tostdout = TRUE; break; case 'B': /* set the job buffer size */ if ((blocksize = atoi(optarg)) <= 0) blocksize = BLOCKSIZE; break; case 'L': /* printer log file */ if ((fp_log = fopen(optarg, "w")) == NULL) { fp_log = stderr; error(NON_FATAL, "can't open log file %s", optarg); } /* End if */ break; case 'P': /* initial PostScript program */ postbegin = optarg; break; case 'D': /* debug flag */ debug = ON; break; case 'I': /* ignore FATAL errors */ ignore = ON; break; case '?': /* don't understand the option */ error(FATAL, ""); break; default: /* don't know what to do for ch */ error(FATAL, "missing case for option %c\n", ch); break; } /* End switch */ } /* End while */ } /* End of options */ static void initialize(void) { if ((block = malloc(blocksize)) == NULL) error(FATAL, "no memory"); ttyi = fileno(stdout); if ((ttyo = dup(ttyi)) == -1) error(FATAL, "can't dup file descriptor for stdout"); /* * * Makes sure the printer is in the * IDLE state before any real data is sent. * */ logit("printer startup\n"); while ( 1 ) switch (getstatus(1)) { case IDLE: if (postbegin != NULL) write(ttyo, postbegin, strlen(postbegin)); else write(ttyo, "\n", 1); return; case WAITING: case BUSY: case ERROR: write(ttyo, "\003\004", 2); sleep(1); break; case FLUSHING: write(ttyo, "\004", 1); sleep(1); break; case PRINTERERROR: case INITIALIZING: sleep(15); break; case DISCONNECT: /* talk to spooler w/S_FAULT_ALERT */ error(FATAL, "printer appears to be offline"); break; default: sleep(1); break; } /* End switch */ } /* End of initialize */ static void filter(void) { static int wflag = 0; /* nonzero if we've written a block */ int fd_in = fileno(stdin); /* * * Responsible for sending the next file to the printer. * Most of the hard stuff is done in getstatus() and readline(). * All this routine really does is control what happens for the * different printer states. * */ logit("sending file\n"); curfile++; while (readblock(fd_in)) switch (getstatus(0)) { case WAITING: writeblock(); wflag = 1; break; case BUSY: case PRINTING: case PRINTERERROR: if (tostdout == FALSE) { writeblock(); wflag = 1; } else sleep(1); break; case UNKNOWN: if (tostdout == FALSE) { writeblock(); wflag = 1; } break; case NOSTATUS: if (tostdout == FALSE) { if (wflag) writeblock(); } else sleep(1); break; case IDLE: if (wflag) error(FATAL, "printer is idle"); write(ttyo, "\n", 1); break; case ERROR: fprintf(stderr, "%s", mesg); /* for csw */ error(FATAL, "PostScript error"); break; case FLUSHING: error(FATAL, "PostScript error"); break; case INITIALIZING: error(FATAL, "printer booting"); break; case DISCONNECT: error(FATAL, "printer appears to be offline"); break; } /* End switch */ } /* End of print */ static int readblock(int fd_in) /* current input file */ { /* * * Fills the input buffer with the next block, provided we're all done * with the last one. Blocks from fd_in are stored in array block[]. * Head is the index of the next byte in block[] that's supposed to go * to the printer. tail points one past the last byte in the current * block. head is adjusted in writeblock() after each successful * write, while head and tail are reset here each time a new block is * read. Returns the number of bytes left in the current block. Read * errors cause the program to abort. * */ if (head >= tail) { /* done with the last block */ if ((tail = read(fd_in, block, blocksize)) == -1) error(FATAL, "error reading input file"); head = 0; } return(tail - head); } /* End of readblock */ static int writeblock(void) { int count; /* bytes successfully written */ /* * * Called from send() when it's OK to send the next block to the * printer. head is adjusted after the write, and the number of bytes * that were successfully written is returned to the caller. * */ if ((count = write(ttyo, &block[head], tail - head)) == -1) error(FATAL, "error writing to stdout"); else if (count == 0) error(FATAL, "printer appears to be offline"); head += count; return(count); } /* End of writeblock */ static int getstatus(int t) /* sleep time after sending '\024' */ { char *state; /* new printer state - from sbuf[] */ int i; /* index of new state in status[] */ static int laststate = NOSTATUS; /* last state we found out about */ /* * * Sends a status request to the printer and tries to read the response. * If an entire line is available readline() returns TRUE and the * string in sbuf[] is parsed and converted into an integer code that * represents the printer's state. If readline() returns FALSE, * meaning an entire line wasn't available, NOSTATUS is returned. * */ if (readline() == TRUE) { state = sbuf; if (strncmp(sbuf, "%%[", 3) == 0) { strtok(sbuf, " "); /* skip the leading "%%[ " */ if (strcmp(state = strtok(NULL, " :;"), "status") == 0) state = strtok(NULL, " :;"); } for (i = 0; status[i].state != NULL; i++) if (strcmp(state, status[i].state) == 0) break; if (status[i].val != laststate || debug == ON) logit("%s", mesg); if (tostdout == TRUE && status[i].val == UNKNOWN && curfile > 0) fprintf(stdout, "%s", mesg); return(laststate = status[i].val); } /* End if */ if ( write(ttyo, "\024", 1) != 1 ) error(FATAL, "printer appears to be offline"); if ( t > 0 ) sleep(t); return(NOSTATUS); } /* End of getstatus */ static void reset(void) { int sleeptime = 15; /* for 'out of paper' etc. */ int senteof = FALSE; /* * * We're all done sending the input files, so we'll send an EOF to the * printer and wait until it tells us it's done. * */ logit("waiting for end of job\n"); while (1) { switch (getstatus(2)) { case WAITING: write(ttyo, "\004", 1); senteof = TRUE; sleeptime = 15; break; case ENDOFJOB: if (senteof == TRUE) { logit("job complete\n"); return; } sleeptime = 15; break; case BUSY: case PRINTING: sleeptime = 15; sleep(1); break; case PRINTERERROR: sleep(sleeptime++); break; case ERROR: fprintf(stderr, "%s", mesg); /* for csw */ error(FATAL, "PostScript error"); return; case FLUSHING: error(FATAL, "PostScript error"); return; case IDLE: error(FATAL, "printer is idle"); return; case INITIALIZING: error(FATAL, "printer booting"); return; case DISCONNECT: error(FATAL, "printer appears to be offline"); return; default: sleep(1); break; } /* End switch */ if (sleeptime > 60) sleeptime = 60; } /* End while */ } /* End of reset */ static int readline(void) { char ch; /* next character from ttyi */ int n; /* read() return value */ /* * * Reads the printer's tty line up to a newline (or EOF) or until no * more characters are available. As characters are read they're * converted to lower case and put in sbuf[next] until a newline (or * EOF) are found. The string is then terminated with '\0', next is * reset to zero, and TRUE is returned. * */ while ((n = read(ttyi, &ch, 1)) != 0) { if (n < 0) error(FATAL, "error reading stdout"); mesg[next] = ch; sbuf[next++] = tolower(ch); if (ch == '\n' || ch == '\004') { mesg[next] = sbuf[next] = '\0'; if (ch == '\004') sprintf(sbuf, "%%%%[ status: endofjob ]%%%%\n"); next = 0; return(TRUE); } /* End if */ } /* End while */ return(FALSE); } /* End of readline */