/*
 * jam 841112-15
 * jam 850108-9-10
 */

#include "../h/param.h"
#include "../h/time.h"
#include "../h/proc.h"
#include "../h/errno.h"
#include "../vnet/vnet.h"
#include "../conn/conn.h"
#include "../rpc/rpc.h"

extern rpc_clientState_t *rpc_findClientState();
extern rpc_serverState_t *rpc_sStateBySeqno();
extern struct proc *prpc_serverProc();

struct siginfo
{
	long		sig;		/* Pending signals */
	long		sigmask;	/* Current signal mask */
	long		sigignore;	/* Signals being ignored */
	long		sigcatch;	/* Signals being caught by user */
	short		cursig;		/* Current signal */
};

struct psignalParams
{
	u_long		clientId;	/* Client we are referring to */
	u_long		seqno;		/* Sequence number being signalled */
	short		signum;		/* Signal number being sent */
	struct siginfo	siginfo;	/* Process signal information */
};

struct psignalResults
{
	u_long		clientId;	/* Client we are referring to */
	u_long		seqno;		/* Sequence number being signalled */
	short		signum;		/* Signal number being sent */
	u_short		flags;		/* Flags for process change */
	struct siginfo	siginfo;	/* Process signal information */
};

struct changeParams
{
	u_long		clientId;	/* Client we are referring to */
	u_long		seqno;		/* Sequence number being signalled */
	u_short		flags;		/* Flags from server end */
	struct siginfo	siginfo;	/* Process signal information */
};

struct changeResults
{
	u_long		clientId;	/* Client we are referring to */
	u_long		seqno;		/* Sequence number that was signalled */
};

/*
 * Values for the psignalResults.flags
 * field.
 */
#define DELIVERED	0x0001		/* Signal was delivered */
#define STOPPED		0x0002		/* Server process is stopped */
#define UNUSABLE	0xFF00		/* Unusable (for compatibility) */


int rpcsignal_return();
int rpcsignal_error();
int rpcsignal_server();

#ifdef DEBUG
int rpcsignal_debug = 0;
#endif DEBUG

rpc_clientClass_t signalClientClass =
{
	0,	0,	"Signal",	RPC_CLASS_SIGNAL,
	rpcsignal_return,	rpcsignal_error,	NULL,
	0,	2,
};

rpc_serverClass_t signalServerClass =
{
	0,	0,	"Signal",	RPC_CLASS_SIGNAL,
	rpcsignal_server,	NULL,			NULL,
	0,	2,
	0,	NULL
};

rpcsignal_init()
{
	rpc_serverAttach(&signalServerClass);
	rpc_clientAttach(&signalClientClass);
}

/*
 * Forward a psignal() call to a remote server
 * so that the process THREAD  will respond to
 * signals (almost) identically in the remote
 * and purely local cases.  This function must
 * be called at spl6().
 */
rpcsignal_psignal(p, signum)
   register struct proc *p;
   int signum;
{
	struct psignalParams *params;
	rpc_clientState_t *cstate;

	/* BUG what if more than one signal is outstanding */
	if ((cstate = rpc_findClientState((u_long)p)) == NULL) {
		return(0);
	}

	/*
	 * If the system to which we are sending the
	 * signal is lower than version 0x0001 then
	 * it would choke if we sent the signal so
	 * we just let it go here.
	 */
	if (cstate->conn->version < 0x0001)
		return;

	/*
	 * Perform some local processing which would
	 * normally be done by psignal() for the local
	 * case.
	 */
	switch (signum)
	{
	case SIGTSTP:
	case SIGTTIN:
	case SIGTTOU:
		/*
		 * Test copied from psignal(): do not clog
		 * system with children of init stopped from
		 * the terminal.
		 */
		if (p->p_pptr == &proc[1]) {
			psignal(p, SIGKILL);
			p->p_sig &= ~(1 << (signum-1));
			return;
		}
		/* Fall through */
	case SIGSTOP:
		/*
		 * Test copied from psignal(): if we are a
		 * child in vfork(), stopping could cause
		 * deadlock.
		 */
		if (p->p_flag & SVFORK)
			return;
		break;
	}

	params = (struct psignalParams *)rpc_allocParams(sizeof(*params));
	params->clientId = (u_long)p;
	params->seqno = cstate->seqno;
	params->signum = signum;
	rpcsignal_getinfo(p, &params->siginfo);

	p->p_rpcflags |= RPCFLAG_SIGNAL;
#ifdef DEBUG
	if (rpcsignal_debug)
		printf("rpcsig: Cpsig %08X %08X %2d      %08X %08X %08X %08X %d\n",
			params->clientId, params->seqno, params->signum,
			params->siginfo.sig, params->siginfo.sigmask,
			params->siginfo.sigignore, params->siginfo.sigcatch,
			params->siginfo.cursig);
#endif DEBUG
	rpc_call(signalclient(p), &signalClientClass, cstate->conn,
		 RPC_CLASS_SIGNAL, RPCSIGNAL_PSIGNAL, params);
	return(1);
}

rpcsignal_remotePsignal(clientConn, clientId, params, cookie)
   connection_t *clientConn;
   u_long clientId;
   register struct psignalParams *params;
   u_long cookie;
{
	register struct proc *p;
	register struct psignalResults *results;
	register rpc_serverState_t *sstate;

#ifdef DEBUG
	if (rpcsignal_debug)
		printf("rpcsig: Spsig %08X %08X %2d      %08X %08X %08X %08X\ %dn",
			params->clientId, params->seqno, params->signum,
			params->siginfo.sig, params->siginfo.sigmask,
			params->siginfo.sigignore, params->siginfo.sigcatch,
			params->siginfo.cursig);
#endif DEBUG
	/*
	 * Allocate the result packet and fill it in
	 * so that it indicates that we were unable
	 * to deliver the signal.
	 */
	results = (struct psignalResults *)rpc_allocResults(sizeof(*results));
	results->clientId = params->clientId;
	results->seqno = params->seqno;
	results->signum = params->signum;
	/* BUG the 0x0100 is for compatibility */
	results->flags = 0x0100;

#define DELIVERSIGNALS
#ifdef DELIVERSIGNALS
	/*
	 * If we can find the server for the client
	 * specified in the request then we send the
	 * signal to it and return any information
	 * in the proc structure which may have changed.
	 */
	sstate =
	     rpc_sStateBySeqno(&clientConn->node, params->clientId, cookie);

	if (sstate && sstate->state == RPCSTATE_WORKING && 
	    (p = prpc_serverProc(clientConn, params->clientId, cookie))) {
		rpcsignal_putinfo(&params->siginfo, p);
		psignal1(p, params->signum);
		results->flags |= DELIVERED;
		if (p->p_stat == SSTOP)
			results->flags |= STOPPED;
		rpcsignal_getinfo(p, &results->siginfo);
	}
#endif DELIVERSIGNALS

	rpc_freeParams(params);
#ifdef DEBUG
	if (rpcsignal_debug)
		printf("rpcsig: Spret %08X %08X %2d %04x %08X %08X %08X %08X %d\n",
			results->clientId, results->seqno, results->signum,
			results->flags,
			results->siginfo.sig, results->siginfo.sigmask,
			results->siginfo.sigignore, results->siginfo.sigcatch,
			results->siginfo.cursig);
#endif DEBUG
	rpc_return(clientConn, clientId, results, cookie);
}

rpcsignal_psignalReturn(clientId, results)
   u_long clientId;
   struct psignalResults *results;
{
	register struct proc *p = clientproc(results->clientId);

#ifdef DEBUG
	if (rpcsignal_debug)
		printf("rpcsig: Cpret %08X %08X %2d %04x %08X %08X %08X %08X %d\n",
			results->clientId, results->seqno, results->signum,
			results->flags,
			results->siginfo.sig, results->siginfo.sigmask,
			results->siginfo.sigignore, results->siginfo.sigcatch,
			results->siginfo.cursig);
#endif DEBUG
	if (results->flags & DELIVERED) {
		rpcsignal_putinfo(&results->siginfo, p);
		if (!(results->flags & STOPPED) && p->p_stat != SSLEEP)
		{
			p->p_cursig = 0;
			p->p_stat = SSLEEP;
		}
		else if ((results->flags & STOPPED) && p->p_stat != SSTOP)
			stop(p);
	}

	p->p_rpcflags &= ~RPCFLAG_SIGNAL;

	/*
	 * If the client process has already
	 * received a return (or error) then
	 * we are responsible for waking it
	 * up.
	 */
	if (!(p->p_rpcflags & RPCFLAG_CLIENT))
		wakeup((caddr_t)&p->p_rpcresults);

	rpc_freeResults(results);
}

/*
 * If we got an error from our signal
 * call instead of a return then we tell
 * conn that we suspect that the node is
 * down which will cause a cleanup on our
 * side.
 */
rpcsignal_psignalError(clientId, error)
   u_long clientId;
   int error;
{
	register struct proc *p = clientproc(clientId);
	register rpc_clientState_t **cstatep;
	register rpc_clientState_t *cstate;

#ifdef NoLonger
	printf("rpcsignal_psignalError: signal did not get through\n");
#endif NoLonger
	/*
	 * Through the miracle of poor design, the call to conn_suspect
	 * will eventually force a call upon this same routine
	 * via the various and sundry RPC exception handlers giving us 
	 * a recursive cycle into the bowels of hell.  Thus this lock
	 * via the proc structure.
	 * Hallelujah brothers and sisters.
	 * sas 851001
	 */
	if (p->p_rpcflags & RPCFLAG_SIGERR)
		return;
	p->p_rpcflags |= RPCFLAG_SIGERR;
	efsDEBUG("rpcsignal_psignalError/I", "client id 0x%x", clientId);

	/*
	 * If the client process has already
	 * received a return (or error) then
	 * we are responsible for waking it
	 * up.
	 */
	if (!(p->p_rpcflags & RPCFLAG_CLIENT))
		wakeup((caddr_t)&p->p_rpcresults);

	if (!(p->p_rpcflags & RPCFLAG_SIGNAL) ||
	    (cstate = rpc_findClientState((u_long)p)) == NULL)
		/*
		 * Since SIGERR is really a lock around conn_suspect(),
		 * we should unset the lock before we exit.
		 */
		goto out;

	p->p_rpcflags &= ~RPCFLAG_SIGNAL;
	if (cstate->state == RPCSTATE_CALLING ||
	    cstate->state == RPCSTATE_WANTRETURN)
		conn_suspect(cstate->conn->name);

out:
	p->p_rpcflags &= ~RPCFLAG_SIGERR;
}

rpcsignal_server(clientConn, clientId, classp, op, params, cookie)
   connection_t *clientConn;
   u_long clientId;
   rpc_serverClass_t *classp;
   int op;
   caddr_t params;
   u_long cookie;
{
	switch (op)
	{
	case RPCSIGNAL_PSIGNAL:
		rpcsignal_remotePsignal(clientConn, clientId, params, cookie);
		break;

	case RPCSIGNAL_CHANGE:
		rpcsignal_remoteChange(clientConn, clientId, params, cookie);
		break;

	default:
		rpc_error(clientConn, clientId, EREMOTEOPERATION, cookie);
		break;
	}
}

rpcsignal_return(clientId, results)
   u_long clientId;
   caddr_t results;
{
	switch (clienttype(clientId))
	{
	case SIGNALCLIENT:
		rpcsignal_psignalReturn(clientId, results);
		break;

	case CHANGECLIENT:
	default:
		rpc_freeResults(results);
		break;

	}
}

rpcsignal_error(clientId, error)
   u_long clientId;
   int error;
{
	switch (clienttype(clientId))
	{
	case SIGNALCLIENT:
		rpcsignal_psignalError(clientId, error);
		break;

	case CHANGECLIENT:
		/* BUG what to do in this case */
	default:
		break;

	}
}

/*
 * A server has changed due to the processing
 * of a signal which was sent earlier.  We
 * send the information which (may have) changed
 * to the client so that it will be up-to-date.
 */
rpcsignal_change(p)
   register struct proc *p;
{
	register struct changeParams *params;
	/* BUG the next line needs to be changed */
	rpc_clientState_t *cstate = 0;

	params = (struct changeParams *)rpc_allocParams(sizeof(*params));
	/* BUG the next two lines need to be changed */
	params->clientId = 0;
	params->seqno = 0;
	params->flags = DELIVERED;
	if (p->p_stat == SSTOP)
		params->flags |= STOPPED;
	rpcsignal_getinfo(p, &params->siginfo);
	rpc_call(changeclient(p), &signalClientClass, cstate->conn,
		 RPC_CLASS_SIGNAL, RPCSIGNAL_CHANGE, params);
}

/*
 * The server for a process on our machine has
 * possibly changed due to processing a signal
 * which we sent earlier.  Update the client
 * so that it reflects the changes.
 */
rpcsignal_remoteChange(clientConn, clientId, params, cookie)
   connection_t *clientConn;
   u_long clientId;
   register struct changeParams *params;
   u_long cookie;
{
	register struct proc *p = (struct proc *)params->clientId;
	register struct changeResults *results;

	/*
	 * Copy all of the information from the
	 * change packet to the process whose
	 * thread was changed.
	 */
	if (params->flags & DELIVERED) {
		rpcsignal_putinfo(&params->siginfo, p);
		if (!(params->flags & STOPPED) && p->p_stat != SSLEEP)
		{
			p->p_cursig = 0;
			p->p_stat = SSLEEP;
		}
		else if ((params->flags & STOPPED) && p->p_stat != SSTOP)
			stop(p);
	}

	/*
	 * Acknowledge that we received the
	 * change information.
	 */
	results = (struct changeResults *)rpc_allocResults(sizeof(*results));
	results->clientId = params->clientId;
	results->seqno = params->seqno;
	rpc_freeParams(params);
	rpc_return(clientConn, clientId, results, cookie);
}

rpcsignal_getinfo(p, siginfo)
   register struct proc *p;
   register struct siginfo *siginfo;
{
	siginfo->sig = p->p_sig;
	siginfo->sigmask = p->p_sigmask;
	siginfo->sigignore = p->p_sigignore;
	siginfo->sigcatch = p->p_sigcatch;
	siginfo->cursig = p->p_cursig;
}

rpcsignal_putinfo(siginfo, p)
   register struct siginfo *siginfo;
   register struct proc *p;
{
	p->p_sig = siginfo->sig;
	p->p_sigmask = siginfo->sigmask;
	p->p_sigignore = siginfo->sigignore;
	p->p_sigcatch = siginfo->sigcatch;
	p->p_cursig = siginfo->cursig;
}
