/*
 * rdfservercallbacks.c
 * server-side application logic for the Roster-handling stuff.
 *
 * Copyright (C) Leni Mayo 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 <unistd.h>
#include <stdlib.h> /* for free()     */
#include <string.h> /* for strerror() */
#include <errno.h>
#include <glib.h>
#include <ctype.h>  /* for isspace() */
#include "common.h"
#include "log.h"
#include "parselsb2rdf.h"
#include "rdfconsturi.h"
#include "servercommon.h"
#include "servercallbacks.h"
#include "serverconfig.h"
#include "traverse.h"
#include "rdfservercallbacks.h"

#include "pidmap.h" /* for the time - debugging stuff */

static const char * pszInventoryRdfId          = "http://hello.world/serel-service-inventory";
static const char * pszAutoGeneratedForUnknown = "auto-generated-unknown-service";
static const char * pszAutoGeneratedForStarted = "auto-generated-started-service";

static StopwatchTime start; /* used for profiling within this file */


static int addStatementToModel(librdf_model *pModel, librdf_node *pSubject, librdf_node *pPredicate, librdf_node *pObject);
static int addStatementToModelWithRewrite(librdf_model *pModel, librdf_statement *pStatement, librdf_node *pService);
static int addServiceCatalogToModel(ServiceCatalog *pServiceCatalog, librdf_model *pModelDst, GHashTable *pHashServices);
static int serviceCopyToModel(librdf_model *pModelDst, librdf_model *pModelSrc, librdf_node *pService);
static int isGeneratedId(librdf_node *pNode);
static librdf_node * rewriteGeneratedId(librdf_node *pNode, librdf_node *pService);

static int parsePopulateFp(FILE *fpOut, const char *pszBigString,
             GSList **ppStartedServices, GSList **ppServices, GSList **ppServicePaths);

static int reuseHistoryDatabase(librdf_model *pModel, GSList *pServicePaths, GSList *pConfigPaths);
static int buildModelDefinitions(RdfServer *pRdfServer, GSList *pStartedServices, GSList *pServices, GSList *pServicePaths, GSList *pConfigPaths);
static int buildModelRecentBoot(RdfServer *pRdfServer, char **ppszErrorMessage, GSList *pStartedServices, GSList *pServices, GSList *pServicePaths);
static int longTermModelAddInventory(RdfServer *pRdfServer, GSList *pPaths);

static int addService(librdf_model *pModel, const char *pszBaseUri, const char *pszName);
static librdf_node * newServiceNodeFromName(librdf_model *pModel, const char *pszBaseUri, const char *pszName);
static librdf_statement * newServiceStatement(const char *pszBaseUri, const char *pszName);
static librdf_node * edgeWithSource(librdf_model *pModel, const char *pszBaseUri, const char *pszService);
static int makeEdge(librdf_model *pModel, const char *pszBaseUri, const char *pszFrom, const char *pszTo);
static gint compare_fn_str(gconstpointer v, gconstpointer v2);

static void print_rdf_xml_for_inventory(FILE *fp, GSList *pPaths);

static GSList * newListOfStartedNodes(librdf_model *pModel);
static GSList * listOfServiceEdges(librdf_model *pModel, librdf_node *pService);

static char * listOfStringToHash(GSList *pList, GHashTable **ppHash);
static GSList * newIgnoreListFromServiceCatalogs(GSList *pListServiceCatalogRdf);
static void noteError(LogLevel level, char buf[], int bufsiz, const char *fmt, ...);
static int testNoDependencies(librdf_model *pModelRecentBoot, RdfServer *pRdfServer, char bufErrorMessage[], const char *pszService, RdfProperty code);

CallbackReturnCode callbackRosterPopulate(RdfServer *pRdfServer, FILE *fpOut, const char *pszBigString)
{
	GSList *pConfigPaths = rdfServerGetUriSeq();
	GSList *pStartedServices; /* services that we're told are already started */
	GSList *pServices;        /* services that are being started              */
	GSList *pServicePaths;    /* matching path names                          */
	char *pszErrorMessage = NULL;
	int ret;

	getStopwatchTime(&start);

	ret = parsePopulateFp(fpOut, pszBigString, &pStartedServices, &pServices, &pServicePaths);

	printLog(eLogLevelProfiling, "Time after parsePopulateFp() is: %s\n", printTimeDelta(&start));

	{
	char *psz;
	GSList *pElement;
	GSList *pList;

	/* pStartedServices
	*/
	psz = pStartedServices ? newStringFromStringsInList(pStartedServices) : strdup("none");
	printLog(eLogLevelRdf, "services already started: %s\n", psz);
	free(psz);

	/* pServices
	*/
	psz = pServices ? newStringFromStringsInList(pServices) : strdup("none");
	printLog(eLogLevelRdf, "services about to start: %s\n", psz);
	free(psz);

	/* pServicePaths
	*/
	pList = NULL;

	for (pElement = pServicePaths; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);
		pList = g_slist_append(pList, (char *) stringFromUriRemovingFileScheme(pElement->data));
	}

	psz = pList ? newStringFromStringsInList(pList) : strdup("none");
	printLog(eLogLevelRdf, "paths about to start: %s\n", psz);
	free(psz);
	g_slist_free(pList);

	/* pConfigPaths
	*/
	pList = NULL;

	for (pElement = pConfigPaths; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);
		pList = g_slist_append(pList, (char *) stringFromUriRemovingFileScheme(pElement->data));
	}

	psz = pList ? newStringFromStringsInList(pList) : strdup("none");
	printLog(eLogLevelRdf, "Look for service definitions in: %s\n", psz);
	free(psz);
	g_slist_free(pList);
	}

	if (ret)
	{
		pRdfServer->fHistoryUsed = reuseHistoryDatabase(pRdfServer->table[eRdfModelDefinitions].pModel, pServicePaths, pConfigPaths);

		if (! pRdfServer->fHistoryUsed)
		{
			int i;

			printLog(eLogLevelRdf, "Rebuilding history database.\n");

			printLog(eLogLevelProfiling, "Time after reuseHistoryDatabase() is: %s\n", printTimeDelta(&start));

			for (i = 0; ret && i < eRdfModelCodeMax; i++)
			{
				librdf_free_model(pRdfServer->table[i].pModel);
				librdf_free_storage(pRdfServer->table[i].pStorage);

				/*
				 * leni TODO: better handling of return code for truncateStorage()
				 * Right now, we don't check the return code - if it failed
				 * we assume that the serviceCatalogInitialise() will fail on
				 * the open-for-read-write and open-for-read and end up using memory.
				 * But if this assumption isn't true if it's a permission problem
				 * and the database is read-only.  In that case, we probably want to
				 * insist on using memory storage, rather than appending to the stale
				 * read from disk storage.
				 */
				truncateStorage(pRdfServer->table[i].pszFilePrefix);
				ret = ret && serviceCatalogInitialise(&(pRdfServer->table[i]));

				if (!ret)
				{
					const char *pszFormat = "Error: Unable to initialise rdf storage!\n";

					printLog(eLogLevelError, pszFormat);
				}
			}

			ret = ret && buildModelDefinitions(pRdfServer, pStartedServices, pServices, pServicePaths, pConfigPaths);

			printLog(eLogLevelProfiling, "Time after buildModelDefinitions() is: %s\n", printTimeDelta(&start));

			ret = ret && buildModelRecentBoot(pRdfServer, &pszErrorMessage, pStartedServices, pServices, pServicePaths);
			printLog(eLogLevelProfiling, "Time after buildModelRecentBoot() is: %s\n", printTimeDelta(&start));
		
			if (!ret)
			{
				librdf_node *pNodeInventory = librdf_new_node_from_uri_string(pWorld, pszInventoryRdfId);

				addPropertyToNode(pRdfServer->table[eRdfModelDefinitions].pModel,
					pNodeInventory, eSerelMarkDeleted, "true");

				librdf_free_node(pNodeInventory);
			}
		}
		else
		{
			printLog(eLogLevelRdf, "History database appears up-to-date - no need to rebuild.\n");

			pRdfServer->state = eRdfServerStatePopulateSuccess;
			pRdfServer->fHistoryUsed = TRUE;
		}
	}

#ifdef TAKENOUT
	fprintf(stderr, "Service definitions moodel contains:\n");
	librdf_model_print(pRdfServer->table[eRdfModelDefinitions].pModel, stderr);
	fprintf(stderr, "\n");
	fprintf(stderr, "\n");

	fprintf(stderr, "Recent boot model contains:\n");
	librdf_model_print(pRdfServer->table[eRdfModelRecentBoot].pModel, stderr);
	fprintf(stderr, "\n");
#endif

	if (!ret && pszErrorMessage == NULL)
	{
		pszErrorMessage = strdup("Error: server --populate command failed.  Refer to server logs for more information.");
	}

	/* printf("Error message for verify is %s\n", pszErrorMessage ? pszErrorMessage : "NULL"); */

	if (ret)
		assert(pszErrorMessage == NULL || strlen(pszErrorMessage) == 0);
	else
		assert(pszErrorMessage != NULL && strlen(pszErrorMessage) > 0);

	pRdfServer->pszErrorMessage = pszErrorMessage;

	pRdfServer->state = ret ? eRdfServerStatePopulateSuccess : eRdfServerStatePopulateFailed;

	return (pRdfServer->state == eRdfServerStatePopulateSuccess) ? eReturnSuccess : eReturnFailure;
}

int buildModelRecentBoot(RdfServer *pRdfServer, char **ppszErrorMessage, GSList *pStartedServices, GSList *pServices, GSList *pServicePaths)
{
	librdf_model *pModelRecentBoot, *pModelDefinitions;
	GSList *pElement;
	GSList *pCycle;
	GSList *pUnknownServices = NULL;
	GSList *pLateServices = NULL; /* services with the 'late' property     */
	GSList *pLastServices = NULL; /* unknown services + the 'last' service */
	char *pszService;
	librdf_node *pService;
	char bufErrorMessage[2000];
	int ret = TRUE;

	assert(pRdfServer);

	bufErrorMessage[0] = '\0';

	pModelDefinitions = pRdfServer->table[eRdfModelDefinitions].pModel;
	pModelRecentBoot = pRdfServer->table[eRdfModelRecentBoot].pModel;

	/* 1a. Add "started" services
	*/
	if (ret)
	{
		for (pElement = pStartedServices; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);
			pszService = pElement->data;

			addService(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

			addPropertyToBaseAndFragment(pModelRecentBoot, pRdfServer->pszBaseUri, pszService,
					eSerelSourceUri, pszAutoGeneratedForStarted);

			printLog(eLogLevelRdf, "Adding defn for 'started' service: %s\n", pszService);
		}
	}

	/* 1b. Add "starting" services
	*/
	if (ret)
	{
		for (pElement = pServices; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pszService = pElement->data;
			pService = newServiceNodeFromName(pModelDefinitions, pRdfServer->pszBaseUri, pszService);

			if (pService)
			{
				serviceCopyToModel(pModelRecentBoot, pModelDefinitions, pService);

				/*
				 * I think this is ok, but get it working first.
				 * librdf_free_node(pService);
				 */
				printLog(eLogLevelRdf, "Adding defn for 'starting' service: %s\n", pszService);
			}
			else
			{
				pUnknownServices = g_slist_append(pUnknownServices, pszService);

				printLog(eLogLevelRdf, "Don't have a defn for a 'starting' service - moving to list for unknown: %s\n", pszService);
			}
		}
	}

	/* 2. Process the "unknown services" list
	*/
	if (ret)
	{
		librdf_node *pEdge;

		for (pElement = pUnknownServices; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pszService = pElement->data;

			pEdge = edgeWithSource(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

			if (pEdge)
			{
				noteError(eLogLevelError, bufErrorMessage, sizeof(bufErrorMessage),
					"Error: a service depends upon an unknown service via: %s\n",
					local_part_of_uri(node_as_uri_string(pEdge)));

				ret = FALSE;
			}
			else
			{
				pLastServices = g_slist_append(pLastServices, pElement->data);

				addService(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

				addPropertyToBaseAndFragment(pModelRecentBoot, pRdfServer->pszBaseUri, pszService,
					eSerelSourceUri, pszAutoGeneratedForUnknown);

				printLog(eLogLevelRdf, "Creating defn for an 'unknown' service and moving it to the list of 'last' services: %s\n", pszService);
			}
		}
	}

	/* 3. Identify services with the "late" property
	*/
	if (ret)
	{
		for (pElement = pServices; pElement != NULL && ret; pElement = pElement->next)
		{
			assert(pElement->data);

			pszService = pElement->data;
			pService = newServiceNodeFromName(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

			if (nodeHasPropertyName(pModelRecentBoot, pService, eSerelLate))
			{
				ret = testNoDependencies(pModelRecentBoot, pRdfServer, bufErrorMessage, pszService, eSerelLate);

				if (ret)
				{
					pLateServices = g_slist_append(pLateServices, pszService);
				}
			}

			/* librdf_free_node(pService); */
		}
	}

	/* 4. Identify and process the service with the "last" property
	*/
	if (ret)
	{
		GSList *pListOfLast = NULL;
	
		for (pElement = pServices; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pszService = pElement->data;
			pService = newServiceNodeFromName(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

			if (nodeHasPropertyName(pModelRecentBoot, pService, eSerelLast))
			{
				pListOfLast = g_slist_append(pListOfLast, pszService);
			}

			/* librdf_free_node(pService); */
		}

		if (g_slist_length(pListOfLast) > 1)
		{
			char *psz = newStringFromStringsInList(pListOfLast);

			noteError(eLogLevelError, bufErrorMessage, sizeof(bufErrorMessage),
				"Error: no support for multiple services with the 'last' property: %s\n",
				psz);

			free(psz);

			ret = FALSE;
		}
		else if (g_slist_length(pListOfLast) == 1)
		{
			pszService = pListOfLast->data;

			ret = testNoDependencies(pModelRecentBoot, pRdfServer, bufErrorMessage, pszService, eSerelLast);

			if (ret)
			{
				pLastServices = g_slist_append(pLastServices, pListOfLast->data);
				printLog(eLogLevelRdf, "Moving the service with the 'last' property to the list of 'last' services: %s\n", (const char *) pListOfLast->data);
			}

			/* librdf_free_node(pService); */
		}
	
		if (pListOfLast)
			g_slist_free(pListOfLast);
	}

	/* 5. Delete edges where the source doesn't exist
	*/
	if (ret)
	{
		GSList *pListEdges = listOfReferencedNode(pModelRecentBoot, nodeFromCode(eGraphEdge), NULL);
		librdf_node *pEdge;
		librdf_statement *pStatement;

		for (pElement = pListEdges; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);
			pEdge = (librdf_node *) pElement->data;

			pService = librdf_model_get_target(pModelRecentBoot, pEdge, nodeFromCode(eGraphFrom));
			pszService = (char *) local_part_of_uri(node_as_uri_string(pService));

			pStatement = newServiceStatement(pRdfServer->pszBaseUri, pszService);

			if ( ! librdf_model_contains_statement(pModelRecentBoot, pStatement))
			{
				printLog(eLogLevelRdf, "Deleting a dependency: %s\n", (char *) local_part_of_uri(node_as_uri_string(pEdge)));

				removeStatement(pModelRecentBoot, pEdge, NULL, NULL);
			}

			librdf_free_statement(pStatement);
		}

	}

	/* 6. Test for acyclic-ness
	*/
	if (ret)
	{
		testAcyclic(pModelRecentBoot, &pCycle, nodeFromCode(eGraphService));

		if (pCycle)
		{
			char *psz = newStringFromNodesInList(pCycle);

			noteError(eLogLevelError, bufErrorMessage, sizeof(bufErrorMessage),
				"Error: dependencies contain a cycle: %s\n",
				psz);

			free(psz);
			
			ret = FALSE;
		}
		else
		{
			printLog(eLogLevelRdf, "Interim test of graph checks out - no cycles.\n");
		}
	}

	/* 7. Process the pLateServices list
	*/
	if (ret && g_slist_length(pLateServices) > 0)
	{
		GSList *pLeafNodes;
		char *pszFirstOfLast = (char *) pLastServices->data;
		GSList *pElementLate;
		char *pszLateService;

		pLeafNodes = listOfReferencedNode(pModelRecentBoot, nodeFromCode(eGraphService), nodeFromCode(eGraphFrom));

		for (pElementLate = pLateServices; pElementLate != NULL; pElementLate = pElementLate->next)
		{
			pszLateService = pElementLate->data;

			for (pElement = pLeafNodes; pElement != NULL; pElement = pElement->next)
			{
				assert(pElement->data);

				pService = (librdf_node *) pElement->data;
				pszService = (char *) local_part_of_uri(node_as_uri_string(pService));

				if (
				 ! isNodeWithPropertyValue(pModelRecentBoot, pService, eSerelSourceUri, pszAutoGeneratedForUnknown)
				 &&
				 ! isNodeWithPropertyValue(pModelRecentBoot, pService, eSerelSourceUri, pszAutoGeneratedForStarted)
				 &&
				 ! nodeHasPropertyName(pModelRecentBoot, pService, eSerelLate)
				 &&
				 ! nodeHasPropertyName(pModelRecentBoot, pService, eSerelLast) )
				{
					assert(strcmp(pszService, pszLateService) != 0);

					makeEdge(pModelRecentBoot, pRdfServer->pszBaseUri, pszService, pszLateService);
					printLog(eLogLevelRdf, "Creating a dependency: late service %s depends on %s\n", pszLateService, pszService);
				}
				else
				{
					/* printf("5a. Avoided creating a dependency from %s to the service %s\n", pszLateService, pszService); */
				}
			}
		}
	}

	/* 8. Process the pLastServices list
	*/
	if (ret && g_slist_length(pLastServices) > 0)
	{
		GSList *pLeafNodes;
		char *pszFirstOfLast = (char *) pLastServices->data;
		char *pszServicePrevious;

		pLeafNodes = listOfReferencedNode(pModelRecentBoot, nodeFromCode(eGraphService), nodeFromCode(eGraphFrom));

		for (pElement = pLeafNodes; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pService = (librdf_node *) pElement->data;
			pszService = (char *) local_part_of_uri(node_as_uri_string(pService));

			if (
			 ! isNodeWithPropertyValue(pModelRecentBoot, pService, eSerelSourceUri, pszAutoGeneratedForUnknown)
			 &&
			 ! isNodeWithPropertyValue(pModelRecentBoot, pService, eSerelSourceUri, pszAutoGeneratedForStarted)
			 &&
			 ! nodeHasPropertyName(pModelRecentBoot, pService, eSerelLast)
			 &&
			 (pLastServices->next == NULL || !g_slist_find_custom(pLastServices->next, pszService, compare_fn_str)) )
			{
				assert(strcmp(pszService, pszFirstOfLast) != 0);

				makeEdge(pModelRecentBoot, pRdfServer->pszBaseUri, pszService, pszFirstOfLast);
				printLog(eLogLevelRdf, "Creating a dependency: %s depends on %s\n", pszFirstOfLast, pszService);
			}
			else
			{
				/* printf("5a. Avoided creating a dependency from %s to the service %s\n", pszFirstOfLast, pszService); */
			}
		}

		pszServicePrevious = pLastServices->data;
	
		for (pElement = pLastServices->next; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);
			pszService = pElement->data;

			if (pElement->next == NULL || !g_slist_find_custom(pElement->next, pszService, compare_fn_str))
			{
				makeEdge(pModelRecentBoot, pRdfServer->pszBaseUri, pszServicePrevious, pszService);

				printLog(eLogLevelRdf, "Creating a dependency: %s depends on %s\n", pszService, pszServicePrevious);
			}
			else
			{
				/* printf("5b. Avoided creating a dependency from %s to the later service %s\n", pszService, pszServicePrevious); */
			}

			pszServicePrevious = pElement->data;
		}
	}

	/*
	 * 7. Tag all Services with eSerelStartUri so that we can find the service
	 *    for a given path.
	 */
	if (ret)
	{
		GSList *pElementPath = pServicePaths;

		assert(g_slist_length(pServicePaths) == g_slist_length(pServices));

		for (pElement = pServices; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pszService = pElement->data;
			pService = newServiceNodeFromName(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

			if (pService)
			{
				librdf_node *pPredicate = librdf_new_node_from_node(nodeFromCode(eSerelStartUri));
				librdf_node *pObject    = librdf_new_node_from_uri_string(pWorld, (const char *) pElementPath->data);

				ret = addStatementToModel(pModelRecentBoot, pService, pPredicate, pObject);

				/* fprintf(stdout, "7. Adding Statement: (%s, eSerelStartUri, %s)\n", pszService, (const char *) pElementPath->data); */
			}
			else
			{
				/* service shouldnt be unknown
				*/
				assert(NOTHERE);
			}

			pElementPath = pElementPath->next;
		}
	}

	/* 8. Test for acyclic-ness (again, just for sanity)
	*/
	if (ret)
	{
		testAcyclic(pModelRecentBoot, &pCycle, nodeFromCode(eGraphService));

		if (pCycle)
		{
			char *psz = newStringFromNodesInList(pCycle);

			noteError(eLogLevelInternalError, bufErrorMessage, sizeof(bufErrorMessage),
				"Internal error: the server created a cyclic dependency: %s\n",
				psz);

			free(psz);
			
			ret = FALSE;
		}
		else
		{
			GSList *pRootNodes = listOfReferencedNode(pModelRecentBoot, nodeFromCode(eGraphService), nodeFromCode(eGraphTo));
			GSList *pLeafNodes = listOfReferencedNode(pModelRecentBoot, nodeFromCode(eGraphService), nodeFromCode(eGraphFrom));
			char *pszRootNodes = newStringFromNodesInList(pRootNodes);
			char *pszLeafNodes = newStringFromNodesInList(pLeafNodes);

			printLog(eLogLevelRdf, "Dependency graph checks out.\n");
			printLog(eLogLevelRdf, "Root nodes: %s\n", pszRootNodes);
			printLog(eLogLevelRdf, "Leaf nodes: %s\n", pszLeafNodes);

			free(pszRootNodes);
			free(pszLeafNodes);

			g_slist_free(pRootNodes);
			g_slist_free(pLeafNodes);
		}
	}

	if (pLateServices);
		g_slist_free(pLateServices);

	if (pLastServices);
		g_slist_free(pLastServices);

	*ppszErrorMessage = ret ? NULL : strdup(bufErrorMessage);

	return ret;
}

int buildModelDefinitions(RdfServer *pRdfServer, GSList *pStartedServices, GSList *pServices, GSList *pServicePaths, GSList *pConfigPaths)
{
	GSList *pElement;
	const char *pszFilename;
	char *pszTmpFileAsUri;
	GSList *pListServiceCatalogScripts = NULL;
	GSList *pListServiceCatalogRdf     = NULL;
	GSList *pListOfPathsToIgnore;
	GHashTable *pHashServices = NULL;
	ServiceCatalog *pServiceCatalog;
	librdf_model *pModel;
	char tmpfilename[100];
	FILE *fpOut, *fpIn;
	ParseBootResult parseResult;
	int ret = TRUE;

	assert(pRdfServer);
	assert(g_slist_length(pServices) == g_slist_length(pServicePaths));

	if (ret && pConfigPaths)
	{
		GSList *pAllServices = newListFromLists(pStartedServices, pServices);
		char *pszDuplicate;

		pszDuplicate = listOfStringToHash(pAllServices, &pHashServices);

		assert(!pszDuplicate); /* should have been detected and handled earlier */

		for (pElement = pConfigPaths; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);
			pszFilename = pElement->data;

			pServiceCatalog = serviceCatalogFromUri(pRdfServer, pszFilename, pszFilename);

			if (pServiceCatalog)
			{
				pListServiceCatalogRdf = g_slist_append(pListServiceCatalogRdf, pServiceCatalog);
			}
			else
			{
				printLog(eLogLevelError, "Unable to parse rdf: %s.\n", pszFilename);
			}

			printLog(eLogLevelProfiling, "Time after serviceCatalogFromUri(%s) is: %s\n", pszFilename, printTimeDelta(&start));
		}

		g_slist_free(pAllServices);
	}

	pListOfPathsToIgnore = newIgnoreListFromServiceCatalogs(pListServiceCatalogRdf);

	/*
	 * Parse the lsb comments of out boot scripts into a .rdf file,
	 * then parse the .rdf into a ServiceCatalog.
	 * Ideally, we wouldn't need to go out to the filesystem here.
	 * leni - Sat Apr 20 09:05:28 EST 2002
	 */
	for (pElement = pServicePaths; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);
		pszFilename = filenameFromUri(pElement->data); /* the boot script filename */

		pszTmpFileAsUri = newAbsoluteFromRelativePath(pszFilename);

		if (g_slist_find_custom(pListOfPathsToIgnore, (char *) pszTmpFileAsUri, compare_fn_str))
		{
			printLog(eLogLevelRdf, "%s - skipping the parse for special comments\n", pszTmpFileAsUri);

			free(pszTmpFileAsUri);
			continue;
		}

		free(pszTmpFileAsUri);

    	fpOut = createAndOpenTmpFp(tmpfilename, sizeof(tmpfilename), "w");

		if (fpOut == NULL)
		{
			printLog(eLogLevelError, "Unable to create temporary file: \"%s\" - %s\n", tmpfilename, strerror(errno));

			break;  /* if we can't create a tmp file forget the rest */
		}

		fpIn = fopen(pszFilename, "r");

		if (fpIn == NULL)
		{
			printLog(eLogLevelError, "Unable to open file for reading: \"%s\" - %s\n", pszFilename, strerror(errno));

			continue;
		}

		print_rdf_header(fpOut);
		fprintf(fpOut, "\n");

		parseResult = parseLsbToRdf(fpIn, fpOut, pszFilename);

		fprintf(fpOut, "\n");

		print_rdf_trailer(fpOut);

		fclose(fpIn);
		fclose(fpOut);

		if (parseResult != eParseBootSuccess)
		{
			if (parseResult == eParseBootFailed)
			{
				printLog(eLogLevelError, "Error in parsing rdf generated from: \"%s\" - ignored.  Try debugging using lsb2rdf.\n", pszFilename);
			}
			else
			{
				assert(parseResult == eParseBootEmpty);

				printLog(eLogLevelRdf, "No special comments found in script: \"%s\".\n", pszFilename);
			}

			unlink(tmpfilename);

			continue;
		}

		pszTmpFileAsUri = newUriStringFromFilename(tmpfilename);

		pServiceCatalog = serviceCatalogFromUri(pRdfServer, pszTmpFileAsUri, (const char *) pElement->data);

		if (pServiceCatalog)
		{
			pListServiceCatalogScripts = g_slist_append(pListServiceCatalogScripts, pServiceCatalog);

			printLog(eLogLevelRdf, "Parsed service definition from: %s.\n", pszFilename);
		}
		else
		{
			printLog(eLogLevelInternalError, "Internal Error: unable to parse rdf generated from: %s.\n", pszFilename);
		}

		free(pszTmpFileAsUri);
		unlink(tmpfilename);
	}

	for (pElement = pListOfPathsToIgnore; pElement != NULL; pElement = pElement->next)
	{
		free(pElement->data);
	}

	g_slist_free(pListOfPathsToIgnore);

	printLog(eLogLevelProfiling, "Time after building script definitions is: %s\n", printTimeDelta(&start));

	pModel = pRdfServer->table[eRdfModelDefinitions].pModel;

	for (pElement = pListServiceCatalogScripts; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement && pElement->data);

		addServiceCatalogToModel((ServiceCatalog *) pElement->data, pModel, NULL);
	}

	for (pElement = pListServiceCatalogRdf; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement && pElement->data);

		pServiceCatalog = (ServiceCatalog *) pElement->data;

		addServiceCatalogToModel(pServiceCatalog, pModel, pHashServices);

		pszFilename = serviceCatalogMetaDataValue(pServiceCatalog, eSerelSourceUri);

		if (pszFilename)
		{
			pszFilename = filenameFromUri(pszFilename); /* the boot script filename */
		}

		if (!pszFilename)
		{
			pszFilename = "unknown file";
		}

		printLog(eLogLevelProfiling, "Time after addServiceCatalogToModel(%s) is: %s\n", pszFilename, printTimeDelta(&start));
		printLog(eLogLevelRdf, "Adding service definitions from parsed rdf: %s.\n", pszFilename);
	}

	if (ret)
	{
		GSList *pPaths = newListFromLists(pServicePaths, pConfigPaths);

		longTermModelAddInventory(pRdfServer, pPaths);

		g_slist_free(pPaths);
	}

	if (pHashServices)
	{
		g_hash_table_destroy(pHashServices);
	}

	return ret;
}


/*
 * reuseHistoryDatabase()
 *
 * Efficiency is an issue in this routine, so we test that the paths match
 * before testing that the mtimes match.
 */
int reuseHistoryDatabase(librdf_model *pModel, GSList *pServicePaths, GSList *pConfigPaths)
{
	typedef enum
	{
		eInModelPath   = 0,
		eInModelMtime  = 1,
		eThisBootPath  = 2,
		eListCodeLast  = 3
	} ListCode;

	GSList *list[eListCodeLast];
	librdf_node *pNode, *pNodeMtime, *pContainer;
	librdf_node *pNodeInventory = librdf_new_node_from_uri_string(pWorld, pszInventoryRdfId);
	const char *pszLast, *pszThis;
	int i;
	int match = TRUE;

	if (nodeHasPropertyName(pModel, pNodeInventory, eSerelMarkDeleted))
	{
		match = FALSE;

		printLog(eLogLevelRdf, "stale database: marked dirty.\n");

		librdf_free_node(pNodeInventory);

		return match;
	}

	for (i = 0; i < eListCodeLast; i++)
	{
		list[i] = NULL;
	}

	list[eThisBootPath] = newListFromLists(pServicePaths, pConfigPaths);

#ifdef TAKENOUT
	fprintf(stdout, "reuseHistoryDatabase(");
	stringsInListToFp(stdout, list[eThisBootPath]);
	fprintf(stdout, ")\n");
#endif

	/* eg: {[pNodeInventory], [nodeFromCode(eSerelInventory)], [file:/tmp/H7ZUfC#genid1]}
	*/
	pContainer = librdf_model_get_target(pModel, pNodeInventory, nodeFromCode(eSerelInventory)),

	i = 0;

	while (TRUE)
	{
		i++;

		pNode = nodeAtContainerItem(pModel, pContainer, i);

		if (!pNode)
		{
			break;
		}

		list[eInModelPath] = g_slist_append(list[eInModelPath], (gpointer) node_as_uri_string(pNode));

		pNodeMtime = librdf_model_get_target(pModel, pNode, nodeFromCode(eSerelMtime));

		if (pNodeMtime)
		{
			list[eInModelMtime] = g_slist_append(list[eInModelMtime], librdf_node_get_literal_value(pNodeMtime));
		}
		else
		{
			match = FALSE;
			break;
		}
	}

	/* Make sure the paths match
	*/
	if (match)
	{
		GSList *pElement1, *pElement2;

		pElement1 = list[eInModelPath];
		pElement2 = list[eThisBootPath];

		while (pElement1 != NULL && pElement2 != NULL && match)
		{
			assert(pElement1->data && pElement2->data);

			pszLast = (const char *) pElement1->data;
			pszThis = (const char *) pElement2->data;

			match = strcmp(pszLast, pszThis) == 0;

			if (!match)
				printLog(eLogLevelRdf, "stale database - %s vs %s.\n", pszLast, pszThis);

			/* printLog(eLogLevelRdf, "comparing %s with %s - %s\n", pszLast, pszThis match ? "match" : "different!"); */

			pElement1 = pElement1->next;
			pElement2 = pElement2->next;
		}

		if (match)
		{
			match = (pElement1 == NULL && pElement2 == NULL); /* make sure they're the same length */

			if (!match)
				printLog(eLogLevelRdf, "stale database - %d scripts+rdf files last time, now %d.\n",
					g_slist_length(list[eInModelPath]),
					g_slist_length(list[eThisBootPath]));
		}
	}

	/* Make sure the mtimes match
	*/
	if (match)
	{
		GSList *pElement1, *pElement2;
		char *pszMtimeThisBoot;

		pElement1 = list[eInModelMtime];
		pElement2 = list[eThisBootPath];

		while (pElement1 != NULL && pElement2 != NULL && match)
		{
			pszLast = (const char *) pElement1->data;

			pszMtimeThisBoot = mtimeFromUri((const char *) pElement2->data);

			match = strcmp(pszLast, pszMtimeThisBoot) == 0;

			if (!match)
				printLog(eLogLevelRdf, "stale database - mtime %s vs %s.\n", pszLast, pszMtimeThisBoot);

#ifdef TAKENOUT
			printLog(eLogLevelRdf, "comparing %s with %s - %s\n",
				pszLast, pszMtimeThisBoot,
				match ? "match" : "different!");
#endif

			pElement1 = pElement1->next;
			pElement2 = pElement2->next;
		}

		if (match)
		{
			match = (pElement1 == NULL && pElement2 == NULL); /* make sure they're the same length */

			if (!match)
				printLog(eLogLevelRdf, "stale database - %d scripts+rdf mtimes last time, now %d.\n",
					g_slist_length(list[eInModelMtime]),
					g_slist_length(list[eThisBootPath]));
		}
	}

	for (i = 0; i < eListCodeLast; i++)
	{
		g_slist_free(list[i]);
	}

	librdf_free_node(pNodeInventory);

	return match;
}

int addStatementToModelWithRewrite(librdf_model *pModel, librdf_statement *pStatement, librdf_node *pService)
{
	int failed = TRUE;
	librdf_node *pSubject, *pPredicate, *pObject;
	librdf_node *pNewSubject = NULL;
	librdf_node *pNewObject  = NULL;
	librdf_statement *pNewStatement = NULL;
	librdf_statement *pStatementToAdd;

	assert(pModel && pStatement);

	pSubject = librdf_statement_get_subject(pStatement);
	pObject  = librdf_statement_get_object(pStatement);

	if (isGeneratedId(pSubject) || isGeneratedId(pObject))
	{
		pNewSubject = isGeneratedId(pSubject) ? rewriteGeneratedId(pSubject, pService) : pSubject;
		pNewObject  = isGeneratedId(pObject)  ? rewriteGeneratedId(pObject,  pService) : pObject;

		pPredicate = librdf_statement_get_predicate(pStatement);

		pNewStatement = librdf_new_statement_from_nodes(pWorld, pNewSubject, pPredicate, pNewObject);

		pStatementToAdd = pNewStatement;
	}
	else
	{
		pStatementToAdd = pStatement;
	}

	failed = librdf_model_add_statement(pModel, pStatementToAdd);

	if (failed)
	{
		printLog(eLogLevelError, "Error: unable to add statement (x) to model.\n");
	}

#ifdef TAKENOUT
	fprintf(stderr, "Added statement to model: ");
	librdf_statement_print(pStatementToAdd, stderr);
	fprintf(stderr, "\n");
#endif
	 
	return failed;
}

/*
 *	- for all statements matching (* (X), rdf:type, serel:service)
 *	  - print statement matching (X, *, *)
 *	  - for the statement matching (X, serel:edges, * (Y))
 *	    - print statements matching (Y, *, * (Z))
 *	    - if statement matches (Z, rdf:Type, serel:edge)
 *		  - print statements matching (Z, *, *)
 */

int addServiceCatalogToModel(ServiceCatalog *pServiceCatalog, librdf_model *pModelDst, GHashTable *pHashServices)
{
	librdf_statement *pPartialStatement = librdf_new_statement(pWorld);
	librdf_statement *pStatement;
	librdf_stream    *pStreamOfServices;
	librdf_node      *pService;
	const char       *pszService;
	librdf_model     *pModelSrc;
	int ret = TRUE;

	assert(pServiceCatalog && pServiceCatalog->pModel && pModelDst);

	pModelSrc = pServiceCatalog->pModel;

	librdf_statement_set_predicate(pPartialStatement, nodeFromCode(eRdfType));
	librdf_statement_set_object(pPartialStatement, nodeFromCode(eGraphService));

	pStreamOfServices = librdf_model_find_statements(pModelSrc, pPartialStatement);

	while (!librdf_stream_end(pStreamOfServices) && ret)
	{
		pStatement = librdf_stream_next(pStreamOfServices);

		if (! librdf_model_contains_statement(pModelDst, pStatement))
		{
			GSList *pElement;
			ServiceCatalogMetaData *pMetaData;

			pService = librdf_statement_get_subject(pStatement);
			pszService = local_part_of_uri(node_as_uri_string(pService));

			/*
			 *	If the hash table was provided, only copy services named
			 *	as a key.  We want to avoid needlessly copying loads of useless
			 *	service definitions from pModelSrc to pModelDst.
			 */
			if ( (pHashServices == NULL) ||
				 (g_hash_table_lookup(pHashServices, pszService) != NULL) )
			{
				ret = serviceCopyToModel(pModelDst, pModelSrc, pService);

				for (pElement = pServiceCatalog->pList; pElement != NULL; pElement = pElement->next)
				{
					assert(pElement->data);

					pMetaData = pElement->data;

					addPropertyToNode(pModelDst, pService, pMetaData->code, pMetaData->pszValue);
				}
			}
		}

		librdf_free_statement(pStatement);
	}
	
	librdf_free_stream(pStreamOfServices);  

	return ret;
}

/*
 * here's how you would do it with callbacks:
 * int foreachMatchingStatement(model, partial statement, callback, user_data)
 * callback(model, statement, user_data)
 * user_data is (pModelDst, ret)
 */

int serviceCopyToModel(librdf_model *pModelDst, librdf_model *pModelSrc, librdf_node *pService)
{
	librdf_statement *pPartialStatementX = librdf_new_statement(pWorld);
	librdf_statement *pPartialStatementY = librdf_new_statement(pWorld);
	librdf_statement *pPartialStatementZ = librdf_new_statement(pWorld);
	librdf_statement *pStatementTest     = librdf_new_statement(pWorld);
	librdf_statement *pStatementX;
	librdf_statement *pStatementY;
	librdf_statement *pStatementZ;
	librdf_stream    *pStreamOfServiceProperties, *pStreamY, *pStreamZ;
	librdf_node      *pPredicate, *pObject, *pContainedInEdges, *pEdge;
	int failed = FALSE;

	assert(pModelDst && pModelSrc && pService);

	librdf_statement_set_subject(pPartialStatementX, pService);

	pStreamOfServiceProperties = librdf_model_find_statements(pModelSrc, pPartialStatementX);

	while (!librdf_stream_end(pStreamOfServiceProperties) && !failed)
	{
		pStatementX = librdf_stream_next(pStreamOfServiceProperties);

		failed = addStatementToModelWithRewrite(pModelDst, pStatementX, pService);

		if (failed)
		{
			librdf_free_statement(pStatementX);

			break;
		}

		pPredicate = librdf_statement_get_predicate(pStatementX);
		pObject = librdf_statement_get_object(pStatementX);

		if (librdf_node_equals(pPredicate, nodeFromCode(eGraphEdges)))
		{
			librdf_statement_set_subject(pPartialStatementY, pObject);

			pStreamY = librdf_model_find_statements(pModelSrc, pPartialStatementY);

			while (!librdf_stream_end(pStreamY) && !failed)
			{
				pStatementY = librdf_stream_next(pStreamY);

				failed = addStatementToModelWithRewrite(pModelDst, pStatementY, pService);

				if (failed)
				{
					librdf_free_statement(pStatementY);

					break;
				}

				pContainedInEdges = librdf_statement_get_object(pStatementY);

				pEdge = librdf_model_get_source(pModelSrc, nodeFromCode(eGraphEdge), pContainedInEdges);

				librdf_statement_set_subject(pStatementTest, pContainedInEdges);
				librdf_statement_set_predicate(pStatementTest, nodeFromCode(eRdfType));
				librdf_statement_set_object(pStatementTest, nodeFromCode(eGraphEdge));

				if (librdf_model_contains_statement(pModelSrc, pStatementTest))
				{
					librdf_statement_set_subject(pPartialStatementZ, pContainedInEdges);

					pStreamZ = librdf_model_find_statements(pModelSrc, pPartialStatementZ);

					while (!librdf_stream_end(pStreamZ) && !failed)
					{
						pStatementZ = librdf_stream_next(pStreamZ);

						failed = addStatementToModelWithRewrite(pModelDst, pStatementZ, pService);
				
						librdf_free_statement(pStatementZ);

						if (failed)
						{
							break;
						}
					}
			
					librdf_free_stream(pStreamZ);
				}

				librdf_free_statement(pStatementY);
			}

			librdf_free_stream(pStreamY);  
		
			librdf_free_statement(pStatementX);
		}
	}
	
	librdf_free_stream(pStreamOfServiceProperties);  

	return !failed;
}

int parsePopulateFp(FILE *fpOut, const char *pszBig, GSList **ppStartedServices, GSList **ppServices, GSList **ppServicePaths)
{
	char *pszBigString = strdup(pszBig);
	char *pszBigStringUnpermuted = strdup(pszBig);
	char *pszElement;
	GSList *pStartedServices = NULL;
	GSList *pServices        = NULL;
	GSList *pServicePaths    = NULL;
	GSList *pElement;
	GSList *pList;
	typedef enum
	{
		eFsmInitial,
		eFsmStarted,
		eFsmPairs
	} Fsm;
	Fsm state = eFsmInitial;
	int failed = FALSE;

	assert(ppStartedServices && ppServices && ppServicePaths);

	pList = splitStringIntoList(pszBigString, isWhitespace);

#ifdef TAKENOUT
	fprintf(stdout, "parsePopulateFp()\nChannel broken up into tokens is: ");
	stringsInListToFp(stdout, pList);
	finventoryprintf(stdout, "\n");
#endif

	for (pElement = pList; pElement != NULL && !failed; pElement = pElement->next)
	{
		assert(pElement->data);

		pszElement = pElement->data;

		if (strcmp(pszElement, "--started") == 0)
		{
			if (state != eFsmInitial)
			{
				const char *pszFormat;

				pszFormat = "Aborting %s --populate: contains out-of-place --started keyword:\n%s\n";

				printLog(eLogLevelError, pszFormat, argvCodeAsString(eArgvSerelhistory), pszBigStringUnpermuted);

				pszFormat = "error: out-of-place --started keyword:\n%s\n";

				fprintf(fpOut, pszFormat, pszBigStringUnpermuted);

				failed = TRUE;
			}
			else
			{
				state = eFsmStarted;
			}

			continue;
		}
		else if (strcmp(pszElement, "--pairs") == 0) 
		{
			if (state == eFsmPairs)
			{
				const char *pszFormat;

				pszFormat = "Aborting %s --populate: contains out-of-place --pairs keyword:\n%s\n";
				
				printLog(eLogLevelError, pszFormat, argvCodeAsString(eArgvSerelhistory), pszBigStringUnpermuted);

				pszFormat = "error: out-of-place --pairs keyword:\n%s\n";

				fprintf(fpOut, pszFormat, argvCodeAsString(eArgvSerelhistory), pszBigStringUnpermuted);

				failed = TRUE;
			}
			else
			{
				state = eFsmPairs;
			}

			continue;
		}
		
		if (state == eFsmStarted)   
		{
			pStartedServices = g_slist_append(pStartedServices, strdup(pszElement));
		}
		else
		{
			assert (state == eFsmPairs);

			if (pElement->next != NULL)
			{
				pServices = g_slist_append(pServices, strdup(pszElement));
				pServicePaths = g_slist_append(pServicePaths, newUriStringFromFilename(pElement->next->data));

				pElement = pElement->next;
			}
			else
			{
				const char *pszFormat;
				
				pszFormat = "Aborting %s --populate: expected pairs following --pairs keyword:\n%s\n";

				printLog(eLogLevelError, pszFormat, argvCodeAsString(eArgvSerelhistory), pszBigStringUnpermuted);

				pszFormat = "error: expected pairs following --pairs keyword:\n%s\n";

				fprintf(fpOut, pszFormat, pszBigStringUnpermuted);

				failed = TRUE;
				break;
			}
		}
	}

	free(pszBigString);
	free(pszBigStringUnpermuted);

	if (!failed)
	{
		char *pszDuplicate;
		GSList *pAllServices = newListFromLists(pStartedServices, pServices);

		pszDuplicate = listOfStringToHash(pAllServices, NULL);

		if (pszDuplicate)
		{
			const char *pszFormat = "Error: Duplicate found in list of services: %s\n";

			printLog(eLogLevelError, pszFormat, pszDuplicate);

			fprintf(fpOut, pszFormat, pszDuplicate);

			failed = TRUE;
		}

		/*
		 *	TODO: Note that testing whether two paths match by doing a string
		 *	comparison is very weak.  Must revisit.
		 */

		pszDuplicate = listOfStringToHash(pServicePaths, NULL);

		if (pszDuplicate)
		{
			const char *pszFormat = "Error: Duplicate found in list of service paths: %s\n";

			printLog(eLogLevelError, pszFormat, pszDuplicate);

			fprintf(fpOut, pszFormat, pszDuplicate);

			failed = TRUE;
		}
	}

	if (!failed)
	{
		*ppStartedServices = pStartedServices;
		*ppServices        = pServices;
		*ppServicePaths    = pServicePaths;
	}
	else
	{
		*ppStartedServices = NULL;
		*ppServices        = NULL;
		*ppServicePaths    = NULL;

		g_slist_free(pStartedServices);
		g_slist_free(pServices);
		g_slist_free(pServicePaths);
	}

	return !failed;
}

int longTermModelAddInventory(RdfServer *pRdfServer, GSList *pPaths)
{
	librdf_model *pModel;
	char tmpfilename[100];
	char *pszTmpFileAsUri;
	FILE *fpOut;
	int ret;

	assert(pRdfServer);

	pModel = pRdfServer->table[eRdfModelDefinitions].pModel;

	fpOut = createAndOpenTmpFp(tmpfilename, sizeof(tmpfilename), "w");

	if (fpOut == NULL)
	{
		printLog(eLogLevelError, "Unable to create temporary file: \"%s\" - %s\n", tmpfilename, strerror(errno));

		return FALSE;
	}

	pszTmpFileAsUri = newUriStringFromFilename(tmpfilename);

	print_rdf_header(fpOut);
	print_rdf_xml_for_inventory(fpOut, pPaths);
	print_rdf_trailer(fpOut);

	fclose(fpOut);

	ret = parseUriIntoModel(pRdfServer, pModel, pszTmpFileAsUri, pszTmpFileAsUri);

	if (ret)
	{
		char *psz = newStringFromStringsInList(pPaths);

		/* printLog(eLogLevelRdf, "Inventory regenerated: %s\n", psz); */

		free(psz);
	}
	else
	{
		printLog(eLogLevelInternalError, "Internal error: unable to parse self-generated inventory.\n");
	}

	free(pszTmpFileAsUri);
	unlink(tmpfilename);

	return ret;
}

void print_rdf_xml_for_inventory(FILE *fp, GSList *pPaths)
{
	GSList *pElement;
	char *pszMtime;

	fprintf(fp, "<rdf:Description about=\"%s\" >\n", pszInventoryRdfId);
	fprintf(fp, "<serel:%s >\n", rdfPropertyFragment(eSerelInventory));
  	fprintf(fp, "  <rdf:Seq>\n");

	for (pElement = pPaths; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);

		fprintf(fp, "    <rdf:li rdf:resource=\"%s\" />\n", (const char *) pElement->data);
	}

	fprintf(fp, "  </rdf:Seq>\n");
	fprintf(fp, "</serel:%s>\n", rdfPropertyFragment(eSerelInventory));
	fprintf(fp, "</rdf:Description>\n");

	for (pElement = pPaths; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);

		pszMtime = mtimeFromUri((const char *) pElement->data);

		fprintf(fp, "<rdf:Description about=\"%s\" serel:%s=\"%s\" />\n",
			(const char *) pElement->data,
			rdfPropertyFragment(eSerelMtime),
			pszMtime);

		free(pszMtime);
	}
}

int isGeneratedId(librdf_node *pNode)
{
	int ret = FALSE;
	const char *pszParserName = configAsString(eConfigKeyRdfParser);
	const char *pszLocal;
	librdf_node_type type = librdf_node_get_type(pNode);

	if (type == LIBRDF_NODE_TYPE_RESOURCE || type == LIBRDF_NODE_TYPE_PROPERTY)
	{
		pszLocal = local_part_of_uri(node_as_uri_string(pNode));

		if (strcmp(pszParserName, "repat") == 0)
		{
			char *pattern = "genid";

			ret = (strncmp(pszLocal, pattern, strlen(pattern)) == 0);
		}
		else
		{
			/*
			 * Need to know how to identify generated ids for this particular parser
			 */
			assert(NOTHERE);
		}
	}

	return ret;
}

/*
 * use pService as the basis for the rewritten uri
 */
librdf_node * rewriteGeneratedId(librdf_node *pNode, librdf_node *pService)
{
	librdf_node *pNewNode = NULL;
	const char *pszFragment, *pszBase;
	char *pszNewUri;

	assert(pNode && pService);

	pszFragment = local_part_of_uri(node_as_uri_string(pNode));
	pszBase     = node_as_uri_string(pService);
	pszNewUri   = malloc(strlen(pszBase) + strlen(pszFragment) + 2);

	if (pszFragment && pszBase && pszNewUri)
	{
		strcpy(pszNewUri, pszBase);

		strcat(pszNewUri, "_");
		strcat(pszNewUri, pszFragment);

		pNewNode = librdf_new_node_from_uri_string(pWorld, pszNewUri);
	}

	return pNewNode;
}

/*
 * addService(name)
 *	Add a service to the model:
 *	ie. this is equivalent to parsing this rdf into the model:
 *		<service rdf:ID="name" />
 *  creating this triple in the model:
 *	{
 *	  [http://localhost#bill],
 *	  [http://www.w3.org/1999/02/22-rdf-syntax-ns#type],
 *	  [http://www.fastboot.org/2002/03/graph-schema#service]
 *	}
 */
int addService(librdf_model *pModel, const char *pszBaseUri, const char *pszName)
{
	librdf_statement *pStatement = newServiceStatement(pszBaseUri, pszName);
	int failure = TRUE;

	if (pStatement)
	{
		failure = librdf_model_add_statement(pModel, pStatement);
	}

	return !failure;
}

librdf_node * newServiceNodeFromName(librdf_model *pModel, const char *pszBaseUri, const char *pszName)
{
	librdf_statement *pStatement = newServiceStatement(pszBaseUri, pszName);
	librdf_node *pReturn = NULL;
	int present;

	present = librdf_model_contains_statement(pModel, pStatement);

	if (present)
	{
		pReturn = librdf_new_node_from_node(librdf_statement_get_subject(pStatement));
	}

	librdf_free_statement(pStatement);

	return pReturn;
}

librdf_statement * newServiceStatement(const char *pszBaseUri, const char *pszName)
{
	librdf_node *pSubject, *pPredicate, *pObject;
	char *pszSubject = new_string_from_base_and_fragment(pszBaseUri, pszName);
	librdf_statement *pStatement = NULL;

	pSubject   = librdf_new_node_from_uri_string(pWorld, pszSubject);
	pPredicate = librdf_new_node_from_node(nodeFromCode(eRdfType));
	pObject    = librdf_new_node_from_node(nodeFromCode(eGraphService));

	if (pSubject && pPredicate && pObject)
	{
		pStatement = librdf_new_statement_from_nodes(pWorld, pSubject, pPredicate, pObject);
	}

	free(pszSubject);

	return pStatement;
}

librdf_node *edgeWithSource(librdf_model *pModel, const char *pszBaseUri, const char *pszService)
{
	librdf_node *pResult = NULL;
	librdf_node *pPredicate, *pObject;
	char *pszObject = new_string_from_base_and_fragment(pszBaseUri, pszService);

	pPredicate = librdf_new_node_from_node(nodeFromCode(eGraphFrom));
	pObject   = librdf_new_node_from_uri_string(pWorld, pszObject);

	if (pPredicate && pObject)
	{
		pResult = librdf_model_get_source(pModel, pPredicate, pObject);
	}

	free(pszObject);

	return pResult;
}

/*
 * makeEdge()
 *	Add an edge to the model, the three statements look like:
 * {
 * 	[http://localhost#edge-kudzu-qmail],
 *	[http://www.w3.org/1999/02/22-rdf-syntax-ns#type],
 *	[http://www.fastboot.org/2002/03/graph-schema#edge]
 * }
 * {
 *	[http://localhost#edge-kudzu-qmail],
 *	[http://www.fastboot.org/2002/03/graph-schema#source],
 *	[http://localhost#kudzu]
 * }
 * {
 *	[http://localhost#edge-kudzu-qmail],
 *	[http://www.fastboot.org/2002/03/graph-schema#target],
 *	[http://localhost#qmail]
 * }
 */
int makeEdge(librdf_model *pModel, const char *pszBaseUri, const char *pszFrom, const char *pszTo)
{
	librdf_node *pSubject, *pPredicate, *pObject;
	char *pszSubject, *pszSubjectFragment, *pszObject;
	char buf[200];
	int ret = TRUE;

	assert(pModel && pszBaseUri && pszFrom && pszTo);

	pszSubjectFragment = edgeFragmentFromPair(buf, sizeof(buf), pszFrom, pszTo);

	pszSubject = new_string_from_base_and_fragment(pszBaseUri, pszSubjectFragment);

	pSubject   = librdf_new_node_from_uri_string(pWorld, pszSubject);
	pPredicate = librdf_new_node_from_node(nodeFromCode(eRdfType));
	pObject    = librdf_new_node_from_node(nodeFromCode(eGraphEdge));

	ret = addStatementToModel(pModel, pSubject, pPredicate, pObject);

	if (ret)
	{
		pszObject  = new_string_from_base_and_fragment(pszBaseUri, pszFrom);

		pSubject   = librdf_new_node_from_uri_string(pWorld, pszSubject);
		pPredicate = librdf_new_node_from_node(nodeFromCode(eGraphFrom));
		pObject    = librdf_new_node_from_uri_string(pWorld, pszObject);

		free(pszObject);

		ret = addStatementToModel(pModel, pSubject, pPredicate, pObject);
	}
	
	if (ret)
	{
		pszObject  = new_string_from_base_and_fragment(pszBaseUri, pszTo);

		pSubject   = librdf_new_node_from_uri_string(pWorld, pszSubject);
		pPredicate = librdf_new_node_from_node(nodeFromCode(eGraphTo));
		pObject    = librdf_new_node_from_uri_string(pWorld, pszObject);

		free(pszObject);

		ret = addStatementToModel(pModel, pSubject, pPredicate, pObject);
	}

	free(pszSubject);

	return ret;
}

int addStatementToModel(librdf_model *pModel, librdf_node *pSubject, librdf_node *pPredicate, librdf_node *pObject)
{
	librdf_statement *pStatement = NULL;
	int failure = TRUE;

	if (pSubject && pPredicate && pObject)
	{
		pStatement = librdf_new_statement_from_nodes(pWorld, pSubject, pPredicate, pObject);
	}

	if (pStatement)
	{
		failure = librdf_model_add_statement(pModel, pStatement);
	}

	return !failure;
}

/* Return 0 if the two strings match
*/
gint compare_fn_str(gconstpointer v, gconstpointer v2)
{
	return g_str_equal(v, v2) ? 0 : 1;
}

CallbackReturnCode callbackRosterApplyStarted(RdfServer *pRdfServer, FILE *fpOut, const char *pszBigString)
{
	GSList *pStarted;
	GSList *pElement;
	librdf_model *pModel;
	librdf_node *pService;
	const char *pszService;
	CallbackReturnCode ret = eReturnSuccess;

	pModel = pRdfServer->table[eRdfModelRecentBoot].pModel;

	pStarted = newListOfStartedNodes(pModel);

	for (pElement = pStarted; ret == eReturnSuccess && pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);
		pService = (librdf_node *) pElement->data;
		pszService = local_part_of_uri(node_as_uri_string(pService));

		ret = setNodeAsStarted(pszService);
	}

	g_slist_free(pStarted);

	return ret;
}

GSList * newListOfStartedNodes(librdf_model *pModel)
{
	GSList *pReturn = NULL;
	GSList *pServices;
	GSList *pElement;
	librdf_node *pService;

	assert(pModel);

	/*
	 *	This routine can be made more efficient if boolean properties
	 *	were URIs instead of literals.  We could then use librdf_get_sources()
	 *	rather than iterating through a larger list and testing each.
	 *	leni - Fri Apr 19 06:53:22 EST 2002
	 */
	pServices = listOfReferencedNode(pModel, nodeFromCode(eGraphService), NULL);

	for (pElement = pServices; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement->data);

		pService = (librdf_node *) pElement->data;

		if (isNodeWithPropertyValue(pModel, pService, eSerelSourceUri, pszAutoGeneratedForStarted))
		{
			pReturn = g_slist_append(pReturn, pElement->data);
		}
	}

	g_slist_free(pServices);

	return pReturn;
}

CallbackReturnCode callbackRosterQueryCodeApply(RdfServer *pRdfServer, FILE *fpOut, const char *pszBigString)
{
	GSList *pElement;
	librdf_model *pModel;
	librdf_node *pService = NULL;
	librdf_node *pNodeFileUri = NULL;
	char *pszFileUri;
	CallbackReturnCode ret = eReturnFailure;

	assert(pRdfServer && pszBigString);

	pszFileUri = malloc(strlen(pszBigString) + strlen(pszFileScheme) + 1);

	strcpy(pszFileUri, pszFileScheme);
	strcat(pszFileUri, pszBigString);

	pNodeFileUri = librdf_new_node_from_uri_string(pWorld, pszFileUri);

	pModel = pRdfServer->table[eRdfModelRecentBoot].pModel;

	pService = librdf_model_get_source(pModel, nodeFromCode(eSerelStartUri), pNodeFileUri);

	if (pService)
	{
		GSList *pListReply = NULL;
		GSList *pNeeders;

		pListReply = g_slist_append(pListReply, (char *) local_part_of_uri(node_as_uri_string(pService)));

		pNeeders = listOfServiceEdges(pModel, pService);

		for (pElement = pNeeders; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			pListReply = g_slist_append(pListReply, (char *) local_part_of_uri(node_as_uri_string(pElement->data)));
		}

		for (pElement = pListReply; pElement != NULL; pElement = pElement->next)
		{
			assert(pElement->data);

			fprintf(fpOut, "%s ", (const char *) pElement->data);
		}

		{
			char *psz = newStringFromStringsInList(pListReply);
			printLog(eLogLevelRdf, "--apply %s, server responds with: %s\n", pszBigString, psz);
			free(psz);
		}
	
		for (pElement = pNeeders; pElement != NULL; pElement = pElement->next)
		{
			librdf_free_node((librdf_node *) pElement->data);
		}

		g_slist_free(pNeeders);
		g_slist_free(pListReply);

		ret = eReturnSuccess;
	}

	return ret;
}

GSList * listOfServiceEdges(librdf_model *pModel, librdf_node *pService)
{
	librdf_iterator *iterator;
	librdf_node *pEdge;
	librdf_node *pFrom;
	GSList *pResult = NULL;

	iterator = librdf_model_get_sources(pModel, nodeFromCode(eGraphTo), pService);

	while (!librdf_iterator_end(iterator))
	{
		pEdge = (librdf_node *) librdf_iterator_get_next(iterator);

		assert(pEdge != NULL);

		if (! nodeHasPropertyName(pModel, pEdge, eSerelMarkDeleted))
		{
			pFrom = librdf_model_get_target(pModel, pEdge, nodeFromCode(eGraphFrom));

			pResult = g_slist_append(pResult, pFrom);
		}
		else
		{
			librdf_free_node(pEdge);
		}
	}

	librdf_free_iterator(iterator); 

	return pResult;
}

char * listOfStringToHash(GSList *pList, GHashTable **ppHash)
{
	GHashTable *pHash;
	GSList *pElement;
	char *pszDuplicate = NULL;

	pHash = g_hash_table_new(g_str_hash, g_str_equal);

	for (pElement = pList; pszDuplicate == NULL && pElement != NULL; pElement = pElement->next)
	{
		pszDuplicate = (char *) g_hash_table_lookup(pHash, pElement->data);

		g_hash_table_insert(pHash, pElement->data, pElement->data);
	}

	if (ppHash)
	{
		*ppHash = pHash;
	}
	else
	{
		g_hash_table_destroy(pHash);
	}

	return pszDuplicate;
}

/*
 * Iterate through the models in the ServiceCatalogs looking for:
 *   <path rdf:ID="/etc/rc.d/rc5.d/S10network"> <serel:parse>false</serel:parse> </path>
 * and return the list of path names we're not going to parse.
 */

GSList * newIgnoreListFromServiceCatalogs(GSList *pListServiceCatalogRdf)
{
	GSList *pListParseFalse = NULL;
	GSList *pListParseTrue  = NULL;
	GSList *pElement;
	ServiceCatalog *pServiceCatalog;
	librdf_statement *pPartialStatement = librdf_new_statement(pWorld);
	librdf_statement *pStatement;
	librdf_stream    *pStreamOfServices;
	librdf_node      *pSubject, *pObject;
	librdf_model     *pModel;
	char *pszValue;

	for (pElement = pListServiceCatalogRdf; pElement != NULL; pElement = pElement->next)
	{
		assert(pElement);

		pServiceCatalog = (ServiceCatalog *) pElement->data;
	
		assert(pServiceCatalog && pServiceCatalog->pModel);

		pModel = pServiceCatalog->pModel;

		librdf_statement_set_predicate(pPartialStatement, nodeFromCode(eSerelParse));

		pStreamOfServices = librdf_model_find_statements(pModel, pPartialStatement);

		while (!librdf_stream_end(pStreamOfServices))
		{
			pStatement = librdf_stream_next(pStreamOfServices);

			if (pStatement)
			{
				pSubject = librdf_statement_get_subject(pStatement);

				pObject = librdf_model_get_target(pModel, pSubject, nodeFromCode(eSerelParse));

				pszValue = pObject ? librdf_node_get_literal_value(pObject) : NULL;

				if (pszValue)
				{
					char *pszUri = (char *) node_as_uri_string(pSubject);
					char *pszNewUri;

					/*
					 * If pszUri is a file scheme uri eg, file:/dir/file 
					 * then we strip "file:" and run realpath to resolve it to a real file
					 * otherwise pszUri is unchanged.
					 * Resolving the filename in this way makes for more robust matching.
					 */

					if (pszUri == stringFromUriRemovingFileScheme(pszUri))
					{
						pszNewUri = strdup(pszUri);
					}
					else
					{
						pszNewUri = newAbsoluteFromRelativePath(stringFromUriRemovingFileScheme(pszUri));
					}

					if (pszNewUri == NULL)
					{
						/*
						 * a possible cause is that realpath() is saying that the file doesn't exist.
						 * No matter, just go with the original
						 */
						pszNewUri = strdup(pszUri);
					}

					if (g_slist_find_custom(pListParseTrue, pszNewUri, compare_fn_str) || 
					    g_slist_find_custom(pListParseFalse, pszNewUri, compare_fn_str) )
					{
						free(pszNewUri);

						continue;
					}

					if (strcasecmp(pszValue, "true") == 0)
					{
						pListParseTrue = g_slist_append(pListParseTrue, pszNewUri);
					}
					else if (strcasecmp(pszValue, "false") == 0)
					{
						pListParseFalse = g_slist_append(pListParseFalse, pszNewUri);
					}
					else
					{
						free(pszNewUri);
					}
				}
			}
		}
	}

	{
		char *psz;

		psz = pListParseFalse ? newStringFromStringsInList(pListParseFalse) : strdup("none");
		printLog(eLogLevelRdf, "script paths where serel:parse is false: %s\n", psz);
		free(psz);

		psz = pListParseTrue ? newStringFromStringsInList(pListParseTrue) : strdup("none");
		printLog(eLogLevelRdf, "script paths where serel:parse is true: %s\n", psz);
		free(psz);
	}

	for (pElement = pListParseTrue; pElement != NULL; pElement = pElement->next)
	{
		free(pElement->data);
	}

	g_slist_free(pListParseTrue);

	return pListParseFalse;
}

void noteError(LogLevel level, char buf[], int bufsiz, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt); 
	vsnprintf(buf, bufsiz, fmt, ap);

	printLog(level, fmt, ap);

	va_end(ap);
}


/*
 * int testNoDependencies(const char *pszService)
 * Report an error if:
 *	- pszService depends on another service, or
 *	- another service depends on pszService
 */
int testNoDependencies(librdf_model *pModelRecentBoot, RdfServer *pRdfServer, char bufErrorMessage[], const char *pszService, RdfProperty code)
{
	librdf_node *pService;
	librdf_node *pSubject;
	int ret = TRUE;

	pService = newServiceNodeFromName(pModelRecentBoot, pRdfServer->pszBaseUri, pszService);

	pSubject = librdf_model_get_source(pModelRecentBoot, nodeFromCode(eGraphFrom), pService);

	if (pSubject)
	{
		noteError(eLogLevelError, bufErrorMessage, sizeof(bufErrorMessage),
			"Error: %s indicates a dependency on a service with the '%s' property: %s\n",
			local_part_of_uri(node_as_uri_string(pSubject)), rdfPropertyFragment(code), pszService); 

		ret = FALSE;
	}

	pSubject = librdf_model_get_source(pModelRecentBoot, nodeFromCode(eGraphTo), pService);

	if (pSubject)
	{
		noteError(eLogLevelError, bufErrorMessage, sizeof(bufErrorMessage),
			"Error: a service with the '%s' property (%s) shouldn't depend on another service (via %s)\n",
			pszService, rdfPropertyFragment(code), local_part_of_uri(node_as_uri_string(pSubject)));

		ret = FALSE;
	}

	return ret;
}
