#ifndef lint
#define _NOTICE static char
_NOTICE N1[] = "Copyright (c) 1985,1987,1990,1991,1992 Adobe Systems Incorporated";
_NOTICE N2[] = "GOVERNMENT END USERS: See Notice file in TranScript library directory";
_NOTICE N3[] = "-- probably /usr/lib/ps/Notice";
_NOTICE RCSID[]="$Header: /afs/dev.mit.edu/project/sipb/repository/third/transcript/src/qmscomm.sysv,v 1.1.1.1 1997/12/10 21:41:51 ghudson Exp $";
#endif
/* pscomm.c
 *
 * Copyright (C) 1985,1987,1990,1991,1992 Adobe Systems Incorporated. All
 * rights reserved. 
 * GOVERNMENT END USERS: See Notice file in TranScript library directory
 * -- probably /usr/lib/ps/Notice
 * 
 * Copyright (C) 1991 QMS, Inc
 *
 * System V lp/lpsched communications filter for PostScript printers.
 *
 * pscomm is the general communications filter for sending files to
 * a PostScript printer via RS232 lines.  It does
 * error handling/reporting, job logging, etc.
 * It observes (parts of) the PostScript file structuring conventions.
 * In particular, it distinguishes between PostScript files (beginning
 * with the "%!" magic number) -- which are shipped to the printer --
 * and text files (no magic number) which are formatted and listed
 * on the printer.
 *
 * pscomm gets called with:
 *	stdin	== the file to print (may be a pipe!)
 *	stdout	== the printer
 *	stderr	== the printer log file
 *	fd 3    == printer responses
 *	cwd	== the spool directory
 *	argv	== varies depending on how invoked, but one of: ???
 *	  filtername -p filtername -n login -h host
 *	environ	== various environment variable effect behavior
 *		JOBOUTPUT	- file for actual printer stream
 *				  output (if defined)
 *		VERBOSELOG	- do job verbose logging
 *
 * General flow of control for communications:
 * 1) "Sync" with printer: Send small accounting job, and make sure reply
 *    matches what we sent.  If not a match, sleep a little and try again.
 * 2) Send user job: Start listener process, and write user job to printer.
 *    CTRL-T gets sent occasionally. Listener
 *    get ALL output from printer.  If listener sees "idle" status message
 *    it aborts, which flushes the job (printer rebooted). End-of-job character
 *    is written to printer after job has been sent.
 * 3) Wait for user job to complete: Listener process exits when end-of-job
 *    character is received from printer.  "idle" status will make job
 *    abort as in (2) above.
 * Also, note that jobs containing or printing end-of-job characters may
 * not print correctly -- the "sync" process guarantees the next job will
 * be executed correctly, however.
 *
 * RCSLOG:
 * $Log: qmscomm.sysv,v $
 * Revision 1.1.1.1  1997/12/10 21:41:51  ghudson
 * 12/10/97 snapshot of the Athena source tree.
 *
 * Revision 1.1.1.1  1996/10/07 20:25:52  ghudson
 * Import of Transcript 4.1
 *
# Revision 1.10  1994/04/08  23:27:38  snichols
# added sigignore for SIGPIPE, surrounded by ifdef SYSV since
# not all BSD systems have sigignore, and the problem doesn't
# happen there anyway.
#
# Revision 1.9  1994/04/08  21:03:25  snichols
# close fdinput as soon as we're done with it, to avoid SIGPIPE problems
# on Solaris.
#
# Revision 1.8  1993/10/28  22:17:04  snichols
# Solaris cleanups.
#
# Revision 1.7  1993/10/27  21:05:50  snichols
# fixed typo.
#
# Revision 1.6  1993/09/09  16:16:29  snichols
# corrected typo.
#
# Revision 1.5  1993/05/25  21:55:55  snichols
# cleanup for Solaris
#
# Revision 1.4  1992/08/21  16:26:32  snichols
# Release 4.0
#
# Revision 1.3  1992/07/14  22:10:28  snichols
# Updated copyrights.
#
# Revision 1.2  1992/05/26  21:56:00  snichols
# comments after endif
#
# Revision 1.1  1992/05/19  23:09:37  snichols
# Initial revision
#
 * Revision 3.0  1991/06/17  16:50:45  snichols
 * Release 3.0
 *
 * Revision 2.5  1991/03/26  20:43:36  snichols
 * added config.h
 *
 * Revision 2.4  1991/01/02  15:32:08  snichols
 * accidently deleted GotEmtSig; restored and repaired.
 * 
 * Revision 2.3  90/12/20  10:32:31  snichols
 * got rid of old stuff that's no longer necessary.
 * 
 * Revision 2.2  87/11/17  16:51:37  byron
 *** empty log message ***
 *
 * Revision 2.2  87/11/17  16:51:37  byron
 * Release 2.1
 * 
 * Revision 2.1.1.8  87/11/12  13:41:24  byron
 * Changed Government user's notice.
 * 
 * Revision 2.1.1.7  87/09/30  16:53:46  byron
 * 1. Added some info to the "printer sync" error message.
 * 2. Changed the timeout parsing not to recognize "PrinterError: timeout".
 * 
 * Revision 2.1.1.6  87/09/16  11:15:34  byron
 * 1. Added check for printer timeout, which aborts job and tries again.
 * 2. Added warning if CTRL-D is found while job is still being sent.
 * 
 * Revision 2.1.1.5  87/09/09  10:11:25  byron
 * Added blank-skipping to status close string, so that %%[ ... ] %% works.
 * 
 * Revision 2.1.1.4  87/07/24  15:50:59  byron
 * Clone of pscomm.bsd changes.
 * Complete rearrangement of code (most algorithms the same). Goals:
 * 1. Make listener process simpler, and able to communicate better w/ parent.
 * 2. Make sure printer is replying to the messages we actually sent.
 *    syncprinter() accomplishes this using accounting info.
 * 3. Eliminate the race conditions, especially involving signal processing.
 *    Had multiple signal calls -- now has single signal call for each
 *    signal and state variable (intstate). Also added STARTCRIT and ENDCRIT.
 * 4. Explicitly kill existing children, and make sure they go away.
 * 5. Make goto's and setjmp/longjmp's more straightforward (if possible).
 * 
 * Revision 2.1.1.3  87/05/18  16:28:31  byron
 * Fix magic number error that made things blow up on small PostScript files.
 * 
 * Revision 2.1.1.2  87/04/23  10:26:20  byron
 * Copyright notice.
 * 
 * Revision 2.1.1.1  87/03/24  16:35:34  shore
 * fixed parsing of %%X messages back from printer, X was getting dropped
 * 
 * Revision 2.1  85/11/24  11:50:32  shore
 * Product Release 2.0
 * 
 * Revision 1.2  85/11/20  10:11:40  shore
 * Alarm kept ringing, reset signal handler
 * 
 * 
 *
 */

#include <ctype.h>
#include <setjmp.h>
#include <termio.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <time.h>

#ifdef QMS
#include <fcntl.h>
#include <netdb.h>
#include <tiuser.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <stropts.h>
#include <poll.h>
#include "qef.h"
#endif /* QMS */

/* #define _NFILE 20        /* Maximum number of open files */

/* Should be in system definitions.  Return value of wait(). */
typedef struct {
    int w_termsig : 8;
    int w_retcode : 8;
    } stat_buf;

#ifdef BRIDGE  /* Unsupported code for Bridge communications boxes */
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#endif /* BRIDGE */

#include "transcript.h"
#include "psspool.h"
#include "config.h"

#ifdef BDEBUG
#define debugp(x) {fprintf x ; (void) fflush(stderr);}
#else
#define debugp(x)
#endif /* BDEBUG */

/*
 * the following string is sent to the printer when we want it to
 * report its current pagecount (for accounting)
 */

private char *getpages =
"\n(%%%%[ pagecount: )print statusdict/pagecount get exec(                )cvs \
print(, %d %d ]%%%%)= flush\n%s";

private jmp_buf initlabel, synclabel, sendlabel, croaklabel;

private char	*prog;			/* invoking program name */
#ifdef QMS
private char *pname;
#endif /* QMS */
private int	progress, oldprogress;	/* finite progress counts */
private int	getstatus = FALSE;      /* TRUE = Query printer for status */
private int	newstatmsg = FALSE;     /* TRUE = We changed status message */
private int	childdone = FALSE;      /* TRUE = Listener process finished */
private int	jobaborted = FALSE;     /* TRUE = Aborting current job */
private long	startpagecount;         /* Page count at start of job */
private long	starttime;              /* Timer start. For status warnings */
private int	saveerror;		/* Place to save errno when exiting */

private char *verboselog;
private int VerboseLog;

/* Interrupt state variables */
typedef enum {                  /* Values for "die" interrupts, like SIGINT */
    init,                       /* Initialization */
    syncstart,                  /* Synchronize communications with printer */
    sending,                    /* Send info to printer */
    waiting,                    /* Waiting for listener to get EOF from prntr */
    lastpart,                   /* Final processing following user job */
    synclast,                   /* Syncronize communications at the end */
    ending,                     /* Cleaning up */
    croaking,			/* Abnormal exit, waiting for children to die */
    child                       /* Used ONLY for the child (listener) process */
    } dievals;
private dievals intstate;       /* State of interrupts */
private flagsig;                /* TRUE = On signal receipt, just set flag */
#define DIE_INT    1            /* Got a "die" interrupt. like SIGINT */
#define ALARM_INT  2            /* Got an alarm */
#define EMT_INT    4            /* Got a SIGEMT signal (child-parent comm.) */
private int gotsig;             /* Mask that may take any of the above values */

#define STARTCRIT() {gotsig=0; flagsig=TRUE;}  /* Start a critical region */
#define ENDCRIT()   {flagsig=FALSE;}           /* End a critical region */

/* WARNING: Make sure reapchildren() routine kills all processes we started */
private int	cpid = 0;	/* listener pid */
private int	mpid = 0;	/* current process pid */
private int     wpid;		/* Temp pid */
private int status;		/* Return value from wait() */

private char abortbuf[] = "\003";	/* ^C abort */
private char statusbuf[] = "\024";	/* ^T status */
private char eofbuf[] = "\004";		/* ^D end of file */

/* global file descriptors (avoid stdio buffering!) */
private int fdsend;		/* to printer (from stdout) */
private int fdlisten;		/* from printer (same tty line) */
private int fdinput;		/* file to print (from stdin) */

private FILE *psin=NULL;        /* Buffered printer input */
private FILE *jobout;		/* special printer output log */

#ifdef BRIDGE
/* socket numbers here are for SUN with ntohs() to convert */
#define GENERIC_SOCKET	ntohs((u_short) 0x07D0)
#define NMUI_SOCKET	ntohs((u_short) 0x004D)
private int socklen;
struct sockaddr_in dest;
struct hostent *hp;
#endif /* BRIDGE */

#ifdef QMS
struct sockaddr_in printaddr;
struct command_opt copts;
struct sockaddr_in claddr;
struct hostent *printer;
struct t_call *callptr;
int tpfd, ufd;

extern int t_errno;
#endif /* QMS */

/* Return values from the NextCh() routine */
typedef enum {
    ChOk,			/* Everything is fine */
    ChIdle,			/* We got status="idle" from the printer */
    ChTimeout			/* The printer timed out */
    } ChStat;


extern char *getenv();
extern int errno;

private VOID    GotDieSig();
private VOID    GotAlarmSig();
private VOID    GotEmtSig();
private VOID    syncprinter();
private VOID    listenexit();
private VOID    closedown();
private VOID    myexit1(),myexit2();
private VOID    croak();
private char 	*FindPattern();
private ChStat  NextCh();

/* The following are alarms settings for various states of this program */
#define SENDALARM 90		/* Status check while sending job to printer */
#define WAITALARM 90            /* Status check while waiting for job to end */
#define CROAKALARM 10           /* Waiting for child processes to die */
#define CHILDWAIT 60		/* Child waiting to die -- checking on parent */
#define ABORTALARM 90           /* Waiting for printer to respond to job abort */
#define SYNCALARM 30            /* Waiting for response to communications sync */
#define MAXSLEEP  15            /* Max time to sleep while sync'ing printer */

/* Exit values from the listener process */
#define LIS_NORMAL 0        /* No problems */
#define LIS_EOF    1        /* Listener got EOF on printer */
#define LIS_IDLE   2        /* Heard a "status: idle" from the printer */
#define LIS_DIE    3        /* Parent told listener to kill itself */
#define LIS_NOPARENT  4     /* Parent is no longer present */
#define LIS_ERROR  5        /* Unrecoverable error */
#define LIS_TIMEOUT 6       /* Printer timed out */



main(argc,argv)            /* MAIN ROUTINE */
	int argc;
	char *argv[];
{
    register char  *cp;
    register int cnt, wc;
    register char *mbp;

    char  **av;
    FILE *streamin;

    char mybuf[BUFSIZ];
    int format = 0;
    int i;

#ifdef QMS
    int sleeptime, pstatus;
#endif /* QMS */

    mpid = getpid();    /* Save the current process ID for later */

    /* initialize signal processing */
    flagsig = FALSE;           /* Process the signals */
    intstate = init;       /* We are initializing things now */
    VOIDC signal(SIGINT, GotDieSig);
    VOIDC signal(SIGHUP, GotDieSig);
    VOIDC signal(SIGTERM, GotDieSig);
    VOIDC signal(SIGALRM, GotAlarmSig);
    VOIDC signal(SIGEMT, GotEmtSig);
#ifdef SYSV
    VOIDC sigignore(SIGPIPE);
#endif /* SYSV */

    /* parse command-line arguments */
    /* the argv (see header comments) comes from the spooler daemon */
    /* itself, so it should be canonical, but at least one 4.2-based */
    /* system uses -nlogin -hhost (insead of -n login -h host) so I */
    /* check for both */

    av = argv;
    if (prog = strrchr(*av,'/')) prog++;
    else prog = *av;

#ifdef QMS
    while (--argc) {
	if (*(cp = *++av) == '-') {
	    switch (*(cp + 1)) {
		case 'P': /* printer name */
		    argc--;
		    pname = *(++av);
		    break;
		default:
		    break;
		}
	}
    }
#endif /* QMS */    

	

    debugp((stderr,"args: %s \n",prog));

    /* close all file descriptors >= 4 */
    for (i = 4; i < _NFILE ; i++) VOIDC close(i);

    /* do printer-specific options processing */

    if (verboselog=envget("VERBOSELOG")) {
	VerboseLog=atoi(verboselog);
    }

    if (VerboseLog) {
	VOIDC time(&starttime);
	fprintf(stderr, "%s: start - %s", prog, ctime(&starttime));
	VOIDC fflush(stderr);
    }
    debugp((stderr,"%s: pid %d ppid %d\n",prog,getpid(),getppid()));
    debugp((stderr,"%s: options VL=%d\n",prog,VerboseLog));

    /* IMPORTANT: in the case of cascaded filters, */ 
    /* stdin may be a pipe! (and hence we cannot seek!) */


    streamin = stdin;
    fdinput = fileno(streamin); /* the file to print */

#ifdef BRIDGE
    /* open network connection to the printer */
    if (envget("NETNAME")) pname=envget("NETNAME");

    if ((hp = gethostbyname(pname)) == NULL) {
	myexit2(prog,"badhost",TRY_AGAIN);
    }
    bcopy(hp->h_addr, &dest.sin_addr.s_addr, hp->h_length);
    dest.sin_family = AF_INET;
    dest.sin_port = GENERIC_SOCKET;

    if ((fdsend = socket(AF_INET,SOCK_STREAM, 0)) < 0) {
	myexit2(prog,"opening socket",TRY_AGAIN);
    }
    socklen = sizeof(dest);
    if (connect(fdsend,&dest,socklen)) {
	saveerror = errno;
	sleep(30);
	errno = saveerror;
	myexit2(prog,"connecting",TRY_AGAIN);
    }
    if (getsockname(fdsend,&dest,&socklen)) {
	saveerror = errno;
	VOIDC close(fdsend);
	errno = saveerror;
	myexit2(prog,"getting socket name",TRY_AGAIN);
    }
    /* socket options processing */
    if (setsockopt(fdsend,SOL_SOCKET,SO_DONTLINGER,0,0) < 0) {
	perror("sockopt dontlinger");
    }
#else
#ifdef QMS
    init_data(&copts);

    if ((printer = gethostbyname(pname)) == (struct hostent
						      *)NULL) { 
	fprintf(stderr,
		"gethostbyname failed:hostname not found in /etc/hosts\n");
	perror("");
	exit(1);
    }
	
    /*
     * Set up the address of the peer process with whom we want to
     * communicate. The same address (printaddr) can be used for both
     * TCP and UDP connections since both tcp port and udp port numbers
     * are the same (PRINTER_PORT).
     */

    memset((void *)&printaddr, 0, sizeof(printaddr));
    printaddr.sin_family = AF_INET;
    printaddr.sin_port = htons(PRINTER_PORT);
    memcpy((void *)&printaddr.sin_addr, (void *)printer->h_addr,
	  printer->h_length);

    copts.cpid = spawn_statproc();
    /*
     * Open TCP TLI.
     */
    if ((tpfd = t_open(TCP_PROVIDER, O_RDWR, (struct t_info *)0)) < 0) {
	t_error("Failed to open connection to transport provider\n");
        croak(TRYAGAIN);
    }

    debugp((stderr,"TCP file descriptor:: %d\n", tpfd));
    /*
     * Since we are the 'active' partner in the communication, we don't
     * really care what address is bound to our transport endpoint, tpfd;
     * happy with whatever Transport Provider provides. Same holds true
     * for maximum number of connections.
     */
    
    if (t_bind(tpfd, (struct t_bind *)0, (struct t_bind *)0) < 0) {
	t_error("Failed to bind address to endpoint\n");
	croak(THROW_AWAY);
    }
    
    /*
     * Set up UDP TLI.
     */
    if ((ufd = set_udptli()) < 0) {
	fprintf(stderr, "Failed to open UDP TLI link\n");
	perror("");
	croak(THROW_AWAY);
    }
    
    debugp((stderr,"UDP file descriptor:: %d\n", ufd));
#else /* QMS */
    fdsend = fileno(stdout);	/* the printer (write) */
    fdlisten = 3;		/* The printer (read) */
#endif /* QMS */
#endif /* BRIDGE */

    intstate = syncstart;   /* start communications */
#ifdef QMS
    /*
     * Get printer status using UDP based STATUS1 protocol.
     */
    pstatus = get_printstat(ufd, 0, (char *)0);
    sleeptime = SLEEPTIME;
    while (pstatus == 0) {
	sleeptime = sleeptime * 2;
	sleep(sleeptime);
	pstatus = get_printstat(ufd, 0, (char *)0);
    }
#else /* QMS */
    debugp((stderr,"%s: sync printer and get initial page count\n",prog));
    syncprinter(&startpagecount);     /* Make sure printer is listening */
#endif /* QMS */

    STARTCRIT();		/* Child state change and setjmp() */
    intstate = sending;

#ifdef QMS
    /*
     * Connect (TCP) to the peer on the printer.
     */
    
    if ((callptr = (struct t_call *)t_alloc(tpfd, T_CALL, T_ADDR)) == NULL) {
	t_error("Failed to allocate t_call structure\n");
	croak(THROW_AWAY);
    }
    
    callptr->addr.maxlen = sizeof(printaddr);
    callptr->addr.len = sizeof(printaddr);
    callptr->addr.buf = (char *)&printaddr;
    callptr->opt.len = 0;
    callptr->udata.len = 0;
    
    
    if (t_connect(tpfd, callptr, (struct t_call *)0) < 0) {
	t_error("Failed to connect to printer\n");
	croak(THROW_AWAY);
    }
    
    /*
     * Printer is accepting data; Transfer it (over TCP).
     */
    if (xfer_data(tpfd, &copts, fdinput) < 0) {
	fprintf(stderr, "Data Transfer to the printer failed\n");
	croak(TRYAGAIN);
    }
    
    if (t_close(tpfd) < 0) {
	t_error("Failed to close tcp descriptor\n");
	croak(TRYAGAIN);
    }
    
    if (t_close(ufd) < 0) {
	t_error("Failed to close udp descriptor\n");
	croak(TRYAGAIN);
    }
#else /* QMS */
    if ((cpid = fork()) < 0) myexit1(prog, THROW_AWAY);
    else if (cpid) {/* START PARENT -- SENDER */

	if( setjmp(sendlabel) ) goto donefile;
	ENDCRIT();		/* Child state change and setjmp() */
	if( gotsig & DIE_INT ) closedown();

	debugp((stderr,"%s: printer responding\n",prog));
	progress = oldprogress = 0; /* finite progress on sender */


	/* now ship the rest of the file */

	VOIDC alarm(0); /* NO ALARMS. They screw up write() */

	while ((cnt = read(fdinput, mybuf, sizeof mybuf)) > 0) {
	    if (getstatus) {       /* Get printer status sometimes */
		VOIDC write(fdsend, statusbuf, 1);
		getstatus = FALSE;
		progress++;
	    }
	    mbp = mybuf;
	    while ((cnt > 0) && ((wc = write(fdsend, mbp, (unsigned)cnt)) != cnt)) {
		if (wc < 0) {
		    fprintf(stderr,"%s: error writing to printer",prog);
		    perror("");
		    sleep(10);
		    croak(TRY_AGAIN);
		}
		mbp += wc;
		cnt -= wc;
		progress++;
	    }
	    progress++;
	    if (progress > (oldprogress + 20)) {
		getstatus = TRUE;
		oldprogress = progress;
	    }
	}
	if (cnt < 0) {
	    fprintf(stderr,"%s: error reading from stdin", prog);
	    perror("");
	    sleep(10);
	    croak(TRY_AGAIN);
	}
        close(fdinput);

	donefile:   /* Done sending the user's file */

	/* Send the PostScript end-of-job character */
	debugp((stderr,"%s: done sending\n",prog));
	STARTCRIT();		/* Only do end-of-job char once */
	VOIDC write(fdsend, eofbuf, 1);
	intstate = waiting;    /* Waiting for end of user job */
	ENDCRIT();		/* Only do end-of-job char once */
	if( gotsig & DIE_INT ) VOIDC kill( getpid(),SIGINT );

	VOIDC alarm(WAITALARM);
	while( !childdone ) pause();	/* Wait for listener to finish */
	VOIDC alarm(0);

	intstate = ending;

	if (VerboseLog) {
	    VOIDC time(&starttime);
	    fprintf(stderr,"%s: end - %s",prog,ctime(&starttime));
	    VOIDC fflush(stderr);
	}
	exit(0);

    }  /* END PARENT -- SENDER */
    else {/* START CHILD -- LISTENER */
	/* This process listens while the user job is sent.
	 * It communicates with the parent by signalling a SIGEMT, then
	 * exiting.  The exit code is the only form of communication implemented.
	 * The user job is aborted: A) If we get a status of "idle" back from
	 * the printer (see NextCh() routine), or B) If we get EOF back
	 * while reading the printer (somebody unplugged the comm line?),
	 * or C) If we receive a SIGEMT signal (parent wants us to die).
	 * This process ignores most signals except SIGEMT.
	 *
	 * A signal is used before exiting to make it easy for the parent to
	 * use wait() to get the child status, and for portability to System V.
	 */
	register int r;
	register ChStat i;
	char *outname;	/* file name for job output */
	int havejobout = FALSE; /* flag if jobout != stderr */

	intstate = child;
	ENDCRIT();		/* Child state change and setjmp() */
	prog = "pslisten";      /* Change our name */

	/* get jobout from environment if there, otherwise use stderr */
	if (((outname = envget("JOBOUTPUT")) == NULL)
	|| ((jobout = fopen(outname,"w")) == NULL)) {
	  jobout = stderr;
	}
	else havejobout = TRUE;

	/* listen for the user job */
	NextChInit();
	while (TRUE) {
	    r = getc(psin);
	    if ((r&0377) == 004) break;     /* Printer says end of job */
	    else if (r == EOF) {            /* Printer comm line died? */
		if( feof(psin) ) {
		    VOIDC kill( getppid(),SIGEMT );   /* Tell parent we exit */
		    exit(LIS_EOF);
		    }
		else {
		    if( errno != EINTR ) {	/* Got a random error */
			fprintf(stderr,"%s: Error" );
			perror("");
			fflush(stderr);
			exit(LIS_ERROR);
			}
		    }
		clearerr(psin);		/* Make it so we can read again */
		continue;
		}
	    if( (i=NextCh(r)) != ChOk ) {
		switch( (int)i ) {
		    case ChIdle:
			VOIDC kill( getppid(),SIGEMT );
			exit(LIS_IDLE);
		    case ChTimeout:
			VOIDC kill( getppid(),SIGEMT );
			exit(LIS_TIMEOUT);
		    }
		}
	}

	debugp((stderr,"%s: listener saw eof, done listening\n",prog));
	if (havejobout) VOIDC fclose(jobout);
	VOIDC fclose(psin);
	VOIDC kill( getppid(),SIGEMT );   /* Tell parent we are exiting */
	exit(LIS_NORMAL);
    }    /* END CHILD -- LISTENER */
    /* Can't get here */
#endif /* QMS */
#ifdef QMS
	/* Terminate the status gathering process */
	kill_statproc();
	if (VerboseLog) {
	    VOIDC time(&starttime);
	    fprintf(stderr,"%s: end - %s",prog,ctime(&starttime));
	    VOIDC fflush(stderr);
	}
	exit(0);
#endif /* QMS */
}

/* search backwards from p in start for patt */
private char *FindPattern(p, start, patt)
register char *p;
         char *start;
         char *patt;
{
    int patlen;
    register char c;

    patlen = strlen(patt);
    c = *patt;
    
    p -= patlen;
    for (; p >= start; p--) {
	if (c == *p && strncmp(p, patt, patlen) == 0) return(p);
    }
    return ((char *)NULL);
}

/* Static variables for NextCh() routine */
static char linebuf[BUFSIZ];
static char *cp;
static enum {normal, onep, twop, inmessage,
	     close1, close2, close3, close4} st;

/* Initialize the NextCh routine */
private NextChInit() {
    cp = linebuf;
    st = normal;
}

/* Overflowed the NextCh() buffer */
private NextChErr()
{
    *cp = '\0';
    fprintf(stderr,"%s: Status message too long: (%s)\n",prog,linebuf);
    VOIDC fflush(stderr);
    st = normal;
    cp = linebuf;
}

/* Put one character in the status line buffer. Called by NextCh() */
#define NextChChar(c) if( cp <= linebuf+BUFSIZ-2 ) *cp++ = c; else NextChErr();

/* Process a char from the printer.  This picks out and processes status
 * and PrinterError messages.  The printer status file is handled IN THIS
 * ROUTINE when status messages are encountered -- there is no way to
 * control how these are handled outside this routine.
 */
private ChStat NextCh(c)
register int c;
{
    char *match, *last;

    switch ((int)st) {
	case normal:
	    if (c == '%') {
		st = onep;
		cp = linebuf;
		NextChChar(c);
		break;
	    }
	    putc(c,jobout);
	    VOIDC fflush(jobout);
	    break;
	case onep:
	    if (c == '%') {
		st = twop;
		NextChChar(c);
		break;
	    }
	    putc('%',jobout);
	    putc(c,jobout);
	    VOIDC fflush(jobout);
	    st = normal;
	    break;
	case twop:
	    if (c == '[') {
		st = inmessage;
		NextChChar(c);
		break;
	    }
	    if (c == '%') {
		putc('%',jobout);
		VOIDC fflush(jobout);
		/* don't do anything to cp */
		break;
	    }
	    putc('%',jobout);
	    putc('%',jobout);
	    putc(c,jobout);
	    VOIDC fflush(jobout);
	    st = normal;
	    break;
	case inmessage:
	    NextChChar(c);
	    if (c == ']') st = close1;
	    break;
	case close1:
	    NextChChar(c);
	    switch (c) {
		case '%': st = close2; break;
		case ']': st = close1; break;
		case ' ': break;
		default: st = inmessage; break;
	    }
	    break;
	case close2:
	    NextChChar(c);
	    switch (c) {
		case '%': st = close3; break;
		case ']': st = close1; break;
		default: st = inmessage; break;
	    }
	    break;
	case close3:
	    NextChChar(c);
	    switch (c) {
		case '\r': st = close4; break;
		case ']': st = close1; break;
		default: st = inmessage; break;
	    }
	    break;
	case close4:
	    NextChChar(c);
	    switch(c) {
		case '\n': st = normal; break;
		case ']': st = close1; break;
		default: st = inmessage; break;
	    }
	    if (st == normal) {
		/* parse complete message */
		last = cp;
		*cp = '\0';
		debugp((stderr,">>%s",linebuf));
		if (match = FindPattern(cp, linebuf, " pagecount: ")) {
		    /* Do nothing */
		}
		else if (match = FindPattern(cp, linebuf, " PrinterError: ")) {
		    if (*(match-1) != ':') {
			fprintf(stderr,"%s",linebuf);
			VOIDC fflush(stderr);
			*(last-6) = 0;
			/* Status(match+15); */
		    }
		    else {
			last = strchr(match,';');
			*last = '\0';
			/* Status(match+15); */
		    }
		}
		else if (match = FindPattern(cp, linebuf, " status: ")) {
		    match += 9;
		    if (strncmp(match,"idle",4) == 0) { /* Printer is idle */
			return(ChIdle);
		    }
		    else {
			/* one of: busy, waiting, printing, initializing */
			/* clear status message */
			/* RestoreStatus(); */
		    }
		}
		/* WARNING: Must NOT match "PrinterError: timeout" */
		else if (match = FindPattern(cp, linebuf, " Error: timeout")) {
		    return(ChTimeout);
		}
		else {
		    /* message not for us */
		    fprintf(jobout,"%s",linebuf);
		    VOIDC fflush(jobout);
		    st = normal;
		    break;
		}
	    }
	    break;
	default:
	    fprintf(stderr,"bad case;\n");
    }
    return(ChOk);
}

/* Child has exited.  If there is a problem, this routine causes the program
 * to abort.  Otherwise, the routine just returns.
 */
private VOID listenexit(exitval)
int exitval;     /* Status returned by the child */
{
    stat_buf exitstatus;    /* Kludge to easily get at fields */

    debugp((stderr,"%s: Listener return status: 0x%x\n",prog,exitval));
    exitstatus.w_termsig = exitval & 0xFF;  /* Low order 8 bits */
    exitstatus.w_retcode = (exitval>>8) & 0xFF;  /* High order 8 bits of 16 */
    if( exitstatus.w_termsig != 0 ) {   /* Some signal got the child */
	fprintf(stderr,"%s: Error: Listener process killed using signal=%d\n",
	    prog,exitstatus.w_termsig);
	VOIDC fflush(stderr);
	croak(TRY_AGAIN);
	}
    else switch( exitstatus.w_retcode ) {   /* Depends on child's exit status */
	case LIS_IDLE:    /* Printer went idle during job. Probably rebooted. */
	    fprintf(stderr,"%s: ERROR: printer is idle. Giving up!\n",prog);
	    VOIDC fflush(stderr);
	    croak(THROW_AWAY);
	case LIS_TIMEOUT:    /* Printer timed out during job. System loaded? */
	    fprintf(stderr,"%s: ERROR: printer timed out. Trying again.\n",prog);
	    VOIDC fflush(stderr);
	    croak(TRY_AGAIN);
	case LIS_EOF:     /* Comm line down.  Somebody unplugged the printer? */
	    fprintf(stderr,
		    "%s: unexpected EOF from printer (listening)!\n",prog);
	    VOIDC fflush(stderr);
	    sleep(10);
	    croak(TRY_AGAIN);
	case LIS_ERROR:		   /* Listener died */
	    fprintf(stderr,
		    "%s: unrecoverable error from printer (listening)!\n",prog);
	    VOIDC fflush(stderr);
	    sleep(30);
	    croak(TRY_AGAIN);
	case LIS_NORMAL:           /* Normal exit. Keep going */
	case LIS_DIE:              /* Parent said to die. */
	default:
	    break;
	}
}

/* Reap the children.  This returns when all children are dead.
 * This routine ASSUMES we are dying.
 */
private VOID reapchildren() {
    intstate = croaking;    /* OK -- we are dying */
    VOIDC setjmp(croaklabel);    /* Get back here when we get an alarm */
    VOIDC alarm(CROAKALARM);
    if( cpid != 0 ) VOIDC kill(cpid,SIGEMT);  /* This kills listener */
    while (wait((int *) 0) > 0);
    VOIDC alarm(0);         /* No more alarms */
    if (VerboseLog) {
	VOIDC time(&starttime);
	fprintf(stderr,"%s: end - %s",prog,ctime(&starttime));
	VOIDC fflush(stderr);
    }
}

/* Reap our children and die */
private VOID croak(exitcode)
int exitcode;
{
#ifdef QMS
    if (copts.cpid != getpid()) {
	kill_statproc();
    }
#else /* QMS */
    VOIDC reapchildren();
#endif /* QMS */    
    exit(exitcode);
}

/* Exit and printer system error message with perror() */
private VOID myexit1(progname,exitcode)
char  *progname;
int    exitcode;
{
    saveerror = errno;
    VOIDC reapchildren();
    errno = saveerror;
    pexit(progname,exitcode);
}

/* Exit and print system error message with perror() and printer a reason */
private VOID myexit2(progname,reason,exitcode)
char  *progname;
char  *reason;
int    exitcode;
{
    saveerror = errno;
    VOIDC reapchildren();
    errno = saveerror;
    pexit2(progname,reason,exitcode);
}

/* Close down without having done anything much */
private VOID closedown() {
    fprintf(stderr,"%s: abort (during startup)\n",prog);
    VOIDC fflush(stderr);
    croak(THROW_AWAY);
}

/* On receipt of a job abort, we normally get the printer to abort the job
 * and still do the normal final banner page and accounting entry.  This
 * requires the printer to respond correctly.  If the printer is busted, it
 * will not respond.  In order to prevent this process from sitting around
 * until a working printer is connected to the printer port, this routine
 * gets called went it is deemed that the printer will not respond...
 */
private VOID dieanyway() {
    fprintf(stderr,"%s: No response from printer after abort.  Giving up!\n",
	prog);
    croak(THROW_AWAY);
    }

/* Abort the current job running on the printer */
private VOID abortjob() {
    if( jobaborted ) return;		/* Don't repeat work */
    alarm(ABORTALARM);               /* Limit time for printer to respond */
    jobaborted = TRUE;

    if( resetprt() || write(fdsend,abortbuf,1) != 1 ) {
	fprintf(stderr, "%s: ioctl error (abort job)", prog);
	perror("");
    }
}
/* Got an EMT signal */
private VOID GotEmtSig(sig)
    int sig;
{
    debugp((stderr,"%s: Got SIGEMT signal\n",prog));
    VOIDC signal(SIGEMT, GotEmtSig);
    /* This signal does not need to obey the critical region rules */
    switch( (int)intstate ) {
	case sending:
	case waiting:
	    while( (wpid=wait(&status)) > 0 )
		if(wpid==cpid) {
		    cpid = 0;
		    listenexit(status);
		    break;
		    }
	    if( intstate == sending ) {
		fprintf(stderr,"WARNING: Check spooled PostScript for control characters.\n");
		fflush(stderr);
		}
	    childdone = TRUE;    /* Child exited somehow */
	    break;
	case child:
	    VOIDC kill( getppid(),SIGEMT );   /* Tell parent we are exiting */
	    exit(LIS_DIE);    /* Parent says die -- we exit early */
	default:
	    break; /* Ignore it */
        }
}


/* Got an alarm signal. */
private VOID GotAlarmSig(sig)
    int sig;
{
    char mybuf[BUFSIZ];

    debugp((stderr,"%s: Got alarm signal %d %d %d %d\n",
	prog,intstate,oldprogress,progress,getstatus));
    VOIDC signal(SIGALRM, GotAlarmSig);  /* Re-instate the alarm */
    if(flagsig) {
	gotsig |= ALARM_INT;  /* We just say we got one and return */
	return;
	}
    switch( (int)intstate ) {
	case syncstart:
	case synclast:
	    if( jobaborted ) dieanyway();   /* If already aborted, just croak */
	    longjmp(synclabel,1);
	case sending:
	    if( progress == oldprogress ) { /* Nothing written since last time */
		getstatus = TRUE;
		}
	    else {
		oldprogress = progress;
		getstatus = FALSE;
		}
	    /* VOIDC alarm(SENDALARM); /* reset the alarm and return */
	    break;
	case waiting:
	    if( jobaborted ) dieanyway();   /* If already aborted, just croak */
	    VOIDC write(fdsend, statusbuf, 1);
	    if( kill(cpid,0) < 0 ) childdone = TRUE;  /* Missed exit somehow */
	    VOIDC alarm(WAITALARM); /* reset the alarm and return */
	    break;
	case lastpart:
	    if( jobaborted ) dieanyway();   /* If already aborted, just croak */
	    break;
	case croaking:
	    longjmp(croaklabel,1);
	case child:
	    if( kill(getppid(),0) < 0 ) {  /* Missed death signal from parent */
		fprintf(stderr,
		    "%s: Error: Parent exited without signalling child\n",prog);
		VOIDC fflush(stderr);
		exit(LIS_NOPARENT);
		}
	    alarm(CHILDWAIT);
	    break;
	default:
	    break; /* Ignore it */
        }
}

/* Got a "die" signal, like SIGINT */
private VOID GotDieSig(sig) int sig; {
    debugp((stderr,"%s: Got 'die' signal=%d\n",prog,sig));
    VOIDC signal(sig, GotDieSig);
    if(flagsig) {
	gotsig |= DIE_INT;  /* We just say we got one and return */
	return;
	}
    switch( (int)intstate ) {
	case init:
	    VOIDC closedown();
	case syncstart:
	    fprintf(stderr,"%s: abort (start communications)\n",prog);
	    VOIDC fflush(stderr);
	    abortjob();
	    VOIDC write(fdsend, eofbuf, 1);
	    croak(THROW_AWAY);
	case sending:
	    fprintf(stderr,"%s: abort (sending job)\n",prog);
	    VOIDC fflush(stderr);
	    abortjob();
	    longjmp(sendlabel,1);
	case waiting:
	    if( !jobaborted ) {
	        fprintf(stderr,"%s: abort (waiting for job end)\n",prog);
	        VOIDC fflush(stderr);
		}
	    abortjob();
	    break;
	case lastpart:
	case synclast:
	    if( jobaborted ) dieanyway();   /* If already aborted, just croak */
	    fprintf(stderr,"%s: abort (post-job processing)\n",prog);
	    VOIDC fflush(stderr);
	    alarm(ABORTALARM);
	    jobaborted = TRUE;
	    break;
	case child:
	    alarm(CHILDWAIT);   /* Wait a while, then see if parent is alive */
	    break;
	default:
	    break; /* Ignore it */
        }
}

/* Open the printer for listening (if it isn't open yet).
 */
private VOID openprtread() {
#ifdef BRIDGE
    if (psin == NULL && (psin=fdopen(fdsend, "r")) == NULL) {
	myexit1(prog, THROW_AWAY);
    }
#else
    if (psin == NULL && (psin = fdopen(fdlisten, "r")) == NULL) {
	myexit1(prog, THROW_AWAY);
    }
#endif /* BRIDGE */
}

/* Flush the I/O queues for the printer, and restart output to the
 * printer if it has been XOFF'ed.
 * Returns correct error stuff for perror() if a system call fails.
 * NOTE: If this routine does nothing, one can get into a state where
 * syncprinter() loops forever reading OLD responses to the acct message.
 */
private int resetprt() {
#ifdef BRIDGE
    static struct timeval t = {0,0};    /* Zero time */
    int  rmask;				/* Hope fdsend bit fits in here... */
    int c;

    rmask = 1<<fdsend;
    debugp((stderr,"%s: Start reset\n",prog));
    while( select(sizeof(int)*8,&rmask,(int*)0,(int*)0,&t) == 1 ) {
        debugp((stderr,"%s: Read another buffer cnt=%d rmask=0x%x\n",prog,psin->_cnt,rmask));
	while( psin->_cnt > 0 ) c = getc(psin);  /* Empty the buffer */
	c = getc(psin);   /* Fill the buffer */
	if (c == EOF) {
	    if( feof(psin) ) {
	        flagsig = 1;
	        fprintf(stderr,"%s: unexpected EOF from printer (resetprinter)!\n",
		        prog);
	        VOIDC fflush(stderr);
	        sleep(10);
	        croak(TRY_AGAIN);
	        }
	    else {
	        if( errno != EINTR ) {	/* Got a random error */
		    fprintf(stderr,"%s: Error" );
		    perror("");
		    fflush(stderr);
		    sleep(30);
		    croak(TRY_AGAIN);
		    }
	        }
	    clearerr(psin);		/* Make it so we can read again */
	    continue;
	    }
	}
    debugp((stderr,"%s: Read last buffer cnt=%d rmask=0x%x\n",prog,psin->_cnt,rmask));
    while( psin->_cnt > 0 ) c = getc(psin);  /* Empty the buffer */
#else
    int c;

    VOIDC openprtread();    /* Re-open the printer */
    if (ioctl(fdsend,TCFLSH,2) || ioctl(fdsend,TCXONC,1) )
	return(-1);
    while( psin->_cnt > 0 ) c = getc(psin);  /* Empty the buffer */
#endif /* BRIDGE */

    return(0);
}

/* Synchronize the input and output of the printer.
 * We use the accounting message, and include our process ID and
 * a sequence number.  If the output doesn't match what we expect, we
 * sleep a bit, flush the terminal buffers, and try again.
 * WARNING: Make sure there are no pending alarms before calling this routine.
 */
private VOID syncprinter(pagecount)
long    *pagecount;        /* The current page count in the printer */
{
    static int synccount=0; /* Unique number for acct output.  This is static
			       so ALL calls will produce different output */
    unsigned int sleeptime; /* Number of seconds to sleep */
    char *mp;         /* Current pointer into mybuf */
    int gotpid;       /* The process ID returned from the printer */
    int gotnum;       /* The synccount returned from the printer */
    register int r;   /* Place to put the chars we get */
    int sc;           /* Results from sscanf() */
    int errcnt;       /* Number of errors we have output */
    char mybuf[BUFSIZ];

    VOIDC time(&starttime);    /* Get current time for status warnings */
    errcnt = 0;
    sleeptime = 2;    /* Initial sleep interval */
    jobout = stderr;  /* Write extra stuff to the log file */
    NextChInit();
    openprtread();    /* Open the printer for reading */
    while(TRUE) {
	synccount++;
	if( setjmp(synclabel) ) goto tryagain;   /* Got an alarm */
	VOIDC alarm(SYNCALARM); /* schedule an alarm/timeout */
	VOIDC sprintf(mybuf, getpages, mpid, synccount, "\004");
	VOIDC write(fdsend, mybuf, strlen(mybuf)); /* Send program */
        mp = mybuf; *mp = '\0';
	while (TRUE) {  /* Listen for return string */
	    r = getc(psin);
	    if (r == EOF) {
		if( feof(psin) ) {
		    flagsig = 1;
		    fprintf(stderr,
			"%s: unexpected EOF from printer (sync)!\n",prog);
	            VOIDC fflush(stderr);
	            sleep(10);
	            croak(TRY_AGAIN);
		    }
		else {
		    if( errno != EINTR ) {	/* Got a random error */
			fprintf(stderr,"%s: Error" );
			perror("");
			fflush(stderr);
		        sleep(10);
		        croak(TRY_AGAIN);
			}
		    }
		clearerr(psin);		/* Make it so we can read again */
		continue;
	        }
	    if ((r&0377) == 004) break; /* Echoed back end of job */
	    VOIDC NextCh(r);   /* Ignore return values */
	    if( mp >= mybuf+sizeof(mybuf)-1 ) {  /* Overflow? */
		mp = '\0';
		strcpy( mybuf,mybuf+sizeof(mybuf)/2 );
		mp = mybuf+sizeof(mybuf)/2;
		}
	    *mp++ = r;
	    }
	*mp = '\0';
	debugp((stderr,"%s: sync reply (%s)\n",prog,mybuf));
	if (mp = FindPattern(mp, mybuf, "%%[ pagecount: ")) {
	    sc = sscanf(mp, "%%%%[ pagecount: %ld, %d %d ]%%%%\r",
		pagecount,&gotpid,&gotnum);
	    }
	if( mp!=NULL && sc==3 && gotpid==mpid && gotnum==synccount ) break;
	errcnt++;
	if( errcnt <= 3 || errcnt%10==0 ) {   /* Only give a few errors */
	    fprintf(stderr, "%s: printer sync problem [%d] (%s)\n",
		prog,synccount,mybuf);
	    VOIDC fflush(stderr);
	    }
	VOIDC alarm(0);    /* Don't want alarms anymore */
	sleep(sleeptime);    /* Sleep for a while */
	sleeptime *= 2;      /* Wait for longer next time */
	if( sleeptime > MAXSLEEP ) {
	    if( jobaborted ) dieanyway();   /* If already aborted, just croak */
	    sleeptime = MAXSLEEP;
	    }
	tryagain:
	if(resetprt()) {      /* Reset the printer I/O queues */
	    fprintf(stderr, "%s: ioctl error (sync)", prog);
	    perror("");
	    }
	}
    VOIDC alarm(0);    /* Make sure we don't longjmp() after exiting */
}

#ifdef QMS
xfer_data(tpfd, copts, rfd)
    int tpfd;
    struct command_opt *copts;
    int rfd;
{
    int nw, nr, w, tobewritten;
    char buf[BUFSIZE], *bufp;
    
    
    while (( nr = read(rfd, buf, BUFSIZE)) !=  0) {
	if (nr < 0) {
	    fprintf(stderr, "read of input file failed\n");
	    perror("");
	    return(-1);	
	}	
	
	nw = 0;
	bufp = buf;
	tobewritten = nr;
	
	while (nw != nr) {
	    if ((w = t_snd(tpfd, bufp, tobewritten, 0)) < 0) {
		t_error("send across tcp TLI failed\n");
		return(-1);	
	    }
	    nw +=w;
	    bufp += w;
	    tobewritten -= w;
	    debugp((stderr,"Numbytes written to the printer %d\n", nw));
	}
    }
}


set_udptli()
{
    int ufd;

    if ((ufd = t_open(UDP_PROVIDER, O_RDWR, (struct t_info *)0)) < 0) {
	t_error("Failed to open connection to transport provider\n");
        croak(TRYAGAIN);
        }

    debugp((stderr,"UDP file descriptor = %d\n", ufd));
    /*
     * Since we are the 'active' partner in the communication, we don't
     * really care what address is bound to our transport endpoint, tpfd;
     * happy with whatever Transport Provider provides. Same holds true
     * for maximum number of connections.
     */

    if (t_bind(ufd, (struct t_bind *)0, (struct t_bind *)0) < 0) {
	t_error("Failed to bind address to endpoint\n");
	croak(TRYAGAIN);
    }
    return(ufd);
}
	
/*
 * Get printer status. This is an infinite loop; if no replies
 * are being received from the printer, the procedure keeps on sending
 * requests in the hope that a reply will be received. It would be trivial,
 * however, to add logic to terminate the procedure if no replies are
 * received after a long timeout, say 5 minutes.
 * Use of select() enables low level timeout mechanism to be enforced and so,
 * in theory, a network read, i.e. recvfrom(), should never block.
 * A reply packet from the printer indicates if the printer is busy or not.
 * If it is, the procedure, after RETRY counts, sleeps for SLEEPTIME before
 * sending the request packet again. The priming select() outside the 
 * inner while loop (RETRY loop), checks to see if any old packets have
 * finally found their destination and discards them.
 */
get_printstat(ufd, statflag, statbuf)
    int ufd;
    int statflag;
    char *statbuf;
{
    int nfound, nfd, rcnt, rflags, pstatus, recvlen, age;
    char *strptr;
    struct printer_pkt  preq, *ps; 
    struct t_unitdata sudata, *rudata;
    struct t_uderr *ruderr;
    char  reply[MAX_REPLY_SIZE];
    struct pollfd fds[1];
    
    
    /* 
     * Allocate a unitdata structure and its address field.
     */
    
    if ((rudata = (struct t_unitdata *)t_alloc(ufd, T_UNITDATA, T_ADDR))
	==  (struct t_unitdata *) NULL){
	t_error("Failed to allocate Rcv unitdata\n");
	croak(TRYAGAIN);
    }
    
    /* Allocate a uderr structure and its buffers.
     */
    if ((ruderr = (struct t_uderr *)t_alloc(ufd, T_UDERROR, T_ALL))
	==  (struct t_uderr *) NULL){
	t_error("Failed to allocate uderror structure\n");
	croak(TRYAGAIN);
    }
    
    /* Flush out past reply packets, if any */
    
    while (TRUE) {
	debugp((stderr,"UFD = %d\n", ufd));
	
	fds[0].events = fds[0].revents = 0;
	fds[0].fd = ufd;
	fds[0].events = fds[0].events | POLLIN;
	
	nfd = 1;
	
	nfound = poll(fds, nfd, POLL_TIMEOUT);
	
	if ((nfound < 0) || (nfound > 1)) {
	    fprintf(stderr, "Poll failed\n");
	    perror("");
	    croak(TRYAGAIN); 
	}
	else if (nfound == 1) {
	    debugp((stderr,"Found an old reply packet\n"));
	    
	    rudata->opt.maxlen = 0;
	    rudata->udata.maxlen = MAX_REPLY_SIZE;
	    rudata->udata.buf = reply;
	    
	    if (t_rcvudata(ufd, rudata, &rflags) < 0) { 
		if (t_errno == TLOOK) {
		    if (t_rcvuderr(ufd, ruderr) < 0) {
			fprintf(stderr, "t_rcvuderr failed\n");
			perror("");
			croak(TRYAGAIN);
		    }
		    fprintf(stderr,
			    "Bad datagram: Error=%d\n", ruderr->error);
		    perror("");
		    croak(TRYAGAIN);
		}
		else {
		    t_error("Failed to receive udata\n");
		    croak(TRYAGAIN);
		}
	    }
	}
	else {
	    debugp((stderr,"Timed out: No[more] past reply packets\n"));
	    break;
	}
    }
    
    pstatus = 0;
    rcnt = NUMRETRY;
    while (rcnt--) {
	
	/*
	 * Initialize udata to be sent to the printer.
	 */
	
	sudata.addr.maxlen = sizeof(printaddr);
	sudata.addr.len = sizeof(printaddr);
	sudata.addr.buf = (char *)&printaddr;
	
	sudata.opt.maxlen = 0;
	sudata.opt.len = 0;
	sudata.opt.buf = (char *)0;
	
	memset((void *)&preq, 0, sizeof(preq));
	sudata.udata.maxlen = sizeof(preq);
	sudata.udata.len = sizeof(preq);
	sudata.udata.buf = (char *) &preq;
	
	
	if (t_sndudata(ufd, &sudata) < 0) {
	    t_error("Failed to send udp data \n");
	    croak(TRYAGAIN);
	}
	
	/*
	 * Issue a select and see if you get a packet within the timeout
	 * interval. 
	 */
	
	
	debugp((stderr,"UFD = %d\n", ufd));
	
	fds[0].events = fds[0].revents = 0;
	fds[0].fd = ufd;
	fds[0].events = fds[0].events | POLLIN;
	
	nfd = 1;
	
	if ((nfound = poll(fds, nfd, POLL_TIMEOUT)) < 0) {
	    fprintf(stderr, "Poll failed\n");
	    perror("");
	    croak(TRYAGAIN);
	}
	
	if ((nfound < 0) || (nfound > 1)) {
	    fprintf(stderr, "Poll failed\n");
	    perror("");
	    croak(TRYAGAIN); 
	}
	else if (nfound == 0) {
	    debugp((stderr,"Timed out: Reply packet not received from printer\n"));
	    continue; 
	}
	else {
	    debugp((stderr,"Reply packet received from printer\n"));
	}
	
	if (fds[0].revents & POLLIN) { 
	    debugp((stderr,"Poll:: Data available on UDP streams\n"));
	}
	
	rudata->opt.maxlen = 0;
	rudata->udata.maxlen = MAX_REPLY_SIZE;
	rudata->udata.buf = reply;
	
	if (t_rcvudata(ufd, rudata, &rflags) < 0) {
	    if (t_errno == TLOOK) {
		if (t_rcvuderr(ufd, ruderr) < 0) {
		    fprintf(stderr, "t_rcvuderr failed\n");
		    perror("");
		    croak(TRYAGAIN);
		}
		fprintf(stderr, "Bad datagram: Error=%d\n", ruderr->error);
		perror("");
		croak(TRYAGAIN);
	    }
	    else {
		t_error("Failed to receive udata\n");
		croak(TRYAGAIN);
	    }
	}
	
	
	ps = (struct printer_pkt *)rudata->udata.buf;
	
	debugp((stderr,"Printer Status data:\n\t pr_status:0x%x\n\t pr_acceptmask:0x%x\n",
		ps->pr_status, ntohl(ps->pr_acceptmask)));
	
	pstatus = (!(ps->pr_status & 0x0f)) &
	    (ntohl(ps->pr_acceptmask) & 0x01);
	debugp((stderr,"print_status=%x\n", pstatus));
	
	
#ifdef BDEBUG
	if (ps->pr_status & 0x02) 
	    debugp((stderr,"Printer lacks paper\n"));
	
	if (ps->pr_status & 0x04)
	    debugp((stderr,"Printer has paper jam\n"));
	
	if (ps->pr_status & 0x08)
	    debugp((stderr,"Printer lacks consumables (other than paper)\n"));
	
	if (ps->pr_status & 0x10)
	    debugp((stderr,"Job in progress at the printer\n"));
	
	if (pstatus != 0) {
	    debugp((stderr,"...printer ready to accept data\n"));
	}
	else {
	    debugp((stderr,"....printer busy or has problems\n"));
	}
#endif
	
	age = ntohl(ps->pr_age);
	
	strptr = (&rudata->udata.buf[0] + ntohs(ps->pr_stroffset));
	/* Make sure string is null terminated */
	*(strptr + ntohs(ps->pr_strlength)) = 0x00;
	
	if (statflag) {
	    sprintf(statbuf, "%s (for %02d:%02d:%02d)",
		    strptr, age/3600, (age/60)%60, age%60);
	}
	
	return(pstatus);
	
    } /* end rcnt while */
    
    debugp((stderr,"....printer not responding \n"));
    
    if (statflag) {
	sprintf(statbuf, "Printer not responding to status request\n");
    }
    
    return(0);
}


init_data(copts)
    struct command_opt *copts;
{
    copts->input_stdin = 1;
}

/*
 * Fork a child process to gather printer status while a job is active.
 */

spawn_statproc()
{
    int pid, ufd;
    
    if ((pid = fork()) < 0) {
	fprintf(stderr, "Failed to spawn status gathering process\n");
	perror("");
	exit(1);
    }
    if (pid == 0) { /* child */
	/*
	 * Set up UDP socket.
	 */
	if ((ufd = set_udptli()) < 0) {
	    printf("Failed to open UDP connection for child (status) process\n");
	    exit(1);
	}
	update_status(ufd);
    }
    else {
	return(pid);
    }
}

/*
 * The core of the child process. Continuously queries and logs the 
 * printer status.
 */
update_status(ufd)
    int ufd;
{
    int i;
    char statstring[650];
    char statbuf[512];
    
    sleep(STATUSINTERVAL);
    
    i = 100;
    while (TRUE) {
	get_printstat(ufd, 1, statbuf);
	sprintf(statstring, "Job in progress--- Status: %s", statbuf);
/*
	write_status(i++, statstring);
*/
	sleep(STATUSINTERVAL);
    }
}

/*
 * Terminate the child process.
 */
kill_statproc()
{
    if (kill(copts.cpid, SIGQUIT) < 0) {
	if (errno != ESRCH) {
	    fprintf(stderr, "Failed to terminate status gathering process\n");
	    perror("");
	    exit(1);
	}
    }
    while (wait(0) != copts.cpid);
}
#endif /* QMS */
