diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/postscript/postio |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/postscript/postio')
-rwxr-xr-x | sys/src/cmd/postscript/postio/README | 20 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/ifdef.c | 867 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/ifdef.h | 66 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/postio.1 | 308 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/postio.c | 1212 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/postio.h | 209 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/postio.mk | 109 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/postio.mk.old | 84 | ||||
-rwxr-xr-x | sys/src/cmd/postscript/postio/slowsend.c | 121 |
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 */ + +/*****************************************************************************/ + |