1212 lines · 35.7 KB · C
RawDownload
/* * * 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();

Lines 1–500 of 1212.