/*
 * servercallbacks.c
 * The guts of the server-side application logic.
 *
 * Copyright (C) Leni Mayo 2001-2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *	
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h> /* for free()     */
#include <string.h> /* for strerror() */
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <glib.h>
#include "command.h"
#include "fsm.h"
#include "log.h"
#include "node.h"
#include "edge.h"
#include "persist.h"
#include "pidmap.h"
#include "msgqueue.h"
#include "servercommon.h"
#include "serviceid.h"
#include "serverhandle.h"
#include "servercallbacks.h"

#ifndef SEREL_SERVER_NO_RDF
#include "rdfserver.h"
#include "rdfservercallbacks.h"
#endif

/*
 * Notes
 *
 * The msgqueue stuff allows us to separate the fsm event
 * generation and dispostion from the handling of the Commands.
 *
 * The Callbacks just respond to the Command and add fsm events to the queue.
 *
 * callbackPre() is reponsible for sucking messages off the queue
 * and dispatching events to the Node fsms.
 */

CallbackReturnCode vtCallbackCommand(CommandType cmd, const Command *pCommand);
CallbackReturnCode callbackCommandRegisterNeed(const Command *pCommand);
CallbackReturnCode callbackCommandRegisterProvide(const Command *pCommand);
CallbackReturnCode callbackCommandDeregister(const Command *pCommand);
CallbackReturnCode callbackCommandStatusChange(const Command *pCommand);
CallbackReturnCode callbackCommandServerControl(const Command *pCommand);
CallbackReturnCode callbackCommandQuery(const Command *pCommand);
CallbackReturnCode callbackCommandViaFile(const Command *pCommand);

void unblockDeregistrant(const ServiceId id, void *pUserData); 

static void transition(const ServiceId id, Event event);
static void sendSignal(Pid pid, CallbackReturnCode code);

static void onStateEntry(Node *pNode, State state);
static void unblockNeeders(Edges *pEdges, ServiceId id, CallbackReturnCode retCode);
static void unblockProvidingEdges(Edges *pEdges, ServiceId id, CallbackReturnCode retCode);
static void unblockEdge(Edge *pEdge, CallbackReturnCode retCode);

static Nodes   *pNodes;     /* pointer to an Nodes structure */
static Edges   *pEdges;     /* pointer to an Edges structure */
static PidMap  *pPidmap;

#ifndef SEREL_SERVER_NO_RDF
static RdfServer *pRdfServer;
#endif

/*
 * serverCallbackInitialise()
 *
 * Returns
 *   TRUE if it initialised ok, FALSE otherwise
 */
int serverCallbackInitialise()
{
	int ret;

	ret = serviceIdInitialise();
	msgqueueInitialise();

	pNodes     = nodesNew();
	pEdges     = edgesNew(unblockDeregistrant);
	pPidmap    = pidmapNew();

	if (pNodes == NULL || pEdges == NULL || pPidmap == NULL)
	{
		ret = FALSE;
	}

#ifndef SEREL_SERVER_NO_RDF
	if (ret)
	{
		pRdfServer = rdfServerNew();

		ret = pRdfServer != NULL;
	}
#endif

	return ret;
}

void serverCallbackCleanup()
{
	CallbackReturnCode codeFailure = eReturnFailure;
	GList *pElement;
	Edge *pEdge;

	/* fail blocked deregistrants
	*/
	if (pNodes)
	{
		nodesForeach(pNodes, unblockDeregistrant, &codeFailure);
	}

	/* fail blocked needers
	*/
	if (pEdges)
	{
		for (pElement = edgesGet(pEdges); pElement != NULL; pElement = pElement->next)
		{
			pEdge = pElement->data;

			if (pEdge->pUserData != NULL)
			{
				sendSignal(pEdge->pUserData->pidReply, codeFailure);

				free(pEdge->pUserData);
				pEdge->pUserData = NULL;
			}
		}
	}

	nodesDelete(pNodes);
	edgesDelete(pEdges);
	pidmapDelete(pPidmap);

#ifndef SEREL_SERVER_NO_RDF
	rdfServerDelete(pRdfServer);
#endif

	msgqueueCleanup();
	serviceIdCleanup();
}

void callbackCommand(const Command *pCommand)
{
	CallbackReturnCode code;

	assert(pCommand != NULL);
	assert(pCommand->c < eCommandTypeMax);

	printLog(eLogLevelDeveloper, "received: %s\n", commandAsString(pCommand));

	code = vtCallbackCommand(pCommand->c, pCommand);

	assert(code != eReturnUndefined);

	if (code != eReturnNone)
	{
		sendSignal(pCommand->pidReply, code);
	}
}

/*
 * callbackCommandRegisterProvide()
 * Creates a Node
 */
CallbackReturnCode callbackCommandRegisterProvide(const Command *p)
{
	const CommandRegister *pCommand = (CommandRegister *) p;
	Node *pNode;
	ServiceId id;
	int ret = eReturnUndefined;
	StopwatchTime time;

	assert(pCommand);

	getStopwatchTime(&time);

	pNode = nodeFromName(pNodes, pCommand->name);

	if ((!pNode || pNode->fsm == eStateRegisterFailed) &&
	    !pidmapToNode(pPidmap, pCommand->pid))
	{
		if (!pNode)
		{
			/* id might have been added by a needer
			*/
			id = serviceIdFromName(pCommand->name);

			if (id == 0)
			{
				id = serviceIdAdd(pCommand->name);
			}

			pNode = nodeAdd(pNodes, id);
		}
		else
		{
			assert(pNode->fsm == eStateRegisterFailed);
			id = pNode->id;
		}

		pidmapAdd(pPidmap, pCommand->pid, pNode);

		pidmapStopwatchStart(pPidmap, pCommand->pid, &time);

		msgqueueAdd(id, eEventRegister);

		ret = eReturnSuccess;
	}
	else
	{
		/*
		 * either there's another provider of the same name, or
		 * this pid is already a provider of another name
		 * Guarantees a 1:1 mapping of pid:name
		 *
		 * Also note that the stopwatch doesn't get started for
		 * failed providers.
		 */
		ret = eReturnFailure;
	}

	return ret;
}

/*
 * callbackCommandRegisterNeed()
 * Creates a Edge
 */

CallbackReturnCode callbackCommandRegisterNeed(const Command *p)
{
	const CommandRegister *pCommand = (CommandRegister *) p;
	ServiceId idProvider;
	Node *pNode;
	StopwatchTime time;
	int ret = eReturnUndefined;

	assert(pCommand);

	getStopwatchTime(&time);

	pNode      = pidmapToNode(pPidmap, pCommand->pid);
	idProvider = serviceIdFromName(pCommand->name);

	if (pNode == NULL)
	{
		/* can't be a needer unless you've registered as a provider
		*/
		ret = eReturnFailure;
	}
	else if (pNode->id == idProvider)
	{
		/* Can't provide a service and need it too
		*/
		ret = eReturnFailure;
	}
	else
	{
		Node  *pNodeProvider = nodeFromId(pNodes, idProvider);
		State state = (pNodeProvider != NULL) ? pNodeProvider->fsm : eStateSpirit;

		if (idProvider == 0)
		{
			/*
			 * leni Note: Note that if the provider never shows up,
			 * the ServiceId name::id mapping is currently left dangling
			 * (ie. no Node references the mapping), so 
			 * it's potentially hanging around for ever.
			 * Of course if the name shows up again, the id gets reused.
			 * We could put some code to remove dangling references in
			 * callbackStatusChange, but the test to check for no references
			 * amongst the Edges has complexity O(n).  A C++ implementation
			 * would use a reference-counted object.
			 */
			idProvider = serviceIdAdd(pCommand->name);
		}

		switch (state)
		{
			case eStateSpirit:
			case eStateOnTheWayIn:
			{
				Edge *pEdge;
				EdgeUserData *pUserData;

				/*
				 * the signal gets sent as a side effect of the
				 * provider's transition to either
				 * eStateIn or eStateRegisterFailed
				 */
				pEdge = edgesAdd(pEdges, idProvider, pNode->id);
				assert(pEdge); /* leni TODO: memory allocation */
				
				/* memory is freed during by the callback
				*/
				pUserData = malloc(sizeof(EdgeUserData));
				pUserData->pid = pCommand->pid;
				pUserData->pidReply = pCommand->cmd.pidReply;
				pEdge->pUserData = pUserData;

				ret = eReturnNone;
				break;
			}
			case eStateRegisterFailed:
			case eStateOnTheWayOut:
			{
				ret = eReturnFailure;
				break;
			}
			case eStateIn:
			{
				edgesAdd(pEdges, idProvider, pNode->id);

				ret = eReturnSuccess;
				break;
			}
			default:
				assert(FALSE);
		}
	}

	if (ret == eReturnNone)
	{
		pidmapStopwatchStop(pPidmap, pCommand->pid, &time);
	}

	return ret;
}


CallbackReturnCode callbackCommandDeregister(const Command *p)
{
	const CommandRegister *pCommand = (CommandRegister *) p;
	ServiceId id;
	Node *pNode;
	StopwatchTime time;
	int ret = eReturnUndefined;

	getStopwatchTime(&time);

	assert(pCommand);

	/*
	 * Note that testing for the service name : id mapping being present isn't
	 * equivalent to testing for the Node being present with state eStateIn
	 */
	id = serviceIdFromName(pCommand->name);

	pNode = (id != 0) ? nodeFromId(pNodes, id) : NULL;

	if (id != 0 && pNode && pNode->fsm == eStateIn && pidmapToNode(pPidmap, pCommand->pid) == NULL)
	{
		if (edgesNeedersOf(pEdges, id) == NULL)
		{
			pidmapAdd(pPidmap, pCommand->pid, pNode);

			msgqueueAdd(id, eEventDeregister);

			/*
			 * There's not a huge amount of use in setting the stopwatch
			 * for deregistering pids, because their Nodes disappear
			 * immediately after the process exits.
			 * We do it for completeness anway, and in case we 
			 * ever want to debug shutdown times.
			 * leni - Wed Mar 20 13:12:08 EST 2002
			 */
			pidmapStopwatchStart(pPidmap, pCommand->pid, &time);

			ret = eReturnSuccess;
		}
		else
		{
			/*
			 * deregistrant has a needer.
			 * signal is sent when last needer transitions to the initial
			 * state pidmap entry is added at that time also.
			 * leni TODO: hm, what happens if we get a status change event
			 * before then?  We could assume that it's for the deregistrant
			 * and add the appropriate transitions from eStateIn, as it is
			 * now we just ignore it 'cos the serviceid entry hasn't been
			 * added...
			 */

			NodeUserData *pUserData;

			pUserData = malloc(sizeof(NodeUserData));
			pUserData->pidReply = pCommand->cmd.pidReply;
			pUserData->pid = pCommand->pid;

			pNode->pUserData = pUserData;

			/*
			 * Note:
			 * If we ever allow more than one unprovide to be called from
			 * the same pid then we would need to:
			 * - rethink the relationship to Pidmap.
			 *   eg. adding to pidmap in the unblock function won't work,
			 *   'cos it might already be there, ie added by the first
			 *   unprovide.
			 *
			 *  - turn the stopwatch off here in the unblock, 'cos it might
			 *    have been turned on earlier.
			 */
			ret = eReturnNone;
		}
	}
	else
	{
		/*
		 * either the named service hasn't been successfully registered
		 * (in which case it can't be deregistered), or
		 * this process is a provider so it can't also be a unprovider
		 */
		ret = eReturnFailure;
	}

	return ret;
}

CallbackReturnCode callbackCommandStatusChange(const Command *p)
{
	const CommandStatusChange *pCommand = (CommandStatusChange *) p;
	Node *pNode = pidmapToNode(pPidmap, pCommand->pid);
	StopwatchTime time;
	int ret = eReturnUndefined;

	getStopwatchTime(&time);

	if (pNode)
	{
		/*
		 * return true iff the command is telling
		 * us something about a registering or deregistering provider.
		 * It might be a little rude to the caller to return false
		 * simply because the message is useless in the absence of a
		 * corresponding Node.
		 */
		Event event = (pCommand->status == ePidStatusSuccess) ?
		                     eEventProcessSuccess : eEventProcessFailure;
		msgqueueAdd(pNode->id, event);

		pidmapStopwatchStop(pPidmap, pCommand->pid, &time);

		pNode->stopwatch = pidmapStopwatchTime(pPidmap, pCommand->pid);

		/*
		 * once we've received an exit status, it's reasonable to
		 * assume that the process isn't around anymore
		 */

		pidmapRemove(pPidmap, pCommand->pid);

		/*
		 * leni TODO: at this point, we could try and clean up
		 * service id:name mappings created by needers
		 * ie. if there's no provider and no other needer of this
		 * node's providers, we don't need the mapping.
		 * Problem is that doing the cleanup has complexity O(n).
		 */

		ret = eReturnSuccess;
	}
	else
	{
		/* leni TODO: might be nice to log a warning message here
		*/
		ret = eReturnFailure;
	}

	return ret;
}

CallbackReturnCode callbackCommandServerControl(const Command *p)
{
	const CommandServerControl *pCommand = (CommandServerControl *) p;
	int ret = eReturnSuccess;

	assert(pCommand != NULL);

	serverhandle.fContinueEventLoop = FALSE;

	return ret;
}

CallbackReturnCode callbackCommandQuery(const Command *p)
{
	const CommandQuery *pCommand = (CommandQuery *) p;
	int ret = eReturnFailure;
	FILE *fp;

	assert(pCommand != NULL);

	fp = fopen(pCommand->fileChannel, "w");

	if (fp == NULL)
	{
		printLog(eLogLevelError, "Unable to open file for writing: \"%s\" - %s\n",
		              pCommand->fileChannel, strerror(errno));
	}
	else
	{
		ret = eReturnSuccess;

		switch (pCommand->code)
		{
		case eQueryCodeStatus:
		{
			Node *pNode = nodeFromName(pNodes, pCommand->name);
			const char *pszResult;
			
			pszResult = pNode ? stateAsString(pNode->fsm) : "service unknown";

			fprintf(fp, "%s", pszResult);

			break;
		}
		case eQueryCodeGraph:
		{
			print_as_xml_rdf_graph(fp, pNodes, pEdges, pCommand->name, pCommand->fileAdd);

			break;
		}
		default:
			assert(NOTHERE);
		}

		fclose(fp);
	}

	return ret;
}

CallbackReturnCode callbackCommandViaFile(const Command *p)
{
	const CommandViaFile *pCommand = (CommandViaFile *) p;
	CallbackReturnCode ret = eReturnFailure;
	char *pszBigString;
	const char *pszNotImplemented = "server feature not implemented on this platform.\n";
	FILE *fp;

	assert(pCommand != NULL);

#ifndef SEREL_SERVER_NO_RDF
	fp = fopen(pCommand->fileChannel, "r");

	if (fp == NULL)
	{
		printLog(eLogLevelError, "Unable to open channel to client, open for read failed: \"%s\" - %s\n",
		              pCommand->fileChannel, strerror(errno));
	}

	pszBigString = stringFromFp(fp);

	fclose(fp);

	/*
	 * Server messages sometimes need to go back to the client.
	 * Open the channel here.
	 */

	fp = fopen(pCommand->fileChannel, "w");

	if (fp == NULL)
	{
		printLog(eLogLevelError, "Unable to open file for writing: \"%s\" - %s\n",
		              pCommand->fileChannel, strerror(errno));
	}
	else
	{
		/* fprintf(stdout, "Recieved ViaFile code %s:\n", viaFileCodeAsString(pCommand->code)); */

		switch (pCommand->code)
		{
		case eViaFileCodeRosterApply:
		{
			if (pRdfServer->state == eRdfServerStatePopulateSuccess)
			{
				ret = callbackRosterQueryCodeApply(pRdfServer, fp, pszBigString);
			}

			break;
		}
		case eViaFileCodeRosterApplyStarted:
		{
			ret = callbackRosterApplyStarted(pRdfServer, fp, pszBigString);

			break;
		}
		case eViaFileCodeRosterPopulate:
		{
			/*
			 *	Ideally, this routine would be split so that the server:
			 *	1. parses the --populate and restores the rdf database
			 *	2. responds to the client, unblocking it
			 *	3. sends itself a message telling itself to prepare & verify the graph.
			 *
			 *	But right now, the "server sending itself a message" mechanism
			 *	only works for fsm-related events.  Will refactor at some point.
			 *	leni - Wed Apr 17 07:32:11 EST 2002
			 */
			ret = callbackRosterPopulate(pRdfServer, fp, pszBigString);

			break;
		}
		case eViaFileCodeRosterQuery:
		{
			RosterQueryCode rqCode = atoi(pszBigString);

			switch(rqCode)
			{
			case eRosterQueryCodeVerify:
			{
				char *pszMessage;

				ret = eReturnSuccess;

				switch (pRdfServer->state)
				{
					case eRdfServerStateInitial:
						pszMessage = "Error: must run '--populate' before '--query'\n";
						break;
					case eRdfServerStatePopulateFailed:
						pszMessage = pRdfServer->pszErrorMessage;
						break;
					case eRdfServerStatePopulateSuccess:
						pszMessage = "success\n";
						break;
					default:
						assert(NOTHERE);
				}

				fprintf(fp, "%s", pszMessage);
				break;
			}
			case eRosterQueryCodePriorHistory:
			{
				char *pszMessage;

				ret = eReturnSuccess;

				switch (pRdfServer->state)
				{
					case eRdfServerStateInitial:
						pszMessage = "Error: must run '--populate' before '--query'\n";
						break;
					case eRdfServerStatePopulateFailed:
						pszMessage = pRdfServer->pszErrorMessage;
						break;
					case eRdfServerStatePopulateSuccess:
						if (pRdfServer->fHistoryUsed)
						{
							pszMessage = "success\n";
						}
						else
						{
							pszMessage = "history rebuilt";
						}
						break;
					default:
						assert(NOTHERE);
				}

				fprintf(fp, "%s", pszMessage);
				break;
			}
			default:
				fprintf(fp, "%s", pszNotImplemented);
				break;
			}

			break;
		}
		default:
			assert(NOTHERE);
		}

		free(pszBigString);
		pszBigString = NULL; /* dont reference after free() */

		fclose(fp);
	}
#else
	fp = fopen(pCommand->fileChannel, "w");

	if (fp == NULL)
	{
		printLog(eLogLevelError, "Unable to open file for writing: \"%s\" - %s\n",
		              pCommand->fileChannel, strerror(errno));
	}
	else
	{
		fprintf(fp, "server feature not implemented on this platform\n");
		fclose(fp);
	}
#endif

	return ret;
}


/*
 * This function is called by the Handler *after* a callbackCommand*
 * returned but before waiting for a new event.
 * This function's job is to dispatch pending Events to Nodes.
 */
void callbackPre()
{
	int found;
	ServiceId id;
	Event event;

	found = msgqueueRemove(&id, &event);

	while (found)
	{
		transition(id, event);
		found = msgqueueRemove(&id, &event);
	}

	if (logLevel() == eLogLevelDeveloper)
	{
		char buf[5000];

		buf[0] = '\0';
		printLog(eLogLevelDeveloper, "Nodes:\n%s", nodesAsString(pNodes, buf, sizeof(buf)));
		buf[0] = '\0';
		printLog(eLogLevelDeveloper, "Edges:\n%s", edgesAsString(pEdges, buf, sizeof(buf)));
		buf[0] = '\0';
		printLog(eLogLevelDeveloper, "Pidmap:\n%s", pidmapAsString(pPidmap, buf, sizeof(buf)));
		buf[0] = '\0';
		printLog(eLogLevelDeveloper, "ServiceIds:\n%s", serviceIdsAsString(buf, sizeof(buf)));
	}
}

/*
 * send a signal to the client process
 *
 * Note:
 * It might be cleaner to have hidden sendSignal() from the callbacks
 * and instead present a msgque-style interface, ie: push (pid, code).
 * This would make for consistent usage of notion of "handle" as a
 * sink/source of events.
 */
static void sendSignal(Pid pid, CallbackReturnCode code)
{
	int sig;

	assert(code == eReturnSuccess || code == eReturnFailure);

	sig = (code == eReturnSuccess) ? SIG_RETURNSUCCESS : SIG_RETURNFAIL;

	/*
	 * Don't mess with init.  Don't want to mess with keventd or swapper
	 * either, but don't know how to make a portable assertion for that.
	 */
	assert(pid > 1);

	kill(pid, sig);

	printLog(eLogLevelDeveloper, "sig sent: (%d, %s)\n", pid, (code == eReturnSuccess ? "success" : "fail"));
}

void transition(const ServiceId id, Event event)
{
	State stateNext;
	int   fTransitionOccurred;
	Node  *pNode = nodeFromId(pNodes, id);

	assert(pNode);

	fTransitionOccurred = fsmStateNext(&stateNext, pNode->fsm, event);

	if (fTransitionOccurred)
	{
		pNode->fsm = stateNext;

		onStateEntry(pNode, stateNext);
	}
}

/*
 * Note that the node is transitioned into the new state before this
 * function is called.
 */
void onStateEntry(Node *pNode, State state)
{
	switch(state)
	{
	    case eStateSpirit:
		{
			edgesRemoveNode(pEdges, pNode->id);

			nodeRemove(pNodes, pNode->id);
			break;
		}
		case eStateOnTheWayIn:  break;
		case eStateOnTheWayOut: break;

		case eStateRegisterFailed:
		{
			unblockNeeders(pEdges, pNode->id, eReturnFailure);
			unblockProvidingEdges(pEdges, pNode->id, eReturnFailure);

			break;
		}
		case eStateIn:
		{
			unblockNeeders(pEdges, pNode->id, eReturnSuccess);
			unblockProvidingEdges(pEdges, pNode->id, eReturnFailure);

			break;
		}
		default:
			assert(FALSE);
	}
}

/*
 * Note the similarity b/n unblockNeeders() and unblockProvidingEdges().
 */
void unblockNeeders(Edges *pEdges, ServiceId id, CallbackReturnCode retCode)
{
	GList *pList;
	GList *pElement;

	pList = edgesNeedersOf(pEdges, id); 

	for (pElement = pList; pElement != NULL; pElement = pElement->next)
	{
		unblockEdge(pElement->data, retCode);
	}
}

/*
 * void unblockProvidingEdges()
 * 
 * If (somehow) a Node has transitioned to eStateIn or eStateRegisterFailed
 * and there are still Edges that indicate that there are blocked need commands
 * out there, go ahead and unblock them.
 * There are only two wierd scenarios where this might happen:
 * a) someone explicity sends a "statuschange pid status" command prior
 *    to the service script exiting, perhaps in order to force
 *    the Node into a failed or success state
 * b) someone kills the blocked "need" allowing the calling script to continue
 *    and subsequently exit
 * 
 * This function is about cleaning up the zombie process in case a) and
 * freeing the EdgeUserData in both cases a) and b)
 */

void unblockProvidingEdges(Edges *pEdges, ServiceId id, CallbackReturnCode retCode)
{
	GList *pList;
	GList *pElement;

	pList = edgesProvidersTo(pEdges, id); 

	for (pElement = pList; pElement != NULL; pElement = pElement->next)
	{
		unblockEdge(pElement->data, retCode);
	}
}

void unblockEdge(Edge *pEdge, CallbackReturnCode retCode)
{
	assert(pEdge);

	if (pEdge->pUserData != NULL)
	{
		StopwatchTime time;

		getStopwatchTime(&time);

		assert(pEdge->pUserData->pidReply > 0);

		sendSignal(pEdge->pUserData->pidReply, retCode);

		pidmapStopwatchStart(pPidmap, pEdge->pUserData->pid, &time);

		free(pEdge->pUserData);
		pEdge->pUserData = NULL;
	}
}

/*
 * unblockDeregistrant()
 * This function is called by the edges* stuff, immediately after the
 * last Edge in which id is a provider is removed.
 */
void unblockDeregistrant(const ServiceId id, void *pUserData)
{
	Node *pNode;
	CallbackReturnCode *pCode = pUserData;

	pNode = nodeFromId(pNodes, id);

	assert(pNode);

	if (pNode->pUserData != NULL)
	{
		StopwatchTime time;

		getStopwatchTime(&time);

		pidmapAdd(pPidmap, pNode->pUserData->pid, pNode);

		msgqueueAdd(id, eEventDeregister);

		sendSignal(pNode->pUserData->pidReply, *pCode);

		pidmapStopwatchStart(pPidmap, pNode->pUserData->pid, &time);

		free(pNode->pUserData);
		pNode->pUserData = NULL;
	}
}

CallbackReturnCode vtCallbackCommand(CommandType cmd, const Command *pCommand)
{
	typedef CallbackReturnCode (*CallbackCommand)(const Command *pCommand);

	static CallbackCommand vt[eCommandTypeMax];
	static int fIsInitalised = FALSE;

	if (!fIsInitalised)
	{
		vt[eCommandTypeRegisterNeed]    = callbackCommandRegisterNeed;
		vt[eCommandTypeRegisterProvide] = callbackCommandRegisterProvide;
		vt[eCommandTypeDeregister]      = callbackCommandDeregister;
		vt[eCommandTypeStatusChange]    = callbackCommandStatusChange;
		vt[eCommandTypeServerControl]   = callbackCommandServerControl;
		vt[eCommandTypeQuery]           = callbackCommandQuery;
		vt[eCommandTypeViaFile]         = callbackCommandViaFile;

		fIsInitalised = TRUE;
	}
	
	return vt[cmd](pCommand);
}

/*
 *	This routine is based on callbackCommandRegisterProvide(const Command *p);
 *
 *	The reason it doesn't look like a callback and its interface is exposed
 *	is because the real callback is implemented in rdfservercallbacks.c
 *
 *	This routine uses the static pNodes, so it has to be in this file.
 *
 *	leni TODO: This coupling is unhealthy, needs revisiting.
 */
CallbackReturnCode setNodeAsStarted(const char *pszName)
{
	Node *pNode;
	ServiceId id;
	int ret = eReturnUndefined;

	assert(pszName);

	pNode = nodeFromName(pNodes, pszName);

	if (!pNode || pNode->fsm == eStateRegisterFailed)
	{
		if (!pNode)
		{
			/* id might have been added by a needer
			*/
			id = serviceIdFromName(pszName);

			if (id == 0)
			{
				id = serviceIdAdd(pszName);
			}

			pNode = nodeAdd(pNodes, id);

			pNode->stopwatch = 0;
		}
		else
		{
			assert(pNode->fsm == eStateRegisterFailed);
			id = pNode->id;
		}

		/* transition the node right into eStateIn
		*/
		msgqueueAdd(id, eEventRegister);
		msgqueueAdd(id, eEventProcessSuccess);

		ret = eReturnSuccess;
	}
	else
	{
		/*
		 * there's another provider of the same name.
		 */
		ret = eReturnFailure;
	}

	return ret;
}

