/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:ibm3812pp.c 12.0$ */
/* $ACIS:ibm3812pp.c 12.0$ */
/* $Source: /ibm/acis/usr/src/usr.lib/lpr/filters/RCS/ibm3812pp.c,v $ */

#ifndef lint
static char *rcsid = "$Header:ibm3812pp.c 12.0$";
#endif

/*
 * ibm3812pp - Interfaces to IBM 3812 Pageprinter via asynch i/o mode.
 *
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <netinet/in.h>
#include <sgtty.h>
#include <errno.h>
#include <machineio/apio.h>
#include "printer3812.h"

/* Status of the print server... */
#define RUNNING  	0x00	/* Print server is running */
#define NEED_TO_RESET	0x01	/* Print server doesn't know printer status */
#define	CLEAR_3812_DONE	0x02	/* The 3812 has been CLEARed. */
#define SHUTDOWN 	0x03	/* Print server is shutting down printer */

#define PMP  'P'    /* we believe we are about to print PMP data */
#define TEXT 'T'    /* we believe we are about to print text */

#define DEFSTATF "/usr/spool/ppd/status3812"  /* default status file */
#define	TIME_STAMP_EVERY	20	/* Time stamp every N seconds */

static char  *server_status[] = {   "Running",
				    "Need to reset",
				    "Reset complete",
				    "Shutdown"
				};
static int current_ir[IRSTATUS_CNT];

static char	*statusfile = DEFSTATF;
 
extern  errno;

int cleanup();       /* routines to cleanup on errors */

static int status;
static struct sgttyb sg;
static int p;        /* file descriptor for the printer */
static int f;        /* file descriptor for the socket */
static int s;        /* file descriptor for the active socket connection */
static char *sock_name;
static char *dev;
static char c_state;
static char *baudrate;
static print_3812_flags err_info;
static int debug;
static FILE *sf;		/* file id for status file */

/*
 * Stuff for handling transfer of data
 */
static char	cbuf[IBM3812_BUFSIZ];	/* data buffer */

main(argc, argv)
int argc;
char **argv;
{
	int i, len, socket_fds;
	struct sockaddr_un server;
        register char *cp;
	struct timeval timeout; 

	setlinebuf(stderr);
	debug = 0;
	sock_name = "/usr/spool/ppd/pp3812";
	dev  = "/dev/pp";
	baudrate = "19200";
	c_state = TEXT;
	while (--argc > 0) {
		if (*(cp = *++argv) == '-') {
			switch (cp[1]) {
			case 's':
				argc--;
				sock_name = *++argv;
				break;
			case 'b':
				argc--; 
				baudrate = *++argv;
				break;
			case 'd':
				debug++;
				break;
			case 'S':
				argc--;
				statusfile = *++argv;
				break;
			case 'D':
				argc--;
				dev = *++argv;
				break;
			}
		} else {
			errout("unknown parameter %s", cp);
			exit(1);
		}
	}

	if (debug <= 1) {
		/*
		 * Set up standard environment by detaching from the parent.
		 */
		if (fork())
			exit(0);
		(void) close(0);
		(void) close(1);
		(void) open("/dev/null", O_RDONLY);
		(void) open("/dev/null", O_WRONLY);
		if (isatty(fileno(stderr))) {
			(void) close(2);
			if ((f=open("/dev/console",O_WRONLY))!=fileno(stderr))
			{
				if (dup2(f, fileno(stderr)) == -1) 
				{
					errout("Unable to open /dev/console as stderr\n");
					exit(1);
				} else {    
					(void) close(f);
				}
			}
		}
		f = open("/dev/tty", 2);
		if (f > 0) {
			ioctl(f, TIOCNOTTY, 0);
			(void) close(f);
		}
		setpgrp(0,getpid());
	}

	/*
	 * open the printer for reading and writing.
	 */
	open3812();   
	/*
	 * The printer is our way of serializing.  Given that it has been 
	 * opened, we are the only process running to this printer.  Go
	 * ahead and start processing incoming data.
	 */

	/*
	 * Open the status log file for write.  First, we unlink it, 
	 * in case it happens to be owned by someone else.
	 */
	if (unlink(statusfile) == -1) 
	{
		if (errno != ENOENT) 
		{
		        logerr("unlinking status file");
			exit(1);
		}
	}
	
	if ((sf = fopen(statusfile,"w")) == NULL) 
	{
		logerr("status file");
		exit(1);
	}

	set_status(NEED_TO_RESET);

	/*
	 * Startup socket and bind to "sock_name".  First, 
	 * remove socket so that we can make sure bind will succeed... 
	 */
	if (unlink(sock_name) == -1) 
	{
	        if (errno != ENOENT) 
		{
		        logerr("unlinking socket");
			exit(1);
		}
	}
	print_debug("Starting socket name %s, on dev %s.\n", sock_name, dev);
	if ((f = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 
	{
		logerr("socket");
		exit(1);
	}
	server.sun_family = AF_UNIX;
	strcpy(server.sun_path, sock_name);
	len = sizeof(server.sun_family) + strlen(server.sun_path);
	if ( bind(f, &server, len) < 0)
	{
		logerr("binding sock stream");
		exit(1);
	}

	print_debug("ibm3812pp: socket descriptor (%d): %d, %s\n",
		     f, server.sun_family, server.sun_path );

	/*
	 * Listen on the socket; queue up to 1 connection.
	 */
	if (listen(f,5) <0)   {
		logerr("listen error");
		closesocket();
		exit(1);		
	}
	socket_fds = 1 << f; 	/* set read mask */

	/* Set up signal handling.
	 *   - Call cleanup for all signals except SIGPIPE.
	 *   - Ignore SIGPIPE (which may occur when socket is closed).
	 */
	for (i = 0; i < NSIG; signal(i, cleanup), i++);
	signal(SIGPIPE,SIG_IGN); 

	/*
	 * Main loop: accept, do a request, continue.
	 */
	for (;;) {
		int nfound, readfds;

		readfds = socket_fds;
		switch (status) {
		case NEED_TO_RESET:
			/*  If line went down, then we need to send a
			 *  reset to the printer to maintain integrity.
			 */
			sendreset(); 
			set_status(RUNNING);
			break;
		case CLEAR_3812_DONE:
			/* flush any knowledge of printer state
			 * (ie: loaded fonts, etc.)
			 */
			set_status(RUNNING);
			break;
		case RUNNING:
			break;
		case SHUTDOWN: /* don't do an accept until printer is reset */
	    		readfds = 0;
			break;
		}

		timeout.tv_sec = 20;
		timeout.tv_usec = 0;
		errno = 0;

		nfound = select(f + 1,&readfds,0,0,&timeout);
		print_debug("nfound %d errno %d\n", nfound, errno);
		if (nfound < 0) {
			if (errno != EINTR) {
				logerr("select");
				closesocket();
				exit(1);    
			}
			continue;
		}
			

		if (nfound == 0) /* select timed out	      */
		{		 /* check if printer has info */
		        (void) read_prt_status();
	        } 
		else
		{ 
			if (readfds & socket_fds) 
			{
				if ((s = accept(f, 0, 0)) < 0) 
				{
					if (errno == EINTR)
						continue;
		    			logerr("accept");
					continue;
				}
				(void) doit();
				errout("End of job\n");
				(void) close(s);
			}
		}
	}
}


/* ----------------------handle signal to stop -----------------*/
static int
cleanup()
{
      	set_status(SHUTDOWN);
      	errout("About to shutdown 3812 Print Server %s \n",sock_name);
      	closesocket();
      	close(p);		/* close the printer      */
      	exit(-1);
}

#define PMP_ESC_LSB	3
#define PMP_ESC_MSB	(PMP_ESC_LSB+1)
static char pmp_esc[8] = "\33[C";	/* pmp escape data - ] */

static int
doit()
{
	register int rc;
	int n;
        char *readbuf;
	int readsize,i;

	/* PMP initialization data:
	 *
	 * In order to execute PMP.init (a macro), we need
	 * to go into PMP mode, we then ask for PMP.init to be
	 * executed.
	 *
	 */
	static char pmp_init[] = { 
		'\33', '[', 'C', 	/* PMP_ESCAPE_SEQUENCE */
		'\12', '\0', 		/* Length:  LSB   MSB  */
		'\373', 		/* x'FB' -- EXECUTE_LIBRARY_MACRO */
		'\10',			/* length of macro name */
		'P', 'M', 'P', '.', 'i', 'n', 'i', 't' };  /* macro name */


        /* read print request information*/ 
	readbuf = (char *)&err_info;
	if ((n = read(s,readbuf,sizeof(print_3812_flags))) <= 0 ) {
        	errout("Startup msg not received: %d, errno:%d\n", n, errno);
		return(0);
	}
	if ((n = write(s,readbuf,sizeof(print_3812_flags))) <= 0 ) {
        	errout("Startup msg not returned: %d, errno:%d\n", n, errno);
		return(0);
	}
	errout("Start new document\n");
	errout("User: %s Node: %s\n", err_info.username, err_info.hostname);
	/*
	 * if new file is text and old state was pmp then reset
	 * printer.
	 */
        readbuf = cbuf;
	readsize = IBM3812_BUFSIZ;

	switch (err_info.type) {
	default:
		/* NOTE:  We are going to assume that if this
		 * is not a PMP file, then it is a TEXT file.
		 * We do this to try to accept as many print jobs
		 * as possible.  The alternative would be to reject
		 * jobs which did not go through the correct filter.
		 */
		/* fall through */
	case 1:	/* TEXT file */
		if (c_state == PMP) {
	   	/* if transition from PMP to TEXT -
	    	 * initialize printer
	    	 */
			bcopy(pmp_init, cbuf, sizeof pmp_init);
			n = sizeof pmp_init;
			if ((rc = write3812(n)) !=0 ) {
				return (rc);
			}
			c_state = TEXT;
		}
		break;

	case 2:	/* PMP file */
		readbuf = cbuf + 5;    /* leave 5 bytes for the pmp escape*/
		readsize = IBM3812_BUFSIZ -5;
		c_state = PMP;
		break;
	}

	/*
	 *      Keep reading input until finished, or until the printer status
	 *      changes to something other than running.
	 */
	rc = 0;
	while (rc == 0)  {
		if (status != RUNNING) {
			return(status);
		}
		if ((n = read(s, readbuf, readsize)) <= 0) {
			print_debug("Read result: %d\n",n);
			if (n < 0) {
				logerr("error reading on socket");
				return(-1);
			}
			return(0);
		}
		print_debug("Input Read: n:%d\n",n);
		
		if (c_state == PMP) 	/* PMP data must have pmp escape */
		{
			bcopy(pmp_esc, cbuf, 3);
			cbuf[PMP_ESC_LSB] = n;
			cbuf[PMP_ESC_MSB] = n >> 8;
			if (debug) 
			{
				print_debug("PMP: ");
				for (i=0;i<10;i++) 
				{
					print_debug("%2x",cbuf[i]);
				}
				print_debug("\n");
			}
			n = n+5;         
		}
		rc = write3812(n);
	}

	return(rc);             /* error in write3812  */
}


/* 
 * Write to the 3812 on the file descriptor `p`   
 * Check the return code.
 * If all bytes were not written then do an APTESTRD.
 */
int
write3812(n)
int n;
{
	int r,i,i_rc;

	i = 0;                   /* offset into cbuf */         
	while (n>0) {
		print_debug("Current Count: %d first 10:%.10s\n",n,&cbuf[i]);
		if ( (r = write(p,&cbuf[i],n)) < n) /* send data to 3812 */
		{ 
			print_debug("R Count: %d\n",r);
			if ( (r >= 0) || ((r == -1) && (errno == EIO)) ) 
			{
				if ( (i_rc = read_prt_status()) != 0 ) 
				{
					return(i_rc);
				}
				if (r>0) /* calculate amount to write */
				{   
				    	i= i+r;     /* new offset into cbuf */
					n = n-r;    /* new length to write */
				}
	    		} else {
				logerr("Error writing to 3812"); 
				/* XXX What to do? */
				cleanup();
			}
		} else {
			print_debug("Just wrote %d bytes to printer\n",r);
			n = n-r;
		}
    	}
    	return(0);
}

int
read_prt_status()
{
	register int i_rc;
	struct apioinfo rdinfo;

        for (;;){
		if ( (i_rc=ioctl(p,APTESTRD,&rdinfo)) == 0) 
		{ 
			switch (rdinfo.apstatus) {
			case APNOINFO:     /* no more data to read */
				return(0); 
	    		case APDOWN:
				errout("Printer lost synchronization\n");
				set_status(NEED_TO_RESET);
				bzero((char *)current_ir,sizeof current_ir);
				/* in order to resynchronize,
				 * we need to close and open the
				 * device again.
				 */
				closeopen3812();
				return (-1);
			case APGOTDATA:
				print_debug("Need to Read\n");
				readap();
				return(0);
			case APGOTCMD:
				errout("Immed Command %x\n",rdinfo.apcmd);
				return(0);
			}
		}			
		else errout("ioctl err %d\n",i_rc); 
         }
}


readap()
{
        char bufp[512];         /* buffer for reading from printer */
	int cnt;                /* count of bytes read             */
	int i;
	if ( (cnt=read(p,bufp,512)) > 0) 
	{
		if (debug) 
		{
			print_debug("PRINTER INPUT(%d):",cnt);
			for (i = 0;i < cnt; i++) {
				print_debug("%2x",bufp[i]);
			}
			print_debug("\n");
		}
		parsebuf(cnt,bufp);  /* parse the input */
	} 
	else 
	{
		if ((cnt == -1) && (errno == EIO)) 
		{
			errout("printer input failed after GOTDATA\n");
	        } 
		else 
		{
			perror("ibm3812pp: readap:");
		}
	}
        print_debug("End of printer read.\n");	
}


/*
 * Parse the input buffer from the printer.  
 */
parsebuf(cnt,bufp) 
int cnt;
char bufp[512];
{

	int page_done, i = 0, type = 0;
        char ch;
	/*
	 * Scan the input
	 */
        while (i < cnt)  
	{
		switch (bufp[i]) {
		case POWER_ON:  
		        write_printer_info(&bufp[i],1,err_info.power_on);
			errout("Printer Status: Power On\n");
			bzero((char *)current_ir,sizeof current_ir);
			i = i + 1;
			break;
	    	case PAGE_DONE:
		        write_printer_info(&bufp[i],3,err_info.page_done);
			if ( (i+2) < cnt) 
			{
				ch  = bufp[i+2];  /* pick up page done*/
				page_done = ch;
				print_debug("Page Done:%d\n",page_done);
				i = i + 3;
			} 
			else 
			{
				print_debug("Page Done command is incomplete");
				i = cnt;
			}
			break;
		case INTERVENTION:		/* Intervention required */
			if ((i+3) <= cnt) 
			{
				type = bufp[i+1];
				ch   = bufp[i+3];
				page_done = ch;
				i = i + 4;
				if ((type > MEMORY_ERR)||(type < PAPER_JAM)) 
					type = 0;   /* unknown type */
			        write_printer_info(&bufp[i],5,
						err_info.intervention[type]);
		                errout("Intervention required: ");	
			        errout("%s\n",irstatus[type]);
			} 
			else 
			{
				write_printer_info(&bufp[i],5,
						err_info.intervention[0]);
				errout("Intervention msg incomplete.\n");
			}
			set_current_ir(type);
			break;
		case I_CLEARED:		/* Intervention cleared   */
			if ((i+3) <= cnt) {
				type = bufp[i+1];
				ch   = bufp[i+3];
				page_done = ch;
				i = i + 4;
				if ((type > MEMORY_ERR)||(type < PAPER_JAM)) 
					type = 0;   /* unknown type */
				write_printer_info(&bufp[i],4,
						    err_info.i_cleared[type]);
		                errout("Intervention cleared: ");	
		                errout("%s\n",ircleared[type]);
			} 
			else 
			{
				write_printer_info(&bufp[i],4,
						    err_info.i_cleared[0]);
	    	                errout("Intervention msg incomplete.\n");
				i = cnt;
			}
			clear_current_ir(type);
			break;
		case COMMAND_ERR:
			if ((i+4) <= cnt) {
		                ch   = bufp[i+1];
				type = ch;
				ch   = bufp[i+4];
				page_done = ch;
				i = i + 5;
				if ((type > EXCEED_NEST_LIM) ||
				    (type < BAD_PMP_CMD))
					type = 0;   /* unknown type */
	    		        write_printer_info(&bufp[i],5,
						err_info.command_err[type]);
	    	                errout("Command Exception: ");	
		                errout("%s\n",cmd_excp[type]);
			} 
			else	
			{
				write_printer_info(&bufp[i],5,
						err_info.command_err[0]);
		                errout("Command Exception incomplete.\n");
				i = cnt;
			}
			break;
		case PRINTER_ERR:
			write_printer_info(&bufp[i],5,err_info.printer_err);
			if ((i+4) <= cnt) {
				ch   = bufp[i+4];
				page_done = ch;
				i = i + 5;
		                errout("Printer Failure: ");	
		                errout("%2x \n",bufp[1]);
		        } 
			else 
			{
				errout("Printer Failure Command.\n");
				i = cnt;
			}
			break;
	        }      /* end of switch on bufp[i] */
	}         /* end of while statement   */
}            /* end of parsebuf */


write_printer_info(ptr,cnt,test)
char *ptr; 
int cnt;
short test;
{
	int num;
	if (test)
	{
		if ((num=write(s,ptr,cnt)) != cnt) 
		{
			if ((num == -1) && (errno == EPIPE)) 
				return;
			if (num < 0)
				logerr("Lost connection on write");
	        	errout("Error on write\n");
		}
     }

}

closesocket()
{
	
        (void) close(s);	   /* close the active socket */
	(void) close(f);           /* close the socket        */
        (void) unlink (sock_name);
}


closeopen3812()
{
	if (close(p) < 0 ) {
		logerr("error closing line discipline");
		closesocket();
		exit(1);		
	}
	/* open line and open line discipline */
	open3812();
}

open3812()
{
	int ir;
	int ldisc = APLDISC; /* identify 3812 line discipline */
	/*  open the printer for read/write.
	 *  This will stay open until this daemon is stopped.
	 *  or until the printer has an APDOWN error and 
	 *  it needs to be restarted by closeopen3812.
	 */
	if (( p = open (dev,O_RDWR, 0644)) <0) {
		print_debug( "dev %s, open returns %x\n", dev, p);
		logerr("error opening printer");
		exit(1);
	}
	print_debug("printer file descriptor: %d\n",p);
	/*  Set the sgttyb bits and call ioctl */
	switch (atoi(baudrate)) {
	case 1200:
		sg.sg_ispeed = sg.sg_ospeed = B1200;
		break;
	case 2400:
		sg.sg_ispeed = sg.sg_ospeed = B2400;
		break;
	case 4800:
		sg.sg_ispeed = sg.sg_ospeed = B4800;
		break;
	case 9600:
		sg.sg_ispeed = sg.sg_ospeed = B9600;
		break;
	case 19200:
		sg.sg_ispeed = sg.sg_ospeed = EXTA;
		break;
	default:
		errout("Illegal printer baud rate (%s).\n", baudrate);
		errout("Valid rates: 1200 2400 4800 9600 19200\n");
		exit(1);
		/*NOTREACHED*/
	}
	sg.sg_flags = RAW + EVENP + ODDP;
	if ((ir = ioctl(p,TIOCSETP,&sg)) < 0) {
		print_debug("dev %s, printer %d ioctl returns %x\n",dev,p,ir);
		logerr("error setting sgtty");
		exit(1);
	}
	/* Change to the IBM3812 Line Discipline */
	if (ioctl(p,TIOCSETD,&ldisc) < 0 ) {
		logerr("error changing to 3812 line discipline");
		exit(1);		
	} 
}


/*      Write a reset to the printer and wait until the printer 
 *      returns the command that it was reset before continuing.
 */
sendreset()
{
	struct apioinfo writeinfo;
	int w_rc;

	writeinfo.apcmd = 0x01;     /* expedited cmd to CLEAR  */
	c_state = PMP;

	/*
	 * The CLEAR command has no explicit acknowledgement.  So, if the
	 * write is successful, then a DATA LINK ACK has been received
	 * for the CLEAR, and we assume the CLEAR was performed.
	 */
	while (status == NEED_TO_RESET) 
	{
		if ( (w_rc=ioctl(p,APWRTCMD,&writeinfo)) >= 0) 
		{
			set_status (CLEAR_3812_DONE);
		} 
		else 
		{
			if ((w_rc == -1) && (errno == EIO)) 
			{
				errout("sendreset: reading printer state\n");
				(void) read_prt_status();
			} 
			else 
			{
				logerr("error writing expedited command");
				closesocket();
				exit(1);
			}
		}
	}
}


/*VARARGS1*/
print_debug(format,a1,a2,a3,a4,a5)
char *format,*a1,*a2,*a3,*a4,*a5;
{
	if (debug)
		errout(format,a1,a2,a3,a4,a5);

}

static 
set_current_ir(n)
int n;
{
	current_ir[n] = 1;
	print_status();
}

static 
clear_current_ir(n)
int n;
{
	current_ir[n] = 0;
	print_status();
}

static
set_status(n)
int n;
{
	status = n;
	print_status();
}

static
print_status()
{
        int i;

	fseek(sf,0,0);		/* rewrite entire file */
        ftruncate(fileno(sf),0);
	fprintf(sf,"3812 print server status: %s\n", server_status[status]);
        for (i = 0; i < IRSTATUS_CNT; i++) 
	{
	        if (current_ir[i] != 0) 
		{
			fprintf(sf,"3812 Printer Intervention Required: %s\n",
				    irstatus[i]); 
		}
	}
	fflush(sf);
}


static
logerr(msg)
char *msg;
{
	register int err = errno;
	extern int sys_nerr;
	extern char *sys_errlist[];

	errout("ibm3812pp: %s:  %s\n", msg,
		(err < sys_nerr) ? sys_errlist[err] : "Unknown error");
}


/*VARARGS1*/
errout(format,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)
char *format,*a1,*a2,*a3,*a4,*a5,*a6,*a7,*a8,*a9,*a10,*a11,*a12;
{
        static struct timeval lastwritten = {0,0};
	struct timeval thistime;
        struct timezone timezone;
	char *ctime();

        if (gettimeofday(&thistime, &timezone) == -1) 
	{
		perror("gettimeofday");
		exit(1);
	}

	if ((thistime.tv_sec-lastwritten.tv_sec) > TIME_STAMP_EVERY) 
	{
		fprintf(stderr, ctime(&thistime.tv_sec));
		lastwritten = thistime;
	}

	fprintf(stderr,format,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12);
	fflush(stderr);
}
