/*
 * jam 841102-09
 */

#include "../h/param.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/kernel.h"
#include "../h/systm.h"
#include "../vnet/vnet.h"
#include "../conn/conn.h"
#include "../rpc/rpc.h"

typedef struct sinfo
{
	struct sinfo	*hashlink;	/* Forward link on hash list */
	struct sinfo	*freelink;	/* Forward link on free list */
	struct proc	*procp;		/* Server process */
	int		count;		/* Reference count */
	connection_t	*clientConn;	/* Connection to client node */
	u_long		clientId;	/* Client being serviced */
	rpc_serverClass_t *classp;	/* Service class */
	char		havecall;	/* Set if we have a request */
	u_short		operation;	/* Operation from call packet */
	caddr_t		params;		/* Parameters from call */
	u_long		cookie;		/* Distinguishing handle */
} sinfo_t;

#define NSIBINS		32	/* Size of server information hash table */
#define SIHASH(clientId) prpc_sibins[binarymod(clientId, NSIBINS)]
sinfo_t *prpc_sibins[NSIBINS];	/* Server information descriptor hash table */
sinfo_t *prpc_sinfos;		/* List of sinfo structures */

typedef struct prpc_handlerList
{
	struct prpc_handlerList *next; /* Next exception handler */
	int		(*handler)();	/* Exception handler to call */
} prpc_handlerList_t;

prpc_handlerList_t *prpc_serverHandlerList;	/* Server handlers attached */
prpc_handlerList_t *prpc_freeServerHandlerList;	/* Free handler structures */

typedef struct prpc_exceptionList
{
	struct prpc_exceptionList *next; /* Next exception */
	connection_t	*conn;		/* Connection affected */
	int		exception;	/* Exception type */
} prpc_exceptionList_t;

prpc_exceptionList_t *prpc_serverExceptionList;
prpc_exceptionList_t *prpc_lastServerException;
prpc_exceptionList_t *prpc_freeServerExceptionList;

int prpc_receiveCall();
int prpc_receiveAbort();
int prpc_serverException();

static caddr_t gettime();
static caddr_t failfunc();

rpc_entry_t prpcOptable[] =
{
	{ "gettime",	0,	gettime,	},
	{ "fail",	0,	failfunc,	},
};

rpc_serverClass_t prpcServerClass =
{
	0,	0,	"time",		5,
	prpc_receiveCall,	prpc_receiveAbort,	prpc_serverException,
	0,	2,
	2,	prpcOptable
};

sinfo_t *prpc_allocServerInfo();
sinfo_t *prpc_findSinfo();
sinfo_t *prpc_getSinfo();

prpc_init()
{
	rpc_serverAttach(&prpcServerClass);
}

prpc_serverMain()
{
	register struct proc *p = u.u_procp;
	register sinfo_t *sinfo;
	register int i;
	connection_t *lastClientConn = NULL;
	u_long lastClientId;
	extern realitexpire();

	/*
	 * Clear things that are no longer needed.
	 */
	p->p_sigignore = 0;
	p->p_sigcatch = 0;
	untimeout(realitexpire, (caddr_t)p);

	/*
	 * Get rid of the data and stack segments.
	 */
	expand(-u.u_dsize, 0);
	expand(-u.u_ssize, 1);

	/*
	 * Close all open files.
	 */
	for (i = 0; i < NOFILE; i++) {
		struct file *f;

		f = u.u_ofile[i];
		u.u_ofile[i] = NULL;
		u.u_pofile[i] = 0;
		closef(f);
	}

	/*
	 * Get a server information descriptor.
	 */
	sinfo = prpc_allocServerInfo();
	sinfo->procp = p;

	/*
	 * Main loop of the server.  Loop, doing work
	 * until we get killed then exit.
	 */
	for (;;) {

		int s = RPC_SPL();

		/*
		 * If we are not cacheing any information for
		 * a client then we set ourselves up to catch
		 * a local signal so that we can be killed.
		 */
		if (sinfo->count == 0) {
			p->p_sig = 0;
			p->p_sigmask = 0;
			p->p_sigignore = 0;
			p->p_sigcatch = 0;
			p->p_rpcflags &= ~RPCFLAG_AGENT;
			p->p_cursig = 0;
			/*
			 * Save state here so that longjmp will come
			 * here if we are interrupted by a signal (killing
			 * us).
			 */
			if (setjmp(&u.u_qsave)) {
				/* BUG only some signals should kill us */
				prpc_freeServerInfo(sinfo);
				exit(0);
			}
		}

		/*
		 * If there are some exceptions to be processed
		 * then call the exception handler.
		 */
		while (prpc_serverExceptionList)
			prpc_serverExceptionHandler();

		/*
		 * Sleep until we get a signal or a call
		 * packet with work to do.
		 */
		while (!sinfo->havecall && !prpc_serverExceptionList)
			sleep((caddr_t)sinfo, PZERO+1);
		splx(s);

		if (sinfo->havecall) {

			rpc_serverClass_t *classp = sinfo->classp;

			/*
			 * If are servicing a new client we
			 * must initialize some things.
			 */
			if (sinfo->clientConn != lastClientConn ||
			    sinfo->clientId != lastClientId) {
				lastClientConn = sinfo->clientConn;
				lastClientId = sinfo->clientId;
				p->p_sig = 0;
				p->p_sigmask = 0;
				p->p_sigignore = 0;
				p->p_sigcatch = 0;
			}

			/*
			 * Set some things to a known state before
			 * giving control to the service function.
			 */
			u.u_error = 0;

			if (sinfo->operation >= classp->noperations ||
			    classp->optable[sinfo->operation].call == NULL) {
				s = RPC_SPL();
				rpc_error(sinfo->clientConn, sinfo->clientId,
						EREMOTEOPERATION,
						sinfo->cookie);
			} else {
				caddr_t results;

				results = (caddr_t)(*classp->optable[sinfo->operation].call)
						(sinfo->clientConn,
						 sinfo->clientId,
						 sinfo->operation,
						 sinfo->params);
				s = RPC_SPL();
				u.u_procp->p_rpcflags &= ~RPCFLAG_ABORTED;
				if (results == NULL)
					rpc_error(sinfo->clientConn,
						  sinfo->clientId,
						  EREMOTERESOURCE,
						  sinfo->cookie);
				else
					rpc_return(sinfo->clientConn,
						   sinfo->clientId,
					     	   results,
						   sinfo->cookie);
			}
			sinfo->havecall = 0;
			--sinfo->count;
			splx(s);
		}
	}
}

prpc_receiveCall(clientConn, clientId, classp, operation, params, cookie)
   connection_t *clientConn;
   u_long clientId;
   rpc_serverClass_t *classp;
   u_short operation;
   caddr_t params;
   u_long cookie;
{
	register sinfo_t *sinfo;
	register struct proc *p;

	if ((sinfo = prpc_findSinfo(clientConn, clientId, cookie)) == NULL &&
	    (sinfo = prpc_getSinfo(clientConn, clientId, cookie)) == NULL) {
		rpc_freeParams(params);
		rpc_error(clientConn, clientId, EREMOTERESOURCE, cookie);
		return;
	}

	/*
	 * Mark the process as servicing a request
	 * so that the local system will not send
	 * signals to it.
	 */
	p = sinfo->procp;
	p->p_rpcflags |= RPCFLAG_AGENT;

	++sinfo->count;
	sinfo->havecall = 1;
	sinfo->clientConn = clientConn;
	sinfo->clientId = clientId;
	sinfo->classp = classp;
	sinfo->operation = operation;
	sinfo->params = params;
	sinfo->cookie = cookie;

	wakeup((caddr_t)sinfo);
}

prpc_receiveAbort(clientConn, clientId, cookie)
   connection_t *clientConn;
   u_long clientId;
   u_long cookie;
{
	register sinfo_t *sinfo;
	register struct proc *p;

	if ((sinfo = prpc_findSinfo(clientConn, clientId, cookie)) == NULL)
		return;
	p = sinfo->procp;
	p->p_rpcflags |= RPCFLAG_ABORTED;
}

/*
 * Must be called with interrupts locked out.
 */
prpc_serverException(conn, exception)
   connection_t *conn;
   int exception;
{
	register prpc_exceptionList_t *listp = prpc_lastServerException;

	/*
	 * If we are already aware of this exception
	 * then we do not add it to the list a second
	 * time.
	 */
	if (listp && listp->conn == conn && listp->exception == exception)
		return;

	if (listp = prpc_freeServerExceptionList)
		prpc_freeServerExceptionList = listp->next;
	else
		listp = (prpc_exceptionList_t *)calloc(sizeof(*listp), C_WAIT);
	conn_link(conn);	/* Once per 'listp' entry */
	listp->conn = conn;
	listp->exception = exception;
	listp->next = NULL;
	if (prpc_serverExceptionList)
		prpc_lastServerException->next = listp;
	else {
		register sinfo_t *sinfo;

		prpc_serverExceptionList = listp;
		for (sinfo=prpc_sinfos; sinfo; sinfo=sinfo->hashlink)
			if (sinfo->procp->p_stat == SSLEEP &&
			    sinfo->procp->p_wchan == (caddr_t)sinfo) {
				wakeup((caddr_t)sinfo);
				break;
			}
	}
	prpc_lastServerException = listp;
}

prpc_serverExceptionHandler()
{
	register prpc_exceptionList_t *listp;
	int s = RPC_SPL();

	while (listp = prpc_serverExceptionList)
	{
		register prpc_handlerList_t *shandp = prpc_serverHandlerList;

		prpc_serverExceptionList = listp->next;
		if (prpc_serverExceptionList == NULL)
			prpc_lastServerException = NULL;
		splx(s);

		for (; shandp; shandp=shandp->next)
			(*shandp->handler)(listp->conn, listp->exception);

		s = RPC_SPL();
		conn_free(listp->conn);	/* Once per 'listp' entry */

		listp->next = prpc_freeServerExceptionList;
		prpc_freeServerExceptionList = listp;
	}
	splx(s);
}

sinfo_t *
prpc_allocServerInfo()
{
	register sinfo_t *sinfo = (sinfo_t *)calloc(sizeof(sinfo_t), C_WAIT);

	sinfo->hashlink = prpc_sinfos;
	prpc_sinfos = sinfo;
	return(sinfo);
}

prpc_freeServerInfo(sinfo)
   register sinfo_t *sinfo;
{
	register sinfo_t **sinfop;

	for (sinfop = &prpc_sinfos; *sinfop; sinfop = &(*sinfop)->hashlink)
		if (*sinfop == sinfo)
			break;
	*sinfop = sinfo->hashlink;
}

sinfo_t *
prpc_findSinfo(clientConn, clientId, cookie)
   register connection_t *clientConn;
   register u_long clientId;
   register u_long cookie;
{
	register sinfo_t *sinfo;

	for (sinfo=prpc_sinfos; sinfo; sinfo=sinfo->hashlink)
		if (sinfo->clientConn == clientConn &&
		    sinfo->clientId == clientId &&
		    sinfo->cookie == cookie)
			return(sinfo);
	return(NULL);
}

sinfo_t *
prpc_getSinfo(clientConn, clientId, cookie)
   register connection_t *clientConn;
   register u_long clientId;
   register u_long cookie;
{
	register sinfo_t *sinfo;

	for (sinfo=prpc_sinfos; sinfo; sinfo=sinfo->hashlink)
		if (sinfo->count == 0)
			return(sinfo);
	return(NULL);
}

static caddr_t gettime(clientConn, clientId, operation, params)
   connection_t *clientConn;
   u_long clientId;
   u_short operation;
   caddr_t params;
{
	struct timeval *tv = (struct timeval *)rpc_allocResults(sizeof(*tv));
	int s;

	rpc_freeParams(params);
	if (tv == NULL)
		return(NULL);
	s = spl6();
	*tv = time;
	splx(s);
	return((caddr_t)tv);
}

static caddr_t failfunc(params)
   caddr_t params;
{
	rpc_freeParams(params);
	return((caddr_t)NULL);
}

prpc_attachServerHandler(handler)
   int (*handler)();
{
	prpc_handlerList_t *shandp;

	if (shandp = prpc_freeServerHandlerList)
		prpc_freeServerHandlerList = shandp->next;
	else
		shandp = (prpc_handlerList_t *)calloc(sizeof(*shandp), C_WAIT);
	shandp->handler = handler;
	shandp->next = prpc_serverHandlerList;
	prpc_serverHandlerList = shandp;
	return(1);
}

prpc_detachServerHandler(handler)
   int (*handler)();
{
	prpc_handlerList_t **shandpp = &prpc_serverHandlerList;

	for (; *shandpp; shandpp = &(*shandpp)->next)
		if ((*shandpp)->handler == handler)
		{
			prpc_handlerList_t *shandp = *shandpp;

			*shandpp = shandp->next;
			shandp->next = prpc_freeServerHandlerList;
			prpc_freeServerHandlerList = shandp;
			return(1);
		}
	return(0);
}

struct proc *
prpc_serverProc(clientConn, clientId, cookie)
   connection_t *clientConn;
   u_long clientId;
   u_long cookie;
{
	register sinfo_t *sinfo;

	if ((sinfo = prpc_findSinfo(clientConn, clientId, cookie)) &&
	    sinfo->count)
		return(sinfo->procp);
	return(NULL);
}
