diff options
Diffstat (limited to 'modules.c')
-rw-r--r-- | modules.c | 707 |
1 files changed, 556 insertions, 151 deletions
@@ -1,24 +1,32 @@ /* modules.c * This is the implementation of syslogd modules object. - * This object handles plug-ins and buil-in modules of all kind. + * This object handles plug-ins and build-in modules of all kind. + * + * Modules are reference-counted. Anyone who access a module must call + * Use() before any function is accessed and Release() when he is done. + * When the reference count reaches 0, rsyslog unloads the module (that + * may be changed in the future to cache modules). Rsyslog does NOT + * unload modules with a reference count > 0, even if the unload + * method is called! * * 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 file is part of rsyslog. + * + * Rsyslog 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 3 of the License, or + * (at your option) any later version. * - * This program is distributed in the hope that it will be useful, + * Rsyslog 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. + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ @@ -31,6 +39,9 @@ #include <time.h> #include <assert.h> #include <errno.h> +#ifdef OS_BSD +# include "libgen.h" +#endif #include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */ @@ -40,10 +51,117 @@ #include "syslogd.h" #include "cfsysline.h" #include "modules.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */ static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */ -static int bCfsyslineInitialized = 0; + +/* config settings */ +uchar *pModDir = NULL; /* read-only after startup */ + + +#ifdef DEBUG +/* we add some home-grown support to track our users (and detect who does not free us). In + * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 + */ + +/* add a user to the current list of users (always at the root) */ +static void +modUsrAdd(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + + BEGINfunc + if((pUsr = calloc(1, sizeof(modUsr_t))) == NULL) + goto finalize_it; + + if((pUsr->pszFile = strdup(pszUsr)) == NULL) { + free(pUsr); + goto finalize_it; + } + + if(pThis->pModUsrRoot != NULL) { + pUsr->pNext = pThis->pModUsrRoot; + } + pThis->pModUsrRoot = pUsr; + +finalize_it: + ENDfunc; +} + + +/* remove a user from the current user list + * rgerhards, 2008-03-11 + */ +static void +modUsrDel(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + modUsr_t *pPrev = NULL; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + if(!strcmp(pUsr->pszFile, pszUsr)) + break; + else + pPrev = pUsr; + } + + if(pUsr == NULL) { + dbgprintf("oops - tried to delete user %s from module %s and it wasn't registered as one...\n", + pszUsr, pThis->pszName); + } else { + if(pPrev == NULL) { + /* This was at the root! */ + pThis->pModUsrRoot = pUsr->pNext; + } else { + pPrev->pNext = pUsr->pNext; + } + /* free ressources */ + free(pUsr->pszFile); + free(pUsr); + pUsr = NULL; /* just to make sure... */ + } +} + + +/* print a short list all all source files using the module in question + * rgerhards, 2008-03-11 + */ +static void +modUsrPrint(modInfo_t *pThis) +{ + modUsr_t *pUsr; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + dbgprintf("\tmodule %s is currently in use by file %s\n", + pThis->pszName, pUsr->pszFile); + } +} + + +/* print all loaded modules and who is accessing them. This is primarily intended + * to be called at end of run to detect "module leaks" and who is causing them. + * rgerhards, 2008-03-11 + */ +//static void +void +modUsrPrintAll(void) +{ + modInfo_t *pMod; + + BEGINfunc + for(pMod = pLoadedModules ; pMod != NULL ; pMod = pMod->pNext) { + dbgprintf("printing users of loadable module %s, refcount %u, ptr %p, type %d\n", pMod->pszName, pMod->uRefCnt, pMod, pMod->eType); + modUsrPrint(pMod); + } + ENDfunc +} + +#endif /* #ifdef DEBUG */ /* Construct a new module object @@ -70,22 +188,29 @@ static rsRetVal moduleConstruct(modInfo_t **pThis) */ static void moduleDestruct(modInfo_t *pThis) { + assert(pThis != NULL); if(pThis->pszName != NULL) free(pThis->pszName); - if(pThis->pModHdlr != NULL) + if(pThis->pModHdlr != NULL) { +# ifdef VALGRIND +# warning "dlclose disabled for valgrind" +# else dlclose(pThis->pModHdlr); +# endif + } + free(pThis); } -/* The followind function is the queryEntryPoint for host-based entry points. +/* The following 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)()) +static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) { DEFiRet; @@ -94,11 +219,23 @@ rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) if(!strcmp((char*) name, "regCfSysLineHdlr")) { *pEtryPoint = regCfSysLineHdlr; + } else if(!strcmp((char*) name, "objGetObjInterface")) { + *pEtryPoint = objGetObjInterface; + } else { + *pEtryPoint = NULL; /* to be on the safe side */ + ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND); } - if(iRet == RS_RET_OK) - iRet = (*pEtryPoint == NULL) ? RS_RET_NOT_FOUND : RS_RET_OK; - return iRet; +finalize_it: + RETiRet; +} + + +/* get the name of a module + */ +static uchar *modGetName(modInfo_t *pThis) +{ + return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); } @@ -108,23 +245,16 @@ rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) * rgerhards, 2007-07-24 * TODO: the actual state name is not yet pulled */ -uchar *modGetStateName(modInfo_t *pThis) +static 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) +static inline void +addModToList(modInfo_t *pThis) { assert(pThis != NULL); @@ -132,6 +262,7 @@ static inline void addModToList(modInfo_t *pThis) pLoadedModules = pLoadedModulesLast = pThis; } else { /* there already exist entries */ + pThis->pPrev = pLoadedModulesLast; pLoadedModulesLast->pNext = pThis; pLoadedModulesLast = pThis; } @@ -145,7 +276,7 @@ static inline void addModToList(modInfo_t *pThis) * returned - then, the list is empty. * rgerhards, 2007-07-23 */ -modInfo_t *modGetNxt(modInfo_t *pThis) +static modInfo_t *GetNxt(modInfo_t *pThis) { modInfo_t *pNew; @@ -158,14 +289,20 @@ modInfo_t *modGetNxt(modInfo_t *pThis) } -/* this function is like modGetNxt(), but it returns pointers to - * output modules only. As we currently deal just with output modules, +/* this function is like GetNxt(), but it returns pointers to + * modules of specific type 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) +static modInfo_t *GetNxtType(modInfo_t *pThis, eModType_t rqtdType) { - return(modGetNxt(pThis)); + modInfo_t *pMod = pThis; + + do { + pMod = GetNxt(pMod); + } while(!(pMod == NULL || pMod->eType == rqtdType)); /* warning: do ... while() */ + + return pMod; } @@ -177,112 +314,90 @@ modInfo_t *omodGetNxt(modInfo_t *pThis) * 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). + * rgerhards, 2008-03-10: reject unload request if the module has a reference + * count > 0. */ -static rsRetVal modPrepareUnload(modInfo_t *pThis) +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 - */ + if(pThis->uRefCnt > 0) { + dbgprintf("rejecting unload of module '%s' because it has a refcount of %d\n", + pThis->pszName, pThis->uRefCnt); + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + CHKiRet(pThis->modGetID(&pModCookie)); pThis->modExit(); /* tell the module to get ready for unload */ CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie)); - /* END DEVEL */ - finalize_it: - return iRet; + RETiRet; } /* 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) +static rsRetVal +doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), uchar *name, void *pModHdlr) { DEFiRet; - modInfo_t *pNew; + modInfo_t *pNew = NULL; + rsRetVal (*modGetType)(eModType_t *pType); 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) { + pNew = NULL; + ABORT_FINALIZE(iRet); } - if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) - return iRet; + CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); - 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; + if(pNew->iIFVers != CURR_MOD_IF_VERSION) { + ABORT_FINALIZE(RS_RET_MISSING_INTERFACE); } + /* We now poll the module to see what type it is. We do this only once as this + * can never change in the lifetime of an module. -- rgerhards, 2007-12-14 + */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getType", &modGetType)); + CHKiRet((iRet = (*modGetType)(&pNew->eType)) != RS_RET_OK); + dbgprintf("module of type %d being loaded.\n", pNew->eType); + /* OK, we know we can successfully work with the module. So we now fill the - * rest of the data elements. + * rest of the data elements. First we load the interfaces common to all + * module types. */ - 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; + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)); + + /* ... and now the module-specific interfaces */ + switch(pNew->eType) { + case eMOD_IN: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"runInput", &pNew->mod.im.runInput)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"willRun", &pNew->mod.im.willRun)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"afterRun", &pNew->mod.im.afterRun)); + break; + case eMOD_OUT: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", &pNew->dbgPrintInstInfo)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)); + break; + case eMOD_LIB: + break; } 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; @@ -293,18 +408,24 @@ rsRetVal doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)()) addModToList(pNew); finalize_it: - return iRet; + if(iRet != RS_RET_OK) { + if(pNew != NULL) + moduleDestruct(pNew); + } + + RETiRet; } /* 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. + * TODO: update for new input modules! */ -void modPrintList(void) +static void modPrintList(void) { modInfo_t *pMod; - pMod = modGetNxt(NULL); + pMod = GetNxt(NULL); while(pMod != NULL) { dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ", (char*) modGetName(pMod), pMod->iIFVers); @@ -316,8 +437,8 @@ void modPrintList(void) case eMOD_IN: dbgprintf("input"); break; - case eMOD_FILTER: - dbgprintf("filter"); + case eMOD_LIB: + dbgprintf("library"); break; } dbgprintf(" module.\n"); @@ -328,70 +449,354 @@ void modPrintList(void) 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 */ + pMod = GetNxt(pMod); /* done, go next */ } } -/* unload all modules and free module linked list - * rgerhards, 2007-08-09 +/* unlink and destroy a module. The caller must provide a pointer to the module + * itself as well as one to its immediate predecessor. + * rgerhards, 2008-02-26 */ -rsRetVal modUnloadAndDestructAll(void) +static rsRetVal +modUnlinkAndDestroy(modInfo_t **ppThis) { DEFiRet; - modInfo_t *pMod; - modInfo_t *pModPrev; + modInfo_t *pThis; - 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); + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + /* first check if we are permitted to unload */ + if(pThis->eType == eMOD_LIB) { + if(pThis->uRefCnt > 0) { + dbgprintf("module %s NOT unloaded because it still has a refcount of %u\n", + pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + //modUsrPrintAll(); +# endif + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + } + + /* we need to unlink the module before we can destruct it -- rgerhards, 2008-02-26 */ + if(pThis->pPrev == NULL) { + /* module is root, so we need to set a new root */ + pLoadedModules = pThis->pNext; + } else { + pThis->pPrev->pNext = pThis->pNext; } - return iRet; + if(pThis->pNext == NULL) { + pLoadedModulesLast = pThis->pPrev; + } else { + pThis->pNext->pPrev = pThis->pPrev; + } + + /* finally, we are ready for the module to go away... */ + dbgprintf("Unloading module %s\n", modGetName(pThis)); + CHKiRet(modPrepareUnload(pThis)); + *ppThis = pThis->pNext; + + moduleDestruct(pThis); + +finalize_it: + RETiRet; } -rsRetVal modUnloadAndDestructDynamic(void) +/* unload all loaded modules of a specific type (use eMOD_ALL if you want to + * unload all module types). The unload happens only if the module is no longer + * referenced. So some modules may survive this call. + * rgerhards, 2008-03-11 + */ +static rsRetVal +modUnloadAndDestructAll(eModLinkType_t modLinkTypesToUnload) { DEFiRet; - modInfo_t *pMod; - modInfo_t *pModPrev; + modInfo_t *pModCurr; /* module currently being processed */ + + pModCurr = GetNxt(NULL); + while(pModCurr != NULL) { + if(modLinkTypesToUnload == eMOD_LINK_ALL || pModCurr->eLinkType == modLinkTypesToUnload) { + if(modUnlinkAndDestroy(&pModCurr) == RS_RET_MODULE_STILL_REFERENCED) { + pModCurr = GetNxt(pModCurr); + } + /* Note: if the module was successfully unloaded, it has updated the + * pModCurr pointer to the next module. So we do NOT need to advance + * to the next module on successful unload. + */ + } else { + pModCurr = GetNxt(pModCurr); + } + } + +# ifdef DEBUG + if(pLoadedModules != NULL) { + dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); + modUsrPrintAll(); + } +# endif - pLoadedModulesLast = NULL; + RETiRet; +} - 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; + +/* load a module and initialize it, based on doModLoad() from conf.c + * rgerhards, 2008-03-05 + * varmojfekoj added support for dynamically loadable modules on 2007-08-13 + * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is + * called below. This is ok because modules are currently only loaded during + * configuration file processing, which is executed on a single thread. Should we + * change that design at any stage (what is unlikely), we need to find a + * replacement. + */ +static rsRetVal +Load(uchar *pModName) +{ + DEFiRet; + + size_t iPathLen, iModNameLen; + uchar szPath[PATH_MAX]; + uchar *pModNameCmp; + int bHasExtension; + void *pModHdlr, *pModInit; + modInfo_t *pModInfo; + + assert(pModName != NULL); + dbgprintf("Requested to load module '%s'\n", pModName); + + iModNameLen = strlen((char *) pModName); + if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) { + iModNameLen -= 3; + bHasExtension = TRUE; + } else + bHasExtension = FALSE; + + pModInfo = GetNxt(NULL); + while(pModInfo != NULL) { + if(!strncmp((char *) pModName, (char *) (pModNameCmp = modGetName(pModInfo)), iModNameLen) && + (!*(pModNameCmp + iModNameLen) || !strcmp((char *) pModNameCmp + iModNameLen, ".so"))) { + dbgprintf("Module '%s' already loaded\n", pModName); + ABORT_FINALIZE(RS_RET_OK); } + pModInfo = GetNxt(pModInfo); } - /* Note: the last modules pNext pointer is now invalid - * (except if the last module was not touched, what is highly - * unlikely. We simply fix this be setting it to NULL. After all, - * it is the last module ;). This bug had some severe effects in - * v3, but none in v2 because in v2 the list was never again - * traversed before a new one was added. But even in v2 it may cause - * a segfault if the number of loaded modules changed between HUPs. - * rgerhards, 2008-02-26 - */ - if(pLoadedModulesLast != NULL) - pLoadedModulesLast->pNext = NULL; + /* now build our load module name */ + if(*pModName == '/') { + *szPath = '\0'; /* we do not need to append the path - its already in the module name */ + iPathLen = 0; + } else { + *szPath = '\0'; + strncat((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath) - 1); + iPathLen = strlen((char*) szPath); + if((szPath[iPathLen - 1] != '/')) { + if((iPathLen <= sizeof(szPath) - 2)) { + szPath[iPathLen++] = '/'; + szPath[iPathLen] = '\0'; + } else { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + } + } + + /* ... add actual name ... */ + strncat((char *) szPath, (char *) pModName, sizeof(szPath) - iPathLen - 1); + + /* now see if we have an extension and, if not, append ".so" */ + if(!bHasExtension) { + /* we do not have an extension and so need to add ".so" + * TODO: I guess this is highly importable, so we should change the + * algo over time... -- rgerhards, 2008-03-05 + */ + /* ... so now add the extension */ + strncat((char *) szPath, ".so", sizeof(szPath) - strlen((char*) szPath) - 1); + iPathLen += 3; + } + + if(iPathLen + strlen((char*) pModName) >= sizeof(szPath)) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + + /* complete load path constructed, so ... GO! */ + dbgprintf("loading module '%s'\n", szPath); + if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlopen: %s\n", szPath, dlerror()); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN); + } + if(!(pModInit = dlsym(pModHdlr, "modInit"))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlsym: %s\n", szPath, dlerror()); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT); + } + if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', rsyslog error %d\n", szPath, iRet); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); + } - return iRet; +finalize_it: + RETiRet; } -/* - * vi:set ai: + + +/* set the default module load directory. A NULL value may be provided, in + * which case any previous value is deleted but no new one set. The caller-provided + * string is duplicated. If it needs to be freed, that's the caller's duty. + * rgerhards, 2008-03-07 + */ +static rsRetVal +SetModDir(uchar *pszModDir) +{ + DEFiRet; + + dbgprintf("setting default module load directory '%s'\n", pszModDir); + if(pModDir != NULL) { + free(pModDir); + } + + pModDir = (uchar*) strdup((char*)pszModDir); + + RETiRet; +} + + +/* Reference-Counting object access: add 1 to the current reference count. Must be + * called by anyone interested in using a module. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Use(char *srcFile, modInfo_t *pThis) +{ + DEFiRet; + + assert(pThis != NULL); + pThis->uRefCnt++; + dbgprintf("source file %s requested reference for module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); + +# ifdef DEBUG + modUsrAdd(pThis, srcFile); +# endif + + RETiRet; + +} + + +/* Reference-Counting object access: subract one from the current refcount. Must + * by called by anyone who no longer needs a module. If count reaches 0, the + * module is unloaded. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Release(char *srcFile, modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + if(pThis->uRefCnt == 0) { + /* oops, we are already at 0? */ + dbgprintf("internal error: module '%s' already has a refcount of 0 (released by %s)!\n", + pThis->pszName, srcFile); + } else { + --pThis->uRefCnt; + dbgprintf("file %s released module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + modUsrDel(pThis, srcFile); + modUsrPrint(pThis); +# endif + } + + if(pThis->uRefCnt == 0) { + /* we have a zero refcount, so we must unload the module */ + dbgprintf("module '%s' has zero reference count, unloading...\n", pThis->pszName); + modUnlinkAndDestroy(&pThis); + /* we must NOT do a *ppThis = NULL, because ppThis now points into freed memory! + * If in doubt, see obj.c::ReleaseObj() for how we are called. + */ + } + + RETiRet; + +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(module, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(module) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + +# ifdef DEBUG + modUsrPrintAll(); /* debug aid - TODO: integrate with debug.c, at least the settings! */ +# endif +ENDObjClassExit(module) + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(module) +CODESTARTobjQueryInterface(module) + if(pIf->ifVersion != moduleCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->GetNxt = GetNxt; + pIf->GetNxtType = GetNxtType; + pIf->GetName = modGetName; + pIf->GetStateName = modGetStateName; + pIf->PrintList = modPrintList; + pIf->UnloadAndDestructAll = modUnloadAndDestructAll; + pIf->doModInit = doModInit; + pIf->SetModDir = SetModDir; + pIf->Load = Load; + pIf->Use = Use; + pIf->Release = Release; +finalize_it: +ENDobjQueryInterface(module) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-03-05 + */ +BEGINAbstractObjClassInit(module, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + uchar *pModPath; + + /* use any module load path specified in the environment */ + if((pModPath = (uchar*) getenv("RSYSLOG_MODDIR")) != NULL) { + SetModDir(pModPath); + } + + /* now check if another module path was set via the command line (-M) + * if so, that overrides the environment. Please note that we must use + * a global setting here because the command line parser can NOT call + * into the module object, because it is not initialized at that point. So + * instead a global setting is changed and we pick it up as soon as we + * initialize -- rgerhards, 2008-04-04 + */ + if(glblModPath != NULL) { + SetModDir(glblModPath); + } + + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDObjClassInit(module) + +/* vi:set ai: */ |