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

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

/*
 * Connect to the socket that drives the 3812 printer.
 */

#include <sys/un.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>

#include "printer3812.h"

static int s;				/* socket descriptor */

#define MAXWIDTH 		132	/* maximum line length */
#define CLOSE_ON_EXEC		1	/* flag to fcntl */
#define DU       		1	/* daemon uid */
#define DORETURN		0	/* absorb fork error */
#define DOABORT			1	/* abort if dofork fails */
#define	TIME_STAMP_EVERY	20	/* Time stamp every N seconds. */
#define	EXIT_OK			0	/* Tells lpd that we finished OK */
#define	EXIT_RETRY		1	/* ... the job needs to be re-run */
#define	EXIT_FATAL_ERROR	2	/* ... the job should be re-run,
					 * after some manual intervention.
					 */
extern  errno;
extern  char *pgetstr();
int     sockdown();

int	width = MAXWIDTH;	/* default line length */
int	length = 66;		/* page length */
int	indent;			/* indentation length */
int	literal;		/* print control characters */
int	debug = 0;		/* controls the printing of debugging info */
char	*name;			/* user's login name */
char	*host;			/* user's machine name */
char	*acctfile;		/* accounting information file */
char    *printer;      		/* name of printcap entry   */
char    *type;      	   	/* type of file to print    */
char    line[BUFSIZ];
char    *PP;			/* device address of 3812 */
char    *SD;			/* spool directory        */
char    *SS;			/* status file            */
char    *PL;			/* print server log       */
int     br;			/* baud rate of printer line */
char sock_name[108];		/* 108 because 'sun_path' can be that long */


/* dowrite - write to socket after checking socket for any pending input. */
static void
dowrite(buffer, length, socket)
char	*buffer;		/* buffer */
int	length;			/* length of buffer */
int	socket;			/* socket to do write/read to/from */
{
	int readfds;
	int rd_cnt;
	int nfound;
	static struct timeval timeout = { 0, 0 };
	char inputbuf[1000];

	/* anything trying to send us something? 
	 * If so, clear socket before doing the write 
	 */
	readfds = 1<<socket;
	
	if ( (nfound = select(socket + 1, &readfds, 0, 0, &timeout)) < 0) 
	{
		if (errno != EINTR)
		{
			perr("select");
			exit(EXIT_FATAL_ERROR);
		}
	} 
	else if (nfound > 0 && (readfds & (1<<socket))) 
	{
		if ((rd_cnt=read(socket, inputbuf, sizeof(inputbuf))) <= 0) 
		{
			errout("error reading from socket:%d\n",rd_cnt);
		} 
		else 
		{
			errout("read from socket cnt:%d\n",rd_cnt);
		}
	}
	if (write(socket, buffer, length) != length) {
		perr("write");
		exit(EXIT_RETRY);
	}
}

main(argc,argv)
int argc;
char *argv[];
{
	register char *cp;
	int i;
	int printcap_status;
	char pbuf[BUFSIZ/2]; /* buffer for printcap strings */
	char *bp= pbuf;      /* pointer into pbuf for pgetstr */

	setlinebuf(stderr);
	parsecmd(argv[0], &type, &printer);
	while (--argc) {
		if (*(cp = *++argv) == '-') 
		{
			switch (cp[1]) {
			case 'd':
				debug++;
				break;

			case 't':
				argc--;
				type = *++argv;
				break;

			case 'n':
				argc--;
				name = *++argv;
				break;

			case 'h':
				argc--;
				host = *++argv;
				break;

			case 'w':
				if ((i = atoi(&cp[2])) > 0 && i <= MAXWIDTH)
					width = i;
				break;

			case 'l':
				length = atoi(&cp[2]);
				break;

			case 'i':
				indent = atoi(&cp[2]);
				break;

			case 'c':	/* Print control chars */
				literal++;
				break;

			}
		} 
		else 
		{
			acctfile = cp;
		}
	}
	print_debug("type is %s, printer is %s\n", type, printer);


	/* ---------look in printcap entry and get ---------------------*/
	/*            1. spool directory name 	                        */
	/*            2. device name from PP          	                */

	if ((printcap_status = pgetent(line,printer)) < 0) 
	{
		perr("can't open printer description file");
		exit(EXIT_FATAL_ERROR);
	} 
	else 
	{
		if (printcap_status == 0) 
		{
			perr("unknown printer");
			exit(EXIT_FATAL_ERROR);
		}
	}
	if ((PP =pgetstr("PP", &bp))==NULL)  PP="/dev/pp";
	if ((SD =pgetstr("sd", &bp))==NULL)  SD="/usr/spool/ppd";
	if ((SS =pgetstr("SS", &bp))==NULL)  SS="/usr/spool/ppd/printer3812";
	if ((br =pgetnum("br"))==-1)  br=19200;

	sprintf(sock_name,"%s/%s3812",SD,printer);  /* generate socket name */

	print_debug("Socket name: %s\n",sock_name);


	/* Set up signal handling.
	 *  - Since the filters take care of wrapping up a package, 
	 * 	resetting printer, etc., and then closing the pipe, we
	 * 	ignore SIGINT so that we can continue processing everything
	 * 	that the filter has sent to us.
	 *  - We get SIGPIPE if socket goes down, catch it and close socket.
	 */
	signal(SIGINT, SIG_IGN); 
	signal(SIGPIPE, sockdown);

	print_debug("calling getsocket from main\n");
	if ( (s = getsocket(sock_name, type)) == -1) 
	{
		exit(EXIT_FATAL_ERROR);
	}

	processInput();

	(void) close(s);
	exit(EXIT_OK);
}

processInput()
{
	register  num;
	char buf[IBM3812_BUFSIZ];

	print_debug("processInput():\n");
	while ((num = read(0, buf, sizeof(buf))) > 0) 
	{
		print_debug("input %d\n",num);
		if (*type == 'i') {	/* search for commands from lpd */
			static enum { BORING, GOT031 } state = BORING;
			char *begin, *p;

			if (state == GOT031) 
			{
				if (buf[0] == '\1') 
				{	/* hang for a while */
					s = hang(s, sock_name, type);	
					if (s == -1) 
					{
						exit(EXIT_FATAL_ERROR);
					}
				} 
				else 
				{
					dowrite("\31", 1, s);
				}
			}
			state = BORING;
			begin = p = buf;
			while (num) 
			{
				if (*p == '\31') 
				{
					if (num != 1) 
					{
						if (*(p+1) == '\1') 
						{
							if (p-begin) 
							{
								dowrite(begin, p-begin, s);
							}
							s = hang(s, sock_name, type);
							if (s == -1) 
							{
								exit(EXIT_FATAL_ERROR);
							}
							num--;
							p++;
							begin = p+1;
						}
					} 
					else 
					{
						if (p-begin) 
						{
							dowrite(begin, p-begin, s);
						}
						state = GOT031;
						begin = p+1;
					}
				}
				num--;
				p++;
			}
			if ((p-begin) && (state != GOT031)) 
			{
				dowrite(begin, p-begin, s);
			}
		} 
		else 
		{	/* This is PMP data */
			dowrite(buf, num, s);
		}
	}
	if (num < 0) 
	{
		perr("reading from standard input");
		exit(EXIT_RETRY);
	}
}


start3812()
{
	union wait prstatus;

	if (dofork(DORETURN) == 0) 
	{
		char baudrate[20];
		print_debug("child is execing ibm3812pp\n");
		sprintf(baudrate, "%d", br);
		execl("/usr/lib/p3812/ibm3812pp", "ibm3812pp", 
		"-b", baudrate,
		"-s", sock_name, 
		"-D", PP,
		"-S", SS,
		(debug>0)? "-d" : 0, 
		(debug>1)? "-d" : 0, 
		0);
		perr("cannot execl ibm3812pp");
		exit(EXIT_FATAL_ERROR);
	}
	print_debug("parent returning from dofork\n");
	sleep(10);
	wait3(&prstatus, WNOHANG, 0); 

}


/*
 * dofork - fork with retries on failure
 */
static
dofork(action)
int action;
{
	register int i, pid;

	for (i = 0; i < 20; i++) {
		if ((pid = fork()) < 0) {
			sleep((unsigned)(i*i));
			continue;
		}
		/*
		 * Child should run as daemon instead of root
		 */
		if (pid == 0)
			setuid(DU);
		return(pid);
	}
	perr("can't fork");

	switch (action) {
	case DORETURN:
		return (-1);
	default:
		perr("bad action (%d) to dofork", action);
		/*FALL THRU*/
	case DOABORT:
		exit(EXIT_RETRY);
	}
	/*NOTREACHED*/
}

static
sockdown()
{
	(void) close (s);
	(void) errout("ppt: socket connection went down\n");
	exit(EXIT_RETRY);    /* reprocess the document */
	/*NOTREACHED*/
}

/* getsocket - get a socket, and connect to another socket.
 * The return value is -1 on an error, or the new socket file descriptor.
 */
static int
getsocket(sock_name, type)
char	*sock_name;
char	*type;
{
	int s;
	int addrlen;
	struct sockaddr_un addr;
	print_3812_flags prt_start_msg;
	print_3812_flags prt_start_ack;

	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
		perr("socket");
		exit(EXIT_FATAL_ERROR);
	}

	/* Set the descriptor to close on an exec.  This is done so
	 * that the daemon process doesn't inherit our descriptor,
	 * since if it inherits our descriptor, when we "close"
	 * the descriptor (after having connected), the system
	 * will still leave the connection open.
	 */
	if (fcntl(s, F_SETFD, CLOSE_ON_EXEC) == -1) {
		perr("fcntl");
		exit(EXIT_FATAL_ERROR);
	}

	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, sock_name); 
	print_debug("ppt: sockaddr %d, %08x\n", addr.sun_family, addr.sun_path);

	addrlen = sizeof(addr.sun_family) + strlen(addr.sun_path);
	if (connect(s, &addr, addrlen) < 0) 
	{
		if (errno == ENOENT || errno == ECONNREFUSED) 
		{
			print_debug("ppt: calling start3812 from getsocket\n");
			start3812();
			if (connect(s, &addr, addrlen) < 0)
			{
				perr("ibm3812pp printer server not there");
				exit(EXIT_FATAL_ERROR);
			}
			else
			{
				print_debug("ppt: sockaddr %d, %08x\n", 
					     addr.sun_family, addr.sun_path);
			}
		} 
		else {
			perr("connect");
			exit(EXIT_FATAL_ERROR);
		}
	}

	bzero((char *)&prt_start_msg, sizeof prt_start_msg);
	if (*type == 'v') {
		prt_start_msg.type = 2;
	} 
	else {
		prt_start_msg.type = 1;
	}

	prt_start_msg.sequence_id = getpid();
	strncpy(prt_start_msg.username,name,sizeof prt_start_msg.username);
	strncpy(prt_start_msg.hostname,host,sizeof prt_start_msg.hostname);

	print_debug("ppt: writing startup message to socket\n");
	if ((write(s,(char *)&prt_start_msg, sizeof prt_start_msg))
	    != sizeof prt_start_msg) {
		perr("write startup message");
		(void) close(s);
		exit(EXIT_RETRY);
	}

	print_debug("ppt: reading confirmation message from socket\n");
	if ( read(s,(char *)&prt_start_ack, sizeof prt_start_ack) != 
	     sizeof prt_start_ack) 
	{
		perr("read startup message");
		(void) close(s);
		exit(EXIT_RETRY);
	}

	print_debug("ppt: comparing startup messages\n");
	if ( bcmp((char *)&prt_start_msg,(char *)&prt_start_ack,
		  sizeof prt_start_msg) != 0) 
	{
		perr("startup message not confirmed");
		(void) close(s);
		exit(EXIT_RETRY);
	}
	return(s);
}


/* hang - close the socket, STOP ourselves, then re-open the connection.
 * We return the new file descriptor (or, -1 is an error ocurred)
 */
static int
hang(s, sock_name, type)
int	s;		/* file descriptor of already open socket */
char	*sock_name;	/* name of socket to be opened */
char	*type;		/* type to send */
{
	if (close(s) != 0) {			 /* Close the socket */
		perr("closing socket during a 'hang' operation");
		exit(EXIT_RETRY);
	}

	if (kill(getpid(), SIGSTOP) != 0) {	 /* Stop us */
		perr("sending SIGSTOP to ourselves");
		exit(EXIT_RETRY);
	}

	return(getsocket(sock_name, type));	/* We have been CONT'd */
}


char	TYPE_PMP[] = "v";
char	TYPE_ASCII[] = "i";

/* parsecmd looks to see what name was used when this program was invoked.
 * if it was pmp or ppt then the printcap printer name is pp.
 * Otherwise look for a hyphen in the name, and the printcap printer name
 * follows the hyphen.
 */
parsecmd(argv0, p_type, p_printer)
char	*argv0;			/* name we were invoked under */
char	**p_type;		/* where we stuff address of type of data */
char	**p_printer;		/* where we put the name of the printer */
{
	static char printer[40];
	char *command;	/* argv[0] with everything but the final component
			 * of the pathname stripped out.
			 */

	for (command = argv0; *argv0; argv0++) {
		if (*argv0 == '/') {
			command = argv0+1;
		}
	}

	if (!bcmp(command, "ppt", 3)) {
		*p_type = TYPE_ASCII;
	} 
	else if (!bcmp(command, "pmp", 3)) {
		*p_type = TYPE_PMP;
	} 
	else {
		error(EXIT_FATAL_ERROR, "invalid type");
		/*NOTREACHED*/
	}
	if (*(command+3) == '_') {
		if (strlen(command+4) >= sizeof(printer)) {
			error(EXIT_FATAL_ERROR, "type of printer too long");
			/*NOTREACHED*/
		}
		strcpy(printer, command+4);
		*p_printer = printer;
	} 
	else if (*(command+3) == 0) {
		strcpy(printer, "pp");	/* the default */
		*p_printer = printer;
	} 
	else {
		error(EXIT_FATAL_ERROR, "invalid invocation");
		/*NOTREACHED*/
	}
	/* at this point, we know the type of data, and the
	 * (printcap) name of the printer.
	 */
}

static
perr(msg)
char *msg;
{
	extern int sys_nerr;
	extern char *sys_errlist[];
	extern int errno;

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

/*VARARGS1*/
print_debug(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;
{
	if (debug) {
		errout(format,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12);
	}

}

error(code, msg)
int	code;
char	*msg;
{
	errout("%s\n", msg);
	exit(code);
}


/*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) {
		/* note: can't call perr, it would recurse*/	
		perror("gettimeofday"); 
		exit(EXIT_FATAL_ERROR);
	}

	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);
}
