/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ifdef.h" /* conditional compilation stuff */ #include "gen.h" /* general purpose definitions */ #include "postio.h" /* some special definitions */ static char **argv; /* global so everyone can use them */ static int argc; static char *prog_name = ""; /* really just for error messages */ static int x_stat = 0; /* program exit status */ static int debug = OFF; /* debug flag */ static int ignore = OFF; /* what's done for FATAL errors */ static Baud baudtable[] = BAUDTABLE; /* converts strings to termio values */ static int quiet = FALSE; /* no status queries in send if TRUE */ char *postbegin = POSTBEGIN; /* preceeds all the input files */ static int useslowsend = FALSE; /* not recommended! */ static int splitme = FALSE; /* into READ & WRITE procs if TRUE */ static int whatami = READWRITE; /* a READ or WRITE process - or both */ static int otherpid = -1; /* who gets signals if greater than 1 */ static int joinsig = SIGTRAP; /* reader gets when writing is done */ static int writedone = FALSE; /* and then sets this to TRUE */ static char sbuf[MESGSIZE]; /* for parsing the message */ static char *mesgptr = NULL; /* printer msg starts here in mesg[] */ static Status status[] = STATUS; /* for converting status strings */ static int nostatus = NOSTATUS; /* default getstatus() return value */ static int tostdout = FALSE; /* non-status stuff goes to stdout? */ static int currentstate = NOTCONNECTED; /* START, SEND, or DONE */ char *line = NULL; /* printer is on this tty line */ short baudrate = BAUDRATE; /* and running at this baud rate */ int stopbits = 1; /* number of stop bits */ int interactive = FALSE; /* interactive mode */ 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 canread = TRUE; /* allow reads */ int canwrite = TRUE; /* and writes if TRUE */ char mesg[MESGSIZE]; /* exactly what came back on ttyi */ char *endmesg = NULL; /* end for readline() in mesg[] */ int ttyi = 0; /* input */ int ttyo = 2; /* and output file descriptors */ FILE *fp_log = stderr; /* log file for stuff from printer */ static void init_signals(void); static void interrupt(int); static void options(void); static void initialize(void); static void initialize_parallel(void); static void start(void); static void split(void); static void arguments(void); static void send(int, char *); static void done(void); static void cleanup(void); static void clearline(void); void logit(char *, ...); static void quit(int sig); static void Rest(int t); static int parsemesg(void); static int sendsignal(int); static int writeblock(void); static int Write(int, char *, int); static short getbaud(char *); static char *find(char *, char *); void error(int, char *, ...); int getstatus(int); int readblock(int); /* from parallel.c for parallel interfaces */ extern int is_a_parallel_bpp(int); extern int bpp_state(int); extern int is_a_prnio(int); extern int prnio_state(int); extern int parallel_comm(int, int()); /* arg is bpp_state */ /* from ifdef.c for serial interfaces */ extern void setupline(void); extern void setupstdin(int); extern void slowsend(int); extern int resetline(void); extern int readline(void); /* * 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. */ int nop(int fd) { return(0); } int main(int agc, char *agv[]) { argc = agc; argv = agv; prog_name = argv[0]; /* really just for error messages */ /* is this a serial or parallel port? */ init_signals(); /* sets up interrupt handling */ options(); /* get command line options */ setbuf(stderr, NULL); /* unbuffer io for stderr */ if (line) { close(1); open(line, O_RDWR); } if (is_a_prnio(1)) { initialize_parallel(); x_stat = parallel_comm(1, prnio_state); } else if (is_a_parallel_bpp(1) || (get_ecpp_status(1) == ECPP_CENTRONICS)) { initialize_parallel(); x_stat = parallel_comm(1, bpp_state); } else if (isatty(1)) { 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 */ } else { initialize_parallel(); x_stat = parallel_comm(1, nop); } return (x_stat); /* everything probably went OK */ } /* * 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. */ static void init_signals(void) { 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); } signal(SIGTERM, interrupt); } /* * 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! */ static void options(void) { int ch; /* return value from getopt() */ char *optnames = "b:il:qs:tB:L:P:R:SDI"; extern char *optarg; /* used by getopt() */ extern int optind; while ((ch = getopt(argc, argv, optnames)) != EOF) { switch (ch) { case 'b': /* baud rate string */ baudrate = getbaud(optarg); 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 '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 vers. 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; } /* * 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. */ static short getbaud(char *rate) /* string representing the baud rate */ { int i; /* for looking through baudtable[] */ 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); /*NOTREACHED*/ return (0); } /* * 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. */ static void initialize(void) { whatami = READWRITE; /* always run start() as one process */ canread = canwrite = TRUE; if (line == NULL) /* kludge for lp - they use -t option */ tostdout = FALSE; if (tostdout == TRUE) /* force separate read/write procs */ splitme = TRUE; if (interactive == TRUE) { /* interactive mode settings win */ quiet = FALSE; tostdout = FALSE; splitme = TRUE; blocksize = 1; postbegin = NULL; useslowsend = FALSE; nostatus = INTERACTIVE; setbuf(stdout, NULL); } 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; } 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 pos. in mesg */ setupline(); /* configure the communications line */ setupstdin(0); /* save current stdin term settings */ } static void initialize_parallel(void) { if ((block = malloc(blocksize)) == NULL) error(FATAL, "no memory"); } /* * 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. */ static void start(void) { int longwait = 0; logit("printer startup\n"); currentstate = START; clearline(); for (;;) switch (getstatus(1)) { case IDLE: case INTERACTIVE: if (postbegin != NULL && *postbegin != '\0') Write(ttyo, postbegin, strlen(postbegin)); clearline(); return; case BUSY: Write(ttyo, "\003", 1); Rest(1); break; /* 03/24/95 - bob golden * The HP LJ3 starts in waiting mode and needs the EOF to move * from waiting to idle. To see what would happen, code was added * to send the INTR on waiting and later changed to INTR/EOF. * The INTR by itself had no effect. The INTR/EOF put the * the printer in a busy status loop from which the only * recovery was to reset the printer. Until further testing * testing is done, do not send an INTR to a HPLJ3 in waiting * state. WAITING moved to a separate case to eliminate the * INTR write. */ case WAITING: Write(ttyo, "\004", 1); Rest(1); break; /* 03/24/95 - bob golden * The HP LJ3 seems to process INTR at later times. All the * longwaits are increaased to reduce the number of INTRs sent. */ case ERROR: case FLUSHING: Write(ttyo, "\004", 1); if (longwait++ == 5) { Write(ttyo, "\003", 1); Rest(5); longwait = 0; } Rest(1); break; case PRINTERERROR: Rest(15); break; case DISCONNECT: error(FATAL, "Disconnected - printer may be offline"); break; /* 03/24/95 - bob golden * The ENDJOB case has been removed. The HP LJ3 echoes all EOFs * sent so the ENDJOB has no real meaning. */ case UNKNOWN: clearline(); break; default: Rest(1); break; } /* End switch */ } /* End of start */ /* * * 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. */ static void split(void) { int pid; 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; } /* * 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. */ static void arguments(void) { int fd_in; /* next input 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++; } } while (interactive == TRUE); } /* * 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. * * fd_in next input file * name it's pathname */ static void send(int fd_in, char *name) { if (interactive == FALSE) logit("sending file %s\n", name); currentstate = SEND; if (useslowsend == TRUE) { slowsend(fd_in); return; } 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; } } /* * 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. */ static void done(void) { int sleeptime = 15; /* for 'out of paper' etc. */ int longwait = 0; if (canwrite == TRUE) logit("waiting for end of job\n"); currentstate = DONE; writedone = (whatami == READWRITE) ? TRUE : FALSE; for (;;) { switch (getstatus(1)) { case WRITEPROCESS: if (writedone == FALSE) { sendsignal(joinsig); Write(ttyo, "\004", 1); writedone = TRUE; sleeptime = 1; } Rest(sleeptime++); break; /* 03/24/95 - bob golden * For the HP LJ3 INTR sent while in the waiting state have * either had no effect or put the printer into a unrecoverable * loop. Further testing may reveal this to not be the case * but for now, remove the send INTR. */ case WAITING: Write(ttyo, "\004", 1); Rest(1); sleeptime = 15; break; /* 03/24/95 - bob golden * ENDOFJOB case removed here. The HP LJ 3 echoes all EOFs sent so * the ENDOFJOB case is meaningless. */ case IDLE: if (writedone == TRUE) { logit("job complete\n"); return; } break; /* 03/24/95 - bob golden * During print data transmission, the HP LJ3 stays in * status busy. So give it a rest. * */ case BUSY: case PRINTING: Rest(1); sleeptime = 15; break; case INTERACTIVE: Write(ttyo, "\004", 1); sleeptime = 15; break; case PRINTERERROR: Rest(sleeptime++); break; case ERROR: Write(ttyo, "\004", 1); fprintf(stderr, "%s", mesg); /* for csw */ error(USER_FATAL, "PostScript Error"); return; case FLUSHING: Write(ttyo, "\004", 1); error(USER_FATAL, "Flushing Job"); return; case DISCONNECT: error(FATAL, "Disconnected - printer may be offline"); return; /* 03/24/95 - bob golden * These cases are ignored without a EOF being sent */ case ENDOFJOB: case NOSTATUS: Rest(1); break; default: Write(ttyo, "\004", 1); Rest(1); break; } if (sleeptime > 60) sleeptime = 60; } } /* * 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. */ static void cleanup(void) { int w; while (sendsignal(SIGKILL) != -1 && (w = wait((int *)0)) != otherpid && w != -1); if ( currentstate != NOTCONNECTED ) Write(ttyo, "\004", 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. */ int readblock(int fd_in) { static long blocknum = 1; 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; } return (tail - head); } /* * 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. */ static int writeblock(void) { int count; /* bytes successfully written */ 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); } /* * 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. */ int getstatus(int t) /* sleep time after sending '\024' */ { int state = nostatus; /* the current state */ static int laststate = NOSTATUS; /* last state recognized */ if (canread == TRUE && readline() == TRUE) { state = parsemesg(); if (state != laststate || mesgptr != mesg || debug == ON) logit("%s", mesg); if (tostdout == TRUE && currentstate != START) { *mesgptr = '\0'; fprintf(stdout, "%s", mesg); } return (laststate = state); } if ((quiet == FALSE || currentstate != SEND) && interactive == FALSE) { if (Write(ttyo, "\024", 1) != 1) error(FATAL, "printer appears to be offline"); if (t > 0) Rest(t); } return (nostatus); } /* * * 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. */ static int parsemesg(void) { 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[] */ 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 leading space */ for (p = key; *p; p++) /* conv to lower case */ if (*p == ':' || *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); } } else if (strcmp(mesg, "CONVERSATION ENDED.\n") == 0) return (DISCONNECT); return (nostatus); } /* * 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. */ static char * find(char *str1, char *str2) { char *s1, *s2; /* can't change str1 or str2 too fast */ for (; *str2 != '\0'; str2++) { for (s1 = str1, s2 = str2; *s1 != '\0' && *s1 == *s2; s1++, s2++); if (*s1 == '\0') break; } return (str2); } /* * Reads characters from the input line until nothing's left. Don't do * anything if we're currently running separate read and write processes. */ static void clearline(void) { if (whatami == READWRITE) while (readline() != FALSE); } /* * 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. * */ static int sendsignal(int sig) { if (whatami != READWRITE && otherpid > 1) return (kill(otherpid, sig)); return (-1); } /* * 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. */ static void interrupt(int sig) { 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); } writedone = TRUE; signal(joinsig, interrupt); } /* * Simple routine that's used to write a message to the log file. */ void logit(char *mesg, ...) { va_list ap; va_start(ap, mesg); vfprintf(fp_log, mesg, ap); va_end(ap); fflush(fp_log); } /* * Called when we've run into some kind of program error. First *mesg is * printed. 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. */ void error(int kind, char *mesg, ...) { va_list ap; if (mesg != NULL && *mesg != '\0') { fprintf(fp_log, "%s: ", prog_name); va_start(ap, mesg); vfprintf(fp_log, mesg, ap); va_end(ap); putc('\n', fp_log); } x_stat |= kind; if (kind != NON_FATAL && ignore == OFF) quit(SIGTERM); } /* * * 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. */ static void quit(int sig) { int w; signal(sig, SIG_IGN); ignore = ON; while (sendsignal(sig) != -1 && (w = wait((int *)0)) != otherpid && w != -1); setupstdin(2); if (currentstate != NOTCONNECTED) Write(ttyo, "\003\004", 2); alarm(0); /* prevents sleep() loop on V9 systems */ Rest(2); exit(x_stat); } /* * 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. */ static void Rest(int t) { if (t > 0 && canwrite == TRUE) sleep(t); } /* * 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. */ #ifdef NEVER static int Read(int fd, char *buf, int n) { int count; if (canread == TRUE) { if ((count = read(fd, buf, n)) == -1 && errno == EINTR) count = 0; } else count = 0; return (count); } #endif /* NEVER */ /* * * 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. * */ static int Write(int fd, char *buf, int n) { int count; if (canwrite == TRUE) { if ((count = write(fd, buf, n)) == -1 && errno == EINTR) count = n; } else count = n; return (count); }