/* omlibdbi.c * This is the implementation of the dbi output module. * * NOTE: read comments in module-template.h to understand how this file * works! * * This depends on libdbi being present with the proper settings. Older * versions do not necessarily have them. Please visit this bug tracker * for details: http://bugzilla.adiscon.com/show_bug.cgi?id=31 * * File begun on 2008-02-14 by RGerhards (extracted from syslogd.c) * * Copyright 2008-2016 Adiscon GmbH. * * This file is part of rsyslog. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * -or- * see COPYING.ASL20 in the source distribution * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "rsyslog.h" #include #include #include #include #include #include #include #include #include #include #include "dirty.h" #include "syslogd-types.h" #include "cfsysline.h" #include "conf.h" #include "srUtils.h" #include "template.h" #include "module-template.h" #include "debug.h" #include "errmsg.h" #include "conf.h" #undef HAVE_DBI_TXSUPP /* transaction support disabled in v8 -- TODO: reenable */ MODULE_TYPE_OUTPUT MODULE_TYPE_NOKEEP MODULE_CNFNAME("omlibdbi") /* internal structures */ DEF_OMOD_STATIC_DATA static int bDbiInitialized = 0; /* dbi_initialize() can only be called one - this keeps track of it */ typedef struct _instanceData { uchar *dbiDrvrDir; /* where do the dbi drivers reside? */ dbi_conn conn; /* handle to database */ uchar *drvrName; /* driver to use */ uchar *host; /* host to connect to */ uchar *usrName; /* user name for connect */ uchar *pwd; /* password for connect */ uchar *dbName; /* database to use */ unsigned uLastDBErrno; /* last errno returned by libdbi or 0 if all is well */ uchar *tplName; /* format template to use */ int txSupport; /* transaction support */ } instanceData; typedef struct wrkrInstanceData { instanceData *pData; } wrkrInstanceData_t; typedef struct configSettings_s { uchar *dbiDrvrDir; /* global: where do the dbi drivers reside? */ uchar *drvrName; /* driver to use */ uchar *host; /* host to connect to */ uchar *usrName; /* user name for connect */ uchar *pwd; /* password for connect */ uchar *dbName; /* database to use */ } configSettings_t; static configSettings_t cs; static uchar *pszFileDfltTplName; /* name of the default template to use */ struct modConfData_s { rsconf_t *pConf; /* our overall config object */ uchar *dbiDrvrDir; /* where do the dbi drivers reside? */ uchar *tplName; /* default template */ }; static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */ static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */ static pthread_mutex_t mutDoAct = PTHREAD_MUTEX_INITIALIZER; /* tables for interfacing with the v6 config system */ /* module-global parameters */ static struct cnfparamdescr modpdescr[] = { { "template", eCmdHdlrGetWord, 0 }, { "driverdirectory", eCmdHdlrGetWord, 0 } }; static struct cnfparamblk modpblk = { CNFPARAMBLK_VERSION, sizeof(modpdescr)/sizeof(struct cnfparamdescr), modpdescr }; /* action (instance) parameters */ static struct cnfparamdescr actpdescr[] = { { "server", eCmdHdlrGetWord, 1 }, { "db", eCmdHdlrGetWord, 1 }, { "uid", eCmdHdlrGetWord, 1 }, { "pwd", eCmdHdlrGetWord, 1 }, { "driver", eCmdHdlrGetWord, 1 }, { "template", eCmdHdlrGetWord, 0 } }; static struct cnfparamblk actpblk = { CNFPARAMBLK_VERSION, sizeof(actpdescr)/sizeof(struct cnfparamdescr), actpdescr }; /* this function gets the default template. It coordinates action between * old-style and new-style configuration parts. */ static uchar* getDfltTpl(void) { if(loadModConf != NULL && loadModConf->tplName != NULL) return loadModConf->tplName; else if(pszFileDfltTplName == NULL) return (uchar*)" StdDBFmt"; else return pszFileDfltTplName; } BEGINinitConfVars /* (re)set config variables to default values */ CODESTARTinitConfVars cs.dbiDrvrDir = NULL; cs.drvrName = NULL; cs.host = NULL; cs.usrName = NULL; cs.pwd = NULL; cs.dbName = NULL; ENDinitConfVars /* config settings */ #ifdef HAVE_DBI_R static dbi_inst dbiInst; #endif BEGINcreateInstance CODESTARTcreateInstance ENDcreateInstance BEGINcreateWrkrInstance CODESTARTcreateWrkrInstance ENDcreateWrkrInstance BEGINisCompatibleWithFeature CODESTARTisCompatibleWithFeature /* we do not like repeated message reduction inside the database */ ENDisCompatibleWithFeature /* The following function is responsible for closing a * database connection. */ static void closeConn(instanceData *pData) { assert(pData != NULL); if(pData->conn != NULL) { /* just to be on the safe side... */ dbi_conn_close(pData->conn); pData->conn = NULL; } } BEGINfreeInstance CODESTARTfreeInstance free(pData->drvrName); free(pData->host); free(pData->usrName); free(pData->pwd); free(pData->dbName); ENDfreeInstance BEGINfreeWrkrInstance CODESTARTfreeWrkrInstance closeConn(pWrkrData->pData); ENDfreeWrkrInstance BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo /* nothing special here */ ENDdbgPrintInstInfo /* log a database error with descriptive message. * We check if we have a valid database handle. If not, we simply * report an error, but can not be specific. RGerhards, 2007-01-30 */ static void reportDBError(instanceData *pData, int bSilent) { unsigned uDBErrno; char errMsg[1024]; const char *pszDbiErr; assert(pData != NULL); /* output log message */ errno = 0; if(pData->conn == NULL) { LogError(0, NO_ERRCODE, "unknown DB error occurred - could not obtain connection handle"); } else { /* we can ask dbi for the error description... */ uDBErrno = dbi_conn_error(pData->conn, &pszDbiErr); snprintf(errMsg, sizeof(errMsg), "db error (%d): %s\n", uDBErrno, pszDbiErr); if(bSilent || uDBErrno == pData->uLastDBErrno) dbgprintf("libdbi, DBError(silent): %s\n", errMsg); else { pData->uLastDBErrno = uDBErrno; LogError(0, NO_ERRCODE, "%s", errMsg); } } } /* The following function is responsible for initializing a connection */ static rsRetVal initConn(instanceData *pData, int bSilent) { DEFiRet; int iDrvrsLoaded; assert(pData != NULL); assert(pData->conn == NULL); if(bDbiInitialized == 0) { /* we need to init libdbi first */ # ifdef HAVE_DBI_R iDrvrsLoaded = dbi_initialize_r((char*) pData->dbiDrvrDir, &dbiInst); # else iDrvrsLoaded = dbi_initialize((char*) pData->dbiDrvrDir); # endif if(iDrvrsLoaded == 0) { LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi or libdbi drivers not " "present on this system - suspending."); ABORT_FINALIZE(RS_RET_SUSPENDED); } else if(iDrvrsLoaded < 0) { LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi could not be " "initialized (do you have any dbi drivers installed?) - suspending."); ABORT_FINALIZE(RS_RET_SUSPENDED); } bDbiInitialized = 1; /* we are done for the rest of our existence... */ } # ifdef HAVE_DBI_R pData->conn = dbi_conn_new_r((char*)pData->drvrName, dbiInst); # else pData->conn = dbi_conn_new((char*)pData->drvrName); # endif if(pData->conn == NULL) { LogError(0, RS_RET_SUSPENDED, "can not initialize libdbi connection"); ABORT_FINALIZE(RS_RET_SUSPENDED); } else { /* we could get the handle, now on with work... */ /* Connect to database */ dbi_conn_set_option(pData->conn, "host", (char*) pData->host); dbi_conn_set_option(pData->conn, "username", (char*) pData->usrName); /* libdbi-driver-sqlite(2/3) requires to provide sqlite3_db dir which is absolute path, where database file lives, * and dbname, which is database file name itself. So in order to keep the config API unchanged, * we split the dbname to path and filename. */ int is_sqlite2 = !strcmp((const char *)pData->drvrName, "sqlite"); int is_sqlite3 = !strcmp((const char *)pData->drvrName, "sqlite3"); if(is_sqlite2 || is_sqlite3) { char *const dn_org = strdup((char*)pData->dbName); char *const dn = dirname(dn_org); dbi_conn_set_option(pData->conn, is_sqlite3 ? "sqlite3_dbdir" : "sqlite_dbdir",dn); free(dn_org); /* Free original buffer - dirname may return different pointer */ char *tmp = strdup((char*)pData->dbName); char *bn = basename(tmp); dbi_conn_set_option(pData->conn, "dbname", bn); free(tmp); } else { dbi_conn_set_option(pData->conn, "dbname", (char*) pData->dbName); } if(pData->pwd != NULL) dbi_conn_set_option(pData->conn, "password", (char*) pData->pwd); if(dbi_conn_connect(pData->conn) < 0) { reportDBError(pData, bSilent); closeConn(pData); /* ignore any error we may get */ ABORT_FINALIZE(RS_RET_SUSPENDED); } pData->txSupport = dbi_conn_cap_get(pData->conn, "transaction_support"); } finalize_it: RETiRet; } /* The following function writes the current log entry * to an established database connection. */ static rsRetVal writeDB(const uchar *psz, instanceData *const __restrict__ pData) { DEFiRet; dbi_result dbiRes = NULL; assert(psz != NULL); assert(pData != NULL); /* see if we are ready to proceed */ if(pData->conn == NULL) { CHKiRet(initConn(pData, 0)); } /* try insert */ if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { /* error occurred, try to re-init connection and retry */ closeConn(pData); /* close the current handle */ CHKiRet(initConn(pData, 0)); /* try to re-open */ if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { /* re-try insert */ /* we failed, giving up for now */ reportDBError(pData, 0); closeConn(pData); /* free ressources */ ABORT_FINALIZE(RS_RET_SUSPENDED); } } finalize_it: if(iRet == RS_RET_OK) { pData->uLastDBErrno = 0; /* reset error for error supression */ } if(dbiRes != NULL) dbi_result_free(dbiRes); RETiRet; } BEGINtryResume CODESTARTtryResume if(pWrkrData->pData->conn == NULL) { iRet = initConn(pWrkrData->pData, 1); } ENDtryResume /* transaction support 2013-03 */ BEGINbeginTransaction CODESTARTbeginTransaction if(pWrkrData->pData->conn == NULL) { CHKiRet(initConn(pWrkrData->pData, 0)); } # ifdef HAVE_DBI_TXSUPP if (pData->txSupport == 1) { if (dbi_conn_transaction_begin(pData->conn) != 0) { const char *emsg; dbi_conn_error(pData->conn, &emsg); dbgprintf("libdbi server error: begin transaction " "not successful: %s\n", emsg); closeConn(pData); ABORT_FINALIZE(RS_RET_SUSPENDED); } } # endif finalize_it: ENDbeginTransaction /* end transaction */ BEGINdoAction CODESTARTdoAction pthread_mutex_lock(&mutDoAct); CHKiRet(writeDB(ppString[0], pWrkrData->pData)); # ifdef HAVE_DBI_TXSUPP if (pData->txSupport == 1) { iRet = RS_RET_DEFER_COMMIT; } # endif finalize_it: pthread_mutex_unlock(&mutDoAct); ENDdoAction /* transaction support 2013-03 */ BEGINendTransaction CODESTARTendTransaction # ifdef HAVE_DBI_TXSUPP if (dbi_conn_transaction_commit(pData->conn) != 0) { const char *emsg; dbi_conn_error(pData->conn, &emsg); dbgprintf("libdbi server error: transaction not committed: %s\n", emsg); closeConn(pData); iRet = RS_RET_SUSPENDED; } # endif ENDendTransaction /* end transaction */ BEGINbeginCnfLoad CODESTARTbeginCnfLoad loadModConf = pModConf; pModConf->pConf = pConf; pModConf->tplName = NULL; bLegacyCnfModGlobalsPermitted = 1; ENDbeginCnfLoad BEGINsetModCnf struct cnfparamvals *pvals = NULL; int i; CODESTARTsetModCnf pvals = nvlstGetParams(lst, &modpblk, NULL); if(pvals == NULL) { LogError(0, RS_RET_MISSING_CNFPARAMS, "omlibdbi: error processing " "module config parameters [module(...)]"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } if(Debug) { dbgprintf("module (global) param blk for omlibdbi:\n"); cnfparamsPrint(&modpblk, pvals); } for(i = 0 ; i < modpblk.nParams ; ++i) { if(!pvals[i].bUsed) continue; if(!strcmp(modpblk.descr[i].name, "template")) { loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); if(pszFileDfltTplName != NULL) { LogError(0, RS_RET_DUP_PARAM, "omlibdbi: warning: default template " "was already set via legacy directive - may lead to inconsistent " "results."); } } else if(!strcmp(modpblk.descr[i].name, "driverdirectory")) { loadModConf->dbiDrvrDir = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else { dbgprintf("omlibdbi: program error, non-handled " "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); } } bLegacyCnfModGlobalsPermitted = 0; finalize_it: if(pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk); ENDsetModCnf BEGINendCnfLoad CODESTARTendCnfLoad loadModConf = NULL; /* done loading */ /* free legacy config vars */ free(cs.dbiDrvrDir); free(cs.drvrName); free(cs.host); free(cs.usrName); free(cs.pwd); free(cs.dbName); cs.dbiDrvrDir = NULL; cs.drvrName = NULL; cs.host = NULL; cs.usrName = NULL; cs.pwd = NULL; cs.dbName = NULL; free(pszFileDfltTplName); pszFileDfltTplName = NULL; ENDendCnfLoad BEGINcheckCnf CODESTARTcheckCnf ENDcheckCnf BEGINactivateCnf CODESTARTactivateCnf runModConf = pModConf; ENDactivateCnf BEGINfreeCnf CODESTARTfreeCnf free(pModConf->tplName); free(pModConf->dbiDrvrDir); ENDfreeCnf static inline void setInstParamDefaults(instanceData *pData) { pData->tplName = NULL; } BEGINnewActInst struct cnfparamvals *pvals; uchar *tplToUse; int i; CODESTARTnewActInst if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) { ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } CHKiRet(createInstance(&pData)); setInstParamDefaults(pData); CODE_STD_STRING_REQUESTnewActInst(1) for(i = 0 ; i < actpblk.nParams ; ++i) { if(!pvals[i].bUsed) continue; if(!strcmp(actpblk.descr[i].name, "server")) { pData->host = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(actpblk.descr[i].name, "db")) { pData->dbName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(actpblk.descr[i].name, "uid")) { pData->usrName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(actpblk.descr[i].name, "pwd")) { pData->pwd = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(actpblk.descr[i].name, "driver")) { pData->drvrName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else if(!strcmp(actpblk.descr[i].name, "template")) { pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else { dbgprintf("omlibdbi: program error, non-handled " "param '%s'\n", actpblk.descr[i].name); } } tplToUse = (pData->tplName == NULL) ? (uchar*)strdup((char*)getDfltTpl()) : pData->tplName; CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_RQD_TPL_OPT_SQL)); CODE_STD_FINALIZERnewActInst cnfparamvalsDestruct(pvals, &actpblk); ENDnewActInst BEGINparseSelectorAct CODESTARTparseSelectorAct CODE_STD_STRING_REQUESTparseSelectorAct(1) if(!strncmp((char*) p, ":omlibdbi:", sizeof(":omlibdbi:") - 1)) { p += sizeof(":omlibdbi:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ } else { ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); } /* ok, if we reach this point, we have something for us */ CHKiRet(createInstance(&pData)); /* no create the instance based on what we currently have */ if(cs.drvrName == NULL) { LogError(0, RS_RET_NO_DRIVERNAME, "omlibdbi: no db driver name given - action can not " "be created"); ABORT_FINALIZE(RS_RET_NO_DRIVERNAME); } if((pData->drvrName = (uchar*) strdup((char*)cs.drvrName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); /* NULL values are supported because drivers have different needs. * They will err out on connect. -- rgerhards, 2008-02-15 */ if(cs.host != NULL) CHKmalloc(pData->host = (uchar*) strdup((char*)cs.host)); if(cs.usrName != NULL) CHKmalloc(pData->usrName = (uchar*) strdup((char*)cs.usrName)); if(cs.dbName != NULL) CHKmalloc(pData->dbName = (uchar*) strdup((char*)cs.dbName)); if(cs.pwd != NULL) CHKmalloc(pData->pwd = (uchar*) strdup((char*)cs.pwd)); if(cs.dbiDrvrDir != NULL) CHKmalloc(loadModConf->dbiDrvrDir = (uchar*) strdup((char*)cs.dbiDrvrDir)); iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, getDfltTpl()); CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct BEGINmodExit CODESTARTmodExit /* if we initialized libdbi, we now need to cleanup */ if(bDbiInitialized) { # ifdef HAVE_DBI_R dbi_shutdown_r(dbiInst); # else dbi_shutdown(); # endif } ENDmodExit BEGINqueryEtryPt CODESTARTqueryEtryPt CODEqueryEtryPt_STD_OMOD_QUERIES CODEqueryEtryPt_STD_OMOD8_QUERIES CODEqueryEtryPt_STD_CONF2_QUERIES CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES CODEqueryEtryPt_TXIF_OMOD_QUERIES /* we support the transactional interface! */ ENDqueryEtryPt /* Reset config variables for this module to default values. */ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) { DEFiRet; free(cs.dbiDrvrDir); cs.dbiDrvrDir = NULL; free(cs.drvrName); cs.drvrName = NULL; free(cs.host); cs.host = NULL; free(cs.usrName); cs.usrName = NULL; free(cs.pwd); cs.pwd = NULL; free(cs.dbName); cs.dbName = NULL; RETiRet; } BEGINmodInit() CODESTARTmodInit INITLegCnfVars *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr # ifndef HAVE_DBI_TXSUPP DBGPRINTF("omlibdbi: no transaction support in libdbi\n"); # endif CHKiRet(regCfSysLineHdlr2((uchar *)"actionlibdbidriverdirectory", 0, eCmdHdlrGetWord, NULL, &cs.dbiDrvrDir, STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbidriver", 0, eCmdHdlrGetWord, NULL, &cs.drvrName, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbihost", 0, eCmdHdlrGetWord, NULL, &cs.host, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbiusername", 0, eCmdHdlrGetWord, NULL, &cs.usrName, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbipassword", 0, eCmdHdlrGetWord, NULL, &cs.pwd, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionlibdbidbname", 0, eCmdHdlrGetWord, NULL, &cs.dbName, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); DBGPRINTF("omlibdbi compiled with version %s loaded, libdbi version %s\n", VERSION, dbi_version()); ENDmodInit /* vim:set ai: */