/* modules.c
 * This is the implementation of syslogd modules object.
 * This object handles plug-ins and buil-in modules of all kind.
 *
 * File begun on 2007-07-22 by RGerhards
 *
 * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 */
#include "config.h"
#include "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <errno.h>

#include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */

#include <unistd.h>
#include <sys/file.h>

#include "syslogd.h"
#include "cfsysline.h"
#include "modules.h"

static modInfo_t *pLoadedModules = NULL;	/* list of currently-loaded modules */
static modInfo_t *pLoadedModulesLast = NULL;	/* tail-pointer */
static int bCfsyslineInitialized = 0;


/* Construct a new module object
 */
static rsRetVal moduleConstruct(modInfo_t **pThis)
{
	modInfo_t *pNew;

	if((pNew = calloc(1, sizeof(modInfo_t))) == NULL)
		return RS_RET_OUT_OF_MEMORY;

	/* OK, we got the element, now initialize members that should
	 * not be zero-filled.
	 */

	*pThis = pNew;
	return RS_RET_OK;
}


/* Destructs a module object. The object must not be linked to the
 * linked list of modules. Please note that all other dependencies on this
 * modules must have been removed before (e.g. CfSysLineHandlers!)
 */
static void moduleDestruct(modInfo_t *pThis)
{
	if(pThis->pszName != NULL)
		free(pThis->pszName);
	if(pThis->pModHdlr != NULL)
		dlclose(pThis->pModHdlr);
	free(pThis);
}


/* The followind function is the queryEntryPoint for host-based entry points.
 * Modules may call it to get access to core interface functions. Please note
 * that utility functions can be accessed via shared libraries - at least this
 * is my current shool of thinking.
 * Please note that the implementation as a query interface allows to take
 * care of plug-in interface version differences. -- rgerhards, 2007-07-31
 */
rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)())
{
	DEFiRet;

	if((name == NULL) || (pEtryPoint == NULL))
		return RS_RET_PARAM_ERROR;

	if(!strcmp((char*) name, "regCfSysLineHdlr")) {
		*pEtryPoint = regCfSysLineHdlr;
	}

	if(iRet == RS_RET_OK)
		iRet = (*pEtryPoint == NULL) ? RS_RET_NOT_FOUND : RS_RET_OK;
	return iRet;
}


/* get the state-name of a module. The state name is its name
 * together with a short description of the module state (which
 * is pulled from the module itself.
 * rgerhards, 2007-07-24
 * TODO: the actual state name is not yet pulled
 */
uchar *modGetStateName(modInfo_t *pThis)
{
	return(modGetName(pThis));
}


/* get the name of a module
 */
uchar *modGetName(modInfo_t *pThis)
{
	return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName);
}


/* Add a module to the loaded module linked list
 */
static inline void addModToList(modInfo_t *pThis)
{
	assert(pThis != NULL);

	if(pLoadedModules == NULL) {
		pLoadedModules = pLoadedModulesLast = pThis;
	} else {
		/* there already exist entries */
		pLoadedModulesLast->pNext = pThis;
		pLoadedModulesLast = pThis;
	}
}


/* Get the next module pointer - this is used to traverse the list.
 * The function returns the next pointer or NULL, if there is no next one.
 * The last object must be provided to the function. If NULL is provided,
 * it starts at the root of the list. Even in this case, NULL may be 
 * returned - then, the list is empty.
 * rgerhards, 2007-07-23
 */
modInfo_t *modGetNxt(modInfo_t *pThis)
{
	modInfo_t *pNew;

	if(pThis == NULL)
		pNew = pLoadedModules;
	else
		pNew = pThis->pNext;

	return(pNew);
}


/* this function is like modGetNxt(), but it returns pointers to
 * output modules only. As we currently deal just with output modules,
 * it is a dummy, to be filled with real code later.
 * rgerhards, 2007-07-24
 */
modInfo_t *omodGetNxt(modInfo_t *pThis)
{
	return(modGetNxt(pThis));
}


/* Prepare a module for unloading.
 * This is currently a dummy, to be filled when we have a plug-in
 * interface - rgerhards, 2007-08-09
 * rgerhards, 2007-11-21:
 * When this function is called, all instance-data must already have
 * been destroyed. In the case of output modules, this happens when the
 * rule set is being destroyed. When we implement other module types, we
 * need to think how we handle it there (and if we have any instance data).
 */
static rsRetVal modPrepareUnload(modInfo_t *pThis)
{
	DEFiRet;
	void *pModCookie;

	assert(pThis != NULL);

	/* WARNING - the current code does NOT work and causes an abort - this is acceptable right now
	 * as I am DEVELOPING the working code and will NOT release until it is there. If you use a
	 * CVS snapshot, be aware of this limitation. For now, you can just remove everything up to
	 * (but not including) the END DEVEL comment. That will do the trick. rgerhards, 2007-11-21
	 */
	CHKiRet(pThis->modGetID(&pModCookie));
	pThis->modExit(); /* tell the module to get ready for unload */
	CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie));

	/* END DEVEL */

finalize_it:
	return iRet;
}


/* Add an already-loaded module to the module linked list. This function does
 * everything needed to fully initialize the module.
 */
rsRetVal doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)()), uchar *name, void *pModHdlr)
{
	DEFiRet;
	modInfo_t *pNew;

	assert(modInit != NULL);

	if(bCfsyslineInitialized == 0) {
		/* we need to initialize the cfsysline subsystem first */
		CHKiRet(cfsyslineInit());
		bCfsyslineInitialized = 1;
	}

	if((iRet = moduleConstruct(&pNew)) != RS_RET_OK)
		return iRet;

	if((iRet = (*modInit)(1, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}

	if(pNew->iIFVers != 1) {
		moduleDestruct(pNew);
		return RS_RET_MISSING_INTERFACE;
	}

	/* OK, we know we can successfully work with the module. So we now fill the
	 * rest of the data elements.
	 */
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature",
			                   &pNew->isCompatibleWithFeature)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo",
			                   &pNew->dbgPrintInstInfo)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"getWriteFDForSelect", &pNew->getWriteFDForSelect)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"onSelectReadyWrite", &pNew->onSelectReadyWrite)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"needUDPSocket", &pNew->needUDPSocket)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}
	if((iRet = (*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)) != RS_RET_OK) {
		moduleDestruct(pNew);
		return iRet;
	}

	pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */
	pNew->pModHdlr = pModHdlr;
	pNew->eType = eMOD_OUT; /* TODO: take this from module */
	/* TODO: take this from module */
	if(pModHdlr == NULL)
		pNew->eLinkType = eMOD_LINK_STATIC;
	else
		pNew->eLinkType = eMOD_LINK_DYNAMIC_LOADED;

	/* we initialized the structure, now let's add it to the linked list of modules */
	addModToList(pNew);

finalize_it:
	return iRet;
}

/* Print loaded modules. This is more or less a 
 * debug or test aid, but anyhow I think it's worth it...
 * This only works if the dbgprintf() subsystem is initialized.
 */
void modPrintList(void)
{
	modInfo_t *pMod;

	pMod = modGetNxt(NULL);
	while(pMod != NULL) {
		dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ",
			(char*) modGetName(pMod), pMod->iIFVers);
		dbgprintf("type=");
		switch(pMod->eType) {
		case eMOD_OUT:
			dbgprintf("output");
			break;
		case eMOD_IN:
			dbgprintf("input");
			break;
		case eMOD_FILTER:
			dbgprintf("filter");
			break;
		}
		dbgprintf(" module.\n");
		dbgprintf("Entry points:\n");
		dbgprintf("\tqueryEtryPt:        0x%lx\n", (unsigned long) pMod->modQueryEtryPt);
		dbgprintf("\tdoAction:           0x%lx\n", (unsigned long) pMod->mod.om.doAction);
		dbgprintf("\tparseSelectorAct:   0x%lx\n", (unsigned long) pMod->mod.om.parseSelectorAct);
		dbgprintf("\tdbgPrintInstInfo:   0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo);
		dbgprintf("\tfreeInstance:       0x%lx\n", (unsigned long) pMod->freeInstance);
		dbgprintf("\n");
		pMod = modGetNxt(pMod); /* done, go next */
	}
}


/* unload all modules and free module linked list
 * rgerhards, 2007-08-09
 */
rsRetVal modUnloadAndDestructAll(void)
{
	DEFiRet;
	modInfo_t *pMod;
	modInfo_t *pModPrev;

	pMod = modGetNxt(NULL);
	while(pMod != NULL) {
		pModPrev = pMod;
		pMod = modGetNxt(pModPrev); /* get next */
		/* now we can destroy the previous module */
		dbgprintf("Unloading module %s\n", modGetName(pModPrev));
		modPrepareUnload(pModPrev);
		moduleDestruct(pModPrev);
	}

	return iRet;
}


rsRetVal modUnloadAndDestructDynamic(void)
{
	DEFiRet;
	modInfo_t *pMod;
	modInfo_t *pModPrev;

	pLoadedModulesLast = NULL;

	pMod = modGetNxt(NULL);
	while(pMod != NULL) {
		pModPrev = pMod;
		pMod = modGetNxt(pModPrev); /* get next */
		/* now we can destroy the previous module */
		if(pModPrev->eLinkType != eMOD_LINK_STATIC) {
			dbgprintf("Unloading module %s\n", modGetName(pModPrev));
			modPrepareUnload(pModPrev);
			moduleDestruct(pModPrev);
		} else {
			pLoadedModulesLast = pModPrev;
		}
	}

	return iRet;
}
/*
 * vi:set ai:
 */


syntax highlighted by Code2HTML, v. 0.9.1