xref: /titanic_41/usr/src/cmd/lp/filter/postscript/postio/postio.c (revision f928ce67ef743c33ea27c573c9c7e2d4a4833cbd)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 /*
33  *
34  * postio - RS-232 serial interface for PostScript printers
35  *
36  * A simple program that manages input and output for PostScript printers. Much
37  * has been added and changed from early versions of the program, but the basic
38  * philosophy is still the same. Don't send real data until we're certain we've
39  * connected to a PostScript printer that's in the idle state and try to hold
40  * the connection until the job is completely done. It's more work than you
41  * might expect is necessary, but should provide a reasonably reliable spooler
42  * interface that can return error indications to the caller via the program's
43  * exit status.
44  *
45  * I've added code that will let you split the program into separate read/write
46  * processes. Although it's not the default it should be useful if you have a
47  * file that will be returning useful data from the printer. The two process
48  * stuff was laid down on top of the single process code and both methods still
49  * work. The implementation isn't as good as it could be, but didn't require
50  * many changes to the original program (despite the fact that there are now
51  * many differences).
52  *
53  * By default the program still runs as a single process. The -R2 option forces
54  * separate read and write processes after the intial connection is made. If you
55  * want that as the default initialize splitme (below) to TRUE. In addition the
56  * -t option that's used to force stuff not recognized as status reports to
57  * stdout also tries to run as two processes (by setting splitme to TRUE). It
58  * will only work if the required code (ie. resetline() in ifdef.c) has been
59  * implemented for your Unix system. I've only tested the System V code.
60  *
61  * Code needed to support interactive mode has also been added, although again
62  * it's not as efficient as it could be. It depends on the system dependent
63  * procedures resetline() and setupstdin() (file ifdef.c) and for now is only
64  * guaranteed to work on System V. Can be requested using the -i option.
65  *
66  * Quiet mode (-q option) is also new, but was needed for some printers
67  * connected to RADIAN. If you're running in quiet mode no status requests will
68  * be sent to the printer while files are being transmitted (ie. in send()).
69  *
70  * The program expects to receive printer status lines that look like,
71  *
72  *	%%[ status: idle; source: serial 25 ]%%
73  *	%%[ status: waiting; source: serial 25 ]%%
74  *	%%[ status: initializing; source: serial 25 ]%%
75  *	%%[ status: busy; source: serial 25 ]%%
76  *	%%[ status: printing; source: serial 25 ]%%
77  *	%%[ status: PrinterError: out of paper; source: serial 25 ]%%
78  *	%%[ status: PrinterError: no paper tray; source: serial 25 ]%%
79  *
80  * although this list isn't complete. Sending a '\024' (control T) character
81  * forces the return of a status report. PostScript errors detected on the
82  * printer result in the immediate transmission of special error messages that
83  * look like,
84  *
85  *	%%[ Error: undefined; OffendingCommand: xxx ]%%
86  *	%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%
87  *
88  * although we only use the Error and Flushing keywords. Finally conditions,
89  * like being out of paper, result in other messages being sent back from the
90  * printer over the communications line. Typical PrinterError messages look
91  * like,
92  *
93  *	%%[ PrinterError: out of paper; source: serial 25 ]%%
94  *	%%[ PrinterError: paper jam; source: serial 25 ]%%
95  *
96  * although we only use the PrinterError keyword rather than trying to recognize
97  * all possible printer errors.
98  *
99  * The implications of using one process and only flow controlling data going to
100  * the printer are obvious. Job transmission should be reliable, but there can
101  * be data loss in stuff sent back from the printer. Usually that only caused
102  * problems with jobs designed to run on the printer and return useful data
103  * back over the communications line. If that's the kind of job you're sending
104  * call postio with the -t option. That should force the program to split into
105  * separate read and write processes and everything not bracketed by "%%[ "
106  * and " ]%%" strings goes to stdout. In otherwords the data you're expecting
107  * should be separated from the status stuff that goes to the log file (or
108  * stderr). The -R2 option does almost the same thing (ie. separate read and
109  * write processes), but everything that comes back from the printer goes to
110  * the log file (stderr by default) and you'll have to separate your data from
111  * any printer messages.
112  *
113  * A typical command line might be,
114  *
115  *	postio -l /dev/tty01 -b 9600 -L log file1 file2
116  *
117  * where -l selects the line, -b sets the baud rate, and -L selects the printer
118  * log file. Since there's no default line, at least not right now, you'll
119  * always need to use the -l option, and if you don't choose a log file stderr
120  * will be used. If you have a program that will be returning data the command
121  * line might look like,
122  *
123  *	postio -t -l/dev/tty01 -b9600 -Llog file >results
124  *
125  * Status stuff goes to file log while the data you're expecting back from the
126  * printer gets put in file results.
127  *
128  */
129 
130 #include <stdio.h>
131 #include <unistd.h>
132 #include <stdarg.h>
133 #include <stdlib.h>
134 #include <ctype.h>
135 #include <fcntl.h>
136 #include <signal.h>
137 #include <sys/types.h>
138 #include <sys/wait.h>
139 #include <errno.h>
140 #include <string.h>
141 #include <sys/ioccom.h>
142 #include <sys/ioctl.h>
143 #include <sys/bpp_io.h>
144 #include <sys/ecppsys.h>
145 
146 #include "ifdef.h"			/* conditional compilation stuff */
147 #include "gen.h"			/* general purpose definitions */
148 #include "postio.h"			/* some special definitions */
149 
150 static char	**argv;			/* global so everyone can use them */
151 static int	argc;
152 static char	*prog_name = "";	/* really just for error messages */
153 static int	x_stat = 0;		/* program exit status */
154 static int	debug = OFF;		/* debug flag */
155 static int	ignore = OFF;		/* what's done for FATAL errors */
156 static Baud	baudtable[] = BAUDTABLE; /* converts strings to termio values */
157 static int	quiet = FALSE;		/* no status queries in send if TRUE */
158 char	*postbegin = POSTBEGIN;	/* preceeds all the input files */
159 static int	useslowsend = FALSE;	/* not recommended! */
160 static int	splitme = FALSE;	/* into READ & WRITE procs if TRUE */
161 static int	whatami = READWRITE;	/* a READ or WRITE process - or both */
162 static int	otherpid = -1;		/* who gets signals if greater than 1 */
163 static int	joinsig = SIGTRAP;	/* reader gets when writing is done */
164 static int	writedone = FALSE;	/* and then sets this to TRUE */
165 static char	sbuf[MESGSIZE];		/* for parsing the message */
166 static char	*mesgptr = NULL;	/* printer msg starts here in mesg[] */
167 static Status	status[] = STATUS;	/* for converting status strings */
168 static int	nostatus = NOSTATUS;	/* default getstatus() return value */
169 static int	tostdout = FALSE;	/* non-status stuff goes to stdout? */
170 static int	currentstate = NOTCONNECTED;	/* START, SEND, or DONE */
171 
172 char	*line = NULL;			/* printer is on this tty line */
173 short	baudrate = BAUDRATE;		/* and running at this baud rate */
174 int	stopbits = 1;			/* number of stop bits */
175 int	interactive = FALSE;		/* interactive mode */
176 char	*block = NULL;			/* input file buffer */
177 int	blocksize = BLOCKSIZE;		/* and its size in bytes */
178 int	head = 0;			/* block[head] is the next character */
179 int	tail = 0;			/* one past the last byte in block[] */
180 int	canread = TRUE;			/* allow reads */
181 int	canwrite = TRUE;		/* and writes if TRUE */
182 char	mesg[MESGSIZE];			/* exactly what came back on ttyi */
183 char	*endmesg = NULL;		/* end for readline() in mesg[] */
184 int	ttyi = 0;			/* input */
185 int	ttyo = 2;			/* and output file descriptors */
186 FILE	*fp_log = stderr;		/* log file for stuff from printer */
187 
188 static void	init_signals(void);
189 static void	interrupt(int);
190 static void	options(void);
191 static void	initialize(void);
192 static void	initialize_parallel(void);
193 static void	start(void);
194 static void	split(void);
195 static void	arguments(void);
196 static void	send(int, char *);
197 static void	done(void);
198 static void	cleanup(void);
199 static void	clearline(void);
200 void	logit(char *, ...);
201 static void	quit(int sig);
202 static void	Rest(int t);
203 static int	parsemesg(void);
204 static int	sendsignal(int);
205 static int	writeblock(void);
206 static int	Write(int, char *, int);
207 static short	getbaud(char *);
208 static char	*find(char *, char *);
209 
210 void		error(int, char *, ...);
211 int		getstatus(int);
212 int		readblock(int);
213 
214 
215 /*	from parallel.c for parallel interfaces		*/
216 extern int	is_a_parallel_bpp(int);
217 extern int	bpp_state(int);
218 extern int	is_a_prnio(int);
219 extern int	prnio_state(int);
220 extern int	parallel_comm(int, int()); /* arg is bpp_state */
221 
222 /*	from ifdef.c for serial interfaces	*/
223 extern void	setupline(void);
224 extern void	setupstdin(int);
225 extern void	slowsend(int);
226 extern int	resetline(void);
227 extern int	readline(void);
228 
229 /*
230  * A simple program that manages input and output for PostScript printers.
231  * Can run as a single process or as separate read/write processes. What's
232  * done depends on the value assigned to splitme when split() is called.
233  */
234 
nop(int fd)235 int nop(int fd) { return(0); }
236 
237 
238 int
main(int agc,char * agv[])239 main(int agc, char *agv[])
240 {
241 	argc = agc;
242 	argv = agv;
243 	prog_name = argv[0];		/* really just for error messages */
244 
245 	/* is this a serial or parallel port? */
246 
247 	init_signals();		/* sets up interrupt handling */
248 	options();		/* get command line options */
249 
250 	setbuf(stderr, NULL);   /* unbuffer io for stderr */
251 
252 
253 	if (line) {
254 		close(1);
255 		open(line, O_RDWR);
256 
257 	}
258 
259 	if (is_a_prnio(1)) {
260 		initialize_parallel();
261 		x_stat = parallel_comm(1, prnio_state);
262 	} else if (is_a_parallel_bpp(1) ||
263 		    (get_ecpp_status(1) == ECPP_CENTRONICS)) {
264 		initialize_parallel();
265 		x_stat = parallel_comm(1, bpp_state);
266 	} else if (isatty(1)) {
267 		initialize();		/* must be done after options() */
268 		start();		/* make sure the printer is ready */
269 		split();		/* into read/write processes - maybe */
270 		arguments();		/* then send each input file */
271 		done();			/* wait until the printer is finished */
272 		cleanup();		/* make sure the write process stops */
273 	} else {
274 		initialize_parallel();
275 		x_stat = parallel_comm(1, nop);
276 	}
277 
278 
279 	return (x_stat);		/* everything probably went OK */
280 }
281 
282 
283 /*
284  * Makes sure we handle interrupts. The proper way to kill the program, if
285  * necessary, is to do a kill -15. That forces a call to interrupt(), which in
286  * turn tries to reset the printer and then exits with a non-zero status. If the
287  * program is running as two processes, sending SIGTERM to either the parent or
288  * child should clean things up.
289  */
290 
291 static void
init_signals(void)292 init_signals(void)
293 {
294     if (signal(SIGINT, interrupt) == SIG_IGN) {
295 	signal(SIGINT, SIG_IGN);
296 	signal(SIGQUIT, SIG_IGN);
297 	signal(SIGHUP, SIG_IGN);
298     } else {
299 	signal(SIGHUP, interrupt);
300 	signal(SIGQUIT, interrupt);
301     }
302 
303     signal(SIGTERM, interrupt);
304 
305 }
306 
307 
308 /*
309  * Reads and processes the command line options. The -R2, -t, and -i options all
310  * force separate read and write processes by eventually setting splitme to TRUE
311  * (check initialize()). The -S option is not recommended and should only be
312  * used as a last resort!
313  */
314 
315 static void
options(void)316 options(void)
317 {
318     int		ch;			/* return value from getopt() */
319     char	*optnames = "b:il:qs:tB:L:P:R:SDI";
320 
321     extern char	*optarg;		/* used by getopt() */
322     extern int	optind;
323 
324     while ((ch = getopt(argc, argv, optnames)) != EOF) {
325 
326 	switch (ch) {
327 
328 	    case 'b':			/* baud rate string */
329 		    baudrate = getbaud(optarg);
330 		    break;
331 
332 	    case 'i':			/* interactive mode */
333 		    interactive = TRUE;
334 		    break;
335 
336 	    case 'l':			/* printer line */
337 		    line = optarg;
338 		    break;
339 
340 	    case 'q':			/* no status queries - for RADIAN? */
341 		    quiet = TRUE;
342 		    break;
343 
344 	    case 's':			/* use 2 stop bits - for UNISON? */
345 		    if ((stopbits = atoi(optarg)) < 1 || stopbits > 2)
346 			stopbits = 1;
347 		    break;
348 
349 	    case 't':			/* non-status stuff goes to stdout */
350 		    tostdout = TRUE;
351 		    break;
352 
353 	    case 'B':			/* set the job buffer size */
354 		    if ((blocksize = atoi(optarg)) <= 0)
355 			blocksize = BLOCKSIZE;
356 		    break;
357 
358 	    case 'L':			/* printer log file */
359 		    if ((fp_log = fopen(optarg, "w")) == NULL)  {
360 			fp_log = stderr;
361 			error(NON_FATAL, "can't open log file %s", optarg);
362 		    }	/* End if */
363 		    break;
364 
365 	    case 'P':			/* initial PostScript code */
366 		    postbegin = optarg;
367 		    break;
368 
369 	    case 'R':			/* run as one or two processes */
370 		    if (atoi(optarg) == 2)
371 			splitme = TRUE;
372 		    else splitme = FALSE;
373 		    break;
374 
375 	    case 'S':			/* slow and kludged up vers. of send */
376 		    useslowsend = TRUE;
377 		    break;
378 
379 	    case 'D':			/* debug flag */
380 		    debug = ON;
381 		    break;
382 
383 	    case 'I':			/* ignore FATAL errors */
384 		    ignore = ON;
385 		    break;
386 
387 	    case '?':			/* don't understand the option */
388 		    error(FATAL, "");
389 		    break;
390 
391 	    default:			/* don't know what to do for ch */
392 		    error(FATAL, "missing case for option %c\n", ch);
393 		    break;
394 
395 	}   /* End switch */
396 
397     }   /* End while */
398 
399     argc -= optind;			/* get ready for non-option args */
400     argv += optind;
401 
402 }
403 
404 
405 /*
406  * Called from options() to convert a baud rate string into an appropriate
407  * termio value. *rate is looked up in baudtable[] and if it's found, the
408  * corresponding value is returned to the caller.
409  */
410 
411 static short
getbaud(char * rate)412 getbaud(char *rate)			/* string representing the baud rate */
413 {
414     int		i;			/* for looking through baudtable[] */
415 
416     for (i = 0; baudtable[i].rate != NULL; i++)
417 	if (strcmp(rate, baudtable[i].rate) == 0)
418 	    return (baudtable[i].val);
419 
420     error(FATAL, "don't recognize baud rate %s", rate);
421     /*NOTREACHED*/
422     return (0);
423 
424 }
425 
426 
427 /*
428  * Initialization, a few checks, and a call to setupline() (file ifdef.c) to
429  * open and configure the communications line. Settings for interactive mode
430  * always take precedence. The setupstdin() call with an argument of 0 saves
431  * the current terminal settings if interactive mode has been requested -
432  * otherwise nothing's done. Unbuffering stdout (via the setbuf() call) isn't
433  * really needed on System V since it's flushed whenever terminal input is
434  * requested. It's more efficient if we buffer the stdout (on System V) but
435  * safer (for other versions of Unix) if we include the setbuf() call.
436  */
437 
438 static void
initialize(void)439 initialize(void)
440 {
441     whatami = READWRITE;		/* always run start() as one process */
442     canread = canwrite = TRUE;
443 
444     if (line == NULL)			/* kludge for lp - they use -t option */
445 	tostdout = FALSE;
446 
447     if (tostdout == TRUE)		/* force separate read/write procs */
448 	splitme = TRUE;
449 
450     if (interactive == TRUE) {		/* interactive mode settings win */
451 	quiet = FALSE;
452 	tostdout = FALSE;
453 	splitme = TRUE;
454 	blocksize = 1;
455 	postbegin = NULL;
456 	useslowsend = FALSE;
457 	nostatus = INTERACTIVE;
458 	setbuf(stdout, NULL);
459     }
460 
461     if (useslowsend == TRUE) {		/* last resort only - not recommended */
462 	quiet = FALSE;
463 	splitme = FALSE;
464 	if (blocksize > 1024)		/* don't send too much all at once */
465 	    blocksize = 1024;
466     }
467 
468     if (line == NULL && (interactive == TRUE || tostdout == TRUE))
469 	error(FATAL, "a printer line must be supplied - use the -l option");
470 
471     if ((block = malloc(blocksize)) == NULL)
472 	error(FATAL, "no memory");
473 
474     endmesg = mesg + sizeof mesg - 2;	/* one byte from last pos. in mesg */
475 
476     setupline();			/* configure the communications line */
477     setupstdin(0);			/* save current stdin term settings */
478 
479 }
480 
481 static void
initialize_parallel(void)482 initialize_parallel(void)
483 {
484 	if ((block = malloc(blocksize)) == NULL)
485 		error(FATAL, "no memory");
486 }
487 
488 
489 /*
490  * Tries to put the printer in the IDLE state before anything important is sent.
491  * Run as a single process no matter what has been assigned to splitme. Separate
492  * read and write processes, if requested, will be created after we're done
493  * here.
494  */
495 
496 static void
start(void)497 start(void)
498 {
499    int longwait = 0;
500 
501     logit("printer startup\n");
502 
503     currentstate = START;
504     clearline();
505 
506     for (;;)
507 	switch (getstatus(1))  {
508 
509 	    case IDLE:
510 	    case INTERACTIVE:
511 		    if (postbegin != NULL && *postbegin != '\0')
512 			Write(ttyo, postbegin, strlen(postbegin));
513 		    clearline();
514 		    return;
515 
516 	    case BUSY:
517 		    Write(ttyo, "\003", 1);
518 		    Rest(1);
519 		    break;
520 
521 	    /* 03/24/95 - bob golden
522 	     * The HP LJ3 starts in waiting mode and needs the EOF to move
523 	     * from waiting to idle. To see what would happen, code was added
524 	     * to send the INTR on waiting and later changed to INTR/EOF.
525 	     * The INTR by itself had no effect. The INTR/EOF put the
526 	     * the printer in a busy status loop from which the only
527 	     * recovery was to reset the printer. Until further testing
528 	     * testing is done, do not send an INTR to a HPLJ3 in waiting
529 	     * state. WAITING moved to a separate case to eliminate the
530 	     * INTR write.
531 	     */
532 	    case WAITING:
533 		    Write(ttyo, "\004", 1);
534 		    Rest(1);
535 		    break;
536 
537 	    /* 03/24/95 - bob golden
538 	     * The HP LJ3 seems to process INTR at later times. All the
539 	     * longwaits are increaased to reduce the number of INTRs sent.
540 	     */
541 	    case ERROR:
542 	    case FLUSHING:
543 		    Write(ttyo, "\004", 1);
544 		    if (longwait++ == 5) {
545 		    	Write(ttyo, "\003", 1);
546 			Rest(5);
547 			longwait = 0;
548 		    }
549 		    Rest(1);
550 		    break;
551 
552 	    case PRINTERERROR:
553 		    Rest(15);
554 		    break;
555 
556 	    case DISCONNECT:
557 		    error(FATAL, "Disconnected - printer may be offline");
558 		    break;
559 
560 	    /* 03/24/95 - bob golden
561 	     * The ENDJOB case has been removed. The HP LJ3 echoes all EOFs
562 	     * sent so the ENDJOB has no real meaning.
563 	     */
564 	    case UNKNOWN:
565 		    clearline();
566 		    break;
567 
568 	    default:
569 		    Rest(1);
570 		    break;
571 
572 	}   /* End switch */
573 
574 }   /* End of start */
575 
576 
577 /*
578  *
579  * If splitme is TRUE we fork a process, make the parent handle reading, and let
580  * the child take care of writing. resetline() (file ifdef.c) contains all the
581  * system dependent code needed to reset the communications line for separate
582  * read and write processes. For now it's expected to return TRUE or FALSE and
583  * that value controls whether we try the fork. I've only tested the two process
584  * stuff for System V. Other versions of resetline() may just be dummy
585  * procedures that always return FALSE. If the fork() failed previous versions
586  * continued as a single process, although the implementation wasn't quite
587  * right, but I've now decided to quit. The main reason is a Datakit channel
588  * may be configured to flow control data in both directions, and if we run
589  * postio over that channel as a single process we likely will end up in
590  * deadlock.
591  */
592 
593 static void
split(void)594 split(void)
595 {
596 	int	pid;
597 
598 	if (splitme == TRUE)
599 		if (resetline() == TRUE) {
600 			pid = getpid();
601 			signal(joinsig, interrupt);
602 			if ((otherpid = fork()) == -1)
603 				error(FATAL, "can't fork");
604 			else if (otherpid == 0) {
605 				whatami = WRITE;
606 				nostatus = WRITEPROCESS;
607 				otherpid = pid;
608 				setupstdin(1);
609 			} else
610 				whatami = READ;
611 		} else if (interactive == TRUE || tostdout == TRUE)
612 			error(FATAL,
613 				"can't create two process - check resetline()");
614 		else
615 			error(NON_FATAL,
616 			    "running as a single process - check resetline()");
617 
618 	canread = (whatami & READ) ? TRUE : FALSE;
619 	canwrite = (whatami & WRITE) ? TRUE : FALSE;
620 }
621 
622 
623 /*
624  * Makes sure all the non-option command line arguments are processed. If there
625  * aren't any arguments left when we get here we'll send stdin. Input files are
626  * only read and sent to the printer if canwrite is TRUE. Checking it here means
627  * we won't have to do it in send(). If interactive mode is TRUE we'll stay here
628  * forever sending stdin when we run out of files - exit with a break. Actually
629  * the loop is bogus and used at most once when we're in interactive mode
630  * because stdin is in a pseudo raw mode and the read() in readblock() should
631  * never see the end of file.
632  */
633 
634 static void
arguments(void)635 arguments(void)
636 {
637     int		fd_in;			/* next input file */
638 
639     if (canwrite == TRUE)
640 	do				/* loop is for interactive mode */
641 	    if (argc < 1)
642 		send(fileno(stdin), "pipe.end");
643 	    else  {
644 		while (argc > 0) {
645 		    if ((fd_in = open(*argv, O_RDONLY)) == -1)
646 			error(FATAL, "can't open %s", *argv);
647 		    send(fd_in, *argv);
648 		    close(fd_in);
649 		    argc--;
650 		    argv++;
651 		}
652 	    }
653 	while (interactive == TRUE);
654 }
655 
656 /*
657  * Sends file *name to the printer. There's nothing left here that depends on
658  * sending and receiving status reports, although it can be reassuring to know
659  * the printer is responding and processing our job. Only the writer gets here
660  * in the two process implementation, and in that case split() has reset
661  * nostatus to WRITEPROCESS and that's what getstatus() always returns. For
662  * now we accept the IDLE state and ENDOFJOB as legitimate and ignore the
663  * INITIALIZING state.
664  *
665  * fd_in	next input file
666  * name		it's pathname
667  */
668 
669 static void
send(int fd_in,char * name)670 send(int fd_in, char *name)
671 {
672     if (interactive == FALSE)
673 	logit("sending file %s\n", name);
674 
675     currentstate = SEND;
676 
677     if (useslowsend == TRUE) {
678 	slowsend(fd_in);
679 	return;
680     }
681 
682     while (readblock(fd_in))
683 
684 	switch (getstatus(0)) {
685 
686 	    case IDLE:
687 	    case BUSY:
688 	    case WAITING:
689 	    case PRINTING:
690 	    case ENDOFJOB:
691 	    case PRINTERERROR:
692 	    case UNKNOWN:
693 	    case NOSTATUS:
694 	    case WRITEPROCESS:
695 	    case INTERACTIVE:
696 		    writeblock();
697 		    break;
698 
699 	    case ERROR:
700 		    fprintf(stderr, "%s", mesg);	/* for csw */
701 		    error(USER_FATAL, "PostScript Error");
702 		    break;
703 
704 	    case FLUSHING:
705 		    error(USER_FATAL, "Flushing Job");
706 		    break;
707 
708 	    case DISCONNECT:
709 		    error(FATAL, "Disconnected - printer may be offline");
710 		    break;
711 
712 	}
713 
714 }
715 
716 
717 /*
718  * Tries to stay connected to the printer until we're reasonably sure the job is
719  * complete. It's the only way we can recover error messages or data generated
720  * by the PostScript program and returned over the communication line. Actually
721  * doing it correctly for all possible PostScript jobs is more difficult that it
722  * might seem. For example if we've sent several jobs, each with their own EOF
723  * mark, then waiting for ENDOFJOB won't guarantee all the jobs have completed.
724  * Even waiting for IDLE isn't good enough. Checking for the WAITING state after
725  * all the files have been sent and then sending an EOF may be the best
726  * approach, but even that won't work all the time - we could miss it or might
727  * not get there. Even sending our own special PostScript job after all the
728  * input files has it's own different set of problems, but probably could work
729  * (perhaps by printing a fake status message or just not timing out). Anyway
730  * it's probably not worth the trouble so for now we'll quit if writedone is
731  * TRUE and we get ENDOFJOB or IDLE.
732  *
733  * If we're running separate read and write processes the reader gets here after
734  * after split() while the writer goes to send() and only gets here after all
735  * the input files have been transmitted. When they're both here the writer
736  * sends the reader signal joinsig and that forces writedone to TRUE in the
737  * reader. At that point the reader can begin looking for an indication of the
738  * end of the job.  The writer hangs around until the reader kills it (usually
739  * in cleanup()) sending occasional status requests.
740  */
741 
742 static void
done(void)743 done(void)
744 {
745     int		sleeptime = 15;		/* for 'out of paper' etc. */
746     int longwait = 0;
747 
748     if (canwrite == TRUE)
749 	logit("waiting for end of job\n");
750 
751     currentstate = DONE;
752     writedone = (whatami == READWRITE) ? TRUE : FALSE;
753 
754     for (;;) {
755 
756 	switch (getstatus(1)) {
757 	    case WRITEPROCESS:
758 		    if (writedone == FALSE) {
759 			sendsignal(joinsig);
760 			Write(ttyo, "\004", 1);
761 			writedone = TRUE;
762 			sleeptime = 1;
763 		    }
764 		    Rest(sleeptime++);
765 		    break;
766 
767 	    /* 03/24/95 - bob golden
768 	     * For the HP LJ3 INTR sent while in the waiting state have
769 	     * either had no effect or put the printer into a unrecoverable
770 	     * loop. Further testing may reveal this to not be the case
771 	     * but for now, remove the send INTR.
772 	     */
773 	    case WAITING:
774 		    Write(ttyo, "\004", 1);
775 		    Rest(1);
776 		    sleeptime = 15;
777 		    break;
778 
779 	    /* 03/24/95 - bob golden
780 	     * ENDOFJOB case removed here. The HP LJ 3 echoes all EOFs sent so
781 	     * the ENDOFJOB case is meaningless.
782 	     */
783 	    case IDLE:
784 		    if (writedone == TRUE) {
785 			logit("job complete\n");
786 			return;
787 		    }
788 		    break;
789 
790 	    /* 03/24/95 - bob golden
791 	     * During print data transmission, the HP LJ3 stays in
792 	     * status busy. So give it a rest.
793 	     *
794 	     */
795 	    case BUSY:
796 	    case PRINTING:
797 		    Rest(1);
798 		    sleeptime = 15;
799 		    break;
800 
801 	    case INTERACTIVE:
802 		    Write(ttyo, "\004", 1);
803 		    sleeptime = 15;
804 		    break;
805 
806 	    case PRINTERERROR:
807 		    Rest(sleeptime++);
808 		    break;
809 
810 	    case ERROR:
811 		    Write(ttyo, "\004", 1);
812 		    fprintf(stderr, "%s", mesg);	/* for csw */
813 		    error(USER_FATAL, "PostScript Error");
814 		    return;
815 
816 	    case FLUSHING:
817 		    Write(ttyo, "\004", 1);
818 		    error(USER_FATAL, "Flushing Job");
819 		    return;
820 
821 	    case DISCONNECT:
822 		    error(FATAL, "Disconnected - printer may be offline");
823 		    return;
824 
825 	    /* 03/24/95 - bob golden
826 	     * These cases are ignored without a EOF being sent
827 	     */
828 	    case ENDOFJOB:
829 	    case NOSTATUS:
830 		    Rest(1);
831 		    break;
832 
833 	    default:
834 		    Write(ttyo, "\004", 1);
835 		    Rest(1);
836 		    break;
837 
838 	}
839 
840 	if (sleeptime > 60)
841 	    sleeptime = 60;
842 
843     }
844 
845 }
846 
847 
848 /*
849  * Only needed if we're running separate read and write processes. Makes sure
850  * the write process is killed after the read process has successfully finished
851  * with all the jobs. sendsignal() returns a -1 if there's nobody to signal so
852  * things work when we're running a single process.
853  */
854 
855 static void
cleanup(void)856 cleanup(void)
857 {
858     int		w;
859 
860     while (sendsignal(SIGKILL) != -1 && (w = wait((int *)0)) != otherpid &&
861 	w != -1);
862     if ( currentstate != NOTCONNECTED )
863       Write(ttyo, "\004", 1);
864 }
865 
866 
867 /*
868  * Fills the input buffer with the next block, provided we're all done with the
869  * last one. Blocks from fd_in are stored in array block[]. head is the index
870  * of the next byte in block[] that's supposed to go to the printer. tail points
871  * one past the last byte in the current block. head is adjusted in writeblock()
872  * after each successful write, while head and tail are reset here each time
873  * a new block is read. Returns the number of bytes left in the current block.
874  * Read errors cause the program to abort. The fake status message that's put
875  * out in quiet mode is only so you can look at the log file and know
876  * something's happening - take it out if you want.
877  */
878 
879 int
readblock(int fd_in)880 readblock(int fd_in)
881 {
882     static long	blocknum = 1;
883 
884     if (head >= tail) {		/* done with the last block */
885 	if ((tail = read(fd_in, block, blocksize)) == -1)
886 	    error(FATAL, "error reading input file");
887 	if (quiet == TRUE && tail > 0)	/* put out a fake message? */
888 	    logit("%%%%[ status: busy; block: %d ]%%%%\n", blocknum++);
889 	head = 0;
890     }
891 
892     return (tail - head);
893 
894 }
895 
896 
897 /*
898  * Called from send() when it's OK to send the next block to the printer. head
899  * is adjusted after the write, and the number of bytes that were successfully
900  * written is returned to the caller.
901  */
902 
903 static int
writeblock(void)904 writeblock(void)
905 {
906     int		count;			/* bytes successfully written */
907 
908     if ((count = write(ttyo, &block[head], tail - head)) == -1)
909 	error(FATAL, "error writing to %s", line);
910     else if (count == 0)
911 	error(FATAL, "printer appears to be offline");
912 
913     head += count;
914     return (count);
915 
916 }
917 
918 
919 /*
920  * Looks for things coming back from the printer on the communications line,
921  * parses complete lines retrieved by readline(), and returns an integer
922  * representation of the current printer status to the caller. If nothing was
923  * available a status request (control T) is sent to the printer and nostatus
924  * is returned to the caller (provided quiet isn't TRUE). Interactive mode
925  * either never returns from readline() or returns FALSE.
926  */
927 
928 int
getstatus(int t)929 getstatus(int t)			/* sleep time after sending '\024' */
930 {
931     int		state = nostatus;	/* the current state */
932     static int	laststate = NOSTATUS;	/* last state recognized */
933 
934 
935     if (canread == TRUE && readline() == TRUE)  {
936 	state = parsemesg();
937 	if (state != laststate || mesgptr != mesg || debug == ON)
938 	    logit("%s", mesg);
939 
940 	if (tostdout == TRUE && currentstate != START) {
941 	    *mesgptr = '\0';
942 	    fprintf(stdout, "%s", mesg);
943 	}
944 	return (laststate = state);
945     }
946 
947     if ((quiet == FALSE || currentstate != SEND) && interactive == FALSE) {
948 	if (Write(ttyo, "\024", 1) != 1)
949 	    error(FATAL, "printer appears to be offline");
950 	if (t > 0) Rest(t);
951     }
952 
953     return (nostatus);
954 }
955 
956 
957 /*
958  *
959  * Parsing the lines that readline() stores in mesg[] is messy, and what's done
960  * here isn't completely correct nor as fast as it could be. The general format
961  * of lines that come back from the printer (assuming no data loss) is:
962  *
963  *		str%%[ key: val; key: val; key: val ]%%\n
964  *
965  * where str can be most anything not containing a newline and printer reports
966  * (eg. status or error messages) are bracketed by "%%[ " and " ]%%" strings and
967  * end with a newline. Usually we'll have the string or printer report but not
968  * both. For most jobs the leading string will be empty, but could be anything
969  * generated on a printer and returned over the communications line using the
970  * PostScript print operator. I'll assume PostScript jobs are well behaved and
971  * never bracket their messages with "%%[ " and " ]%%" strings that delimit
972  * status or error messages.
973  *
974  * Printer reports consist of one or more key/val pairs, and what we're
975  * interested in (status or error indications) may not be the first pair in the
976  * list. In addition we'll sometimes want the value associated with a keyword
977  * (eg. when key = status) and other times we'll want the keyword (eg. when
978  * key = Error or Flushing). The last pair isn't terminated by a semicolon and
979  * a value string often contains many space separated words and it can even
980  * include colons in meaningful places. I've also decided to continue
981  * converting things to lower case before doing the lookup in status[]. The
982  * isupper() test is for Berkeley systems.
983  */
984 
985 static int
parsemesg(void)986 parsemesg(void)
987 {
988 	char	*e;			/* end of printer message in mesg[] */
989 	char	*key, *val;		/* keyword/value strings in sbuf[] */
990 	char	*p;			/* for converting to lower case etc. */
991 	int	i;			/* where *key was found in status[] */
992 
993 	if (*(mesgptr = find("%%[ ", mesg)) != '\0' &&
994 	    *(e = find(" ]%%", mesgptr+4)) != '\0') {
995 
996 		strcpy(sbuf, mesgptr+4);	/* don't change mesg[] */
997 		sbuf[e-mesgptr-4] = '\0';	/* ignore the trailing " ]%%" */
998 
999 		for (key = strtok(sbuf, " :"); key != NULL;
1000 		    key = strtok(NULL, " :")) {
1001 			if ((val = strtok(NULL, ";")) != NULL &&
1002 			    strcmp(key, "status") == 0)
1003 				key = val;
1004 
1005 			for (; *key == ' '; key++);	/* skip leading space */
1006 			for (p = key; *p; p++)		/* conv to lower case */
1007 				if (*p == ':' || *p == ',') {
1008 					*p = '\0';
1009 					break;
1010 				} else if (isupper(*p))
1011 					*p = tolower(*p);
1012 
1013 			for (i = 0; status[i].state != NULL; i++)
1014 				if (strcmp(status[i].state, key) == 0)
1015 					return (status[i].val);
1016 		}
1017 	} else if (strcmp(mesg, "CONVERSATION ENDED.\n") == 0)
1018 		return (DISCONNECT);
1019 
1020 	return (nostatus);
1021 }
1022 
1023 
1024 /*
1025  * Looks for *str1 in string *str2. Returns a pointer to the start of the
1026  * substring if it's found or to the end of string str2 otherwise.
1027  */
1028 
1029 static char *
find(char * str1,char * str2)1030 find(char *str1, char *str2)
1031 {
1032     char	*s1, *s2;		/* can't change str1 or str2 too fast */
1033 
1034     for (; *str2 != '\0'; str2++) {
1035 	for (s1 = str1, s2 = str2; *s1 != '\0' && *s1 == *s2; s1++, s2++);
1036 	if (*s1 == '\0')
1037 	    break;
1038     }
1039 
1040     return (str2);
1041 
1042 }
1043 
1044 
1045 /*
1046  * Reads characters from the input line until nothing's left. Don't do
1047  * anything if we're currently running separate read and write processes.
1048  */
1049 
1050 static void
clearline(void)1051 clearline(void)
1052 {
1053     if (whatami == READWRITE)
1054 	while (readline() != FALSE);
1055 
1056 }
1057 
1058 
1059 /*
1060  * Sends signal sig to the other process if we're running as separate read and
1061  * write processes. Returns the result of the kill if there's someone else to
1062  * signal or -1 if we're running alone.
1063  *
1064  */
1065 
1066 static int
sendsignal(int sig)1067 sendsignal(int sig)
1068 {
1069     if (whatami != READWRITE && otherpid > 1)
1070 	return (kill(otherpid, sig));
1071 
1072     return (-1);
1073 }
1074 
1075 
1076 /*
1077  * Caught a signal - all except joinsig cause the program to quit. joinsig is
1078  * the signal sent by the writer to the reader after all the jobs have been
1079  * transmitted.  Used to tell the read process when it can start looking for
1080  * the end of the job.
1081  */
1082 
1083 static void
interrupt(int sig)1084 interrupt(int sig)
1085 {
1086     signal(sig, SIG_IGN);
1087 
1088     if (sig != joinsig) {
1089 	x_stat |= FATAL;
1090 	if (canread == TRUE)
1091 	    if (interactive == FALSE)
1092 		error(NON_FATAL, "signal %d abort", sig);
1093 	    else error(NON_FATAL, "quitting");
1094 	quit(sig);
1095     }
1096 
1097     writedone = TRUE;
1098     signal(joinsig, interrupt);
1099 }
1100 
1101 
1102 /*
1103  * Simple routine that's used to write a message to the log file.
1104  */
1105 
1106 void
logit(char * mesg,...)1107 logit(char *mesg, ...)
1108 {
1109 	va_list	ap;
1110 
1111 	va_start(ap, mesg);
1112 	vfprintf(fp_log, mesg, ap);
1113 	va_end(ap);
1114 
1115 	fflush(fp_log);
1116 
1117 }
1118 
1119 /*
1120  * Called when we've run into some kind of program error. First *mesg is
1121  * printed.  If kind is FATAL and we're not ignoring errors the program
1122  * will be terminated. If mesg is NULL or *mesg is the NULL string nothing
1123  * will be printed.
1124  */
1125 
1126 void
error(int kind,char * mesg,...)1127 error(int kind, char *mesg, ...)
1128 {
1129 	va_list	ap;
1130 
1131 	if (mesg != NULL && *mesg != '\0') {
1132 		fprintf(fp_log, "%s: ", prog_name);
1133 
1134 		va_start(ap, mesg);
1135 		vfprintf(fp_log, mesg, ap);
1136 		va_end(ap);
1137 
1138 		putc('\n', fp_log);
1139 	}
1140 
1141 	x_stat |= kind;
1142 
1143 	if (kind != NON_FATAL && ignore == OFF)
1144 		quit(SIGTERM);
1145 
1146 }
1147 
1148 
1149 /*
1150  *
1151  * Makes sure everything is properly cleaned up if there's a signal or FATAL
1152  * error that should cause the program to terminate. The sleep by the write
1153  * process is to help give the reset sequence a chance to reach the printer
1154  * before we break the connection - primarily for printers connected to Datakit.
1155  * There's a very slight chance the reset sequence that's sent to the printer
1156  * could get us stuck here. Simplest solution is don't bother to send it -
1157  * everything works without it.  Flushing ttyo would be better, but means yet
1158  * another system dependent procedure in ifdef.c! I'll leave things be for now.
1159  */
1160 
1161 static void
quit(int sig)1162 quit(int sig)
1163 {
1164     int		w;
1165 
1166     signal(sig, SIG_IGN);
1167     ignore = ON;
1168 
1169     while (sendsignal(sig) != -1 && (w = wait((int *)0)) != otherpid &&
1170 	w != -1);
1171 
1172     setupstdin(2);
1173 
1174     if (currentstate != NOTCONNECTED)
1175 	Write(ttyo, "\003\004", 2);
1176     alarm(0);			/* prevents sleep() loop on V9 systems */
1177     Rest(2);
1178 
1179     exit(x_stat);
1180 
1181 }
1182 
1183 
1184 /*
1185  * Used to replace sleep() calls. Only needed if we're running the program as
1186  * a read and write process and don't want to have the read process sleep. Most
1187  * sleeps are in the code because of the non-blocking read used by the single
1188  * process implementation. Probably should be a macro.
1189  */
1190 
1191 static void
Rest(int t)1192 Rest(int t)
1193 {
1194     if (t > 0 && canwrite == TRUE)
1195 	sleep(t);
1196 
1197 }
1198 
1199 
1200 /*
1201  * Used to replace some of the read() calls. Only needed if we're running
1202  * separate read and write processes. Should only be used to replace read calls
1203  * on ttyi.  Always returns 0 to the caller if the process doesn't have its
1204  * READ flag set.  Probably should be a macro.
1205  */
1206 
1207 #ifdef NEVER
1208 
1209 static int
Read(int fd,char * buf,int n)1210 Read(int fd, char *buf, int n)
1211 {
1212     int		count;
1213 
1214     if (canread == TRUE) {
1215 	if ((count = read(fd, buf, n)) == -1 && errno == EINTR)
1216 	    count = 0;
1217     } else count = 0;
1218 
1219     return (count);
1220 
1221 }
1222 
1223 #endif	/* NEVER */
1224 
1225 
1226 /*
1227  *
1228  * Used to replace some of the write() calls. Again only needed if we're running
1229  * separate read and write processes. Should only be used to replace write calls
1230  * on ttyo. Always returns n to the caller if the process doesn't have its WRITE
1231  * flag set. Should also probably be a macro.
1232  *
1233  */
1234 
1235 static int
Write(int fd,char * buf,int n)1236 Write(int fd, char *buf, int n)
1237 {
1238     int		count;
1239 
1240     if (canwrite == TRUE) {
1241 	if ((count = write(fd, buf, n)) == -1 && errno == EINTR)
1242 	    count = n;
1243     } else count = n;
1244 
1245     return (count);
1246 }
1247