/*======================================================================
                    R P C . I N E T R A Y D . C 
                    doc: Mon Feb 24 16:24:35 1992
                    dlm: Thu Aug 19 23:08:22 1993
                    (c) 1992 ant@julia
                    uE-Info: 430 24 T 0 0 72 10 2 8 ofnI
======================================================================*/

/*#define		VERBOSE 		/* babble */
/*#define		TRACEWORKERS		/* trace worker pids */

#include	<string.h>		/* The long and sad inclusion story */
#include	<signal.h>
#include	<netdb.h>	/* Prefer system over rpc/netdb.h */
#include	<stdio.h>
#include	<pwd.h>
#include	<errno.h>
#include	<syslog.h>
#include	<sys/file.h>		/* A/UX wants that */
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/param.h>
#include	<sys/wait.h>
#ifndef INADDR_ANY
#include	<netinet/in.h>
#endif
#include	<rpc/rpc.h>
#include	"rayshade.h"
#include	"picture.h"
#include	"version.h"
#include	"patchlevel.h"
#include	"inetray.h"
#include	"config.h"
#include 	"common.h"
#include	"irtrace.h"
#include	"utils.h"
#include	"lbuffer.h"
#include	"stderr.h"

#define MAXARGS	256			/* max # of args allowed */
#define ARGBUFSZE 8192			/* max buffer for args */

extern int	errno;			/* for connection retry */
extern void 	inetray_1();		/* generated dispatch routine */

EXPORT int 	_rpcpmstart;		/* Started by a port monitor ? */

static int 	argc;			/* argcount */
static char 	*argv[MAXARGS];		/* argvector */
static char 	argbuf[ARGBUFSZE];	/* buffer for argv */
static int	pid = -1;		/* pid of child */
static int	bufPid = -1;		/* pid of lBuffer */
static char	rName[MAXHOSTNAMELEN];	/* remote hostname */
static int	lSize;			/* size of scanline */
static int	sock = -1;		/* result socket */
static XDR	xdrs;			/* result XDR stream */
static int	key;			/* session key */
static u_long 	progNum;		/* program number (INETRAY + wid) */
static uid_t	uid;			/* user id (ideal) */
static int	bufPipe[2];		/* pipe from live buffer */
static char	initialized = FALSE;	/* files read */
static int	firstFrame;		/* deferred startframe number */
static char	bufReadingSock = FALSE;	/* live buffer is reading socket */
static int	rpcSock,proto;		/* socket & proto to register */
static SVCXPRT	*transport;		/* transport handle */
static int	bNr = -1;		/* bNr of last request */

/*----------------------------------------------------------------------*/

static void parseQuote(s,spp,bpp)		/* parse quoted arg */
char s[]; int *spp,*bpp;
{
	argv[argc] = &argbuf[*bpp];
	(*spp)++;
	while (s[*spp] != '\'')	{	/* copy simply */
		argbuf[(*bpp)++] = s[(*spp)++];
	}
	argbuf[(*bpp)++] = '\0';
	(*spp)++;				/* skip quote char */
}

static void parseArgs(s)			/* break up s into argv */
char s[];
{
	int bufp,sp,sl;

	argc = bufp = sp = 0;
	sl = strlen(s);
	while (sp < sl) {
		if (s[sp] != '\'') {
			sp++;
			continue;
		}
		parseQuote(s,&sp,&bufp);
		argc++;
	}
	argv[argc] = NULL;
}

static void exitClean(s)			/* kill & exit */
int s;
{
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"exitClean(%d)",s);
#endif
	if (pid > 0) kill(pid,SIGKILL);
	if (!_rpcpmstart)
	 	(void)pmap_unset(progNum,IRV1);
	CLOSELOG();
	closeStderr();
	exit(s);
}
	
/*----------------------------------------------------------------------*/

static void checkAbort()			/* abort on crash */
{
	fd_set	selRead;
	int	status;

	signal(SIGALRM,checkAbort);		/* sysV yummy dummy */

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"Checking for abort");
#endif
	if (pid > 0) {				/* check child exit stat */
#ifdef VERBOSE
		SYSLOG(LOG_NOTICE,"Checking for child termination");
#endif
		if (waitpid(pid,&status,WNOHANG) > 0) {
			pid = 0;
			if (status != 0) {
				SYSLOG(LOG_ERR,"Abort: worker exit status %d",status);
				exitClean(1);
			}
		}
	}

	if (sock == -1) {			/* not yet set up */
		SYSLOG(LOG_ERR,"Lost INIT request");
		exitClean(1);
	}
	
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"Checking socket");
#endif
	
	if (bufReadingSock) {			/* watch out! */
		FD_ZERO(&selRead);
		FD_SET(bufPipe[IN],&selRead);	/* check buffer pipe */
		if (select(bufPipe[IN]+1,&selRead,NULL,NULL,&now) < 0) {
			SYSLOG(LOG_ERR,"select(): %m");
			exitClean(1);
		}
		bufReadingSock = !FD_ISSET(bufPipe[IN],&selRead);
	}

	if (bufReadingSock) return;		/* really reading -> wait */
		
	FD_ZERO(&selRead);			/* check connection */
	FD_SET(sock,&selRead);
	if (select(sock+1,&selRead,NULL,NULL,&now) < 0) {
		SYSLOG(LOG_ERR,"select(): %m");
		exitClean(1);
	}
	if (FD_ISSET(sock,&selRead)) {		/* read() would return 0 */
		SYSLOG(LOG_ERR,"Dispatcher disappeared");
		exitClean(1);
	}
}

static void setupTimer()
{
	struct itimerval chkClntT;
	
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"setupTimer()");
#endif
	chkClntT.it_interval.tv_sec = chkClntT.it_value.tv_sec = CHKCLNT;
	chkClntT.it_interval.tv_usec = chkClntT.it_value.tv_usec = 0;
	signal(SIGALRM,checkAbort);
	if (setitimer(ITIMER_REAL,&chkClntT,NULL) < 0) {
		SYSLOG(LOG_ERR,"setitimer: %m");
		exitClean(1);
	}
}

static void registerSelf()		/* register self */
{
	transport = svcudp_create(rpcSock);
	if (transport == NULL) {
		SYSLOG(LOG_ERR,"cannot create udp service.");
		exitClean(1);
	}
	if (!svc_register(transport,progNum,IRV1,inetray_1,proto)) {
		SYSLOG(LOG_ERR,"unable to register (%d,VER,udp)",progNum);
		exitClean(1);
	}
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"Opening Stderr");
#endif
	if (openStderr("rpc.inetrayd") < 0) exitClean(1);
}

static void slaveSvcs(n)		/* start secondary servers */
int n;					/* clean up & register */
{
	int	pid,thisProg;

	thisProg = progNum;
	while (n-- > 0) {		/* start n */
		thisProg++;
		pid = fork();
		if (pid < 0) {
			SYSLOG(LOG_ERR,"fork: %m");
			exitClean(1);
		}
		if (pid == 0) {		/* new server */
			progNum = thisProg;
			svc_destroy(transport);
			(void)pmap_unset(progNum,IRV1);
			_rpcpmstart = FALSE;
			rpcSock = RPC_ANYSOCK;
			proto = IPPROTO_UDP;
			registerSelf();
			setupTimer();
#ifdef VERBOSE
			SYSLOG(LOG_NOTICE,"INIT: forked prog # %d",progNum);
#endif
			return;
		}
	}
}

/*----------------------------------------------------------------------*/

static void killSelf()			/* received SIGINT(kill) signal */
{
	if (!xdrrec_endofrecord(&xdrs,TRUE)) {
		SYSLOG(LOG_ERR,"xdrrec_endofrecord() failed");
		exitClean(1);
	} 
	exit(0);
}

/*----------------------------------------------------------------------*/

static writeit(dummy,buf,nbyte)			/* XDR write routine */
char *dummy,*buf; int nbyte;
{
	int writ;

	writ = write(sock,buf,nbyte);
	if (writ < 0) SYSLOG(LOG_ERR,"XDR write: %m");
	return writ;
}

static void sendResult(bNr,bSz,block,skip)	/* send whole resultblock back */
int bNr,bSz; Scanline *block; int skip;
{
	pixArr	res;
	int	i;
	
	bNr += skip;
	if (!xdr_int(&xdrs,&bNr)) {
		SYSLOG(LOG_ERR,"xdr_int() failed");
		exitClean(1);
	}
	
	res.pixArr_len = lSize;
	for (i=skip*bSz+1; i<=(skip+1)*bSz; i++) {
		res.pixArr_val = (xdrPix *)block[i].pix;
		if (!xdr_pixArr(&xdrs,&res)) {
			SYSLOG(LOG_ERR,"xdr_pixArr() failed");
			exitClean(1);
		}
	}
	if (!xdrrec_endofrecord(&xdrs,TRUE)) {
		SYSLOG(LOG_ERR,"xdrrec_endofrecord() failed");
		exitClean(1);
	}
}

/*----------------------------------------------------------------------*/

void *init_1(param)				/* init self */
iPrm *param;
{
	int	wid,wpid;
	char	wd[MAXPATHLEN],v[8],*cp,*getcwd();
	struct passwd		*p;
	struct hostent		*host;
	struct sockaddr_in	name;
	
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"INIT:");
#endif
	if (pid != -1)				/* only once */
		return (void *)NULL;		

	pid = 0;				/* initialized */
	slaveSvcs(param->nSvcs-1);		/* start 2nd svcs */
	
	signal(SIGINT,SIG_IGN);			/* ignore kill signal */

	strncpy(rName,param->rName,MAXHOSTNAMELEN);
	key = param->key;			/* session key */

	if (uid == 0) {				/* set default uid */
		if (setuid(param->uid) < 0) {
			SYSLOG(LOG_NOTICE,"setuid: %m");
		}
		uid = geteuid();		/* get effective uid */
		if (uid == 0) {
			SYSLOG(LOG_ERR,"can't run as root");
			exitClean(1);
		}
	}
	p = getpwuid(uid);			/* get user info */
	if (p == NULL) {
		SYSLOG(LOG_ERR,"illegal user id %d",uid);
		exitClean(1);
	}
	
	addHome(uid,param->cwd,wd);		/* get right dir */
	if (chdir(wd) < 0) {			/* try to get there */
		SYSLOG(LOG_NOTICE,"WARNING: chdir(%s): %m",wd);
	}

	if (key != 0) {				/* only pinged */
		parseArgs(param->cmdLine);
	}
						/* init socket */
	if ((host = gethostbyname(rName)) == NULL) {
		SYSLOG(LOG_ERR,"gethostbyname(%s) failed!",rName);
		exitClean(1);
	}
	name.sin_family = AF_INET;
	name.sin_port = htons(param->rPort);
	memcpy((char *)&name.sin_addr,host->h_addr_list[0],host->h_length);

	if ((sock = socket(PF_INET,SOCK_STREAM,0)) < 0) {
		SYSLOG(LOG_ERR,"socket: %m");
		exitClean(1);
	}
	if (connect(sock,&name,sizeof(name)) < 0) {
		if (errno == ECONNREFUSED) {	/* retry once */
			close(sock);
			sleep((rand()%2)+1);	/* wait random time */
		        if ((sock = socket(PF_INET,SOCK_STREAM,0)) < 0) {
		                SYSLOG(LOG_ERR,"socket: %m");
		                exitClean(1);
		        }
			if (connect(sock,&name,sizeof(name)) < 0) {
		                SYSLOG(LOG_ERR,"connect: %m");
				exitClean(1);
			}
		} else {
	                SYSLOG(LOG_ERR,"connect: %m");
			exitClean(1);
		}
	}

	xdrrec_create(&xdrs,0,0,NULL,NULL,writeit); /* create XDR */
	xdrs.x_op = XDR_ENCODE;
	wid = progNum - INETRAY;		/* send back info */
	if (!xdr_int(&xdrs,&wid)) {		/* worker id */
		SYSLOG(LOG_ERR,"xdr_int() failed");
		exitClean(1);
	}
	wpid = getpid();
	if (!xdr_int(&xdrs,&wpid)) {		/* pid */
		SYSLOG(LOG_ERR,"xdr_int() failed");
		exitClean(1);
	}
	cp = p->pw_name;			/* user name */
	if (!xdr_string(&xdrs,&cp,32)) {
		SYSLOG(LOG_ERR,"xdr_string() failed");
		exitClean(1);
	}
	getcwd(wd,MAXPATHLEN);			/* working dir */
	stripHead(STRIPHEAD,wd);
	stripHome(uid,wd); 
	cp = wd;
	if (!xdr_string(&xdrs,&cp,MAXPATHLEN)) {
		SYSLOG(LOG_ERR,"xdr_string() failed");
		exitClean(1);
	}
	sprintf(v,"%s%d",VERSION,PATCHLEVEL);	/* version check */
	cp = v;
	if (!xdr_string(&xdrs,&cp,8)) {
		SYSLOG(LOG_ERR,"xdr_string() failed");
		exitClean(1);
	}
	
	if (!xdrrec_endofrecord(&xdrs,TRUE)) {
		SYSLOG(LOG_ERR,"xdrrec_endofrecord() failed");
		exitClean(1);
	}

	if (param->sendIn) {			/* receive file */
		if (pipe(bufPipe) < 0) {
			SYSLOG(LOG_ERR,"pipe: %m");
			exitClean(1);
		}
		bufReadingSock = TRUE;		/* buffer is reading */
		bufPid = lPostBuffer(sock,bufPipe);
	}

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"INIT done");
#endif
	return (void *)NULL;			/* dont reply rpc */
}

int *wait_1(keyP)				/* wait 4 worker 2 die */
int *keyP;
{
	int	status,dPid;
	static int res = 0;
	
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"WAITING FOR: %d",pid);
#endif
	if (pid < 0) exit(0);			/* not initialized */
	if (*keyP != key) { 			/* authorize */
		return (int *)NULL;
	}
	if (pid > 0) {				/* one has been started */
	    reStartWait:
		if ((dPid = waitpid(pid,&status,0)) < 0) { /* wait for it */
			if (errno == EINTR) goto reStartWait;
			SYSLOG(LOG_ERR,"wait: %m");
			exitClean(1);
		}
		if (status != 0) {
			SYSLOG(LOG_NOTICE,"worker exit status %d",status);
		}
		if (dPid != pid) {		/* what happened ? */
			kill(pid,SIGKILL);	/* abort ! */
			SYSLOG(LOG_ERR,"pid of dead %d instead of %d",dPid,pid);
			exitClean(1);
		}
#ifdef VERBOSE
		SYSLOG(LOG_NOTICE,"pid %d returned %d",dPid,status);
#endif
		pid = 0;
	}
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"WAITING done");
#endif
	return &res; 
}		

int *startframe_1(param)			/* start new frame */
sfPrm *param;
{
	static int res = 0;
	int	status;

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"STARTFRAME:");
#endif
	if (pid < 0) exit(0);			/* not initialized */
	if (param->key != key) {		/* authorize */
		return (int *)NULL;
	}
	wait_1(&(param->key));
	if (initialized)			/* start new frame */
		RSStartFrame(param->fNr);
	else					/* defer */
		firstFrame = param->fNr;
	bNr = -1;				/* clear saved block# */
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"STARTFRAME done");
#endif
	return &res;
}

void *traceblock_1(param)			/* trace block of lines */
tbPrm *param;
{
	int	pgrp,status,saveIn,i;
	Scanline *resBlock,*Raytrace();

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"TRACEBLOCK msg received");
#endif
	if (pid < 0) exit(0);			/* not initialized */
	if (param->key != key) 			/* authorize */
		return (void *)NULL;
	if (bNr == param->bNr)			/* message resent */
		return (void *)NULL;
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"TRACEBLOCK started");
#endif

	bNr = param->bNr;			/* avoid doublework */
	
	if (!initialized) {			/* deferred stuff */
#ifdef VERBOSE
		SYSLOG(LOG_NOTICE,"initializing");
#endif
		if (bufPid > 0) {		/* buffered stdin */
			saveIn = dup(IN);
			close(IN);
			dup(bufPipe[IN]);
			close(bufPipe[IN]);
		}
		lSize = RaytraceInit(argc,argv);
		if (bufPid > 0) {		/* restore stdin */
			waitpid(bufPid,NULL,0);
			bufPid = -1;
			bufReadingSock = FALSE;
			close(IN);
			dup(saveIn);
		}
		RSStartFrame(firstFrame);
		initialized = TRUE;
/*		wait(NULL);			/* get status */
	}
	wait_1(&(param->key));
	pid = fork();				/* make new */
	if (pid < 0) {
		SYSLOG(LOG_ERR,"fork: %m");
		exitClean(1);
	}
	if (pid == 0) {				/* child */
		nice(NICEDAEMON);		/* run nicely */

		signal(SIGINT,killSelf);
		resBlock = Raytrace(param->nBlocks*param->bSz,
				    param->lNr);
		signal(SIGINT,SIG_IGN);
		for (i=0; i<param->nBlocks; i++)
			sendResult(param->bNr,
				   param->bSz,
				   resBlock,i);
#ifdef VERBOSE
		SYSLOG(LOG_NOTICE,"> EXIT");
#endif
		exit(0);
	}
#ifdef TRACEWORKERS
	SYSLOG(LOG_NOTICE,"pid %d forked",pid);
#endif
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"pid %d forked",pid);
#endif
	return NULL;				/* don't block */
}

int *kill_1(keyP)				/* kill worker */
int *keyP;
{
	static int res = 0;
	
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"KILLING: %d",pid);
#endif
	if (pid < 0) exit(0);			/* not initialized */
	if (*keyP != key) {			/* authorize */
		return (int *)NULL;
	}
	if (pid > 0) (void)kill(pid,SIGINT);
	return &res;
}

void *terminate_1(keyP)				/* terminate */
int *keyP;
{
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"TERMINATE:");
#endif
	if (pid < 0) exit(0);			/* not initialized */
	if (*keyP != key) {			/* authorize */
		return;
	}
	if (pid > 0) {				/* force */
		kill(pid,SIGKILL);
		wait_1(keyP);
	}
	if (bufPid > 0)
		kill(bufPid,SIGKILL);
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"TERMINATE done");
#endif
/*	if (progNum == INETRAY)			/* wid == 0 */
/*		CLOSELOG();*/
	exitClean(0);
}

/*======================================================================*/

int _rpcfdtype = SOCK_DGRAM;	/* Whether Stream or Datagram ? */

main(ac,av)
int ac; char *av[];
{
	struct sockaddr_in 	saddr;
	int asize = sizeof(saddr);
	int ssize = sizeof(int);
	int 	fd;

	_rpcpmstart = (ac == 1);			/* logging */
	if (!_rpcpmstart) CLOSELOG();
	OPENLOG("rpc.inetrayd");
#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"started!");
#endif
	setupTimer();

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"Timer set up");
#endif
	if (_rpcpmstart) {				/* inetd */
		rpcSock = 0;
		proto = 0;
		if (getsockname(rpcSock, (struct sockaddr *)&saddr, &asize) < 0) {
			SYSLOG(LOG_ERR,"socket: %m");
			exitClean(1);
		}
		if (saddr.sin_family != AF_INET) {
			SYSLOG(LOG_ERR,"illegal protocol");
			exitClean(1);
		}
		if (getsockopt(0, SOL_SOCKET, SO_TYPE,
				(char *)&_rpcfdtype, &ssize) == -1) {
			SYSLOG(LOG_ERR,"getsockopt: %m");
			exitClean(1);
		}
	} else {				/* inetray.starter */
		(void)pmap_unset(INETRAY,IRV1);
		rpcSock = RPC_ANYSOCK;
		proto = IPPROTO_UDP;
	}

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"trying to register self...");
#endif
	uid = geteuid();			/* set up */
	progNum = INETRAY;
	registerSelf();

#ifdef VERBOSE
	SYSLOG(LOG_NOTICE,"waiting for request...");
#endif
	svc_run();
	SYSLOG(LOG_ERR,"svc_run returned");
	exitClean(1);
	/* NOTREACHED */
}

