/*
* 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 <file1 > 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 <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
# 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 */