summaryrefslogtreecommitdiff
path: root/sys/src/cmd/postscript/postio
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/postscript/postio
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/postscript/postio')
-rwxr-xr-xsys/src/cmd/postscript/postio/README20
-rwxr-xr-xsys/src/cmd/postscript/postio/ifdef.c867
-rwxr-xr-xsys/src/cmd/postscript/postio/ifdef.h66
-rwxr-xr-xsys/src/cmd/postscript/postio/postio.1308
-rwxr-xr-xsys/src/cmd/postscript/postio/postio.c1212
-rwxr-xr-xsys/src/cmd/postscript/postio/postio.h209
-rwxr-xr-xsys/src/cmd/postscript/postio/postio.mk109
-rwxr-xr-xsys/src/cmd/postscript/postio/postio.mk.old84
-rwxr-xr-xsys/src/cmd/postscript/postio/slowsend.c121
9 files changed, 2996 insertions, 0 deletions
diff --git a/sys/src/cmd/postscript/postio/README b/sys/src/cmd/postscript/postio/README
new file mode 100755
index 000000000..88da4bc4f
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/README
@@ -0,0 +1,20 @@
+Serial communications program for PostScript printers.
+
+Runs as a single read/write process (by default). Use the -R2 option
+or set splitme to TRUE (file postio.c) to get separate read and write
+processes. Although not the default, we recommend using separate read
+and write processes.
+
+Sends occasional status queries (control Ts) while transmitting files.
+Use the -q option or set quiet (file postio.c) to TRUE to disable status
+queries.
+
+Datakit connections are supported on System V and Ninth Edition systems.
+The syntax (for connecting to a Datakit destination) varies. Check the
+SYSV and V9 versions of setupline() in file ifdef.c.
+
+Set DKHOST and DKSTREAMS to TRUE in postio.mk for streams based DKHOST
+support. When DKSTREAMS is TRUE postio.mk uses "dknetty" as the stream
+module. Settings like DKSTREAMS=dkty select a different stream module
+and may be required for full Datakit support on some systems.
+
diff --git a/sys/src/cmd/postscript/postio/ifdef.c b/sys/src/cmd/postscript/postio/ifdef.c
new file mode 100755
index 000000000..1d7be0905
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/ifdef.c
@@ -0,0 +1,867 @@
+/*
+ *
+ * Conditionally compiled routines for setting up and reading the line. Things
+ * were getting out of hand with all the ifdefs, and even though this defeats
+ * part of the purpose of conditional complilation directives, I think it's easier
+ * to follow this way. Thanks to Alan Buckwalter for the System V DKHOST code.
+ *
+ * postio now can be run as separate read and write processes, but requires that
+ * you write a procedure called resetline() and perhaps modify readline() some.
+ * I've already tested the code on System V and it seems to work. Ninth Edition
+ * and BSD code may be missing.
+ *
+ * By request I've changed the way some of the setupline() procedures (eg. in the
+ * System V implementation) handle things when no line has been given. If line is
+ * NULL the new setupline() procedures try to continue, assuming whoever called
+ * postio connected stdout to the printer. Things will only work if we can read
+ * and write stdout!
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "ifdef.h" /* conditional header file inclusion */
+#include "gen.h" /* general purpose definitions */
+
+FILE *fp_ttyi, *fp_ttyo;
+char *ptr = mesg;
+
+extern int window_size;
+
+/*****************************************************************************/
+
+#ifdef SYSV
+setupline()
+
+{
+
+ struct termio termio;
+
+/*
+ *
+ * Line initialization for SYSV. For now if no line is given (ie. line == NULL )
+ * we continue on as before using stdout as ttyi and ttyo. Doesn't work when we're
+ * running in interactive mode or forcing stuff that comes back from the printer
+ * to stdout. Both cases are now caught by a test that's been added to routine
+ * initialize(). The change is primarily for the version of lp that's available
+ * with SVR3.2.
+ *
+ */
+
+#ifdef DKHOST
+ if ( line != NULL && *line != '/' ) {
+ if ( strncmp(line, "DK:", 3) == 0 )
+ line += 3;
+ dkhost_connect();
+#ifdef DKSTREAMS
+ if ( ioctl(ttyi, I_PUSH, DKSTREAMS) == -1 )
+ error(FATAL, "ioctl error - %s", DKSTREAMS);
+ if ( ioctl(ttyi, I_PUSH, "ldterm") == -1 )
+ error(FATAL, "ioctl error - ldterm");
+#endif
+ } else
+#endif
+
+ if ( line == NULL )
+ ttyi = fileno(stdout);
+ else if ( (ttyi = open(line, O_RDWR)) == -1 )
+ error(FATAL, "can't open %s", line);
+
+ if ( (ttyo = dup(ttyi)) == -1 )
+ error(FATAL, "can't dup file descriptor for %s", line);
+
+ if ( stopbits == 1 )
+ stopbits = 0;
+ else stopbits = CSTOPB;
+
+ if ( fcntl(ttyi, F_SETFL, O_NDELAY) == -1 )
+ error(FATAL, "fcntl error - F_SETFL");
+
+ if ( ioctl(ttyi, TCGETA, &termio) == -1 )
+ error(FATAL, "ioctl error - TCGETA");
+
+ termio.c_iflag = IXON | IGNCR;
+ termio.c_oflag = 0;
+ termio.c_cflag = HUPCL | CREAD | CS8 | stopbits | baudrate;
+ termio.c_lflag = 0;
+ termio.c_cc[VMIN] = termio.c_cc[VTIME] = 0;
+
+ if ( ioctl(ttyi, TCSETA, &termio) == -1 )
+ error(FATAL, "ioctl error - TCSETA");
+
+ if ( ioctl(ttyi, TCFLSH, 2) == -1 )
+ error(FATAL, "ioctl error - TCFLSH");
+
+ fp_ttyi = fdopen(ttyi, "r");
+
+} /* End of setupline */
+
+/*****************************************************************************/
+
+resetline()
+
+{
+
+ int flags; /* for turning O_NDELAY off */
+ struct termio termio; /* so we can reset flow control */
+
+/*
+ *
+ * Only used if we're running the program as separate read and write processes.
+ * Called from split() after the initial connection has been made and returns
+ * TRUE if two processes should work. Don't know if the O_NDELAY stuff is really
+ * needed, but setting c_cc[VMIN] to 1 definitely is. If we leave it be (as a 0)
+ * the read in readline() won't block!
+ *
+ */
+
+ if ( (flags = fcntl(ttyi, F_GETFL, 0)) == -1 )
+ error(FATAL, "fcntl error - F_GETFL");
+
+ flags &= ~O_NDELAY;
+
+ if ( fcntl(ttyi, F_SETFL, flags) == -1 )
+ error(FATAL, "fcntl error - F_SETFL");
+
+ if ( ioctl(ttyi, TCGETA, &termio) == -1 )
+ error(FATAL, "ioctl error - TCGETA");
+
+ termio.c_iflag &= ~IXANY;
+ termio.c_iflag |= IXON | IXOFF;
+ termio.c_cc[VMIN] = 1;
+ termio.c_cc[VTIME] = 0;
+
+ if ( ioctl(ttyi, TCSETA, &termio) == -1 )
+ error(FATAL, "ioctl error - TCSETA");
+
+ return(TRUE);
+
+} /* End of resetline */
+
+/*****************************************************************************/
+
+setupstdin(mode)
+
+ int mode; /* what to do with stdin settings */
+
+{
+
+ struct termio termio;
+
+ static int saved = FALSE;
+ static struct termio oldtermio;
+
+/*
+ *
+ * Save (mode = 0), reset (mode = 1), or restore (mode = 2) the tty settings for
+ * stdin. Expect something like raw mode with no echo will be set up. Explicit
+ * code to ensure blocking reads probably isn't needed because blocksize is set
+ * to 1 when we're in interactive mode, but I've included it anyway.
+ *
+ */
+
+ if ( interactive == TRUE )
+ switch ( mode ) {
+ case 0:
+ if ( isatty(0) != 1 )
+ error(FATAL, "stdin not a terminal - can't run interactive mode");
+ if ( ioctl(0, TCGETA, &oldtermio) == -1 )
+ error(FATAL, "can't save terminal settings");
+ saved = TRUE;
+ break;
+
+ case 1:
+ termio = oldtermio;
+ termio.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
+ termio.c_cc[VMIN] = 1;
+ termio.c_cc[VTIME] = 0;
+ ioctl(0, TCSETA, &termio);
+ break;
+
+ case 2:
+ if ( saved == TRUE )
+ ioctl(0, TCSETA, &oldtermio);
+ break;
+ } /* End switch */
+
+} /* End of setupstdin */
+
+/*****************************************************************************/
+
+readline()
+
+{
+
+ int n; /* read() return value */
+ int ch; /* for interactive mode */
+
+ static int tries = 0; /* consecutive times read returned 0 */
+
+/*
+ *
+ * Reads characters coming back from the printer on ttyi up to a newline (or EOF)
+ * or until no more characters are available. Characters are put in mesg[], the
+ * string is terminated with '\0' when we're done with a line and TRUE is returned
+ * to the caller. If complete line wasn't available FALSE is returned. Interactive
+ * mode should loop here forever, except during start(), echoing characters to
+ * stdout. If it happens to leave FALSE should be returned. The non-blocking read
+ * gets us out until split() is called.
+ *
+ * Some users (apparently just on 3B2 DKHOST systems) have had problems with the
+ * two process implementation that's forced me to kludge things up some. When a
+ * printer (on those systems) is turned off while postio is transmitting files
+ * the write process hangs in writeblock() (postio.c) - it's typically in the
+ * middle of a write() call, while the read() call (below) continually returns 0.
+ * In the original code readline() returned FALSE when read() returned 0 and we
+ * get into a loop that never ends - because the write process is hung. In the
+ * one process implementation having read return 0 is legitimate because the line
+ * is opened for no delay, but with two processes the read() blocks and a return
+ * value of 0 should never occur. From my point of view the real problem is that
+ * the write() call hangs on 3B2 DKHOST systems and apparently doesn't anywhere
+ * else. If the write returned anything less than or equal to 0 writeblock() would
+ * shut things down. The kludge I've implemented counts the number of consecutive
+ * times read() returns a 0 and if it exceeds a limit (100) the read process will
+ * shut things down. In fact one return of 0 from read() when we're in the two
+ * process mode is undoubtedly sufficient and no counting should be necessary!!!
+ * Moving the check to getstatus() should also work and is probably where things
+ * belong.
+ *
+ */
+
+ if ( interactive == FALSE ) {
+ while ( (n = read(ttyi, ptr, 1)) != 0 ) {
+ if ( n < 0 )
+ if ( errno == EINTR )
+ continue;
+ else error(FATAL, "error reading %s", line);
+ tries = 0;
+ if ( *ptr == '\n' || *ptr == '\004' || ptr >= endmesg ) {
+ *(ptr+1) = '\0';
+ if ( *ptr == '\004' )
+ strcpy(ptr, "%%[ status: endofjob ]%%\n");
+ ptr = mesg;
+ return(TRUE);
+ } /* End if */
+ ptr++;
+ } /* End while */
+ if ( canread == TRUE && canwrite == FALSE ) /* read process kludge */
+ if ( ++tries > 100 )
+ error(FATAL, "printer appears to be offline - shutting down");
+ return(FALSE);
+ } /* End if */
+
+ if ( canwrite == TRUE ) /* don't block during start() */
+ return(FALSE);
+
+ while ( (ch = getc(fp_ttyi)) != EOF )
+ putc(ch, stdout);
+ return(FALSE);
+
+} /* End of readline */
+#endif
+
+/*****************************************************************************/
+
+#ifdef V9
+#include <ipc.h>
+
+char tbuf[256]; /* temporary input buffer */
+char *nptr = tbuf; /* next character comes from here */
+char *eptr = tbuf; /* one past the last character in tbuf */
+
+setupline()
+
+{
+
+ struct sgttyb sgtty;
+ struct ttydevb ttydev; /* for setting up the line */
+ static struct tchars tchar = { '\377', /* interrupt */
+ '\377', /* quit */
+ '\021', /* start output */
+ '\023', /* stop output */
+ '\377', /* end-of-file */
+ '\377' /* input delimiter */
+ };
+
+/*
+ *
+ * Line initialization for V9.
+ *
+ */
+
+ if ( line == NULL ) {
+ ttyi = ttyo = 1;
+ return;
+ } /* End if */
+ alarm(120); /* watch for hanging opens */
+ if ( line[0] == '/' ) {
+ if ( (ttyi = open(line, O_RDWR)) == -1 )
+ error(FATAL, "can't open %s", line);
+ } else if ((ttyi = ipcopen(ipcpath(line, "dk", 0), "")) < 0) {
+ sleep(5); /* wait for Datakit to hangup */
+ if ((ttyi = ipcopen(ipcpath(line, "dk", 0), "")) < 0) {
+ fprintf(stderr, "%s", errstr);
+ error(FATAL, "can't ipcopen %s", line);
+ }
+ }
+ alarm(0);
+
+ if ( (ttyo = dup(ttyi)) == -1 )
+ error(FATAL, "can't dup file descriptor for %s", line);
+
+ if ( ioctl(ttyi, FIOPUSHLD, &tty_ld) == -1 )
+ error(FATAL, "ioctl error - FIOPUSHLD");
+
+ if ( ioctl(ttyi, TIOCGDEV, &ttydev) == -1 )
+ error(FATAL, "ioctl error - TIOCGDEV");
+
+ if ( ioctl(ttyi, TIOCGETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCGETP");
+
+ sgtty.sg_flags &= ~ECHO;
+ sgtty.sg_flags &= ~CRMOD;
+ sgtty.sg_flags |= CBREAK;
+ ttydev.ispeed = baudrate;
+ ttydev.ospeed = baudrate;
+
+ if ( ioctl(ttyi, TIOCSDEV, &ttydev) == -1 )
+ error(FATAL, "ioctl error - TIOCSDEV");
+
+ if ( ioctl(ttyi, TIOCSETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCSETP");
+
+ if ( ioctl(ttyi, TIOCSETC, &tchar) == -1 )
+ error(FATAL, "ioctl error - TIOCSETC");
+
+ fp_ttyi = fdopen(ttyi, "r");
+
+} /* End of setupline */
+
+/*****************************************************************************/
+
+resetline()
+
+{
+
+ struct sgttyb sgtty;
+
+/*
+ *
+ * Only used if we're running the program as separate read and write processes.
+ * Called from split() after the initial connection has been made and returns
+ * TRUE if two processes should work. Haven't tested or even compiled the stuff
+ * for separate read and write processes on Ninth Edition systems - no guarantees
+ * even though we return TRUE!
+ *
+ */
+
+ if ( ioctl(ttyi, TIOCGETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCGETP");
+
+ sgtty.sg_flags |= TANDEM;
+
+ if ( ioctl(ttyi, TIOCSETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCSETP");
+
+ return(TRUE);
+
+} /* End of resetline */
+
+/*****************************************************************************/
+
+setupstdin(mode)
+
+ int mode; /* what to do with stdin settings */
+
+{
+
+ struct sgttyb sgtty;
+
+ static int saved = FALSE;
+ static struct sgttyb oldsgtty;
+
+/*
+ *
+ * Save (mode = 0), reset (mode = 1), or restore (mode = 2) the tty settings for
+ * stdin. Expect something like raw mode with no echo will be set up. Need to make
+ * sure interrupt and quit still work - they're the only good way to exit when
+ * we're running interactive mode. I haven't tested or even compiled this code
+ * so there are no guarantees.
+ *
+ */
+
+ if ( interactive == TRUE )
+ switch ( mode ) {
+ case 0:
+ if ( ioctl(0, TIOCGETP, &oldsgtty) == -1 )
+ error(FATAL, "can't save terminal settings");
+ saved = TRUE;
+ break;
+
+ case 1:
+ sgtty = oldsgtty;
+ sgtty.sg_flags &= ~ECHO;
+ sgtty.sg_flags |= CBREAK;
+ ioctl(0, TIOCSETP, &sgtty);
+ break;
+
+ case 2:
+ if ( saved == TRUE )
+ ioctl(0, TIOCSETP, &oldsgtty);
+ break;
+ } /* End switch */
+
+} /* End of setupstdin */
+
+/*****************************************************************************/
+
+readline()
+
+{
+
+ int n; /* read() return value */
+ int ch; /* for interactive mode */
+
+/*
+ *
+ * Reads characters coming back from the printer on ttyi up to a newline (or EOF)
+ * and transfers each line to the mesg[] array. Everything available on ttyi is
+ * initially stored in tbuf[] and a line at a time is transferred from there to
+ * mesg[]. The string in mesg[] is terminated with a '\0' and TRUE is returned to
+ * the caller when we find a newline, EOF, or reach the end of the mesg[] array.
+ * If nothing is available on ttyi we return FALSE if a single process is being
+ * used for reads and writes, while in the two process implementation we force a
+ * one character read. Interactive mode loops here forever, except during start(),
+ * echoing everything that comes back on ttyi to stdout. The performance of a
+ * simple getc/putc loop for interactive mode was unacceptable when run under mux
+ * and has been replaced by more complicated code. When layers wasn't involved
+ * the getc/putc loop worked well.
+ *
+ */
+
+ if ( interactive == FALSE ) {
+ while ( 1 ) {
+ while ( nptr < eptr ) { /* grab characters from tbuf */
+ *ptr = *nptr++;
+ if ( *ptr == '\r' ) continue;
+ if ( *ptr == '\n' || *ptr == '\004' || ptr >= endmesg ) {
+ *(ptr+1) = '\0';
+ if ( *ptr == '\004' )
+ strcpy(ptr, "%%[ status: endofjob ]%%\n");
+ ptr = mesg;
+ return(TRUE);
+ } /* End if */
+ ++ptr;
+ } /* End for */
+
+ nptr = eptr = tbuf;
+ if ( ioctl(ttyi, FIONREAD, &n) < 0 )
+ if ( errno == EINTR )
+ continue;
+ else error(FATAL, "ioctl error - FIONREAD");
+ if ( n <= 0 )
+ if ( canwrite == TRUE )
+ return(FALSE);
+ n = ((n < 1) ? 1 : ((n < sizeof(tbuf)) ? n : sizeof(tbuf)));
+ if ( (n = read(ttyi, tbuf, n)) < 0 )
+ if ( errno == EINTR )
+ continue;
+ else error(FATAL, "error reading line %s", line);
+ else eptr = nptr + n;
+ } /* End while */
+ } /* End if */
+
+ if ( canwrite == TRUE ) /* don't block during start() */
+ return(FALSE);
+
+ while ( 1 ) { /* only interactive mode gets here */
+ if ( ioctl(ttyi, FIONREAD, &n) < 0 )
+ error(FATAL, "ioctl error - FIONREAD");
+ n = ((n < 1) ? 1 : ((n < sizeof(tbuf)) ? n : sizeof(tbuf)));
+ if ( (n = read(ttyi, tbuf, n)) < 0 )
+ error(FATAL, "error reading line %s", line);
+ else if ( n == 0 ) /* should not happen */
+ error(FATAL, "end of file in interactive mode");
+ if ( write(1, tbuf, n) != n )
+ error(FATAL, "error writing to stdout");
+ } /* End while */
+
+ return(FALSE);
+
+} /* End of readline */
+#endif
+
+/*****************************************************************************/
+
+#ifdef BSD4_2
+setupline()
+
+{
+
+ struct sgttyb sgtty;
+ static struct tchars tchar = { '\377', /* interrupt */
+ '\377', /* quit */
+ '\021', /* start output */
+ '\023', /* stop output */
+ '\377', /* end-of-file */
+ '\377' /* input delimiter */
+ };
+ long lmodes;
+ int disc = NTTYDISC;
+
+/*
+ *
+ * Line initialization for BSD4_2. As in the System V code, if no line is given
+ * (ie. line == NULL) we continue on as before using stdout as ttyi and ttyo.
+ *
+ */
+
+ if ( line == NULL )
+ ttyi = fileno(stdout);
+ else if ( (ttyi = open(line, O_RDWR)) == -1 )
+ error(FATAL, "can't open %s", line);
+
+ if ( (ttyo = dup(ttyi)) == -1 )
+ error(FATAL, "can't dup file descriptor for %s", line);
+
+ if (ioctl(ttyi, TIOCSETD, &disc) == -1 )
+ error(FATAL, "ioctl error - TIOCSETD");
+
+ if ( ioctl(ttyi, TIOCGETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCGETP");
+
+ if ( ioctl(ttyi, TIOCLGET, &lmodes) == -1 )
+ error(FATAL, "ioctl error - TIOCLGET");
+
+ sgtty.sg_flags &= ~ECHO;
+ sgtty.sg_flags &= ~CRMOD;
+ sgtty.sg_flags |= CBREAK;
+ sgtty.sg_ispeed = baudrate;
+ sgtty.sg_ospeed = baudrate;
+ lmodes |= LDECCTQ;
+
+ if ( ioctl(ttyi, TIOCSETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCSETP");
+
+ if ( ioctl(ttyi, TIOCSETC, &tchar) == -1 )
+ error(FATAL, "ioctl error - TIOCSETC");
+
+ if ( ioctl(ttyi, TIOCLSET, &lmodes) == -1 )
+ error(FATAL, "ioctl error - TIOCLSET");
+
+ fp_ttyi = fdopen(ttyi, "r");
+
+} /* End of setupline */
+
+/*****************************************************************************/
+
+resetline()
+
+{
+
+ struct sgttyb sgtty;
+
+/*
+ *
+ * Only used if we're running the program as separate read and write processes.
+ * Called from split() after the initial connection has been made and returns
+ * TRUE if two processes should work. Haven't tested or even compiled the stuff
+ * for separate read and write processes on Berkeley systems - no guarantees
+ * even though we return TRUE!
+ *
+ */
+
+ if ( ioctl(ttyi, TIOCGETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCGETP");
+
+ sgtty.sg_flags |= TANDEM;
+
+ if ( ioctl(ttyi, TIOCSETP, &sgtty) == -1 )
+ error(FATAL, "ioctl error - TIOCSETP");
+
+ return(TRUE);
+
+} /* End of resetline */
+
+/*****************************************************************************/
+
+setupstdin(mode)
+
+ int mode; /* what to do with stdin settings */
+
+{
+
+ struct sgttyb sgtty;
+
+ static int saved = FALSE;
+ static struct sgttyb oldsgtty;
+
+/*
+ *
+ * Save (mode = 0), reset (mode = 1), or restore (mode = 2) the tty settings for
+ * stdin. Expect something like raw mode with no echo will be set up. Need to make
+ * sure interrupt and quit still work - they're the only good way to exit when
+ * we're running interactive mode. I haven't tested or even compiled this code
+ * so there are no guarantees.
+ *
+ */
+
+ if ( interactive == TRUE )
+ switch ( mode ) {
+ case 0:
+ if ( isatty(0) != 1 )
+ error(FATAL, "stdin not a terminal - can't run interactive mode");
+ if ( ioctl(0, TIOCGETP, &oldsgtty) == -1 )
+ error(FATAL, "can't save terminal settings");
+ saved = TRUE;
+ break;
+
+ case 1:
+ sgtty = oldsgtty;
+ sgtty.sg_flags &= ~ECHO;
+ sgtty.sg_flags |= CBREAK;
+ ioctl(0, TIOCSETP, &sgtty);
+ break;
+
+ case 2:
+ if ( saved == TRUE )
+ ioctl(0, TIOCSETP, &oldsgtty);
+ break;
+ } /* End switch */
+
+} /* End of setupstdin */
+
+/*****************************************************************************/
+
+readline()
+
+{
+
+ int n; /* read() return value */
+ int ch; /* for interactive mode */
+
+/*
+ *
+ * Reads characters coming back from the printer on ttyo up to a newline (or EOF)
+ * or until no more characters are available. Characters are put in mesg[], the
+ * string is terminated with '\0' when we're done with a line and TRUE is returned
+ * to the caller. If complete line wasn't available FALSE is returned. Interactive
+ * mode should loop here forever, except during start(), echoing characters to
+ * stdout. If it happens to leave FALSE should be returned. Probably should read
+ * everything available on ttyi into a temporary buffer and work from there rather
+ * than reading one character at a time.
+ *
+ */
+
+ if ( interactive == FALSE ) {
+ while ( 1 ) {
+ if ( ioctl(ttyi, FIONREAD, &n) < 0 )
+ if ( errno == EINTR )
+ continue;
+ else error(FATAL, "ioctl error - FIONREAD");
+ if ( n <= 0 )
+ if ( canwrite == TRUE )
+ return(FALSE);
+ else n = 1;
+ for ( ; n > 0; n-- ) {
+ /*if ( read(ttyi, ptr, 1) < 0 )*/
+ if ( (*ptr = getc(fp_ttyi)) == EOF )
+ if ( errno == EINTR )
+ continue;
+ else error(FATAL, "error reading %s", line);
+ if ( *ptr == '\r' ) continue;
+ if ( *ptr == '\n' || *ptr == '\004' || ptr >= endmesg ) {
+ *(ptr+1) = '\0';
+ if ( *ptr == '\004' )
+ strcpy(ptr, "%%[ status: endofjob ]%%\n");
+ ptr = mesg;
+ return(TRUE);
+ } /* End if */
+ ++ptr;
+ } /* End for */
+ } /* End while */
+ } /* End if */
+
+ if ( canwrite == TRUE ) /* don't block during start() */
+ return(FALSE);
+
+ while ( (ch = getc(fp_ttyi)) != EOF )
+ putc(ch, stdout);
+ return(FALSE);
+
+} /* End of readline */
+
+/*****************************************************************************/
+
+/* @(#)strspn.c 1.2 */
+/*LINTLIBRARY*/
+/*
+ * Return the number of characters in the maximum leading segment
+ * of string which consists solely of characters from charset.
+ */
+int
+strspn(string, charset)
+char *string;
+register char *charset;
+{
+ register char *p, *q;
+
+ for(q=string; *q != '\0'; ++q) {
+ for(p=charset; *p != '\0' && *p != *q; ++p)
+ ;
+ if(*p == '\0')
+ break;
+ }
+ return(q-string);
+}
+
+/* @(#)strpbrk.c 1.2 */
+/*LINTLIBRARY*/
+/*
+ * Return ptr to first occurance of any character from `brkset'
+ * in the character string `string'; NULL if none exists.
+ */
+
+char *
+strpbrk(string, brkset)
+register char *string, *brkset;
+{
+ register char *p;
+
+ do {
+ for(p=brkset; *p != '\0' && *p != *string; ++p)
+ ;
+ if(*p != '\0')
+ return(string);
+ }
+ while(*string++);
+ return((char*)0);
+}
+
+/* @(#)strtok.c 1.2 */
+/* 3.0 SID # 1.2 */
+/*LINTLIBRARY*/
+/*
+ * uses strpbrk and strspn to break string into tokens on
+ * sequentially subsequent calls. returns NULL when no
+ * non-separator characters remain.
+ * `subsequent' calls are calls with first argument NULL.
+ */
+
+
+extern int strspn();
+extern char *strpbrk();
+
+char *
+strtok(string, sepset)
+char *string, *sepset;
+{
+ register char *p, *q, *r;
+ static char *savept;
+
+ /*first or subsequent call*/
+ p = (string == (char*)0)? savept: string;
+
+ if(p == 0) /* return if no tokens remaining */
+ return((char*)0);
+
+ q = p + strspn(p, sepset); /* skip leading separators */
+
+ if(*q == '\0') /* return if no tokens remaining */
+ return((char*)0);
+
+ if((r = strpbrk(q, sepset)) == (char*)0) /* move past token */
+ savept = 0; /* indicate this is last token */
+ else {
+ *r = '\0';
+ savept = ++r;
+ }
+ return(q);
+}
+#endif
+
+/*****************************************************************************/
+
+#ifdef DKHOST
+
+#ifndef DKSTREAMS
+short dkrmode[3] = {DKR_TIME, 0, 0};
+#endif
+
+dkhost_connect()
+
+{
+
+ int ofd; /* for saving and restoring stderr */
+ int dfd;
+ int retrytime = 5;
+
+/*
+ *
+ * Tries to connect to a Datakit destination. The extra stuff I've added to save
+ * and later restore stderr is primarily for our spooling setup at Murray Hill.
+ * postio is usually called with stderr directed to a file that will be returned
+ * to the user when the job finishes printing. Problems encountered by dkdial(),
+ * like busy messages, go to stderr but don't belong in the user's mail. They'll
+ * be temporarily directed to the log file. After we've connected stderr will be
+ * restored.
+ *
+ */
+
+ if ( *line == '\0' )
+ error(FATAL, "incomplete Datakit line");
+
+ if ( fp_log != NULL && fp_log != stderr ) { /* redirect dkdial errors */
+ ofd = dup(2);
+ close(2);
+ dup(fileno(fp_log));
+ } /* End if */
+
+ while ( (dfd = ttyi = dkdial(line)) < 0 ) {
+ if ( retrytime < 0 )
+ error(FATAL, "can't connect to %s", line);
+ sleep(retrytime++);
+ if ( retrytime > 60 )
+ retrytime = 60;
+ } /* End while */
+
+ if ( fp_log != NULL && fp_log != stderr ) { /* restore stderr */
+ close(2);
+ dup(ofd);
+ close(ofd);
+ } /* End if */
+
+#ifndef DKSTREAMS
+ if ( ioctl(ttyi, DIOCRMODE, dkrmode) == -1 )
+ error(FATAL, "ioctl error - DIOCRMODE");
+
+#ifdef DIOURPWD
+ if ( window_size > 0 ) {
+ short dkparm[3];
+
+ dkparm[0] = dkminor(ttyi);
+ dkparm[1] = 1;
+ dkparm[2] = window_size;
+ if ( ioctl(ttyi, DIOURPWD, dkparm) < 0 || ioctl(ttyi, DIOCFLUSH, 0) < 0 )
+ error(NON_FATAL, "WSA failed");
+ } /* End if */
+#endif
+
+ line = dtnamer(dkminor(ttyi));
+
+ if ( (ttyi = open(line, O_RDWR)) == -1 )
+ error(FATAL, "can't open %s", line);
+
+ close(dfd);
+#endif
+
+} /* End of dkhost_connect */
+#endif
+
+/*****************************************************************************/
+
diff --git a/sys/src/cmd/postscript/postio/ifdef.h b/sys/src/cmd/postscript/postio/ifdef.h
new file mode 100755
index 000000000..f158c46db
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/ifdef.h
@@ -0,0 +1,66 @@
+/*
+ *
+ * Conditional compilation definitions needed in ifdef.c and postio.c.
+ *
+ */
+
+#ifdef SYSV
+#include <termio.h>
+
+#ifdef DKSTREAMS
+#include <sys/stream.h>
+#include <sys/stropts.h>
+#endif
+
+#endif
+
+#ifdef V9
+#include <sys/filio.h>
+#include <sys/ttyio.h>
+
+extern int tty_ld;
+#endif
+
+#ifdef BSD4_2
+#include <sgtty.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#define FD_ZERO(s) (s) = 0
+#define FD_SET(n,s) (s) |= 1 << (n)
+
+extern int errno;
+#endif
+
+#ifdef DKHOST
+#include <dk.h>
+#include <sysexits.h>
+
+extern char *dtnamer();
+extern int dkminor();
+#endif
+
+/*
+ *
+ * External variable declarations - most (if not all) are defined in postio.c and
+ * needed by the routines in ifdef.c.
+ *
+ */
+
+extern char *line; /* printer is on this line */
+extern int ttyi; /* input */
+extern int ttyo; /* and output file descriptors */
+extern FILE *fp_log; /* just for DKHOST stuff */
+
+extern char mesg[]; /* exactly what came back on ttyi */
+extern char *endmesg; /* one in front of last free slot in mesg */
+extern int next; /* next character goes in mesg[next] */
+
+extern short baudrate; /* printer is running at this speed */
+extern int stopbits; /* and expects this many stop bits */
+extern int interactive; /* TRUE for interactive mode */
+
+extern int whatami; /* a READ or WRITE process - or both */
+extern int canread; /* allows reads */
+extern int canwrite; /* and writes if TRUE */
+
diff --git a/sys/src/cmd/postscript/postio/postio.1 b/sys/src/cmd/postscript/postio/postio.1
new file mode 100755
index 000000000..3075951b7
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/postio.1
@@ -0,0 +1,308 @@
+.TH POSTIO 1 "DWB 3.2"
+.SH NAME
+.B postio
+\- serial interface for PostScript printers
+.SH SYNOPSIS
+\*(mBpostio\f1
+.OP \-l line
+.OP "" options []
+.OP "" files []
+.SH DESCRIPTION
+.B postio
+sends
+.I files
+to the PostScript printer attached to
+.IR line .
+If no
+.I files
+are specified the standard input is sent.
+The first group of
+.I options
+should be sufficient for most applications:
+.TP 0.75i
+.OP \-b speed
+Transmit data over
+.I line
+at baud rate
+.I speed.
+Recognized baud rates are 1200, 2400, 4800, 9600, and 19200.
+The default
+.I speed
+is 9600 baud.
+.TP
+.OP \-c
+Do not send
+.MR ^C s
+(interrupts) to the printer,
+which means
+.B postio
+does not force a busy printer into the idle state.
+.TP
+.OP \-l line
+Connect to printer attached to
+.IR line .
+In most cases there is no default and
+.B postio
+must be able to read and write
+.IR line .
+If
+.I line
+does not begin with
+.MW /
+it is treated as a Datakit destination.
+.TP
+.OP \-q
+Prevents status queries while
+.I files
+are being sent to the printer.
+When status queries are disabled a dummy message is appended
+to the log file before each block is transmitted.
+.TP
+.OP \-B num
+Set internal buffer size for reading and writing
+.I files
+to
+.I num
+bytes
+(default is 2048 bytes).
+.TP
+.OP \-D
+Enable debug mode.
+Guarantees that everything read on
+.I line
+will be added to the log file (standard error by default).
+.TP
+.OP \-L file
+Data received on
+.I line
+gets put in
+.IR file .
+The default log
+.I file
+is standard error.
+Printer or status messages that do not indicate a change in state
+are not normally written to
+.I file
+but can be forced out using the
+.OP \-D
+option.
+.TP
+.OP \-P string
+Send
+.I string
+to the printer before any of the input files.
+The default
+.I string
+is simple PostScript code that disables timeouts.
+.TP
+.OP \-R num
+Run
+.B postio
+as a single process if
+.I num
+is 1 or as separate read and write processes if
+.I num
+is 2.
+By default
+.B postio
+runs as a single process.
+.PP
+The next two
+.I options
+are provided for users who expect to run
+.B postio
+on their own.
+Neither is suitable for use in spooler interface
+programs:
+.TP 0.35i
+.OP \-i
+Run the program in interactive mode.
+Any
+.I files
+are sent first and followed by the standard input.
+Forces separate read and write processes
+and overrides many other options.
+To exit interactive mode use your interrupt or quit character.
+To get a friendly interactive connection with the printer type
+.MW executive
+on a line by itself.
+.TP
+.OP \-t
+Data received on
+.I line
+and not recognized as printer or status information is written to
+the standard output.
+Forces separate read and write processes.
+Convenient if you have a PostScript program that
+will be returning useful data to the host.
+.PP
+The last option is not generally recommended and should only
+be used if all else fails to provide a reliable connection:
+.TP 0.35i
+.OP \-S
+Slow the transmission of data to the printer.
+Severely limits throughput, runs as a single process,
+disables the
+.OP \-q
+option, limits the internal buffer size to 1024 bytes,
+can use an excessive amount of
+.SM CPU
+time, and does nothing in interactive mode.
+.PP
+Best performance is usually obtained by using
+a large internal buffer
+.OP -B "" ) (
+and by running the program as separate read and write processes
+.OP \-R2 "" ). (
+Inability to fork the additional process causes
+.B postio
+to continue as a single read/write process.
+When one process is used, only data sent to the printer is flow-controlled.
+.PP
+The options are not all mutually exclusive.
+The
+.OP \-i
+option always wins, selecting its own settings for whatever is
+needed to run interactive mode, independent of anything else
+found on the command line.
+Interactive mode runs as separate read and write processes
+and few of the other
+.I options
+accomplish anything in the presence of the
+.OP \-i
+option.
+The
+.OP \-t
+option needs a reliable two way connection to the printer and
+therefore tries to force separate read and write processes.
+The
+.OP \-S
+option relies on the status query mechanism, so
+.OP \-q
+is disabled and the program runs as a single process.
+.PP
+In most cases
+.B postio
+starts by making a connection to
+.I line
+and then attempts to force the printer into the
+.SM IDLE
+state by sending an appropriate sequence of
+.MW ^T
+(status query),
+.MW ^C
+(interrupt), and
+.MW ^D
+(end of job) characters.
+When the printer goes
+.SM IDLE
+.I files
+are transmitted along with an occasional
+.MW ^T
+(unless the
+.OP \-q
+option was used).
+After all the
+.I files
+are sent the program waits until it is reasonably sure the
+job is complete.
+Printer generated error messages received at any time
+except while establishing the initial connection
+(or when running interactive mode) cause
+.B postio
+to exit with a non-zero status.
+In addition to being added to the log file, printer error messages
+are also echoed to standard error.
+.SH EXAMPLES
+Run as a single process at 9600 baud and send
+.I file1
+and
+.I file2
+to the printer attached to
+.MR /dev/tty01 :
+.EX
+postio -l /dev/tty01 \f2file1 file2
+.EE
+Same as above except two processes are used,
+the internal buffer is set to 4096 bytes,
+and data returned by the printer gets put in file
+.MR log :
+.EX
+postio -R2 -B4096 -l/dev/tty01 -Llog \f2file1 file2
+.EE
+Establish an interactive connection with the printer at Datakit
+destination
+.MR my/printer :
+.EX
+postio -i -l my/printer
+.EE
+Send file
+.MW program
+to the printer connected to
+.MR /dev/tty22 ,
+recover any data in file
+.MR results ,
+and put log messages in file
+.MR log :
+.EX
+postio -t -l /dev/tty22 -L log program >results
+.EE
+.SH DIAGNOSTICS
+A 0 exit status is returned if the files ran successfully.
+System errors (e.g., ``can't open the line'') set the low order
+bit in the exit status, while PostScript errors set bit 1.
+An exit status of 2 usually means the printer
+detected a PostScript error in the input
+.IR files .
+.SH WARNINGS
+.PP
+The input
+.I files
+are handled as a single PostScript job.
+Sending several different jobs, each with their own internal
+end of job mark
+.RM ( ^D )
+is not guaranteed to work properly.
+.B postio
+may quit before all the jobs have completed and could be restarted
+before the last one finishes.
+.PP
+All the capabilities described above may not be available on every
+machine or even across the different versions of
+.SM UNIX
+that are currently supported by the program.
+For example, the code needed to connect to a Datakit destination may only
+work on System\ V and may require that the
+.SM DKHOST
+software package be available at compile time.
+.PP
+There may be no default
+.I line
+so using
+.OP \-l
+option is strongly recommended.
+If omitted
+.B postio
+may attempt to connect to the printer using the standard output.
+If Datakit is involved the
+.OP \-b
+may be ineffective and attempts by
+.B postio
+to flow control data in both directions may not work.
+The
+.OP \-q
+option can help if the printer is connected to \s-1RADIAN\s+1.
+The
+.OP \-S
+option is not generally recommended and should only be used if
+all else fails to establish a reliable connection.
+.SH SEE ALSO
+.BR buildtables (1),
+.BR dpost (1),
+.BR postdaisy (1),
+.BR postdmd (1),
+.BR postmd (1),
+.BR postprint (1),
+.BR postreverse (1),
+.BR posttek (1),
+.BR printfont (1)
diff --git a/sys/src/cmd/postscript/postio/postio.c b/sys/src/cmd/postscript/postio/postio.c
new file mode 100755
index 000000000..14adc77a1
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/postio.c
@@ -0,0 +1,1212 @@
+/*
+ *
+ * postio - RS-232 serial interface for PostScript printers
+ *
+ * A simple program that manages input and output for PostScript printers. Much
+ * has been added and changed from early versions of the program, but the basic
+ * philosophy is still the same. Don't send real data until we're certain we've
+ * connected to a PostScript printer that's in the idle state and try to hold the
+ * connection until the job is completely done. It's more work than you might
+ * expect is necessary, but should provide a reasonably reliable spooler interface
+ * that can return error indications to the caller via the program's exit status.
+ *
+ * I've added code that will let you split the program into separate read/write
+ * processes. Although it's not the default it should be useful if you have a file
+ * that will be returning useful data from the printer. The two process stuff was
+ * laid down on top of the single process code and both methods still work. The
+ * implementation isn't as good as it could be, but didn't require many changes
+ * to the original program (despite the fact that there are now many differences).
+ *
+ * By default the program still runs as a single process. The -R2 option forces
+ * separate read and write processes after the intial connection is made. If you
+ * want that as the default initialize splitme (below) to TRUE. In addition the
+ * -t option that's used to force stuff not recognized as status reports to stdout
+ * also tries to run as two processes (by setting splitme to TRUE). It will only
+ * work if the required code (ie. resetline() in ifdef.c) has been implemented
+ * for your Unix system. I've only tested the System V code.
+ *
+ * Code needed to support interactive mode has also been added, although again it's
+ * not as efficient as it could be. It depends on the system dependent procedures
+ * resetline() and setupstdin() (file ifdef.c) and for now is only guaranteed to
+ * work on System V. Can be requested using the -i option.
+ *
+ * Quiet mode (-q option) is also new, but was needed for some printers connected
+ * to RADIAN. If you're running in quiet mode no status requests will be sent to
+ * the printer while files are being transmitted (ie. in send()).
+ *
+ * The program expects to receive printer status lines that 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 this list isn't complete. Sending a '\024' (control T) character forces
+ * the return of a status report. PostScript errors detected on the printer result
+ * in the immediate transmission of special error messages that look like,
+ *
+ * %%[ Error: undefined; OffendingCommand: xxx ]%%
+ * %%[ Flushing: rest of job (to end-of-file) will be ignored ]%%
+ *
+ * although we only use the Error and Flushing keywords. Finally conditions, like
+ * being out of paper, result in other messages being sent back from the printer
+ * over the communications line. Typical PrinterError messages look like,
+ *
+ * %%[ PrinterError: out of paper; source: serial 25 ]%%
+ * %%[ PrinterError: paper jam; source: serial 25 ]%%
+ *
+ * although we only use the PrinterError keyword rather than trying to recognize
+ * all possible printer errors.
+ *
+ * The implications of using one process and only flow controlling data going to
+ * the printer are obvious. Job transmission should be reliable, but there can be
+ * data loss in stuff sent back from the printer. Usually that only caused problems
+ * with jobs designed to run on the printer and return useful data back over the
+ * communications line. If that's the kind of job you're sending call postio with
+ * the -t option. That should force the program to split into separate read and
+ * write processes and everything not bracketed by "%%[ " and " ]%%" strings goes
+ * to stdout. In otherwords the data you're expecting should be separated from the
+ * status stuff that goes to the log file (or stderr). The -R2 option does almost
+ * the same thing (ie. separate read and write processes), but everything that
+ * comes back from the printer goes to the log file (stderr by default) and you'll
+ * have to separate your data from any printer messages.
+ *
+ * A typical command line might be,
+ *
+ * postio -l /dev/tty01 -b 9600 -L log file1 file2
+ *
+ * where -l selects the line, -b sets the baud rate, and -L selects the printer
+ * log file. Since there's no default line, at least not right now, you'll always
+ * need to use the -l option, and if you don't choose a log file stderr will be
+ * used. If you have a program that will be returning data the command line might
+ * look like,
+ *
+ * postio -t -l/dev/tty01 -b9600 -Llog file >results
+ *
+ * Status stuff goes to file log while the data you're expecting back from the
+ * printer gets put in file results.
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "ifdef.h" /* conditional compilation stuff */
+#include "gen.h" /* general purpose definitions */
+#include "postio.h" /* some special definitions */
+
+char **argv; /* global so everyone can use them */
+int argc;
+
+char *prog_name = ""; /* really just for error messages */
+int x_stat = 0; /* program exit status */
+int debug = OFF; /* debug flag */
+int ignore = OFF; /* what's done for FATAL errors */
+
+char *line = NULL; /* printer is on this tty line */
+short baudrate = BAUDRATE; /* and running at this baud rate */
+Baud baudtable[] = BAUDTABLE; /* converts strings to termio values */
+
+int stopbits = 1; /* number of stop bits */
+int tostdout = FALSE; /* non-status stuff goes to stdout? */
+int quiet = FALSE; /* no status queries in send() if TRUE */
+int interactive = FALSE; /* interactive mode */
+char *postbegin = POSTBEGIN; /* preceeds all the input files */
+int useslowsend = FALSE; /* not recommended! */
+int sendctrlC = TRUE; /* interrupt with ctrl-C when BUSY */
+int window_size = -1; /* for Datakit - use -w */
+
+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[] */
+
+int splitme = FALSE; /* into READ and WRITE processes if TRUE */
+int whatami = READWRITE; /* a READ or WRITE process - or both */
+int canread = TRUE; /* allow reads */
+int canwrite = TRUE; /* and writes if TRUE */
+int otherpid = -1; /* who gets signals if greater than 1 */
+int joinsig = SIGTRAP; /* reader gets this when writing is done */
+int writedone = FALSE; /* and then sets this to TRUE */
+
+char mesg[MESGSIZE]; /* exactly what came back on ttyi */
+char sbuf[MESGSIZE]; /* for parsing the message */
+int next = 0; /* next character goes in mesg[next] */
+char *mesgptr = NULL; /* printer message starts here in mesg[] */
+char *endmesg = NULL; /* as far as readline() can go in mesg[] */
+
+Status status[] = STATUS; /* for converting status strings */
+int nostatus = NOSTATUS; /* default getstatus() return value */
+
+int currentstate = NOTCONNECTED; /* what's happening START, SEND, or DONE */
+
+int ttyi = 0; /* input */
+int ttyo = 2; /* and output file descriptors */
+
+FILE *fp_log = stderr; /* log file for stuff from the printer */
+
+/*****************************************************************************/
+
+main(agc, agv)
+
+ int agc;
+ char *agv[];
+
+{
+
+/*
+ *
+ * A simple program that manages input and output for PostScript printers. Can run
+ * as a single process or as separate read/write processes. What's done depends on
+ * the value assigned to splitme when split() is called.
+ *
+ */
+
+ argc = agc; /* other routines may want them */
+ argv = agv;
+
+ prog_name = argv[0]; /* really just for error messages */
+
+ init_signals(); /* sets up interrupt handling */
+ options(); /* get command line options */
+ initialize(); /* must be done after options() */
+ start(); /* make sure the printer is ready */
+ split(); /* into read/write processes - maybe */
+ arguments(); /* then send each input file */
+ done(); /* wait until the printer is finished */
+ cleanup(); /* make sure the write process stops */
+
+ exit(x_stat); /* everything probably went OK */
+
+} /* End of main */
+
+/*****************************************************************************/
+
+init_signals()
+
+{
+
+ void interrupt(); /* handles them if we catch signals */
+
+/*
+ *
+ * Makes sure we handle interrupts. The proper way to kill the program, if
+ * necessary, is to do a kill -15. That forces a call to interrupt(), which in
+ * turn tries to reset the printer and then exits with a non-zero status. If the
+ * program is running as two processes, sending SIGTERM to either the parent or
+ * child should clean things up.
+ *
+ */
+
+ if ( signal(SIGINT, interrupt) == SIG_IGN ) {
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ } else {
+ signal(SIGHUP, interrupt);
+ signal(SIGQUIT, interrupt);
+ } /* End else */
+
+ signal(SIGTERM, interrupt);
+
+} /* End of init_sig */
+
+/*****************************************************************************/
+
+options()
+
+{
+
+ int ch; /* return value from getopt() */
+ char *optnames = "b:cil:qs:tw:B:L:P:R:SDI";
+
+ extern char *optarg; /* used by getopt() */
+ extern int optind;
+
+/*
+ *
+ * Reads and processes the command line options. The -R2, -t, and -i options all
+ * force separate read and write processes by eventually setting splitme to TRUE
+ * (check initialize()). The -S option is not recommended and should only be used
+ * as a last resort!
+ *
+ */
+
+ while ( (ch = getopt(argc, argv, optnames)) != EOF ) {
+ switch ( ch ) {
+ case 'b': /* baud rate string */
+ baudrate = getbaud(optarg);
+ break;
+
+ case 'c': /* no ctrl-C's */
+ sendctrlC = FALSE;
+ break;
+
+ case 'i': /* interactive mode */
+ interactive = TRUE;
+ break;
+
+ case 'l': /* printer line */
+ line = optarg;
+ break;
+
+ case 'q': /* no status queries - for RADIAN? */
+ quiet = TRUE;
+ break;
+
+ case 's': /* use 2 stop bits - for UNISON? */
+ if ( (stopbits = atoi(optarg)) < 1 || stopbits > 2 )
+ stopbits = 1;
+ break;
+
+ case 't': /* non-status stuff goes to stdout */
+ tostdout = TRUE;
+ break;
+
+ case 'w': /* Datakit window size */
+ window_size = atoi(optarg);
+ 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 code */
+ postbegin = optarg;
+ break;
+
+ case 'R': /* run as one or two processes */
+ if ( atoi(optarg) == 2 )
+ splitme = TRUE;
+ else splitme = FALSE;
+ break;
+
+ case 'S': /* slow and kludged up version of send */
+ useslowsend = TRUE;
+ 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 */
+
+ argc -= optind; /* get ready for non-option args */
+ argv += optind;
+
+} /* End of options */
+
+/*****************************************************************************/
+
+getbaud(rate)
+
+ char *rate; /* string representing the baud rate */
+
+{
+
+ int i; /* for looking through baudtable[] */
+
+/*
+ *
+ * Called from options() to convert a baud rate string into an appropriate termio
+ * value. *rate is looked up in baudtable[] and if it's found, the corresponding
+ * value is returned to the caller.
+ *
+ */
+
+ for ( i = 0; baudtable[i].rate != NULL; i++ )
+ if ( strcmp(rate, baudtable[i].rate) == 0 )
+ return(baudtable[i].val);
+
+ error(FATAL, "don't recognize baud rate %s", rate);
+
+} /* End of getbaud */
+
+/*****************************************************************************/
+
+initialize()
+
+{
+
+/*
+ *
+ * Initialization, a few checks, and a call to setupline() (file ifdef.c) to open
+ * and configure the communications line. Settings for interactive mode always
+ * take precedence. The setupstdin() call with an argument of 0 saves the current
+ * terminal settings if interactive mode has been requested - otherwise nothing's
+ * done. Unbuffering stdout (via the setbuf() call) isn't really needed on System V
+ * since it's flushed whenever terminal input is requested. It's more efficient if
+ * we buffer the stdout (on System V) but safer (for other versions of Unix) if we
+ * include the setbuf() call.
+ *
+ */
+
+ whatami = READWRITE; /* always run start() as one process */
+ canread = canwrite = TRUE;
+
+ if ( tostdout == TRUE ) /* force separate read/write processes */
+ splitme = TRUE;
+
+ if ( interactive == TRUE ) { /* interactive mode settings always win */
+ quiet = FALSE;
+ tostdout = FALSE;
+ splitme = TRUE;
+ blocksize = 1;
+ postbegin = NULL;
+ useslowsend = FALSE;
+ nostatus = INTERACTIVE;
+ setbuf(stdout, NULL);
+ } /* End if */
+
+ if ( useslowsend == TRUE ) { /* last resort only - not recommended */
+ quiet = FALSE;
+ splitme = FALSE;
+ if ( blocksize > 1024 ) /* don't send too much all at once */
+ blocksize = 1024;
+ } /* End if */
+
+ if ( tostdout == TRUE && fp_log == stderr )
+ fp_log = NULL;
+
+ if ( line == NULL && (interactive == TRUE || tostdout == TRUE) )
+ error(FATAL, "a printer line must be supplied - use the -l option");
+
+ if ( (block = malloc(blocksize)) == NULL )
+ error(FATAL, "no memory");
+
+ endmesg = mesg + sizeof mesg - 2; /* one byte from last position in mesg */
+
+ setupline(); /* configure the communications line */
+ setupstdin(0); /* save current stdin terminal settings */
+
+} /* End of initialize */
+
+/*****************************************************************************/
+
+start()
+
+{
+
+/*
+ *
+ * Tries to put the printer in the IDLE state before anything important is sent.
+ * Run as a single process no matter what has been assigned to splitme. Separate
+ * read and write processes, if requested, will be created after we're done here.
+ *
+ */
+
+ logit("printer startup\n");
+
+ currentstate = START;
+ clearline();
+
+ while ( 1 )
+ switch ( getstatus(1) ) {
+ case IDLE:
+ case INTERACTIVE:
+ if ( postbegin != NULL && *postbegin != '\0' )
+ Write(ttyo, postbegin, strlen(postbegin));
+ clearline();
+ return;
+
+ case BUSY:
+ if ( sendctrlC == TRUE ) {
+ Write(ttyo, "\003", 1);
+ Rest(1);
+ } /* End if */
+ break;
+
+ case WAITING:
+ case ERROR:
+ case FLUSHING:
+ Write(ttyo, "\004", 1);
+ Rest(1);
+ break;
+
+ case PRINTERERROR:
+ Rest(15);
+ break;
+
+ case DISCONNECT:
+ error(FATAL, "Disconnected - printer may be offline");
+ break;
+
+ case ENDOFJOB:
+ case UNKNOWN:
+ clearline();
+ break;
+
+ default:
+ Rest(1);
+ break;
+ } /* End switch */
+
+} /* End of start */
+
+/*****************************************************************************/
+
+split()
+
+{
+
+ int pid;
+ void interrupt();
+
+/*
+ *
+ * If splitme is TRUE we fork a process, make the parent handle reading, and let
+ * the child take care of writing. resetline() (file ifdef.c) contains all the
+ * system dependent code needed to reset the communications line for separate
+ * read and write processes. For now it's expected to return TRUE or FALSE and
+ * that value controls whether we try the fork. I've only tested the two process
+ * stuff for System V. Other versions of resetline() may just be dummy procedures
+ * that always return FALSE. If the fork() failed previous versions continued as
+ * a single process, although the implementation wasn't quite right, but I've now
+ * decided to quit. The main reason is a Datakit channel may be configured to
+ * flow control data in both directions, and if we run postio over that channel
+ * as a single process we likely will end up in deadlock.
+ *
+ */
+
+ if ( splitme == TRUE )
+ if ( resetline() == TRUE ) {
+ pid = getpid();
+ signal(joinsig, interrupt);
+ if ( (otherpid = fork()) == -1 )
+ error(FATAL, "can't fork");
+ else if ( otherpid == 0 ) {
+ whatami = WRITE;
+ nostatus = WRITEPROCESS;
+ otherpid = pid;
+ setupstdin(1);
+ } else whatami = READ;
+ } else if ( interactive == TRUE || tostdout == TRUE )
+ error(FATAL, "can't create two process - check resetline()");
+ else error(NON_FATAL, "running as a single process - check resetline()");
+
+ canread = (whatami & READ) ? TRUE : FALSE;
+ canwrite = (whatami & WRITE) ? TRUE : FALSE;
+
+} /* End of split */
+
+/*****************************************************************************/
+
+arguments()
+
+{
+
+ int fd_in; /* next input file */
+
+/*
+ *
+ * Makes sure all the non-option command line arguments are processed. If there
+ * aren't any arguments left when we get here we'll send stdin. Input files are
+ * only read and sent to the printer if canwrite is TRUE. Checking it here means
+ * we won't have to do it in send(). If interactive mode is TRUE we'll stay here
+ * forever sending stdin when we run out of files - exit with a break. Actually
+ * the loop is bogus and used at most once when we're in interactive mode because
+ * stdin is in a pseudo raw mode and the read() in readblock() should never see
+ * the end of file.
+ *
+ */
+
+ if ( canwrite == TRUE )
+ do /* loop is for interactive mode */
+ if ( argc < 1 )
+ send(fileno(stdin), "pipe.end");
+ else {
+ while ( argc > 0 ) {
+ if ( (fd_in = open(*argv, O_RDONLY)) == -1 )
+ error(FATAL, "can't open %s", *argv);
+ send(fd_in, *argv);
+ close(fd_in);
+ argc--;
+ argv++;
+ } /* End while */
+ } /* End else */
+ while ( interactive == TRUE );
+
+} /* End of arguments */
+
+/*****************************************************************************/
+
+send(fd_in, name)
+
+ int fd_in; /* next input file */
+ char *name; /* and it's pathname */
+
+{
+
+/*
+ *
+ * Sends file *name to the printer. There's nothing left here that depends on
+ * sending and receiving status reports, although it can be reassuring to know
+ * the printer is responding and processing our job. Only the writer gets here
+ * in the two process implementation, and in that case split() has reset nostatus
+ * to WRITEPROCESS and that's what getstatus() always returns. For now we accept
+ * the IDLE state and ENDOFJOB as legitimate and ignore the INITIALIZING state.
+ *
+ */
+
+ if ( interactive == FALSE )
+ logit("sending file %s\n", name);
+
+ currentstate = SEND;
+
+ if ( useslowsend == TRUE ) {
+ slowsend(fd_in);
+ return;
+ } /* End if */
+
+ while ( readblock(fd_in) )
+ switch ( getstatus(0) ) {
+ case IDLE:
+ case BUSY:
+ case WAITING:
+ case PRINTING:
+ case ENDOFJOB:
+ case PRINTERERROR:
+ case UNKNOWN:
+ case NOSTATUS:
+ case WRITEPROCESS:
+ case INTERACTIVE:
+ writeblock();
+ break;
+
+ case ERROR:
+ fprintf(stderr, "%s", mesg); /* for csw */
+ error(USER_FATAL, "PostScript Error");
+ break;
+
+ case FLUSHING:
+ error(USER_FATAL, "Flushing Job");
+ break;
+
+ case DISCONNECT:
+ error(FATAL, "Disconnected - printer may be offline");
+ break;
+ } /* End switch */
+
+} /* End of send */
+
+/*****************************************************************************/
+
+done()
+
+{
+
+ int sleeptime = 15; /* for 'out of paper' etc. */
+
+/*
+ *
+ * Tries to stay connected to the printer until we're reasonably sure the job is
+ * complete. It's the only way we can recover error messages or data generated by
+ * the PostScript program and returned over the communication line. Actually doing
+ * it correctly for all possible PostScript jobs is more difficult that it might
+ * seem. For example if we've sent several jobs, each with their own EOF mark, then
+ * waiting for ENDOFJOB won't guarantee all the jobs have completed. Even waiting
+ * for IDLE isn't good enough. Checking for the WAITING state after all the files
+ * have been sent and then sending an EOF may be the best approach, but even that
+ * won't work all the time - we could miss it or might not get there. Even sending
+ * our own special PostScript job after all the input files has it's own different
+ * set of problems, but probably could work (perhaps by printing a fake status
+ * message or just not timing out). Anyway it's probably not worth the trouble so
+ * for now we'll quit if writedone is TRUE and we get ENDOFJOB or IDLE.
+ *
+ * If we're running separate read and write processes the reader gets here after
+ * after split() while the writer goes to send() and only gets here after all the
+ * input files have been transmitted. When they're both here the writer sends the
+ * reader signal joinsig and that forces writedone to TRUE in the reader. At that
+ * point the reader can begin looking for an indication of the end of the job.
+ * The writer hangs around until the reader kills it (usually in cleanup()) sending
+ * occasional status requests.
+ *
+ */
+
+ if ( canwrite == TRUE )
+ logit("waiting for end of job\n");
+
+ currentstate = DONE;
+ writedone = (whatami == READWRITE) ? TRUE : FALSE;
+
+ while ( 1 ) {
+ switch ( getstatus(1) ) {
+
+ case WRITEPROCESS:
+ if ( writedone == FALSE ) {
+ sendsignal(joinsig);
+ Write(ttyo, "\004", 1);
+ writedone = TRUE;
+ sleeptime = 1;
+ } /* End if */
+ Rest(sleeptime++);
+ break;
+
+ case WAITING:
+ Write(ttyo, "\004", 1);
+ Rest(1);
+ sleeptime = 15;
+ break;
+
+ case IDLE:
+ case ENDOFJOB:
+ if ( writedone == TRUE ) {
+ logit("job complete\n");
+ return;
+ } /* End if */
+ break;
+
+ case BUSY:
+ case PRINTING:
+ case INTERACTIVE:
+ sleeptime = 15;
+ break;
+
+ case PRINTERERROR:
+ Rest(sleeptime++);
+ break;
+
+ case ERROR:
+ fprintf(stderr, "%s", mesg); /* for csw */
+ error(USER_FATAL, "PostScript Error");
+ return;
+
+ case FLUSHING:
+ error(USER_FATAL, "Flushing Job");
+ return;
+
+ case DISCONNECT:
+ error(FATAL, "Disconnected - printer may be offline");
+ return;
+
+ default:
+ Rest(1);
+ break;
+ } /* End switch */
+
+ if ( sleeptime > 60 )
+ sleeptime = 60;
+ } /* End while */
+
+} /* End of done */
+
+/*****************************************************************************/
+
+cleanup()
+
+{
+
+ int w;
+
+/*
+ *
+ * Only needed if we're running separate read and write processes. Makes sure the
+ * write process is killed after the read process has successfully finished with
+ * all the jobs. sendsignal() returns a -1 if there's nobody to signal so things
+ * work when we're running a single process.
+ *
+ */
+
+ while ( sendsignal(SIGKILL) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
+
+} /* End of cleanup */
+
+/*****************************************************************************/
+
+readblock(fd_in)
+
+ int fd_in; /* current input file */
+
+{
+
+ static long blocknum = 1;
+
+/*
+ *
+ * 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. The fake status message that's put out
+ * in quiet mode is only so you can look at the log file and know something's
+ * happening - take it out if you want.
+ *
+ */
+
+ if ( head >= tail ) { /* done with the last block */
+ if ( (tail = read(fd_in, block, blocksize)) == -1 )
+ error(FATAL, "error reading input file");
+ if ( quiet == TRUE && tail > 0 ) /* put out a fake message? */
+ logit("%%%%[ status: busy; block: %d ]%%%%\n", blocknum++);
+ head = 0;
+ } /* End if */
+
+ return(tail - head);
+
+} /* End of readblock */
+
+/*****************************************************************************/
+
+writeblock()
+
+{
+
+ 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 %s", line);
+ else if ( count == 0 )
+ error(FATAL, "printer appears to be offline");
+
+ head += count;
+ return(count);
+
+} /* End of writeblock */
+
+/*****************************************************************************/
+
+getstatus(t)
+
+ int t; /* sleep time after sending '\024' */
+
+{
+
+ int gotline = FALSE; /* value returned by readline() */
+ int state = nostatus; /* representation of the current state */
+ int mesgch; /* to restore mesg[] when tostdout == TRUE */
+
+ static int laststate = NOSTATUS; /* last state recognized */
+
+/*
+ *
+ * Looks for things coming back from the printer on the communications line, parses
+ * complete lines retrieved by readline(), and returns an integer representation
+ * of the current printer status to the caller. If nothing was available a status
+ * request (control T) is sent to the printer and nostatus is returned to the
+ * caller (provided quiet isn't TRUE). Interactive mode either never returns from
+ * readline() or returns FALSE.
+ *
+ */
+
+ if ( canread == TRUE && (gotline = readline()) == TRUE ) {
+ state = parsemesg();
+ if ( state != laststate || state == UNKNOWN || mesgptr != mesg || debug == ON )
+ logit("%s", mesg);
+
+ if ( tostdout == TRUE && currentstate != START ) {
+ mesgch = *mesgptr;
+ *mesgptr = '\0';
+ fprintf(stdout, "%s", mesg);
+ fflush(stdout);
+ *mesgptr = mesgch; /* for ERROR in send() and done() */
+ } /* End if */
+ return(laststate = state);
+ } /* End if */
+
+ if ( (quiet == FALSE || currentstate != SEND) &&
+ (tostdout == FALSE || currentstate == START) && interactive == FALSE ) {
+ if ( Write(ttyo, "\024", 1) != 1 )
+ error(FATAL, "printer appears to be offline");
+ if ( t > 0 ) Rest(t);
+ } /* End if */
+
+ return(nostatus);
+
+} /* End of getstatus */
+
+/*****************************************************************************/
+
+parsemesg()
+
+{
+
+ char *e; /* end of printer message in mesg[] */
+ char *key, *val; /* keyword/value strings in sbuf[] */
+ char *p; /* for converting to lower case etc. */
+ int i; /* where *key was found in status[] */
+
+/*
+ *
+ * Parsing the lines that readline() stores in mesg[] is messy, and what's done
+ * here isn't completely correct nor as fast as it could be. The general format
+ * of lines that come back from the printer (assuming no data loss) is:
+ *
+ * str%%[ key: val; key: val; key: val ]%%\n
+ *
+ * where str can be most anything not containing a newline and printer reports
+ * (eg. status or error messages) are bracketed by "%%[ " and " ]%%" strings and
+ * end with a newline. Usually we'll have the string or printer report but not
+ * both. For most jobs the leading string will be empty, but could be anything
+ * generated on a printer and returned over the communications line using the
+ * PostScript print operator. I'll assume PostScript jobs are well behaved and
+ * never bracket their messages with "%%[ " and " ]%%" strings that delimit status
+ * or error messages.
+ *
+ * Printer reports consist of one or more key/val pairs, and what we're interested
+ * in (status or error indications) may not be the first pair in the list. In
+ * addition we'll sometimes want the value associated with a keyword (eg. when
+ * key = status) and other times we'll want the keyword (eg. when key = Error or
+ * Flushing). The last pair isn't terminated by a semicolon and a value string
+ * often contains many space separated words and it can even include colons in
+ * meaningful places. I've also decided to continue converting things to lower
+ * case before doing the lookup in status[]. The isupper() test is for Berkeley
+ * systems.
+ *
+ */
+
+ if ( *(mesgptr = find("%%[ ", mesg)) != '\0' && *(e = find(" ]%%", mesgptr+4)) != '\0' ) {
+ strcpy(sbuf, mesgptr+4); /* don't change mesg[] */
+ sbuf[e-mesgptr-4] = '\0'; /* ignore the trailing " ]%%" */
+
+ for ( key = strtok(sbuf, " :"); key != NULL; key = strtok(NULL, " :") ) {
+ if ( (val = strtok(NULL, ";")) != NULL && strcmp(key, "status") == 0 )
+ key = val;
+
+ for ( ; *key == ' '; key++ ) ; /* skip any leading spaces */
+ for ( p = key; *p; p++ ) /* convert to lower case */
+ if ( *p == ':' ) {
+ *p = '\0';
+ break;
+ } else if ( isupper(*p) ) *p = tolower(*p);
+
+ for ( i = 0; status[i].state != NULL; i++ )
+ if ( strcmp(status[i].state, key) == 0 )
+ return(status[i].val);
+ } /* End for */
+ } else if ( strcmp(mesg, "CONVERSATION ENDED.\n") == 0 )
+ return(DISCONNECT);
+
+ return(mesgptr == '\0' ? nostatus : UNKNOWN);
+
+} /* End of parsemesg */
+
+/*****************************************************************************/
+
+char *find(str1, str2)
+
+ char *str1; /* look for this string */
+ char *str2; /* in this one */
+
+{
+
+ char *s1, *s2; /* can't change str1 or str2 too fast */
+
+/*
+ *
+ * Looks for *str1 in string *str2. Returns a pointer to the start of the substring
+ * if it's found or to the end of string str2 otherwise.
+ *
+ */
+
+ for ( ; *str2 != '\0'; str2++ ) {
+ for ( s1 = str1, s2 = str2; *s1 != '\0' && *s1 == *s2; s1++, s2++ ) ;
+ if ( *s1 == '\0' )
+ break;
+ } /* End for */
+
+ return(str2);
+
+} /* End of find */
+
+/*****************************************************************************/
+
+clearline()
+
+{
+
+/*
+ *
+ * Reads characters from the input line until nothing's left. Don't do anything if
+ * we're currently running separate read and write processes.
+ *
+ */
+
+ if ( whatami == READWRITE )
+ while ( readline() != FALSE ) ;
+
+} /* End of clearline */
+
+/*****************************************************************************/
+
+sendsignal(sig)
+
+ int sig; /* this goes to the other process */
+
+{
+
+/*
+ *
+ * Sends signal sig to the other process if we're running as separate read and
+ * write processes. Returns the result of the kill if there's someone else to
+ * signal or -1 if we're running alone.
+ *
+ */
+
+ if ( whatami != READWRITE && otherpid > 1 )
+ return(kill(otherpid, sig));
+
+ return(-1);
+
+} /* End of sendsignal */
+
+/*****************************************************************************/
+
+void interrupt(sig)
+
+ int sig; /* signal that we caught */
+
+{
+
+/*
+ *
+ * Caught a signal - all except joinsig cause the program to quit. joinsig is the
+ * signal sent by the writer to the reader after all the jobs have been transmitted.
+ * Used to tell the read process when it can start looking for the end of the job.
+ *
+ */
+
+ signal(sig, SIG_IGN);
+
+ if ( sig != joinsig ) {
+ x_stat |= FATAL;
+ if ( canread == TRUE )
+ if ( interactive == FALSE )
+ error(NON_FATAL, "signal %d abort", sig);
+ else error(NON_FATAL, "quitting");
+ quit(sig);
+ } /* End if */
+
+ writedone = TRUE;
+ signal(joinsig, interrupt);
+
+} /* End of interrupt */
+
+/*****************************************************************************/
+
+logit(mesg, a1, a2, a3)
+
+ char *mesg; /* control string */
+ unsigned a1, a2, a3; /* and possible arguments */
+
+{
+
+/*
+ *
+ * Simple routine that's used to write a message to the log file.
+ *
+ */
+
+ if ( mesg != NULL && fp_log != NULL ) {
+ fprintf(fp_log, mesg, a1, a2, a3);
+ fflush(fp_log);
+ } /* End if */
+
+} /* End of logit */
+
+/*****************************************************************************/
+
+error(kind, mesg, a1, a2, a3)
+
+ int kind; /* FATAL or NON_FATAL error */
+ char *mesg; /* error message control string */
+ unsigned a1, a2, a3; /* control string arguments */
+
+{
+
+ FILE *fp_err;
+
+/*
+ *
+ * Called when we've run into some kind of program error. First *mesg is printed
+ * using the control string arguments a?. 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.
+ *
+ */
+
+ fp_err = (fp_log != NULL) ? fp_log : stderr;
+
+ if ( mesg != NULL && *mesg != '\0' ) {
+ fprintf(fp_err, "%s: ", prog_name);
+ fprintf(fp_err, mesg, a1, a2, a3);
+ putc('\n', fp_err);
+ } /* End if */
+
+ x_stat |= kind;
+
+ if ( kind != NON_FATAL && ignore == OFF )
+ quit(SIGTERM);
+
+} /* End of error */
+
+/*****************************************************************************/
+
+quit(sig)
+
+ int sig;
+
+{
+
+ int w;
+
+/*
+ *
+ * Makes sure everything is properly cleaned up if there's a signal or FATAL error
+ * that should cause the program to terminate. The sleep by the write process is
+ * to help give the reset sequence a chance to reach the printer before we break
+ * the connection - primarily for printers connected to Datakit. There's a very
+ * slight chance the reset sequence that's sent to the printer could get us stuck
+ * here. Simplest solution is don't bother to send it - everything works without it.
+ * Flushing ttyo would be better, but means yet another system dependent procedure
+ * in ifdef.c! I'll leave things be for now.
+ *
+ * Obscure problem on PS-810 turbos says wait a bit after sending an interrupt.
+ * Seem to remember the printer getting into a bad state immediately after the
+ * top was opened when the toner light was on. A sleep after sending the ctrl-C
+ * seemed to fix things.
+ *
+ */
+
+ signal(sig, SIG_IGN);
+ ignore = ON;
+
+ while ( sendsignal(sig) != -1 && (w = wait((int *)0)) != otherpid && w != -1 ) ;
+
+ setupstdin(2);
+
+ if ( currentstate != NOTCONNECTED ) {
+ if ( sendctrlC == TRUE ) {
+ Write(ttyo, "\003", 1);
+ Rest(1); /* PS-810 turbo problem?? */
+ } /* End if */
+ Write(ttyo, "\004", 1);
+ } /* End if */
+
+ alarm(0); /* prevents sleep() loop on V9 systems */
+ Rest(2);
+
+ exit(x_stat);
+
+} /* End of quit */
+
+/*****************************************************************************/
+
+Rest(t)
+
+ int t;
+
+{
+
+/*
+ *
+ * Used to replace sleep() calls. Only needed if we're running the program as
+ * a read and write process and don't want to have the read process sleep. Most
+ * sleeps are in the code because of the non-blocking read used by the single
+ * process implementation. Probably should be a macro.
+ *
+ */
+
+ if ( t > 0 && canwrite == TRUE )
+ sleep(t);
+
+} /* End of Rest */
+
+/*****************************************************************************/
+
+Read(fd, buf, n)
+
+ int fd;
+ char *buf;
+ int n;
+
+{
+
+ int count;
+
+/*
+ *
+ * Used to replace some of the read() calls. Only needed if we're running separate
+ * read and write processes. Should only be used to replace read calls on ttyi.
+ * Always returns 0 to the caller if the process doesn't have its READ flag set.
+ * Probably should be a macro.
+ *
+ */
+
+ if ( canread == TRUE ) {
+ if ( (count = read(fd, buf, n)) == -1 && errno == EINTR )
+ count = 0;
+ } else count = 0;
+
+ return(count);
+
+} /* End of Read */
+
+/*****************************************************************************/
+
+Write(fd, buf, n)
+
+ int fd;
+ char *buf;
+ int n;
+
+{
+
+ int count;
+
+/*
+ *
+ * Used to replace some of the write() calls. Again only needed if we're running
+ * separate read and write processes. Should only be used to replace write calls
+ * on ttyo. Always returns n to the caller if the process doesn't have its WRITE
+ * flag set. Should also probably be a macro.
+ *
+ */
+
+ if ( canwrite == TRUE ) {
+ if ( (count = write(fd, buf, n)) == -1 && errno == EINTR )
+ count = n;
+ } else count = n;
+
+ return(count);
+
+} /* End of Write */
+
+/*****************************************************************************/
+
diff --git a/sys/src/cmd/postscript/postio/postio.h b/sys/src/cmd/postscript/postio/postio.h
new file mode 100755
index 000000000..c0ac6ad18
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/postio.h
@@ -0,0 +1,209 @@
+/*
+ *
+ * POSTBEGIN, if it's not NULL, is some PostScript code that's sent to the printer
+ * before any of the input files. It's not terribly important since the same thing
+ * can be accomplished in other ways, but this approach is convenient. POSTBEGIN
+ * is initialized so as to disable job timeouts. The string can also be set on the
+ * command line using the -P option.
+ *
+ */
+
+#define POSTBEGIN "statusdict /waittimeout 0 put\n"
+
+/*
+ *
+ * The following help determine where postio is when it's running - either in the
+ * START, SEND, or DONE states. Primarily controls what's done in getstatus().
+ * RADIAN occasionally had problems with two way conversations. Anyway this stuff
+ * can be used to prevent status queries while we're transmitting a job. Enabled
+ * by the -q option.
+ *
+ */
+
+#define NOTCONNECTED 0
+#define START 1
+#define SEND 2
+#define DONE 3
+
+/*
+ *
+ * Previous versions of postio only ran as a single process. That was (and still
+ * is) convenient, but meant we could only flow control one direction. Data coming
+ * back from the printer occasionally got lost, but that didn't often hurt (except
+ * for lost error messages). Anyway I've added code that lets you split the program
+ * into separate read and write processes, thereby helping to prevent data loss in
+ * both directions. It should be particularly useful when you're sending a job that
+ * you expect will be returning useful data over the communications line.
+ *
+ * The next three definitions control what's done with data on communications line.
+ * The READ flag means the line can be read, while the WRITE flag means it can be
+ * written. When we're running as a single process both flags are set. I tried to
+ * overlay the separate read/write process code on what was there and working for
+ * one process. The implementation isn't as good as it could be, but should be
+ * safe. The single process version still works, and remains the default.
+ *
+ */
+
+#define READ 1
+#define WRITE 2
+#define READWRITE 3
+
+/*
+ *
+ * Messages generated on the printer and returned over the communications line
+ * 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 ]%%
+ *
+ * %%[ PrinterError: out of paper; source: serial 25 ]%%
+ * %%[ PrinterError: no paper tray; source: serial 25 ]%%
+ *
+ * %%[ Error: undefined; OffendingCommand: xxx ]%%
+ * %%[ Flushing: rest of job (to end-of-file) will be ignored ]%%
+ *
+ * although the list isn't meant to be complete.
+ *
+ * The following constants are used to classify the recognized printer states.
+ * readline() reads complete lines from ttyi and stores them in array mesg[].
+ * getstatus() looks for the "%%[ " and " ]%%" delimiters that bracket printer
+ * messages and if found it tries to parse the enclosed message. After the lookup
+ * one of the following numbers is returned as an indication of the existence or
+ * content of the printer message. The return value is used in start(), send(),
+ * and done() to figure out what's happening and what can be done next.
+ *
+ */
+
+#define BUSY 0 /* processing data already sent */
+#define WAITING 1 /* printer wants more data */
+#define PRINTING 2 /* printing a page */
+#define IDLE 3 /* ready to start the next job */
+#define ENDOFJOB 4 /* readline() builds this up on EOF */
+#define PRINTERERROR 5 /* PrinterError - eg. out of paper */
+#define ERROR 6 /* some kind of PostScript error */
+#define FLUSHING 7 /* throwing out the rest of the job */
+#define INITIALIZING 8 /* printer is booting */
+#define DISCONNECT 9 /* from Datakit! */
+#define UNKNOWN 10 /* in case we missed anything */
+#define NOSTATUS 11 /* no response from the printer */
+
+#define WRITEPROCESS 12 /* dummy states for write process */
+#define INTERACTIVE 13 /* and interactive mode */
+
+/*
+ *
+ * An array of type Status is used, in getstatus(), to figure out the printer's
+ * current state. Just helps convert strings representing the current state into
+ * integer codes that other routines use.
+ *
+ */
+
+typedef struct {
+ char *state; /* printer's current status */
+ int val; /* value returned by getstatus() */
+} Status;
+
+/*
+ *
+ * STATUS is used to initialize an array of type Status that translates the ASCII
+ * strings returned by the printer into appropriate codes that can be used later
+ * on in the program. getstatus() converts characters to lower case, so if you
+ * add any entries make them lower case and put them in before the UNKNOWN entry.
+ * The lookup terminates when we get a match or when an entry with a NULL state
+ * is found.
+ *
+ */
+
+#define STATUS \
+ \
+ { \
+ "busy", BUSY, \
+ "waiting", WAITING, \
+ "printing", PRINTING, \
+ "idle", IDLE, \
+ "endofjob", ENDOFJOB, \
+ "printererror", PRINTERERROR, \
+ "error", ERROR, \
+ "flushing", FLUSHING, \
+ "initializing", INITIALIZING, \
+ NULL, UNKNOWN \
+ }
+
+/*
+ *
+ * The baud rate can be set on the command line using the -b option. If you omit
+ * it BAUDRATE will be used.
+ *
+ */
+
+#define BAUDRATE B9600
+
+/*
+ *
+ * An array of type Baud is used, in routine getbaud(), to translate ASCII strings
+ * into termio values that represent the requested baud rate.
+ *
+ */
+
+typedef struct {
+ char *rate; /* string identifying the baud rate */
+ short val; /* and its termio.h value */
+} Baud;
+
+/*
+ *
+ * BAUDTABLE initializes the array that's used to translate baud rate requests
+ * into termio values. It needs to end with an entry that has NULL assigned to
+ * the rate field.
+ *
+ */
+
+#define BAUDTABLE \
+ \
+ { \
+ "9600", B9600, \
+ "B9600", B9600, \
+ "19200", EXTA, \
+ "19.2", EXTA, \
+ "B19200", EXTA, \
+ "EXTA", EXTA, \
+ "1200", B1200, \
+ "B1200", B1200, \
+ "2400", B2400, \
+ "B2400", B2400, \
+ "B4800", B4800, \
+ "4800", B4800, \
+ "38400", EXTB, \
+ "38.4", EXTB, \
+ "B38400", EXTB, \
+ "EXTB", EXTB, \
+ NULL, B9600 \
+ }
+
+/*
+ *
+ * A few miscellaneous definitions. BLOCKSIZE is the default size of the buffer
+ * used for reading the input files (changed with the -B option). MESGSIZE is the
+ * size of the character array used to store printer status lines - don't make it
+ * too small!
+ *
+ */
+
+#define BLOCKSIZE 2048
+#define MESGSIZE 512
+
+/*
+ *
+ * Some of the non-integer valued functions used in postio.c.
+ *
+ */
+
+char *find();
+
+char *malloc();
+char *strtok();
diff --git a/sys/src/cmd/postscript/postio/postio.mk b/sys/src/cmd/postscript/postio/postio.mk
new file mode 100755
index 000000000..6e17273fd
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/postio.mk
@@ -0,0 +1,109 @@
+MAKE=/bin/make
+MAKEFILE=postio.mk
+
+SYSTEM=V9
+VERSION=3.3.2
+
+GROUP=bin
+OWNER=bin
+
+MAN1DIR=/tmp
+POSTBIN=/usr/bin/postscript
+
+COMMONDIR=../common
+
+CFLGS=-O
+LDFLGS=-s
+
+CFLAGS=$(CFLGS) -I$(COMMONDIR)
+LDFLAGS=$(LDFLGS)
+
+DKLIB=-ldk
+DKHOST=FALSE
+DKSTREAMS=FALSE
+
+#
+# Need dk.h and libdk.a for Datakit support on System V. We recommend you put
+# them in standard places. If it's not possible define DKHOSTDIR (below) and
+# try uncommenting the following lines:
+#
+# DKHOSTDIR=/usr
+# CFLAGS=$(CFLGS) -D$(SYSTEM) -I$(COMMONDIR) -I$(DKHOSTDIR)/include
+# EXTRA=-Wl,-L$(DKHOSTDIR)/lib
+#
+
+HFILES=postio.h\
+ ifdef.h\
+ $(COMMONDIR)/gen.h
+
+OFILES=postio.o\
+ ifdef.o\
+ slowsend.o
+
+all : postio
+
+install : all
+ @if [ ! -d "$(POSTBIN)" ]; then \
+ mkdir $(POSTBIN); \
+ chmod 755 $(POSTBIN); \
+ chgrp $(GROUP) $(POSTBIN); \
+ chown $(OWNER) $(POSTBIN); \
+ fi
+ cp postio $(POSTBIN)/postio
+ @chmod 755 $(POSTBIN)/postio
+ @chgrp $(GROUP) $(POSTBIN)/postio
+ @chown $(OWNER) $(POSTBIN)/postio
+ cp postio.1 $(MAN1DIR)/postio.1
+ @chmod 644 $(MAN1DIR)/postio.1
+ @chgrp $(GROUP) $(MAN1DIR)/postio.1
+ @chown $(OWNER) $(MAN1DIR)/postio.1
+
+clean :
+ rm -f *.o
+
+clobber : clean
+ rm -f postio
+
+postio ::
+ @CFLAGS="$(CFLAGS)"; export CFLAGS; \
+ DKLIB=" "; export DKLIB; \
+ if [ "$(SYSTEM)" != V9 ]; \
+ then \
+ if [ "$(DKHOST)" = TRUE ]; then \
+ if [ "$(DKSTREAMS)" != FALSE ]; then \
+ if [ "$(DKSTREAMS)" = TRUE ]; \
+ then CFLAGS="$$CFLAGS -DDKSTREAMS=\\\"dknetty\\\""; \
+ else CFLAGS="$$CFLAGS -DDKSTREAMS=\\\"$(DKSTREAMS)\\\""; \
+ fi; \
+ fi; \
+ CFLAGS="$$CFLAGS -DDKHOST"; export CFLAGS; \
+ DKLIB=-ldk; export DKLIB; \
+ SYSTEM=SYSV; export SYSTEM; \
+ fi; \
+ else DKLIB=-lipc; export DKLIB; \
+ fi; \
+ CFLAGS="$$CFLAGS -D$$SYSTEM"; export CFLAGS; \
+ $(MAKE) -e -f $@.mk compile
+
+compile : $(OFILES)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o postio $(OFILES) $(EXTRA) $(DKLIB)
+
+postio.o : $(HFILES)
+slowsend.o : postio.h $(COMMONDIR)/gen.h
+ifdef.o : ifdef.h $(COMMONDIR)/gen.h
+
+changes :
+ @trap "" 1 2 3 15; \
+ sed \
+ -e "s'^SYSTEM=.*'SYSTEM=$(SYSTEM)'" \
+ -e "s'^VERSION=.*'VERSION=$(VERSION)'" \
+ -e "s'^GROUP=.*'GROUP=$(GROUP)'" \
+ -e "s'^OWNER=.*'OWNER=$(OWNER)'" \
+ -e "s'^DKLIB=.*'DKLIB=$(DKLIB)'" \
+ -e "s'^DKHOST=.*'DKHOST=$(DKHOST)'" \
+ -e "s'^DKSTREAMS=.*'DKSTREAMS=$(DKSTREAMS)'" \
+ -e "s'^MAN1DIR=.*'MAN1DIR=$(MAN1DIR)'" \
+ -e "s'^POSTBIN=.*'POSTBIN=$(POSTBIN)'" \
+ $(MAKEFILE) >XXX.mk; \
+ mv XXX.mk $(MAKEFILE)
+
diff --git a/sys/src/cmd/postscript/postio/postio.mk.old b/sys/src/cmd/postscript/postio/postio.mk.old
new file mode 100755
index 000000000..e5469869a
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/postio.mk.old
@@ -0,0 +1,84 @@
+MAKE=/bin/make
+MAKEFILE=postio.mk
+
+SYSTEM=V9
+VERSION=3.3.1
+
+GROUP=bin
+OWNER=bin
+
+MAN1DIR=/tmp
+POSTBIN=/usr/bin/postscript
+
+COMMONDIR=../common
+
+DKLIB=-lipc
+CFLGS=-O
+LDFLGS=-s
+
+CFLAGS=$(CFLGS) -D$(SYSTEM) -I$(COMMONDIR)
+LDFLAGS=$(LDFLGS)
+
+#
+# Need dk.h and libdk.a for Datakit support on System V. We recommend you put
+# them in standard places. If it's not possible define DKHOSTDIR (below) and
+# try uncommenting the following lines:
+#
+# DKHOSTDIR=/usr
+# CFLAGS=$(CFLGS) -D$(SYSTEM) -I$(COMMONDIR) -I$(DKHOSTDIR)/include
+# EXTRA=-Wl,-L$(DKHOSTDIR)/lib
+#
+
+HFILES=postio.h\
+ ifdef.h\
+ $(COMMONDIR)/gen.h
+
+OFILES=postio.o\
+ ifdef.o\
+ slowsend.o
+
+all : postio
+
+install : all
+ @if [ ! -d "$(POSTBIN)" ]; then \
+ mkdir $(POSTBIN); \
+ chmod 755 $(POSTBIN); \
+ chgrp $(GROUP) $(POSTBIN); \
+ chown $(OWNER) $(POSTBIN); \
+ fi
+ cp postio $(POSTBIN)/postio
+ @chmod 755 $(POSTBIN)/postio
+ @chgrp $(GROUP) $(POSTBIN)/postio
+ @chown $(OWNER) $(POSTBIN)/postio
+ cp postio.1 $(MAN1DIR)/postio.1
+ @chmod 644 $(MAN1DIR)/postio.1
+ @chgrp $(GROUP) $(MAN1DIR)/postio.1
+ @chown $(OWNER) $(MAN1DIR)/postio.1
+
+clean :
+ rm -f *.o
+
+clobber : clean
+ rm -f postio
+
+postio : $(OFILES)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o postio $(OFILES) $(EXTRA) $(DKLIB)
+
+postio.o : $(HFILES)
+slowsend.o : postio.h $(COMMONDIR)/gen.h
+ifdef.o : ifdef.h $(COMMONDIR)/gen.h
+
+changes :
+ @trap "" 1 2 3 15; \
+ sed \
+ -e "s'^SYSTEM=.*'SYSTEM=$(SYSTEM)'" \
+ -e "s'^VERSION=.*'VERSION=$(VERSION)'" \
+ -e "s'^GROUP=.*'GROUP=$(GROUP)'" \
+ -e "s'^OWNER=.*'OWNER=$(OWNER)'" \
+ -e "s'^CFLGS=.*'CFLGS=$(CFLGS)'" \
+ -e "s'^DKLIB=.*'DKLIB=$(DKLIB)'" \
+ -e "s'^MAN1DIR=.*'MAN1DIR=$(MAN1DIR)'" \
+ -e "s'^POSTBIN=.*'POSTBIN=$(POSTBIN)'" \
+ $(MAKEFILE) >XXX.mk; \
+ mv XXX.mk $(MAKEFILE)
+
diff --git a/sys/src/cmd/postscript/postio/slowsend.c b/sys/src/cmd/postscript/postio/slowsend.c
new file mode 100755
index 000000000..dbe8ca874
--- /dev/null
+++ b/sys/src/cmd/postscript/postio/slowsend.c
@@ -0,0 +1,121 @@
+/*
+ *
+ * Stuff that slows the transmission of jobs to PostScript printers. ONLY use it
+ * if you appear to be having trouble with flow control. The idea is simple - only
+ * send a significant amount of data when we're certain the printer is in the
+ * WAITING state. Depends on receiving status messages and only works when the
+ * program is run as a single process. What's done should stop printer generated
+ * XOFFs - provided our input buffer (ie. blocksize) is sufficiently small. Was
+ * originally included in the postio.tmp directory, but can now be requested with
+ * the -S option. Considered eliminating this code, but some printers still depend
+ * on it. In particular Datakit connections made using Datakit PVCs and DACUs seem
+ * to have the most problems. Much of the new stuff that was added can't work when
+ * you use this code and will be automatically disabled.
+ *
+ */
+
+#include <stdio.h>
+
+#include "gen.h"
+#include "postio.h"
+
+extern char *block;
+extern int blocksize;
+extern int head;
+extern int tail;
+extern char *line;
+extern char mesg[];
+extern int ttyo;
+
+/*****************************************************************************/
+
+slowsend(fd_in)
+
+ int fd_in; /* next input file */
+
+{
+
+/*
+ *
+ * A slow version of send() that's very careful about when data is sent to the
+ * printer. Should help prevent overflowing the printer's input buffer, provided
+ * blocksize is sufficiently small (1024 should be safe). It's a totally kludged
+ * up routine that should ONLY be used if you have constant transmission problems.
+ * There's really no way it will be able to drive a printer much faster that about
+ * six pages a minute, even for the simplest jobs. Get it by using the -S option.
+ *
+ */
+
+ while ( readblock(fd_in) )
+ switch ( getstatus(0) ) {
+ case WAITING:
+ writeblock(blocksize);
+ break;
+
+ case BUSY:
+ case IDLE:
+ case PRINTING:
+ writeblock(30);
+ break;
+
+ case NOSTATUS:
+ case UNKNOWN:
+ break;
+
+ case PRINTERERROR:
+ sleep(30);
+ break;
+
+ case ERROR:
+ fprintf(stderr, "%s", mesg); /* for csw */
+ error(FATAL, "PostScript Error");
+ break;
+
+ case FLUSHING:
+ error(FATAL, "Flushing Job");
+ break;
+
+ case DISCONNECT:
+ error(FATAL, "Disconnected - printer may be offline");
+ break;
+
+ default:
+ sleep(2);
+ break;
+ } /* End switch */
+
+} /* End of send */
+
+/*****************************************************************************/
+
+static writeblock(num)
+
+ int num; /* most bytes we'll write */
+
+{
+
+ 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 ( num > tail - head )
+ num = tail - head;
+
+ if ( (count = write(ttyo, &block[head], num)) == -1 )
+ error(FATAL, "error writing to %s", line);
+ else if ( count == 0 )
+ error(FATAL, "printer appears to be offline");
+
+ head += count;
+ return(count);
+
+} /* End of writeblock */
+
+/*****************************************************************************/
+