Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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