diff options
Diffstat (limited to 'runtime')
80 files changed, 29149 insertions, 0 deletions
diff --git a/runtime/Makefile.am b/runtime/Makefile.am new file mode 100644 index 00000000..81a9d5bd --- /dev/null +++ b/runtime/Makefile.am @@ -0,0 +1,137 @@ +sbin_PROGRAMS = +man_MANS = +noinst_LTLIBRARIES = librsyslog.la +pkglib_LTLIBRARIES = +#pkglib_LTLIBRARIES = librsyslog.la + +librsyslog_la_SOURCES = \ + rsyslog.c \ + rsyslog.h \ + atomic.h \ + syslogd-types.h \ + module-template.h \ + obj-types.h \ + nsd.h \ + glbl.h \ + glbl.c \ + conf.c \ + conf.h \ + msg.c \ + msg.h \ + linkedlist.c \ + linkedlist.h \ + objomsr.c \ + objomsr.h \ + stringbuf.c \ + stringbuf.h \ + datetime.c \ + datetime.h \ + srutils.c \ + srUtils.h \ + errmsg.c \ + errmsg.h \ + debug.c \ + debug.h \ + obj.c \ + obj.h \ + modules.c \ + modules.h \ + sync.c \ + sync.h \ + expr.c \ + expr.h \ + ctok.c \ + ctok.h \ + ctok_token.c \ + ctok_token.h \ + stream.c \ + stream.h \ + var.c \ + var.h \ + wtp.c \ + wtp.h \ + wti.c \ + wti.h \ + sysvar.c \ + sysvar.h \ + vm.c \ + vm.h \ + vmstk.c \ + vmstk.h \ + vmprg.c \ + vmprg.h \ + vmop.c \ + vmop.h \ + queue.c \ + queue.h \ + cfsysline.c \ + cfsysline.h \ + \ + \ + ../action.h \ + ../action.c \ + ../threads.c \ + ../threads.h \ + \ + ../parse.c \ + ../parse.h \ + \ + ../outchannel.c \ + ../outchannel.h \ + ../template.c \ + ../template.h +# the files with ../ we need to work on - so that they either become part of the +# runtime or will no longer be needed. -- rgerhards, 2008-06-13 + +librsyslog_la_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" -I$(top_srcdir) $(pthreads_cflags) +#librsyslog_la_LDFLAGS = -module -avoid-version +librsyslog_la_LIBADD = $(dl_libs) $(rt_libs) + +# +# regular expression support +# +if ENABLE_REGEXP +pkglib_LTLIBRARIES += lmregexp.la +lmregexp_la_SOURCES = regexp.c regexp.h +lmregexp_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmregexp_la_LDFLAGS = -module -avoid-version +lmregexp_la_LIBADD = +endif + +if ENABLE_INET +pkglib_LTLIBRARIES += lmnet.la lmnetstrms.la +# +# network support +# +lmnet_la_SOURCES = net.c net.h +lmnet_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmnet_la_LDFLAGS = -module -avoid-version +lmnet_la_LIBADD = + +# network stream master class and stream factory +lmnetstrms_la_SOURCES = netstrms.c netstrms.h netstrm.c netstrm.h nssel.c nssel.h +lmnetstrms_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmnetstrms_la_LDFLAGS = -module -avoid-version +lmnetstrms_la_LIBADD = + +# netstream drivers + +# plain tcp driver - main driver +pkglib_LTLIBRARIES += lmnsd_ptcp.la +lmnsd_ptcp_la_SOURCES = nsd_ptcp.c nsd_ptcp.h nsdsel_ptcp.c nsdsel_ptcp.h +lmnsd_ptcp_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmnsd_ptcp_la_LDFLAGS = -module -avoid-version +lmnsd_ptcp_la_LIBADD = +endif # if ENABLE_INET + +# +# GnuTLS netstream driver +# +if ENABLE_GNUTLS +pkglib_LTLIBRARIES += lmnsd_gtls.la +lmnsd_gtls_la_SOURCES = nsd_gtls.c nsd_gtls.h nsdsel_gtls.c nsdsel_gtls.h +lmnsd_gtls_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) $(gnutls_cflags) +lmnsd_gtls_la_LDFLAGS = -module -avoid-version +lmnsd_gtls_la_LIBADD = $(gnutls_libs) +endif + diff --git a/runtime/atomic.h b/runtime/atomic.h new file mode 100644 index 00000000..430ae7f0 --- /dev/null +++ b/runtime/atomic.h @@ -0,0 +1,51 @@ +/* This header supplies atomic operations. So far, we rely on GCC's + * atomic builtins. I have no idea if we can check them via autotools, + * but I am making the necessary provisioning to live without them if + * they are not available. Please note that you should only use the macros + * here if you think you can actually live WITHOUT an explicit atomic operation, + * because in the non-presence of them, we simply do it without atomicitiy. + * Which, for word-aligned data types, usually (but only usually!) should work. + * + * We are using the functions described in + * http:/gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html + * + * THESE MACROS MUST ONLY BE USED WITH WORD-SIZED DATA TYPES! + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" /* autotools! */ + +#ifndef INCLUDED_ATOMIC_H +#define INCLUDED_ATOMIC_H + +/* set the following to 1 if we have atomic operations (and #undef it otherwise) */ +/* #define DO_HAVE_ATOMICS 1 */ +/* for this release, we disable atomic calls because there seem to be some + * portability problems and we can not fix that without destabilizing the build. + * They simply came in too late. -- rgerhards, 2008-04-02 + */ +/* make sure they are not used! +#define ATOMIC_INC(data) ((void) __sync_fetch_and_add(&data, 1)) +#define ATOMIC_DEC_AND_FETCH(data) __sync_sub_and_fetch(&data, 1) +*/ +#define ATOMIC_INC(data) (++(data)) + +#endif /* #ifndef INCLUDED_ATOMIC_H */ diff --git a/runtime/cfsysline.c b/runtime/cfsysline.c new file mode 100644 index 00000000..c4490b48 --- /dev/null +++ b/runtime/cfsysline.c @@ -0,0 +1,996 @@ +/* cfsysline.c + * Implementation of the configuration system line object. + * + * File begun on 2007-07-30 by RGerhards + * + * Copyright (C) 2007, 2008 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> + +#include "cfsysline.h" +#include "obj.h" +#include "errmsg.h" +#include "srUtils.h" + + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(errmsg) + +linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */ + +/* --------------- START functions for handling canned syntaxes --------------- */ + + +/* parse a character from the config line + * added 2007-07-17 by rgerhards + * TODO: enhance this function to handle different classes of characters + * HINT: check if char is ' and, if so, use 'c' where c may also be things + * like \t etc. + */ +static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* if we are not at a '\0', we have our new char - no validity checks here... */ + if(**pp == '\0') { + errmsg.LogError(0, RS_RET_NOT_FOUND, "No character available"); + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((uchar*)pVal) = **pp; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, **pp)); + } + ++(*pp); /* eat processed char */ + } + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. This is more or less + * a shell to call the custom handler. + * rgerhards, 2007-07-31 + */ +static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar**, void*), void *pVal) +{ + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(pSetHdlr(pp, pVal)); + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. This functions just parses + * the number and does NOT call any handlers or set any values. It is just + * for INTERNAL USE by other parse functions! + * rgerhards, 2008-01-08 + */ +static rsRetVal parseIntVal(uchar **pp, int64 *pVal) +{ + DEFiRet; + uchar *p; + int64 i; + int bWasNegative; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pVal != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + p = *pp; + + if(*p == '-') { + bWasNegative = 1; + ++p; /* eat it */ + } else { + bWasNegative = 0; + } + + if(!isdigit((int) *p)) { + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_INT, "invalid number"); + ABORT_FINALIZE(RS_RET_INVALID_INT); + } + + /* pull value */ + for(i = 0 ; *p && (isdigit((int) *p) || *p == '.' || *p == ',') ; ++p) { + if(isdigit((int) *p)) { + i = i * 10 + *p - '0'; + } + } + + if(bWasNegative) + i *= -1; + + *pVal = i; + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse a number from the configuration line. + * rgerhards, 2007-07-31 + */ +static rsRetVal doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + uchar *p; + DEFiRet; + int64 i; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(parseIntVal(pp, &i)); + p = *pp; + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = (int) i; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, (int) i)); + } + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse a size from the configuration line. This is basically an integer + * syntax, but modifiers may be added after the integer (e.g. 1k to mean + * 1024). The size must immediately follow the number. Note that the + * param value must be int64! + * rgerhards, 2008-01-09 + */ +static rsRetVal doGetSize(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + DEFiRet; + int64 i; + + assert(pp != NULL); + assert(*pp != NULL); + + CHKiRet(parseIntVal(pp, &i)); + + /* we now check if the next character is one of our known modifiers. + * If so, we accept it as such. If not, we leave it alone. tera and + * above does not make any sense as that is above a 32-bit int value. + */ + switch(**pp) { + /* traditional binary-based definitions */ + case 'k': i *= 1024; ++(*pp); break; + case 'm': i *= 1024 * 1024; ++(*pp); break; + case 'g': i *= 1024 * 1024 * 1024; ++(*pp); break; + case 't': i *= (int64) 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* tera */ + case 'p': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* peta */ + case 'e': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* exa */ + /* and now the "new" 1000-based definitions */ + case 'K': i *= 1000; ++(*pp); break; + case 'M': i *= 1000000; ++(*pp); break; + case 'G': i *= 1000000000; ++(*pp); break; + case 'T': i *= 1000000000000; ++(*pp); break; /* tera */ + case 'P': i *= 1000000000000000; ++(*pp); break; /* peta */ + case 'E': i *= 1000000000000000000; ++(*pp); break; /* exa */ + } + + /* done */ + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int64*)pVal) = i; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, i)); + } + +finalize_it: + RETiRet; +} + + +/* Parse and interpet a $FileCreateMode and $umask line. This function + * pulls the creation mode and, if successful, stores it + * into the global variable so that the rest of rsyslogd + * opens files with that mode. Any previous value will be + * overwritten. + * HINT: if we store the creation mode in selector_t, we + * can even specify multiple modes simply be virtue of + * being placed in the right section of rsyslog.conf + * rgerhards, 2007-07-4 (happy independence day to my US friends!) + * Parameter **pp has a pointer to the current config line. + * On exit, it will be updated to the processed position. + */ +static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + uchar *p; + DEFiRet; + uchar errMsg[128]; /* for dynamic error messages */ + int iVal; + + assert(pp != NULL); + assert(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + p = *pp; + + /* for now, we parse and accept only octal numbers + * Sequence of tests is important, we are using boolean shortcuts + * to avoid addressing invalid memory! + */ + if(!( (*p == '0') + && (*(p+1) && *(p+1) >= '0' && *(p+1) <= '7') + && (*(p+2) && *(p+2) >= '0' && *(p+2) <= '7') + && (*(p+3) && *(p+3) >= '0' && *(p+3) <= '7') ) ) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "value must be octal (e.g 0644)."); + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_VALUE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_INVALID_VALUE); + } + + /* we reach this code only if the octal number is ok - so we can now + * compute the value. + */ + iVal = (*(p+1)-'0') * 64 + (*(p+2)-'0') * 8 + (*(p+3)-'0'); + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iVal; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iVal)); + } + + p += 4; /* eat the octal number */ + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and interpret an on/off inside a config file line. This is most + * often used for boolean options, but of course it may also be used + * for other things. The passed-in pointer is updated to point to + * the first unparsed character on exit. Function emits error messages + * if the value is neither on or off. It returns 0 if the option is off, + * 1 if it is on and another value if there was an error. + * rgerhards, 2007-07-15 + */ +static int doParseOnOffOption(uchar **pp) +{ + uchar *pOptStart; + uchar szOpt[32]; + + assert(pp != NULL); + assert(*pp != NULL); + + pOptStart = *pp; + skipWhiteSpace(pp); /* skip over any whitespace */ + + if(getSubString(pp, (char*) szOpt, sizeof(szOpt) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, NO_ERRCODE, "Invalid $-configline - could not extract on/off option"); + return -1; + } + + if(!strcmp((char*)szOpt, "on")) { + return 1; + } else if(!strcmp((char*)szOpt, "off")) { + return 0; + } else { + errmsg.LogError(0, NO_ERRCODE, "Option value must be on or off, but is '%s'", (char*)pOptStart); + return -1; + } +} + + +/* extract a groupname and return its gid. + * rgerhards, 2007-07-17 + */ +static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + struct group *pgBuf; + struct group gBuf; + DEFiRet; + uchar szName[256]; + char stringBuf[2048]; /* I hope this is large enough... */ + + assert(pp != NULL); + assert(*pp != NULL); + + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract group name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + getgrnam_r((char*)szName, &gBuf, stringBuf, sizeof(stringBuf), &pgBuf); + + if(pgBuf == NULL) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found or error", (char*)szName); + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((gid_t*)pVal) = pgBuf->gr_gid; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, pgBuf->gr_gid)); + } + dbgprintf("gid %d obtained for group '%s'\n", pgBuf->gr_gid, szName); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + RETiRet; +} + + +/* extract a username and return its uid. + * rgerhards, 2007-07-17 + */ +static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal) +{ + struct passwd *ppwBuf; + struct passwd pwBuf; + DEFiRet; + uchar szName[256]; + char stringBuf[2048]; /* I hope this is large enough... */ + + assert(pp != NULL); + assert(*pp != NULL); + + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract user name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + getpwnam_r((char*)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf); + + if(ppwBuf == NULL) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for user '%s' could not be found or error", (char*)szName); + iRet = RS_RET_NOT_FOUND; + } else { + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((uid_t*)pVal) = ppwBuf->pw_uid; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, ppwBuf->pw_uid)); + } + dbgprintf("uid %d obtained for user '%s'\n", ppwBuf->pw_uid, szName); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + RETiRet; +} + + +/* Parse and process an binary cofig option. pVal must be + * a pointer to an integer which is to receive the option + * value. + * rgerhards, 2007-07-15 + */ +static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + int iOption; + DEFiRet; + + assert(pp != NULL); + assert(*pp != NULL); + + if((iOption = doParseOnOffOption(pp)) == -1) + return RS_RET_ERR; /* nothing left to do */ + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iOption; + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iOption)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + RETiRet; +} + + +/* parse a whitespace-delimited word from the provided string. This is a + * helper function for a number of syntaxes. The parsed value is returned + * in ppStrB (which must be provided by caller). + * rgerhards, 2008-02-14 + */ +static rsRetVal +getWord(uchar **pp, cstr_t **ppStrB) +{ + DEFiRet; + uchar *p; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + ASSERT(ppStrB != NULL); + + CHKiRet(rsCStrConstruct(ppStrB)); + + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* parse out the word */ + p = *pp; + + while(*p && !isspace((int) *p)) { + CHKiRet(rsCStrAppendChar(*ppStrB, *p++)); + } + CHKiRet(rsCStrFinish(*ppStrB)); + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and a word config line option. A word is a consequtive + * sequence of non-whitespace characters. pVal must be + * a pointer to a string which is to receive the option + * value. The returned string must be freed by the caller. + * rgerhards, 2007-09-07 + * To facilitate multiple instances of the same command line + * directive, doGetWord() now checks if pVal is already a + * non-NULL pointer. If so, we assume it was created by a previous + * incarnation and is automatically freed. This happens only when + * no custom handler is defined. If it is, the customer handler + * must do the cleanup. I have checked and this was al also memory + * leak with some code. Obviously, not a large one. -- rgerhards, 2007-12-20 + * Just to clarify: if pVal is parsed to a custom handler, this handler + * is responsible for freeing pVal. -- rgerhards, 2008-03-20 + */ +static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void*, uchar*), void *pVal) +{ + DEFiRet; + cstr_t *pStrB; + uchar *pNewVal; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + CHKiRet(getWord(pp, &pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &pNewVal, 0)); + pStrB = NULL; + + /* we got the word, now set it */ + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + if(*((uchar**)pVal) != NULL) + free(*((uchar**)pVal)); /* free previous entry */ + *((uchar**)pVal) = pNewVal; /* set new one */ + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, pNewVal)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + } + + RETiRet; +} + + +/* parse a syslog name from the string. This is the generic code that is + * called by the facility/severity functions. Note that we do not check the + * validity of numerical values, something that should probably change over + * time (TODO). -- rgerhards, 2008-02-14 + */ +static rsRetVal +doSyslogName(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal, syslogName_t *pNameTable) +{ + DEFiRet; + cstr_t *pStrB; + int iNewVal; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + CHKiRet(getWord(pp, &pStrB)); /* get word */ + iNewVal = decodeSyslogName(rsCStrGetSzStr(pStrB), pNameTable); + + if(pSetHdlr == NULL) { + /* we should set value directly to var */ + *((int*)pVal) = iNewVal; /* set new one */ + } else { + /* we set value via a set function */ + CHKiRet(pSetHdlr(pVal, iNewVal)); + } + + skipWhiteSpace(pp); /* skip over any whitespace */ + +finalize_it: + if(pStrB != NULL) + rsCStrDestruct(&pStrB); + + RETiRet; +} + + +/* Implements the facility syntax. + * rgerhards, 2008-02-14 + */ +static rsRetVal +doFacility(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + DEFiRet; + iRet = doSyslogName(pp, pSetHdlr, pVal, syslogFacNames); + RETiRet; +} + + +/* Implements the severity syntax. + * rgerhards, 2008-02-14 + */ +static rsRetVal +doSeverity(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal) +{ + DEFiRet; + iRet = doSyslogName(pp, pSetHdlr, pVal, syslogPriNames); + RETiRet; +} + + +/* --------------- END functions for handling canned syntaxes --------------- */ + +/* destructor for cslCmdHdlr + * pThis is actually a cslCmdHdlr_t, but we do not cast it as all we currently + * need to do is free it. + */ +static rsRetVal cslchDestruct(void *pThis) +{ + ASSERT(pThis != NULL); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor for cslCmdHdlr + */ +static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis) +{ + cslCmdHdlr_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + if((pThis = calloc(1, sizeof(cslCmdHdlr_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + *ppThis = pThis; + RETiRet; +} + +/* destructor for linked list keys. As we do not use any dynamic memory, + * we simply return. However, this entry point must be defined for the + * linkedList class to make sure we have not forgotten a destructor. + * rgerhards, 2007-11-21 + */ +static rsRetVal cslchKeyDestruct(void __attribute__((unused)) *pData) +{ + return RS_RET_OK; +} + + +/* Key compare operation for linked list class. This compares two + * owner cookies (void *). + * rgerhards, 2007-11-21 + */ +static int cslchKeyCompare(void *pKey1, void *pKey2) +{ + if(pKey1 == pKey2) + return 0; + else + if(pKey1 < pKey2) + return -1; + else + return 1; +} + + +/* set data members for this object + */ +rsRetVal cslchSetEntry(cslCmdHdlr_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData) +{ + assert(pThis != NULL); + assert(eType != eCmdHdlrInvalid); + + pThis->eType = eType; + pThis->cslCmdHdlr = pHdlr; + pThis->pData = pData; + + return RS_RET_OK; +} + + +/* call the specified handler + */ +static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine) +{ + DEFiRet; + rsRetVal (*pHdlr)() = NULL; + assert(pThis != NULL); + assert(ppConfLine != NULL); + + switch(pThis->eType) { + case eCmdHdlrCustomHandler: + pHdlr = doCustomHdlr; + break; + case eCmdHdlrUID: + pHdlr = doGetUID; + break; + case eCmdHdlrGID: + pHdlr = doGetGID; + break; + case eCmdHdlrBinary: + pHdlr = doBinaryOptionLine; + break; + case eCmdHdlrFileCreateMode: + pHdlr = doFileCreateMode; + break; + case eCmdHdlrInt: + pHdlr = doGetInt; + break; + case eCmdHdlrSize: + pHdlr = doGetSize; + break; + case eCmdHdlrGetChar: + pHdlr = doGetChar; + break; + case eCmdHdlrFacility: + pHdlr = doFacility; + break; + case eCmdHdlrSeverity: + pHdlr = doSeverity; + break; + case eCmdHdlrGetWord: + pHdlr = doGetWord; + break; + default: + iRet = RS_RET_NOT_IMPLEMENTED; + goto finalize_it; + } + + /* we got a pointer to the handler, so let's call it */ + assert(pHdlr != NULL); + CHKiRet(pHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData)); + +finalize_it: + RETiRet; +} + + +/* ---------------------------------------------------------------------- * + * now come the handlers for cslCmd_t + * ---------------------------------------------------------------------- */ + +/* destructor for a cslCmd list key (a string as of now) + */ +static rsRetVal cslcKeyDestruct(void *pData) +{ + free(pData); /* we do not need to cast as all we do is free it anyway... */ + return RS_RET_OK; +} + +/* destructor for cslCmd + */ +static rsRetVal cslcDestruct(void *pData) +{ + cslCmd_t *pThis = (cslCmd_t*) pData; + + assert(pThis != NULL); + + llDestroy(&pThis->llCmdHdlrs); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor for cslCmd + */ +static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted) +{ + cslCmd_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + if((pThis = calloc(1, sizeof(cslCmd_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->bChainingPermitted = bChainingPermitted; + + CHKiRet(llInit(&pThis->llCmdHdlrs, cslchDestruct, cslchKeyDestruct, cslchKeyCompare)); + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* add a handler entry to a known command + */ +static rsRetVal cslcAddHdlr(cslCmd_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie) +{ + DEFiRet; + cslCmdHdlr_t *pCmdHdlr = NULL; + + assert(pThis != NULL); + + CHKiRet(cslchConstruct(&pCmdHdlr)); + CHKiRet(cslchSetEntry(pCmdHdlr, eType, pHdlr, pData)); + CHKiRet(llAppend(&pThis->llCmdHdlrs, pOwnerCookie, pCmdHdlr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pHdlr != NULL) + cslchDestruct(pCmdHdlr); + } + + RETiRet; +} + + +/* function that registers cfsysline handlers. + * The supplied pCmdName is copied and a new buffer is allocated. This + * buffer is automatically destroyed when the element is freed, the + * caller does not need to take care of that. The caller must, however, + * free pCmdName if he allocated it dynamically! -- rgerhards, 2007-08-09 + */ +rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, + void *pOwnerCookie) +{ + DEFiRet; + cslCmd_t *pThis; + uchar *pMyCmdName; + + iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pThis); + if(iRet == RS_RET_NOT_FOUND) { + /* new command */ + CHKiRet(cslcConstruct(&pThis, bChainingPermitted)); + CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie)) { + cslcDestruct(pThis); + goto finalize_it; + } + /* important: add to list, AFTER everything else is OK. Else + * we mess up things in the error case. + */ + if((pMyCmdName = (uchar*) strdup((char*)pCmdName)) == NULL) { + cslcDestruct(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + CHKiRet_Hdlr(llAppend(&llCmdList, pMyCmdName, (void*) pThis)) { + cslcDestruct(pThis); + goto finalize_it; + } + } else { + /* command already exists, are we allowed to chain? */ + if(pThis->bChainingPermitted == 0 || bChainingPermitted == 0) { + ABORT_FINALIZE(RS_RET_CHAIN_NOT_PERMITTED); + } + CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie)) { + cslcDestruct(pThis); + goto finalize_it; + } + } + +finalize_it: + RETiRet; +} + + +rsRetVal unregCfSysLineHdlrs(void) +{ + return llDestroy(&llCmdList); +} + + +/* helper function for unregCfSysLineHdlrs4Owner(). This is used to see if there is + * a handler of this owner inside the element and, if so, remove it. Please note that + * it keeps track of a pointer to the last linked list entry, as this is needed to + * remove an entry from the list. + * rgerhards, 2007-11-21 + */ +DEFFUNC_llExecFunc(unregHdlrsHeadExec) +{ + DEFiRet; + cslCmd_t *pListHdr = (cslCmd_t*) pData; + int iNumElts; + + /* first find element */ + iRet = llFindAndDelete(&(pListHdr->llCmdHdlrs), pParam); + + /* now go back and check how many elements are left */ + CHKiRet(llGetNumElts(&(pListHdr->llCmdHdlrs), &iNumElts)); + + if(iNumElts == 0) { + /* nothing left in header, so request to delete it */ + iRet = RS_RET_OK_DELETE_LISTENTRY; + } + +finalize_it: + RETiRet; +} +/* unregister and destroy cfSysLineHandlers for a specific owner. This method is + * most importantly used before unloading a loadable module providing some handlers. + * The full list of handlers is searched. If the to-be removed handler was the only + * handler for a directive name, the directive header, too, is deleted. + * rgerhards, 2007-11-21 + */ +rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie) +{ + DEFiRet; + /* we need to walk through all directive names, as the linked list + * class does not provide a way to just search the lower-level handlers. + */ + iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie); + + RETiRet; +} + + +/* process a cfsysline command (based on handler structure) + * param "p" is a pointer to the command line after the command. Should be + * updated. + */ +rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p) +{ + DEFiRet; + rsRetVal iRetLL; /* for linked list handling */ + cslCmd_t *pCmd; + cslCmdHdlr_t *pCmdHdlr; + linkedListCookie_t llCookieCmdHdlr; + uchar *pHdlrP; /* the handler's private p (else we could only call one handler) */ + int bWasOnceOK; /* was the result of an handler at least once RS_RET_OK? */ + uchar *pOKp = NULL; /* returned conf line pointer when it was OK */ + + iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd); + + if(iRet == RS_RET_NOT_FOUND) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "invalid or yet-unknown config file command - have you forgotten to load a module?"); + } + + if(iRet != RS_RET_OK) + goto finalize_it; + + llCookieCmdHdlr = NULL; + bWasOnceOK = 0; + while((iRetLL = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) { + /* for the time being, we ignore errors during handlers. The + * reason is that handlers are independent. An error in one + * handler does not necessarily mean that another one will + * fail, too. Later, we might add a config variable to control + * this behaviour (but I am not sure if that is rally + * necessary). -- rgerhards, 2007-07-31 + */ + pHdlrP = *p; + if((iRet = cslchCallHdlr(pCmdHdlr, &pHdlrP)) == RS_RET_OK) { + bWasOnceOK = 1; + pOKp = pHdlrP; + } + } + + if(bWasOnceOK == 1) { + *p = pOKp; + iRet = RS_RET_OK; + } + + if(iRetLL != RS_RET_END_OF_LINKEDLIST) + iRet = iRetLL; + +finalize_it: + RETiRet; +} + + +/* debug print the command handler structure + */ +void dbgPrintCfSysLineHandlers(void) +{ + DEFiRet; + + cslCmd_t *pCmd; + cslCmdHdlr_t *pCmdHdlr; + linkedListCookie_t llCookieCmd; + linkedListCookie_t llCookieCmdHdlr; + uchar *pKey; + + dbgprintf("Sytem Line Configuration Commands:\n"); + llCookieCmd = NULL; + while((iRet = llGetNextElt(&llCmdList, &llCookieCmd, (void*)&pCmd)) == RS_RET_OK) { + llGetKey(llCookieCmd, (void*) &pKey); /* TODO: using the cookie is NOT clean! */ + dbgprintf("\tCommand '%s':\n", pKey); + llCookieCmdHdlr = NULL; + while((iRet = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) { + dbgprintf("\t\ttype : %d\n", pCmdHdlr->eType); + dbgprintf("\t\tpData: 0x%lx\n", (unsigned long) pCmdHdlr->pData); + dbgprintf("\t\tHdlr : 0x%lx\n", (unsigned long) pCmdHdlr->cslCmdHdlr); + dbgprintf("\t\tOwner: 0x%lx\n", (unsigned long) llCookieCmdHdlr->pKey); + dbgprintf("\n"); + } + } + dbgprintf("\n"); + ENDfunc +} + + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal cfsyslineInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp)); + +finalize_it: + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/cfsysline.h b/runtime/cfsysline.h new file mode 100644 index 00000000..07ab5fcd --- /dev/null +++ b/runtime/cfsysline.h @@ -0,0 +1,76 @@ +/* Definition of the cfsysline (config file system line) object. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef CFSYSLINE_H_INCLUDED +#define CFSYSLINE_H_INCLUDED + +#include "linkedlist.h" + +/* types of configuration handlers + */ +typedef enum cslCmdHdlrType { + eCmdHdlrInvalid = 0, /* invalid handler type - indicates a coding error */ + eCmdHdlrCustomHandler, /* custom handler, just call handler function */ + eCmdHdlrUID, + eCmdHdlrGID, + eCmdHdlrBinary, + eCmdHdlrFileCreateMode, + eCmdHdlrInt, + eCmdHdlrSize, + eCmdHdlrGetChar, + eCmdHdlrFacility, + eCmdHdlrSeverity, + eCmdHdlrGetWord +} ecslCmdHdrlType; + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct cslCmdHdlr_s { /* config file sysline parse entry */ + ecslCmdHdrlType eType; /* which type of handler is this? */ + rsRetVal (*cslCmdHdlr)(); /* function pointer to use with handler (params depending on eType) */ + void *pData; /* user-supplied data pointer */ +}; +typedef struct cslCmdHdlr_s cslCmdHdlr_t; + + +/* this is the list of known configuration commands with pointers to + * their handlers. + * The short name is cslc (Configfile SysLine Command) + */ +struct cslCmd_s { /* config file sysline parse entry */ + int bChainingPermitted; /* may multiple handlers be chained for this command? */ + linkedList_t llCmdHdlrs; /* linked list of command handlers */ +}; +typedef struct cslCmd_s cslCmd_t; + +/* prototypes */ +rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie); +rsRetVal unregCfSysLineHdlrs(void); +rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie); +rsRetVal processCfSysLineCommand(uchar *pCmd, uchar **p); +rsRetVal cfsyslineInit(void); +void dbgPrintCfSysLineHandlers(void); + +#endif /* #ifndef CFSYSLINE_H_INCLUDED */ diff --git a/runtime/conf.c b/runtime/conf.c new file mode 100644 index 00000000..71b2b2da --- /dev/null +++ b/runtime/conf.c @@ -0,0 +1,1217 @@ +/* The config file handler (not yet a real object) + * + * This file is based on an excerpt from syslogd.c, which dates back + * much later. I began the file on 2008-02-19 as part of the modularization + * effort. Over time, a clean abstration will become even more important + * because the config file handler will by dynamically be loaded and be + * kept in memory only as long as the config file is actually being + * processed. Thereafter, it shall be unloaded. -- rgerhards + * + * TODO: the license MUST be changed to LGPL. However, we can not + * currently do that, because we use some sysklogd code to crunch + * the selector lines (e.g. *.info). That code is scheduled for removal + * as part of RainerScript. After this is done, we can change licenses. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * 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. + * + * 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#include <stddef.h> +#include <string.h> +#include <dlfcn.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> +#include <assert.h> +#include <dirent.h> +#include <glob.h> +#include <sys/types.h> +#ifdef HAVE_LIBGEN_H +# include <libgen.h> +#endif + +#include "rsyslog.h" +#include "../tools/syslogd.h" /* TODO: this must be removed! */ +#include "dirty.h" +#include "parse.h" +#include "action.h" +#include "template.h" +#include "cfsysline.h" +#include "modules.h" +#include "outchannel.h" +#include "stringbuf.h" +#include "conf.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "errmsg.h" +#include "net.h" +#include "expr.h" +#include "ctok.h" +#include "ctok_token.h" + + +/* forward definitions */ +static rsRetVal cfline(uchar *line, selector_t **pfCurr); +static rsRetVal processConfFile(uchar *pConfFile); + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(expr) +DEFobjCurrIf(ctok) +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) + +/* The following global variables are used for building + * tag and host selector lines during startup and config reload. + * This is stored as a global variable pool because of its ease. It is + * also fairly compatible with multi-threading as the stratup code must + * be run in a single thread anyways. So there can be no race conditions. These + * variables are no longer used once the configuration has been loaded (except, + * of course, during a reload). rgerhards 2005-10-18 + */ +EHostnameCmpMode eDfltHostnameCmpMode; +cstr_t *pDfltHostnameCmp; +cstr_t *pDfltProgNameCmp; + + +/* process a directory and include all of its files into + * the current config file. There is no specific order of inclusion, + * files are included in the order they are read from the directory. + * The caller must have make sure that the provided parameter is + * indeed a directory. + * rgerhards, 2007-08-01 + */ +static rsRetVal doIncludeDirectory(uchar *pDirName) +{ + DEFiRet; + int iEntriesDone = 0; + DIR *pDir; + union { + struct dirent d; + char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; + } u; + struct dirent *res; + size_t iDirNameLen; + size_t iFileNameLen; + uchar szFullFileName[MAXFNAME]; + + ASSERT(pDirName != NULL); + + if((pDir = opendir((char*) pDirName)) == NULL) { + errmsg.LogError(errno, RS_RET_FOPEN_FAILURE, "error opening include directory"); + ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); + } + + /* prepare file name buffer */ + iDirNameLen = strlen((char*) pDirName); + memcpy(szFullFileName, pDirName, iDirNameLen); + + /* now read the directory */ + iEntriesDone = 0; + while(readdir_r(pDir, &u.d, &res) == 0) { + if(res == NULL) + break; /* this also indicates end of directory */ +# ifdef DT_REG + /* TODO: find an alternate way to checking for special files if this is + * not defined. This is currently a known problem on HP UX, but the work- + * around is simple: do not create special files in that directory. So + * fixing this is actually not the most important thing on earth... + * rgerhards, 2008-03-04 + */ + if(res->d_type != DT_REG) + continue; /* we are not interested in special files */ +# endif + if(res->d_name[0] == '.') + continue; /* these files we are also not interested in */ + ++iEntriesDone; + /* construct filename */ + iFileNameLen = strlen(res->d_name); + if (iFileNameLen > NAME_MAX) + iFileNameLen = NAME_MAX; + memcpy(szFullFileName + iDirNameLen, res->d_name, iFileNameLen); + *(szFullFileName + iDirNameLen + iFileNameLen) = '\0'; + dbgprintf("including file '%s'\n", szFullFileName); + processConfFile(szFullFileName); + /* we deliberately ignore the iRet of processConfFile() - this is because + * failure to process one file does not mean all files will fail. By ignoring, + * we retry with the next file, which is the best thing we can do. -- rgerhards, 2007-08-01 + */ + } + + if(iEntriesDone == 0) { + /* I just make it a debug output, because I can think of a lot of cases where it + * makes sense not to have any files. E.g. a system maintainer may place a $Include + * into the config file just in case, when additional modules be installed. When none + * are installed, the directory will be empty, which is fine. -- rgerhards 2007-08-01 + */ + dbgprintf("warning: the include directory contained no files - this may be ok.\n"); + } + +finalize_it: + if(pDir != NULL) + closedir(pDir); + + RETiRet; +} + + +/* process a $include config line. That type of line requires + * inclusion of another file. + * rgerhards, 2007-08-01 + */ +rsRetVal +doIncludeLine(uchar **pp, __attribute__((unused)) void* pVal) +{ + DEFiRet; + char pattern[MAXFNAME]; + uchar *cfgFile; + glob_t cfgFiles; + size_t i = 0; + struct stat fileInfo; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + if(getSubString(pp, (char*) pattern, sizeof(pattern) / sizeof(char), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract group name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* Use GLOB_MARK to append a trailing slash for directories. + * Required by doIncludeDirectory(). + */ + glob(pattern, GLOB_MARK, NULL, &cfgFiles); + + for(i = 0; i < cfgFiles.gl_pathc; i++) { + cfgFile = (uchar*) cfgFiles.gl_pathv[i]; + + if(stat((char*) cfgFile, &fileInfo) != 0) + continue; /* continue with the next file if we can't stat() the file */ + + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + dbgprintf("requested to include config file '%s'\n", cfgFile); + iRet = processConfFile(cfgFile); + } else if(S_ISDIR(fileInfo.st_mode)) { /* config directory */ + dbgprintf("requested to include directory '%s'\n", cfgFile); + iRet = doIncludeDirectory(cfgFile); + } else { /* TODO: shall we handle symlinks or not? */ + dbgprintf("warning: unable to process IncludeConfig directive '%s'\n", cfgFile); + } + } + + globfree(&cfgFiles); + +finalize_it: + RETiRet; +} + + +/* process a $ModLoad config line. + */ +rsRetVal +doModLoad(uchar **pp, __attribute__((unused)) void* pVal) +{ + DEFiRet; + uchar szName[512]; + uchar *pModName; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + + skipWhiteSpace(pp); /* skip over any whitespace */ + if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract module name"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + skipWhiteSpace(pp); /* skip over any whitespace */ + + /* this below is a quick and dirty hack to provide compatibility with the + * $ModLoad MySQL forward compatibility statement. TODO: clean this up + * For the time being, it is clean enough, it just needs to be done + * differently when we have a full design for loadable plug-ins. For the + * time being, we just mangle the names a bit. + * rgerhards, 2007-08-14 + */ + if(!strcmp((char*) szName, "MySQL")) + pModName = (uchar*) "ommysql.so"; + else + pModName = szName; + + CHKiRet(module.Load(pModName)); + +finalize_it: + RETiRet; +} + + +/* parse and interpret a $-config line that starts with + * a name (this is common code). It is parsed to the name + * and then the proper sub-function is called to handle + * the actual directive. + * rgerhards 2004-11-17 + * rgerhards 2005-06-21: previously only for templates, now + * generalized. + */ +rsRetVal +doNameLine(uchar **pp, void* pVal) +{ + DEFiRet; + uchar *p; + enum eDirective eDir; + char szName[128]; + + ASSERT(pp != NULL); + p = *pp; + ASSERT(p != NULL); + + eDir = (enum eDirective) pVal; /* this time, it actually is NOT a pointer! */ + + if(getSubString(&p, szName, sizeof(szName) / sizeof(char), ',') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid config line: could not extract name - line ignored"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + if(*p == ',') + ++p; /* comma was eaten */ + + /* we got the name - now we pass name & the rest of the string + * to the subfunction. It makes no sense to do further + * parsing here, as this is in close interaction with the + * respective subsystem. rgerhards 2004-11-17 + */ + + switch(eDir) { + case DIR_TEMPLATE: + tplAddLine(szName, &p); + break; + case DIR_OUTCHANNEL: + ochAddLine(szName, &p); + break; + case DIR_ALLOWEDSENDER: + net.addAllowedSenderLine(szName, &p); + break; + default:/* we do this to avoid compiler warning - not all + * enum values call this function, so an incomplete list + * is quite ok (but then we should not run into this code, + * so at least we log a debug warning). + */ + dbgprintf("INTERNAL ERROR: doNameLine() called with invalid eDir %d.\n", + eDir); + break; + } + + *pp = p; + +finalize_it: + RETiRet; +} + + +/* Parse and interpret a system-directive in the config line + * A system directive is one that starts with a "$" sign. It offers + * extended configuration parameters. + * 2004-11-17 rgerhards + */ +rsRetVal +cfsysline(uchar *p) +{ + DEFiRet; + uchar szCmd[64]; + + ASSERT(p != NULL); + errno = 0; + if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) { + errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid $-configline - could not extract command - line ignored\n"); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* we now try and see if we can find the command in the registered + * list of cfsysline handlers. -- rgerhards, 2007-07-31 + */ + CHKiRet(processCfSysLineCommand(szCmd, &p)); + + /* now check if we have some extra characters left on the line - that + * should not be the case. Whitespace is OK, but everything else should + * trigger a warning (that may be an indication of undesired behaviour). + * An exception, of course, are comments (starting with '#'). + * rgerhards, 2007-07-04 + */ + skipWhiteSpace(&p); + + if(*p && *p != '#') { /* we have a non-whitespace, so let's complain */ + errmsg.LogError(0, NO_ERRCODE, + "error: extra characters in config line ignored: '%s'", p); + } + +finalize_it: + RETiRet; +} + + + + +/* process a configuration file + * started with code from init() by rgerhards on 2007-07-31 + */ +static rsRetVal +processConfFile(uchar *pConfFile) +{ + DEFiRet; + int iLnNbr = 0; + FILE *cf; + selector_t *fCurr = NULL; + uchar *p; + uchar cbuf[BUFSIZ]; + uchar *cline; + int i; + ASSERT(pConfFile != NULL); + + if((cf = fopen((char*)pConfFile, "r")) == NULL) { + ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); + } + + /* Now process the file. + */ + cline = cbuf; + while (fgets((char*)cline, sizeof(cbuf) - (cline - cbuf), cf) != NULL) { + ++iLnNbr; + /* drop LF - TODO: make it better, replace fgets(), but its clean as it is */ + if(cline[strlen((char*)cline)-1] == '\n') { + cline[strlen((char*)cline) -1] = '\0'; + } + /* check for end-of-section, comments, strip off trailing + * spaces and newline character. + */ + p = cline; + skipWhiteSpace(&p); + if (*p == '\0' || *p == '#') + continue; + + /* we now need to copy the characters to the begin of line. As this overlaps, + * we can not use strcpy(). -- rgerhards, 2008-03-20 + * TODO: review the code at whole - this is highly suspect (but will go away + * once we do the rest of RainerScript). + */ + /* was: strcpy((char*)cline, (char*)p); */ + for( i = 0 ; p[i] != '\0' ; ++i) { + cline[i] = p[i]; + } + cline[i] = '\0'; + + for (p = (uchar*) strchr((char*)cline, '\0'); isspace((int) *--p);) + /*EMPTY*/; + if (*p == '\\') { + if ((p - cbuf) > BUFSIZ - 30) { + /* Oops the buffer is full - what now? */ + cline = cbuf; + } else { + *p = 0; + cline = p; + continue; + } + } else + cline = cbuf; + *++p = '\0'; /* TODO: check this */ + + /* we now have the complete line, and are positioned at the first non-whitespace + * character. So let's process it + */ + if(cfline(cbuf, &fCurr) != RS_RET_OK) { + /* we log a message, but otherwise ignore the error. After all, the next + * line can be correct. -- rgerhards, 2007-08-02 + */ + uchar szErrLoc[MAXFNAME + 64]; + dbgprintf("config line NOT successfully processed\n"); + snprintf((char*)szErrLoc, sizeof(szErrLoc) / sizeof(uchar), + "%s, line %d", pConfFile, iLnNbr); + errmsg.LogError(0, NO_ERRCODE, "the last error occured in %s", (char*)szErrLoc); + } + } + + /* we probably have one selector left to be added - so let's do that now */ + CHKiRet(selectorAddList(fCurr)); + + /* close the configuration file */ + (void) fclose(cf); + +finalize_it: + if(iRet != RS_RET_OK) { + char errStr[1024]; + if(fCurr != NULL) + selectorDestruct(fCurr); + + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("error %d processing config file '%s'; os error (if any): %s\n", + iRet, pConfFile, errStr); + } + RETiRet; +} + + +/* Helper to cfline() and its helpers. Parses a template name + * from an "action" line. Must be called with the Line pointer + * pointing to the first character after the semicolon. + * rgerhards 2004-11-19 + * changed function to work with OMSR. -- rgerhards, 2007-07-27 + * the default template is to be used when no template is specified. + */ +rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName) +{ + uchar *p; + uchar *tplName; + cstr_t *pStrB; + DEFiRet; + + ASSERT(pp != NULL); + ASSERT(*pp != NULL); + ASSERT(pOMSR != NULL); + + p =*pp; + /* a template must follow - search it and complain, if not found */ + skipWhiteSpace(&p); + if(*p == ';') + ++p; /* eat it */ + else if(*p != '\0' && *p != '#') { + errmsg.LogError(0, RS_RET_ERR, "invalid character in selector line - ';template' expected"); + ABORT_FINALIZE(RS_RET_ERR); + } + + skipWhiteSpace(&p); /* go to begin of template name */ + + if(*p == '\0' || *p == '#') { + /* no template specified, use the default */ + /* TODO: check NULL ptr */ + tplName = (uchar*) strdup((char*)dfltTplName); + } else { + /* template specified, pick it up */ + if(rsCStrConstruct(&pStrB) != RS_RET_OK) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* now copy the string */ + while(*p && *p != '#' && !isspace((int) *p)) { + CHKiRet(rsCStrAppendChar(pStrB, *p)); + ++p; + } + CHKiRet(rsCStrFinish(pStrB)); + CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &tplName, 0)); + } + + iRet = OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts); + if(iRet != RS_RET_OK) goto finalize_it; + +finalize_it: + *pp = p; + + RETiRet; +} + +/* Helper to cfline(). Parses a file name up until the first + * comma and then looks for the template specifier. Tries + * to find that template. + * rgerhards 2004-11-18 + * parameter pFileName must point to a buffer large enough + * to hold the largest possible filename. + * rgerhards, 2007-07-25 + * updated to include OMSR pointer -- rgerhards, 2007-07-27 + * updated to include template name -- rgerhards, 2008-03-28 + */ +rsRetVal +cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl) +{ + register uchar *pName; + int i; + DEFiRet; + + ASSERT(pOMSR != NULL); + + pName = pFileName; + i = 1; /* we start at 1 so that we reseve space for the '\0'! */ + while(*p && *p != ';' && i < MAXFNAME) { + *pName++ = *p++; + ++i; + } + *pName = '\0'; + + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, pszTpl); + + RETiRet; +} + + +/* + * Helper to cfline(). This function takes the filter part of a traditional, PRI + * based line and decodes the PRIs given in the selector line. It processed the + * line up to the beginning of the action part. A pointer to that beginnig is + * passed back to the caller. + * rgerhards 2005-09-15 + */ +/* GPLv3 - stems back to sysklogd */ +static rsRetVal cflineProcessTradPRIFilter(uchar **pline, register selector_t *f) +{ + uchar *p; + register uchar *q; + register int i, i2; + uchar *bp; + int pri; + int singlpri = 0; + int ignorepri = 0; + uchar buf[MAXLINE]; + uchar xbuf[200]; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - traditional PRI filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + + f->f_filter_type = FILTER_PRI; + /* Note: file structure is pre-initialized to zero because it was + * created with calloc()! + */ + for (i = 0; i <= LOG_NFACILITIES; i++) { + f->f_filterData.f_pmask[i] = TABLE_NOPRI; + } + + /* scan through the list of selectors */ + for (p = *pline; *p && *p != '\t' && *p != ' ';) { + + /* find the end of this facility name list */ + for (q = p; *q && *q != '\t' && *q++ != '.'; ) + continue; + + /* collect priority name */ + for (bp = buf; *q && !strchr("\t ,;", *q); ) + *bp++ = *q++; + *bp = '\0'; + + /* skip cruft */ + while (strchr(",;", *q)) + q++; + + /* decode priority name */ + if ( *buf == '!' ) { + ignorepri = 1; + for (bp=buf; *(bp+1); bp++) + *bp=*(bp+1); + *bp='\0'; + } + else { + ignorepri = 0; + } + if ( *buf == '=' ) + { + singlpri = 1; + pri = decodeSyslogName(&buf[1], syslogPriNames); + } + else { + singlpri = 0; + pri = decodeSyslogName(buf, syslogPriNames); + } + + if (pri < 0) { + snprintf((char*) xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf); + errmsg.LogError(0, RS_RET_ERR, "%s", xbuf); + return RS_RET_ERR; + } + + /* scan facilities */ + while (*p && !strchr("\t .;", *p)) { + for (bp = buf; *p && !strchr("\t ,;.", *p); ) + *bp++ = *p++; + *bp = '\0'; + if (*buf == '*') { + for (i = 0; i <= LOG_NFACILITIES; i++) { + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] = TABLE_ALLPRI; + else + f->f_filterData.f_pmask[i] = TABLE_NOPRI; + } + else if ( singlpri ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] &= ~(1<<pri); + else + f->f_filterData.f_pmask[i] |= (1<<pri); + } + else + { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i] = TABLE_NOPRI; + else + f->f_filterData.f_pmask[i] = TABLE_ALLPRI; + } + else + { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i] |= (1<<i2); + } + } + } + } else { + i = decodeSyslogName(buf, syslogFacNames); + if (i < 0) { + + snprintf((char*) xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf); + errmsg.LogError(0, RS_RET_ERR, "%s", xbuf); + return RS_RET_ERR; + } + + if ( pri == INTERNAL_NOPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; + else + f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; + } else if ( singlpri ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] &= ~(1<<pri); + else + f->f_filterData.f_pmask[i >> 3] |= (1<<pri); + } else { + if ( pri == TABLE_ALLPRI ) { + if ( ignorepri ) + f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; + else + f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; + } else { + if ( ignorepri ) + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i >> 3] &= ~(1<<i2); + else + for (i2= 0; i2 <= pri; ++i2) + f->f_filterData.f_pmask[i >> 3] |= (1<<i2); + } + } + } + while (*p == ',' || *p == ' ') + p++; + } + + p = q; + } + + /* skip to action part */ + while (*p == '\t' || *p == ' ') + p++; + + *pline = p; + return RS_RET_OK; +} + + +/* Helper to cfline(). This function processes an "if" type of filter, + * what essentially means it parses an expression. As usual, + * It processes the line up to the beginning of the action part. + * A pointer to that beginnig is passed back to the caller. + * rgerhards 2008-01-19 + */ +static rsRetVal cflineProcessIfFilter(uchar **pline, register selector_t *f) +{ + DEFiRet; + ctok_t *tok; + ctok_token_t *pToken; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - general expression-based filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + +dbgprintf("calling expression parser, pp %p ('%s')\n", *pline, *pline); + f->f_filter_type = FILTER_EXPR; + + /* if we come to over here, pline starts with "if ". We just skip that part. */ + (*pline) += 3; + + /* we first need a tokenizer... */ + CHKiRet(ctok.Construct(&tok)); + CHKiRet(ctok.Setpp(tok, *pline)); + CHKiRet(ctok.ConstructFinalize(tok)); + + /* now construct our expression */ + CHKiRet(expr.Construct(&f->f_filterData.f_expr)); + CHKiRet(expr.ConstructFinalize(f->f_filterData.f_expr)); + + /* ready to go... */ + CHKiRet(expr.Parse(f->f_filterData.f_expr, tok)); + + /* we now need to parse off the "then" - and note an error if it is + * missing... + */ + CHKiRet(ctok.GetToken(tok, &pToken)); + if(pToken->tok != ctok_THEN) { + ctok_token.Destruct(&pToken); + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + } + + ctok_token.Destruct(&pToken); /* no longer needed */ + + /* we are done, so we now need to restore things */ + CHKiRet(ctok.Getpp(tok, pline)); + CHKiRet(ctok.Destruct(&tok)); + + /* we now need to skip whitespace to the action part, else we confuse + * the legacy rsyslog conf parser. -- rgerhards, 2008-02-25 + */ + while(isspace(**pline)) + ++(*pline); + +finalize_it: + if(iRet == RS_RET_SYNTAX_ERROR) { + errmsg.LogError(0, RS_RET_SYNTAX_ERROR, "syntax error in expression"); + } + + RETiRet; +} + + +/* Helper to cfline(). This function takes the filter part of a property + * based filter and decodes it. It processes the line up to the beginning + * of the action part. A pointer to that beginnig is passed back to the caller. + * rgerhards 2005-09-15 + */ +static rsRetVal cflineProcessPropFilter(uchar **pline, register selector_t *f) +{ + rsParsObj *pPars; + cstr_t *pCSCompOp; + rsRetVal iRet; + int iOffset; /* for compare operations */ + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(f != NULL); + + dbgprintf(" - property-based filter\n"); + errno = 0; /* keep strerror_r() stuff out of logerror messages */ + + f->f_filter_type = FILTER_PROP; + + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { + errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring selector", iRet); + return(iRet); + } + + /* read property */ + iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d parsing filter property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* read operation */ + iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1, 1); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d compare operation property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* we now first check if the condition is to be negated. To do so, we first + * must make sure we have at least one char in the param and then check the + * first one. + * rgerhards, 2005-09-26 + */ + if(rsCStrLen(pCSCompOp) > 0) { + if(*rsCStrGetBufBeg(pCSCompOp) == '!') { + f->f_filterData.prop.isNegated = 1; + iOffset = 1; /* ignore '!' */ + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + } else { + f->f_filterData.prop.isNegated = 0; + iOffset = 0; + } + + if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) { + f->f_filterData.prop.operation = FIOP_CONTAINS; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) { + f->f_filterData.prop.operation = FIOP_ISEQUAL; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) { + f->f_filterData.prop.operation = FIOP_STARTSWITH; + } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) { + f->f_filterData.prop.operation = FIOP_REGEX; + } else { + errmsg.LogError(0, NO_ERRCODE, "error: invalid compare operation '%s' - ignoring selector", + (char*) rsCStrGetSzStrNoNULL(pCSCompOp)); + } + rsCStrDestruct(&pCSCompOp); /* no longer needed */ + + /* read compare value */ + iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d compare value property - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* skip to action part */ + if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { + errmsg.LogError(0, iRet, "error %d skipping to action part - ignoring selector", iRet); + rsParsDestruct(pPars); + return(iRet); + } + + /* cleanup */ + *pline = *pline + rsParsGetParsePointer(pPars) + 1; + /* we are adding one for the skipped initial ":" */ + + return rsParsDestruct(pPars); +} + + +/* + * Helper to cfline(). This function interprets a BSD host selector line + * from the config file ("+/-hostname"). It stores it for further reference. + * rgerhards 2005-10-19 + */ +static rsRetVal cflineProcessHostSelector(uchar **pline) +{ + rsRetVal iRet; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(**pline == '-' || **pline == '+'); + + dbgprintf(" - host selector line\n"); + + /* check include/exclude setting */ + if(**pline == '+') { + eDfltHostnameCmpMode = HN_COMP_MATCH; + } else { /* we do not check for '-', it must be, else we wouldn't be here */ + eDfltHostnameCmpMode = HN_COMP_NOMATCH; + } + (*pline)++; /* eat + or - */ + + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dbgprintf("resetting BSD-like hostname filter\n"); + eDfltHostnameCmpMode = HN_NO_COMP; + if(pDfltHostnameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) + return(iRet); + } + } else { + dbgprintf("setting BSD-like hostname filter to '%s'\n", *pline); + if(pDfltHostnameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) + return(iRet); + } + } + return RS_RET_OK; +} + + +/* + * Helper to cfline(). This function interprets a BSD tag selector line + * from the config file ("!tagname"). It stores it for further reference. + * rgerhards 2005-10-18 + */ +static rsRetVal cflineProcessTagSelector(uchar **pline) +{ + rsRetVal iRet; + + ASSERT(pline != NULL); + ASSERT(*pline != NULL); + ASSERT(**pline == '!'); + + dbgprintf(" - programname selector line\n"); + + (*pline)++; /* eat '!' */ + + /* the below is somewhat of a quick hack, but it is efficient (this is + * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic + * this, too. As it is easy to check that condition, we do not fire up a + * parser process, just make sure we do not address beyond our space. + * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 + */ + if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { + dbgprintf("resetting programname filter\n"); + if(pDfltProgNameCmp != NULL) { + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) + return(iRet); + } + } else { + dbgprintf("setting programname filter to '%s'\n", *pline); + if(pDfltProgNameCmp == NULL) { + /* create string for parser */ + if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } else { /* string objects exists, just update... */ + if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) + return(iRet); + } + } + return RS_RET_OK; +} + + +/* read the filter part of a configuration line and store the filter + * in the supplied selector_t + * rgerhards, 2007-08-01 + */ +static rsRetVal cflineDoFilter(uchar **pp, selector_t *f) +{ + DEFiRet; + + ASSERT(pp != NULL); + ASSERT(f != NULL); + + /* check which filter we need to pull... */ + switch(**pp) { + case ':': + CHKiRet(cflineProcessPropFilter(pp, f)); + break; + case 'i': /* "if" filter? */ + if(*(*pp+1) && (*(*pp+1) == 'f') && isspace(*(*pp+2))) { + CHKiRet(cflineProcessIfFilter(pp, f)); + break; + } + /*FALLTHROUGH*/ + default: + CHKiRet(cflineProcessTradPRIFilter(pp, f)); + break; + } + + /* we now check if there are some global (BSD-style) filter conditions + * and, if so, we copy them over. rgerhards, 2005-10-18 + */ + if(pDfltProgNameCmp != NULL) { + CHKiRet(rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)); + } + + if(eDfltHostnameCmpMode != HN_NO_COMP) { + f->eHostnameCmpMode = eDfltHostnameCmpMode; + CHKiRet(rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)); + } + +finalize_it: + RETiRet; +} + + +/* process the action part of a selector line + * rgerhards, 2007-08-01 + */ +static rsRetVal cflineDoAction(uchar **p, action_t **ppAction) +{ + DEFiRet; + modInfo_t *pMod; + omodStringRequest_t *pOMSR; + action_t *pAction; + void *pModData; + + ASSERT(p != NULL); + ASSERT(ppAction != NULL); + + /* loop through all modules and see if one picks up the line */ + pMod = module.GetNxtType(NULL, eMOD_OUT); + while(pMod != NULL) { + pOMSR = NULL; + iRet = pMod->mod.om.parseSelectorAct(p, &pModData, &pOMSR); + dbgprintf("tried selector action for %s: %d\n", module.GetName(pMod), iRet); + if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) { + if((iRet = addAction(&pAction, pMod, pModData, pOMSR, (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { + /* now check if the module is compatible with select features */ + if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) + pAction->f_ReduceRepeated = bReduceRepeatMsgs; + else { + dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); + pAction->f_ReduceRepeated = 0; + } + pAction->bEnabled = 1; /* action is enabled */ + } + break; + } + else if(iRet != RS_RET_CONFLINE_UNPROCESSED) { + /* In this case, the module would have handled the config + * line, but some error occured while doing so. This error should + * already by reported by the module. We do not try any other + * modules on this line, because we found the right one. + * rgerhards, 2007-07-24 + */ + dbgprintf("error %d parsing config line\n", (int) iRet); + break; + } + pMod = module.GetNxtType(pMod, eMOD_OUT); + } + + *ppAction = pAction; + RETiRet; +} + + +/* Process a configuration file line in traditional "filter selector" format + * or one that builds upon this format. + */ +static rsRetVal cflineClassic(uchar *p, selector_t **pfCurr) +{ + DEFiRet; + action_t *pAction; + selector_t *fCurr; + + ASSERT(pfCurr != NULL); + + fCurr = *pfCurr; + + /* lines starting with '&' have no new filters and just add + * new actions to the currently processed selector. + */ + if(*p == '&') { + ++p; /* eat '&' */ + skipWhiteSpace(&p); /* on to command */ + } else { + /* we are finished with the current selector (on previous line). + * So we now need to check + * if it has any actions associated and, if so, link it to the linked + * list. If it has nothing associated with it, we can simply discard + * it. In any case, we create a fresh selector for our new filter. + * We have one special case during initialization: then, the current + * selector is NULL, which means we do not need to care about it at + * all. -- rgerhards, 2007-08-01 + */ + CHKiRet(selectorAddList(fCurr)); + CHKiRet(selectorConstruct(&fCurr)); /* create "fresh" selector */ + CHKiRet(cflineDoFilter(&p, fCurr)); /* pull filters */ + } + + CHKiRet(cflineDoAction(&p, &pAction)); + CHKiRet(llAppend(&fCurr->llActList, NULL, (void*) pAction)); + +finalize_it: + *pfCurr = fCurr; + RETiRet; +} + + +/* process a configuration line + * I re-did this functon because it was desperately time to do so + * rgerhards, 2007-08-01 + */ +static rsRetVal +cfline(uchar *line, selector_t **pfCurr) +{ + DEFiRet; + + ASSERT(line != NULL); + + dbgprintf("cfline: '%s'\n", line); + + /* check type of line and call respective processing */ + switch(*line) { + case '!': + iRet = cflineProcessTagSelector(&line); + break; + case '+': + case '-': + iRet = cflineProcessHostSelector(&line); + break; + case '$': + ++line; /* eat '$' */ + iRet = cfsysline(line); + break; + default: + iRet = cflineClassic(line, pfCurr); + break; + } + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(conf) +CODESTARTobjQueryInterface(conf) + if(pIf->ifVersion != confCURR_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->doNameLine = doNameLine; + pIf->cfsysline = cfsysline; + pIf->doModLoad = doModLoad; + pIf->doIncludeLine = doIncludeLine; + pIf->cfline = cfline; + pIf->processConfFile = processConfFile; + +finalize_it: +ENDobjQueryInterface(conf) + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(conf, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(conf) + /* release objects we no longer need */ + objRelease(expr, CORE_COMPONENT); + objRelease(ctok, CORE_COMPONENT); + objRelease(ctok_token, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); +ENDObjClassExit(conf) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +BEGINAbstractObjClassInit(conf, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(expr, CORE_COMPONENT)); + CHKiRet(objUse(ctok, CORE_COMPONENT)); + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); /* TODO: make this dependcy go away! */ +ENDObjClassInit(conf) + +/* vi:set ai: + */ diff --git a/runtime/conf.h b/runtime/conf.h new file mode 100644 index 00000000..31ca27b3 --- /dev/null +++ b/runtime/conf.h @@ -0,0 +1,53 @@ +/* Definitions for config file handling (not yet an object). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * 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. + * + * 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef INCLUDED_CONF_H +#define INCLUDED_CONF_H + +/* definitions used for doNameLine to differentiate between different command types + * (with otherwise identical code). This is a left-over from the previous config + * system. It stays, because it is still useful. So do not wonder why it looks + * somewhat strange (at least its name). -- rgerhards, 2007-08-01 + */ +enum eDirective { DIR_TEMPLATE = 0, DIR_OUTCHANNEL = 1, DIR_ALLOWEDSENDER = 2}; + +/* interfaces */ +BEGINinterface(conf) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*doNameLine)(uchar **pp, void* pVal); + rsRetVal (*cfsysline)(uchar *p); + rsRetVal (*doModLoad)(uchar **pp, __attribute__((unused)) void* pVal); + rsRetVal (*doIncludeLine)(uchar **pp, __attribute__((unused)) void* pVal); + rsRetVal (*cfline)(uchar *line, selector_t **pfCurr); + rsRetVal (*processConfFile)(uchar *pConfFile); +ENDinterface(conf) +#define confCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(conf); + + +/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ +extern EHostnameCmpMode eDfltHostnameCmpMode; +extern cstr_t *pDfltHostnameCmp; +extern cstr_t *pDfltProgNameCmp; + +#endif /* #ifndef INCLUDED_CONF_H */ diff --git a/runtime/ctok.c b/runtime/ctok.c new file mode 100644 index 00000000..ceab15bd --- /dev/null +++ b/runtime/ctok.c @@ -0,0 +1,593 @@ +/* cfgtok.c - helper class to tokenize an input stream - which surprisingly + * currently does not work with streams but with string. But that will + * probably change over time ;) This class was originally written to support + * the expression module but may evolve when (if) the expression module is + * expanded (or aggregated) by a full-fledged ctoken based config parser. + * Obviously, this class is used together with config files and not any other + * parse function. + * + * Module begun 2008-02-19 by Rainer Gerhards + * + * Copyright (C) 2008 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <ctype.h> +#include <strings.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "ctok.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(ctok) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(ctok) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal ctokConstructFinalize(ctok_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the ctok object */ +BEGINobjDestruct(ctok) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ctok) + /* ... then free resources */ +ENDobjDestruct(ctok) + + +/* unget character from input stream. At most one character can be ungotten. + * This funtion is only permitted to be called after at least one character + * has been read from the stream. Right now, we handle the situation simply by + * moving the string "stream" pointer one position backwards. If we work with + * real streams (some time), the strm object will handle the functionality + * itself. -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokUngetCharFromStream(ctok_t *pThis, uchar __attribute__((unused)) c) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + --pThis->pp; + + RETiRet; +} + + +/* get the next character from the input "stream" (currently just a in-memory + * string...) -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetCharFromStream(ctok_t *pThis, uchar *pc) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pc != NULL); + + /* end of string or begin of comment terminates the "stream" */ + if(*pThis->pp == '\0' || *pThis->pp == '#') { + ABORT_FINALIZE(RS_RET_EOS); + } else { + *pc = *pThis->pp; + ++pThis->pp; + } + +finalize_it: + RETiRet; +} + + +/* skip whitespace in the input "stream". + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokSkipWhitespaceFromStream(ctok_t *pThis) +{ + DEFiRet; + uchar c; + + ISOBJ_TYPE_assert(pThis, ctok); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + while(isspace(c)) { + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + + /* we must unget the one non-whitespace we found */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + +dbgprintf("skipped whitespace, stream now '%s'\n", pThis->pp); +finalize_it: + RETiRet; +} + + +/* get the next word from the input "stream" (currently just a in-memory + * string...). A word is anything from the current location until the + * first non-alphanumeric character. If the word is longer + * than the provided memory buffer, parsing terminates when buffer length + * has been reached. A buffer of 128 bytes or more should always be by + * far sufficient. -- rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetWordFromStream(ctok_t *pThis, uchar *pWordBuf, size_t lenWordBuf) +{ + DEFiRet; + uchar c; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pWordBuf != NULL); + ASSERT(lenWordBuf > 0); + + CHKiRet(ctokSkipWhitespaceFromStream(pThis)); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + while((isalnum(c) || c == '_' || c == '-') && lenWordBuf > 1) { + *pWordBuf++ = c; + --lenWordBuf; + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + *pWordBuf = '\0'; /* there is always space for this - see while() */ + + /* push back the char that we have read too much */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + +finalize_it: + RETiRet; +} + + +/* read in a constant number + * This is the "number" ABNF element + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetNumber(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + number_t n; /* the parsed number */ + uchar c; + int valC; + int iBase; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + pToken->tok = ctok_NUMBER; + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + if(c == '0') { /* octal? */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); + if(c == 'x') { /* nope, hex! */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); + c = tolower(c); + iBase = 16; + } else { + iBase = 8; + } + } else { + iBase = 10; + } + + n = 0; + /* this loop is quite simple, a variable name is terminated by whitespace. */ + while(isdigit(c) || (c >= 'a' && c <= 'f')) { + if(isdigit(c)) { + valC = c - '0'; + } else { + valC = c - 'a' + 10; + } + + if(valC >= iBase) { + if(iBase == 8) { + ABORT_FINALIZE(RS_RET_INVALID_OCTAL_DIGIT); + } else { + ABORT_FINALIZE(RS_RET_INVALID_HEX_DIGIT); + } + } + /* we now have the next value and know it is right */ + n = n * iBase + valC; + CHKiRet(ctokGetCharFromStream(pThis, &c)); + c = tolower(c); + } + + /* we need to unget the character that made the loop terminate */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + + CHKiRet(var.SetNumber(pToken->pVar, n)); + +finalize_it: + RETiRet; +} + + +/* read in a variable + * This covers both msgvar and sysvar from the ABNF. + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetVar(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + uchar c; + cstr_t *pstrVal; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); + + if(c == '$') { /* second dollar, we have a system variable */ + pToken->tok = ctok_SYSVAR; + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* "eat" it... */ + } else { + pToken->tok = ctok_MSGVAR; + } + + CHKiRet(rsCStrConstruct(&pstrVal)); + /* this loop is quite simple, a variable name is terminated by whitespace. */ + while(!isspace(c)) { + CHKiRet(rsCStrAppendChar(pstrVal, tolower(c))); + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + CHKiRet(rsCStrFinish(pStrB)); + + CHKiRet(var.SetString(pToken->pVar, pstrVal)); + pstrVal = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrVal != NULL) { + rsCStrDestruct(&pstrVal); + } + } + + RETiRet; +} + + +/* read in a simple string (simpstr in ABNF) + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetSimpStr(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + uchar c; + int bInEsc = 0; + cstr_t *pstrVal; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + + pToken->tok = ctok_SIMPSTR; + + CHKiRet(rsCStrConstruct(&pstrVal)); + CHKiRet(ctokGetCharFromStream(pThis, &c)); + /* while we are in escape mode (had a backslash), no sequence + * terminates the loop. If outside, it is terminated by a single quote. + */ + while(bInEsc || c != '\'') { + if(bInEsc) { + CHKiRet(rsCStrAppendChar(pstrVal, c)); + bInEsc = 0; + } else { + if(c == '\\') { + bInEsc = 1; + } else { + CHKiRet(rsCStrAppendChar(pstrVal, c)); + } + } + CHKiRet(ctokGetCharFromStream(pThis, &c)); + } + CHKiRet(rsCStrFinish(pStrB)); + + CHKiRet(var.SetString(pToken->pVar, pstrVal)); + pstrVal = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrVal != NULL) { + rsCStrDestruct(&pstrVal); + } + } + + RETiRet; +} + + +/* Unget a token. The token ungotten will be returned the next time + * ctokGetToken() is called. Only one token can be ungotten at a time. + * If a second token is ungotten, the first is lost. This is considered + * a programming error. + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctokUngetToken(ctok_t *pThis, ctok_token_t *pToken) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(pToken != NULL); + ASSERT(pThis->pUngotToken == NULL); + + pThis->pUngotToken = pToken; + + RETiRet; +} + + +/* skip an inine comment (just like a C-comment) + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctokSkipInlineComment(ctok_t *pThis) +{ + DEFiRet; + uchar c; + int bHadAsterisk = 0; + + ISOBJ_TYPE_assert(pThis, ctok); + + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + while(!(bHadAsterisk && c == '/')) { + bHadAsterisk = (c == '*') ? 1 : 0; + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read next */ + } + +finalize_it: + RETiRet; +} + + + +/* Get the *next* token from the input stream. This parses the next token and + * ignores any whitespace in between. End of stream is communicated via iRet. + * The returned token must either be destructed by the caller OR being passed + * back to ctokUngetToken(). + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetToken(ctok_t *pThis, ctok_token_t **ppToken) +{ + DEFiRet; + ctok_token_t *pToken; + uchar c; + uchar szWord[128]; + int bRetry = 0; /* retry parse? Only needed for inline comments... */ + + ISOBJ_TYPE_assert(pThis, ctok); + ASSERT(ppToken != NULL); + + /* first check if we have an ungotten token and, if so, provide that + * one back (without any parsing). -- rgerhards, 2008-02-20 + */ + if(pThis->pUngotToken != NULL) { + *ppToken = pThis->pUngotToken; + pThis->pUngotToken = NULL; + FINALIZE; + } + + /* setup the stage - create our token */ + CHKiRet(ctok_token.Construct(&pToken)); + CHKiRet(ctok_token.ConstructFinalize(pToken)); + + /* find the next token. We may loop when we have inline comments */ + do { + bRetry = 0; + CHKiRet(ctokSkipWhitespaceFromStream(pThis)); + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + switch(c) { + case '=': /* == */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a character */ + pToken->tok = (c == '=')? ctok_CMP_EQ : ctok_INVALID; + break; + case '!': /* != */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a character */ + pToken->tok = (c == '=')? ctok_CMP_NEQ : ctok_INVALID; + break; + case '<': /* <, <=, <> */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a character */ + if(c == '=') { + pToken->tok = ctok_CMP_LTEQ; + } else if(c == '>') { + pToken->tok = ctok_CMP_NEQ; + } else { + pToken->tok = ctok_CMP_LT; + } + break; + case '>': /* >, >= */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a character */ + if(c == '=') { + pToken->tok = ctok_CMP_GTEQ; + } else { + pToken->tok = ctok_CMP_GT; + } + break; + case '+': + pToken->tok = ctok_PLUS; + break; + case '-': + pToken->tok = ctok_MINUS; + break; + case '*': + pToken->tok = ctok_TIMES; + break; + case '/': /* /, /.* ... *./ (comments, mungled here for obvious reasons...) */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '*') { + /* we have a comment and need to skip it */ + ctokSkipInlineComment(pThis); + bRetry = 1; + } else { + CHKiRet(ctokUngetCharFromStream(pThis, c)); /* put back, not processed */ + } + pToken->tok = ctok_DIV; + break; + case '%': + pToken->tok = ctok_MOD; + break; + case '(': + pToken->tok = ctok_LPAREN; + break; + case ')': + pToken->tok = ctok_RPAREN; + break; + case ',': + pToken->tok = ctok_COMMA; + break; + case '&': + pToken->tok = ctok_STRADD; + break; + case '$': + CHKiRet(ctokGetVar(pThis, pToken)); + break; + case '\'': /* simple string, this is somewhat more elaborate */ + CHKiRet(ctokGetSimpStr(pThis, pToken)); + break; + case '"': + /* TODO: template string parser */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + default: + CHKiRet(ctokUngetCharFromStream(pThis, c)); /* push back, we need it in any case */ + if(isdigit(c)) { + CHKiRet(ctokGetNumber(pThis, pToken)); + } else { /* now we check if we have a multi-char sequence */ + CHKiRet(ctokGetWordFromStream(pThis, szWord, sizeof(szWord)/sizeof(uchar))); + if(!strcasecmp((char*)szWord, "and")) { + pToken->tok = ctok_AND; + } else if(!strcasecmp((char*)szWord, "or")) { + pToken->tok = ctok_OR; + } else if(!strcasecmp((char*)szWord, "not")) { + pToken->tok = ctok_NOT; + } else if(!strcasecmp((char*)szWord, "contains")) { + pToken->tok = ctok_CMP_CONTAINS; + } else if(!strcasecmp((char*)szWord, "contains_i")) { + pToken->tok = ctok_CMP_CONTAINSI; + } else if(!strcasecmp((char*)szWord, "startswith")) { + pToken->tok = ctok_CMP_STARTSWITH; + } else if(!strcasecmp((char*)szWord, "startswith_i")) { + pToken->tok = ctok_CMP_STARTSWITHI; + } else if(!strcasecmp((char*)szWord, "then")) { + pToken->tok = ctok_THEN; + } else { + /* finally, we check if it is a function */ + CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */ + if(c == '(') { + /* push c back, higher level parser needs it */ + CHKiRet(ctokUngetCharFromStream(pThis, c)); + pToken->tok = ctok_FUNCTION; + /* TODO: fill function name */ + } else { /* give up... */ + dbgprintf("parser has an invalid word (token) '%s'\n", szWord); + pToken->tok = ctok_INVALID; + } + } + } + break; + } + } while(bRetry); /* warning: do ... while()! */ + + *ppToken = pToken; + dbgoprint((obj_t*) pToken, "token: %d\n", pToken->tok); + +finalize_it: +/*dbgprintf("ctokGetToken, returns %d, returns token %d, addr %p\n", iRet, (*ppToken)->tok, &((*ppToken)->tok));*/ + if(iRet != RS_RET_OK) { + if(pToken != NULL) + ctok_token.Destruct(&pToken); + } + + RETiRet; +} + + +/* property set methods */ +/* simple ones first */ +DEFpropSetMeth(ctok, pp, uchar*) + +/* return the current position of pp - most important as currently we do only + * partial parsing, so the rest must know where to start from... + * rgerhards, 2008-02-19 + */ +static rsRetVal +ctokGetpp(ctok_t *pThis, uchar **pp) +{ + DEFiRet; + ASSERT(pp != NULL); + *pp = pThis->pp; + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ctok) +CODESTARTobjQueryInterface(ctok) + if(pIf->ifVersion != ctokCURR_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->Construct = ctokConstruct; + pIf->ConstructFinalize = ctokConstructFinalize; + pIf->Destruct = ctokDestruct; + pIf->Getpp = ctokGetpp; + pIf->GetToken = ctokGetToken; + pIf->UngetToken = ctokUngetToken; + pIf->Setpp = ctokSetpp; +finalize_it: +ENDobjQueryInterface(ctok) + + + +BEGINObjClassInit(ctok, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, ctokConstructFinalize); +ENDObjClassInit(ctok) + +/* vi:set ai: + */ diff --git a/runtime/ctok.h b/runtime/ctok.h new file mode 100644 index 00000000..591f0838 --- /dev/null +++ b/runtime/ctok.h @@ -0,0 +1,56 @@ +/* The ctok object (implements a config file tokenizer). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_CTOK_H +#define INCLUDED_CTOK_H + +#include "obj.h" +#include "stringbuf.h" +#include "ctok_token.h" + +/* the ctokession object */ +typedef struct ctok_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pp; /* this points to the next unread character, it is a reminescent of pp in + the config parser code ;) */ + ctok_token_t *pUngotToken; /* buffer for ctokUngetToken(), NULL if not set */ +} ctok_t; + + +/* interfaces */ +BEGINinterface(ctok) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(ctok); + INTERFACEpropSetMeth(ctok, pp, uchar*); + rsRetVal (*Construct)(ctok_t **ppThis); + rsRetVal (*ConstructFinalize)(ctok_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(ctok_t **ppThis); + rsRetVal (*Getpp)(ctok_t *pThis, uchar **pp); + rsRetVal (*GetToken)(ctok_t *pThis, ctok_token_t **ppToken); + rsRetVal (*UngetToken)(ctok_t *pThis, ctok_token_t *pToken); +ENDinterface(ctok) +#define ctokCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(ctok); + +#endif /* #ifndef INCLUDED_CTOK_H */ diff --git a/runtime/ctok_token.c b/runtime/ctok_token.c new file mode 100644 index 00000000..8c17f693 --- /dev/null +++ b/runtime/ctok_token.c @@ -0,0 +1,129 @@ +/* ctok_token - implements the token_t class. + * + * Module begun 2008-02-20 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <ctype.h> +#include <strings.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "ctok_token.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(ctok_token) /* be sure to specify the object type also in END macro! */ + /* TODO: we may optimize the code below and alloc var only if actually + * needed (but we need it quite often) + */ + CHKiRet(var.Construct(&pThis->pVar)); + CHKiRet(var.ConstructFinalize(pThis->pVar)); +finalize_it: +ENDobjConstruct(ctok_token) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal ctok_tokenConstructFinalize(ctok_token_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the ctok object */ +BEGINobjDestruct(ctok_token) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ctok_token) + if(pThis->pVar != NULL) { + var.Destruct(&pThis->pVar); + } +ENDobjDestruct(ctok_token) + + +/* get the cstr_t from the token, but do not destruct it. This is meant to + * be used by a caller who passes on the string to some other function. The + * caller is responsible for destructing it. + * rgerhards, 2008-02-20 + */ +static rsRetVal +ctok_tokenUnlinkVar(ctok_token_t *pThis, var_t **ppVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, ctok_token); + ASSERT(ppVar != NULL); + + *ppVar = pThis->pVar; + pThis->pVar = NULL; + + RETiRet; +} + + +/* tell the caller if the supplied token is a compare operation */ +static int ctok_tokenIsCmpOp(ctok_token_t *pThis) +{ + return(pThis->tok >= ctok_CMP_EQ && pThis->tok <= ctok_CMP_GTEQ); +} + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ctok_token) +CODESTARTobjQueryInterface(ctok_token) + if(pIf->ifVersion != ctok_tokenCURR_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->Construct = ctok_tokenConstruct; + pIf->ConstructFinalize = ctok_tokenConstructFinalize; + pIf->Destruct = ctok_tokenDestruct; + pIf->UnlinkVar = ctok_tokenUnlinkVar; + pIf->IsCmpOp = ctok_tokenIsCmpOp; +finalize_it: +ENDobjQueryInterface(ctok_token) + + +BEGINObjClassInit(ctok_token, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, ctok_tokenConstructFinalize); +ENDObjClassInit(ctok_token) + +/* vi:set ai: + */ diff --git a/runtime/ctok_token.h b/runtime/ctok_token.h new file mode 100644 index 00000000..d36689fa --- /dev/null +++ b/runtime/ctok_token.h @@ -0,0 +1,87 @@ +/* The ctok_token object + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_CTOK_TOKEN_H +#define INCLUDED_CTOK_TOKEN_H + +#include "obj.h" +#include "var.h" + +/* the tokens... I use numbers below so that the tokens can be easier + * identified in debug output. These ID's are also partly resused as opcodes. + * As such, they should be kept below 1,000 so that they do not interfer + * with the rest of the opcodes. + */ +typedef struct { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + enum { + ctok_INVALID = 0, + ctok_OR = 1, + ctok_AND = 2, + ctok_PLUS = 3, + ctok_MINUS = 4, + ctok_TIMES = 5, /* "*" */ + ctok_DIV = 6, + ctok_MOD = 7, + ctok_NOT = 8, + ctok_RPAREN = 9, + ctok_LPAREN = 10, + ctok_COMMA = 11, + ctok_SYSVAR = 12, + ctok_MSGVAR = 13, + ctok_SIMPSTR = 14, + ctok_TPLSTR = 15, + ctok_NUMBER = 16, + ctok_FUNCTION = 17, + ctok_THEN = 18, + ctok_STRADD = 19, + ctok_CMP_EQ = 100, /* all compare operations must be in a row */ + ctok_CMP_NEQ = 101, + ctok_CMP_LT = 102, + ctok_CMP_GT = 103, + ctok_CMP_LTEQ = 104, + ctok_CMP_CONTAINS = 105, + ctok_CMP_STARTSWITH = 106, + ctok_CMP_CONTAINSI = 107, + ctok_CMP_STARTSWITHI = 108, + ctok_CMP_GTEQ = 109 /* end compare operations */ + } tok; + var_t *pVar; +} ctok_token_t; + + +/* interfaces */ +BEGINinterface(ctok_token) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(ctok_token); + rsRetVal (*Construct)(ctok_token_t **ppThis); + rsRetVal (*ConstructFinalize)(ctok_token_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(ctok_token_t **ppThis); + rsRetVal (*UnlinkVar)(ctok_token_t *pThis, var_t **ppVar); + int (*IsCmpOp)(ctok_token_t *pThis); +ENDinterface(ctok_token) +#define ctok_tokenCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(ctok_token); + +#endif /* #ifndef INCLUDED_CTOK_TOKEN_H */ diff --git a/runtime/datetime.c b/runtime/datetime.c new file mode 100644 index 00000000..6d5652ff --- /dev/null +++ b/runtime/datetime.c @@ -0,0 +1,681 @@ +/* The datetime object. It contains date and time related functions. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. The main intension was to move code out of syslogd.c + * in a useful manner. It is still undecided if all functions will continue + * to stay here or some will be moved into parser modules (once we have them). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> +#include <assert.h> +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include "rsyslog.h" +#include "obj.h" +#include "modules.h" +#include "datetime.h" +#include "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + + +/* ------------------------------ methods ------------------------------ */ + + +/** + * Get the current date/time in the best resolution the operating + * system has to offer (well, actually at most down to the milli- + * second level. + * + * The date and time is returned in separate fields as this is + * most portable and removes the need for additional structures + * (but I have to admit it is somewhat "bulky";)). + * + * Obviously, all caller-provided pointers must not be NULL... + */ +static void getCurrTime(struct syslogTime *t) +{ + struct timeval tp; + struct tm *tm; + struct tm tmBuf; + long lBias; +# if defined(__hpux) + struct timezone tz; +# endif + + assert(t != NULL); +# if defined(__hpux) + /* TODO: check this: under HP UX, the tz information is actually valid + * data. So we need to obtain and process it there. + */ + gettimeofday(&tp, &tz); +# else + gettimeofday(&tp, NULL); +# endif + tm = localtime_r((time_t*) &(tp.tv_sec), &tmBuf); + + t->year = tm->tm_year + 1900; + t->month = tm->tm_mon + 1; + t->day = tm->tm_mday; + t->hour = tm->tm_hour; + t->minute = tm->tm_min; + t->second = tm->tm_sec; + t->secfrac = tp.tv_usec; + t->secfracPrecision = 6; + +# if __sun + /* Solaris uses a different method of exporting the time zone. + * It is UTC - localtime, which is the opposite sign of mins east of GMT. + */ + lBias = -(daylight ? altzone : timezone); +# elif defined(__hpux) + lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0; +# else + lBias = tm->tm_gmtoff; +# endif + if(lBias < 0) + { + t->OffsetMode = '-'; + lBias *= -1; + } + else + t->OffsetMode = '+'; + t->OffsetHour = lBias / 3600; + t->OffsetMinute = lBias % 3600; +} + + + + +/******************************************************************* + * BEGIN CODE-LIBLOGGING * + ******************************************************************* + * Code in this section is borrowed from liblogging. This is an + * interim solution. Once liblogging is fully integrated, this is + * to be removed (see http://www.monitorware.com/liblogging for + * more details. 2004-11-16 rgerhards + * + * Please note that the orginal liblogging code is modified so that + * it fits into the context of the current version of syslogd.c. + * + * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!! + */ + +/** + * Parse a 32 bit integer number from a string. + * + * \param ppsz Pointer to the Pointer to the string being parsed. It + * must be positioned at the first digit. Will be updated + * so that on return it points to the first character AFTER + * the integer parsed. + * \retval The number parsed. + */ + +static int srSLMGParseInt32(char** ppsz) +{ + int i; + + i = 0; + while(isdigit((int) **ppsz)) + { + i = i * 10 + **ppsz - '0'; + ++(*ppsz); + } + + return i; +} + + +/** + * Parse a TIMESTAMP-3339. + * updates the parse pointer position. + */ +static int +ParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) +{ + char *pszTS = *ppszTS; + + assert(pTime != NULL); + assert(ppszTS != NULL); + assert(pszTS != NULL); + + pTime->year = srSLMGParseInt32(&pszTS); + + /* We take the liberty to accept slightly malformed timestamps e.g. in + * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course, + * with the current state of affairs, we would never run into this code + * here because at postion 11, there is no "T" in such cases ;) + */ + if(*pszTS++ != '-') + return FALSE; + pTime->month = srSLMGParseInt32(&pszTS); + if(pTime->month < 1 || pTime->month > 12) + return FALSE; + + if(*pszTS++ != '-') + return FALSE; + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != 'T') + return FALSE; + + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + + /* Now let's see if we have secfrac */ + if(*pszTS == '.') + { + char *pszStart = ++pszTS; + pTime->secfrac = srSLMGParseInt32(&pszTS); + pTime->secfracPrecision = (int) (pszTS - pszStart); + } + else + { + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + } + + /* check the timezone */ + if(*pszTS == 'Z') + { + pszTS++; /* eat Z */ + pTime->OffsetMode = 'Z'; + pTime->OffsetHour = 0; + pTime->OffsetMinute = 0; + } + else if((*pszTS == '+') || (*pszTS == '-')) + { + pTime->OffsetMode = *pszTS; + pszTS++; + + pTime->OffsetHour = srSLMGParseInt32(&pszTS); + if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->OffsetMinute = srSLMGParseInt32(&pszTS); + if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) + return FALSE; + } + else + /* there MUST be TZ information */ + return FALSE; + + /* OK, we actually have a 3339 timestamp, so let's indicated this */ + if(*pszTS == ' ') + ++pszTS; + else + return FALSE; + + /* update parse pointer */ + *ppszTS = pszTS; + + return TRUE; +} + + +/** + * Parse a TIMESTAMP-3164. + * Returns TRUE on parse OK, FALSE on parse error. + */ +static int +ParseTIMESTAMP3164(struct syslogTime *pTime, char** ppszTS) +{ + char *pszTS; + + assert(ppszTS != NULL); + pszTS = *ppszTS; + assert(pszTS != NULL); + assert(pTime != NULL); + + getCurrTime(pTime); /* obtain the current year and UTC offsets! */ + + /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), + * we may see the following character sequences occur: + * + * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec + * + * We will use this for parsing, as it probably is the + * fastest way to parse it. + * + * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C... + * Fixed a bug that lead to invalid detection of the data. The issue was that + * we had an if(++pszTS == 'x') inside of some of the consturcts below. However, + * there were also some elseifs (doing the same ++), which than obviously did not + * check the orginal character but the next one. Now removed the ++ and put it + * into the statements below. Was a really nasty bug... I didn't detect it before + * june, when it first manifested. This also lead to invalid parsing of the rest + * of the message, as the time stamp was not detected to be correct. - rgerhards + */ + switch(*pszTS++) + { + case 'J': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 1; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 6; + } else if(*pszTS == 'l') { + ++pszTS; + pTime->month = 7; + } else + return FALSE; + } else + return FALSE; + break; + case 'F': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'b') { + ++pszTS; + pTime->month = 2; + } else + return FALSE; + } else + return FALSE; + break; + case 'M': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 3; + } else if(*pszTS == 'y') { + ++pszTS; + pTime->month = 5; + } else + return FALSE; + } else + return FALSE; + break; + case 'A': + if(*pszTS == 'p') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 4; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'g') { + ++pszTS; + pTime->month = 8; + } else + return FALSE; + } else + return FALSE; + break; + case 'S': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'p') { + ++pszTS; + pTime->month = 9; + } else + return FALSE; + } else + return FALSE; + break; + case 'O': + if(*pszTS == 'c') { + ++pszTS; + if(*pszTS == 't') { + ++pszTS; + pTime->month = 10; + } else + return FALSE; + } else + return FALSE; + break; + case 'N': + if(*pszTS == 'o') { + ++pszTS; + if(*pszTS == 'v') { + ++pszTS; + pTime->month = 11; + } else + return FALSE; + } else + return FALSE; + break; + case 'D': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'c') { + ++pszTS; + pTime->month = 12; + } else + return FALSE; + } else + return FALSE; + break; + default: + return FALSE; + } + + /* done month */ + + if(*pszTS++ != ' ') + return FALSE; + + /* we accept a slightly malformed timestamp when receiving. This is + * we accept one-digit days + */ + if(*pszTS == ' ') + ++pszTS; + + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != ' ') + return FALSE; + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + + /* we provide support for an exter ":" after the date. While this is an + * invalid format, it occurs frequently enough (e.g. with Cisco devices) + * to permit it as a valid case. -- rgerhards, 2008-09-12 + */ + if(*pszTS++ == ':') + ++pszTS; + + /* OK, we actually have a 3164 timestamp, so let's indicate this + * and fill the rest of the properties. */ + pTime->timeType = 1; + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + *ppszTS = pszTS; /* provide updated parse position back to caller */ + return TRUE; +} + +/******************************************************************* + * END CODE-LIBLOGGING * + *******************************************************************/ + +/** + * Format a syslogTimestamp into format required by MySQL. + * We are using the 14 digits format. For example 20041111122600 + * is interpreted as '2004-11-11 12:26:00'. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst) +{ + /* currently we do not consider localtime/utc. This may later be + * added. If so, I recommend using a property replacer option + * and/or a global configuration option. However, we should wait + * on user requests for this feature before doing anything. + * rgerhards, 2007-06-26 + */ + assert(ts != NULL); + assert(pDst != NULL); + + if (iLenDst < 15) /* we need at least 14 bytes + 14 digits for timestamp + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); + +} + +int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) +{ + /* see note in formatTimestampToMySQL, applies here as well */ + assert(ts != NULL); + assert(pDst != NULL); + + if (iLenDst < 21) /* we need 20 bytes + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); +} + + +/** + * Format a syslogTimestamp to just the fractional seconds. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + * The buffer must be at least 10 bytes large. + * rgerhards, 2008-06-06 + */ +int formatTimestampSecFrac(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + int lenRet; + char szFmtStr[64]; + + assert(ts != NULL); + assert(pBuf != NULL); + assert(iLenBuf >= 10); + + if(ts->secfracPrecision > 0) + { /* We must look at + * the precision specified. For example, if we have millisec precision (3 digits), a + * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this + * is a huge difference ;). To avoid this, we first create a format string with + * the specific precision and *then* use that format string to do the actual formating. + */ + /* be careful: there is ONE actual %d in the format string below ;) */ + snprintf(szFmtStr, sizeof(szFmtStr), "%%0%dd", ts->secfracPrecision); + lenRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->secfrac); + } else { + pBuf[0] = '0'; + pBuf[1] = '1'; + lenRet = 1; + } + + return(lenRet); +} + + +/** + * Format a syslogTimestamp to a RFC3339 timestamp string (as + * specified in syslog-protocol). + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + int iRet; + char szTZ[7]; /* buffer for TZ information */ + + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 20) + return(0); /* we NEED at least 20 bytes */ + + /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ + if(ts->OffsetMode == 'Z') { + szTZ[0] = 'Z'; + szTZ[1] = '\0'; + } else { + snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", + ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); + } + + if(ts->secfracPrecision > 0) + { /* we now need to include fractional seconds. While doing so, we must look at + * the precision specified. For example, if we have millisec precision (3 digits), a + * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this + * is a huge difference ;). To avoid this, we first create a format string with + * the specific precision and *then* use that format string to do the actual + * formating (mmmmhhh... kind of self-modifying code... ;)). + */ + char szFmtStr[64]; + /* be careful: there is ONE actual %d in the format string below ;) */ + snprintf(szFmtStr, sizeof(szFmtStr), + "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", + ts->secfracPrecision); + iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); + } + else + iRet = snprintf(pBuf, iLenBuf, + "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, szTZ); + return(iRet); +} + +/** + * Format a syslogTimestamp to a RFC3164 timestamp sring. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string termnator). If 0 is returend, an error occured. + */ +int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", + "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec"}; + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 16) + return(0); /* we NEED 16 bytes */ + return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", + monthNames[ts->month], ts->day, ts->hour, + ts->minute, ts->second + )); +} + +/** + * Format a syslogTimestamp to a text format. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string termnator). If 0 is returend, an error occured. + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + assert(ts != NULL); + assert(pBuf != NULL); + + if(ts->timeType == 1) { + return(formatTimestamp3164(ts, pBuf, iLenBuf)); + } + + if(ts->timeType == 2) { + return(formatTimestamp3339(ts, pBuf, iLenBuf)); + } + + return(0); +} +#endif +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(datetime) +CODESTARTobjQueryInterface(datetime) + if(pIf->ifVersion != datetimeCURR_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->getCurrTime = getCurrTime; + pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339; + pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164; + pIf->formatTimestampToMySQL = formatTimestampToMySQL; + pIf->formatTimestampToPgSQL = formatTimestampToPgSQL; + pIf->formatTimestampSecFrac = formatTimestampSecFrac; + pIf->formatTimestamp3339 = formatTimestamp3339; + pIf->formatTimestamp3164 = formatTimestamp3164; +finalize_it: +ENDobjQueryInterface(datetime) + + +/* Initialize the datetime class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +ENDObjClassInit(datetime) + +/* vi:set ai: + */ diff --git a/runtime/datetime.h b/runtime/datetime.h new file mode 100644 index 00000000..755cc0ed --- /dev/null +++ b/runtime/datetime.h @@ -0,0 +1,58 @@ +/* The datetime object. Contains time-related functions. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_DATETIME_H +#define INCLUDED_DATETIME_H + +#include "datetime.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the datetime object */ +typedef struct datetime_s { +} datetime_t; + + +/* interfaces */ +BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */ + void (*getCurrTime)(struct syslogTime *t); + int (*ParseTIMESTAMP3339)(struct syslogTime *pTime, char** ppszTS); + int (*ParseTIMESTAMP3164)(struct syslogTime *pTime, char** pszTS); + int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst, size_t iLenDst); + int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst, size_t iLenDst); + int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); + int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); + int (*formatTimestampSecFrac)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); +ENDinterface(datetime) +#define datetimeCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ +/* interface changes: + * 1 - initial version + * 2 - not compatible to 1 - bugfix required ParseTIMESTAMP3164 to accept char ** as + * last parameter. Did not try to remain compatible as this is not something any + * third-party module should call. -- rgerhards, 2008.-09-12 + */ + +/* prototypes */ +PROTOTYPEObj(datetime); + +#endif /* #ifndef INCLUDED_DATETIME_H */ diff --git a/runtime/debug.c b/runtime/debug.c new file mode 100644 index 00000000..1450d029 --- /dev/null +++ b/runtime/debug.c @@ -0,0 +1,1334 @@ +/* debug.c + * + * This file proides debug and run time error analysis support. Some of the + * settings are very performance intense and my be turned off during a release + * build. + * + * File begun on 2008-01-22 by RGerhards + * + * Some functions are controlled by environment variables: + * + * RSYSLOG_DEBUGLOG if set, a debug log file is written to that location + * RSYSLOG_DEBUG specific debug options + * + * For details, visit doc/debug.html + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" /* autotools! */ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#include <ctype.h> +#include <assert.h> + +#include "rsyslog.h" +#include "debug.h" +#include "atomic.h" +#include "obj.h" + + +/* static data (some time to be replaced) */ +DEFobjCurrIf(obj) +int Debug; /* debug flag - read-only after startup */ +int debugging_on = 0; /* read-only, except on sig USR1 */ +static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bLogAllocFree = 0; /* shall calls to (m/c)alloc and free be logged to the debug log? */ +static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */ +static int bPrintTime = 1; /* print a timestamp together with debug message */ +static int bPrintAllDebugOnExit = 0; +static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */ +static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ +static FILE *altdbg = NULL; /* and the handle for alternate debug output */ +static FILE *stddbg; + +/* list of files/objects that should be printed */ +typedef struct dbgPrintName_s { + uchar *pName; + struct dbgPrintName_s *pNext; +} dbgPrintName_t; + + +/* forward definitions */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID); +static dbgThrdInfo_t *dbgGetThrdInfo(void); +static int dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot); + + +/* This lists are single-linked and members are added at the top */ +static dbgPrintName_t *printNameFileRoot = NULL; + + +/* list of all known FuncDBs. We use a special list, because it must only be single-linked. As + * functions never disappear, we only need to add elements when we see a new one and never need + * to remove anything. For this, we simply add at the top, which saves us a Last pointer. The goal + * is to use as few memory as possible. + */ +typedef struct dbgFuncDBListEntry_s { + dbgFuncDB_t *pFuncDB; + struct dbgFuncDBListEntry_s *pNext; +} dbgFuncDBListEntry_t; +dbgFuncDBListEntry_t *pFuncDBListRoot; + +static pthread_mutex_t mutFuncDBList; + +typedef struct dbgMutLog_s { + struct dbgMutLog_s *pNext; + struct dbgMutLog_s *pPrev; + pthread_mutex_t *mut; + pthread_t thrd; + dbgFuncDB_t *pFuncDB; + int lockLn; /* the actual line where the mutex was locked */ + short mutexOp; +} dbgMutLog_t; +static dbgMutLog_t *dbgMutLogListRoot = NULL; +static dbgMutLog_t *dbgMutLogListLast = NULL; +static pthread_mutex_t mutMutLog; + + +static dbgThrdInfo_t *dbgCallStackListRoot = NULL; +static dbgThrdInfo_t *dbgCallStackListLast = NULL; +static pthread_mutex_t mutCallStack; + +static pthread_mutex_t mutdbgprintf; +static pthread_mutex_t mutdbgoprint; + +static pthread_key_t keyCallStack; + + +/* we do not have templates, so we use some macros to create linked list handlers + * for the several types + * DLL means "doubly linked list" + * rgerhards, 2008-01-23 + */ +#define DLL_Del(type, pThis) \ + if(pThis->pPrev != NULL) \ + pThis->pPrev->pNext = pThis->pNext; \ + if(pThis->pNext != NULL) \ + pThis->pNext->pPrev = pThis->pPrev; \ + if(pThis == dbg##type##ListRoot) \ + dbg##type##ListRoot = pThis->pNext; \ + if(pThis == dbg##type##ListLast) \ + dbg##type##ListLast = pThis->pPrev; \ + free(pThis); + +#define DLL_Add(type, pThis) \ + if(dbg##type##ListRoot == NULL) { \ + dbg##type##ListRoot = pThis; \ + dbg##type##ListLast = pThis; \ + } else { \ + pThis->pPrev = dbg##type##ListLast; \ + dbg##type##ListLast->pNext = pThis; \ + dbg##type##ListLast = pThis; \ + } + +/* we need to do our own mutex cancel cleanup handler as it shall not + * be subject to the debugging instrumentation (that would probably run us + * into an infinite loop + */ +static void dbgMutexCancelCleanupHdlr(void *pmut) +{ + pthread_mutex_unlock((pthread_mutex_t*) pmut); +} + + +/* handler to update the last execution location seen + * rgerhards, 2008-01-28 + */ +static inline void +dbgRecordExecLocation(int iStackPtr, int line) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + pThrd->lastLine[iStackPtr] = line; +} + + +/* ------------------------- mutex tracking code ------------------------- */ + +/* ------------------------- FuncDB utility functions ------------------------- */ + +#define SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ((int) (sizeof(pFuncDB->mutInfo) / sizeof(dbgFuncDBmutInfoEntry_t))) + +/* print a FuncDB + */ +static void dbgFuncDBPrint(dbgFuncDB_t *pFuncDB) +{ + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + /* make output suitable for sorting on invocation count */ + dbgprintf("%10.10ld times called: %s:%d:%s\n", pFuncDB->nTimesCalled, pFuncDB->file, pFuncDB->line, pFuncDB->func); +} + + +/* print all funcdb entries + */ +static void dbgFuncDBPrintAll(void) +{ + dbgFuncDBListEntry_t *pFuncDBList; + int nFuncs = 0; + + for(pFuncDBList = pFuncDBListRoot ; pFuncDBList != NULL ; pFuncDBList = pFuncDBList->pNext) { + dbgFuncDBPrint(pFuncDBList->pFuncDB); + nFuncs++; + } + + dbgprintf("%d unique functions called\n", nFuncs); +} + + +/* find a mutex inside the FuncDB mutex table. Returns NULL if not found. Only mutexes from the same thread + * are found. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBGetMutexInfo(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + int i; + int iFound = -1; + pthread_t ourThrd = pthread_self(); + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].pmut == pmut && pFuncDB->mutInfo[i].lockLn != -1 && pFuncDB->mutInfo[i].thrd == ourThrd) { + iFound = i; + break; + } + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + + +/* print any mutex that can be found in the FuncDB. Custom header is provided. + * "thrd" is the thread that is searched. If it is 0, mutexes for all threads + * shall be printed. + */ +static inline void +dbgFuncDBPrintActiveMutexes(dbgFuncDB_t *pFuncDB, char *pszHdrText, pthread_t thrd) +{ + int i; + char pszThrdName[64]; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn != -1 && (thrd == 0 || thrd == pFuncDB->mutInfo[i].thrd)) { + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pFuncDB->mutInfo[i].thrd, 1); + dbgprintf("%s:%d:%s:invocation %ld: %s %p[%d/%s]\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, + pFuncDB->mutInfo[i].lInvocation, pszHdrText, (void*)pFuncDB->mutInfo[i].pmut, i, + pszThrdName); + } + } +} + +/* find a free mutex info spot in FuncDB. NULL is returned if table is full. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBFindFreeMutexInfo(dbgFuncDB_t *pFuncDB) +{ + int i; + int iFound = -1; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn == -1) { + iFound = i; + break; + } + } + + if(iFound == -1) { + dbgprintf("%s:%d:%s: INFO: out of space in FuncDB for mutex info (max %d entries) - ignoring\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func, SIZE_FUNCDB_MUTEX_TABLE(pFuncDB)); + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + +/* add a mutex lock to the FuncDB. If the size is exhausted, info is discarded. + */ +static inline void dbgFuncDBAddMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut, int lockLn) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBFindFreeMutexInfo(pFuncDB)) != NULL) { + pMutInfo->pmut = pmut; + pMutInfo->lockLn = lockLn; + pMutInfo->lInvocation = pFuncDB->nTimesCalled; + pMutInfo->thrd = pthread_self(); + } +} + +/* remove a locked mutex from the FuncDB (unlock case!). + */ +static inline void dbgFuncDBRemoveMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBGetMutexInfo(pFuncDB, pmut)) != NULL) { + pMutInfo->lockLn = -1; + } +} + + +/* ------------------------- END FuncDB utility functions ------------------------- */ + +/* ########################################################################### + * IMPORTANT NOTE + * Mutex instrumentation reduces the code's concurrency and thus affects its + * order of execution. It is vital to test the code also with mutex + * instrumentation turned off! Some bugs may not show up while it on... + * ########################################################################### + */ + +/* constructor & add new entry to list + */ +dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pLog = calloc(1, sizeof(dbgMutLog_t)); + assert(pLog != NULL); + + /* fill data members */ + pLog->mut = pmut; + pLog->thrd = pthread_self(); + pLog->mutexOp = mutexOp; + pLog->lockLn = lockLn; + pLog->pFuncDB = pFuncDB; + + DLL_Add(MutLog, pLog); + + return pLog; +} + + +/* destruct log entry + */ +void dbgMutLogDelEntry(dbgMutLog_t *pLog) +{ + assert(pLog != NULL); + DLL_Del(MutLog, pLog); +} + + +/* print a single mutex log entry */ +static void dbgMutLogPrintOne(dbgMutLog_t *pLog) +{ + char *strmutop; + char buf[64]; + char pszThrdName[64]; + + assert(pLog != NULL); + switch(pLog->mutexOp) { + case MUTOP_LOCKWAIT: + strmutop = "waited on"; + break; + case MUTOP_LOCK: + strmutop = "owned"; + break; + default: + snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp); + strmutop = buf; + break; + } + + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pLog->thrd, 1); + dbgprintf("mutex 0x%lx is being %s by code at %s:%d, thread %s\n", (unsigned long) pLog->mut, + strmutop, pLog->pFuncDB->file, + (pLog->mutexOp == MUTOP_LOCK) ? pLog->lockLn : pLog->pFuncDB->line, + pszThrdName); +} + +/* print the complete mutex log */ +static void dbgMutLogPrintAll(void) +{ + dbgMutLog_t *pLog; + + dbgprintf("Mutex log for all known mutex operations:\n"); + for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext) + dbgMutLogPrintOne(pLog); + +} + + +/* find the last log entry for that specific mutex object. Is used to delete + * a thread's own requests. Searches occur from the back. + * The pFuncDB is optional and may be NULL to indicate no specific funciont is + * reqested (aka "it is ignored" ;)). This is important for the unlock case. + */ +dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, dbgFuncDB_t *pFuncDB) +{ + dbgMutLog_t *pLog; + pthread_t mythrd = pthread_self(); + + pLog = dbgMutLogListLast; + while(pLog != NULL) { + if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop + && (pFuncDB == NULL || pLog->pFuncDB == pFuncDB)) + break; + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find mutex object from the back of the list */ +dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast) +{ + dbgMutLog_t *pLog; + + if(pLast == NULL) + pLog = dbgMutLogListLast; + else + pLog = pLast->pPrev; /* if we get the last processed one, we need to go one before it, else its an endless loop */ + + while(pLog != NULL) { + if(pLog->mut == pmut) { + break; + } + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find lock aquire for mutex from back of list */ +dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut) +{ + dbgMutLog_t *pLog; + + pLog = dbgMutLogFindFromBack(pmut, NULL); + while(pLog != NULL) { + if(pLog->mutexOp == MUTOP_LOCK) + break; + pLog = dbgMutLogFindFromBack(pmut, pLog); + } + + return pLog; +} + +/* report wait on a mutex and add it to the mutex log */ +static inline void dbgMutexPreLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln) +{ + dbgMutLog_t *pHolder; + dbgMutLog_t *pLog; + char pszBuf[128]; + char pszHolderThrdName[64]; + char *pszHolder; + + pthread_mutex_lock(&mutMutLog); + pHolder = dbgMutLogFindHolder(pmut); + pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, pFuncDB, ln); + + if(pHolder == NULL) + pszHolder = "[NONE]"; + else { + dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1); + snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName); + pszHolder = pszBuf; + } + + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder); + pthread_mutex_unlock(&mutMutLog); +} + + +/* report aquired mutex */ +static inline void dbgMutexLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + + /* find and delete "waiting" entry */ + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, pFuncDB); + assert(pLog != NULL); + dbgMutLogDelEntry(pLog); + + /* add "lock" entry */ + pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn); + dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn); + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut); +} + +/* if we unlock, we just remove the lock aquired entry from the log list */ +static inline void dbgMutexUnlockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int unlockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL); + assert(pLog != NULL); + + /* we found the last lock entry. We now need to see from which FuncDB we need to + * remove it. This is recorded inside the mutex log entry. + */ + dbgFuncDBRemoveMutexLock(pLog->pFuncDB, pmut); + + /* donw with the log entry, get rid of it... */ + dbgMutLogDelEntry(pLog); + + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p UNlocked\n", pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut); +} + + +/* wrapper for pthread_mutex_lock() */ +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_lock(pmut); + if(ret == 0) { + dbgMutexLockLog(pmut, pFuncDB, ln); + } else { + dbgprintf("%s:%d:%s: ERROR: pthread_mutex_lock() for mutex %p failed with error %d\n", + pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret); + } + + return ret; +} + + +/* wrapper for pthread_mutex_unlock() */ +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_unlock(pmut); + return ret; +} + + +/* wrapper for pthread_cond_wait() */ +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p\n", pFuncDB->file, pFuncDB->line, + pFuncDB->func, (void*)pmut, (void*)cond); + } + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_cond_wait(cond, pmut); + return ret; +} + + +/* wrapper for pthread_cond_timedwait() */ +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p (with timeout)\n", pFuncDB->file, + pFuncDB->line, pFuncDB->func, (void*)pmut, (void*)cond); + } + ret = pthread_cond_timedwait(cond, pmut, abstime); + dbgMutexLockLog(pmut, pFuncDB, ln); + return ret; +} + + +/* ------------------------- end mutex tracking code ------------------------- */ + + +/* ------------------------- malloc/free tracking code ------------------------- */ + +/* wrapper for free() */ +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + dbgRecordExecLocation(iStackPtr, ln); + if(bLogAllocFree) { + dbgprintf("%s:%d:%s: free %p\n", pFuncDB->file, ln, pFuncDB->func, (void*) pMem); + } + free(pMem); +} + + +/* ------------------------- end malloc/free tracking code ------------------------- */ + +/* ------------------------- thread tracking code ------------------------- */ + +/* get ptr to call stack - if none exists, create a new stack + */ +static dbgThrdInfo_t *dbgGetThrdInfo(void) +{ + dbgThrdInfo_t *pThrd; + + pthread_mutex_lock(&mutCallStack); + if((pThrd = pthread_getspecific(keyCallStack)) == NULL) { + /* construct object */ + pThrd = calloc(1, sizeof(dbgThrdInfo_t)); + pThrd->thrd = pthread_self(); + (void) pthread_setspecific(keyCallStack, pThrd); + DLL_Add(CallStack, pThrd); + } + pthread_mutex_unlock(&mutCallStack); + return pThrd; +} + + + +/* find a specific thread ID. It must be present, else something is wrong + */ +static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd) +{ + dbgThrdInfo_t *pThrd; + + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + if(pThrd->thrd == thrd) + break; + } + return pThrd; +} + + +/* build a string with the thread name. If none is set, the thread ID is + * used instead. Caller must provide buffer space. If bIncludeNumID is set + * to 1, the numerical ID is always included. + * rgerhards 2008-01-23 + */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID) +{ + dbgThrdInfo_t *pThrd; + + assert(pszBuf != NULL); + + pThrd = dbgFindThrd(thrd); + + if(pThrd == 0 || pThrd->pszThrdName == NULL) { + /* no thread name, use numeric value */ + snprintf(pszBuf, lenBuf, "%lx", (long) thrd); + } else { + if(bIncludeNumID) { + snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, (long) thrd); + } else { + snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName); + } + } + +} + + +/* set a name for the current thread. The caller provided string is duplicated. + */ +void dbgSetThrdName(uchar *pszName) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + if(pThrd->pszThrdName != NULL) + free(pThrd->pszThrdName); + pThrd->pszThrdName = strdup((char*)pszName); +} + + +/* destructor for a call stack object */ +static void dbgCallStackDestruct(void *arg) +{ + dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg; + + dbgprintf("destructor for debug call stack %p called\n", pThrd); + if(pThrd->pszThrdName != NULL) { + free(pThrd->pszThrdName); + } + + pthread_mutex_lock(&mutCallStack); + DLL_Del(CallStack, pThrd); + pthread_mutex_unlock(&mutCallStack); +} + + +/* print a thread's call stack + */ +static void dbgCallStackPrint(dbgThrdInfo_t *pThrd) +{ + int i; + char pszThrdName[64]; + + pthread_mutex_lock(&mutCallStack); + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1); + dbgprintf("\n"); + dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName); + for(i = 0 ; i < pThrd->stackPtr ; i++) { + dbgprintf("%d: %s:%d:%s:\n", i, pThrd->callStack[i]->file, pThrd->lastLine[i], pThrd->callStack[i]->func); + } + dbgprintf("maximum number of nested calls for this thread: %d.\n", pThrd->stackPtrMax); + dbgprintf("NOTE: not all calls may have been recorded, code does not currently guarantee that!\n"); + pthread_mutex_unlock(&mutCallStack); +} + +/* print all threads call stacks + */ +static void dbgCallStackPrintAll(void) +{ + dbgThrdInfo_t *pThrd; + /* stack info */ + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + dbgCallStackPrint(pThrd); + } +} + + +/* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat + * more meaningful way. + * rgerhards, 2008-01-22 + */ +void +sigsegvHdlr(int signum) +{ + char *signame; + struct sigaction sigAct; + + /* first, restore the default abort handler */ + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + sigaction(SIGABRT, &sigAct, NULL); + + /* then do our actual processing */ + if(signum == SIGSEGV) { + signame = " (SIGSEGV)"; + } else if(signum == SIGABRT) { + signame = " (SIGABRT)"; + } else { + signame = ""; + } + + dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame); + + if(bAbortTrace) { + dbgPrintAllDebugInfo(); + dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + } + + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + + /* and finally abort... */ + /* TODO: think about restarting rsyslog in this case: may be a good idea, + * but may also be a very bad one (restart loops!) + */ + abort(); +} + + +#pragma GCC diagnostic ignored "-Wempty-body" +/* print some debug output when an object is given + * This is mostly a copy of dbgprintf, but I do not know how to combine it + * into a single function as we have variable arguments and I don't know how to call + * from one vararg function into another. I don't dig in this, it is OK for the + * time being. -- rgerhards, 2008-01-29 + */ +void +dbgoprint(obj_t *pObj, char *fmt, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + if(!(Debug && debugging_on)) + return; + + /* a quick and very dirty hack to enable us to display just from those objects + * that we are interested in. So far, this must be changed at compile time (and + * chances are great it is commented out while you read it. Later, this shall + * be selectable via the environment. -- rgerhards, 2008-02-20 + */ +#if 0 + if(objGetObjID(pObj) != OBJexpr) + return; +#endif + + + pthread_mutex_lock(&mutdbgoprint); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgoprint); + + /* The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. + * rgerhards, 2005-10-25 + * I have decided that it is not worth fixing it - especially as it works + * pretty well. + * rgerhards, 2007-06-15 + */ + if(ptLastThrdID != pthread_self()) { + if(!bWasNL) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + bWasNL = 1; + } + ptLastThrdID = pthread_self(); + } + + /* do not cache the thread name, as the caller might have changed it + * TODO: optimized, invalidate cache when new name is set + */ + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); + + if(bWasNL) { + if(bPrintTime) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + /* print object name header if we have an object */ + if(pObj != NULL) { + if(stddbg != NULL) fprintf(stddbg, "%s: ", obj.GetName(pObj)); + if(altdbg != NULL) fprintf(altdbg, "%s: ", obj.GetName(pObj)); + } + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} +#pragma GCC diagnostic warning "-Wempty-body" + + +#pragma GCC diagnostic ignored "-Wempty-body" +/* print some debug output when no object is given + * WARNING: duplicate code, see dbgoprin above! + */ +void +dbgprintf(char *fmt, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + if(!(Debug && debugging_on)) + return; + + pthread_mutex_lock(&mutdbgprintf); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprintf); + + /* The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. + * rgerhards, 2005-10-25 + * I have decided that it is not worth fixing it - especially as it works + * pretty well. + * rgerhards, 2007-06-15 + */ + if(ptLastThrdID != pthread_self()) { + if(!bWasNL) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + bWasNL = 1; + } + ptLastThrdID = pthread_self(); + } + + /* do not cache the thread name, as the caller might have changed it + * TODO: optimized, invalidate cache when new name is set + */ + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); + + if(bWasNL) { + if(bPrintTime) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} +#pragma GCC diagnostic warning "-Wempty-body" + +void tester(void) +{ +BEGINfunc +ENDfunc +} + +/* handler called when a function is entered. This function creates a new + * funcDB on the heap if the passed-in pointer is NULL. + */ +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line) +{ + int iStackPtr = 0; /* TODO: find some better default, this one hurts the least, but it is not clean */ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + dbgFuncDBListEntry_t *pFuncDBListEntry; + unsigned int i; + dbgFuncDB_t *pFuncDB; + + assert(ppFuncDB != NULL); + assert(file != NULL); + assert(func != NULL); + pFuncDB = *ppFuncDB; + assert((pFuncDB == NULL) || (pFuncDB->magic == dbgFUNCDB_MAGIC)); + + if(pFuncDB == NULL) { + /* we do not yet have a funcDB and need to create a new one. We also add it + * to the linked list of funcDBs. Please note that when a module is unloaded and + * then reloaded again, we currently do not try to find its previous funcDB but + * instead create a duplicate. While finding the past one is straightforward, it + * opens up the question what to do with e.g. mutex data left in it. We do not + * yet see any need to handle these questions, so duplicaton seems to be the right + * thing to do. -- rgerhards, 2008-03-10 + */ + /* dbgprintf("%s:%d:%s: called first time, initializing FuncDB\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); */ + /* get a new funcDB and add it to the list (all of this is protected by the mutex) */ + pthread_mutex_lock(&mutFuncDBList); + if((pFuncDBListEntry = calloc(1, sizeof(dbgFuncDBListEntry_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB List entry, not adding\n", errno); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + if((pFuncDB = calloc(1, sizeof(dbgFuncDB_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + pFuncDBListEntry->pFuncDB = pFuncDB; + pFuncDBListEntry->pNext = pFuncDBListRoot; + pFuncDBListRoot = pFuncDBListEntry; + } + } + /* now intialize the funcDB + * note that we duplicate the strings, because the address provided may go away + * if a loadable module is unloaded! + */ + pFuncDB->magic = dbgFUNCDB_MAGIC; + pFuncDB->file = strdup(file); + pFuncDB->func = strdup(func); + pFuncDB->line = line; + pFuncDB->nTimesCalled = 0; + for(i = 0 ; i < sizeof(pFuncDB->mutInfo)/sizeof(dbgFuncDBmutInfoEntry_t) ; ++i) { + pFuncDB->mutInfo[i].lockLn = -1; /* set to not Locked */ + } + + /* a round of safety checks... */ + if(pFuncDB->file == NULL || pFuncDB->func == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + /* do a little bit of cleanup */ + if(pFuncDB->file != NULL) + free(pFuncDB->file); + if(pFuncDB->func != NULL) + free(pFuncDB->func); + free(pFuncDB); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } + + /* done mutex-protected operations */ + pthread_mutex_unlock(&mutFuncDBList); + + *ppFuncDB = pFuncDB; /* all went well, so we can update the caller */ + } + + /* when we reach this point, we have a fully-initialized FuncDB! */ + ATOMIC_INC(pFuncDB->nTimesCalled); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) + dbgprintf("%s:%d: %s: enter\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(dbgFuncDB_t*))) { + dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func); + iStackPtr = pThrd->stackPtr; + } else { + iStackPtr = pThrd->stackPtr++; + if(pThrd->stackPtr > pThrd->stackPtrMax) + pThrd->stackPtrMax = pThrd->stackPtr; + pThrd->callStack[iStackPtr] = pFuncDB; + pThrd->lastLine[iStackPtr] = line; + } + +exit_it: + return iStackPtr; +} + + +/* handler called when a function is exited + */ +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + + assert(iStackPtrRestore >= 0); + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + + dbgFuncDBPrintActiveMutexes(pFuncDB, "WARNING: mutex still owned by us as we exit function, mutex: ", pthread_self()); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) { + if(iRet == RS_RET_NO_IRET) + dbgprintf("%s:%d: %s: exit: (no iRet)\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + else + dbgprintf("%s:%d: %s: exit: %d\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, iRet); + } + pThrd->stackPtr = iStackPtrRestore; + if(pThrd->stackPtr < 0) { + dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", (long) pthread_self()); + pThrd->stackPtr = 0; + } +} + + +/* externally-callable handler to record the last exec location. We use a different function + * so that the internal one can be inline. + */ +void +dbgSetExecLocation(int iStackPtr, int line) +{ + dbgRecordExecLocation(iStackPtr, line); +} + + +void dbgPrintAllDebugInfo(void) +{ + dbgCallStackPrintAll(); + dbgMutLogPrintAll(); + if(bPrintFuncDBOnExit) + dbgFuncDBPrintAll(); +} + + +/* Handler for SIGUSR2. Dumps all available debug output + */ +static void sigusr2Hdlr(int __attribute__((unused)) signum) +{ + dbgprintf("SIGUSR2 received, dumping debug information\n"); + dbgPrintAllDebugInfo(); +} + +/* support system to set debug options at runtime */ + + +/* parse a param/value pair from the current location of the + * option string. Returns 1 if an option was found, 0 + * otherwise. 0 means there are NO MORE options to be + * processed. -- rgerhards, 2008-02-28 + */ +static int +dbgGetRTOptNamVal(uchar **ppszOpt, uchar **ppOptName, uchar **ppOptVal) +{ + int bRet = 0; + uchar *p; + size_t i; + static uchar optname[128]; /* not thread- or reentrant-safe, but that */ + static uchar optval[1024]; /* doesn't matter (called only once at startup) */ + + assert(ppszOpt != NULL); + assert(*ppszOpt != NULL); + + /* make sure we have some initial values */ + optname[0] = '\0'; + optval[0] = '\0'; + + p = *ppszOpt; + /* skip whitespace */ + while(*p && isspace(*p)) + ++p; + + /* name - up until '=' or whitespace */ + i = 0; + while(i < (sizeof(optname)/sizeof(uchar) - 1) && *p && *p != '=' && !isspace(*p)) { + optname[i++] = *p++; + } + + if(i > 0) { + bRet = 1; + optname[i] = '\0'; + if(*p == '=') { + /* we have a value, get it */ + ++p; + i = 0; + while(i < (sizeof(optval)/sizeof(uchar) - 1) && *p && !isspace(*p)) { + optval[i++] = *p++; + } + optval[i] = '\0'; + } + } + + /* done */ + *ppszOpt = p; + *ppOptName = optname; + *ppOptVal = optval; + return bRet; +} + + +/* create new PrintName list entry and add it to list (they will never + * be removed. -- rgerhards, 2008-02-28 + */ +static void +dbgPrintNameAdd(uchar *pName, dbgPrintName_t **ppRoot) +{ + dbgPrintName_t *pEntry; + + if((pEntry = calloc(1, sizeof(dbgPrintName_t))) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if((pEntry->pName = (uchar*) strdup((char*) pName)) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if(*ppRoot != NULL) { + pEntry->pNext = *ppRoot; /* we enqueue at the front */ + } + *ppRoot = pEntry; +} + + +/* check if name is in a printName list - returns 1 if so, 0 otherwise. + * There is one special handling: if the root pointer is NULL, the function + * always returns 1. This is because when no name is set, output shall be + * unrestricted. + * rgerhards, 2008-02-28 + */ +static int +dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot) +{ + int bFound = 0; + dbgPrintName_t *pEntry = pRoot; + + if(pRoot == NULL) + bFound = 1; + + while(pEntry != NULL && !bFound) { + if(!strcasecmp((char*)pEntry->pName, (char*)pName)) { + bFound = 1; + } else { + pEntry = pEntry->pNext; + } + } + + return bFound; +} + + +/* read in the runtime options + * rgerhards, 2008-02-28 + */ +static void +dbgGetRuntimeOptions(void) +{ + uchar *pszOpts; + uchar *optval; + uchar *optname; + + /* set some defaults */ + stddbg = stdout; + + if((pszOpts = (uchar*) getenv("RSYSLOG_DEBUG")) != NULL) { + /* we have options set, so let's process them */ + while(dbgGetRTOptNamVal(&pszOpts, &optname, &optval)) { + if(!strcasecmp((char*)optname, "help")) { + fprintf(stderr, + "rsyslogd runtime debug support - help requested, rsyslog terminates\n\n" + "environment variables:\n" + "addional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n" + "to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n" + "Commands are (all case-insensitive):\n" + "help (this list, terminates rsyslogd\n" + "LogFuncFlow\n" + "LogAllocFree (very partly implemented)\n" + "PrintFuncDB\n" + "PrintMutexAction\n" + "PrintAllDebugInfoOnExit (not yet implemented)\n" + "NoLogTimestamp\n" + "Nostdoout\n" + "filetrace=file (may be provided multiple times)\n" + "\nSee debug.html in your doc set or http://www.rsyslog.com for details\n"); + exit(1); + } else if(!strcasecmp((char*)optname, "debug")) { + /* this is earlier in the process than the -d option, as such it + * allows us to spit out debug messages from the very beginning. + */ + Debug = 1; + debugging_on = 1; + } else if(!strcasecmp((char*)optname, "logfuncflow")) { + bLogFuncFlow = 1; + } else if(!strcasecmp((char*)optname, "logallocfree")) { + bLogAllocFree = 1; + } else if(!strcasecmp((char*)optname, "printfuncdb")) { + bPrintFuncDBOnExit = 1; + } else if(!strcasecmp((char*)optname, "printmutexaction")) { + bPrintMutexAction = 1; + } else if(!strcasecmp((char*)optname, "printalldebuginfoonexit")) { + bPrintAllDebugOnExit = 1; + } else if(!strcasecmp((char*)optname, "nologtimestamp")) { + bPrintTime = 0; + } else if(!strcasecmp((char*)optname, "nostdout")) { + stddbg = NULL; + } else if(!strcasecmp((char*)optname, "noaborttrace")) { + bAbortTrace = 0; + } else if(!strcasecmp((char*)optname, "filetrace")) { + if(*optval == '\0') { + fprintf(stderr, "Error: logfile debug option requires filename, " + "e.g. \"logfile=debug.c\"\n"); + exit(1); + } else { + /* create new entry and add it to list */ + dbgPrintNameAdd(optval, &printNameFileRoot); + } + } else { + fprintf(stderr, "Error: invalid debug option '%s', value '%s' - ignored\n", + optval, optname); + } + } + } +} + + +/* end support system to set debug options at runtime */ + +rsRetVal dbgClassInit(void) +{ + DEFiRet; + + struct sigaction sigAct; + sigset_t sigSet; + + (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ + + /* we initialize all Mutexes with code, as some platforms seem to have + * bugs in the static initializer macros. So better be on the safe side... + * rgerhards, 2008-03-06 + */ + pthread_mutex_init(&mutFuncDBList, NULL); + pthread_mutex_init(&mutMutLog, NULL); + pthread_mutex_init(&mutCallStack, NULL); + pthread_mutex_init(&mutdbgprintf, NULL); + pthread_mutex_init(&mutdbgoprint, NULL); + + /* while we try not to use any of the real rsyslog code (to avoid infinite loops), we + * need to have the ability to query object names. Thus, we need to obtain a pointer to + * the object interface. -- rgerhards, 2008-02-29 + */ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sigusr2Hdlr; + sigaction(SIGUSR2, &sigAct, NULL); + + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGUSR2); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + + dbgGetRuntimeOptions(); /* init debug system from environment */ + pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG"); + + if(pszAltDbgFileName != NULL) { + /* we have a secondary file, so let's open it) */ + if((altdbg = fopen(pszAltDbgFileName, "w")) == NULL) { + fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); + } + } + + dbgSetThrdName((uchar*)"main thread"); + +finalize_it: + RETiRet; +} + + +rsRetVal dbgClassExit(void) +{ + dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel; + pthread_key_delete(keyCallStack); + + if(bPrintAllDebugOnExit) + dbgPrintAllDebugInfo(); + + if(altdbg != NULL) + fclose(altdbg); + + /* now free all of our memory to make the memory debugger happy... */ + pFuncDBListEtry = pFuncDBListRoot; + while(pFuncDBListEtry != NULL) { + pToDel = pFuncDBListEtry; + pFuncDBListEtry = pFuncDBListEtry->pNext; + free(pToDel->pFuncDB->file); + free(pToDel->pFuncDB->func); + free(pToDel->pFuncDB); + free(pToDel); + } + + return RS_RET_OK; +} +/* vi:set ai: + */ diff --git a/runtime/debug.h b/runtime/debug.h new file mode 100644 index 00000000..214b7c05 --- /dev/null +++ b/runtime/debug.h @@ -0,0 +1,146 @@ +/* debug.h + * + * Definitions for the debug and run-time analysis support module. + * Contains a lot of macros. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" + +/* external static data elements (some time to be replaced) */ +extern int Debug; /* debug flag - read-only after startup */ +extern int debugging_on; /* read-only, except on sig USR1 */ + +/* data types */ + +/* the function database. It is used as a static var inside each function. That provides + * us the fast access to it that we need to make the instrumentation work. It's address + * also serves as a unique function identifier and can be used inside other structures + * to refer to the function (e.g. for pretty-printing names). + * rgerhards, 2008-01-24 + */ +typedef struct dbgFuncDBmutInfoEntry_s { + pthread_mutex_t *pmut; + int lockLn; /* line where it was locked (inside our func): -1 means mutex is not locked */ + pthread_t thrd; /* thrd where the mutex was locked */ + unsigned long lInvocation; /* invocation (unique during program run!) of this function that locked the mutex */ +} dbgFuncDBmutInfoEntry_t; +typedef struct dbgFuncDB_s { + unsigned magic; + unsigned long nTimesCalled; + char *func; + char *file; + int line; + dbgFuncDBmutInfoEntry_t mutInfo[5]; + /* remember to update the initializer if you add anything or change the order! */ +} dbgFuncDB_t; +#define dbgFUNCDB_MAGIC 0xA1B2C3D4 +#define dbgFuncDB_t_INITIALIZER \ + { \ + .magic = dbgFUNCDB_MAGIC,\ + .nTimesCalled = 0,\ + .func = __func__, \ + .file = __FILE__, \ + .line = __LINE__ \ + } + +/* the structure below was originally just the thread's call stack, but it has + * a bit evolved over time. So we have now ended up with the fact that it + * all debug info we know about the thread. + */ +typedef struct dbgCallStack_s { + pthread_t thrd; + dbgFuncDB_t *callStack[500]; + int lastLine[500]; /* last line where code execution was seen */ + int stackPtr; + int stackPtrMax; + char *pszThrdName; + struct dbgCallStack_s *pNext; + struct dbgCallStack_s *pPrev; +} dbgThrdInfo_t; + + +/* prototypes */ +rsRetVal dbgClassInit(void); +rsRetVal dbgClassExit(void); +void sigsegvHdlr(int signum); +void dbgoprint(obj_t *pObj, char *fmt, ...) __attribute__((format(printf, 2, 3))); +void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr); +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line); +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet); +void dbgSetExecLocation(int iStackPtr, int line); +void dbgSetThrdName(uchar *pszName); +void dbgPrintAllDebugInfo(void); + +/* macros */ +#ifdef RTINST +# define BEGINfunc static dbgFuncDB_t *pdbgFuncDB; int dbgCALLStaCK_POP_POINT = dbgEntrFunc(&pdbgFuncDB, __FILE__, __func__, __LINE__); +# define ENDfunc dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, RS_RET_NO_IRET); +# define ENDfuncIRet dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, iRet); +# define ASSERT(x) assert(x) +#else +# define BEGINfunc +# define ENDfunc +# define ENDfuncIRet +# define ASSERT(x) +#endif +#ifdef RTINST +# define RUNLOG dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__); dbgprintf("%s:%d: %s: log point\n", __FILE__, __LINE__, __func__) +# define RUNLOG_VAR(fmt, x) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: var '%s'[%s]: " fmt "\n", __FILE__, __LINE__, __func__, #x, fmt, x) +# define RUNLOG_STR(str) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: %s\n", __FILE__, __LINE__, __func__, str) +#else +# define RUNLOG +# define RUNLOG_VAR(fmt, x) +# define RUNLOG_STR(str) +#endif + +/* mutex operations */ +#define MUTOP_LOCKWAIT 1 +#define MUTOP_LOCK 2 +#define MUTOP_UNLOCK 3 + + +/* debug aides */ +#ifdef RTINST +#define d_pthread_mutex_lock(x) dbgMutexLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_mutex_unlock(x) dbgMutexUnlock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_wait(cond, mut) dbgCondWait(cond, mut, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_timedwait(cond, mut, to) dbgCondTimedWait(cond, mut, to, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_free(x) dbgFree(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#else +#define d_pthread_mutex_lock(x) pthread_mutex_lock(x) +#define d_pthread_mutex_unlock(x) pthread_mutex_unlock(x) +#define d_pthread_cond_wait(cond, mut) pthread_cond_wait(cond, mut) +#define d_pthread_cond_timedwait(cond, mut, to) pthread_cond_timedwait(cond, mut, to) +#define d_free(x) free(x) +#endif +#endif /* #ifndef DEBUG_H_INCLUDED */ diff --git a/runtime/errmsg.c b/runtime/errmsg.c new file mode 100644 index 00000000..3c3ee02c --- /dev/null +++ b/runtime/errmsg.c @@ -0,0 +1,144 @@ +/* The errmsg object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. I converted this module to lgpl and have checked that + * all contributors agreed to that step. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "errmsg.h" +#include "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + +/* We now receive three parameters: one is the internal error code + * which will also become the error message number, the second is + * errno - if it is non-zero, the corresponding error message is included + * in the text and finally the message text itself. Note that it is not + * 100% clean to use the internal errcode, as it may be reached from + * multiple actual error causes. However, it is much better than having + * no error code at all (and in most cases, a single internal error code + * maps to a specific error event). + * rgerhards, 2008-06-27 + */ +static void __attribute__((format(printf, 3, 4))) +LogError(int iErrno, int iErrCode, char *fmt, ... ) +{ + va_list ap; + char buf[1024]; + char msg[1024]; + char errStr[1024]; + size_t lenBuf; + + BEGINfunc + assert(fmt != NULL); + /* Format parameters */ + va_start(ap, fmt); + lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap); + if(lenBuf >= sizeof(buf)) { + /* if our buffer was too small, we simply truncate. */ + lenBuf--; + } + va_end(ap); + + /* Log the error now */ + buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + + dbgprintf("Called LogError, msg: %s\n", buf); + + if(iErrno != 0) { + rs_strerror_r(iErrno, errStr, sizeof(errStr)); + if(iErrCode == NO_ERRCODE) { + snprintf(msg, sizeof(msg), "%s: %s", buf, errStr); + } else { + snprintf(msg, sizeof(msg), "%s: %s [try http://www.rsyslog.com/e/%d ]", buf, errStr, iErrCode * -1); + } + } else { + if(iErrCode == NO_ERRCODE) { + snprintf(msg, sizeof(msg), "%s", buf); + } else { + snprintf(msg, sizeof(msg), "%s [try http://www.rsyslog.com/e/%d ]", buf, iErrCode * -1); + } + } + msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + errno = 0; + + glblErrLogger(iErrCode, (uchar*)msg); + + ENDfunc +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(errmsg) +CODESTARTobjQueryInterface(errmsg) + if(pIf->ifVersion != errmsgCURR_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->LogError = LogError; +finalize_it: +ENDobjQueryInterface(errmsg) + + +/* Initialize the errmsg class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(errmsg, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(errmsg) + +/* Exit the class. + * rgerhards, 2008-04-17 + */ +BEGINObjClassExit(errmsg, OBJ_IS_CORE_MODULE) /* class, version */ + /* release objects we no longer need */ +ENDObjClassExit(errmsg) + +/* vi:set ai: + */ diff --git a/runtime/errmsg.h b/runtime/errmsg.h new file mode 100644 index 00000000..799954fb --- /dev/null +++ b/runtime/errmsg.h @@ -0,0 +1,46 @@ +/* The errmsg object. It is used to emit error message inside rsyslog. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_ERRMSG_H +#define INCLUDED_ERRMSG_H + +#include "errmsg.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the errmsg object */ +typedef struct errmsg_s { +} errmsg_t; + + +/* interfaces */ +BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */ + void __attribute__((format(printf, 3, 4))) (*LogError)(int iErrno, int iErrCode, char *pszErrFmt, ... ); +ENDinterface(errmsg) +#define errmsgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(errmsg); + +#endif /* #ifndef INCLUDED_ERRMSG_H */ diff --git a/runtime/expr.c b/runtime/expr.c new file mode 100644 index 00000000..ee5b9e2c --- /dev/null +++ b/runtime/expr.c @@ -0,0 +1,418 @@ +/* expr.c - an expression class. + * This module contains all code needed to represent expressions. Most + * importantly, that means code to parse and execute them. Expressions + * heavily depend on (loadable) functions, so it works in conjunction + * with the function manager. + * + * Module begun 2007-11-30 by Rainer Gerhards + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "template.h" +#include "expr.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmprg) +DEFobjCurrIf(var) +DEFobjCurrIf(ctok_token) +DEFobjCurrIf(ctok) + + +/* ------------------------------ parser functions ------------------------------ */ +/* the following functions implement the parser. They are all static. For + * simplicity, the function names match their ABNF definition. The ABNF is defined + * in the doc set. See file expression.html for details. I do *not* reproduce it + * here in an effort to keep both files in sync. + * + * All functions receive the current expression object as parameter as well as the + * current tokenizer. + * + * rgerhards, 2008-02-19 + */ + +/* forward definiton - thanks to recursive ABNF, we can not avoid at least one ;) */ +static rsRetVal expr(expr_t *pThis, ctok_t *tok); + + +static rsRetVal +terminal(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken = NULL; + var_t *pVar; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(ctok.GetToken(tok, &pToken)); + /* note: pToken is destructed in finalize_it */ + + switch(pToken->tok) { + case ctok_SIMPSTR: + dbgoprint((obj_t*) pThis, "simpstr\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHCONSTANT, pVar)); /* add to program */ + break; + case ctok_NUMBER: + dbgoprint((obj_t*) pThis, "number\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHCONSTANT, pVar)); /* add to program */ + break; + case ctok_FUNCTION: + dbgoprint((obj_t*) pThis, "function\n"); + /* TODO: vm: call - well, need to implement that first */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + case ctok_MSGVAR: + dbgoprint((obj_t*) pThis, "MSGVAR\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHMSGVAR, pVar)); /* add to program */ + break; + case ctok_SYSVAR: + dbgoprint((obj_t*) pThis, "SYSVAR\n"); + CHKiRet(ctok_token.UnlinkVar(pToken, &pVar)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHSYSVAR, pVar)); /* add to program */ + break; + case ctok_LPAREN: + dbgoprint((obj_t*) pThis, "expr\n"); + CHKiRet(ctok_token.Destruct(&pToken)); /* "eat" processed token */ + CHKiRet(expr(pThis, tok)); + CHKiRet(ctok.GetToken(tok, &pToken)); /* get next one */ + if(pToken->tok != ctok_RPAREN) + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + break; + default: + dbgoprint((obj_t*) pThis, "invalid token %d\n", pToken->tok); + ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); + break; + } + +finalize_it: + if(pToken != NULL) { + ctok_token.Destruct(&pToken); /* "eat" processed token */ + } + + RETiRet; +} + +static rsRetVal +factor(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + int bWasNot; + int bWasUnaryMinus; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(ctok.GetToken(tok, &pToken)); + if(pToken->tok == ctok_NOT) { + dbgprintf("not\n"); + bWasNot = 1; + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); /* get new one for next check */ + } else { + bWasNot = 0; + } + + if(pToken->tok == ctok_MINUS) { + dbgprintf("unary minus\n"); + bWasUnaryMinus = 1; + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + } else { + bWasUnaryMinus = 0; + /* we could not process the token, so push it back */ + CHKiRet(ctok.UngetToken(tok, pToken)); + } + + CHKiRet(terminal(pThis, tok)); + + /* warning: the order if the two following ifs is important. Do not change them, this + * would change the semantics of the expression! + */ + if(bWasUnaryMinus) { + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_UNARY_MINUS, NULL)); /* add to program */ + } + + if(bWasNot == 1) { + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_NOT, NULL)); /* add to program */ + } + +finalize_it: + RETiRet; +} + + +static rsRetVal +term(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(factor(pThis, tok)); + + /* *(("*" / "/" / "%") factor) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_TIMES || pToken->tok == ctok_DIV || pToken->tok == ctok_MOD) { + dbgoprint((obj_t*) pThis, "/,*,%%\n"); + CHKiRet(factor(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + +static rsRetVal +val(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(term(pThis, tok)); + + /* *(("+" / "-") term) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_PLUS || pToken->tok == ctok_MINUS || pToken->tok == ctok_STRADD) { + dbgoprint((obj_t*) pThis, "+/-/&\n"); + CHKiRet(term(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +static rsRetVal +e_cmp(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(val(pThis, tok)); + + /* 0*1(cmp_op val) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + if(ctok_token.IsCmpOp(pToken)) { + dbgoprint((obj_t*) pThis, "cmp\n"); + CHKiRet(val(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, (opcode_t) pToken->tok, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + } else { + /* we could not process the token, so push it back */ + CHKiRet(ctok.UngetToken(tok, pToken)); + } + + +finalize_it: + RETiRet; +} + + +static rsRetVal +e_and(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(e_cmp(pThis, tok)); + + /* *("and" e_cmp) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_AND) { + dbgoprint((obj_t*) pThis, "and\n"); + CHKiRet(e_cmp(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_AND, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +static rsRetVal +expr(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + ctok_token_t *pToken; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + CHKiRet(e_and(pThis, tok)); + + /* *("or" e_and) part */ + CHKiRet(ctok.GetToken(tok, &pToken)); + while(pToken->tok == ctok_OR) { + dbgoprint((obj_t*) pThis, "found OR\n"); + CHKiRet(e_and(pThis, tok)); + CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_OR, NULL)); /* add to program */ + CHKiRet(ctok_token.Destruct(&pToken)); /* no longer needed */ + CHKiRet(ctok.GetToken(tok, &pToken)); + } + + /* unget the token that made us exit the loop - it's obviously not one + * we can process. + */ + CHKiRet(ctok.UngetToken(tok, pToken)); + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end parser functions ------------------------------ */ + + +/* ------------------------------ actual expr object functions ------------------------------ */ + +/* Standard-Constructor + * rgerhards, 2008-02-09 (a rainy Tenerife return flight day ;)) + */ +BEGINobjConstruct(expr) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(expr) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal exprConstructFinalize(expr_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, expr); + + RETiRet; +} + + +/* destructor for the expr object */ +BEGINobjDestruct(expr) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(expr) + if(pThis->pVmprg != NULL) + vmprg.Destruct(&pThis->pVmprg); +ENDobjDestruct(expr) + + +/* parse an expression object based on a given tokenizer + * rgerhards, 2008-02-19 + */ +rsRetVal +exprParse(expr_t *pThis, ctok_t *tok) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, expr); + ISOBJ_TYPE_assert(tok, ctok); + + /* first, we need to make sure we have a program where we can add to what we parse... */ + CHKiRet(vmprg.Construct(&pThis->pVmprg)); + CHKiRet(vmprg.ConstructFinalize(pThis->pVmprg)); + + /* happy parsing... */ + CHKiRet(expr(pThis, tok)); + dbgoprint((obj_t*) pThis, "successfully parsed/created expression\n"); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(expr) +CODESTARTobjQueryInterface(expr) + if(pIf->ifVersion != exprCURR_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->Construct = exprConstruct; + pIf->ConstructFinalize = exprConstructFinalize; + pIf->Destruct = exprDestruct; + pIf->Parse = exprParse; +finalize_it: +ENDobjQueryInterface(expr) + + +/* Initialize the expr class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(expr, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmprg, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(ctok_token, CORE_COMPONENT)); + CHKiRet(objUse(ctok, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, exprConstructFinalize); +ENDObjClassInit(expr) + +/* vi:set ai: + */ diff --git a/runtime/expr.h b/runtime/expr.h new file mode 100644 index 00000000..974b71ec --- /dev/null +++ b/runtime/expr.h @@ -0,0 +1,56 @@ +/* The expr object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_EXPR_H +#define INCLUDED_EXPR_H + +#include "obj.h" +#include "ctok.h" +#include "vmprg.h" +#include "stringbuf.h" + +/* a node inside an expression tree */ +typedef struct exprNode_s { +} exprNode_t; + + +/* the expression object */ +typedef struct expr_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmprg_t *pVmprg; /* the expression in vmprg format - ready to execute */ +} expr_t; + + +/* interfaces */ +BEGINinterface(expr) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(expr); + rsRetVal (*Construct)(expr_t **ppThis); + rsRetVal (*ConstructFinalize)(expr_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(expr_t **ppThis); + rsRetVal (*Parse)(expr_t *pThis, ctok_t *ctok); +ENDinterface(expr) +#define exprCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(expr); + +#endif /* #ifndef INCLUDED_EXPR_H */ diff --git a/runtime/glbl.c b/runtime/glbl.c new file mode 100644 index 00000000..11a664f8 --- /dev/null +++ b/runtime/glbl.c @@ -0,0 +1,258 @@ +/* glbl.c - this module holds global defintions and data items. + * These are shared among the runtime library. Their use should be + * limited to cases where it is actually needed. The main intension for + * implementing them was support for the transistion from v2 to v4 + * (with fully modular design), but it turned out that there may also + * be some other good use cases besides backwards-compatibility. + * + * Module begun 2008-04-16 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <sys/socket.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "cfsysline.h" +#include "glbl.h" + +/* some defaults */ +#ifndef DFLT_NETSTRM_DRVR +# define DFLT_NETSTRM_DRVR ((uchar*)"ptcp") +#endif + +/* static data */ +DEFobjStaticHelpers + +/* static data + * For this object, these variables are obviously what makes the "meat" of the + * class... + */ +static uchar *pszWorkDir = NULL; +static int iDefPFFamily = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both) */ +static int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ +static int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ +static int bDisableDNS = 0; /* don't look up IP addresses of remote messages */ +static uchar *LocalHostName = NULL;/* our hostname - read-only after startup */ +static uchar *LocalDomain; /* our local domain name - read-only after startup */ +static char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ +static char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ +static uchar *pszDfltNetstrmDrvr = NULL; /* module name of default netstream driver */ +static uchar *pszDfltNetstrmDrvrCAF = NULL; /* default CA file for the netstrm driver */ +static uchar *pszDfltNetstrmDrvrKeyFile = NULL; /* default key file for the netstrm driver (server) */ +static uchar *pszDfltNetstrmDrvrCertFile = NULL; /* default cert file for the netstrm driver (server) */ + + +/* define a macro for the simple properties' set and get functions + * (which are always the same). This is only suitable for pretty + * simple cases which require neither checks nor memory allocation. + */ +#define SIMP_PROP(nameFunc, nameVar, dataType) \ + SIMP_PROP_GET(nameFunc, nameVar, dataType) \ + SIMP_PROP_SET(nameFunc, nameVar, dataType) +#define SIMP_PROP_SET(nameFunc, nameVar, dataType) \ +static rsRetVal Set##nameFunc(dataType newVal) \ +{ \ + nameVar = newVal; \ + return RS_RET_OK; \ +} +#define SIMP_PROP_GET(nameFunc, nameVar, dataType) \ +static dataType Get##nameFunc(void) \ +{ \ + return(nameVar); \ +} + +SIMP_PROP(DefPFFamily, iDefPFFamily, int) /* note that in the future we may check the family argument */ +SIMP_PROP(DropMalPTRMsgs, bDropMalPTRMsgs, int) +SIMP_PROP(Option_DisallowWarning, option_DisallowWarning, int) +SIMP_PROP(DisableDNS, bDisableDNS, int) +SIMP_PROP(LocalDomain, LocalDomain, uchar*) +SIMP_PROP(StripDomains, StripDomains, char**) +SIMP_PROP(LocalHosts, LocalHosts, char**) + +SIMP_PROP_SET(LocalHostName, LocalHostName, uchar*) +SIMP_PROP_SET(DfltNetstrmDrvr, pszDfltNetstrmDrvr, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrCAF, pszDfltNetstrmDrvrCAF, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrKeyFile, pszDfltNetstrmDrvrKeyFile, uchar*) /* TODO: use custom function which frees existing value */ +SIMP_PROP_SET(DfltNetstrmDrvrCertFile, pszDfltNetstrmDrvrCertFile, uchar*) /* TODO: use custom function which frees existing value */ + +#undef SIMP_PROP +#undef SIMP_PROP_SET +#undef SIMP_PROP_GET + + +/* return our local hostname. if it is not set, "[localhost]" is returned + */ +static uchar* +GetLocalHostName(void) +{ + return(LocalHostName == NULL ? (uchar*) "[localhost]" : LocalHostName); +} + + +/* return the current working directory */ +static uchar* +GetWorkDir(void) +{ + return(pszWorkDir == NULL ? (uchar*) "" : pszWorkDir); +} + + +/* return the current default netstream driver */ +static uchar* +GetDfltNetstrmDrvr(void) +{ + return(pszDfltNetstrmDrvr == NULL ? DFLT_NETSTRM_DRVR : pszDfltNetstrmDrvr); +} + + +/* return the current default netstream driver CA File */ +static uchar* +GetDfltNetstrmDrvrCAF(void) +{ + return(pszDfltNetstrmDrvrCAF); +} + + +/* return the current default netstream driver key File */ +static uchar* +GetDfltNetstrmDrvrKeyFile(void) +{ + return(pszDfltNetstrmDrvrKeyFile); +} + + +/* return the current default netstream driver certificate File */ +static uchar* +GetDfltNetstrmDrvrCertFile(void) +{ + return(pszDfltNetstrmDrvrCertFile); +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(glbl) +CODESTARTobjQueryInterface(glbl) + if(pIf->ifVersion != glblCURR_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->GetWorkDir = GetWorkDir; +#define SIMP_PROP(name) \ + pIf->Get##name = Get##name; \ + pIf->Set##name = Set##name; + SIMP_PROP(DefPFFamily); + SIMP_PROP(DropMalPTRMsgs); + SIMP_PROP(Option_DisallowWarning); + SIMP_PROP(DisableDNS); + SIMP_PROP(LocalHostName) + SIMP_PROP(LocalDomain) + SIMP_PROP(StripDomains) + SIMP_PROP(LocalHosts) + SIMP_PROP(DfltNetstrmDrvr) + SIMP_PROP(DfltNetstrmDrvrCAF) + SIMP_PROP(DfltNetstrmDrvrKeyFile) + SIMP_PROP(DfltNetstrmDrvrCertFile) +#undef SIMP_PROP +finalize_it: +ENDobjQueryInterface(glbl) + + +/* Reset config variables to default values. + * rgerhards, 2008-04-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if(pszDfltNetstrmDrvr != NULL) { + free(pszDfltNetstrmDrvr); + pszDfltNetstrmDrvr = NULL; + } + if(pszDfltNetstrmDrvrCAF != NULL) { + free(pszDfltNetstrmDrvrCAF); + pszDfltNetstrmDrvrCAF = NULL; + } + if(pszDfltNetstrmDrvrKeyFile != NULL) { + free(pszDfltNetstrmDrvrKeyFile); + pszDfltNetstrmDrvrKeyFile = NULL; + } + if(pszDfltNetstrmDrvrCertFile != NULL) { + free(pszDfltNetstrmDrvrCertFile); + pszDfltNetstrmDrvrCertFile = NULL; + } + if(pszWorkDir != NULL) { + free(pszWorkDir); + pszWorkDir = NULL; + } + bDropMalPTRMsgs = 0; + return RS_RET_OK; +} + + + +/* Initialize the glbl class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(glbl, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* register config handlers (TODO: we need to implement a way to unregister them) */ + CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, NULL, &pszWorkDir, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriver", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercafile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCAF, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriverkeyfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrKeyFile, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercertfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCertFile, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); +ENDObjClassInit(glbl) + + +/* Exit the glbl class. + * rgerhards, 2008-04-17 + */ +BEGINObjClassExit(glbl, OBJ_IS_CORE_MODULE) /* class, version */ + if(pszDfltNetstrmDrvr != NULL) + free(pszDfltNetstrmDrvr); + if(pszDfltNetstrmDrvrCAF != NULL) + free(pszDfltNetstrmDrvrCAF); + if(pszDfltNetstrmDrvrKeyFile != NULL) + free(pszDfltNetstrmDrvrKeyFile); + if(pszDfltNetstrmDrvrCertFile != NULL) + free(pszDfltNetstrmDrvrCertFile); + if(pszWorkDir != NULL) + free(pszWorkDir); + if(LocalHostName != NULL) + free(LocalHostName); +ENDObjClassExit(glbl) + +/* vi:set ai: + */ diff --git a/runtime/glbl.h b/runtime/glbl.h new file mode 100644 index 00000000..90436319 --- /dev/null +++ b/runtime/glbl.h @@ -0,0 +1,62 @@ +/* Definition of globally-accessible data items. + * + * This module provides access methods to items of global scope. Most often, + * these globals serve as defaults to initialize local settings. Currently, + * many of them are either constants or global variable references. However, + * this module provides the necessary hooks to change that at any time. + * + * Please note that there currently is no glbl.c file as we do not yet + * have any implementations. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef GLBL_H_INCLUDED +#define GLBL_H_INCLUDED + +#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */ + +/* interfaces */ +BEGINinterface(glbl) /* name must also be changed in ENDinterface macro! */ + uchar* (*GetWorkDir)(void); +#define SIMP_PROP(name, dataType) \ + dataType (*Get##name)(void); \ + rsRetVal (*Set##name)(dataType); + SIMP_PROP(DefPFFamily, int) + SIMP_PROP(DropMalPTRMsgs, int) + SIMP_PROP(Option_DisallowWarning, int) + SIMP_PROP(DisableDNS, int) + SIMP_PROP(LocalHostName, uchar*) + SIMP_PROP(LocalDomain, uchar*) + SIMP_PROP(StripDomains, char**) + SIMP_PROP(LocalHosts, char**) + SIMP_PROP(DfltNetstrmDrvr, uchar*) + SIMP_PROP(DfltNetstrmDrvrCAF, uchar*) + SIMP_PROP(DfltNetstrmDrvrKeyFile, uchar*) + SIMP_PROP(DfltNetstrmDrvrCertFile, uchar*) +#undef SIMP_PROP +ENDinterface(glbl) +#define glblCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* the remaining prototypes */ +PROTOTYPEObj(glbl); + +#endif /* #ifndef GLBL_H_INCLUDED */ diff --git a/runtime/linkedlist.c b/runtime/linkedlist.c new file mode 100644 index 00000000..8f842e43 --- /dev/null +++ b/runtime/linkedlist.c @@ -0,0 +1,414 @@ +/* linkedlist.c + * This file set implements a generic linked list object. It can be used + * wherever a linke list is required. + * + * NOTE: we do not currently provide a constructor and destructor for the + * object itself as we assume it will always be part of another strucuture. + * Having a pointer to it, I think, does not really make sense but costs + * performance. Consequently, there is is llInit() and llDestroy() and they + * do what a constructor and destructur do, except for creating the + * linkedList_t structure itself. + * + * File begun on 2007-07-31 by RGerhards + * + * Copyright (C) 2007, 2008 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "linkedlist.h" + + +/* Initialize an existing linkedList_t structure + * pKey destructor may be zero to take care of non-keyed lists. + */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()) +{ + assert(pThis != NULL); + assert(pEltDestructor != NULL); + + pThis->pEltDestruct = pEltDestructor; + pThis->pKeyDestruct = pKeyDestructor; + pThis->cmpOp = pCmpOp; + pThis->pKey = NULL; + pThis->iNumElts = 0; + pThis->pRoot = NULL; + pThis->pLast = NULL; + + return RS_RET_OK; +}; + + +/* llDestroyEltData - destroys a list element + * It is a separate function as the + * functionality is needed in multiple code-pathes. + */ +static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt) +{ + DEFiRet; + + assert(pList != NULL); + assert(pElt != NULL); + + /* we ignore errors during destruction, as we need to try + * free the element in any case. + */ + if(pElt->pData != NULL) + pList->pEltDestruct(pElt->pData); + if(pElt->pKey != NULL) + pList->pKeyDestruct(pElt->pKey); + free(pElt); + pList->iNumElts--; /* one less */ + + RETiRet; +} + + +/* llDestroy - destroys a COMPLETE linkedList + */ +rsRetVal llDestroy(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + assert(pThis != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL) { + pEltPrev = pElt; + pElt = pElt->pNext; + /* we ignore errors during destruction, as we need to try + * finish the linked list in any case. + */ + llDestroyElt(pThis, pEltPrev); + } + /* now clean up the pointers */ + pThis->pRoot = NULL; + pThis->pLast = NULL; + + RETiRet; +} + +/* llDestroyRootElt - destroy the root element but otherwise + * keeps this list intact. -- rgerhards, 2007-08-03 + */ +rsRetVal llDestroyRootElt(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pPrev; + + if(pThis->pRoot == NULL) { + ABORT_FINALIZE(RS_RET_EMPTY_LIST); + } + + pPrev = pThis->pRoot; + if(pPrev->pNext == NULL) { + /* it was the only list element */ + pThis->pLast = NULL; + pThis->pRoot = NULL; + } else { + /* there are other list elements */ + pThis->pRoot = pPrev->pNext; + } + + CHKiRet(llDestroyElt(pThis, pPrev)); + +finalize_it: + RETiRet; +} + + +/* get next user data element of a linked list. The caller must also + * provide a "cookie" to the function. On initial call, it must be + * NULL. Other than that, the caller is not allowed to to modify the + * cookie. In the current implementation, the cookie is an actual + * pointer to the current list element, but this is nothing that the + * caller should rely on. + */ +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr) +{ + llElt_t *pElt; + DEFiRet; + + assert(pThis != NULL); + assert(ppElt != NULL); + assert(ppUsr != NULL); + + pElt = *ppElt; + + pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext; + + if(pElt == NULL) { + iRet = RS_RET_END_OF_LINKEDLIST; + } else { + *ppUsr = pElt->pData; + } + + *ppElt = pElt; + + RETiRet; +} + + +/* return the key of an Elt + * rgerhards, 2007-09-11: note that ppDatea is actually a void**, + * but I need to make it a void* to avoid lots of compiler warnings. + * It will be converted later down in the code. + */ +rsRetVal llGetKey(llElt_t *pThis, void *ppData) +{ + assert(pThis != NULL); + assert(ppData != NULL); + + *(void**) ppData = pThis->pKey; + + return RS_RET_OK; +} + + +/* construct a new llElt_t + */ +static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData) +{ + DEFiRet; + llElt_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->pKey = pKey; + pThis->pData = pData; + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* append a user element to the end of the linked list. This includes setting a key. If no + * key is desired, simply pass in a NULL pointer for it. + */ +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData) +{ + llElt_t *pElt; + DEFiRet; + + CHKiRet(llEltConstruct(&pElt, pKey, pData)); + + pThis->iNumElts++; /* one more */ + if(pThis->pLast == NULL) { + pThis->pRoot = pElt; + } else { + pThis->pLast->pNext = pElt; + } + pThis->pLast = pElt; + +finalize_it: + RETiRet; +} + + +/* unlink a requested element. As we have singly-linked lists, the + * caller also needs to pass in the previous element (or NULL, if it is the + * root element). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + assert(pElt != NULL); + + if(pEltPrev == NULL) { /* root element? */ + pThis->pRoot = pElt->pNext; + } else { /* regular element */ + pEltPrev->pNext = pElt->pNext; + } + + if(pElt == pThis->pLast) + pThis->pLast = pEltPrev; + + return RS_RET_OK; +} + + +/* unlinks and immediately deletes an element. Previous element must + * be given (or zero if the root element is to be deleted). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + DEFiRet; + + assert(pElt != NULL); + + CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev)); + CHKiRet(llDestroyElt(pThis, pElt)); + +finalize_it: + RETiRet; +} + +/* find a user element based on the provided key - this is the + * internal variant, which also tracks the last element pointer + * before the found element. This is necessary to delete elements. + * NULL means there is no element in front of it, aka the found elt + * is the root elt. + * rgerhards, 2007-11-21 + */ +static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev = NULL; + int bFound = 0; + + assert(pThis != NULL); + assert(pKey != NULL); + assert(ppElt != NULL); + assert(ppEltPrev != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL && bFound == 0) { + if(pThis->cmpOp(pKey, pElt->pKey) == 0) + bFound = 1; + else { + pEltPrev = pElt; + pElt = pElt->pNext; + } + } + + if(bFound == 1) { + *ppElt = pElt; + *ppEltPrev = pEltPrev; + } else + iRet = RS_RET_NOT_FOUND; + + RETiRet; +} + + +/* find a user element based on the provided key + */ +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found the element */ + *ppData = pElt->pData; + +finalize_it: + RETiRet; +} + + +/* find a delete an element based on user-provided key. The element is + * delete, the caller does not receive anything. If we need to receive + * the element before destruction, we may implement an llFindAndUnlink() + * at that time. + * rgerhards, 2007-11-21 + */ +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found an element */ + CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev)); + +finalize_it: + RETiRet; +} + + +/* provide the count of linked list elements + */ +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt) +{ + DEFiRet; + + assert(pThis != NULL); + assert(piCnt != NULL); + + *piCnt = pThis->iNumElts; + + RETiRet; +} + + +/* execute a function on all list members. The functions receives a + * user-supplied parameter, which may be either a simple value + * or a pointer to a structure with more data. If the user-supplied + * function does not return RS_RET_OK, this function here terminates. + * rgerhards, 2007-08-02 + * rgerhards, 2007-11-21: added functionality to delete a list element. + * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element + * is deleted. + */ +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + DEFiRet; + rsRetVal iRetLL; + void *pData; + linkedListCookie_t llCookie = NULL; + linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */ + + assert(pThis != NULL); + assert(pFunc != NULL); + + while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) { + iRet = pFunc(pData, pParam); + if(iRet == RS_RET_OK_DELETE_LISTENTRY) { + /* delete element */ + CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev)); + /* we need to revert back, as we have just deleted the current element. + * So the actual current element is the one before it, which happens to be + * stored in llCookiePrev. -- rgerhards, 2007-11-21 + */ + llCookie = llCookiePrev; + } else if (iRet != RS_RET_OK) { + goto finalize_it; + } + llCookiePrev = llCookie; + } + + if(iRetLL != RS_RET_END_OF_LINKEDLIST) + iRet = iRetLL; + +finalize_it: + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/linkedlist.h b/runtime/linkedlist.h new file mode 100644 index 00000000..aeacd6d7 --- /dev/null +++ b/runtime/linkedlist.h @@ -0,0 +1,73 @@ +/* Definition of the linkedlist object. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef LINKEDLIST_H_INCLUDED +#define LINKEDLIST_H_INCLUDED + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct llElt_s { /* config file sysline parse entry */ + struct llElt_s *pNext; + void *pKey; /* key for this element */ + void *pData; /* user-supplied data pointer */ +}; +typedef struct llElt_s llElt_t; + + +/* this is the list of known configuration commands with pointers to + * their handlers. + * The short name is cslc (Configfile SysLine Command) + */ +struct linkedList_s { /* config file sysline parse entry */ + int iNumElts; /* number of elements in list */ + rsRetVal (*pEltDestruct)(void*pData); /* destructor for user pointer in llElt_t's */ + rsRetVal (*pKeyDestruct)(void*pKey); /* destructor for key pointer in llElt_t's */ + int (*cmpOp)(void*, void*); /* pointer to key compare operation function, retval like strcmp */ + void *pKey; /* the list key (searchable, if set) */ + llElt_t *pRoot; /* list root */ + llElt_t *pLast; /* list tail */ +}; +typedef struct linkedList_s linkedList_t; + +typedef llElt_t* linkedListCookie_t; /* this type avoids exposing internals and keeps us flexible */ + +/* prototypes */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()); +rsRetVal llDestroy(linkedList_t *pThis); +rsRetVal llDestroyRootElt(linkedList_t *pThis); +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr); +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData); +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData); +rsRetVal llGetKey(llElt_t *pThis, void *ppData); +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt); +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam); +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey); +/* use the macro below to define a function that will be executed by + * llExecFunc() + */ +#define DEFFUNC_llExecFunc(funcName)\ + static rsRetVal funcName(void __attribute__((unused)) *pData, void __attribute__((unused)) *pParam) + +#endif /* #ifndef LINKEDLIST_H_INCLUDED */ diff --git a/runtime/module-template.h b/runtime/module-template.h new file mode 100644 index 00000000..eb39b587 --- /dev/null +++ b/runtime/module-template.h @@ -0,0 +1,486 @@ +/* module-template.h + * This header contains macros that can be used to implement the + * plumbing of modules. + * + * File begun on 2007-07-25 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef MODULE_TEMPLATE_H_INCLUDED +#define MODULE_TEMPLATE_H_INCLUDED 1 + +#include "modules.h" +#include "obj.h" +#include "objomsr.h" +#include "threads.h" + +/* macro to define standard output-module static data members + */ +#define DEF_MOD_STATIC_DATA \ + static __attribute__((unused)) rsRetVal (*omsdRegCFSLineHdlr)(); + +#define DEF_OMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_IMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_LMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA + + +/* Macro to define the module type. Each module can only have a single type. If + * a module provides multiple types, several separate modules must be created which + * then should share a single library containing the majority of code. This macro + * must be present in each module. -- rgerhards, 2007-12-14 + * Note that MODULE_TYPE_TESTBENCH is reserved for testbenches, but + * declared in their own header files (because the rest does not need these + * defines). -- rgerhards, 2008-06-13 + */ +#define MODULE_TYPE(x)\ +static rsRetVal modGetType(eModType_t *modType) \ + { \ + *modType = x; \ + return RS_RET_OK;\ + } + +#define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN) +#define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT) +#define MODULE_TYPE_LIB \ + DEF_LMOD_STATIC_DATA \ + MODULE_TYPE(eMOD_LIB) + + +/* macro to define a unique module id. This must be able to fit in a void*. The + * module id must be unique inside a running rsyslogd application. It is used to + * track ownership of several objects. Most importantly, when the module is + * unloaded the module id value is used to find what needs to be destroyed. + * We currently use a pointer to modExit() as the module id. This sounds to be + * reasonable save, as each module must have this entry point AND there is no valid + * reason for twice this entry point being in memory. + * rgerhards, 2007-11-21 + */ +#define STD_LOADABLE_MODULE_ID ((void*) modExit) + + +/* macro to implement the "modGetID()" interface function + * rgerhards 2007-11-21 + */ +#define DEFmodGetID \ +static rsRetVal modGetID(void **pID) \ + { \ + *pID = STD_LOADABLE_MODULE_ID;\ + return RS_RET_OK;\ + } + +/* to following macros are used to generate function headers and standard + * functionality. It works as follows (described on the sample case of + * createInstance()): + * + * BEGINcreateInstance + * ... custom variable definitions (on stack) ... (if any) + * CODESTARTcreateInstance + * ... custom code ... (if any) + * ENDcreateInstance + */ + +/* createInstance() + */ +#define BEGINcreateInstance \ +static rsRetVal createInstance(instanceData **ppData)\ + {\ + DEFiRet; /* store error code here */\ + instanceData *pData; /* use this to point to data elements */ + +#define CODESTARTcreateInstance \ + if((pData = calloc(1, sizeof(instanceData))) == NULL) {\ + *ppData = NULL;\ + ENDfunc \ + return RS_RET_OUT_OF_MEMORY;\ + } + +#define ENDcreateInstance \ + *ppData = pData;\ + RETiRet;\ +} + +/* freeInstance() + * This is the cleanup function for the module instance. It is called immediately before + * the module instance is destroyed (unloaded). The module should do any cleanup + * here, e.g. close file, free instantance heap memory and the like. Control will + * not be passed back to the module once this function is finished. Keep in mind, + * however, that other instances may still be loaded and used. So do not destroy + * anything that may be used by another instance. If you have such a ressource, you + * currently need to do the instance counting yourself. + */ +#define BEGINfreeInstance \ +static rsRetVal freeInstance(void* pModData)\ +{\ + DEFiRet;\ + instanceData *pData; + +#define CODESTARTfreeInstance \ + pData = (instanceData*) pModData; + +#define ENDfreeInstance \ + if(pData != NULL)\ + free(pData); /* we need to free this in any case */\ + RETiRet;\ +} + +/* isCompatibleWithFeature() + */ +#define BEGINisCompatibleWithFeature \ +static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\ +{\ + rsRetVal iRet = RS_RET_INCOMPATIBLE; \ + BEGINfunc + +#define CODESTARTisCompatibleWithFeature + +#define ENDisCompatibleWithFeature \ + RETiRet;\ +} + +/* doAction() + */ +#define BEGINdoAction \ +static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __attribute__((unused)) iMsgOpts, instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTdoAction \ + /* ppString may be NULL if the output module requested no strings */ + +#define ENDdoAction \ + RETiRet;\ +} + + +/* dbgPrintInstInfo() + * Extra comments: + * Print debug information about this instance. + */ +#define BEGINdbgPrintInstInfo \ +static rsRetVal dbgPrintInstInfo(void *pModData)\ +{\ + DEFiRet;\ + instanceData *pData = NULL; + +#define CODESTARTdbgPrintInstInfo \ + pData = (instanceData*) pModData; + +#define ENDdbgPrintInstInfo \ + RETiRet;\ +} + + +/* parseSelectorAct() + * Extra comments: + * try to process a selector action line. Checks if the action + * applies to this module and, if so, processed it. If not, it + * is left untouched. The driver will then call another module. + * On exit, ppModData must point to instance data. Also, a string + * request object must be created and filled. A macro is defined + * for that. + * For the most usual case, we have defined a macro below. + * If more than one string is requested, the macro can be used together + * with own code that overwrites the entry count. In this case, the + * macro must come before the own code. It is recommended to be + * placed right after CODESTARTparseSelectorAct. + */ +#define BEGINparseSelectorAct \ +static rsRetVal parseSelectorAct(uchar **pp, void **ppModData, omodStringRequest_t **ppOMSR)\ +{\ + DEFiRet;\ + uchar *p;\ + instanceData *pData = NULL; + +#define CODESTARTparseSelectorAct \ + assert(pp != NULL);\ + assert(ppModData != NULL);\ + assert(ppOMSR != NULL);\ + p = *pp; + +#define CODE_STD_STRING_REQUESTparseSelectorAct(NumStrReqEntries) \ + CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries)); + +#define CODE_STD_FINALIZERparseSelectorAct \ +finalize_it:\ + if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {\ + *ppModData = pData;\ + *pp = p;\ + } else {\ + /* cleanup, we failed */\ + if(*ppOMSR != NULL) {\ + OMSRdestruct(*ppOMSR);\ + *ppOMSR = NULL;\ + }\ + if(pData != NULL) {\ + freeInstance(pData);\ + } \ + } + +#define ENDparseSelectorAct \ + RETiRet;\ +} + + +/* tryResume() + * This entry point is called to check if a module can resume operations. This + * happens when a module requested that it be suspended. In suspended state, + * the engine periodically tries to resume the module. If that succeeds, normal + * processing continues. If not, the module will not be called unless a + * tryResume() call succeeds. + * Returns RS_RET_OK, if resumption succeeded, RS_RET_SUSPENDED otherwise + * rgerhard, 2007-08-02 + */ +#define BEGINtryResume \ +static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTtryResume \ + assert(pData != NULL); + +#define ENDtryResume \ + RETiRet;\ +} + + + +/* queryEtryPt() + */ +#define BEGINqueryEtryPt \ +DEFmodGetID \ +static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ +{\ + DEFiRet; + +#define CODESTARTqueryEtryPt \ + if((name == NULL) || (pEtryPoint == NULL)) {\ + ENDfunc \ + return RS_RET_PARAM_ERROR;\ + } \ + *pEtryPoint = NULL; + +#define ENDqueryEtryPt \ + if(iRet == RS_RET_OK)\ + if(*pEtryPoint == NULL) { \ + dbgprintf("entry point '%s' not present in module\n", name); \ + iRet = RS_RET_MODULE_ENTRY_POINT_NOT_FOUND;\ + } \ + RETiRet;\ +} + +/* the following definition is the standard block for queryEtryPt for all types + * of modules. It should be included in any module, and typically is so by calling + * the module-type specific macros. + */ +#define CODEqueryEtryPt_STD_MOD_QUERIES \ + if(!strcmp((char*) name, "modExit")) {\ + *pEtryPoint = modExit;\ + } else if(!strcmp((char*) name, "modGetID")) {\ + *pEtryPoint = modGetID;\ + } else if(!strcmp((char*) name, "getType")) {\ + *pEtryPoint = modGetType;\ + } + +/* the following definition is the standard block for queryEtryPt for output + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_OMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "doAction")) {\ + *pEtryPoint = doAction;\ + } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\ + *pEtryPoint = dbgPrintInstInfo;\ + } else if(!strcmp((char*) name, "freeInstance")) {\ + *pEtryPoint = freeInstance;\ + } else if(!strcmp((char*) name, "parseSelectorAct")) {\ + *pEtryPoint = parseSelectorAct;\ + } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ + *pEtryPoint = isCompatibleWithFeature;\ + } else if(!strcmp((char*) name, "tryResume")) {\ + *pEtryPoint = tryResume;\ + } + +/* the following definition is the standard block for queryEtryPt for INPUT + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_IMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "runInput")) {\ + *pEtryPoint = runInput;\ + } else if(!strcmp((char*) name, "willRun")) {\ + *pEtryPoint = willRun;\ + } else if(!strcmp((char*) name, "afterRun")) {\ + *pEtryPoint = afterRun;\ + } + +/* the following definition is the standard block for queryEtryPt for LIBRARY + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_LIB_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES + +/* modInit() + * This has an extra parameter, which is the specific name of the modInit + * function. That is needed for built-in modules, which must have unique + * names in order to link statically. Please note that this is always only + * the case with modInit() and NO other entry point. The reason is that only + * modInit() is visible form a linker/loader point of view. All other entry + * points are passed via rsyslog-internal query functions and are defined + * static inside the modules source. This is an important concept, as it allows + * us to support different interface versions within a single module. (Granted, + * we do not currently have different interface versions, so we can not put + * it to a test - but our firm believe is that we can do all abstraction needed...) + * + * Extra Comments: + * initialize the module + * + * Later, much more must be done. So far, we only return a pointer + * to the queryEtryPt() function + * TODO: do interface version checking & handshaking + * iIfVersRequetsed is the version of the interface specification that the + * caller would like to see being used. ipIFVersProvided is what we + * decide to provide. + * rgerhards, 2007-11-21: see modExit() comment below for important information + * on the need to initialize static data with code. modInit() may be called on a + * cached, left-in-memory copy of a previous incarnation. + */ +#define BEGINmodInit(uniqName) \ +rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\ +{\ + DEFiRet; \ + rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf); + +#define CODESTARTmodInit \ + assert(pHostQueryEtryPt != NULL);\ + iRet = pHostQueryEtryPt((uchar*)"objGetObjInterface", &pObjGetObjInterface); \ + if((iRet != RS_RET_OK) || (pQueryEtryPt == NULL) || (ipIFVersProvided == NULL) || (pObjGetObjInterface == NULL)) { \ + ENDfunc \ + return (iRet == RS_RET_OK) ? RS_RET_PARAM_ERROR : iRet; \ + } \ + /* now get the obj interface so that we can access other objects */ \ + CHKiRet(pObjGetObjInterface(&obj)); + +#define ENDmodInit \ +finalize_it:\ + *pQueryEtryPt = queryEtryPt;\ + RETiRet;\ +} + + +/* definitions for host API queries */ +#define CODEmodInit_QueryRegCFSLineHdlr \ + CHKiRet(pHostQueryEtryPt((uchar*)"regCfSysLineHdlr", &omsdRegCFSLineHdlr)); + +#endif /* #ifndef MODULE_TEMPLATE_H_INCLUDED */ + +/* modExit() + * This is the counterpart to modInit(). It destroys a module and makes it ready for + * unloading. It is similiar to freeInstance() for the instance data. Please note that + * this entry point needs to free any module-global data structures and registrations. + * For example, the CfSysLineHandlers a module has registered need to be unregistered + * here. This entry point is only called immediately before unloading of the module. So + * it is likely to be destroyed. HOWEVER, the caller may decide to keep the module cached. + * So a module must never assume that it is actually destroyed. A call to modInit() may + * happen immediately after modExit(). So a module can NOT assume that static data elements + * are being re-initialized by the loader - this must always be done by module code itself. + * It is suggested to do this in modInit(). - rgerhards, 2007-11-21 + */ +#define BEGINmodExit \ +static rsRetVal modExit(void)\ +{\ + DEFiRet; + +#define CODESTARTmodExit + +#define ENDmodExit \ + RETiRet;\ +} + + +/* runInput() + * This is the main function for input modules. It is used to gather data from the + * input source and submit it to the message queue. Each runInput() instance has its own + * thread. This is handled by the rsyslog engine. It needs to spawn off new threads only + * if there is a module-internal need to do so. + */ +#define BEGINrunInput \ +static rsRetVal runInput(thrdInfo_t __attribute__((unused)) *pThrd)\ +{\ + DEFiRet; + +#define CODESTARTrunInput \ + dbgSetThrdName((uchar*)__FILE__); /* we need to provide something better later */ + +#define ENDrunInput \ + RETiRet;\ +} + + +/* willRun() + * This is a function that will be replaced in the longer term. It is used so + * that a module can tell the caller if it will run or not. This is to be replaced + * when we introduce input module instances. However, these require config syntax + * changes and I may (or may not... ;)) hold that until another config file + * format is available. -- rgerhards, 2007-12-17 + * returns RS_RET_NO_RUN if it will not run (RS_RET_OK or error otherwise) + */ +#define BEGINwillRun \ +static rsRetVal willRun(void)\ +{\ + DEFiRet; + +#define CODESTARTwillRun + +#define ENDwillRun \ + RETiRet;\ +} + + +/* afterRun() + * This function is called after an input module has been run and its thread has + * been terminated. It shall do any necessary cleanup. + * This is expected to evolve into a freeInstance type of call once the input module + * interface evolves to support multiple instances. + * rgerhards, 2007-12-17 + */ +#define BEGINafterRun \ +static rsRetVal afterRun(void)\ +{\ + DEFiRet; + +#define CODESTARTafterRun + +#define ENDafterRun \ + RETiRet;\ +} + + +/* + * vi:set ai: + */ diff --git a/runtime/modules.c b/runtime/modules.c new file mode 100644 index 00000000..ceb4768c --- /dev/null +++ b/runtime/modules.c @@ -0,0 +1,809 @@ +/* modules.c + * This is the implementation of syslogd modules object. + * 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 file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" 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> +#ifdef OS_BSD +# include "libgen.h" +#endif + +#include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */ + +#include <unistd.h> +#include <sys/file.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 */ + +/* 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 + */ +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) +{ + assert(pThis != NULL); + if(pThis->pszName != NULL) + free(pThis->pszName); + if(pThis->pModHdlr != NULL) { +# ifdef VALGRIND +# warning "dlclose disabled for valgrind" +# else + dlclose(pThis->pModHdlr); +# endif + } + + free(pThis); +} + + +/* 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 + */ +static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) +{ + DEFiRet; + + if((name == NULL) || (pEtryPoint == NULL)) + return RS_RET_PARAM_ERROR; + + 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); + } + +finalize_it: + RETiRet; +} + + +/* get the name of a module + */ +static uchar *modGetName(modInfo_t *pThis) +{ + return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); +} + + +/* 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 + */ +static uchar *modGetStateName(modInfo_t *pThis) +{ + return(modGetName(pThis)); +} + + +/* 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 */ + pThis->pPrev = pLoadedModulesLast; + 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 + */ +static modInfo_t *GetNxt(modInfo_t *pThis) +{ + modInfo_t *pNew; + + if(pThis == NULL) + pNew = pLoadedModules; + else + pNew = pThis->pNext; + + return(pNew); +} + + +/* 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 + */ +static modInfo_t *GetNxtType(modInfo_t *pThis, eModType_t rqtdType) +{ + modInfo_t *pMod = pThis; + + do { + pMod = GetNxt(pMod); + } while(!(pMod == NULL || pMod->eType == rqtdType)); /* warning: do ... while() */ + + return pMod; +} + + +/* 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). + * rgerhards, 2008-03-10: reject unload request if the module has a reference + * count > 0. + */ +static rsRetVal +modPrepareUnload(modInfo_t *pThis) +{ + DEFiRet; + void *pModCookie; + + assert(pThis != NULL); + + 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)); + +finalize_it: + RETiRet; +} + + +/* Add an already-loaded module to the module linked list. This function does + * everything needed to fully initialize the module. + */ +static rsRetVal +doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), uchar *name, void *pModHdlr) +{ + DEFiRet; + modInfo_t *pNew = NULL; + rsRetVal (*modGetType)(eModType_t *pType); + + assert(modInit != NULL); + + if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) { + pNew = NULL; + ABORT_FINALIZE(iRet); + } + + CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); + + 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. First we load the interfaces common to all + * module types. + */ + 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; + /* 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: + 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! + */ +static void modPrintList(void) +{ + modInfo_t *pMod; + + pMod = GetNxt(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_LIB: + dbgprintf("library"); + 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 = GetNxt(pMod); /* done, go next */ + } +} + + +/* 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 + */ +static rsRetVal +modUnlinkAndDestroy(modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + 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; + } + + 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; +} + + +/* 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 *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); + } else { + /* Note: if the module was successfully unloaded, it has updated the + * pModCurr pointer to the next module. However, the unload process may + * still have indirectly referenced the pointer list in a way that the + * unloaded module is not aware of. So we restart the unload process + * to make sure we do not fall into a trap (what we did ;)). The + * performance toll is minimal. -- rgerhards, 2008-04-28 + */ + pModCurr = GetNxt(NULL); + } + } else { + pModCurr = GetNxt(pModCurr); + } + } + +# ifdef DEBUG + /* DEV DEBUG only! + if(pLoadedModules != NULL) { + dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); + modUsrPrintAll(); + } + */ +# endif + + RETiRet; +} + + +/* 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); + } + + /* 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(0, RS_RET_MODULE_LOAD_ERR_PATHLEN, "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(0, RS_RET_MODULE_LOAD_ERR_PATHLEN, "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(0, RS_RET_MODULE_LOAD_ERR_DLOPEN, "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(0, RS_RET_MODULE_LOAD_ERR_NO_INIT, "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(0, RS_RET_MODULE_LOAD_ERR_INIT_FAILED, "could not load module '%s', rsyslog error %d\n", szPath, iRet); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); + } + +finalize_it: + RETiRet; +} + + +/* 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: + */ diff --git a/runtime/modules.h b/runtime/modules.h new file mode 100644 index 00000000..7d34bcf7 --- /dev/null +++ b/runtime/modules.h @@ -0,0 +1,150 @@ +/* modules.h + * + * Definition for build-in and plug-ins module handler. This file is the base + * for all dynamically loadable module support. In theory, in v3 all modules + * are dynamically loaded, in practice we currently do have a few build-in + * once. This may become removed. + * + * The loader keeps track of what is loaded. For library modules, it is also + * used to find objects (libraries) and to obtain the queryInterface function + * for them. A reference count is maintened for libraries, so that they are + * unloaded only when nobody still accesses them. + * + * File begun on 2007-07-22 by RGerhards + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef MODULES_H_INCLUDED +#define MODULES_H_INCLUDED 1 + +#include "objomsr.h" + + +/* the following define defines the current version of the module interface. + * It can be used by any module which want's to simply prevent version conflicts + * and does not intend to do specific old-version emulations. + * rgerhards, 2008-03-04 + * version 3 adds modInfo_t ptr to call of modInit -- rgerhards, 2008-03-10 + * version 4 removes needUDPSocket OM callback -- rgerhards, 2008-03-22 + */ +#define CURR_MOD_IF_VERSION 4 + +typedef enum eModType_ { + eMOD_IN, /* input module */ + eMOD_OUT, /* output module */ + eMOD_LIB /* library module - this module provides one or many interfaces */ +} eModType_t; + + +#ifdef DEBUG +typedef struct modUsr_s { + struct modUsr_s *pNext; + char *pszFile; +} modUsr_t; +#endif + + +/* how is this module linked? */ +typedef enum eModLinkType_ { + eMOD_LINK_STATIC, + eMOD_LINK_DYNAMIC_UNLOADED, /* dynalink module, currently not loaded */ + eMOD_LINK_DYNAMIC_LOADED, /* dynalink module, currently loaded */ + eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */ +} eModLinkType_t; + +typedef struct modInfo_s { + struct modInfo_s *pPrev; /* support for creating a double linked module list */ + struct modInfo_s *pNext; /* support for creating a linked module list */ + int iIFVers; /* Interface version of module */ + eModType_t eType; /* type of this module */ + eModLinkType_t eLinkType; + uchar* pszName; /* printable module name, e.g. for dbgprintf */ + unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */ + /* functions supported by all types of modules */ + rsRetVal (*modInit)(int, int*, rsRetVal(**)()); /* initialize the module */ + /* be sure to support version handshake! */ + rsRetVal (*modQueryEtryPt)(uchar *name, rsRetVal (**EtryPoint)()); /* query entry point addresses */ + rsRetVal (*isCompatibleWithFeature)(syslogFeature); + rsRetVal (*freeInstance)(void*);/* called before termination or module unload */ + rsRetVal (*dbgPrintInstInfo)(void*);/* called before termination or module unload */ + rsRetVal (*tryResume)(void*);/* called to see if module actin can be resumed now */ + rsRetVal (*modExit)(void); /* called before termination or module unload */ + rsRetVal (*modGetID)(void **); /* get its unique ID from module */ + /* below: parse a configuration line - return if processed + * or not. If not, must be parsed to next module. + */ + rsRetVal (*parseConfigLine)(uchar **pConfLine); + /* below: create an instance of this module. Most importantly the module + * can allocate instance memory in this call. + */ + rsRetVal (*createInstance)(); + /* TODO: pass pointer to msg submit function to IM rger, 2007-12-14 */ + union { + struct {/* data for input modules */ + rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */ + rsRetVal (*willRun)(void); /* function to gather input and submit to queue */ + rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */ + } im; + struct {/* data for output modules */ + /* below: perform the configured action + */ + rsRetVal (*doAction)(uchar**, unsigned, void*); + rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**); + } om; + struct { /* data for library modules */ + } fm; + } mod; + void *pModHdlr; /* handler to the dynamic library holding the module */ +# 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 + */ + modUsr_t *pModUsrRoot; +# endif +} modInfo_t; + +/* interfaces */ +BEGINinterface(module) /* name must also be changed in ENDinterface macro! */ + modInfo_t *(*GetNxt)(modInfo_t *pThis); + modInfo_t *(*GetNxtType)(modInfo_t *pThis, eModType_t rqtdType); + uchar *(*GetName)(modInfo_t *pThis); + uchar *(*GetStateName)(modInfo_t *pThis); + rsRetVal (*Use)(char *srcFile, modInfo_t *pThis); /**< must be called before a module is used (ref counting) */ + rsRetVal (*Release)(char *srcFile, modInfo_t **ppThis); /**< release a module (ref counting) */ + void (*PrintList)(void); + rsRetVal (*UnloadAndDestructAll)(eModLinkType_t modLinkTypesToUnload); + rsRetVal (*doModInit)(rsRetVal (*modInit)(), uchar *name, void *pModHdlr); + rsRetVal (*Load)(uchar *name); + rsRetVal (*SetModDir)(uchar *name); +ENDinterface(module) +#define moduleCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(module); + +/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ +extern uchar *pModDir; /* read-only after startup */ + + +#endif /* #ifndef MODULES_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/runtime/msg.c b/runtime/msg.c new file mode 100644 index 00000000..fdeae077 --- /dev/null +++ b/runtime/msg.c @@ -0,0 +1,2443 @@ +/* msg.c + * The msg object. Implementation of all msg-related functions + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#define SYSLOG_NAMES +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include "rsyslog.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "template.h" +#include "msg.h" +#include "var.h" +#include "datetime.h" +#include "regexp.h" +#include "atomic.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) +DEFobjCurrIf(datetime) +DEFobjCurrIf(regexp) + +static syslogCODE rs_prioritynames[] = + { + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "err", LOG_ERR }, + { "error", LOG_ERR }, /* DEPRECATED */ + { "info", LOG_INFO }, + { "none", INTERNAL_NOPRI }, /* INTERNAL */ + { "notice", LOG_NOTICE }, + { "panic", LOG_EMERG }, /* DEPRECATED */ + { "warn", LOG_WARNING }, /* DEPRECATED */ + { "warning", LOG_WARNING }, + { NULL, -1 } + }; + +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif +static syslogCODE rs_facilitynames[] = + { + { "auth", LOG_AUTH }, + { "authpriv", LOG_AUTHPRIV }, + { "cron", LOG_CRON }, + { "daemon", LOG_DAEMON }, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif + { "kern", LOG_KERN }, + { "lpr", LOG_LPR }, + { "mail", LOG_MAIL }, + { "news", LOG_NEWS }, + { "security", LOG_AUTH }, /* DEPRECATED */ + { "syslog", LOG_SYSLOG }, + { "user", LOG_USER }, + { "uucp", LOG_UUCP }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL, -1 } + }; + +/* some forward declarations */ +static int getAPPNAMELen(msg_t *pM); + +/* The following functions will support advanced output module + * multithreading, once this is implemented. Currently, we + * include them as hooks only. The idea is that we need to guard + * some msg objects data fields against concurrent access if + * we run on multiple threads. Please note that in any case this + * is not necessary for calls from INPUT modules, because they + * construct the message object and do this serially. Only when + * the message is in the processing queue, multiple threads may + * access a single object. Consequently, there are no guard functions + * for "set" methods, as these are called during input. Only "get" + * functions that modify important structures have them. + * rgerhards, 2007-07-20 + * We now support locked and non-locked operations, depending on + * the configuration of rsyslog. To support this, we use function + * pointers. Initially, we start in non-locked mode. There, all + * locking operations call into dummy functions. When locking is + * enabled, the function pointers are changed to functions doing + * actual work. We also introduced another MsgPrepareEnqueue() function + * which initializes the locking structures, if needed. This is + * necessary because internal messages during config file startup + * processing are always created in non-locking mode. So we can + * not initialize locking structures during constructions. We now + * postpone this until when the message is fully constructed and + * enqueued. Then we know the status of locking. This has a nice + * side effect, and that is that during the initial creation of + * the Msg object no locking needs to be done, which results in better + * performance. -- rgerhards, 2008-01-05 + */ +static void (*funcLock)(msg_t *pMsg); +static void (*funcUnlock)(msg_t *pMsg); +static void (*funcDeleteMutex)(msg_t *pMsg); +void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#if 1 /* This is a debug aid */ +#define MsgLock(pMsg) funcLock(pMsg) +#define MsgUnlock(pMsg) funcUnlock(pMsg) +#else +#define MsgLock(pMsg) {dbgprintf("line %d\n - ", __LINE__); funcLock(pMsg);; } +#define MsgUnlock(pMsg) {dbgprintf("line %d - ", __LINE__); funcUnlock(pMsg); } +#endif + +/* the next function is a dummy to be used by the looking functions + * when the class is not yet running in an environment where locking + * is necessary. Please note that the need to lock can (and will) change + * during a single run. Typically, this is depending on the operation mode + * of the message queues (which is operator-configurable). -- rgerhards, 2008-01-05 + */ +static void MsgLockingDummy(msg_t __attribute__((unused)) *pMsg) +{ + /* empty be design */ +} + + +/* The following function prepares a message for enqueue into the queue. This is + * where a message may be accessed by multiple threads. This implementation here + * is the version for multiple concurrent acces. It initializes the locking + * structures. + * TODO: change to an iRet interface! -- rgerhards, 2008-07-14 + */ +static void MsgPrepareEnqueueLockingCase(msg_t *pThis) +{ + int iErr; + BEGINfunc + assert(pThis != NULL); + iErr = pthread_mutexattr_init(&pThis->mutAttr); + if(iErr != 0) { + dbgprintf("error initializing mutex attribute in %s:%d, trying to continue\n", + __FILE__, __LINE__); + } + iErr = pthread_mutexattr_settype(&pThis->mutAttr, PTHREAD_MUTEX_RECURSIVE); + if(iErr != 0) { + dbgprintf("ERROR setting mutex attribute to recursive in %s:%d, trying to continue " + "but we will probably either abort or hang soon\n", + __FILE__, __LINE__); + /* TODO: it makes very little sense to continue here, + * but it requires an iRet interface to gracefully shut + * down. We should do that over time. -- rgerhards, 2008-07-14 + */ + } + pthread_mutex_init(&pThis->mut, &pThis->mutAttr); + + /* we do no longer need the attribute. According to the + * POSIX spec, we can destroy it without affecting the + * initialized mutex (that used the attribute). + * rgerhards, 2008-07-14 + */ + pthread_mutexattr_destroy(&pThis->mutAttr); + ENDfunc +} + + +/* ... and now the locking and unlocking implementations: */ +static void MsgLockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_lock(&pThis->mut); +} + +static void MsgUnlockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_unlock(&pThis->mut); +} + +/* delete the mutex object on message destruction (locking case) + */ +static void MsgDeleteMutexLockingCase(msg_t *pThis) +{ + assert(pThis != NULL); + pthread_mutex_destroy(&pThis->mut); +} + +/* enable multiple concurrent access on the message object + * This works on a class-wide basis and can bot be undone. + * That is, if it is once enabled, it can not be disabled during + * the same run. When this function is called, no other thread + * must manipulate message objects. Then we would have race conditions, + * but guarding against this is counter-productive because it + * would cost additional time. Plus, it would be a programming error. + * rgerhards, 2008-01-05 + */ +rsRetVal MsgEnableThreadSafety(void) +{ + DEFiRet; + funcLock = MsgLockLockingCase; + funcUnlock = MsgUnlockLockingCase; + funcMsgPrepareEnqueue = MsgPrepareEnqueueLockingCase; + funcDeleteMutex = MsgDeleteMutexLockingCase; + RETiRet; +} + +/* end locking functions */ + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". + */ +rsRetVal msgConstruct(msg_t **ppThis) +{ + DEFiRet; + msg_t *pM; + + assert(ppThis != NULL); + if((pM = calloc(1, sizeof(msg_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* initialize members that are non-zero */ + pM->iRefCount = 1; + pM->iSeverity = -1; + pM->iFacility = -1; + datetime.getCurrTime(&(pM->tRcvdAt)); + objConstructSetObjInfo(pM); + + /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/ + + *ppThis = pM; + +finalize_it: + RETiRet; +} + + +BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */ + int currRefCount; +CODESTARTobjDestruct(msg) + /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pM, pM->iRefCount - 1); */ +# ifdef DO_HAVE_ATOMICS + currRefCount = ATOMIC_DEC_AND_FETCH(pThis->iRefCount); +# else + currRefCount = --pThis->iRefCount; +# endif + if(currRefCount == 0) + { + /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */ + if(pThis->pszUxTradMsg != NULL) + free(pThis->pszUxTradMsg); + if(pThis->pszRawMsg != NULL) + free(pThis->pszRawMsg); + if(pThis->pszTAG != NULL) + free(pThis->pszTAG); + if(pThis->pszHOSTNAME != NULL) + free(pThis->pszHOSTNAME); + if(pThis->pszRcvFrom != NULL) + free(pThis->pszRcvFrom); + if(pThis->pszRcvFromIP != NULL) + free(pThis->pszRcvFromIP); + if(pThis->pszMSG != NULL) + free(pThis->pszMSG); + if(pThis->pszFacility != NULL) + free(pThis->pszFacility); + if(pThis->pszFacilityStr != NULL) + free(pThis->pszFacilityStr); + if(pThis->pszSeverity != NULL) + free(pThis->pszSeverity); + if(pThis->pszSeverityStr != NULL) + free(pThis->pszSeverityStr); + if(pThis->pszRcvdAt3164 != NULL) + free(pThis->pszRcvdAt3164); + if(pThis->pszRcvdAt3339 != NULL) + free(pThis->pszRcvdAt3339); + if(pThis->pszRcvdAt_SecFrac != NULL) + free(pThis->pszRcvdAt_SecFrac); + if(pThis->pszRcvdAt_MySQL != NULL) + free(pThis->pszRcvdAt_MySQL); + if(pThis->pszRcvdAt_PgSQL != NULL) + free(pThis->pszRcvdAt_PgSQL); + if(pThis->pszTIMESTAMP3164 != NULL) + free(pThis->pszTIMESTAMP3164); + if(pThis->pszTIMESTAMP3339 != NULL) + free(pThis->pszTIMESTAMP3339); + if(pThis->pszTIMESTAMP_SecFrac != NULL) + free(pThis->pszTIMESTAMP_SecFrac); + if(pThis->pszTIMESTAMP_MySQL != NULL) + free(pThis->pszTIMESTAMP_MySQL); + if(pThis->pszTIMESTAMP_PgSQL != NULL) + free(pThis->pszTIMESTAMP_PgSQL); + if(pThis->pszPRI != NULL) + free(pThis->pszPRI); + if(pThis->pCSProgName != NULL) + rsCStrDestruct(&pThis->pCSProgName); + if(pThis->pCSStrucData != NULL) + rsCStrDestruct(&pThis->pCSStrucData); + if(pThis->pCSAPPNAME != NULL) + rsCStrDestruct(&pThis->pCSAPPNAME); + if(pThis->pCSPROCID != NULL) + rsCStrDestruct(&pThis->pCSPROCID); + if(pThis->pCSMSGID != NULL) + rsCStrDestruct(&pThis->pCSMSGID); + funcDeleteMutex(pThis); + } else { + pThis = NULL; /* tell framework not to destructing the object! */ + } +ENDobjDestruct(msg) + + +/* The macros below are used in MsgDup(). I use macros + * to keep the fuction code somewhat more readyble. It is my + * replacement for inline functions in CPP + */ +#define tmpCOPYSZ(name) \ + if(pOld->psz##name != NULL) { \ + if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + pNew->iLen##name = pOld->iLen##name;\ + } + +/* copy the CStr objects. + * if the old value is NULL, we do not need to do anything because we + * initialized the new value to NULL via calloc(). + */ +#define tmpCOPYCSTR(name) \ + if(pOld->pCS##name != NULL) {\ + if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + } +/* Constructs a message object by duplicating another one. + * Returns NULL if duplication failed. We do not need to lock the + * message object here, because a fully-created msg object is never + * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup() + * can never run into a situation where the message object is being + * modified while its content is copied - it's forbidden by definition. + * rgerhards, 2007-07-10 + */ +msg_t* MsgDup(msg_t* pOld) +{ + msg_t* pNew; + + assert(pOld != NULL); + + BEGINfunc + if(msgConstruct(&pNew) != RS_RET_OK) { + return NULL; + } + + /* now copy the message properties */ + pNew->iRefCount = 1; + pNew->iSeverity = pOld->iSeverity; + pNew->iFacility = pOld->iFacility; + pNew->bParseHOSTNAME = pOld->bParseHOSTNAME; + pNew->msgFlags = pOld->msgFlags; + pNew->iProtocolVersion = pOld->iProtocolVersion; + memcpy(&pNew->tRcvdAt, &pOld->tRcvdAt, sizeof(struct syslogTime)); + memcpy(&pNew->tTIMESTAMP, &pOld->tTIMESTAMP, sizeof(struct syslogTime)); + tmpCOPYSZ(Severity); + tmpCOPYSZ(SeverityStr); + tmpCOPYSZ(Facility); + tmpCOPYSZ(FacilityStr); + tmpCOPYSZ(PRI); + tmpCOPYSZ(RawMsg); + tmpCOPYSZ(MSG); + tmpCOPYSZ(UxTradMsg); + tmpCOPYSZ(TAG); + tmpCOPYSZ(HOSTNAME); + tmpCOPYSZ(RcvFrom); + + tmpCOPYCSTR(ProgName); + tmpCOPYCSTR(StrucData); + tmpCOPYCSTR(APPNAME); + tmpCOPYCSTR(PROCID); + tmpCOPYCSTR(MSGID); + + /* we do not copy all other cache properties, as we do not even know + * if they are needed once again. So we let them re-create if needed. + */ + + ENDfunc + return pNew; +} +#undef tmpCOPYSZ +#undef tmpCOPYCSTR + + +/* This method serializes a message object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object by calling MsgDeSerialize(). + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the cache properties. We re-create them when needed. + * This saves us a lot of memory. Performance is no concern, as serializing + * is a so slow operation that recration of the caches does not count. Also, + * we do not serialize bParseHOSTNAME, as this is only a helper variable + * during msg construction - and never again used later. + * rgerhards, 2008-01-03 + */ +static rsRetVal MsgSerialize(msg_t *pThis, strm_t *pStrm) +{ + DEFiRet; + + assert(pThis != NULL); + assert(pStrm != NULL); + + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + objSerializeSCALAR(pStrm, iProtocolVersion, SHORT); + objSerializeSCALAR(pStrm, iSeverity, SHORT); + objSerializeSCALAR(pStrm, iFacility, SHORT); + objSerializeSCALAR(pStrm, msgFlags, INT); + objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME); + objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); + + objSerializePTR(pStrm, pszRawMsg, PSZ); + objSerializePTR(pStrm, pszMSG, PSZ); + objSerializePTR(pStrm, pszUxTradMsg, PSZ); + objSerializePTR(pStrm, pszTAG, PSZ); + objSerializePTR(pStrm, pszHOSTNAME, PSZ); + objSerializePTR(pStrm, pszRcvFrom, PSZ); + objSerializePTR(pStrm, pszRcvFromIP, PSZ); + + objSerializePTR(pStrm, pCSStrucData, CSTR); + objSerializePTR(pStrm, pCSAPPNAME, CSTR); + objSerializePTR(pStrm, pCSPROCID, CSTR); + objSerializePTR(pStrm, pCSMSGID, CSTR); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + +/* Increment reference count - see description of the "msg" + * structure for details. As a convenience to developers, + * this method returns the msg pointer that is passed to it. + * It is recommended that it is called as follows: + * + * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer); + */ +msg_t *MsgAddRef(msg_t *pM) +{ + assert(pM != NULL); +# ifdef DO_HAVE_ATOMICS + ATOMIC_INC(pM->iRefCount); +# else + MsgLock(pM); + pM->iRefCount++; + MsgUnlock(pM); +# endif + /* DEV debugging only! dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);*/ + return(pM); +} + + +/* This functions tries to aquire the PROCID from TAG. Its primary use is + * when a legacy syslog message has been received and should be forwarded as + * syslog-protocol (or the PROCID is requested for any other reason). + * In legacy syslog, the PROCID is considered to be the character sequence + * between the first [ and the first ]. This usually are digits only, but we + * do not check that. However, if there is no closing ], we do not assume we + * can obtain a PROCID. Take in mind that not every legacy syslog message + * actually has a PROCID. + * rgerhards, 2005-11-24 + */ +static rsRetVal aquirePROCIDFromTAG(msg_t *pM) +{ + register int i; + DEFiRet; + + assert(pM != NULL); + if(pM->pCSPROCID != NULL) + return RS_RET_OK; /* we are already done ;) */ + + if(getProtocolVersion(pM) != 0) + return RS_RET_OK; /* we can only emulate if we have legacy format */ + + /* find first '['... */ + i = 0; + while((i < pM->iLenTAG) && (pM->pszTAG[i] != '[')) + ++i; + if(!(i < pM->iLenTAG)) + return RS_RET_OK; /* no [, so can not emulate... */ + + ++i; /* skip '[' */ + + /* now obtain the PROCID string... */ + CHKiRet(rsCStrConstruct(&pM->pCSPROCID)); + rsCStrSetAllocIncrement(pM->pCSPROCID, 16); + while((i < pM->iLenTAG) && (pM->pszTAG[i] != ']')) { + CHKiRet(rsCStrAppendChar(pM->pCSPROCID, pM->pszTAG[i])); + ++i; + } + + if(!(i < pM->iLenTAG)) { + /* oops... it looked like we had a PROCID, but now it has + * turned out this is not true. In this case, we need to free + * the buffer and simply return. Note that this is NOT an error + * case! + */ + rsCStrDestruct(&pM->pCSPROCID); + FINALIZE; + } + + /* OK, finaally we could obtain a PROCID. So let's use it ;) */ + CHKiRet(rsCStrFinish(pM->pCSPROCID)); + +finalize_it: + RETiRet; +} + + +/* Parse and set the "programname" for a given MSG object. Programname + * is a BSD concept, it is the tag without any instance-specific information. + * Precisely, the programname is terminated by either (whichever occurs first): + * - end of tag + * - nonprintable character + * - ':' + * - '[' + * - '/' + * The above definition has been taken from the FreeBSD syslogd sources. + * + * The program name is not parsed by default, because it is infrequently-used. + * If it is needed, this function should be called first. It checks if it is + * already set and extracts it, if not. + * A message object must be provided, else a crash will occur. + * rgerhards, 2005-10-19 + */ +static rsRetVal aquireProgramName(msg_t *pM) +{ + DEFiRet; + register int i; + + assert(pM != NULL); + if(pM->pCSProgName == NULL) { + /* ok, we do not yet have it. So let's parse the TAG + * to obtain it. + */ + CHKiRet(rsCStrConstruct(&pM->pCSProgName)); + rsCStrSetAllocIncrement(pM->pCSProgName, 33); + for( i = 0 + ; (i < pM->iLenTAG) && isprint((int) pM->pszTAG[i]) + && (pM->pszTAG[i] != '\0') && (pM->pszTAG[i] != ':') + && (pM->pszTAG[i] != '[') && (pM->pszTAG[i] != '/') + ; ++i) { + CHKiRet(rsCStrAppendChar(pM->pCSProgName, pM->pszTAG[i])); + } + CHKiRet(rsCStrFinish(pM->pCSProgName)); + } +finalize_it: + RETiRet; +} + + +/* This function moves the HOSTNAME inside the message object to the + * TAG. It is a specialised function used to handle the condition when + * a message without HOSTNAME is being processed. The missing HOSTNAME + * is only detected at a later stage, during TAG processing, so that + * we already had set the HOSTNAME property and now need to move it to + * the TAG. Of course, we could do this via a couple of get/set methods, + * but it is far more efficient to do it via this specialised method. + * This is especially important as this can be a very common case, e.g. + * when BSD syslog is acting as a sender. + * rgerhards, 2005-11-10. + */ +void moveHOSTNAMEtoTAG(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pszTAG != NULL) + free(pM->pszTAG); + pM->pszTAG = pM->pszHOSTNAME; + pM->iLenTAG = pM->iLenHOSTNAME; + pM->pszHOSTNAME = NULL; + pM->iLenHOSTNAME = 0; +} + +/* Access methods - dumb & easy, not a comment for each ;) + */ +void setProtocolVersion(msg_t *pM, int iNewVersion) +{ + assert(pM != NULL); + if(iNewVersion != 0 && iNewVersion != 1) { + dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion); + iNewVersion = 0; + } + pM->iProtocolVersion = iNewVersion; +} + +int getProtocolVersion(msg_t *pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion); +} + +/* note: string is taken from constant pool, do NOT free */ +char *getProtocolVersionString(msg_t *pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion ? "1" : "0"); +} + +int getMSGLen(msg_t *pM) +{ + return((pM == NULL) ? 0 : pM->iLenMSG); +} + + +char *getRawMsg(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszRawMsg == NULL) + return ""; + else + return (char*)pM->pszRawMsg; +} + +char *getUxTradMsg(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszUxTradMsg == NULL) + return ""; + else + return (char*)pM->pszUxTradMsg; +} + +char *getMSG(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszMSG == NULL) + return ""; + else + return (char*)pM->pszMSG; +} + + +/* Get PRI value in text form */ +char *getPRI(msg_t *pM) +{ + int pri; + + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszPRI == NULL) { + /* OK, we need to construct it... we use a 5 byte buffer - as of + * RFC 3164, it can't be longer. Should it still be, snprintf will truncate... + * Note that we do not use the LOG_MAKEPRI macro. This macro + * is a simple add of the two values under FreeBSD 7. So we implement + * the logic in our own code. This is a change from a bug + * report. -- rgerhards, 2008-07-14 + */ + pri = pM->iFacility * 8 + pM->iSeverity; + if((pM->pszPRI = malloc(5)) == NULL) return ""; + pM->iLenPRI = snprintf((char*)pM->pszPRI, 5, "%d", pri); + } + MsgUnlock(pM); + + return (char*)pM->pszPRI; +} + + +/* Get PRI value as integer */ +int getPRIi(msg_t *pM) +{ + assert(pM != NULL); + return (pM->iFacility << 3) + (pM->iSeverity); +} + + +char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) +{ + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_MySQL == NULL) { + if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL, 15); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_PgSQL == NULL) { + if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL, 21); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_PgSQL); + case tplFmtRFC3164Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3339 == NULL) { + if((pM->pszTIMESTAMP3339 = malloc(33)) == NULL) { + MsgUnlock(pM); + return ""; /* TODO: check this: can it cause a free() of constant memory?) */ + } + datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339, 33); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3339); + case tplFmtSecFrac: + MsgLock(pM); + if(pM->pszTIMESTAMP_SecFrac == NULL) { + if((pM->pszTIMESTAMP_SecFrac = malloc(10)) == NULL) { + MsgUnlock(pM); + return ""; /* TODO: check this: can it cause a free() of constant memory?) */ + } + datetime.formatTimestampSecFrac(&pM->tTIMESTAMP, pM->pszTIMESTAMP_SecFrac, 10); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_SecFrac); + } + return "INVALID eFmt OPTION!"; +} + +char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) +{ + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_MySQL == NULL) { + if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL, 15); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_PgSQL == NULL) { + if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL, 21); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_PgSQL); + case tplFmtRFC3164Date: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszRcvdAt3339 == NULL) { + if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339, 33); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3339); + case tplFmtSecFrac: + MsgLock(pM); + if(pM->pszRcvdAt_SecFrac == NULL) { + if((pM->pszRcvdAt_SecFrac = malloc(10)) == NULL) { + MsgUnlock(pM); + return ""; /* TODO: check this: can it cause a free() of constant memory?) */ + } + datetime.formatTimestampSecFrac(&pM->tRcvdAt, pM->pszRcvdAt_SecFrac, 10); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_SecFrac); + } + return "INVALID eFmt OPTION!"; +} + + +char *getSeverity(msg_t *pM) +{ + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszSeverity == NULL) { + /* we use a 2 byte buffer - can only be one digit */ + if((pM->pszSeverity = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverity = + snprintf((char*)pM->pszSeverity, 2, "%d", pM->iSeverity); + } + MsgUnlock(pM); + return((char*)pM->pszSeverity); +} + + +char *getSeverityStr(msg_t *pM) +{ + syslogCODE *c; + int val; + char *name = NULL; + + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszSeverityStr == NULL) { + for(c = rs_prioritynames, val = pM->iSeverity; c->c_name; c++) + if(c->c_val == val) { + name = c->c_name; + break; + } + if(name == NULL) { + /* we use a 2 byte buffer - can only be one digit */ + if((pM->pszSeverityStr = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverityStr = + snprintf((char*)pM->pszSeverityStr, 2, "%d", pM->iSeverity); + } else { + if((pM->pszSeverityStr = (uchar*) strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverityStr = strlen((char*)name); + } + } + MsgUnlock(pM); + return((char*)pM->pszSeverityStr); +} + +char *getFacility(msg_t *pM) +{ + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszFacility == NULL) { + /* we use a 12 byte buffer - as of + * syslog-protocol, facility can go + * up to 2^32 -1 + */ + if((pM->pszFacility = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacility = + snprintf((char*)pM->pszFacility, 12, "%d", pM->iFacility); + } + MsgUnlock(pM); + return((char*)pM->pszFacility); +} + +char *getFacilityStr(msg_t *pM) +{ + syslogCODE *c; + int val; + char *name = NULL; + + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszFacilityStr == NULL) { + for(c = rs_facilitynames, val = pM->iFacility << 3; c->c_name; c++) + if(c->c_val == val) { + name = c->c_name; + break; + } + if(name == NULL) { + /* we use a 12 byte buffer - as of + * syslog-protocol, facility can go + * up to 2^32 -1 + */ + if((pM->pszFacilityStr = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacilityStr = + snprintf((char*)pM->pszFacilityStr, 12, "%d", val >> 3); + } else { + if((pM->pszFacilityStr = (uchar*)strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacilityStr = strlen((char*)name); + } + } + MsgUnlock(pM); + return((char*)pM->pszFacilityStr); +} + + +/* set flow control state (if not called, the default - NO_DELAY - is used) + * This needs no locking because it is only done while the object is + * not fully constructed (which also means you must not call this + * method after the msg has been handed over to a queue). + * rgerhards, 2008-03-14 + */ +rsRetVal +MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl) +{ + DEFiRet; + assert(pMsg != NULL); + assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY); + + pMsg->flowCtlType = eFlowCtl; + + RETiRet; +} + + +/* rgerhards 2004-11-24: set APP-NAME in msg object + * TODO: revisit msg locking code! + */ +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME) +{ + DEFiRet; + assert(pMsg != NULL); + if(pMsg->pCSAPPNAME == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); + rsCStrSetAllocIncrement(pMsg->pCSAPPNAME, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); + +finalize_it: + RETiRet; +} + + +static void tryEmulateAPPNAME(msg_t *pM); /* forward reference */ +/* rgerhards, 2005-11-24 + */ +char *getAPPNAME(msg_t *pM) +{ + assert(pM != NULL); + MsgLock(pM); + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + MsgUnlock(pM); + return (pM->pCSAPPNAME == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); +} + + +/* rgerhards 2004-11-24: set PROCID in msg object + */ +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSPROCID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSPROCID)); + rsCStrSetAllocIncrement(pMsg->pCSPROCID, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID); + +finalize_it: + RETiRet; +} + +/* rgerhards, 2005-11-24 + */ +int getPROCIDLen(msg_t *pM) +{ + assert(pM != NULL); + MsgLock(pM); + if(pM->pCSPROCID == NULL) + aquirePROCIDFromTAG(pM); + MsgUnlock(pM); + return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); +} + + +/* rgerhards, 2005-11-24 + */ +char *getPROCID(msg_t *pM) +{ + char* pszRet; + + ISOBJ_TYPE_assert(pM, msg); + MsgLock(pM); + if(pM->pCSPROCID == NULL) + aquirePROCIDFromTAG(pM); + pszRet = (pM->pCSPROCID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSPROCID); + MsgUnlock(pM); + return pszRet; +} + + +/* rgerhards 2004-11-24: set MSGID in msg object + */ +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSMSGID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); + rsCStrSetAllocIncrement(pMsg->pCSMSGID, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); + +finalize_it: + RETiRet; +} + +/* rgerhards, 2005-11-24 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getMSGIDLen(msg_t *pM) +{ + return (pM->pCSMSGID == NULL) ? 1 : rsCStrLen(pM->pCSMSGID); +} +#endif + + +/* rgerhards, 2005-11-24 + */ +char *getMSGID(msg_t *pM) +{ + return (pM->pCSMSGID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID); +} + + +/* Set the TAG to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetTAG(). + * rgerhards 2004-11-19 + */ +void MsgAssignTAG(msg_t *pMsg, uchar *pBuf) +{ + assert(pMsg != NULL); + pMsg->iLenTAG = (pBuf == NULL) ? 0 : strlen((char*)pBuf); + pMsg->pszTAG = (uchar*) pBuf; +} + + +/* rgerhards 2004-11-16: set TAG in msg object + */ +void MsgSetTAG(msg_t *pMsg, char* pszTAG) +{ + assert(pMsg != NULL); + if(pMsg->pszTAG != NULL) + free(pMsg->pszTAG); + pMsg->iLenTAG = strlen(pszTAG); + if((pMsg->pszTAG = malloc(pMsg->iLenTAG + 1)) != NULL) + memcpy(pMsg->pszTAG, pszTAG, pMsg->iLenTAG + 1); + else + dbgprintf("Could not allocate memory in MsgSetTAG()\n"); +} + + +/* This function tries to emulate the TAG if none is + * set. Its primary purpose is to provide an old-style TAG + * when a syslog-protocol message has been received. Then, + * the tag is APP-NAME "[" PROCID "]". The function first checks + * if there is a TAG and, if not, if it can emulate it. + * rgerhards, 2005-11-24 + */ +static void tryEmulateTAG(msg_t *pM) +{ + int iTAGLen; + uchar *pBuf; + assert(pM != NULL); + + if(pM->pszTAG != NULL) + return; /* done, no need to emulate */ + + if(getProtocolVersion(pM) == 1) { + if(!strcmp(getPROCID(pM), "-")) { + /* no process ID, use APP-NAME only */ + MsgSetTAG(pM, getAPPNAME(pM)); + } else { + /* now we can try to emulate */ + iTAGLen = getAPPNAMELen(pM) + getPROCIDLen(pM) + 3; + if((pBuf = malloc(iTAGLen * sizeof(char))) == NULL) + return; /* nothing we can do */ + snprintf((char*)pBuf, iTAGLen, "%s[%s]", getAPPNAME(pM), getPROCID(pM)); + MsgAssignTAG(pM, pBuf); + } + } +} + + +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getTAGLen(msg_t *pM) +{ + if(pM == NULL) + return 0; + else { + tryEmulateTAG(pM); + if(pM->pszTAG == NULL) + return 0; + else + return pM->iLenTAG; + } +} +#endif + + +char *getTAG(msg_t *pM) +{ + char *ret; + + if(pM == NULL) + ret = ""; + else { + MsgLock(pM); + tryEmulateTAG(pM); + if(pM->pszTAG == NULL) + ret = ""; + else + ret = (char*) pM->pszTAG; + MsgUnlock(pM); + } + return(ret); +} + + +int getHOSTNAMELen(msg_t *pM) +{ + if(pM == NULL) + return 0; + else + if(pM->pszHOSTNAME == NULL) + return 0; + else + return pM->iLenHOSTNAME; +} + + +char *getHOSTNAME(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszHOSTNAME == NULL) + return ""; + else + return (char*) pM->pszHOSTNAME; +} + + +char *getRcvFrom(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszRcvFrom == NULL) + return ""; + else + return (char*) pM->pszRcvFrom; +} + + +uchar *getRcvFromIP(msg_t *pM) +{ + if(pM == NULL) + return (uchar*) ""; + else + if(pM->pszRcvFromIP == NULL) + return (uchar*) ""; + else + return pM->pszRcvFromIP; +} + +/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object + */ +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSStrucData == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData)); + rsCStrSetAllocIncrement(pMsg->pCSStrucData, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); + +finalize_it: + RETiRet; +} + +/* get the length of the "STRUCTURED-DATA" sz string + * rgerhards, 2005-11-24 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getStructuredDataLen(msg_t *pM) +{ + return (pM->pCSStrucData == NULL) ? 1 : rsCStrLen(pM->pCSStrucData); +} +#endif + + +/* get the "STRUCTURED-DATA" as sz string + * rgerhards, 2005-11-24 + */ +char *getStructuredData(msg_t *pM) +{ + return (pM->pCSStrucData == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSStrucData); +} + + + +/* get the length of the "programname" sz string + * rgerhards, 2005-10-19 + */ +int getProgramNameLen(msg_t *pM) +{ + int iRet; + + assert(pM != NULL); + MsgLock(pM); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramNameLen()\n", iRet); + MsgUnlock(pM); + return 0; /* best we can do (consistent wiht what getProgramName() returns) */ + } + MsgUnlock(pM); + + return (pM->pCSProgName == NULL) ? 0 : rsCStrLen(pM->pCSProgName); +} + + +/* get the "programname" as sz string + * rgerhards, 2005-10-19 + */ +char *getProgramName(msg_t *pM) /* this is the non-locking version for internal use */ +{ + int iRet; + char *pszRet; + + assert(pM != NULL); + MsgLock(pM); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); + pszRet = ""; /* best we can do */ + } else { + pszRet = (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); + } + + MsgUnlock(pM); + return pszRet; +} +/* The code below was an approach without PTHREAD_MUTEX_RECURSIVE + * However, it turned out to be quite complex. So far, we use recursive + * locking, which is OK from a performance point of view, especially as + * we do not anticipate that multithreading msg objects is used often. + * However, we may re-think about using non-recursive locking and I leave this + * code in here to conserve the idea. -- rgerhards, 2008-01-05 + */ +#if 0 +static char *getProgramNameNoLock(msg_t *pM) /* this is the non-locking version for internal use */ +{ + int iRet; + + assert(pM != NULL); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); + return ""; /* best we can do */ + } + + return (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); +} +char *getProgramName(msg_t *pM) /* this is the external callable version */ +{ + char *pszRet; + + MsgLock(pM); + pszRet = getProgramNameNoLock(pM); + MsgUnlock(pM); + return pszRet; +} +/* an alternative approach has been: */ +/* The macro below is used to generate external function definitions + * for such functions that may also be called internally (and thus have + * both a locking and non-locking implementation. Over time, we could + * reconsider how we handle that. -- rgerhards, 2008-01-05 + */ +#define EXT_LOCKED_FUNC(fName, ret) \ +ret fName(msg_t *pM) \ +{ \ + ret valRet; \ + MsgLock(pM); \ + valRet = fName##NoLock(pM); \ + MsgUnlock(pM); \ + return(valRet); \ +} +EXT_LOCKED_FUNC(getProgramName, char*) +/* in this approach, the external function is provided by the macro and + * needs not to be writen. + */ +#endif /* #if 0 -- saved code */ + + +/* This function tries to emulate APPNAME if it is not present. Its + * main use is when we have received a log record via legacy syslog and + * now would like to send out the same one via syslog-protocol. + */ +static void tryEmulateAPPNAME(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME != NULL) + return; /* we are already done */ + + if(getProtocolVersion(pM) == 0) { + /* only then it makes sense to emulate */ + MsgSetAPPNAME(pM, getProgramName(pM)); + } +} + + +/* rgerhards, 2005-11-24 + */ +static int getAPPNAMELen(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +} + + +/* rgerhards 2004-11-16: set pszRcvFrom in msg object + */ +void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom) +{ + assert(pMsg != NULL); + if(pMsg->pszRcvFrom != NULL) + free(pMsg->pszRcvFrom); + + pMsg->iLenRcvFrom = strlen(pszRcvFrom); + if((pMsg->pszRcvFrom = malloc(pMsg->iLenRcvFrom + 1)) != NULL) { + memcpy(pMsg->pszRcvFrom, pszRcvFrom, pMsg->iLenRcvFrom + 1); + } +} + + +/* rgerhards 2005-05-16: set pszRcvFromIP in msg object */ +rsRetVal +MsgSetRcvFromIP(msg_t *pMsg, uchar* pszRcvFromIP) +{ + DEFiRet; + assert(pMsg != NULL); + if(pMsg->pszRcvFromIP != NULL) { + free(pMsg->pszRcvFromIP); + pMsg->iLenRcvFromIP = 0; + } + + CHKmalloc(pMsg->pszRcvFromIP = (uchar*)strdup((char*)pszRcvFromIP)); + pMsg->iLenRcvFromIP = strlen((char*)pszRcvFromIP); +finalize_it: + RETiRet; +} + + +/* Set the HOSTNAME to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetHOSTNAME(). + * rgerhards 2004-11-19 + */ +void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf) +{ + assert(pMsg != NULL); + assert(pBuf != NULL); + pMsg->iLenHOSTNAME = strlen(pBuf); + pMsg->pszHOSTNAME = (uchar*) pBuf; +} + + +/* rgerhards 2004-11-09: set HOSTNAME in msg object + * rgerhards, 2007-06-21: + * Does not return anything. If an error occurs, the hostname is + * simply not set. I have changed this behaviour. The only problem + * we can run into is memory shortage. If we have such, it is better + * to loose the hostname than the full message. So we silently ignore + * that problem and hope that memory will be available the next time + * we need it. The rest of the code already knows how to handle an + * unset HOSTNAME. + */ +void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME) +{ + assert(pMsg != NULL); + if(pMsg->pszHOSTNAME != NULL) + free(pMsg->pszHOSTNAME); + + pMsg->iLenHOSTNAME = strlen(pszHOSTNAME); + if((pMsg->pszHOSTNAME = malloc(pMsg->iLenHOSTNAME + 1)) != NULL) + memcpy(pMsg->pszHOSTNAME, pszHOSTNAME, pMsg->iLenHOSTNAME + 1); + else + dbgprintf("Could not allocate memory in MsgSetHOSTNAME()\n"); +} + + +/* Set the UxTradMsg to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetUxTradMsg(). + * rgerhards 2004-11-19 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static void MsgAssignUxTradMsg(msg_t *pMsg, char *pBuf) +{ + assert(pMsg != NULL); + assert(pBuf != NULL); + pMsg->iLenUxTradMsg = strlen(pBuf); + pMsg->pszUxTradMsg = pBuf; +} +#endif + + +/* rgerhards 2004-11-17: set the traditional Unix message in msg object + */ +int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg) +{ + assert(pMsg != NULL); + assert(pszUxTradMsg != NULL); + pMsg->iLenUxTradMsg = strlen(pszUxTradMsg); + if(pMsg->pszUxTradMsg != NULL) + free(pMsg->pszUxTradMsg); + if((pMsg->pszUxTradMsg = malloc(pMsg->iLenUxTradMsg + 1)) != NULL) + memcpy(pMsg->pszUxTradMsg, pszUxTradMsg, pMsg->iLenUxTradMsg + 1); + else + dbgprintf("Could not allocate memory for pszUxTradMsg buffer."); + + return(0); +} + + +/* rgerhards 2004-11-09: set MSG in msg object + */ +void MsgSetMSG(msg_t *pMsg, char* pszMSG) +{ + assert(pMsg != NULL); + assert(pszMSG != NULL); + + if(pMsg->pszMSG != NULL) + free(pMsg->pszMSG); + + pMsg->iLenMSG = strlen(pszMSG); + if((pMsg->pszMSG = (uchar*) malloc(pMsg->iLenMSG + 1)) != NULL) + memcpy(pMsg->pszMSG, pszMSG, pMsg->iLenMSG + 1); + else + dbgprintf("MsgSetMSG could not allocate memory for pszMSG buffer."); +} + +/* rgerhards 2004-11-11: set RawMsg in msg object + */ +void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg) +{ + assert(pMsg != NULL); + if(pMsg->pszRawMsg != NULL) + free(pMsg->pszRawMsg); + + pMsg->iLenRawMsg = strlen(pszRawMsg); + if((pMsg->pszRawMsg = (uchar*) malloc(pMsg->iLenRawMsg + 1)) != NULL) + memcpy(pMsg->pszRawMsg, pszRawMsg, pMsg->iLenRawMsg + 1); + else + dbgprintf("Could not allocate memory for pszRawMsg buffer."); +} + + +/* Decode a priority into textual information like auth.emerg. + * The variable pRes must point to a user-supplied buffer and + * pResLen must contain its size. The pointer to the buffer + * is also returned, what makes this functiona suitable for + * use in printf-like functions. + * Note: a buffer size of 20 characters is always sufficient. + * Interface to this function changed 2007-06-15 by RGerhards + */ +char *textpri(char *pRes, size_t pResLen, int pri) +{ + syslogCODE *c_pri, *c_fac; + + assert(pRes != NULL); + assert(pResLen > 0); + + for (c_fac = rs_facilitynames; c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri)<<3); c_fac++); + for (c_pri = rs_prioritynames; c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++); + + snprintf (pRes, pResLen, "%s.%s<%d>", c_fac->c_name, c_pri->c_name, pri); + + return pRes; +} + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_HHOUR, NOW_QHOUR, NOW_MINUTE } eNOWType; +#define tmpBUFSIZE 16 /* size of formatting buffer */ +static uchar *getNOW(eNOWType eNow) +{ + uchar *pBuf; + struct syslogTime t; + + if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { + return NULL; + } + + datetime.getCurrTime(&t); + switch(eNow) { + case NOW_NOW: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d", t.year); + break; + case NOW_MONTH: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.month); + break; + case NOW_DAY: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.day); + break; + case NOW_HOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.hour); + break; + case NOW_HHOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 30); + break; + case NOW_QHOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 15); + break; + case NOW_MINUTE: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute); + break; + } + + return(pBuf); +} +#undef tmpBUFSIZE /* clean up */ + + +/* This function returns a string-representation of the + * requested message property. This is a generic function used + * to abstract properties so that these can be easier + * queried. Returns NULL if property could not be found. + * Actually, this function is a big if..elseif. What it does + * is simply to map property names (from MonitorWare) to the + * message object data fields. + * + * In case we need string forms of propertis we do not + * yet have in string form, we do a memory allocation that + * is sufficiently large (in all cases). Once the string + * form has been obtained, it is saved until the Msg object + * is finally destroyed. This is so that we save the processing + * time in the (likely) case that this property is requested + * again. It also saves us a lot of dynamic memory management + * issues in the upper layers, because we so can guarantee that + * the buffer will remain static AND available during the lifetime + * of the object. Please note that both the max size allocation as + * well as keeping things in memory might like look like a + * waste of memory (some might say it actually is...) - we + * deliberately accept this because performance is more important + * to us ;) + * rgerhards 2004-11-18 + * Parameter "bMustBeFreed" is set by this function. It tells the + * caller whether or not the string returned must be freed by the + * caller itself. It is is 0, the caller MUST NOT free it. If it is + * 1, the caller MUST free 1. Handling this wrongly leads to either + * a memory leak of a program abort (do to double-frees or frees on + * the constant memory pool). So be careful to do it right. + * rgerhards 2004-11-23 + * regular expression support contributed by Andres Riancho merged + * on 2005-09-13 + * changed so that it now an be called without a template entry (NULL). + * In this case, only the (unmodified) property is returned. This will + * be used in selector line processing. + * rgerhards 2005-09-15 + */ +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + cstr_t *pCSPropName, unsigned short *pbMustBeFreed) +{ + uchar *pName; + char *pRes; /* result pointer */ + char *pBufStart; + char *pBuf; + int iLen; + short iOffs; + +#ifdef FEATURE_REGEXP + /* Variables necessary for regular expression matching */ + size_t nmatch = 10; + regmatch_t pmatch[10]; +#endif + + assert(pMsg != NULL); + assert(pbMustBeFreed != NULL); + + if(pCSPropName == NULL) { + assert(pTpe != NULL); + pName = pTpe->data.field.pPropRepl; + } else { + pName = rsCStrGetSzStrNoNULL(pCSPropName); + } + *pbMustBeFreed = 0; + + /* sometimes there are aliases to the original MonitoWare + * property names. These come after || in the ifs below. */ + if(!strcmp((char*) pName, "msg")) { + pRes = getMSG(pMsg); + } else if(!strcmp((char*) pName, "rawmsg")) { + pRes = getRawMsg(pMsg); + } else if(!strcmp((char*) pName, "uxtradmsg")) { + pRes = getUxTradMsg(pMsg); + } else if(!strcmp((char*) pName, "fromhost")) { + pRes = getRcvFrom(pMsg); + } else if(!strcmp((char*) pName, "fromhost-ip")) { + pRes = (char*) getRcvFromIP(pMsg); + } else if(!strcmp((char*) pName, "source") || !strcmp((char*) pName, "hostname")) { + pRes = getHOSTNAME(pMsg); + } else if(!strcmp((char*) pName, "syslogtag")) { + pRes = getTAG(pMsg); + } else if(!strcmp((char*) pName, "pri")) { + pRes = getPRI(pMsg); + } else if(!strcmp((char*) pName, "pri-text")) { + pBuf = malloc(20 * sizeof(char)); + if(pBuf == NULL) { + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } else { + *pbMustBeFreed = 1; + pRes = textpri(pBuf, 20, getPRIi(pMsg)); + } + } else if(!strcmp((char*) pName, "iut")) { + pRes = "1"; /* always 1 for syslog messages (a MonitorWare thing;)) */ + } else if(!strcmp((char*) pName, "syslogfacility")) { + pRes = getFacility(pMsg); + } else if(!strcmp((char*) pName, "syslogfacility-text")) { + pRes = getFacilityStr(pMsg); + } else if(!strcmp((char*) pName, "syslogseverity") || !strcmp((char*) pName, "syslogpriority")) { + pRes = getSeverity(pMsg); + } else if(!strcmp((char*) pName, "syslogseverity-text") || !strcmp((char*) pName, "syslogpriority-text")) { + pRes = getSeverityStr(pMsg); + } else if(!strcmp((char*) pName, "timegenerated")) { + pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp((char*) pName, "timereported") + || !strcmp((char*) pName, "timestamp")) { + pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp((char*) pName, "programname")) { + pRes = getProgramName(pMsg); + } else if(!strcmp((char*) pName, "protocol-version")) { + pRes = getProtocolVersionString(pMsg); + } else if(!strcmp((char*) pName, "structured-data")) { + pRes = getStructuredData(pMsg); + } else if(!strcmp((char*) pName, "app-name")) { + pRes = getAPPNAME(pMsg); + } else if(!strcmp((char*) pName, "procid")) { + pRes = getPROCID(pMsg); + } else if(!strcmp((char*) pName, "msgid")) { + pRes = getMSGID(pMsg); + /* here start system properties (those, that do not relate to the message itself */ + } else if(!strcmp((char*) pName, "$now")) { + if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$year")) { + if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$month")) { + if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$day")) { + if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$hour")) { + if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$hhour")) { + if((pRes = (char*) getNOW(NOW_HHOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$qhour")) { + if((pRes = (char*) getNOW(NOW_QHOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$minute")) { + if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else { + /* there is no point in continuing, we may even otherwise render the + * error message unreadable. rgerhards, 2007-07-10 + */ + dbgprintf("invalid property name: '%s'\n", pName); + return "**INVALID PROPERTY NAME**"; + } + + /* If we did not receive a template pointer, we are already done... */ + if(pTpe == NULL) { + return pRes; + } + + /* Now check if we need to make "temporary" transformations (these + * are transformations that do not go back into the message - + * memory must be allocated for them!). + */ + + /* substring extraction */ + /* first we check if we need to extract by field number + * rgerhards, 2005-12-22 + */ + if(pTpe->data.field.has_fields == 1) { + size_t iCurrFld; + char *pFld; + char *pFldEnd; + /* first, skip to the field in question. The field separator + * is always one character and is stored in the template entry. + */ + iCurrFld = 1; + pFld = pRes; + while(*pFld && iCurrFld < pTpe->data.field.iToPos) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) + ++pFld; /* skip to field terminator */ + if(*pFld == pTpe->data.field.field_delim) { + ++pFld; /* eat it */ + ++iCurrFld; + } + } + dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, (int) iCurrFld); + + if(iCurrFld == pTpe->data.field.iToPos) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + /* TODO: code copied from below, this is a candidate for a separate function */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + /* now copy */ + memcpy(pBuf, pFld, iLen); + pBuf[iLen] = '\0'; /* terminate it */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + if(*(pFldEnd+1) != '\0') + ++pFldEnd; /* OK, skip again over delimiter char */ + } else { + /* field not found, return error */ + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**FIELD NOT FOUND**"; + } + } else if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + /* we need to obtain a private copy */ + int iFrom, iTo; + char *pSb; + iFrom = pTpe->data.field.iFromPos; + iTo = pTpe->data.field.iToPos; + /* need to zero-base to and from (they are 1-based!) */ + if(iFrom > 0) + --iFrom; + if(iTo > 0) + --iTo; + iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pSb = pRes; + if(iFrom) { + /* skip to the start of the substring (can't do pointer arithmetic + * because the whole string might be smaller!!) + */ + while(*pSb && iFrom) { + --iFrom; + ++pSb; + } + } + /* OK, we are at the begin - now let's copy... */ + while(*pSb && iLen) { + *pBuf++ = *pSb; + ++pSb; + --iLen; + } + *pBuf = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; +#ifdef FEATURE_REGEXP + } else { + /* Check for regular expressions */ + if (pTpe->data.field.has_regex != 0) { + if (pTpe->data.field.has_regex == 2) + /* Could not compile regex before! */ + return "**NO MATCH** **BAD REGULAR EXPRESSION**"; + + dbgprintf("string to match for regex is: %s\n", pRes); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + short iTry = 0; + uchar bFound = 0; + iOffs = 0; + /* first see if we find a match, iterating through the series of + * potential matches over the string. + */ + while(!bFound) { + if(regexp.regexec(&pTpe->data.field.re, pRes + iOffs, nmatch, pmatch, 0) == 0) { + if(pmatch[0].rm_so == -1) { + dbgprintf("oops ... start offset of successful regexec is -1\n"); + break; + } + if(iTry == pTpe->data.field.iMatchToUse) { + bFound = 1; + } else { + iOffs += pmatch[0].rm_eo; + ++iTry; + } + } else { + break; + } + } + if(!bFound) { + /* we got no match! */ + if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) + return "**NO MATCH**"; + else + return ""; + } + } else { + /* Match- but did it match the one we wanted? */ + /* we got no match! */ + if(pmatch[pTpe->data.field.iSubMatchToUse].rm_so == -1) { + if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) + return "**NO MATCH**"; + else + return ""; + } + } + /* OK, we have a usable match - we now need to malloc pB */ + int iLenBuf; + char *pB; + + iLenBuf = pmatch[pTpe->data.field.iSubMatchToUse].rm_eo + - pmatch[pTpe->data.field.iSubMatchToUse].rm_so; + pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY ALLOCATING pBuf**"; + } + + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + iOffs + pmatch[pTpe->data.field.iSubMatchToUse].rm_so, iLenBuf); + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ + + if (*pbMustBeFreed == 1) + free(pRes); + pRes = pB; + *pbMustBeFreed = 1; + } + } else { + /* we could not load regular expression support. This is quite unexpected at + * this stage of processing (after all, the config parser found it), but so + * it is. We return an error in that case. -- rgerhards, 2008-03-07 + */ + dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n"); + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + return "***REGEXP NOT AVAILABLE***"; + } + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + /* now check if we need to do our "SP if first char is non-space" hack logic */ + if(*pRes && pTpe->data.field.options.bSPIffNo1stSP) { + char *pB; + uchar cFirst = *pRes; + + /* here, we always destruct the buffer and return a new one */ + pB = (char *) malloc(2 * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pRes = pB; + *pbMustBeFreed = 1; + + if(cFirst == ' ') { + /* if we have a SP, we must return an empty string */ + *pRes = '\0'; /* empty */ + } else { + /* if it is no SP, we need to return one */ + *pRes = ' '; + *(pRes+1) = '\0'; + } + } + + if(*pRes) { + /* case conversations (should go after substring, because so we are able to + * work on the smallest possible buffer). + */ + if(pTpe->data.field.eCaseConv != tplCaseConvNo) { + /* we need to obtain a private copy */ + int iBufLen = strlen(pRes); + char *pBStart; + char *pB; + char *pSrc; + pBStart = pB = malloc((iBufLen + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pSrc = pRes; + while(*pSrc) { + *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? + (char)toupper((int)*pSrc) : (char)tolower((int)*pSrc); + /* currently only these two exist */ + ++pSrc; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + + /* now do control character dropping/escaping/replacement + * Only one of these can be used. If multiple options are given, the + * result is random (though currently there obviously is an order of + * preferrence, see code below. But this is NOT guaranteed. + * RGerhards, 2006-11-17 + * We must copy the strings if we modify them, because they may either + * point to static memory or may point into the message object, in which + * case we would actually modify the original property (which of course + * is wrong). + * This was found and fixed by varmojefkoj on 2007-09-11 + */ + if(pTpe->data.field.options.bDropCC) { + int iLenBuf = 0; + char *pSrc = pRes; + char *pDstStart; + char *pDst; + char bDropped = 0; + + while(*pSrc) { + if(!iscntrl((int) *pSrc++)) + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(!iscntrl((int) *pSrc)) + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bSpaceCC) { + char *pSrc; + char *pDstStart; + char *pDst; + + if(*pbMustBeFreed == 1) { + /* in this case, we already work on dynamic + * memory, so there is no need to copy it - we can + * modify it in-place without any harm. This is a + * performance optiomization. + */ + for(pDst = pRes; *pDst; pDst++) { + if(iscntrl((int) *pDst)) + *pDst = ' '; + } + } else { + pDst = pDstStart = malloc(strlen(pRes) + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(iscntrl((int) *pSrc)) + *pDst++ = ' '; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bEscapeCC) { + /* we must first count how many control charactes are + * present, because we need this to compute the new string + * buffer length. While doing so, we also compute the string + * length. + */ + int iNumCC = 0; + int iLenBuf = 0; + char *pB; + + for(pB = pRes ; *pB ; ++pB) { + ++iLenBuf; + if(iscntrl((int) *pB)) + ++iNumCC; + } + + if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ + /* OK, let's do the escaping... */ + char *pBStart; + char szCCEsc[8]; /* buffer for escape sequence */ + int i; + + iLenBuf += iNumCC * 4; + pBStart = pB = malloc((iLenBuf + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + while(*pRes) { + if(iscntrl((int) *pRes)) { + snprintf(szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); + for(i = 0 ; i < 4 ; ++i) + *pB++ = szCCEsc[i]; + } else { + *pB++ = *pRes; + } + ++pRes; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + } + } + + /* Take care of spurious characters to make the property safe + * for a path definition + */ + if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) { + if(pTpe->data.field.options.bSecPathDrop) { + int iLenBuf = 0; + char *pSrc = pRes; + char *pDstStart; + char *pDst; + char bDropped = 0; + + while(*pSrc) { + if(*pSrc++ != '/') + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc != '/') + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else { + char *pSrc; + char *pDstStart; + char *pDst; + + if(*pbMustBeFreed == 1) { + /* here, again, we can modify the string as we already obtained + * a private buffer. As we do not change the size of that buffer, + * in-place modification is possible. This is a performance + * enhancement. + */ + for(pDst = pRes; *pDst; pDst++) { + if(*pDst == '/') + *pDst++ = '_'; + } + } else { + pDst = pDstStart = malloc(strlen(pRes) + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc == '/') + *pDst++ = '_'; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + /* we must NOT check if it needs to be freed, because we have done + * this in the if above. So if we come to hear, the pSrc string needs + * not to be freed (and we do not need to care about it). + */ + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } + + /* check for "." and ".." (note the parenthesis in the if condition!) */ + if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) { + char *pTmp = pRes; + + if(*(pRes + 1) == '\0') + pRes = "_"; + else + pRes = "_.";; + if(*pbMustBeFreed == 1) + free(pTmp); + *pbMustBeFreed = 0; + } else if(*pRes == '\0') { + if(*pbMustBeFreed == 1) + free(pRes); + pRes = "_"; + *pbMustBeFreed = 0; + } + } + + /* Now drop last LF if present (pls note that this must not be done + * if bEscapeCC was set! + */ + if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { + int iLn = strlen(pRes); + char *pB; + if(iLn > 0 && *(pRes + iLn - 1) == '\n') { + /* we have a LF! */ + /* check if we need to obtain a private copy */ + if(*pbMustBeFreed == 0) { + /* ok, original copy, need a private one */ + pB = malloc((iLn + 1) * sizeof(char)); + if(pB == NULL) { + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + memcpy(pB, pRes, iLn - 1); + pRes = pB; + *pbMustBeFreed = 1; + } + *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ + } + } + + /*dbgprintf("MsgGetProp(\"%s\"): \"%s\"\n", pName, pRes); only for verbose debug logging */ + return(pRes); +} + + +/* The returns a message variable suitable for use with RainerScript. Most importantly, this means + * that the value is returned in a var_t object. The var_t is constructed inside this function and + * MUST be freed by the caller. + * rgerhards, 2008-02-25 + */ +rsRetVal +msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar) +{ + DEFiRet; + var_t *pVar; + uchar *pszProp = NULL; + cstr_t *pstrProp; + unsigned short bMustBeFreed = 0; + + ISOBJ_TYPE_assert(pThis, msg); + ASSERT(pstrPropName != NULL); + ASSERT(ppVar != NULL); + + /* make sure we have a var_t instance */ + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + /* always call MsgGetProp() without a template specifier */ + pszProp = (uchar*) MsgGetProp(pThis, NULL, pstrPropName, &bMustBeFreed); + + /* now create a string object out of it and hand that over to the var */ + CHKiRet(rsCStrConstructFromszStr(&pstrProp, pszProp)); + CHKiRet(var.SetString(pVar, pstrProp)); + + /* finally store var */ + *ppVar = pVar; + +finalize_it: + if(bMustBeFreed) + free(pszProp); + + RETiRet; +} + + +/* This function can be used as a generic way to set properties. + * We have to handle a lot of legacy, so our return value is not always + * 100% correct (called functions do not always provide one, should + * change over time). + * rgerhards, 2008-01-07 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal MsgSetProperty(msg_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, msg); + assert(pProp != NULL); + + if(isProp("iProtocolVersion")) { + setProtocolVersion(pThis, pProp->val.num); + } else if(isProp("iSeverity")) { + pThis->iSeverity = pProp->val.num; + } else if(isProp("iFacility")) { + pThis->iFacility = pProp->val.num; + } else if(isProp("msgFlags")) { + pThis->msgFlags = pProp->val.num; + } else if(isProp("pszRawMsg")) { + MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszMSG")) { + MsgSetMSG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszUxTradMsg")) { + MsgSetUxTradMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszTAG")) { + MsgSetTAG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszRcvFromIP")) { + MsgSetRcvFromIP(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszRcvFrom")) { + MsgSetRcvFrom(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszHOSTNAME")) { + MsgSetHOSTNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSStrucData")) { + MsgSetStructuredData(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSAPPNAME")) { + MsgSetAPPNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSPROCID")) { + MsgSetPROCID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSMSGID")) { + MsgSetMSGID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("tRcvdAt")) { + memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } else if(isProp("tTIMESTAMP")) { + memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } + + RETiRet; +} +#undef isProp + + +/* This is a construction finalizer that must be called after all properties + * have been set. It does some final work on the message object. After this + * is done, the object is considered ready for full processing. + * rgerhards, 2008-07-08 + */ +static rsRetVal msgConstructFinalizer(msg_t *pThis) +{ + MsgPrepareEnqueue(pThis); + return RS_RET_OK; +} + + +/* get the severity - this is an entry point that + * satisfies the base object class getSeverity semantics. + * rgerhards, 2008-01-14 + */ +static rsRetVal +MsgGetSeverity(obj_t *pThis, int *piSeverity) +{ + ISOBJ_TYPE_assert(pThis, msg); + assert(piSeverity != NULL); + *piSeverity = ((msg_t*) pThis)->iSeverity; + return RS_RET_OK; +} + + +/* dummy */ +rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the message class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-04 + */ +BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); + OBJSetMethodHandler(objMethod_SETPROPERTY, MsgSetProperty); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, msgConstructFinalizer); + OBJSetMethodHandler(objMethod_GETSEVERITY, MsgGetSeverity); + /* initially, we have no need to lock message objects */ + funcLock = MsgLockingDummy; + funcUnlock = MsgLockingDummy; + funcDeleteMutex = MsgLockingDummy; + funcMsgPrepareEnqueue = MsgLockingDummy; +ENDObjClassInit(msg) + +/* + * vi:set ai: + */ diff --git a/runtime/msg.h b/runtime/msg.h new file mode 100644 index 00000000..c428237a --- /dev/null +++ b/runtime/msg.h @@ -0,0 +1,182 @@ +/* msg.h + * Header file for all msg-related functions. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "template.h" /* this is a quirk, but these two are too interdependant... */ + +#ifndef MSG_H_INCLUDED +#define MSG_H_INCLUDED 1 + +#include <pthread.h> +#include "obj.h" +#include "syslogd-types.h" +#include "template.h" + +/* rgerhards 2004-11-08: The following structure represents a + * syslog message. + * + * Important Note: + * The message object is used for multiple purposes (once it + * has been created). Once created, it actully is a read-only + * object (though we do not specifically express this). In order + * to avoid multiple copies of the same object, we use a + * reference counter. This counter is set to 1 by the constructer + * and increased by 1 with a call to MsgAddRef(). The destructor + * checks the reference count. If it is more than 1, only the counter + * will be decremented. If it is 1, however, the object is actually + * destroyed. To make this work, it is vital that MsgAddRef() is + * called each time a "copy" is stored somewhere. + */ +struct msg { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + pthread_mutexattr_t mutAttr; + pthread_mutex_t mut; + int iRefCount; /* reference counter (0 = unused) */ + short bParseHOSTNAME; /* should the hostname be parsed from the message? */ + /* background: the hostname is not present on "regular" messages + * received via UNIX domain sockets from the same machine. However, + * it is available when we have a forwarder (e.g. rfc3195d) using local + * sockets. All in all, the parser would need parse templates, that would + * resolve all these issues... rgerhards, 2005-10-06 + */ + flowControl_t flowCtlType; /**< type of flow control we can apply, for enqueueing, needs not to be persisted because + once data has entered the queue, this property is no longer needed. */ + short iSeverity; /* the severity 0..7 */ + uchar *pszSeverity; /* severity as string... */ + int iLenSeverity; /* ... and its length. */ + uchar *pszSeverityStr; /* severity name... */ + int iLenSeverityStr; /* ... and its length. */ + short iFacility; /* Facility code 0 .. 23*/ + uchar *pszFacility; /* Facility as string... */ + int iLenFacility; /* ... and its length. */ + uchar *pszFacilityStr; /* facility name... */ + int iLenFacilityStr; /* ... and its length. */ + uchar *pszPRI; /* the PRI as a string */ + int iLenPRI; /* and its length */ + uchar *pszRawMsg; /* message as it was received on the + * wire. This is important in case we + * need to preserve cryptographic verifiers. + */ + int iLenRawMsg; /* length of raw message */ + uchar *pszMSG; /* the MSG part itself */ + int iLenMSG; /* Length of the MSG part */ + uchar *pszUxTradMsg; /* the traditional UNIX message */ + int iLenUxTradMsg;/* Length of the traditional UNIX message */ + uchar *pszTAG; /* pointer to tag value */ + int iLenTAG; /* Length of the TAG part */ + uchar *pszHOSTNAME; /* HOSTNAME from syslog message */ + int iLenHOSTNAME; /* Length of HOSTNAME */ + uchar *pszRcvFrom; /* System message was received from */ + int iLenRcvFrom; /* Length of pszRcvFrom */ + uchar *pszRcvFromIP; /* IP of system message was received from */ + int iLenRcvFromIP; /* Length of pszRcvFromIP */ + short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ + cstr_t *pCSProgName; /* the (BSD) program name */ + cstr_t *pCSStrucData;/* STRUCTURED-DATA */ + cstr_t *pCSAPPNAME; /* APP-NAME */ + cstr_t *pCSPROCID; /* PROCID */ + cstr_t *pCSMSGID; /* MSGID */ + struct syslogTime tRcvdAt;/* time the message entered this program */ + char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */ + char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */ + char *pszRcvdAt_SecFrac;/* time just as fractional seconds (6 charcters) */ + char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 charcters) */ + char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */ + struct syslogTime tTIMESTAMP;/* (parsed) value of the timestamp */ + char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 charcters) */ + char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 charcters at most) */ + char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 charcters) */ + char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */ + char *pszTIMESTAMP_SecFrac;/* TIMESTAMP fractional seconds (always 6 characters) */ + int msgFlags; /* flags associated with this message */ +}; + +/* function prototypes + */ +PROTOTYPEObjClassInit(msg); +char* getProgramName(msg_t*); +rsRetVal msgConstruct(msg_t **ppThis); +rsRetVal msgDestruct(msg_t **ppM); +msg_t* MsgDup(msg_t* pOld); +msg_t *MsgAddRef(msg_t *pM); +void setProtocolVersion(msg_t *pM, int iNewVersion); +int getProtocolVersion(msg_t *pM); +char *getProtocolVersionString(msg_t *pM); +int getMSGLen(msg_t *pM); +char *getRawMsg(msg_t *pM); +char *getUxTradMsg(msg_t *pM); +char *getMSG(msg_t *pM); +char *getPRI(msg_t *pM); +int getPRIi(msg_t *pM); +char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt); +char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt); +char *getSeverity(msg_t *pM); +char *getSeverityStr(msg_t *pM); +char *getFacility(msg_t *pM); +char *getFacilityStr(msg_t *pM); +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME); +char *getAPPNAME(msg_t *pM); +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID); +int getPROCIDLen(msg_t *pM); +char *getPROCID(msg_t *pM); +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID); +void MsgAssignTAG(msg_t *pMsg, uchar *pBuf); +void MsgSetTAG(msg_t *pMsg, char* pszTAG); +rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl); +char *getTAG(msg_t *pM); +int getHOSTNAMELen(msg_t *pM); +char *getHOSTNAME(msg_t *pM); +char *getRcvFrom(msg_t *pM); +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData); +char *getStructuredData(msg_t *pM); +int getProgramNameLen(msg_t *pM); +char *getProgramName(msg_t *pM); +void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom); +rsRetVal MsgSetRcvFromIP(msg_t *pMsg, uchar* pszRcvFromIP); +void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf); +void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME); +int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg); +void MsgSetMSG(msg_t *pMsg, char* pszMSG); +void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg); +void moveHOSTNAMEtoTAG(msg_t *pM); +char *getMSGID(msg_t *pM); +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + cstr_t *pCSPropName, unsigned short *pbMustBeFreed); +char *textpri(char *pRes, size_t pResLen, int pri); +rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar); +rsRetVal MsgEnableThreadSafety(void); + +/* The MsgPrepareEnqueue() function is a macro for performance reasons. + * It needs one global variable to work. This is acceptable, as it gains + * us quite some performance and is fully abstracted using this header file. + * The important thing is that no other module is permitted to actually + * access that global variable! -- rgerhards, 2008-01-05 + */ +extern void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#define MsgPrepareEnqueue(pMsg) funcMsgPrepareEnqueue(pMsg) + +#endif /* #ifndef MSG_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/runtime/net.c b/runtime/net.c new file mode 100644 index 00000000..f5b8f46a --- /dev/null +++ b/runtime/net.c @@ -0,0 +1,1487 @@ +/* net.c + * Implementation of network-related stuff. + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" networking code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Starting 2007-12-24, I have begun to shuffle more network-related code + * from syslogd.c to over here. I am not sure if it will stay here in the + * long term, but it is good to have it out of syslogd.c. Maybe this here is + * an interim location ;) + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * rgerhards, 2008-04-16: I changed this code to LGPL today. I carefully analyzed + * that it does not borrow code from the original sysklogd and that I have + * permission to do so from all other contributors. My analysis found that all + * code from sysklogd has been superseeded by our own functionality, so it + * is OK to move this file to LGPL. Some variable sysklogd variable names + * remain, but even this will change as the net object evolves. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> +#include <netdb.h> +#include <fnmatch.h> +#include <fcntl.h> +#include <unistd.h> + +#include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" +#include "net.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +/* support for defining allowed TCP and UDP senders. We use the same + * structure to implement this (a linked list), but we define two different + * list roots, one for UDP and one for TCP. + * rgerhards, 2005-09-26 + */ +/* All of the five below are read-only after startup */ +struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */ +struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */ +static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */ +static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */ +#ifdef USE_GSSAPI +struct AllowedSenders *pAllowedSenders_GSS = NULL; +static struct AllowedSenders *pLastAllowedSenders_GSS = NULL; +#endif + +int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */ +int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */ + + +/* ------------------------------ begin permitted peers code ------------------------------ */ + + +/* add a wildcard entry to this permitted peer. Entries are always + * added at the tail of the list. pszStr and lenStr identify the wildcard + * entry to be added. Note that the string is NOT \0 terminated, so + * we must rely on lenStr for when it is finished. + * rgerhards, 2008-05-27 + */ +static rsRetVal +AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr) +{ + permittedPeerWildcard_t *pNew = NULL; + size_t iSrc; + size_t iDst; + DEFiRet; + + assert(pPeer != NULL); + assert(pszStr != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); + + if(lenStr == 0) { /* empty domain components are permitted */ + pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT; + FINALIZE; + } else { + /* alloc memory for the domain component. We may waste a byte or + * two, but that's ok. + */ + CHKmalloc(pNew->pszDomainPart = malloc(lenStr +1 )); + } + + if(pszStr[0] == '*') { + pNew->wildcardType = PEER_WILDCARD_AT_START; + iSrc = 1; /* skip '*' */ + } else { + iSrc = 0; + } + + for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) { + pNew->pszDomainPart[iDst] = pszStr[iSrc]; + } + + if(iSrc < lenStr) { + if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') { + if(pNew->wildcardType == PEER_WILDCARD_AT_START) { + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } else { + pNew->wildcardType = PEER_WILDCARD_AT_END; + } + } else { + /* we have an invalid wildcard, something follows the asterisk! */ + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } + } + + if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) { + pNew->wildcardType = PEER_WILDCARD_MATCH_ALL; + } + + /* if we reach this point, we had a valid wildcard. We now need to + * properly terminate the domain component string. + */ + pNew->pszDomainPart[iDst] = '\0'; + pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) { + if(pNew->pszDomainPart != NULL) + free(pNew->pszDomainPart); + free(pNew); + } + } else { + /* enqueue the element */ + if(pPeer->pWildcardRoot == NULL) { + pPeer->pWildcardRoot = pNew; + } else { + pPeer->pWildcardLast->pNext = pNew; + } + pPeer->pWildcardLast = pNew; + } + + RETiRet; +} + + +/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */ +static rsRetVal +DestructPermittedPeerWildcards(permittedPeers_t *pPeer) +{ + permittedPeerWildcard_t *pCurr; + permittedPeerWildcard_t *pDel; + DEFiRet; + + assert(pPeer != NULL); + + for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + free(pDel->pszDomainPart); + free(pDel); + } + + pPeer->pWildcardRoot = NULL; + pPeer->pWildcardLast = NULL; + + RETiRet; +} + + +/* add a permitted peer. PermittedPeers is an interim solution until we can provide + * access control via enhanced RainerScript methods. + * Note: the provided string is handed over to this function, caller must + * no longer access it. -- rgerhards, 2008-05-19 + */ +static rsRetVal +AddPermittedPeer(permittedPeers_t **ppRootPeer, uchar* pszID) +{ + permittedPeers_t *pNew = NULL; + DEFiRet; + + assert(ppRootPeer != NULL); + assert(pszID != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); /* we use calloc() for consistency with "real" objects */ + CHKmalloc(pNew->pszID = (uchar*)strdup((char*)pszID)); + + if(*ppRootPeer != NULL) { + pNew->pNext = *ppRootPeer; + } + *ppRootPeer = pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + free(pNew); + } + RETiRet; +} + + +/* Destruct a permitted peers list -- rgerhards, 2008-05-19 */ +static rsRetVal +DestructPermittedPeers(permittedPeers_t **ppRootPeer) +{ + permittedPeers_t *pCurr; + permittedPeers_t *pDel; + DEFiRet; + + assert(ppRootPeer != NULL); + + for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + DestructPermittedPeerWildcards(pDel); + free(pDel->pszID); + free(pDel); + } + + *ppRootPeer = NULL; + + RETiRet; +} + + +/* Compile a wildcard. The function first checks if there is a wildcard + * present and compiles it only if so ;) It sets the etryType status + * accordingly. + * rgerhards, 2008-05-27 + */ +static rsRetVal +PermittedPeerWildcardCompile(permittedPeers_t *pPeer) +{ + uchar *pC; + uchar *pStart; + DEFiRet; + + assert(pPeer != NULL); + assert(pPeer->pszID != NULL); + + /* first check if we have a wildcard */ + for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC) + /*EMPTY, just skip*/; + + if(*pC == '\0') { + /* no wildcard found, we are mostly done */ + pPeer->etryType = PERM_PEER_TYPE_PLAIN; + FINALIZE; + } + + /* if we reach this point, the string contains wildcards. So let's + * compile the structure. To do so, we must parse from dot to dot + * and create a wildcard entry for each domain component we find. + * We must also flag problems if we have an asterisk in the middle + * of the text (it is supported at the start or end only). + */ + pPeer->etryType = PERM_PEER_TYPE_WILDCARD; + pC = pPeer->pszID; + while(*pC != '\0') { + pStart = pC; + /* find end of domain component */ + for( ; *pC != '\0' && *pC != '.' ; ++pC) + /*EMPTY, just skip*/; + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart)); + /* now check if we have an empty component at end of string */ + if(*pC == '.' && *(pC + 1) == '\0') { + /* pStart is a dummy, it is not used if length is 0 */ + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0)); + } + if(*pC != '\0') + ++pC; + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, iRet, "error compiling wildcard expression '%s'", + pPeer->pszID); + } + RETiRet; +} + + +/* Do a (potential) wildcard match. The function first checks if the wildcard + * has already been compiled and, if not, compiles it. If the peer entry in + * question does NOT contain a wildcard, a simple strcmp() is done. + * *pbIsMatching is set to 0 if there is no match and something else otherwise. + * rgerhards, 2008-05-27 */ +static rsRetVal +PermittedPeerWildcardMatch(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching) +{ + permittedPeerWildcard_t *pWildcard; + uchar *pC; + uchar *pStart; /* start of current domain component */ + size_t iWildcard, iName; /* work indexes for backward comparisons */ + DEFiRet; + + assert(pPeer != NULL); + assert(pszNameToMatch != NULL); + assert(pbIsMatching != NULL); + + if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) { + PermittedPeerWildcardCompile(pPeer); + } + + if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) { + *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch); + FINALIZE; + } + + /* we have a wildcard, so we need to extract the domain components and + * check then against the provided wildcards. + */ + pWildcard = pPeer->pWildcardRoot; + pC = pszNameToMatch; + while(*pC != '\0') { + if(pWildcard == NULL) { + /* we have more domain components than we have wildcards --> no match */ + *pbIsMatching = 0; + FINALIZE; + } + pStart = pC; + while(*pC != '\0' && *pC != '.') { + ++pC; + } + + /* got the component, now do the match */ + switch(pWildcard->wildcardType) { + case PEER_WILDCARD_NONE: + if( pWildcard->lenDomainPart != (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_AT_START: + /* we need to do the backwards-matching manually */ + if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart; + iWildcard = 0; + while(iWildcard < pWildcard->lenDomainPart) { + if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) { + *pbIsMatching = 0; + FINALIZE; + } + ++iName; + ++iWildcard; + } + break; + case PEER_WILDCARD_AT_END: + if( pWildcard->lenDomainPart > (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pWildcard->lenDomainPart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_MATCH_ALL: + /* everything is OK, just continue */ + break; + case PEER_WILDCARD_EMPTY_COMPONENT: + if(pC - pStart > 0) { + /* if it is not empty, it is no match... */ + *pbIsMatching = 0; + FINALIZE; + } + break; + } + pWildcard = pWildcard->pNext; /* we processed this entry */ + + /* skip '.' if we had it and so prepare for next iteration */ + if(*pC == '.') + ++pC; + } + + if(pWildcard != NULL) { + /* we have more domain components than in the name to be + * checked. So this is no match. + */ + *pbIsMatching = 0; + FINALIZE; + } + + *pbIsMatching = 1; /* finally... it matches ;) */ + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end permitted peers code ------------------------------ */ + + +/* Code for handling allowed/disallowed senders + */ +static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) { + register uint8_t i; + + assert (addr != NULL); + assert (bits <= 128); + + i = bits/32; + if (bits%32) + addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof addr->s6_addr32)/4; i++) + addr->s6_addr32[i] = 0; +} + +static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) { + + assert (addr != NULL); + assert (bits <=32 ); + + addr->s_addr &= htonl(0xffffffff << (32 - bits)); +} + +#define SIN(sa) ((struct sockaddr_in *)(sa)) +#define SIN6(sa) ((struct sockaddr_in6 *)(sa)) + +/* This function adds an allowed sender entry to the ACL linked list. + * In any case, a single entry is added. If an error occurs, the + * function does its error reporting itself. All validity checks + * must already have been done by the caller. + * This is a helper to AddAllowedSender(). + * rgerhards, 2007-07-17 + */ +static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + struct AllowedSenders *pEntry = NULL; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { + return RS_RET_OUT_OF_MEMORY; /* no options left :( */ + } + + memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr)); + pEntry->pNext = NULL; + pEntry->SignificantBits = iSignificantBits; + + /* enqueue */ + if(*ppRoot == NULL) { + *ppRoot = pEntry; + } else { + (*ppLast)->pNext = pEntry; + } + *ppLast = pEntry; + + return RS_RET_OK; +} + +/* function to clear the allowed sender structure in cases where + * it must be freed (occurs most often when HUPed. + * TODO: reconsider recursive implementation + * I think there is also a memory leak, because only the last entry + * is acutally deleted... -- rgerhards, 2007-12-25 + */ +void clearAllowedSenders (struct AllowedSenders *pAllow) +{ + if (pAllow != NULL) { + if (pAllow->pNext != NULL) + clearAllowedSenders (pAllow->pNext); + else { + if (F_ISSET(pAllow->allowedSender.flags, ADDR_NAME)) + free (pAllow->allowedSender.addr.HostWildcard); + else + free (pAllow->allowedSender.addr.NetAddr); + + free (pAllow); + } + } +} + +/* function to add an allowed sender to the allowed sender list. The + * root of the list is caller-provided, so it can be used for all + * supported lists. The caller must provide a pointer to the root, + * as it eventually needs to be updated. Also, a pointer to the + * pointer to the last element must be provided (to speed up adding + * list elements). + * rgerhards, 2005-09-26 + * If a hostname is given there are possible multiple entries + * added (all addresses from that host). + */ +static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + DEFiRet; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if (!F_ISSET(iAllow->flags, ADDR_NAME)) { + if(iSignificantBits == 0) + /* we handle this seperatly just to provide a better + * error message. + */ + errmsg.LogError(0, NO_ERRCODE, "You can not specify 0 bits of the netmask, this would " + "match ALL systems. If you really intend to do that, " + "remove all $AllowedSender directives."); + + switch (iAllow->addr.NetAddr->sa_family) { + case AF_INET: + if((iSignificantBits < 1) || (iSignificantBits > 32)) { + errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32", + (int)iSignificantBits); + iSignificantBits = 32; + } + + MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits); + break; + case AF_INET6: + if((iSignificantBits < 1) || (iSignificantBits > 128)) { + errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128", + iSignificantBits); + iSignificantBits = 128; + } + + MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits); + break; + default: + /* rgerhards, 2007-07-16: We have an internal program error in this + * case. However, there is not much we can do against it right now. Of + * course, we could abort, but that would probably cause more harm + * than good. So we continue to run. We simply do not add this line - the + * worst thing that happens is that one host will not be allowed to + * log. + */ + errmsg.LogError(0, NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d", + iAllow->addr.NetAddr->sa_family); + ABORT_FINALIZE(RS_RET_ERR); + } + /* OK, entry constructed, now lets add it to the ACL list */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } else { + /* we need to process a hostname ACL */ + if(glbl.GetDisableDNS()) { + errmsg.LogError(0, NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled."); + ABORT_FINALIZE(RS_RET_OK); + } + + if (!strchr (iAllow->addr.HostWildcard, '*') && + !strchr (iAllow->addr.HostWildcard, '?') && + ACLDontResolve == 0) { + /* single host - in this case, we pull its IP addresses from DNS + * and add IP-based ACLs. + */ + struct addrinfo hints, *res, *restmp; + struct NetAddr allowIP; + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; +# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */ + hints.ai_flags = AI_ADDRCONFIG; +# endif + + if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) { + errmsg.LogError(0, NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); + + if (ACLAddHostnameOnFail) { + errmsg.LogError(0, NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + FINALIZE; + } else { + errmsg.LogError(0, NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); + ABORT_FINALIZE(RS_RET_NOENTRY); + } + } + + for (restmp = res ; res != NULL ; res = res->ai_next) { + switch (res->ai_family) { + case AF_INET: /* add IPv4 */ + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) + != RS_RET_OK) + FINALIZE; + break; + case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */ + if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) { + /* extract & add IPv4 */ + + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(sizeof(struct sockaddr_in))) + == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in); +#endif + SIN(allowIP.addr.NetAddr)->sin_port = 0; + memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr), + &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]), + sizeof (struct sockaddr_in)); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } else { + /* finally add IPv6 */ + + iSignificantBits = 128; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } + break; + } + } + freeaddrinfo (restmp); + } else { + /* wildcards in hostname - we need to add a text-based ACL. + * For this, we already have everything ready and just need + * to pass it along... + */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } + } + +finalize_it: + RETiRet; +} + + +/* Print an allowed sender list. The caller must tell us which one. + * iListToPrint = 1 means UDP, 2 means TCP + * rgerhards, 2005-09-27 + */ +void PrintAllowedSenders(int iListToPrint) +{ + struct AllowedSenders *pSender; + uchar szIP[64]; + + assert((iListToPrint == 1) || (iListToPrint == 2) +#ifdef USE_GSSAPI + || (iListToPrint == 3) +#endif + ); + + dbgprintf("Allowed %s Senders:\n", + (iListToPrint == 1) ? "UDP" : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? "GSS" : +#endif + "TCP"); + + pSender = (iListToPrint == 1) ? pAllowedSenders_UDP : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? pAllowedSenders_GSS : +#endif + pAllowedSenders_TCP; + if(pSender == NULL) { + dbgprintf("\tNo restrictions set.\n"); + } else { + while(pSender != NULL) { + if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) + dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); + else { + if(getnameinfo (pSender->allowedSender.addr.NetAddr, + SALEN(pSender->allowedSender.addr.NetAddr), + (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { + dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits); + } else { + /* getnameinfo() failed - but as this is only a + * debug function, we simply spit out an error and do + * not care much about it. + */ + dbgprintf("\tERROR in getnameinfo() - something may be wrong " + "- ignored for now\n"); + } + } + pSender = pSender->pNext; + } + } +} + + +/* parse an allowed sender config line and add the allowed senders + * (if the line is correct). + * rgerhards, 2005-09-27 + */ +rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) +{ + struct AllowedSenders **ppRoot; + struct AllowedSenders **ppLast; + rsParsObj *pPars; + rsRetVal iRet; + struct NetAddr *uIP = NULL; + int iBits; + + assert(pName != NULL); + assert(ppRestOfConfLine != NULL); + assert(*ppRestOfConfLine != NULL); + + if(!strcasecmp(pName, "udp")) { + ppRoot = &pAllowedSenders_UDP; + ppLast = &pLastAllowedSenders_UDP; + } else if(!strcasecmp(pName, "tcp")) { + ppRoot = &pAllowedSenders_TCP; + ppLast = &pLastAllowedSenders_TCP; +#ifdef USE_GSSAPI + } else if(!strcasecmp(pName, "gss")) { + ppRoot = &pAllowedSenders_GSS; + ppLast = &pLastAllowedSenders_GSS; +#endif + } else { + errmsg.LogError(0, RS_RET_ERR, "Invalid protocol '%s' in allowed sender " + "list, line ignored", pName); + return RS_RET_ERR; + } + + /* OK, we now know the protocol and have valid list pointers. + * So let's process the entries. We are using the parse class + * for this. + */ + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) { + errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring allowed sender list", iRet); + return(iRet); + } + + while(!parsIsAtEndOfParseString(pPars)) { + if(parsPeekAtCharAtParsPtr(pPars) == '#') + break; /* a comment-sign stops processing of line */ + /* now parse a single IP address */ + if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) { + errmsg.LogError(0, iRet, "Error %d parsing address in allowed sender" + "list - ignoring.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) != RS_RET_OK) { + if(iRet == RS_RET_NOENTRY) { + errmsg.LogError(0, iRet, "Error %d adding allowed sender entry " + "- ignoring.", iRet); + } else { + errmsg.LogError(0, iRet, "Error %d adding allowed sender entry " + "- terminating, nothing more will be added.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + } + free (uIP); /* copy stored in AllowedSenders list */ + } + + /* cleanup */ + *ppRestOfConfLine += parsGetCurrentPosition(pPars); + return rsParsDestruct(pPars); +} + + + +/* compares a host to an allowed sender list entry. Handles all subleties + * including IPv4/v6 as well as domain name wildcards. + * This is a helper to isAllowedSender. As it is only called once, it is + * declared inline. + * Returns 0 if they do not match, something else otherwise. + * contributed 1007-07-16 by mildew@gmail.com + */ +static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) +{ + assert(pAllow != NULL); + assert(pFrom != NULL); + + if(F_ISSET(pAllow->flags, ADDR_NAME)) { + dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); + +# if !defined(FNM_CASEFOLD) + /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */ + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0); +# else + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); +# endif + } else {/* We need to compare an IP address */ + switch (pFrom->sa_family) { + case AF_INET: + if (AF_INET == pAllow->addr.NetAddr->sa_family) + return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) ) + == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr); + else + return 0; + break; + case AF_INET6: + switch (pAllow->addr.NetAddr->sa_family) { + case AF_INET6: { + struct in6_addr ip, net; + register uint8_t i; + + memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr)); + memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr)); + + i = bits/32; + if (bits % 32) + ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof ip.s6_addr32)/4; i++) + ip.s6_addr32[i] = 0; + + return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 && + (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ? + SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1)); + } + case AF_INET: { + struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr; + struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr; + + if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr && +#if BYTE_ORDER == LITTLE_ENDIAN + (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) && +#else + (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) && +#endif + (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0)) + return 1; + else + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + } +} + + +/* check if a sender is allowed. The root of the the allowed sender. + * list must be proveded by the caller. As such, this function can be + * used to check both UDP and TCP allowed sender lists. + * returns 1, if the sender is allowed, 0 otherwise. + * rgerhards, 2005-09-26 + */ +static int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost) +{ + struct AllowedSenders *pAllow; + + assert(pFrom != NULL); + + if(pAllowRoot == NULL) + return 1; /* checking disabled, everything is valid! */ + + /* now we loop through the list of allowed senders. As soon as + * we find a match, we return back (indicating allowed). We loop + * until we are out of allowed senders. If so, we fall through the + * loop and the function's terminal return statement will indicate + * that the sender is disallowed. + */ + for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) { + if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) + return 1; + } + return 0; +} + + +/* The following #ifdef sequence is a small compatibility + * layer. It tries to work around the different availality + * levels of SO_BSDCOMPAT on linuxes... + * I borrowed this code from + * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html + * It still needs to be a bit better adapted to rsyslog. + * rgerhards 2005-09-19 + */ +#include <sys/utsname.h> +static int +should_use_so_bsdcompat(void) +{ +#ifndef OS_BSD + static int init_done; + static int so_bsdcompat_is_obsolete; + + if (!init_done) { + struct utsname myutsname; + unsigned int version, patchlevel; + + init_done = 1; + if (uname(&myutsname) < 0) { + char errStr[1024]; + dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr))); + return 1; + } + /* Format is <version>.<patchlevel>.<sublevel><extraversion> + where the first three are unsigned integers and the last + is an arbitrary string. We only care about the first two. */ + if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) { + dbgprintf("uname: unexpected release '%s'\r\n", + myutsname.release); + return 1; + } + /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5 + kernels. It is a no-op in 2.4 but not in 2.2 kernels. */ + if (version > 2 || (version == 2 && patchlevel >= 5)) + so_bsdcompat_is_obsolete = 1; + } + return !so_bsdcompat_is_obsolete; +#else /* #ifndef OS_BSD */ + return 1; +#endif /* #ifndef OS_BSD */ +} +#ifndef SO_BSDCOMPAT +/* this shall prevent compiler errors due to undfined name */ +#define SO_BSDCOMPAT 0 +#endif + + +/* get the hostname of the message source. This was originally in cvthname() + * but has been moved out of it because of clarity and fuctional separation. + * It must be provided by the socket we received the message on as well as + * a NI_MAXHOST size large character buffer for the FQDN. + * 2008-05-16 rgerhards: added field for IP address representation. Must also + * be NI_MAXHOST size large. + * + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. We do by default not + * discard message where we detected malicouos DNS PTR records. However, + * there is a user-configurabel option that will tell us if + * we should abort. For this, the return value tells the caller if the + * message should be processed (1) or discarded (0). + */ +static rsRetVal +gethname(struct sockaddr_storage *f, uchar *pszHostFQDN, uchar *ip) +{ + DEFiRet; + int error; + sigset_t omask, nmask; + struct addrinfo hints, *res; + + assert(f != NULL); + assert(pszHostFQDN != NULL); + + error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f), + (char*) ip, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + + if (error) { + dbgprintf("Malformed from address %s\n", gai_strerror(error)); + strcpy((char*) pszHostFQDN, "???"); + strcpy((char*) ip, "???"); + ABORT_FINALIZE(RS_RET_INVALID_SOURCE); + } + + if(!glbl.GetDisableDNS()) { + sigemptyset(&nmask); + sigaddset(&nmask, SIGHUP); + pthread_sigmask(SIG_BLOCK, &nmask, &omask); + + error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *) f), + (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + + if (error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if (getaddrinfo ((char*)pszHostFQDN, NULL, &hints, &res) == 0) { + uchar szErrMsg[1024]; + freeaddrinfo (res); + /* OK, we know we have evil. The question now is what to do about + * it. One the one hand, the message might probably be intended + * to harm us. On the other hand, losing the message may also harm us. + * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords + * option. If it tells us we should discard, we do so, else we proceed, + * but log an error message together with it. + * time being, we simply drop the name we obtained and use the IP - that one + * is OK in any way. We do also log the error message. rgerhards, 2007-07-16 + */ + if(glbl.GetDropMalPTRMsgs() == 1) { + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record, message dropped " + "IP = \"%s\" HOST = \"%s\"", + ip, pszHostFQDN); + errmsg.LogError(0, RS_RET_MALICIOUS_ENTITY, "%s", szErrMsg); + pthread_sigmask(SIG_SETMASK, &omask, NULL); + ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); + } + + /* Please note: we deal with a malicous entry. Thus, we have crafted + * the snprintf() below so that all text is in front of the entry - maybe + * it contains characters that make the message unreadable + * (OK, I admit this is more or less impossible, but I am paranoid...) + * rgerhards, 2007-07-16 + */ + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record (message accepted, but used IP " + "instead of PTR name: IP = \"%s\" HOST = \"%s\"", + ip, pszHostFQDN); + errmsg.LogError(0, NO_ERRCODE, "%s", szErrMsg); + + error = 1; /* that will trigger using IP address below. */ + } + } + pthread_sigmask(SIG_SETMASK, &omask, NULL); + } + + if(error || glbl.GetDisableDNS()) { + dbgprintf("Host name for your address (%s) unknown\n", ip); + strcpy((char*) pszHostFQDN, (char*)ip); + ABORT_FINALIZE(RS_RET_ADDRESS_UNKNOWN); + } + +finalize_it: + RETiRet; +} + + + +/* print out which socket we are listening on. This is only + * a debug aid. rgerhards, 2007-07-02 + */ +void debugListenInfo(int fd, char *type) +{ + char *szFamily; + int port; + struct sockaddr sa; + struct sockaddr_in *ipv4; + struct sockaddr_in6 *ipv6; + socklen_t saLen = sizeof(sa); + + if(getsockname(fd, &sa, &saLen) == 0) { + switch(sa.sa_family) { + case PF_INET: + szFamily = "IPv4"; + ipv4 = (struct sockaddr_in*) &sa; + port = ntohs(ipv4->sin_port); + break; + case PF_INET6: + szFamily = "IPv6"; + ipv6 = (struct sockaddr_in6*) &sa; + port = ntohs(ipv6->sin6_port); + break; + default: + szFamily = "other"; + port = -1; + break; + } + dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n", + type, fd, szFamily, port); + return; + } + + /* we can not obtain peer info. We are just providing + * debug info, so this is no reason to break the program + * or do any serious error reporting. + */ + dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd); +} + + +/* Return a printable representation of a host address. + * Now (2007-07-16) also returns the full host name (if it could be obtained) + * in the second param [thanks to mildew@gmail.com for the patch]. + * The caller must provide buffer space for pszHost and pszHostFQDN. These + * buffers must be of size NI_MAXHOST. This is not checked here, because + * there is no way to check it. We use this way of doing things because it + * frees us from using dynamic memory allocation where it really does not + * pay. + * 2005-05-16 rgerhards: added IP representation. Must also be NI_MAXHOST + */ +rsRetVal cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN, uchar *pszIP) +{ + DEFiRet; + register uchar *p; + int count; + + assert(f != NULL); + assert(pszHost != NULL); + assert(pszHostFQDN != NULL); + + iRet = gethname(f, pszHostFQDN, pszIP); + + if(iRet == RS_RET_INVALID_SOURCE || iRet == RS_RET_ADDRESS_UNKNOWN) { + strcpy((char*) pszHost, (char*) pszHostFQDN); /* we use whatever was provided as replacement */ + ABORT_FINALIZE(RS_RET_OK); /* this is handled, we are happy with it */ + } else if(iRet != RS_RET_OK) { + FINALIZE; /* we return whatever error state we have - can not handle it */ + } + + /* if we reach this point, we obtained a non-numeric hostname and can now process it */ + + /* Convert to lower case */ + for(p = pszHostFQDN ; *p ; p++) + if (isupper((int) *p)) + *p = tolower(*p); + + /* OK, the fqdn is now known. Now it is time to extract only the hostname + * part if we were instructed to do so. + */ + /* TODO: quick and dirty right now: we need to optimize that. We simply + * copy over the buffer and then use the old code. In the long term, that should + * be placed in its own function and probably outside of the net module (at least + * if should no longer reley on syslogd.c's global config-setting variables). + * Note that the old code always removes the local domain. We may want to + * make this in option in the long term. (rgerhards, 2007-09-11) + */ + strcpy((char*)pszHost, (char*)pszHostFQDN); + if ((p = (uchar*) strchr((char*)pszHost, '.'))) { /* find start of domain name "machine.example.com" */ + if(strcmp((char*)(p + 1), (char*)glbl.GetLocalDomain()) == 0) { + *p = '\0'; /* simply terminate the string */ + } else { + /* now check if we belong to any of the domain names that were specified + * in the -s command line option. If so, remove and we are done. + * TODO: this must go away! -- rgerhards, 2008-04-16 + * For proper modularization, this must be done different, e.g. via a + * "to be stripped" property of *this* object itself. + */ + if(glbl.GetStripDomains() != NULL) { + count=0; + while(glbl.GetStripDomains()[count]) { + if (strcmp((char*)(p + 1), glbl.GetStripDomains()[count]) == 0) { + *p = '\0'; + FINALIZE; /* we are done */ + } + count++; + } + } + /* if we reach this point, we have not found any domain we should strip. Now + * we try and see if the host itself is listed in the -l command line option + * and so should be stripped also. If so, we do it and return. Please note that + * -l list FQDNs, not just the hostname part. If it did just list the hostname, the + * door would be wide-open for all kinds of mixing up of hosts. Because of this, + * you'll see comparison against the full string (pszHost) below. The termination + * still occurs at *p, which points at the first dot after the hostname. + * TODO: this must also go away - see comment above -- rgerhards, 2008-04-16 + */ + if(glbl.GetLocalHosts() != NULL) { + count=0; + while (glbl.GetLocalHosts()[count]) { + if (!strcmp((char*)pszHost, (char*)glbl.GetLocalHosts()[count])) { + *p = '\0'; + break; /* we are done */ + } + count++; + } + } + } + } + +finalize_it: + RETiRet; +} + + +/* get the name of the local host. A pointer to a character pointer is passed + * in, which on exit points to the local hostname. This buffer is dynamically + * allocated and must be free()ed by the caller. If the functions returns an + * error, the pointer is NULL. This function is based on GNU/Hurd's localhostname + * function. + * rgerhards, 20080-04-10 + */ +static rsRetVal +getLocalHostname(uchar **ppName) +{ + DEFiRet; + uchar *buf = NULL; + size_t buf_len = 0; + + assert(ppName != NULL); + + do { + if(buf == NULL) { + buf_len = 128; /* Initial guess */ + CHKmalloc(buf = malloc(buf_len)); + } else { + buf_len += buf_len; + CHKmalloc(buf = realloc (buf, buf_len)); + } + } while((gethostname((char*)buf, buf_len) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG); + + *ppName = buf; + buf = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(buf != NULL) + free(buf); + } + RETiRet; +} + + +/* closes the UDP listen sockets (if they exist) and frees + * all dynamically assigned memory. + */ +void closeUDPListenSockets(int *pSockArr) +{ + register int i; + + assert(pSockArr != NULL); + if(pSockArr != NULL) { + for (i = 0; i < *pSockArr; i++) + close(pSockArr[i+1]); + free(pSockArr); + } +} + + +/* creates the UDP listen sockets + * hostname and/or pszPort may be NULL, but not both! + * bIsServer indicates if a server socket should be created + * 1 - server, 0 - client + */ +int *create_udp_socket(uchar *hostname, uchar *pszPort, int bIsServer) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks, on = 1; + int sockflags; + + assert(!((pszPort == NULL) && (hostname == NULL))); + memset(&hints, 0, sizeof(hints)); + if(bIsServer) + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + else + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res); + if(error) { + errmsg.LogError(0, NO_ERRCODE, "%s", gai_strerror(error)); + errmsg.LogError(0, NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n"); + return NULL; + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + socks = malloc((maxs+1) * sizeof(int)); + if (socks == NULL) { + errmsg.LogError(0, NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP message reception"); + freeaddrinfo(res); + return NULL; + } + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r != NULL ; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + errmsg.LogError(errno, NO_ERRCODE, "create_udp_socket(), socket"); + /* it is debateble if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +# ifdef IPV6_V6ONLY + if (r->ai_family == AF_INET6) { + int ion = 1; + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&ion, sizeof (ion)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt"); + close(*s); + *s = -1; + continue; + } + } +# endif + + /* if we have an error, we "just" suspend that socket. Eventually + * other sockets will work. At the end of this function, we check + * if we managed to open at least one socket. If not, we'll write + * a "inet suspended" message and declare failure. Else we use + * what we could obtain. + * rgerhards, 2007-06-22 + */ + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0 ) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt(REUSEADDR)"); + close(*s); + *s = -1; + continue; + } + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#if !defined(OS_BSD) && !defined(__hpux) + if (should_use_so_bsdcompat()) { + if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "setsockopt(BSDCOMPAT)"); + close(*s); + *s = -1; + continue; + } + } +#endif + /* We must not block on the network socket, in case a packet + * gets lost between select and recv, otherwise the process + * will stall until the timeout, and other processes trying to + * log will also stall. + * Patch vom Colin Phipps <cph@cph.demon.co.uk> to the original + * sysklogd source. Applied to rsyslogd on 2005-10-19. + */ + if ((sockflags = fcntl(*s, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(*s, F_SETFL, sockflags); + } + if (sockflags == -1) { + errmsg.LogError(errno, NO_ERRCODE, "fcntl(O_NONBLOCK)"); + close(*s); + *s = -1; + continue; + } + + if(bIsServer) { + /* rgerhards, 2007-06-22: if we run on a kernel that does not support + * the IPV6_V6ONLY socket option, we need to use a work-around. On such + * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4 + * socket can not listen on the same port as an IPv6 socket. The only + * workaround is to ignore the "socket in use" error. This is what we + * do if we have to. + */ + if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) + # ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) + # endif + ) { + errmsg.LogError(errno, NO_ERRCODE, "bind"); + close(*s); + *s = -1; + continue; + } + } + + (*socks)++; + s++; + } + + if(res != NULL) + freeaddrinfo(res); + + if(Debug && *socks != maxs) + dbgprintf("We could initialize %d UDP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", *socks, maxs); + + if(*socks == 0) { + errmsg.LogError(0, NO_ERRCODE, "No UDP listen socket could successfully be initialized, " + "message reception via UDP disabled.\n"); + /* we do NOT need to free any sockets, because there were none... */ + free(socks); + return(NULL); + } + + return(socks); +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(net) +CODESTARTobjQueryInterface(net) + if(pIf->ifVersion != netCURR_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->cvthname = cvthname; + /* things to go away after proper modularization */ + pIf->addAllowedSenderLine = addAllowedSenderLine; + pIf->PrintAllowedSenders = PrintAllowedSenders; + pIf->clearAllowedSenders = clearAllowedSenders; + pIf->debugListenInfo = debugListenInfo; + pIf->create_udp_socket = create_udp_socket; + pIf->closeUDPListenSockets = closeUDPListenSockets; + pIf->isAllowedSender = isAllowedSender; + pIf->should_use_so_bsdcompat = should_use_so_bsdcompat; + pIf->getLocalHostname = getLocalHostname; + pIf->AddPermittedPeer = AddPermittedPeer; + pIf->DestructPermittedPeers = DestructPermittedPeers; + pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch; +finalize_it: +ENDobjQueryInterface(net) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(net) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(net) + + +/* Initialize the net class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(net) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + netClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/net.h b/runtime/net.h new file mode 100644 index 00000000..0d36e824 --- /dev/null +++ b/runtime/net.h @@ -0,0 +1,164 @@ +/* Definitions for network-related stuff. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NET_H +#define INCLUDED_NET_H + +#include <netinet/in.h> +#include <sys/socket.h> /* this is needed on HP UX -- rgerhards, 2008-03-04 */ + +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; + +#define F_SET(where, flag) (where)|=(flag) +#define F_ISSET(where, flag) ((where)&(flag))==(flag) +#define F_UNSET(where, flag) (where)&=~(flag) + +#define ADDR_NAME 0x01 /* address is hostname wildcard) */ +#define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */ + +#ifdef OS_BSD +# ifndef _KERNEL +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif + +struct NetAddr { + uint8_t flags; + union { + struct sockaddr *NetAddr; + char *HostWildcard; + } addr; +}; + +#ifndef SO_BSDCOMPAT + /* this shall prevent compiler errors due to undefined name */ +# define SO_BSDCOMPAT 0 +#endif + + +/* IPv6 compatibility layer for older platforms + * We need to handle a few things different if we are running + * on an older platform which does not support all the glory + * of IPv6. We try to limit toll on features and reliability, + * but obviously it is better to run rsyslog on a platform that + * supports everything... + * rgerhards, 2007-06-22 + */ +#ifndef AI_NUMERICSERV +# define AI_NUMERICSERV 0 +#endif + + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +#define SALEN(sa) ((sa)->sa_len) +#else +static inline size_t SALEN(struct sockaddr *sa) { + switch (sa->sa_family) { + case AF_INET: return (sizeof (struct sockaddr_in)); + case AF_INET6: return (sizeof (struct sockaddr_in6)); + default: return 0; + } +} +#endif + +struct AllowedSenders { + struct NetAddr allowedSender; /* ip address allowed */ + uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ + struct AllowedSenders *pNext; +}; + + +/* this structure is a helper to implement wildcards in permittedPeers_t. It specifies + * the domain component and the matching mode. + * rgerhards, 2008-05-27 + */ +struct permittedPeerWildcard_s { + uchar *pszDomainPart; + size_t lenDomainPart; + enum { + PEER_WILDCARD_NONE = 0, /**< no wildcard in this entry */ + PEER_WILDCARD_AT_START = 1, /**< wildcard at start of entry (*name) */ + PEER_WILDCARD_AT_END = 2, /**< wildcard at end of entry (name*) */ + PEER_WILDCARD_MATCH_ALL = 3, /**< only * wildcard, matches all values */ + PEER_WILDCARD_EMPTY_COMPONENT = 4/**< special case: domain component empty (e.g. "..") */ + } wildcardType; + permittedPeerWildcard_t *pNext; +}; + +/* for fingerprints and hostnames, we need to have a temporary linked list of + * permitted values. Unforutnately, we must also duplicate this in the netstream + * drivers. However, this is the best interim solution (with the least effort). + * A clean implementation requires that we have more capable variables and the + * full-fledged scripting engine available. So we have opted to do the interim + * solution so that our users can begin to enjoy authenticated TLS. The next step + * (hopefully) is to enhance RainerScript. -- rgerhards, 2008-05-19 + */ +struct permittedPeers_s { + uchar *pszID; + enum { + PERM_PEER_TYPE_UNDECIDED = 0, /**< we have not yet decided the type (fine in some auth modes) */ + PERM_PEER_TYPE_PLAIN = 1, /**< just plain text contained */ + PERM_PEER_TYPE_WILDCARD = 2, /**< wildcards are contained, wildcard struture is filled */ + } etryType; + permittedPeers_t *pNext; + permittedPeerWildcard_t *pWildcardRoot; /**< root of the wildcard, NULL if not initialized */ + permittedPeerWildcard_t *pWildcardLast; /**< end of the wildcard list, NULL if not initialized */ +}; + + +/* interfaces */ +BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*cvthname)(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN, uchar *pszIP); + /* things to go away after proper modularization */ + rsRetVal (*addAllowedSenderLine)(char* pName, uchar** ppRestOfConfLine); + void (*PrintAllowedSenders)(int iListToPrint); + void (*clearAllowedSenders) (); + void (*debugListenInfo)(int fd, char *type); + int *(*create_udp_socket)(uchar *hostname, uchar *LogPort, int bIsServer); + void (*closeUDPListenSockets)(int *finet); + int (*isAllowedSender)(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost); + rsRetVal (*getLocalHostname)(uchar**); + int (*should_use_so_bsdcompat)(void); + /* permitted peer handling should be replaced by something better (see comments above) */ + rsRetVal (*AddPermittedPeer)(permittedPeers_t **ppRootPeer, uchar *pszID); + rsRetVal (*DestructPermittedPeers)(permittedPeers_t **ppRootPeer); + rsRetVal (*PermittedPeerWildcardMatch)(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching); + /* data members - these should go away over time... TODO */ + int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ + int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ + struct AllowedSenders *pAllowedSenders_UDP; + struct AllowedSenders *pAllowedSenders_TCP; + struct AllowedSenders *pAllowedSenders_GSS; +ENDinterface(net) +#define netCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(net); + +/* the name of our library binary */ +#define LM_NET_FILENAME "lmnet" + +#endif /* #ifndef INCLUDED_NET_H */ diff --git a/runtime/netstrm.c b/runtime/netstrm.c new file mode 100644 index 00000000..2f4a1964 --- /dev/null +++ b/runtime/netstrm.c @@ -0,0 +1,353 @@ +/* netstrm.c + * + * This class implements a generic netstrmwork stream class. It supports + * sending and receiving data streams over a netstrmwork. The class abstracts + * the transport, though it is a safe assumption that TCP is being used. + * The class has a number of properties, among which are also ones to + * select privacy settings, eg by enabling TLS and/or GSSAPI. In the + * long run, this class shall provide all stream-oriented netstrmwork + * functionality inside rsyslog. + * + * It is a high-level class, which uses a number of helper objects + * to carry out its work (including, and most importantly, transport + * drivers). + * + * Work on this module begun 2008-04-17 by Rainer Gerhards. This code + * borrows from librelp's tcp.c/.h code. librelp is dual licensed and + * Rainer Gerhards and Adiscon GmbH have agreed to permit using the code + * under the terms of the GNU Lesser General Public License. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "rsyslog.h" +#include "net.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "netstrms.h" +#include "netstrm.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrms) + + +/* Standard-Constructor */ +BEGINobjConstruct(netstrm) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(netstrm) + + +/* destructor for the netstrm object */ +BEGINobjDestruct(netstrm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(netstrm) + if(pThis->pDrvrData != NULL) + iRet = pThis->Drvr.Destruct(&pThis->pDrvrData); +ENDobjDestruct(netstrm) + + +/* ConstructionFinalizer */ +static rsRetVal +netstrmConstructFinalize(netstrm_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData)); +finalize_it: + RETiRet; +} + +/* abort a connection. This is much like Destruct(), but tries + * to discard any unsent data. -- rgerhards, 2008-03-24 + */ +static rsRetVal +AbortDestruct(netstrm_t **ppThis) +{ + DEFiRet; + assert(ppThis != NULL); + ISOBJ_TYPE_assert((*ppThis), netstrm); + + /* we do NOT exit on error, because that would make things worse */ + (*ppThis)->Drvr.Abort((*ppThis)->pDrvrData); + iRet = netstrmDestruct(ppThis); + + RETiRet; +} + + +/* accept an incoming connection request + * The netstrm instance that had the incoming request must be provided. If + * the connection request succeeds, a new netstrm object is created and + * passed back to the caller. The caller is responsible for destructing it. + * pReq is the nsd_t obj that has the accept request. + * rgerhards, 2008-04-21 + */ +static rsRetVal +AcceptConnReq(netstrm_t *pThis, netstrm_t **ppNew) +{ + nsd_t *pNewNsd = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, netstrm); + assert(ppNew != NULL); + + /* accept the new connection */ + CHKiRet(pThis->Drvr.AcceptConnReq(pThis->pDrvrData, &pNewNsd)); + /* construct our object so that we can use it... */ + CHKiRet(objUse(netstrms, DONT_LOAD_LIB)); /* use netstrms obj if not already done so */ + CHKiRet(netstrms.CreateStrm(pThis->pNS, ppNew)); + (*ppNew)->pDrvrData = pNewNsd; + +finalize_it: + if(iRet != RS_RET_OK) { + /* the close may be redundant, but that doesn't hurt... */ + if(pNewNsd != NULL) + pThis->Drvr.Destruct(&pNewNsd); + } + + RETiRet; +} + + +/* make the netstrm listen to specified port and IP. + * pLstnIP points to the port to listen to (NULL means "all"), + * iMaxSess has the maximum number of sessions permitted (this ist just a hint). + * pLstnPort must point to a port name or number. NULL is NOT permitted. + * rgerhards, 2008-04-22 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pNS, netstrms); + assert(fAddLstn != NULL); + assert(pLstnPort != NULL); + + CHKiRet(pNS->Drvr.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax)); + +finalize_it: + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read (or -1 in case of error) on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. If *pLenBuf is -1, an error occured and + * errno holds the exact error cause. + * rgerhards, 2008-03-17 + */ +static rsRetVal +Rcv(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.Rcv(pThis->pDrvrData, pBuf, pLenBuf); + RETiRet; +} + +/* here follows a number of methods that shuffle authentication settings down + * to the drivers. Drivers not supporting these settings may return an error + * state. + * -------------------------------------------------------------------------- */ + +/* set the driver mode + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetDrvrMode(netstrm_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetMode(pThis->pDrvrData, iMode); + RETiRet; +} + + +/* set the driver authentication mode -- rgerhards, 2008-05-16 + */ +static rsRetVal +SetDrvrAuthMode(netstrm_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetAuthMode(pThis->pDrvrData, mode); + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(netstrm_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.SetPermPeers(pThis->pDrvrData, pPermPeers); + RETiRet; +} + + +/* End of methods to shuffle autentication settings to the driver. + * -------------------------------------------------------------------------- */ + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.Send(pThis->pDrvrData, pBuf, pLenBuf); + RETiRet; +} + + +/* check connection - slim wrapper for NSD driver function */ +static void +CheckConnection(netstrm_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrm); + pThis->Drvr.CheckConnection(pThis->pDrvrData); +} + + +/* get remote hname - slim wrapper for NSD driver function */ +static rsRetVal +GetRemoteHName(netstrm_t *pThis, uchar **ppsz) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.GetRemoteHName(pThis->pDrvrData, ppsz); + RETiRet; +} + + +/* get remote IP - slim wrapper for NSD driver function */ +static rsRetVal +GetRemoteIP(netstrm_t *pThis, uchar **ppsz) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + iRet = pThis->Drvr.GetRemoteIP(pThis->pDrvrData, ppsz); + RETiRet; +} + + +/* open a connection to a remote host (server). + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(netstrm_t *pThis, int family, uchar *port, uchar *host) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + assert(port != NULL); + assert(host != NULL); + iRet = pThis->Drvr.Connect(pThis->pDrvrData, family, port, host); + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is dirty + * and scheduled to be removed. Does not work with all nsd drivers. + * See comment in netstrm interface for details. + * rgerhards, 2008-05-05 + */ +static rsRetVal +GetSock(netstrm_t *pThis, int *pSock) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrm); + assert(pSock != NULL); + iRet = pThis->Drvr.GetSock(pThis->pDrvrData, pSock); + RETiRet; +} + + +/* queryInterface function + */ +BEGINobjQueryInterface(netstrm) +CODESTARTobjQueryInterface(netstrm) + if(pIf->ifVersion != netstrmCURR_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->Construct = netstrmConstruct; + pIf->ConstructFinalize = netstrmConstructFinalize; + pIf->Destruct = netstrmDestruct; + pIf->AbortDestruct = AbortDestruct; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->Connect = Connect; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->SetDrvrMode = SetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->CheckConnection = CheckConnection; + pIf->GetSock = GetSock; +finalize_it: +ENDobjQueryInterface(netstrm) + + +/* exit our class + */ +BEGINObjClassExit(netstrm, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(netstrm) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrms, DONT_LOAD_LIB); +ENDObjClassExit(netstrm) + + +/* Initialize the netstrm class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(netstrm, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(netstrm) +/* vi:set ai: + */ diff --git a/runtime/netstrm.h b/runtime/netstrm.h new file mode 100644 index 00000000..1a97ef23 --- /dev/null +++ b/runtime/netstrm.h @@ -0,0 +1,73 @@ +/* Definitions for the stream-based netstrmworking class. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NETSTRM_H +#define INCLUDED_NETSTRM_H + +#include "netstrms.h" + +/* the netstrm object */ +struct netstrm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pDrvrData; /**< the driver's data elements (at most other places, this is called pNsd) */ + nsd_if_t Drvr; /**< our stream driver */ + netstrms_t *pNS; /**< pointer to our netstream subsystem object */ +}; + + +/* interface */ +BEGINinterface(netstrm) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(netstrm_t **ppThis); + rsRetVal (*ConstructFinalize)(netstrm_t *pThis); + rsRetVal (*Destruct)(netstrm_t **ppThis); + rsRetVal (*AbortDestruct)(netstrm_t **ppThis); + rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax); + rsRetVal (*AcceptConnReq)(netstrm_t *pThis, netstrm_t **ppNew); + rsRetVal (*Rcv)(netstrm_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf); + rsRetVal (*Send)(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf); + rsRetVal (*Connect)(netstrm_t *pThis, int family, unsigned char *port, unsigned char *host); + rsRetVal (*GetRemoteHName)(netstrm_t *pThis, uchar **pszName); + rsRetVal (*GetRemoteIP)(netstrm_t *pThis, uchar **pszIP); + rsRetVal (*SetDrvrMode)(netstrm_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(netstrm_t *pThis, uchar*); + rsRetVal (*SetDrvrPermPeers)(netstrm_t *pThis, permittedPeers_t*); + void (*CheckConnection)(netstrm_t *pThis); /* This is a trick mostly for plain tcp syslog */ + /* the GetSock() below is a hack to make imgssapi work. In the long term, + * we should migrate imgssapi to a stream driver, which will relieve us of + * this problem. Please note that nobody else should use GetSock(). Using it + * will also tie the caller to nsd_ptcp, because other drivers may not support + * it at all. Once the imgssapi problem is solved, GetSock should be removed from + * this interface. -- rgerhards, 2008-05-05 + */ + rsRetVal (*GetSock)(netstrm_t *pThis, int *pSock); +ENDinterface(netstrm) +#define netstrmCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(netstrm); + +/* the name of our library binary */ +#define LM_NETSTRM_FILENAME LM_NETSTRMS_FILENAME + +#endif /* #ifndef INCLUDED_NETSTRM_H */ diff --git a/runtime/netstrms.c b/runtime/netstrms.c new file mode 100644 index 00000000..2b754ecc --- /dev/null +++ b/runtime/netstrms.c @@ -0,0 +1,324 @@ +/* netstrms.c + * + * Work on this module begung 2008-04-23 by Rainer Gerhards. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +//#include "errmsg.h" +#include "nsd.h" +#include "netstrm.h" +#include "nssel.h" +#include "netstrms.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +//DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(netstrm) + + +/* load our low-level driver. This must be done before any + * driver-specific functions (allmost all...) can be carried + * out. Note that the driver's .ifIsLoaded is correctly + * initialized by calloc() and we depend on that. + * WARNING: this code is mostly identical to similar code in + * nssel.c - TODO: abstract it and move it to some common place. + * rgerhards, 2008-04-18 + */ +static rsRetVal +loadDrvr(netstrms_t *pThis) +{ + DEFiRet; + uchar *pBaseDrvrName; + uchar szDrvrName[48]; /* 48 shall be large enough */ + + pBaseDrvrName = pThis->pBaseDrvrName; + if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */ + pBaseDrvrName = glbl.GetDfltNetstrmDrvr(); + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsd_%s", pBaseDrvrName) == sizeof(szDrvrName)) + ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG); + CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName)); + + pThis->Drvr.ifVersion = nsdCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean + * enough. -- rgerhards, 2008-04-18 + */ + CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, szDrvrName, (void*) &pThis->Drvr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pDrvrName != NULL) + free(pThis->pDrvrName); + pThis->pDrvrName = NULL; + } + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(netstrms) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(netstrms) + + +/* destructor for the netstrms object */ +BEGINobjDestruct(netstrms) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(netstrms) + /* and now we must release our driver, if we got one. We use the presence of + * a driver name string as load indicator (because we also need that string + * to release the driver + */ + if(pThis->pDrvrName != NULL) { + obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, pThis->pDrvrName, (void*) &pThis->Drvr); + free(pThis->pDrvrName); + } + if(pThis->pBaseDrvrName != NULL) { + free(pThis->pBaseDrvrName); + pThis->pBaseDrvrName = NULL; + } +ENDobjDestruct(netstrms) + + +/* ConstructionFinalizer */ +static rsRetVal +netstrmsConstructFinalize(netstrms_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + CHKiRet(loadDrvr(pThis)); +finalize_it: + RETiRet; +} + + +/* set the base driver name. If the driver name + * is set to NULL, the previously set name is deleted but + * no name set again (which results in the system default being + * used)-- rgerhards, 2008-05-05 + */ +static rsRetVal +SetDrvrName(netstrms_t *pThis, uchar *pszName) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + if(pThis->pBaseDrvrName != NULL) { + free(pThis->pBaseDrvrName); + pThis->pBaseDrvrName = NULL; + } + + if(pszName != NULL) { + CHKmalloc(pThis->pBaseDrvrName = (uchar*) strdup((char*) pszName)); + } +finalize_it: + RETiRet; +} + + +/* set the driver's permitted peers -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrPermPeers(netstrms_t *pThis, permittedPeers_t *pPermPeers) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + pThis->pPermPeers = pPermPeers; + RETiRet; +} +/* return the driver's permitted peers + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-05-19 + */ +static permittedPeers_t* +GetDrvrPermPeers(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->pPermPeers; +} + + +/* set the driver auth mode -- rgerhards, 2008-05-19 */ +static rsRetVal +SetDrvrAuthMode(netstrms_t *pThis, uchar *mode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + CHKmalloc(pThis->pszDrvrAuthMode = (uchar*)strdup((char*)mode)); +finalize_it: + RETiRet; +} +/* return the driver auth mode + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-05-19 + */ +static uchar* +GetDrvrAuthMode(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->pszDrvrAuthMode; +} + + +/* set the driver mode -- rgerhards, 2008-04-30 */ +static rsRetVal +SetDrvrMode(netstrms_t *pThis, int iMode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, netstrms); + pThis->iDrvrMode = iMode; + RETiRet; +} + + +/* return the driver mode + * We use non-standard calling conventions because it makes an awful lot + * of sense here. + * rgerhards, 2008-04-30 + */ +static int +GetDrvrMode(netstrms_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, netstrms); + return pThis->iDrvrMode; +} + + +/* create an instance of a netstrm object. It is initialized with default + * values. The current driver is used. The caller may set netstrm properties + * and must call ConstructFinalize(). + */ +static rsRetVal +CreateStrm(netstrms_t *pThis, netstrm_t **ppStrm) +{ + netstrm_t *pStrm = NULL; + DEFiRet; + + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + CHKiRet(netstrm.Construct(&pStrm)); + /* we copy over our driver structure. We could provide a pointer to + * ourselves, but that costs some performance on each driver invocation. + * As we already have hefty indirection (and thus performance toll), I + * prefer to copy over the function pointers here. -- rgerhards, 2008-04-23 + */ + memcpy(&pStrm->Drvr, &pThis->Drvr, sizeof(pThis->Drvr)); + pStrm->pNS = pThis; + + *ppStrm = pStrm; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStrm != NULL) + netstrm.Destruct(&pStrm); + } + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(netstrms) +CODESTARTobjQueryInterface(netstrms) + if(pIf->ifVersion != netstrmsCURR_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->Construct = netstrmsConstruct; + pIf->ConstructFinalize = netstrmsConstructFinalize; + pIf->Destruct = netstrmsDestruct; + pIf->CreateStrm = CreateStrm; + pIf->SetDrvrName = SetDrvrName; + pIf->SetDrvrMode = SetDrvrMode; + pIf->GetDrvrMode = GetDrvrMode; + pIf->SetDrvrAuthMode = SetDrvrAuthMode; + pIf->GetDrvrAuthMode = GetDrvrAuthMode; + pIf->SetDrvrPermPeers = SetDrvrPermPeers; + pIf->GetDrvrPermPeers = GetDrvrPermPeers; +finalize_it: +ENDobjQueryInterface(netstrms) + + +/* exit our class */ +BEGINObjClassExit(netstrms, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(netstrms) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(netstrm, DONT_LOAD_LIB); +ENDObjClassExit(netstrms) + + +/* Initialize the netstrms class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(netstrms, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(netstrms) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsselClassExit(); + netstrmsClassExit(); + netstrmClassExit(); /* we use this object, so we must exit it after we are finished */ +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(netstrmClassInit(pModInfo)); + CHKiRet(nsselClassInit(pModInfo)); + CHKiRet(netstrmsClassInit(pModInfo)); +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/netstrms.h b/runtime/netstrms.h new file mode 100644 index 00000000..3f686af6 --- /dev/null +++ b/runtime/netstrms.h @@ -0,0 +1,64 @@ +/* Definitions for the stream-based netstrmsworking class. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NETSTRMS_H +#define INCLUDED_NETSTRMS_H + +#include "nsd.h" /* we need our driver interface to be defined */ + +/* the netstrms object */ +struct netstrms_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */ + uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */ + int iDrvrMode; /**< current default driver mode */ + uchar *pszDrvrAuthMode; /**< current driver authentication mode */ + permittedPeers_t *pPermPeers;/**< current driver's permitted peers */ + + nsd_if_t Drvr; /**< our stream driver */ +}; + + +/* interface */ +BEGINinterface(netstrms) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(netstrms_t **ppThis); + rsRetVal (*ConstructFinalize)(netstrms_t *pThis); + rsRetVal (*Destruct)(netstrms_t **ppThis); + rsRetVal (*CreateStrm)(netstrms_t *pThis, netstrm_t **ppStrm); + rsRetVal (*SetDrvrName)(netstrms_t *pThis, uchar *pszName); + rsRetVal (*SetDrvrMode)(netstrms_t *pThis, int iMode); + rsRetVal (*SetDrvrAuthMode)(netstrms_t *pThis, uchar*); + rsRetVal (*SetDrvrPermPeers)(netstrms_t *pThis, permittedPeers_t*); + int (*GetDrvrMode)(netstrms_t *pThis); + uchar* (*GetDrvrAuthMode)(netstrms_t *pThis); + permittedPeers_t* (*GetDrvrPermPeers)(netstrms_t *pThis); +ENDinterface(netstrms) +#define netstrmsCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(netstrms); + +/* the name of our library binary */ +#define LM_NETSTRMS_FILENAME "lmnetstrms" + +#endif /* #ifndef INCLUDED_NETSTRMS_H */ diff --git a/runtime/nsd.h b/runtime/nsd.h new file mode 100644 index 00000000..1811f078 --- /dev/null +++ b/runtime/nsd.h @@ -0,0 +1,76 @@ +/* The interface definition for "NetStream Drivers" (nsd). + * + * This is just an abstract driver interface, which needs to be + * implemented by concrete classes. As such, no nsd data type itself + * is defined. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_NSD_H +#define INCLUDED_NSD_H + +enum nsdsel_waitOp_e { + NSDSEL_RD = 1, + NSDSEL_WR = 2, + NSDSEL_RDWR = 3 +}; /**< the operation we wait for */ + +/* nsd_t is actually obj_t (which is somewhat better than void* but in essence + * much the same). + */ + +/* interface */ +BEGINinterface(nsd) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nsd_t **ppThis); + rsRetVal (*Destruct)(nsd_t **ppThis); + rsRetVal (*Abort)(nsd_t *pThis); + rsRetVal (*Rcv)(nsd_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf); + rsRetVal (*Send)(nsd_t *pThis, uchar *pBuf, ssize_t *pLenBuf); + rsRetVal (*Connect)(nsd_t *pThis, int family, unsigned char *port, unsigned char *host); + rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax); + rsRetVal (*AcceptConnReq)(nsd_t *pThis, nsd_t **ppThis); + rsRetVal (*GetRemoteHName)(nsd_t *pThis, uchar **pszName); + rsRetVal (*GetRemoteIP)(nsd_t *pThis, uchar **pszIP); + rsRetVal (*SetMode)(nsd_t *pThis, int mode); /* sets a driver specific mode - see driver doc for details */ + rsRetVal (*SetAuthMode)(nsd_t *pThis, uchar*); /* sets a driver specific mode - see driver doc for details */ + rsRetVal (*SetPermPeers)(nsd_t *pThis, permittedPeers_t*); /* sets driver permitted peers for auth needs */ + void (*CheckConnection)(nsd_t *pThis); /* This is a trick mostly for plain tcp syslog */ + rsRetVal (*GetSock)(nsd_t *pThis, int *pSock); + rsRetVal (*SetSock)(nsd_t *pThis, int sock); + /* GetSock() and SetSock() return an error if the driver does not use plain + * OS sockets. This interface is primarily meant as an internal aid for + * those drivers that utilize the nsd_ptcp to do some of their work. + */ +ENDinterface(nsd) +#define nsdCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ + +/* interface for the select call */ +BEGINinterface(nsdsel) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nsdsel_t **ppThis); + rsRetVal (*Destruct)(nsdsel_t **ppThis); + rsRetVal (*Add)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp); + rsRetVal (*Select)(nsdsel_t *pNsdsel, int *piNumReady); + rsRetVal (*IsReady)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady); +ENDinterface(nsdsel) +#define nsdselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +#endif /* #ifndef INCLUDED_NSD_H */ diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c new file mode 100644 index 00000000..08623da8 --- /dev/null +++ b/runtime/nsd_gtls.c @@ -0,0 +1,1711 @@ +/* nsd_gtls.c + * + * An implementation of the nsd interface for GnuTLS. + * + * Copyright (C) 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gcrypt.h> +#include <errno.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "module-template.h" +#include "cfsysline.h" +#include "obj.h" +#include "stringbuf.h" +#include "errmsg.h" +#include "net.h" +#include "nsd_ptcp.h" +#include "nsdsel_gtls.h" +#include "nsd_gtls.h" + +/* things to move to some better place/functionality - TODO */ +#define DH_BITS 1024 +#define CRLFILE "crl.pem" + + +GCRY_THREAD_OPTION_PTHREAD_IMPL; +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(nsd_ptcp) + +static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */ + +static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially non-reentrant gtlStrerror() function */ + +/* a macro to check GnuTLS calls against unexpected errors */ +#define CHKgnutls(x) \ + if((gnuRet = (x)) != 0) { \ + uchar *pErr = gtlsStrerror(gnuRet); \ + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); \ + free(pErr); \ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \ + } + + +/* ------------------------------ GnuTLS specifics ------------------------------ */ +static gnutls_certificate_credentials xcred; +static gnutls_dh_params dh_params; + +#ifdef DEBUG +/* This defines a log function to be provided to GnuTLS. It hopefully + * helps us track down hard to find problems. + * rgerhards, 2008-06-20 + */ +static void logFunction(int level, const char *msg) +{ + dbgprintf("GnuTLS log msg, level %d: %s\n", level, msg); +} +#endif /* #ifdef DEBUG */ + + +/* read in the whole content of a file. The caller is responsible for + * freeing the buffer. To prevent DOS, this function can NOT read + * files larger than 1MB (which still is *very* large). + * rgerhards, 2008-05-26 + */ +static rsRetVal +readFile(uchar *pszFile, gnutls_datum_t *pBuf) +{ + int fd; + struct stat stat_st; + DEFiRet; + + assert(pszFile != NULL); + assert(pBuf != NULL); + + pBuf->data = NULL; + + if((fd = open((char*)pszFile, 0)) == -1) { + errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + + } + + if(fstat(fd, &stat_st) == -1) { + errmsg.LogError(0, RS_RET_FILE_NO_STAT, "can not stat file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_FILE_NO_STAT); + } + + /* 1MB limit */ + if(stat_st.st_size > 1024 * 1024) { + errmsg.LogError(0, RS_RET_FILE_TOO_LARGE, "file '%s' too large, max 1MB", pszFile); + ABORT_FINALIZE(RS_RET_FILE_TOO_LARGE); + } + + CHKmalloc(pBuf->data = malloc(stat_st.st_size)); + pBuf->size = stat_st.st_size; + if(read(fd, pBuf->data, stat_st.st_size) != stat_st.st_size) { + errmsg.LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + close(fd); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pBuf->data != NULL) { + free(pBuf->data); + pBuf->data = NULL; + pBuf->size = 0; + } + } + RETiRet; +} + + +/* Load the certificate and the private key into our own store. We need to do + * this in the client case, to support fingerprint authentication. In that case, + * we may be presented no matching root certificate, but we must provide ours. + * The only way to do that is via the cert callback interface, but for it we + * need to load certificates into our private store. + * rgerhards, 2008-05-26 + */ +static rsRetVal +gtlsLoadOurCertKey(nsd_gtls_t *pThis) +{ + DEFiRet; + int gnuRet; + gnutls_datum_t data = { NULL, 0 }; + uchar *keyFile; + uchar *certFile; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + certFile = glbl.GetDfltNetstrmDrvrCertFile(); + keyFile = glbl.GetDfltNetstrmDrvrKeyFile(); + + if(certFile == NULL || keyFile == NULL) { + /* in this case, we can not set our certificate. If we are + * a client and the server is running in "anon" auth mode, this + * may be well acceptable. In other cases, we will see some + * more error messages down the road. -- rgerhards, 2008-07-02 + */ + dbgprintf("our certificate is not set, file name values are cert: '%s', key: '%s'\n", + certFile, keyFile); + ABORT_FINALIZE(RS_RET_CERTLESS); + } + + /* try load certificate */ + CHKiRet(readFile(certFile, &data)); + CHKgnutls(gnutls_x509_crt_init(&pThis->ourCert)); + pThis->bOurCertIsInit = 1; + CHKgnutls(gnutls_x509_crt_import(pThis->ourCert, &data, GNUTLS_X509_FMT_PEM)); + free(data.data); + data.data = NULL; + + /* try load private key */ + CHKiRet(readFile(keyFile, &data)); + CHKgnutls(gnutls_x509_privkey_init(&pThis->ourKey)); + pThis->bOurKeyIsInit = 1; + CHKgnutls(gnutls_x509_privkey_import(pThis->ourKey, &data, GNUTLS_X509_FMT_PEM)); + free(data.data); + +finalize_it: + if(iRet != RS_RET_OK) { + if(data.data != NULL) + free(data.data); + if(pThis->bOurCertIsInit) + gnutls_x509_crt_deinit(pThis->ourCert); + if(pThis->bOurKeyIsInit) + gnutls_x509_privkey_deinit(pThis->ourKey); + } + RETiRet; +} + + +/* This callback must be associated with a session by calling + * gnutls_certificate_client_set_retrieve_function(session, cert_callback), + * before a handshake. We will always return the configured certificate, + * even if it does not match the peer's trusted CAs. This is necessary + * to use self-signed certs in fingerprint mode. And, yes, this usage + * of the callback is quite a hack. But it seems the only way to + * obey to the IETF -transport-tls I-D. + * Note: GnuTLS requires the function to return 0 on success and + * -1 on failure. + * rgerhards, 2008-05-27 + */ +static int +gtlsClientCertCallback(gnutls_session session, + __attribute__((unused)) const gnutls_datum* req_ca_rdn, int __attribute__((unused)) nreqs, + __attribute__((unused)) const gnutls_pk_algorithm* sign_algos, int __attribute__((unused)) sign_algos_length, + gnutls_retr_st *st) +{ + nsd_gtls_t *pThis; + + pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session); + + st->type = GNUTLS_CRT_X509; + st->ncerts = 1; + st->cert.x509 = &pThis->ourCert; + st->key.x509 = pThis->ourKey; + st->deinit_all = 0; + + return 0; +} + + +/* This function extracts some information about this session's peer + * certificate. Works for X.509 certificates only. Adds all + * of the info to a cstr_t, which is handed over to the caller. + * Caller must destruct it when no longer needed. + * rgerhards, 2008-05-21 + */ +static rsRetVal +gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr) +{ + char dn[128]; + uchar lnBuf[256]; + size_t size; + unsigned int algo, bits; + time_t expiration_time, activation_time; + const gnutls_datum *cert_list; + unsigned cert_list_size = 0; + gnutls_x509_crt cert; + cstr_t *pStr = NULL; + int gnuRet; + DEFiRet; + unsigned iAltName; + size_t szAltNameLen; + char szAltName[1024]; /* this is sufficient for the DNSNAME... */ + + assert(ppStr != NULL); + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) + return RS_RET_TLS_CERT_ERR; + + cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); + + CHKiRet(rsCStrConstruct(&pStr)); + + snprintf((char*)lnBuf, sizeof(lnBuf), "peer provided %d certificate(s). ", cert_list_size); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + if(cert_list_size > 0) { + /* we only print information about the first certificate */ + CHKgnutls(gnutls_x509_crt_init(&cert)); + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); + + CHKiRet(rsCStrAppendStr(pStr, (uchar*)"Certificate 1 info: ")); + + expiration_time = gnutls_x509_crt_get_expiration_time(cert); + activation_time = gnutls_x509_crt_get_activation_time(cert); + ctime_r(&activation_time, dn); + dn[strlen(dn) - 1] = '\0'; /* strip linefeed */ + snprintf((char*)lnBuf, sizeof(lnBuf), "certificate valid from %s ", dn); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + ctime_r(&expiration_time, dn); + dn[strlen(dn) - 1] = '\0'; /* strip linefeed */ + snprintf((char*)lnBuf, sizeof(lnBuf), "to %s; ", dn); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + /* Extract some of the public key algorithm's parameters */ + algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits); + + snprintf((char*)lnBuf, sizeof(lnBuf), "Certificate public key: %s; ", + gnutls_pk_algorithm_get_name(algo)); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + /* names */ + size = sizeof(dn); + gnutls_x509_crt_get_dn(cert, dn, &size); + snprintf((char*)lnBuf, sizeof(lnBuf), "DN: %s; ", dn); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + size = sizeof(dn); + gnutls_x509_crt_get_issuer_dn(cert, dn, &size); + snprintf((char*)lnBuf, sizeof(lnBuf), "Issuer DN: %s; ", dn); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + + /* dNSName alt name */ + iAltName = 0; + while(1) { /* loop broken below */ + szAltNameLen = sizeof(szAltName); + gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName, + szAltName, &szAltNameLen, NULL); + if(gnuRet < 0) + break; + else if(gnuRet == GNUTLS_SAN_DNSNAME) { + /* we found it! */ + snprintf((char*)lnBuf, sizeof(lnBuf), "SAN:DNSname: %s; ", szAltName); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + /* do NOT break, because there may be multiple dNSName's! */ + } + ++iAltName; + } + + gnutls_x509_crt_deinit(cert); + } + + CHKiRet(rsCStrFinish(pStr)); + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStr != NULL) + rsCStrDestruct(&pStr); + } + + RETiRet; +} + + + +#if 0 /* we may need this in the future - code needs to be looked at then! */ +/* This function will print some details of the + * given pThis->sess. + */ +static rsRetVal +print_info(nsd_gtls_t *pThis) +{ + const char *tmp; + gnutls_credentials_type cred; + gnutls_kx_algorithm kx; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + /* print the key exchange's algorithm name + */ + kx = gnutls_kx_get(pThis->sess); + tmp = gnutls_kx_get_name(kx); + dbgprintf("- Key Exchange: %s\n", tmp); + + /* Check the authentication type used and switch + * to the appropriate. + */ + cred = gnutls_auth_get_type(pThis->sess); + switch (cred) { + case GNUTLS_CRD_ANON: /* anonymous authentication */ + dbgprintf("- Anonymous DH using prime of %d bits\n", + gnutls_dh_get_prime_bits(pThis->sess)); + break; + case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ + /* Check if we have been using ephemeral Diffie Hellman. + */ + if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) { + dbgprintf("\n- Ephemeral DH using prime of %d bits\n", + gnutls_dh_get_prime_bits(pThis->sess)); + } + + /* if the certificate list is available, then + * print some information about it. + */ + gtlsPrintCert(pThis); + break; + case GNUTLS_CRD_SRP: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_SRP/IA"); + break; + case GNUTLS_CRD_PSK: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_PSK"); + break; + case GNUTLS_CRD_IA: /* certificate authentication */ + dbgprintf("GNUTLS_CRD_IA"); + break; + } /* switch */ + + /* print the protocol's name (ie TLS 1.0) */ + tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(pThis->sess)); + dbgprintf("- Protocol: %s\n", tmp); + + /* print the certificate type of the peer. + * ie X.509 + */ + tmp = gnutls_certificate_type_get_name( + gnutls_certificate_type_get(pThis->sess)); + + dbgprintf("- Certificate Type: %s\n", tmp); + + /* print the compression algorithm (if any) + */ + tmp = gnutls_compression_get_name( gnutls_compression_get(pThis->sess)); + dbgprintf("- Compression: %s\n", tmp); + + /* print the name of the cipher used. + * ie 3DES. + */ + tmp = gnutls_cipher_get_name(gnutls_cipher_get(pThis->sess)); + dbgprintf("- Cipher: %s\n", tmp); + + /* Print the MAC algorithms name. + * ie SHA1 + */ + tmp = gnutls_mac_get_name(gnutls_mac_get(pThis->sess)); + dbgprintf("- MAC: %s\n", tmp); + + RETiRet; +} +#endif + + +/* Convert a fingerprint to printable data. The conversion is carried out + * according IETF I-D syslog-transport-tls-12. The fingerprint string is + * returned in a new cstr object. It is the caller's responsibility to + * destruct that object. + * rgerhards, 2008-05-08 + */ +static rsRetVal +GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr) +{ + cstr_t *pStr = NULL; + uchar buf[4]; + size_t i; + DEFiRet; + + CHKiRet(rsCStrConstruct(&pStr)); + CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4)); + for(i = 0 ; i < sizeFingerprint ; ++i) { + snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]); + CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3)); + } + CHKiRet(rsCStrFinish(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pStr != NULL) + rsCStrDestruct(&pStr); + } + RETiRet; +} + + +/* a thread-safe variant of gnutls_strerror + * The caller must free the returned string. + * rgerhards, 2008-04-30 + */ +uchar *gtlsStrerror(int error) +{ + uchar *pErr; + + pthread_mutex_lock(&mutGtlsStrerror); + pErr = (uchar*) strdup(gnutls_strerror(error)); + pthread_mutex_unlock(&mutGtlsStrerror); + + return pErr; +} + + +/* try to receive a record from the remote peer. This works with + * our own abstraction and handles local buffering and EAGAIN. + * See details on local buffering in Rcv(9 header-comment. + * This function MUST only be called when the local buffer is + * empty. Calling it otherwise will cause losss of current buffer + * data. + * rgerhards, 2008-06-24 + */ +rsRetVal +gtlsRecordRecv(nsd_gtls_t *pThis) +{ + ssize_t lenRcvd; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF); + if(lenRcvd >= 0) { + pThis->lenRcvBuf = lenRcvd; + pThis->ptrRcvBuf = 0; + } else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) { + pThis->rtryCall = gtlsRtry_recv; + dbgprintf("GnuTLS receive requires a retry (this most probably is OK and no error condition)\n"); + ABORT_FINALIZE(RS_RET_RETRY); + } else { + int gnuRet; /* TODO: build a specific function for GnuTLS error reporting */ + CHKgnutls(lenRcvd); /* this will abort the function */ + } + +finalize_it: + dbgprintf("gtlsRecordRecv return. nsd %p, iRet %d, lenRcvd %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, (int) lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + + +/* add our own certificate to the certificate set, so that the peer + * can identify us. Please note that we try to use mutual authentication, + * so we always add a cert, even if we are in the client role (later, + * this may be controlled by a config setting). + * rgerhards, 2008-05-15 + */ +static rsRetVal +gtlsAddOurCert(void) +{ + int gnuRet; + uchar *keyFile; + uchar *certFile; + uchar *pGnuErr; /* for GnuTLS error reporting */ + DEFiRet; + + certFile = glbl.GetDfltNetstrmDrvrCertFile(); + keyFile = glbl.GetDfltNetstrmDrvrKeyFile(); + dbgprintf("GTLS certificate file: '%s'\n", certFile); + dbgprintf("GTLS key file: '%s'\n", keyFile); + CHKgnutls(gnutls_certificate_set_x509_key_file(xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM)); + +finalize_it: + if(iRet != RS_RET_OK) { + pGnuErr = gtlsStrerror(gnuRet); + errno = 0; + errmsg.LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', " + "key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile); + free(pGnuErr); + } + RETiRet; +} + + +/* globally initialize GnuTLS */ +static rsRetVal +gtlsGlblInit(void) +{ + int gnuRet; + uchar *cafile; + DEFiRet; + + /* gcry_control must be called first, so that the thread system is correctly set up */ + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + CHKgnutls(gnutls_global_init()); + + /* X509 stuff */ + CHKgnutls(gnutls_certificate_allocate_credentials(&xcred)); + + /* sets the trusted cas file */ + cafile = glbl.GetDfltNetstrmDrvrCAF(); + dbgprintf("GTLS CA file: '%s'\n", cafile); + gnuRet = gnutls_certificate_set_x509_trust_file(xcred, (char*)cafile, GNUTLS_X509_FMT_PEM); + if(gnuRet < 0) { + /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */ + uchar *pErr = gtlsStrerror(gnuRet); + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); + free(pErr); + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + +# ifdef DEBUG +#if 0 /* do this in special cases only. WARNING: if active, it may reveal sensitive information! */ + /* intialize log function - set a level only for hard-to-find bugs */ + gnutls_global_set_log_function(logFunction); + gnutls_global_set_log_level(10); /* 0 (no) to 9 (most), 10 everything */ +# endif +# endif + +finalize_it: + RETiRet; +} + +static rsRetVal +gtlsInitSession(nsd_gtls_t *pThis) +{ + DEFiRet; + int gnuRet; + gnutls_session session; + + gnutls_init(&session, GNUTLS_SERVER); + pThis->bHaveSess = 1; + pThis->bIsInitiator = 0; + + /* avoid calling all the priority functions, since the defaults are adequate. */ + CHKgnutls(gnutls_set_default_priority(session)); + CHKgnutls(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred)); + + /* request client certificate if any. */ + gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST); + gnutls_dh_set_prime_bits(session, DH_BITS); + + pThis->sess = session; + +finalize_it: + RETiRet; +} + + +static rsRetVal +generate_dh_params(void) +{ + int gnuRet; + DEFiRet; + /* Generate Diffie Hellman parameters - for use with DHE + * kx algorithms. These should be discarded and regenerated + * once a day, once a week or once a month. Depending on the + * security requirements. + */ + CHKgnutls(gnutls_dh_params_init( &dh_params)); + CHKgnutls(gnutls_dh_params_generate2( dh_params, DH_BITS)); +finalize_it: + RETiRet; +} + + +/* set up all global things that are needed for server operations + * rgerhards, 2008-04-30 + */ +static rsRetVal +gtlsGlblInitLstn(void) +{ + DEFiRet; + + if(bGlblSrvrInitDone == 0) { + /* we do not use CRLs right now, and I doubt we'll ever do. This functionality is + * considered legacy. -- rgerhards, 2008-05-05 + */ + /*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/ + CHKiRet(generate_dh_params()); + gnutls_certificate_set_dh_params(xcred, dh_params); /* this is void */ + bGlblSrvrInitDone = 1; /* we are all set now */ + + /* now we need to add our certificate */ + CHKiRet(gtlsAddOurCert()); + } + +finalize_it: + RETiRet; +} + + +/* Obtain the CN from the DN field and hand it back to the caller + * (which is responsible for destructing it). We try to follow + * RFC2253 as far as it makes sense for our use-case. This function + * is considered a compromise providing good-enough correctness while + * limiting code size and complexity. If a problem occurs, we may enhance + * this function. A (pointer to a) certificate must be caller-provided. + * If no CN is contained in the cert, no string is returned + * (*ppstrCN remains NULL). *ppstrCN MUST be NULL on entry! + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsGetCN(nsd_gtls_t *pThis, gnutls_x509_crt *pCert, cstr_t **ppstrCN) +{ + DEFiRet; + int gnuRet; + int i; + int bFound; + cstr_t *pstrCN = NULL; + size_t size; + /* big var the last, so we hope to have all we usually neeed within one mem cache line */ + uchar szDN[1024]; /* this should really be large enough for any non-malicious case... */ + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(pCert != NULL); + assert(ppstrCN != NULL); + assert(*ppstrCN == NULL); + + size = sizeof(szDN); + CHKgnutls(gnutls_x509_crt_get_dn(*pCert, (char*)szDN, &size)); + + /* now search for the CN part */ + i = 0; + bFound = 0; + while(!bFound && szDN[i] != '\0') { + /* note that we do not overrun our string due to boolean shortcut + * operations. If we have '\0', the if does not match and evaluation + * stops. Order of checks is obviously important! + */ + if(szDN[i] == 'C' && szDN[i+1] == 'N' && szDN[i+2] == '=') { + bFound = 1; + i += 2; + } + i++; + + } + + if(!bFound) { + FINALIZE; /* we are done */ + } + + /* we found a common name, now extract it */ + CHKiRet(rsCStrConstruct(&pstrCN)); + while(szDN[i] != '\0' && szDN[i] != ',') { + if(szDN[i] == '\\') { + /* hex escapes are not implemented */ + ++i; /* escape char processed */ + if(szDN[i] == '\0') + ABORT_FINALIZE(RS_RET_CERT_INVALID_DN); + CHKiRet(rsCStrAppendChar(pstrCN, szDN[i])); + } else { + CHKiRet(rsCStrAppendChar(pstrCN, szDN[i])); + } + ++i; /* char processed */ + } + CHKiRet(rsCStrFinish(pstrCN)); + + /* we got it - we ignore the rest of the DN string (if any). So we may + * not detect if it contains more than one CN + */ + + *ppstrCN = pstrCN; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pstrCN != NULL) + rsCStrDestruct(&pstrCN); + } + + RETiRet; +} + + +/* Check the peer's ID in fingerprint auth mode. + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) +{ + uchar fingerprint[20]; + size_t size; + cstr_t *pstrFingerprint = NULL; + int bFoundPositiveMatch; + permittedPeers_t *pPeer; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* obtain the SHA1 fingerprint */ + size = sizeof(fingerprint); + CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size)); + CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint)); + dbgprintf("peer's certificate SHA1 fingerprint: %s\n", rsCStrGetSzStr(pstrFingerprint)); + + /* now search through the permitted peers to see if we can find a permitted one */ + bFoundPositiveMatch = 0; + pPeer = pThis->pPermPeers; + while(pPeer != NULL && !bFoundPositiveMatch) { + if(!rsCStrSzStrCmp(pstrFingerprint, pPeer->pszID, strlen((char*) pPeer->pszID))) { + bFoundPositiveMatch = 1; + } else { + pPeer = pPeer->pNext; + } + } + + if(!bFoundPositiveMatch) { + dbgprintf("invalid peer fingerprint, not permitted to talk to it\n"); + if(pThis->bReportAuthErr == 1) { + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are " + "not permitted to talk to it", rsCStrGetSzStr(pstrFingerprint)); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); + } + +finalize_it: + if(pstrFingerprint != NULL) + rsCStrDestruct(&pstrFingerprint); + RETiRet; +} + + +/* Perform a match on ONE peer name obtained from the certificate. This name + * is checked against the set of configured credentials. *pbFoundPositiveMatch is + * set to 1 if the ID matches. *pbFoundPositiveMatch must have been initialized + * to 0 by the caller (this is a performance enhancement as we expect to be + * called multiple times). + * TODO: implemet wildcards? + * rgerhards, 2008-05-26 + */ +static rsRetVal +gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatch) +{ + permittedPeers_t *pPeer; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(pszPeerID != NULL); + assert(pbFoundPositiveMatch != NULL); + + if(pThis->pPermPeers) { /* do we have configured peer IDs? */ + pPeer = pThis->pPermPeers; + while(pPeer != NULL) { + CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch)); + if(*pbFoundPositiveMatch) + break; + pPeer = pPeer->pNext; + } + } else { + /* we do not have configured peer IDs, so we use defaults */ + if( pThis->pszConnectHost + && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) { + *pbFoundPositiveMatch = 1; + } + } + +finalize_it: + RETiRet; +} + + +/* Check the peer's ID in name auth mode. + * rgerhards, 2008-05-22 + */ +static rsRetVal +gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert) +{ + uchar lnBuf[256]; + char szAltName[1024]; /* this is sufficient for the DNSNAME... */ + int iAltName; + size_t szAltNameLen; + int bFoundPositiveMatch; + cstr_t *pStr = NULL; + cstr_t *pstrCN = NULL; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + bFoundPositiveMatch = 0; + CHKiRet(rsCStrConstruct(&pStr)); + + /* first search through the dNSName subject alt names */ + iAltName = 0; + while(!bFoundPositiveMatch) { /* loop broken below */ + szAltNameLen = sizeof(szAltName); + gnuRet = gnutls_x509_crt_get_subject_alt_name(*pCert, iAltName, + szAltName, &szAltNameLen, NULL); + if(gnuRet < 0) + break; + else if(gnuRet == GNUTLS_SAN_DNSNAME) { + dbgprintf("subject alt dnsName: '%s'\n", szAltName); + snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)szAltName, &bFoundPositiveMatch)); + /* do NOT break, because there may be multiple dNSName's! */ + } + ++iAltName; + } + + if(!bFoundPositiveMatch) { + /* if we did not succeed so far, we try the CN part of the DN... */ + CHKiRet(gtlsGetCN(pThis, pCert, &pstrCN)); + if(pstrCN != NULL) { /* NULL if there was no CN present */ + dbgprintf("gtls now checking auth for CN '%s'\n", rsCStrGetSzStr(pstrCN)); + snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", rsCStrGetSzStr(pstrCN)); + CHKiRet(rsCStrAppendStr(pStr, lnBuf)); + CHKiRet(gtlsChkOnePeerName(pThis, rsCStrGetSzStr(pstrCN), &bFoundPositiveMatch)); + } + } + + if(!bFoundPositiveMatch) { + dbgprintf("invalid peer name, not permitted to talk to it\n"); + if(pThis->bReportAuthErr == 1) { + CHKiRet(rsCStrFinish(pStr)); + errno = 0; + errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - " + "not permitted to talk to it. Names: %s", + rsCStrGetSzStr(pStr)); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT); + } + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + if(pstrCN != NULL) + rsCStrDestruct(&pstrCN); + RETiRet; +} + + +/* check the ID of the remote peer - used for both fingerprint and + * name authentication. This is common code. Will call into specific + * drivers once the certificate has been obtained. + * rgerhards, 2008-05-08 + */ +static rsRetVal +gtlsChkPeerID(nsd_gtls_t *pThis) +{ + const gnutls_datum *cert_list; + unsigned int list_size = 0; + gnutls_x509_crt cert; + int bMustDeinitCert = 0; + int gnuRet; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* This function only works for X.509 certificates. */ + if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509) + return RS_RET_TLS_CERT_ERR; + + cert_list = gnutls_certificate_get_peers(pThis->sess, &list_size); + + if(list_size < 1) { + if(pThis->bReportAuthErr == 1) { + errno = 0; + errmsg.LogError(0, RS_RET_TLS_NO_CERT, "error: peer did not provide a certificate, " + "not permitted to talk to it"); + pThis->bReportAuthErr = 0; + } + ABORT_FINALIZE(RS_RET_TLS_NO_CERT); + } + + /* If we reach this point, we have at least one valid certificate. + * We always use only the first certificate. As of GnuTLS documentation, the + * first certificate always contains the remote peer's own certificate. All other + * certificates are issuer's certificates (up the chain). We are only interested + * in the first certificate, which is our peer. -- rgerhards, 2008-05-08 + */ + CHKgnutls(gnutls_x509_crt_init(&cert)); + bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */ + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)); + + /* Now we see which actual authentication code we must call. */ + if(pThis->authMode == GTLS_AUTH_CERTFINGERPRINT) { + CHKiRet(gtlsChkPeerFingerprint(pThis, &cert)); + } else { + assert(pThis->authMode == GTLS_AUTH_CERTNAME); + CHKiRet(gtlsChkPeerName(pThis, &cert)); + } + +finalize_it: + if(bMustDeinitCert) + gnutls_x509_crt_deinit(cert); + + RETiRet; +} + + +/* Verify the validity of the remote peer's certificate. + * rgerhards, 2008-05-21 + */ +static rsRetVal +gtlsChkPeerCertValidity(nsd_gtls_t *pThis) +{ + DEFiRet; + char *pszErrCause; + int gnuRet; + cstr_t *pStr; + unsigned stateCert; + const gnutls_datum *cert_list; + unsigned cert_list_size = 0; + gnutls_x509_crt cert; + unsigned i; + time_t ttCert; + time_t ttNow; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* check if we have at least one cert */ + cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size); + if(cert_list_size < 1) { + errno = 0; + errmsg.LogError(0, RS_RET_TLS_NO_CERT, "peer did not provide a certificate, not permitted to talk to it"); + ABORT_FINALIZE(RS_RET_TLS_NO_CERT); + } + + CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert)); + + if(stateCert & GNUTLS_CERT_INVALID) { + /* provide error details if we have them */ + if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) { + pszErrCause = "signer not found"; + } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) { + pszErrCause = "signer is not a CA"; + } else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) { + pszErrCause = "insecure algorithm"; + } else if(stateCert & GNUTLS_CERT_REVOKED) { + pszErrCause = "certificate revoked"; + } else { + pszErrCause = "GnuTLS returned no specific reason"; + dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate " + "status is %d\n", stateCert); + } + errmsg.LogError(0, NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s", + pszErrCause); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, NO_ERRCODE, "invalid cert info: %s", rsCStrGetSzStr(pStr)); + rsCStrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_INVALID); + } + + /* get current time for certificate validation */ + if(time(&ttNow) == -1) + ABORT_FINALIZE(RS_RET_SYS_ERR); + + /* as it looks, we need to validate the expiration dates ourselves... + * We need to loop through all certificates as we need to make sure the + * interim certificates are also not expired. + */ + for(i = 0 ; i < cert_list_size ; ++i) { + CHKgnutls(gnutls_x509_crt_init(&cert)); + CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER)); + ttCert = gnutls_x509_crt_get_activation_time(cert); + if(ttCert == -1) + ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); + else if(ttCert > ttNow) { + errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer: certificate %d not yet active", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", rsCStrGetSzStr(pStr)); + rsCStrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE); + } + + ttCert = gnutls_x509_crt_get_expiration_time(cert); + if(ttCert == -1) + ABORT_FINALIZE(RS_RET_TLS_CERT_ERR); + else if(ttCert < ttNow) { + errmsg.LogError(0, RS_RET_CERT_EXPIRED, "not permitted to talk to peer: certificate %d expired", i); + gtlsGetCertInfo(pThis, &pStr); + errmsg.LogError(0, RS_RET_CERT_EXPIRED, "invalid cert info: %s", rsCStrGetSzStr(pStr)); + rsCStrDestruct(&pStr); + ABORT_FINALIZE(RS_RET_CERT_EXPIRED); + } + gnutls_x509_crt_deinit(cert); + } + +finalize_it: + RETiRet; +} + + +/* check if it is OK to talk to the remote peer + * rgerhards, 2008-05-21 + */ +rsRetVal +gtlsChkPeerAuth(nsd_gtls_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + /* call the actual function based on current auth mode */ + switch(pThis->authMode) { + case GTLS_AUTH_CERTNAME: + /* if we check the name, we must ensure the cert is valid */ + CHKiRet(gtlsChkPeerCertValidity(pThis)); + CHKiRet(gtlsChkPeerID(pThis)); + break; + case GTLS_AUTH_CERTFINGERPRINT: + CHKiRet(gtlsChkPeerID(pThis)); + break; + case GTLS_AUTH_CERTVALID: + CHKiRet(gtlsChkPeerCertValidity(pThis)); + break; + case GTLS_AUTH_CERTANON: + FINALIZE; + break; + } + +finalize_it: + RETiRet; +} + + +/* globally de-initialize GnuTLS */ +static rsRetVal +gtlsGlblExit(void) +{ + DEFiRet; + /* X509 stuff */ + gnutls_certificate_free_credentials(xcred); + gnutls_global_deinit(); /* we are done... */ + RETiRet; +} + + +/* end a GnuTLS session + * The function checks if we have a session and ends it only if so. So it can + * always be called, even if there currently is no session. + */ +static rsRetVal +gtlsEndSess(nsd_gtls_t *pThis) +{ + int gnuRet; + DEFiRet; + + if(pThis->bHaveSess) { + if(pThis->bIsInitiator) { + gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR); + while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) { + gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR); + } + } + gnutls_deinit(pThis->sess); + } + RETiRet; +} + + +/* a small wrapper for gnutls_transport_set_ptr(). The main intension for + * creating this wrapper is to get the annoying "cast to pointer from different + * size" compiler warning just once. There seems to be no way around it, see: + * http://lists.gnu.org/archive/html/help-gnutls/2008-05/msg00000.html + * rgerhards, 2008.05-07 + */ +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +static inline void +gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock) +{ + /* Note: the compiler warning for the next line is OK - see header comment! */ + gnutls_transport_set_ptr(pThis->sess, (gnutls_transport_ptr_t) sock); +} +#pragma GCC diagnostic warning "-Wint-to-pointer-cast" + +/* ---------------------------- end GnuTLS specifics ---------------------------- */ + + +/* Standard-Constructor */ +BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */ + iRet = nsd_ptcp.Construct(&pThis->pTcp); + pThis->bReportAuthErr = 1; +ENDobjConstruct(nsd_gtls) + + +/* destructor for the nsd_gtls object */ +BEGINobjDestruct(nsd_gtls) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsd_gtls) + if(pThis->iMode == 1) { + gtlsEndSess(pThis); + } + + if(pThis->pTcp != NULL) { + nsd_ptcp.Destruct(&pThis->pTcp); + } + + if(pThis->pszConnectHost != NULL) { + free(pThis->pszConnectHost); + } + + if(pThis->pszRcvBuf == NULL) { + free(pThis->pszRcvBuf); + } + + if(pThis->bOurCertIsInit) + gnutls_x509_crt_deinit(pThis->ourCert); + if(pThis->bOurKeyIsInit) + gnutls_x509_privkey_deinit(pThis->ourKey); +ENDobjDestruct(nsd_gtls) + + +/* Set the driver mode. For us, this has the following meaning: + * 0 - work in plain tcp mode, without tls (e.g. before a STARTTLS) + * 1 - work in TLS mode + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetMode(nsd_t *pNsd, int mode) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(mode != 0 && mode != 1) { + errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by " + "gtls netstream driver", mode); + ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE); + } + + pThis->iMode = mode; + +finalize_it: + RETiRet; +} + + +/* Set the authentication mode. For us, the following is supported: + * anon - no certificate checks whatsoever (discouraged, but supported) + * x509/certvalid - (just) check certificate validity + * x509/fingerprint - certificate fingerprint + * x509/name - cerfificate name check + * mode == NULL is valid and defaults to x509/name + * rgerhards, 2008-05-16 + */ +static rsRetVal +SetAuthMode(nsd_t *pNsd, uchar *mode) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) { + pThis->authMode = GTLS_AUTH_CERTNAME; + } else if(!strcasecmp((char*) mode, "x509/fingerprint")) { + pThis->authMode = GTLS_AUTH_CERTFINGERPRINT; + } else if(!strcasecmp((char*) mode, "x509/certvalid")) { + pThis->authMode = GTLS_AUTH_CERTVALID; + } else if(!strcasecmp((char*) mode, "anon")) { + pThis->authMode = GTLS_AUTH_CERTANON; + } else { + errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " + "gtls netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + +/* TODO: clear stored IDs! */ + +finalize_it: +dbgprintf("gtls auth mode %d set\n", pThis->authMode); + RETiRet; +} + + +/* Set permitted peers. It is depending on the auth mode if this are + * fingerprints or names. -- rgerhards, 2008-05-19 + */ +static rsRetVal +SetPermPeers(nsd_t *pNsd, permittedPeers_t *pPermPeers) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + if(pPermPeers == NULL) + FINALIZE; + + if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT && pThis->authMode != GTLS_AUTH_CERTNAME) { + errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by " + "gtls netstream driver in the configured authentication mode - ignored"); + ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); + } + + pThis->pPermPeers = pPermPeers; + +finalize_it: + RETiRet; +} + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. -- rgerhards, 2008-04-18 + */ +static rsRetVal +SetSock(nsd_t *pNsd, int sock) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + assert(sock >= 0); + + nsd_ptcp.SetSock(pThis->pTcp, sock); + + RETiRet; +} + + +/* abort a connection. This is meant to be called immediately + * before the Destruct call. -- rgerhards, 2008-03-24 + */ +static rsRetVal +Abort(nsd_t *pNsd) +{ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + + if(pThis->iMode == 0) { + nsd_ptcp.Abort(pThis->pTcp); + } + + RETiRet; +} + + + +/* initialize the tcp socket for a listner + * Here, we use the ptcp driver - because there is nothing special + * at this point with GnuTLS. Things become special once we accept + * a session, but not during listener setup. + * gerhards, 2008-04-25 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + CHKiRet(gtlsGlblInitLstn()); + iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax); +finalize_it: + RETiRet; +} + + +/* This function checks if the connection is still alive - well, kind of... + * This is a dummy here. For details, check function common in ptcp driver. + * rgerhards, 2008-06-09 + */ +static void +CheckConnection(nsd_t __attribute__((unused)) *pNsd) +{ + /* dummy, do nothing */ +} + + +/* get the remote hostname. The returned hostname must be freed by the caller. + * rgerhards, 2008-04-25 + */ +static rsRetVal +GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName); + RETiRet; +} + + +/* get the remote host's IP address. The returned string must be freed by the + * caller. -- rgerhards, 2008-04-25 + */ +static rsRetVal +GetRemoteIP(nsd_t *pNsd, uchar **ppszIP) +{ + DEFiRet; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ppszIP); + RETiRet; +} + + +/* accept an incoming connection request - here, we do the usual accept + * handling. TLS specific handling is done thereafter (and if we run in TLS + * mode at this time). + * rgerhards, 2008-04-25 + */ +static rsRetVal +AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) +{ + DEFiRet; + int gnuRet; + nsd_gtls_t *pNew = NULL; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert((pThis), nsd_gtls); + CHKiRet(nsd_gtlsConstruct(&pNew)); // TODO: prevent construct/destruct! + CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp)); + CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp)); + + if(pThis->iMode == 0) { + /* we are in non-TLS mode, so we are done */ + *ppNew = (nsd_t*) pNew; + FINALIZE; + } + + /* if we reach this point, we are in TLS mode */ + CHKiRet(gtlsInitSession(pNew)); + gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock); + pNew->authMode = pThis->authMode; + pNew->pPermPeers = pThis->pPermPeers; + + /* we now do the handshake. This is a bit complicated, because we are + * on non-blocking sockets. Usually, the handshake will not complete + * immediately, so that we need to retry it some time later. + */ + gnuRet = gnutls_handshake(pNew->sess); + if(gnuRet == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) { + pNew->rtryCall = gtlsRtry_handshake; + dbgprintf("GnuTLS handshake does not complete immediately - setting to retry (this is OK and normal)\n"); + } else if(gnuRet == 0) { + /* we got a handshake, now check authorization */ + CHKiRet(gtlsChkPeerAuth(pNew)); + } else { + ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR); + } + + pNew->iMode = 1; /* this session is now in TLS mode! */ + + *ppNew = (nsd_t*) pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + nsd_gtlsDestruct(&pNew); + } + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. -- rgerhards, 2008-03-17 + * The function now follows the usual iRet calling sequence. + * With GnuTLS, we may need to restart a recv() system call. If so, we need + * to supply the SAME buffer on the retry. We can not assure this, as the + * caller is free to call us with any buffer location (and in current + * implementation, it is on the stack and extremely likely to change). To + * work-around this problem, we allocate a buffer ourselfs and always receive + * into that buffer. We pass data on to the caller only after we have received it. + * To save some space, we allocate that internal buffer only when it is actually + * needed, which means when we reach this function for the first time. To keep + * the algorithm simple, we always supply data only from the internal buffer, + * even if it is a single byte. As we have a stream, the caller must be prepared + * to accept messages in any order, so we do not need to take care about this. + * Please note that the logic also forces us to do some "faking" in select(), as + * we must provide a fake "is ready for readign" status if we have data inside our + * buffer. -- rgerhards, 2008-06-23 + */ +static rsRetVal +Rcv(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + DEFiRet; + ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf)); + FINALIZE; + } + + /* --- in TLS mode now --- */ + + /* Buffer logic applies only if we are in TLS mode. Here we + * assume that we will switch from plain to TLS, but never back. This + * assumption may be unsafe, but it is the model for the time being and I + * do not see any valid reason why we should switch back to plain TCP after + * we were in TLS mode. However, in that case we may lose something that + * is already in the receive buffer ... risk accepted. -- rgerhards, 2008-06-23 + */ + + if(pThis->pszRcvBuf == NULL) { + /* we have no buffer, so we need to malloc one */ + CHKmalloc(pThis->pszRcvBuf = malloc(NSD_GTLS_MAX_RCVBUF)); + pThis->lenRcvBuf = -1; + } + + /* now check if we have something in our buffer. If so, we satisfy + * the request from buffer contents. + */ + if(pThis->lenRcvBuf == -1) { /* no data present, must read */ + CHKiRet(gtlsRecordRecv(pThis)); + } + + if(pThis->lenRcvBuf == 0) { /* EOS */ + *pLenBuf = 0; + ABORT_FINALIZE(RS_RET_CLOSED); + } + + /* if we reach this point, data is present in the buffer and must be copied */ + iBytesCopy = pThis->lenRcvBuf - pThis->ptrRcvBuf; + if(iBytesCopy > *pLenBuf) { + iBytesCopy = *pLenBuf; + } else { + pThis->lenRcvBuf = -1; /* buffer will be emptied below */ + } + + memcpy(pBuf, pThis->pszRcvBuf + pThis->ptrRcvBuf, iBytesCopy); + pThis->ptrRcvBuf += iBytesCopy; + *pLenBuf = iBytesCopy; + +finalize_it: + dbgprintf("gtlsRcv return. nsd %p, iRet %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, pThis->lenRcvBuf, pThis->ptrRcvBuf); + RETiRet; +} + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + int iSent; + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_gtls); + + if(pThis->bAbortConn) + ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ); + + if(pThis->iMode == 0) { + CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf)); + FINALIZE; + } + + /* in TLS mode now */ + while(1) { /* loop broken inside */ + iSent = gnutls_record_send(pThis->sess, pBuf, *pLenBuf); + if(iSent >= 0) { + *pLenBuf = iSent; + break; + } + if(iSent != GNUTLS_E_INTERRUPTED && iSent != GNUTLS_E_AGAIN) { + dbgprintf("unexpected GnuTLS error %d in %s:%d\n", iSent, __FILE__, __LINE__); + gnutls_perror(iSent); /* TODO: can we do better? */ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + } + +finalize_it: + RETiRet; +} + + +/* open a connection to a remote host (server). With GnuTLS, we always + * open a plain tcp socket and then, if in TLS mode, do a handshake on it. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) +{ + nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd; + int sock; + int gnuRet; + /* TODO: later? static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };*/ + static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 }; + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nsd_gtls); + assert(port != NULL); + assert(host != NULL); + + CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host)); + + if(pThis->iMode == 0) + FINALIZE; + + /* we reach this point if in TLS mode */ + CHKgnutls(gnutls_init(&pThis->sess, GNUTLS_CLIENT)); + pThis->bHaveSess = 1; + pThis->bIsInitiator = 1; + + /* in the client case, we need to set a callback that ensures our certificate + * will be presented to the server even if it is not signed by one of the server's + * trusted roots. This is necessary to support fingerprint authentication. + */ + /* store a pointer to ourselfs (needed by callback) */ + gnutls_session_set_ptr(pThis->sess, (void*)pThis); + iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */ + if(iRet == RS_RET_OK) { + gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback); + } else if(iRet != RS_RET_CERTLESS) { + FINALIZE; /* we have an error case! */ + } + + /* Use default priorities */ + CHKgnutls(gnutls_set_default_priority(pThis->sess)); + CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority)); + + /* put the x509 credentials to the current session */ + CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, xcred)); + + /* assign the socket to GnuTls */ + CHKiRet(nsd_ptcp.GetSock(pThis->pTcp, &sock)); + gtlsSetTransportPtr(pThis, sock); + + /* we need to store the hostname as an alternate mean of authentication if no + * permitted peer names are given. Using the hostname is quite useful. It permits + * auto-configuration of security if a commen root cert is present. -- rgerhards, 2008-05-26 + */ + CHKmalloc(pThis->pszConnectHost = (uchar*)strdup((char*)host)); + + /* and perform the handshake */ + CHKgnutls(gnutls_handshake(pThis->sess)); + dbgprintf("GnuTLS handshake succeeded\n"); + + /* now check if the remote peer is permitted to talk to us - ideally, we + * should do this during the handshake, but GnuTLS does not yet provide + * the necessary callbacks -- rgerhards, 2008-05-26 + */ + CHKiRet(gtlsChkPeerAuth(pThis)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->bHaveSess) { + gnutls_deinit(pThis->sess); + pThis->bHaveSess = 0; + } + } + + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nsd_gtls) +CODESTARTobjQueryInterface(nsd_gtls) + if(pIf->ifVersion != nsdCURR_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->Construct = (rsRetVal(*)(nsd_t**)) nsd_gtlsConstruct; + pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_gtlsDestruct; + pIf->Abort = Abort; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->Connect = Connect; + pIf->SetSock = SetSock; + pIf->SetMode = SetMode; + pIf->SetAuthMode = SetAuthMode; + pIf->SetPermPeers =SetPermPeers; + pIf->CheckConnection = CheckConnection; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; +finalize_it: +ENDobjQueryInterface(nsd_gtls) + + +/* exit our class + */ +BEGINObjClassExit(nsd_gtls, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsd_gtls) + gtlsGlblExit(); /* shut down GnuTLS */ + + /* release objects we no longer need */ + objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); + objRelease(net, LM_NET_FILENAME); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(nsd_gtls) + + +/* Initialize the nsd_gtls class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); + + /* now do global TLS init stuff */ + CHKiRet(gtlsGlblInit()); +ENDObjClassInit(nsd_gtls) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsdsel_gtlsClassExit(); + nsd_gtlsClassExit(); + pthread_mutex_destroy(&mutGtlsStrerror); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(nsd_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsdsel_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + + pthread_mutex_init(&mutGtlsStrerror, NULL); +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h new file mode 100644 index 00000000..52eea8ee --- /dev/null +++ b/runtime/nsd_gtls.h @@ -0,0 +1,92 @@ +/* An implementation of the nsd interface for GnuTLS. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSD_GTLS_H +#define INCLUDED_NSD_GTLS_H + +#include "nsd.h" + +#define NSD_GTLS_MAX_RCVBUF 8 * 1024 /* max size of buffer for message reception */ + +typedef enum { + gtlsRtry_None = 0, /**< no call needs to be retried */ + gtlsRtry_handshake = 1, + gtlsRtry_recv = 2 +} gtlsRtryCall_t; /**< IDs of calls that needs to be retried */ + +typedef nsd_if_t nsd_gtls_if_t; /* we just *implement* this interface */ + +/* the nsd_gtls object */ +struct nsd_gtls_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pTcp; /**< our aggregated nsd_ptcp data */ + uchar *pszConnectHost; /**< hostname used for connect - may be used to authenticate peer if no other name given */ + int iMode; /* 0 - plain tcp, 1 - TLS */ + int bAbortConn; /* if set, abort conncection (fatal error had happened) */ + enum { + GTLS_AUTH_CERTNAME = 0, + GTLS_AUTH_CERTFINGERPRINT = 1, + GTLS_AUTH_CERTVALID = 2, + GTLS_AUTH_CERTANON = 3 + } authMode; + gtlsRtryCall_t rtryCall;/**< what must we retry? */ + int bIsInitiator; /**< 0 if socket is the server end (listener), 1 if it is the initiator */ + gnutls_session sess; + int bHaveSess; /* as we don't know exactly which gnutls_session values are invalid, we use this one + to flag whether or not we are in a session (same as -1 for a socket meaning no sess) */ + int bReportAuthErr; /* only the first auth error is to be reported, this var triggers it. Initially, it is + * set to 1 and changed to 0 after the first report. It is changed back to 1 after + * one successful authentication. */ + permittedPeers_t *pPermPeers; /* permitted peers */ + gnutls_x509_crt ourCert; /**< our certificate, if in client mode (unused in server mode) */ + gnutls_x509_privkey ourKey; /**< our private key, if in client mode (unused in server mode) */ + short bOurCertIsInit; /**< 1 if our certificate is initialized and must be deinit on destruction */ + short bOurKeyIsInit; /**< 1 if our private key is initialized and must be deinit on destruction */ + char *pszRcvBuf; + int lenRcvBuf; /**< -1: empty, 0: connection closed, 1..NSD_GTLS_MAX_RCVBUF-1: data of that size present */ + int ptrRcvBuf; /**< offset for next recv operation if 0 < lenRcvBuf < NSD_GTLS_MAX_RCVBUF */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsd_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsd_gtls); +/* some prototypes for things used by our nsdsel_gtls helper class */ +uchar *gtlsStrerror(int error); +rsRetVal gtlsChkPeerAuth(nsd_gtls_t *pThis); +rsRetVal gtlsRecordRecv(nsd_gtls_t *pThis); +static inline rsRetVal gtlsHasRcvInBuffer(nsd_gtls_t *pThis) { + /* we have a valid receive buffer one such is allocated and + * NOT exhausted! + */ + dbgprintf("hasRcvInBuffer on nsd %p: pszRcvBuf %p, lenRcvBuf %d\n", pThis, + pThis->pszRcvBuf, pThis->lenRcvBuf); + return(pThis->pszRcvBuf != NULL && pThis->lenRcvBuf != -1); + } + + +/* the name of our library binary */ +#define LM_NSD_GTLS_FILENAME "lmnsd_gtls" + +#endif /* #ifndef INCLUDED_NSD_GTLS_H */ diff --git a/runtime/nsd_ptcp.c b/runtime/nsd_ptcp.c new file mode 100644 index 00000000..c3899f83 --- /dev/null +++ b/runtime/nsd_ptcp.c @@ -0,0 +1,781 @@ +/* nsd_ptcp.c + * + * An implementation of the nsd interface for plain tcp sockets. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <ctype.h> +#include <netdb.h> +#include <fnmatch.h> +#include <fcntl.h> +#include <unistd.h> + +#include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" +#include "net.h" +#include "netstrms.h" +#include "netstrm.h" +#include "nsdsel_ptcp.h" +#include "nsd_ptcp.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrms) +DEFobjCurrIf(netstrm) + + +/* a few deinit helpers */ + +/* close socket if open (may always be called) */ +static void +sockClose(int *pSock) +{ + if(*pSock >= 0) { + close(*pSock); + *pSock = -1; + } +} + +/* Standard-Constructor + */ +BEGINobjConstruct(nsd_ptcp) /* be sure to specify the object type also in END macro! */ + pThis->sock = -1; +ENDobjConstruct(nsd_ptcp) + + +/* destructor for the nsd_ptcp object */ +BEGINobjDestruct(nsd_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsd_ptcp) + sockClose(&pThis->sock); + if(pThis->pRemHostIP != NULL) + free(pThis->pRemHostIP); + if(pThis->pRemHostName != NULL) + free(pThis->pRemHostName); +ENDobjDestruct(nsd_ptcp) + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. -- rgerhards, 2008-04-18 + */ +static rsRetVal +GetSock(nsd_t *pNsd, int *pSock) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + assert(pSock != NULL); + + *pSock = pThis->sock; + + RETiRet; +} + + +/* Set the driver mode. We support no different modes, but allow mode + * 0 to be set to be compatible with config file defaults and the other + * drivers. + * rgerhards, 2008-04-28 + */ +static rsRetVal +SetMode(nsd_t __attribute__((unused)) *pNsd, int mode) +{ + DEFiRet; + if(mode != 0) { + errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by " + "ptcp netstream driver", mode); + ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE); + } +finalize_it: + RETiRet; +} + + +/* Set the authentication mode. For us, the following is supported: + * anon - no certificate checks whatsoever (discouraged, but supported) + * mode == NULL is valid and defaults to anon + * Actually, we do not even record the mode right now, because we can + * always work in anon mode, only. So there is no point in recording + * something if that's the only choice. What the function does is + * return an error if something is requested that we can not support. + * rgerhards, 2008-05-17 + */ +static rsRetVal +SetAuthMode(nsd_t __attribute__((unused)) *pNsd, uchar *mode) +{ + DEFiRet; + if(mode != NULL && strcasecmp((char*)mode, "anon")) { + errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by " + "ptcp netstream driver", mode); + ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED); + } + +finalize_it: + RETiRet; +} + + +/* Set the permitted peers. This is a dummy, always returning an + * error because we do not support fingerprint authentication. + * rgerhards, 2008-05-17 + */ +static rsRetVal +SetPermPeers(nsd_t __attribute__((unused)) *pNsd, permittedPeers_t __attribute__((unused)) *pPermPeers) +{ + DEFiRet; + + if(pPermPeers != NULL) { + errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by ptcp netstream driver"); + ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE); + } + +finalize_it: + RETiRet; +} + + + + +/* Provide access to the underlying OS socket. This is primarily + * useful for other drivers (like nsd_gtls) who utilize ourselfs + * for some of their functionality. + * This function sets the socket -- rgerhards, 2008-04-25 + */ +static rsRetVal +SetSock(nsd_t *pNsd, int sock) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + DEFiRet; + + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + assert(sock >= 0); + + pThis->sock = sock; + + RETiRet; +} + + +/* abort a connection. This is meant to be called immediately + * before the Destruct call. -- rgerhards, 2008-03-24 + */ +static rsRetVal +Abort(nsd_t *pNsd) +{ + struct linger ling; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + + DEFiRet; + ISOBJ_TYPE_assert((pThis), nsd_ptcp); + + if((pThis)->sock != -1) { + ling.l_onoff = 1; + ling.l_linger = 0; + if(setsockopt((pThis)->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) { + dbgprintf("could not set SO_LINGER, errno %d\n", errno); + } + } + + RETiRet; +} + + +/* Set pRemHost based on the address provided. This is to be called upon accept()ing + * a connection request. It must be provided by the socket we received the + * message on as well as a NI_MAXHOST size large character buffer for the FQDN. + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. If we detect a malicious + * hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide + * on how to deal with that. + * rgerhards, 2008-03-31 + */ +static rsRetVal +FillRemHost(nsd_ptcp_t *pThis, struct sockaddr *pAddr) +{ + int error; + uchar szIP[NI_MAXHOST] = ""; + uchar szHname[NI_MAXHOST] = ""; + struct addrinfo hints, *res; + size_t len; + + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(pAddr != NULL); + + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST); + + if(error) { + dbgprintf("Malformed from address %s\n", gai_strerror(error)); + strcpy((char*)szHname, "???"); + strcpy((char*)szIP, "???"); + ABORT_FINALIZE(RS_RET_INVALID_HNAME); + } + + if(!glbl.GetDisableDNS()) { + error = getnameinfo(pAddr, SALEN(pAddr), (char*)szHname, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + if(error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if(getaddrinfo((char*)szHname, NULL, &hints, &res) == 0) { + freeaddrinfo (res); + /* OK, we know we have evil, so let's indicate this to our caller */ + snprintf((char*)szHname, NI_MAXHOST, "[MALICIOUS:IP=%s]", szIP); + dbgprintf("Malicious PTR record, IP = \"%s\" HOST = \"%s\"", szIP, szHname); + iRet = RS_RET_MALICIOUS_HNAME; + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + } else { + strcpy((char*)szHname, (char*)szIP); + } + + /* We now have the names, so now let's allocate memory and store them permanently. + * (side note: we may hold on to these values for quite a while, thus we trim their + * memory consumption) + */ + len = strlen((char*)szIP) + 1; /* +1 for \0 byte */ + if((pThis->pRemHostIP = malloc(len)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pThis->pRemHostIP, szIP, len); + + len = strlen((char*)szHname) + 1; /* +1 for \0 byte */ + if((pThis->pRemHostName = malloc(len)) == NULL) { + free(pThis->pRemHostIP); /* prevent leak */ + pThis->pRemHostIP = NULL; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(pThis->pRemHostName, szHname, len); + +finalize_it: + RETiRet; +} + + +/* accept an incoming connection request + * rgerhards, 2008-04-22 + */ +static rsRetVal +AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew) +{ + int sockflags; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + nsd_ptcp_t *pNew = NULL; + int iNewSock = -1; + + DEFiRet; + assert(ppNew != NULL); + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + iNewSock = accept(pThis->sock, (struct sockaddr*) &addr, &addrlen); + if(iNewSock < 0) { + ABORT_FINALIZE(RS_RET_ACCEPT_ERR); + } + + /* construct our object so that we can use it... */ + CHKiRet(nsd_ptcpConstruct(&pNew)); + + CHKiRet(FillRemHost(pNew, (struct sockaddr*) &addr)); + + /* set the new socket to non-blocking IO -TODO:do we really need to do this here? Do we always want it? */ + if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(iNewSock, F_SETFL, sockflags); + } + if(sockflags == -1) { + dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + pNew->sock = iNewSock; + *ppNew = (nsd_t*) pNew; + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + nsd_ptcpDestruct(&pNew); + /* the close may be redundant, but that doesn't hurt... */ + sockClose(&iNewSock); + } + + RETiRet; +} + + +/* initialize tcp sockets for a listner. The initialized sockets are passed to the + * app-level caller via a callback. + * pLstnPort must point to a port name or number. NULL is NOT permitted. pLstnIP + * points to the port to listen to (NULL means "all"), iMaxSess has the maximum + * number of sessions permitted. + * rgerhards, 2008-04-22 + */ +static rsRetVal +LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*), + uchar *pLstnPort, uchar *pLstnIP, int iSessMax) +{ + DEFiRet; + netstrm_t *pNewStrm = NULL; + nsd_t *pNewNsd = NULL; + int error, maxs, on = 1; + int sock; + int numSocks; + int sockflags; + struct addrinfo hints, *res = NULL, *r; + + ISOBJ_TYPE_assert(pNS, netstrms); + assert(fAddLstn != NULL); + assert(pLstnPort != NULL); + assert(iSessMax >= 0); + + dbgprintf("creating tcp listen socket on port %s\n", pLstnPort); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = glbl.GetDefPFFamily(); + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo((char*)pLstnIP, (char*) pLstnPort, &hints, &res); + if(error) { + dbgprintf("error %d querying port '%s'\n", error, pLstnPort); + ABORT_FINALIZE(RS_RET_INVALID_PORT); + } + + /* Count max number of sockets we may open */ + for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + + numSocks = 0; /* num of sockets counter at start of array */ + for(r = res; r != NULL ; r = r->ai_next) { + sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if(sock < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + dbgprintf("error %d creating tcp listen socket", errno); + /* it is debatable if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +#ifdef IPV6_V6ONLY + if(r->ai_family == AF_INET6) { + int iOn = 1; + if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + close(sock); + continue; + } + } +#endif + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) { + dbgprintf("error %d setting tcp socket option\n", errno); + close(sock); + continue; + } + + /* We use non-blocking IO! */ + if((sockflags = fcntl(sock, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(sock, F_SETFL, sockflags); + } + if(sockflags == -1) { + dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno); + close(sock); + continue; + } + + + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#ifndef BSD + if(net.should_use_so_bsdcompat()) { + if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)"); + close(sock); + continue; + } + } +#endif + + if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + /* TODO: check if *we* bound the socket - else we *have* an error! */ + dbgprintf("error %d while binding tcp socket", errno); + close(sock); + continue; + } + + if(listen(sock, iSessMax / 10 + 5) < 0) { + /* If the listen fails, it most probably fails because we ask + * for a too-large backlog. So in this case we first set back + * to a fixed, reasonable, limit that should work. Only if + * that fails, too, we give up. + */ + dbgprintf("listen with a backlog of %d failed - retrying with default of 32.", + iSessMax / 10 + 5); + if(listen(sock, 32) < 0) { + dbgprintf("tcp listen error %d, suspending\n", errno); + close(sock); + continue; + } + } + + /* if we reach this point, we were able to obtain a valid socket, so we can + * construct a new netstrm obj and hand it over to the upper layers for inclusion + * into their socket array. -- rgerhards, 2008-04-23 + */ + CHKiRet(pNS->Drvr.Construct(&pNewNsd)); + CHKiRet(pNS->Drvr.SetSock(pNewNsd, sock)); + CHKiRet(pNS->Drvr.SetMode(pNewNsd, netstrms.GetDrvrMode(pNS))); + CHKiRet(pNS->Drvr.SetAuthMode(pNewNsd, netstrms.GetDrvrAuthMode(pNS))); + CHKiRet(pNS->Drvr.SetPermPeers(pNewNsd, netstrms.GetDrvrPermPeers(pNS))); + CHKiRet(netstrms.CreateStrm(pNS, &pNewStrm)); + pNewStrm->pDrvrData = (nsd_t*) pNewNsd; + CHKiRet(fAddLstn(pUsr, pNewStrm)); + pNewNsd = NULL; + pNewStrm = NULL; + ++numSocks; + } + + if(numSocks != maxs) + dbgprintf("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", numSocks, maxs); + + if(numSocks == 0) { + dbgprintf("No TCP listen sockets could successfully be initialized"); + ABORT_FINALIZE(RS_RET_COULD_NOT_BIND); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(pNewStrm != NULL) + netstrm.Destruct(&pNewStrm); + if(pNewNsd != NULL) + pNS->Drvr.Destruct(&pNewNsd); + } + + RETiRet; +} + + +/* receive data from a tcp socket + * The lenBuf parameter must contain the max buffer size on entry and contains + * the number of octets read (or -1 in case of error) on exit. This function + * never blocks, not even when called on a blocking socket. That is important + * for client sockets, which are set to block during send, but should not + * block when trying to read data. If *pLenBuf is -1, an error occured and + * errno holds the exact error cause. + * rgerhards, 2008-03-17 + */ +static rsRetVal +Rcv(nsd_t *pNsd, uchar *pRcvBuf, ssize_t *pLenBuf) +{ + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + *pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT); + + if(*pLenBuf == 0) { + ABORT_FINALIZE(RS_RET_CLOSED); + } else if (*pLenBuf < 0) { + ABORT_FINALIZE(RS_RET_ERR); + } + +finalize_it: + RETiRet; +} + + +/* send a buffer. On entry, pLenBuf contains the number of octets to + * write. On exit, it contains the number of octets actually written. + * If this number is lower than on entry, only a partial buffer has + * been written. + * rgerhards, 2008-03-19 + */ +static rsRetVal +Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ssize_t written; + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + written = send(pThis->sock, pBuf, *pLenBuf, 0); + + if(written == -1) { + switch(errno) { + case EAGAIN: + case EINTR: + /* this is fine, just retry... */ + written = 0; + break; + default: + ABORT_FINALIZE(RS_RET_IO_ERROR); + break; + } + } + + *pLenBuf = written; +finalize_it: + RETiRet; +} + + +/* open a connection to a remote host (server). + * rgerhards, 2008-03-19 + */ +static rsRetVal +Connect(nsd_t *pNsd, int family, uchar *port, uchar *host) +{ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + struct addrinfo *res = NULL; + struct addrinfo hints; + + DEFiRet; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(port != NULL); + assert(host != NULL); + assert(pThis->sock == -1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo((char*)host, (char*)port, &hints, &res) != 0) { + dbgprintf("error %d in getaddrinfo\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) != 0) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + sockClose(&pThis->sock); + } + + RETiRet; +} + + +/* get the remote hostname. The returned hostname must be freed by the + * caller. + * rgerhards, 2008-04-24 + */ +static rsRetVal +GetRemoteHName(nsd_t *pNsd, uchar **ppszHName) +{ + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(ppszHName != NULL); + + // TODO: how can the RemHost be empty? + CHKmalloc(*ppszHName = (uchar*)strdup(pThis->pRemHostName == NULL ? "" : (char*) pThis->pRemHostName)); + +finalize_it: + RETiRet; +} + + +/* This function checks if the connection is still alive - well, kind of... It + * is primarily being used for plain TCP syslog and it is quite a hack. However, + * as it seems to work, it is worth supporting it. The bottom line is that it + * should not be called by anything else but a plain tcp syslog sender. + * In order for it to work, it must be called *immediately* *before* the send() + * call. For details about what is done, see here: + * http://blog.gerhards.net/2008/06/getting-bit-more-reliability-from-plain.html + * rgerhards, 2008-06-09 + */ +static void +CheckConnection(nsd_t *pNsd) +{ + int rc; + char msgbuf[1]; /* dummy */ + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + + rc = recv(pThis->sock, msgbuf, 1, MSG_DONTWAIT | MSG_PEEK); + if(rc == 0) { + dbgprintf("CheckConnection detected broken connection - closing it\n"); + /* in this case, the remote peer had shut down the connection and we + * need to close our side, too. + */ + sockClose(&pThis->sock); + } +} + + +/* get the remote host's IP address. The returned string must be freed by the + * caller. + * rgerhards, 2008-04-24 + */ +static rsRetVal +GetRemoteIP(nsd_t *pNsd, uchar **ppszIP) +{ + DEFiRet; + nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd; + ISOBJ_TYPE_assert(pThis, nsd_ptcp); + assert(ppszIP != NULL); + + CHKmalloc(*ppszIP = (uchar*)strdup(pThis->pRemHostIP == NULL ? "" : (char*) pThis->pRemHostIP)); + +finalize_it: + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nsd_ptcp) +CODESTARTobjQueryInterface(nsd_ptcp) + if(pIf->ifVersion != nsdCURR_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->Construct = (rsRetVal(*)(nsd_t**)) nsd_ptcpConstruct; + pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_ptcpDestruct; + pIf->Abort = Abort; + pIf->GetSock = GetSock; + pIf->SetSock = SetSock; + pIf->SetMode = SetMode; + pIf->SetAuthMode = SetAuthMode; + pIf->SetPermPeers = SetPermPeers; + pIf->Rcv = Rcv; + pIf->Send = Send; + pIf->LstnInit = LstnInit; + pIf->AcceptConnReq = AcceptConnReq; + pIf->Connect = Connect; + pIf->GetRemoteHName = GetRemoteHName; + pIf->GetRemoteIP = GetRemoteIP; + pIf->CheckConnection = CheckConnection; +finalize_it: +ENDobjQueryInterface(nsd_ptcp) + + +/* exit our class + */ +BEGINObjClassExit(nsd_ptcp, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsd_ptcp) + /* release objects we no longer need */ + objRelease(net, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(netstrm, DONT_LOAD_LIB); + objRelease(netstrms, LM_NETSTRMS_FILENAME); +ENDObjClassExit(nsd_ptcp) + + +/* Initialize the nsd_ptcp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsd_ptcp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, CORE_COMPONENT)); + CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(netstrm, DONT_LOAD_LIB)); + + /* set our own handlers */ +ENDObjClassInit(nsd_ptcp) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + nsdsel_ptcpClassExit(); + nsd_ptcpClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(nsd_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + CHKiRet(nsdsel_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/nsd_ptcp.h b/runtime/nsd_ptcp.h new file mode 100644 index 00000000..efd3ed05 --- /dev/null +++ b/runtime/nsd_ptcp.h @@ -0,0 +1,47 @@ +/* An implementation of the nsd interface for plain tcp sockets. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSD_PTCP_H +#define INCLUDED_NSD_PTCP_H + +#include "nsd.h" +typedef nsd_if_t nsd_ptcp_if_t; /* we just *implement* this interface */ + +/* the nsd_ptcp object */ +struct nsd_ptcp_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + uchar *pRemHostIP; /**< IP address of remote peer (currently used in server mode, only) */ + uchar *pRemHostName; /**< host name of remote peer (currently used in server mode, only) */ + int sock; /**< the socket we use for regular, single-socket, operations */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsd_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsd_ptcp); + +/* the name of our library binary */ +#define LM_NSD_PTCP_FILENAME "lmnsd_ptcp" + +#endif /* #ifndef INCLUDED_NSD_PTCP_H */ diff --git a/runtime/nsdsel_gtls.c b/runtime/nsdsel_gtls.c new file mode 100644 index 00000000..c3a93bee --- /dev/null +++ b/runtime/nsdsel_gtls.c @@ -0,0 +1,260 @@ +/* nsdsel_gtls.c + * + * An implementation of the nsd select() interface for GnuTLS. + * + * Copyright (C) 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> +#include <gnutls/gnutls.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "nsd.h" +#include "nsd_gtls.h" +#include "nsd_ptcp.h" +#include "nsdsel_ptcp.h" +#include "nsdsel_gtls.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(nsdsel_ptcp) + + +/* Standard-Constructor + */ +BEGINobjConstruct(nsdsel_gtls) /* be sure to specify the object type also in END macro! */ + iRet = nsdsel_ptcp.Construct(&pThis->pTcp); +ENDobjConstruct(nsdsel_gtls) + + +/* destructor for the nsdsel_gtls object */ +BEGINobjDestruct(nsdsel_gtls) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsdsel_gtls) + if(pThis->pTcp != NULL) + nsdsel_ptcp.Destruct(&pThis->pTcp); +ENDobjDestruct(nsdsel_gtls) + + +/* Add a socket to the select set */ +static rsRetVal +Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls); + if(pNsdGTLS->iMode == 1) { + if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) { + ++pThis->iBufferRcvReady; + FINALIZE; + } + if(pNsdGTLS->rtryCall != gtlsRtry_None) { + if(gnutls_record_get_direction(pNsdGTLS->sess) == 0) { + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_RD)); + } else { + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_WR)); + } + FINALIZE; + } + } + + /* if we reach this point, we need no special handling */ + CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, waitOp)); + +finalize_it: + RETiRet; +} + + +/* perform the select() piNumReady returns how many descriptors are ready for IO + * TODO: add timeout! + */ +static rsRetVal +Select(nsdsel_t *pNsdsel, int *piNumReady) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + if(pThis->iBufferRcvReady > 0) { + /* we still have data ready! */ + *piNumReady = pThis->iBufferRcvReady; + } else { + iRet = nsdsel_ptcp.Select(pThis->pTcp, piNumReady); + } + + RETiRet; +} + + +/* retry an interrupted GTLS operation + * rgerhards, 2008-04-30 + */ +static rsRetVal +doRetry(nsd_gtls_t *pNsd) +{ + DEFiRet; + int gnuRet; + + dbgprintf("GnuTLS requested retry of %d operation - executing\n", pNsd->rtryCall); + + /* We follow a common scheme here: first, we do the systen call and + * then we check the result. So far, the result is checked after the + * switch, because the result check is the same for all calls. Note that + * this may change once we deal with the read and write calls (but + * probably this becomes an issue only when we begin to work on TLS + * for relp). -- rgerhards, 2008-04-30 + */ + switch(pNsd->rtryCall) { + case gtlsRtry_handshake: + gnuRet = gnutls_handshake(pNsd->sess); + if(gnuRet == 0) { + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + /* we got a handshake, now check authorization */ + CHKiRet(gtlsChkPeerAuth(pNsd)); + } + break; + case gtlsRtry_recv: + dbgprintf("retrying gtls recv, nsd: %p\n", pNsd); + CHKiRet(gtlsRecordRecv(pNsd)); + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + gnuRet = 0; + break; + default: + assert(0); /* this shall not happen! */ + dbgprintf("ERROR: pNsd->rtryCall invalid in nsdsel_gtls.c:%d\n", __LINE__); + gnuRet = 0; /* if it happens, we have at least a defined behaviour... ;) */ + break; + } + + if(gnuRet == 0) { + pNsd->rtryCall = gtlsRtry_None; /* we are done */ + } else if(gnuRet != GNUTLS_E_AGAIN && gnuRet != GNUTLS_E_INTERRUPTED) { + uchar *pErr = gtlsStrerror(gnuRet); + dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); + free(pErr); + pNsd->rtryCall = gtlsRtry_None; /* we are also done... ;) */ + ABORT_FINALIZE(RS_RET_GNUTLS_ERR); + } + /* if we are interrupted once again (else case), we do not need to + * change our status because we are already setup for retries. + */ + +finalize_it: + if(iRet != RS_RET_OK && iRet != RS_RET_CLOSED && iRet != RS_RET_RETRY) + pNsd->bAbortConn = 1; /* request abort */ + RETiRet; +} + + +/* check if a socket is ready for IO */ +static rsRetVal +IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady) +{ + DEFiRet; + nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel; + nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsdsel_gtls); + ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls); + if(pNsdGTLS->iMode == 1) { + if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) { + *pbIsReady = 1; + FINALIZE; + } + if(pNsdGTLS->rtryCall != gtlsRtry_None) { + CHKiRet(doRetry(pNsdGTLS)); + /* we used this up for our own internal processing, so the socket + * is not ready from the upper layer point of view. + */ + *pbIsReady = 0; + FINALIZE; + } + } + + CHKiRet(nsdsel_ptcp.IsReady(pThis->pTcp, pNsdGTLS->pTcp, waitOp, pbIsReady)); + +finalize_it: + RETiRet; +} + + +/* ------------------------------ end support for the select() interface ------------------------------ */ + + +/* queryInterface function */ +BEGINobjQueryInterface(nsdsel_gtls) +CODESTARTobjQueryInterface(nsdsel_gtls) + if(pIf->ifVersion != nsdCURR_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->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsConstruct; + pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsDestruct; + pIf->Add = Add; + pIf->Select = Select; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nsdsel_gtls) + + +/* exit our class + */ +BEGINObjClassExit(nsdsel_gtls, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_gtls) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(nsdsel_ptcp, LM_NSD_PTCP_FILENAME); +ENDObjClassExit(nsdsel_gtls) + + +/* Initialize the nsdsel_gtls class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsdsel_gtls, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(nsdsel_ptcp, LM_NSD_PTCP_FILENAME)); + + /* set our own handlers */ +ENDObjClassInit(nsdsel_gtls) +/* vi:set ai: + */ diff --git a/runtime/nsdsel_gtls.h b/runtime/nsdsel_gtls.h new file mode 100644 index 00000000..709ccd03 --- /dev/null +++ b/runtime/nsdsel_gtls.h @@ -0,0 +1,43 @@ +/* An implementation of the nsd select interface for GnuTLS. + * + * Copyright (C) 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSDSEL_GTLS_H +#define INCLUDED_NSDSEL_GTLS_H + +#include "nsd.h" +typedef nsdsel_if_t nsdsel_gtls_if_t; /* we just *implement* this interface */ + +/* the nsdsel_gtls object */ +struct nsdsel_gtls_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsdsel_t *pTcp; /* our aggregated ptcp sel handler (which does almost everything) */ + int iBufferRcvReady; /* number of descriptiors where no RD select is needed because we have data in buf */ +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsdsel_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsdsel_gtls); + +#endif /* #ifndef INCLUDED_NSDSEL_GTLS_H */ diff --git a/runtime/nsdsel_ptcp.c b/runtime/nsdsel_ptcp.c new file mode 100644 index 00000000..41b85e0c --- /dev/null +++ b/runtime/nsdsel_ptcp.c @@ -0,0 +1,196 @@ +/* nsdsel_ptcp.c + * + * An implementation of the nsd select() interface for plain tcp sockets. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "errmsg.h" +#include "nsd_ptcp.h" +#include "nsdsel_ptcp.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + + +/* Standard-Constructor + */ +BEGINobjConstruct(nsdsel_ptcp) /* be sure to specify the object type also in END macro! */ + pThis->maxfds = 0; + FD_ZERO(&pThis->readfds); + FD_ZERO(&pThis->writefds); +ENDobjConstruct(nsdsel_ptcp) + + +/* destructor for the nsdsel_ptcp object */ +BEGINobjDestruct(nsdsel_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nsdsel_ptcp) +ENDobjDestruct(nsdsel_ptcp) + + +/* Add a socket to the select set */ +static rsRetVal +Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; + nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd; + + ISOBJ_TYPE_assert(pSock, nsd_ptcp); + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + + switch(waitOp) { + case NSDSEL_RD: + FD_SET(pSock->sock, &pThis->readfds); + break; + case NSDSEL_WR: + FD_SET(pSock->sock, &pThis->writefds); + break; + case NSDSEL_RDWR: + FD_SET(pSock->sock, &pThis->readfds); + FD_SET(pSock->sock, &pThis->writefds); + break; + } + + if(pSock->sock > pThis->maxfds) + pThis->maxfds = pSock->sock; + + RETiRet; +} + + +/* perform the select() piNumReady returns how many descriptors are ready for IO + * TODO: add timeout! + */ +static rsRetVal +Select(nsdsel_t *pNsdsel, int *piNumReady) +{ + DEFiRet; + int i; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; + + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + assert(piNumReady != NULL); + + if(Debug) { // TODO: debug setting! + // TODO: name in dbgprintf! + dbgprintf("--------<NSDSEL_PTCP> calling select, active fds (max %d): ", pThis->maxfds); + for(i = 0; i <= pThis->maxfds; ++i) + if(FD_ISSET(i, &pThis->readfds) || FD_ISSET(i, &pThis->writefds)) + dbgprintf("%d ", i); + dbgprintf("\n"); + } + + /* now do the select */ + *piNumReady = select(pThis->maxfds+1, &pThis->readfds, &pThis->writefds, NULL, NULL); + + RETiRet; +} + + +/* check if a socket is ready for IO */ +static rsRetVal +IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady) +{ + DEFiRet; + nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel; + nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd; + + ISOBJ_TYPE_assert(pThis, nsdsel_ptcp); + ISOBJ_TYPE_assert(pSock, nsd_ptcp); + assert(pbIsReady != NULL); + + switch(waitOp) { + case NSDSEL_RD: + *pbIsReady = FD_ISSET(pSock->sock, &pThis->readfds); + break; + case NSDSEL_WR: + *pbIsReady = FD_ISSET(pSock->sock, &pThis->writefds); + break; + case NSDSEL_RDWR: + *pbIsReady = FD_ISSET(pSock->sock, &pThis->readfds) + | FD_ISSET(pSock->sock, &pThis->writefds); + break; + } + + RETiRet; +} + + +/* ------------------------------ end support for the select() interface ------------------------------ */ + + +/* queryInterface function */ +BEGINobjQueryInterface(nsdsel_ptcp) +CODESTARTobjQueryInterface(nsdsel_ptcp) + if(pIf->ifVersion != nsdCURR_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->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpConstruct; + pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpDestruct; + pIf->Add = Add; + pIf->Select = Select; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nsdsel_ptcp) + + +/* exit our class + */ +BEGINObjClassExit(nsdsel_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nsdsel_ptcp) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(nsdsel_ptcp) + + +/* Initialize the nsdsel_ptcp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nsdsel_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nsdsel_ptcp) +/* vi:set ai: + */ diff --git a/runtime/nsdsel_ptcp.h b/runtime/nsdsel_ptcp.h new file mode 100644 index 00000000..6c0c7fa7 --- /dev/null +++ b/runtime/nsdsel_ptcp.h @@ -0,0 +1,44 @@ +/* An implementation of the nsd select interface for plain tcp sockets. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSDSEL_PTCP_H +#define INCLUDED_NSDSEL_PTCP_H + +#include "nsd.h" +typedef nsdsel_if_t nsdsel_ptcp_if_t; /* we just *implement* this interface */ + +/* the nsdsel_ptcp object */ +struct nsdsel_ptcp_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int maxfds; + fd_set readfds; + fd_set writefds; +}; + +/* interface is defined in nsd.h, we just implement it! */ +#define nsdsel_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION + +/* prototypes */ +PROTOTYPEObj(nsdsel_ptcp); + +#endif /* #ifndef INCLUDED_NSDSEL_PTCP_H */ diff --git a/runtime/nssel.c b/runtime/nssel.c new file mode 100644 index 00000000..d11d5fe1 --- /dev/null +++ b/runtime/nssel.c @@ -0,0 +1,227 @@ +/* nssel.c + * + * The io waiter is a helper object enabling us to wait on a set of streams to become + * ready for IO - this is modelled after select(). We need this, because + * stream drivers may have different concepts. Consequently, + * the structure must contain nsd_t's from the same stream driver type + * only. This is implemented as a singly-linked list where every + * new element is added at the top of the list. + * + * Work on this module begun 2008-04-22 by Rainer Gerhards. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "rsyslog.h" +#include "obj.h" +#include "module-template.h" +#include "netstrm.h" +#include "nssel.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + + +/* load our low-level driver. This must be done before any + * driver-specific functions (allmost all...) can be carried + * out. Note that the driver's .ifIsLoaded is correctly + * initialized by calloc() and we depend on that. Please note that + * we do some name-mangeling. We know that each nsd driver also needs + * a nssel driver. So we simply append "sel" to the nsd driver name: This, + * of course, means that the driver name must match these rules, but that + * shouldn't be a real problem. + * WARNING: this code is mostly identical to similar code in + * netstrms.c - TODO: abstract it and move it to some common place. + * rgerhards, 2008-04-28 + */ +static rsRetVal +loadDrvr(nssel_t *pThis) +{ + DEFiRet; + uchar *pBaseDrvrName; + uchar szDrvrName[48]; /* 48 shall be large enough */ + + pBaseDrvrName = pThis->pBaseDrvrName; + if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */ + pBaseDrvrName = glbl.GetDfltNetstrmDrvr(); + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsdsel_%s", pBaseDrvrName) == sizeof(szDrvrName)) + ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG); + CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName)); + + pThis->Drvr.ifVersion = nsdCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean + * enough. -- rgerhards, 2008-04-18 + */ + CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pDrvrName != NULL) + free(pThis->pDrvrName); + pThis->pDrvrName = NULL; + } + RETiRet; +} + + +/* Standard-Constructor */ +BEGINobjConstruct(nssel) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(nssel) + + +/* destructor for the nssel object */ +BEGINobjDestruct(nssel) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(nssel) + if(pThis->pDrvrData != NULL) + pThis->Drvr.Destruct(&pThis->pDrvrData); + + /* and now we must release our driver, if we got one. We use the presence of + * a driver name string as load indicator (because we also need that string + * to release the driver + */ + if(pThis->pDrvrName != NULL) { + obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr); + free(pThis->pDrvrName); + } +ENDobjDestruct(nssel) + + +/* ConstructionFinalizer */ +static rsRetVal +ConstructFinalize(nssel_t *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + CHKiRet(loadDrvr(pThis)); + CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData)); +finalize_it: + RETiRet; +} + + +/* Add a stream object to the current select() set. + * Note that a single stream may have multiple "sockets" if + * it is a listener. If so, all of them are begin added. + */ +static rsRetVal +Add(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, nssel); + ISOBJ_TYPE_assert(pStrm, netstrm); + + CHKiRet(pThis->Drvr.Add(pThis->pDrvrData, pStrm->pDrvrData, waitOp)); + +finalize_it: + RETiRet; +} + + +/* wait for IO to happen on one of our netstreams. iNumReady has + * the number of ready "sockets" after the call. This function blocks + * until some are ready. EAGAIN is retried. + */ +static rsRetVal +Wait(nssel_t *pThis, int *piNumReady) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + assert(piNumReady != NULL); + iRet = pThis->Drvr.Select(pThis->pDrvrData, piNumReady); + RETiRet; +} + + +/* Check if a stream is ready for IO. *piNumReady contains the remaining number + * of ready streams. Note that this function may say the stream is not ready + * but still decrement *piNumReady. This can happen when (e.g. with TLS) the low + * level driver requires some IO which is hidden from the upper layer point of view. + * rgerhards, 2008-04-23 + */ +static rsRetVal +IsReady(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, nssel); + ISOBJ_TYPE_assert(pStrm, netstrm); + assert(pbIsReady != NULL); + assert(piNumReady != NULL); + iRet = pThis->Drvr.IsReady(pThis->pDrvrData, pStrm->pDrvrData, waitOp, pbIsReady); + RETiRet; +} + + +/* queryInterface function */ +BEGINobjQueryInterface(nssel) +CODESTARTobjQueryInterface(nssel) + if(pIf->ifVersion != nsselCURR_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->Construct = nsselConstruct; + pIf->ConstructFinalize = ConstructFinalize; + pIf->Destruct = nsselDestruct; + pIf->Add = Add; + pIf->Wait = Wait; + pIf->IsReady = IsReady; +finalize_it: +ENDobjQueryInterface(nssel) + + +/* exit our class + */ +BEGINObjClassExit(nssel, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(nssel) + /* release objects we no longer need */ + objRelease(glbl, CORE_COMPONENT); +ENDObjClassExit(nssel) + + +/* Initialize the nssel class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(nssel, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(nssel) +/* vi:set ai: + */ diff --git a/runtime/nssel.h b/runtime/nssel.h new file mode 100644 index 00000000..8cb34f5a --- /dev/null +++ b/runtime/nssel.h @@ -0,0 +1,56 @@ +/* Definitions for the nssel IO waiter. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NSSEL_H +#define INCLUDED_NSSEL_H + +#include "netstrms.h" + +/* the nssel object */ +struct nssel_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + nsd_t *pDrvrData; /**< the driver's data elements */ + uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */ + uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */ + nsdsel_if_t Drvr; /**< our stream driver */ +}; + + +/* interface */ +BEGINinterface(nssel) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*Construct)(nssel_t **ppThis); + rsRetVal (*ConstructFinalize)(nssel_t *pThis); + rsRetVal (*Destruct)(nssel_t **ppThis); + rsRetVal (*Add)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp); + rsRetVal (*Wait)(nssel_t *pThis, int *pNumReady); + rsRetVal (*IsReady)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady); +ENDinterface(nssel) +#define nsselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(nssel); + +/* the name of our library binary */ +#define LM_NSSEL_FILENAME LM_NETSTRMS_FILENAME + +#endif /* #ifndef INCLUDED_NSSEL_H */ diff --git a/runtime/obj-types.h b/runtime/obj-types.h new file mode 100644 index 00000000..914c2f2c --- /dev/null +++ b/runtime/obj-types.h @@ -0,0 +1,412 @@ +/* Some type definitions and macros for the obj object. + * I needed to move them out of the main obj.h, because obj.h's + * prototypes use other data types. However, their .h's rely + * on some of the obj.h data types and macros. So I needed to break + * that loop somehow and I've done that by moving the typedefs + * into this file here. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJ_TYPES_H_INCLUDED +#define OBJ_TYPES_H_INCLUDED + +#include "stringbuf.h" +#include "syslogd-types.h" + +/* property types for obj[De]Serialize() */ +typedef enum { + PROPTYPE_NONE = 0, /* currently no value set */ + PROPTYPE_PSZ = 1, + PROPTYPE_SHORT = 2, + PROPTYPE_INT = 3, + PROPTYPE_LONG = 4, + PROPTYPE_INT64 = 5, + PROPTYPE_CSTR = 6, + PROPTYPE_SYSLOGTIME = 7 +} propType_t; + +typedef unsigned objID_t; + +typedef enum { /* IDs of base methods supported by all objects - used for jump table, so + * they must start at zero and be incremented. -- rgerhards, 2008-01-04 + */ + objMethod_CONSTRUCT = 0, + objMethod_DESTRUCT = 1, + objMethod_SERIALIZE = 2, + objMethod_DESERIALIZE = 3, + objMethod_SETPROPERTY = 4, + objMethod_CONSTRUCTION_FINALIZER = 5, + objMethod_GETSEVERITY = 6, + objMethod_DEBUGPRINT = 7 +} objMethod_t; +#define OBJ_NUM_METHODS 8 /* must be updated to contain the max number of methods supported */ + + +/* the base data type for interfaces + * This MUST be in sync with the ifBEGIN macro + */ +struct interface_s { + int ifVersion; /* must be set to version requested */ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes, 2-load failed; if not 1, functions can NOT be called! */ +}; + + +struct objInfo_s { + uchar *pszID; /* the object ID as a string */ + size_t lenID; /* length of the ID string */ + int iObjVers; + uchar *pszName; + rsRetVal (*objMethods[OBJ_NUM_METHODS])(); + rsRetVal (*QueryIF)(interface_t*); + struct modInfo_s *pModInfo; +}; + + +struct obj_s { /* the dummy struct that each derived class can be casted to */ + objInfo_t *pObjInfo; +#ifndef NDEBUG /* this means if debug... */ + unsigned int iObjCooCKiE; /* must always be 0xBADEFEE for a valid object */ +#endif + uchar *pszName; /* the name of *this* specific object instance */ +}; + + +/* macros which must be gloablly-visible (because they are used during definition of + * other objects. + */ +#ifndef NDEBUG /* this means if debug... */ +#include <string.h> +# define BEGINobjInstance \ + obj_t objData +# define ISOBJ_assert(pObj) \ + do { \ + ASSERT((pObj) != NULL); \ + ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + } while(0); +# define ISOBJ_TYPE_assert(pObj, objType) \ + do { \ + ASSERT(pObj != NULL); \ + ASSERT((unsigned) ((obj_t*) (pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + if(strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)) { \ + dbgprintf("%s:%d ISOBJ assert failure: invalid object type, expected '%s' " \ + "actual '%s'\n", __FILE__, __LINE__, #objType, (((obj_t*)pObj)->pObjInfo->pszID)); \ + assert(0); /* trigger assertion, messge we already have */ \ + } \ + } while(0) +#else /* non-debug mode, no checks but much faster */ +# define BEGINobjInstance obj_t objData +# define ISOBJ_TYPE_assert(pObj, objType) +# define ISOBJ_assert(pObj) +#endif + +/* a set method for *very simple* object accesses. Note that this does + * NOT conform to the standard calling conventions and should be + * used only if actually nothing can go wrong! -- rgerhards, 2008-04-17 + */ +#define DEFpropGetMeth(obj, prop, dataType)\ + dataType obj##Get##prop(void)\ + { \ + return pThis->prop = pVal; \ + } + +#define DEFpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType *pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType*) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define DEFpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal) +#define INTERFACEpropSetMeth(obj, prop, dataType)\ + rsRetVal (*Set##prop)(obj##_t *pThis, dataType) +/* class initializer */ +#define PROTOTYPEObjClassInit(objName) rsRetVal objName##ClassInit(struct modInfo_s*) +/* below: objName must be the object name (e.g. vm, strm, ...) and ISCORE must be + * 1 if the module is a statically linked core module and 0 if it is a + * dynamically loaded one. -- rgerhards, 2008-02-29 + */ +#define OBJ_IS_CORE_MODULE 1 /* This should better be renamed to something like "OBJ_IS_NOT_LIBHEAD" or so... ;) */ +#define OBJ_IS_LOADABLE_MODULE 0 +#define BEGINObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + (rsRetVal (*)(void*))objName##Construct,\ + (rsRetVal (*)(void*))objName##Destruct,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); \ + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + +/* ... and now the same for abstract classes. + * TODO: consolidate the two -- rgerhards, 2008-02-29 + */ +#define BEGINAbstractObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + NULL,\ + NULL,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + + +/* now come the class exit. This is to be called immediately before the class is + * unloaded (actual unload for plugins, program termination for core modules) + * gerhards, 2008-03-10 + */ +#define PROTOTYPEObjClassExit(objName) rsRetVal objName##ClassExit(void) +#define BEGINObjClassExit(objName, objType) \ +rsRetVal objName##ClassExit(void) \ +{ \ + DEFiRet; + +#define CODESTARTObjClassExit(objName) + +#define ENDObjClassExit(objName) \ + iRet = obj.UnregisterObj((uchar*)#objName); \ + RETiRet; \ +} + +/* this defines both the constructor and initializer + * rgerhards, 2008-01-10 + */ +#define BEGINobjConstruct(obj) \ + rsRetVal obj##Initialize(obj##_t __attribute__((unused)) *pThis) \ + { \ + DEFiRet; + +#define ENDobjConstruct(obj) \ + /* use finalize_it: before calling the macro (if you need it)! */ \ + RETiRet; \ + } \ + rsRetVal obj##Construct(obj##_t **ppThis) \ + { \ + DEFiRet; \ + obj##_t *pThis; \ + \ + ASSERT(ppThis != NULL); \ + \ + if((pThis = (obj##_t *)calloc(1, sizeof(obj##_t))) == NULL) { \ + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); \ + } \ + objConstructSetObjInfo(pThis); \ + \ + obj##Initialize(pThis); \ + \ + finalize_it: \ + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + RETiRet; \ + } + + +/* this defines the destructor. The important point is that the base object + * destructor is called. The upper-level class shall destruct all of its + * properties, but not the instance itself. This is freed here by the + * framework (we need an intact pointer because we need to free the + * obj_t structures inside it). A pointer to the object pointer must be + * parse, because it is re-set to NULL (this, for example, is important in + * cancellation handlers). The object pointer is always named pThis. + * The object is always freed, even if there is some error while + * Cancellation is blocked during destructors, as this could have fatal + * side-effects. However, this also means the upper-level object should + * not perform any lenghty processing. + * IMPORTANT: if the upper level object requires some situations where the + * object shall not be destructed (e.g. via reference counting), then + * it shall set pThis to NULL, which prevents destruction of the + * object. + * processing. + * rgerhards, 2008-01-30 + */ +#define BEGINobjDestruct(OBJ) \ + rsRetVal OBJ##Destruct(OBJ##_t **ppThis) \ + { \ + DEFiRet; \ + int iCancelStateSave; \ + OBJ##_t *pThis; + +#define CODESTARTobjDestruct(OBJ) \ + ASSERT(ppThis != NULL); \ + pThis = *ppThis; \ + ISOBJ_TYPE_assert(pThis, OBJ); \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + +#define ENDobjDestruct(OBJ) \ + goto finalize_it; /* prevent compiler warning ;) */ \ + /* no more code here! */ \ + finalize_it: \ + if(pThis != NULL) { \ + obj.DestructObjSelf((obj_t*) pThis); \ + free(pThis); \ + *ppThis = NULL; \ + } \ + pthread_setcancelstate(iCancelStateSave, NULL); \ + RETiRet; \ + } + + +/* this defines the debug print entry point. DebugPrint is optional. If + * it is provided, the object should output some meaningful information + * via the debug system. + * rgerhards, 2008-02-20 + */ +#define PROTOTYPEObjDebugPrint(obj) rsRetVal obj##DebugPrint(obj##_t *pThis) +#define INTERFACEObjDebugPrint(obj) rsRetVal (*DebugPrint)(obj##_t *pThis) +#define BEGINobjDebugPrint(obj) \ + rsRetVal obj##DebugPrint(obj##_t *pThis) \ + { \ + DEFiRet; \ + +#define CODESTARTobjDebugPrint(obj) \ + ASSERT(pThis != NULL); \ + ISOBJ_TYPE_assert(pThis, obj); \ + +#define ENDobjDebugPrint(obj) \ + RETiRet; \ + } + +/* ------------------------------ object loader system ------------------------------ * + * The following code builds a dynamic object loader system. The + * root idea is that all objects are dynamically loadable, + * which is necessary to get a clean plug-in interface where every plugin can access + * rsyslog's rich object model via simple and quite portable methods. + * + * To do so, each object defines one or more interfaces. They are essentially structures + * with function (method) pointers. Anyone interested in calling an object must first + * obtain the interface and can then call through it. + * + * The interface data type must always be called <obj>_if_t, as this is expected + * by the macros. Having consitent naming is also easier for the programmer. By default, + * macros create a static variable named like the object in each calling objects + * static data block. + * + * rgerhards, 2008-02-21 (initial implementation), 2008-04-17 (update of this note) + */ + +/* this defines the QueryInterface print entry point. Over time, it should be + * present in all objects. + */ +#define BEGINobjQueryInterface(obj) \ + rsRetVal obj##QueryInterface(obj##_if_t *pIf) \ + { \ + DEFiRet; \ + +#define CODESTARTobjQueryInterface(obj) \ + ASSERT(pIf != NULL); + +#define ENDobjQueryInterface(obj) \ + RETiRet; \ + } + + +/* the following macros should be used to define interfaces inside the + * header files. + */ +#define BEGINinterface(obj) \ + typedef struct obj##_if_s {\ + ifBEGIN /* This MUST always be the first interface member */ +#define ENDinterface(obj) \ + } obj##_if_t; + +/* the following macro is used to get access to an object (not an instance, + * just the class itself!). It must be called before any of the object's + * methods can be accessed. The MYLIB part is the name of my library, or NULL if + * the caller is a core module. Using the right value here is important to get + * the reference counting correct (object accesses from the same library must + * not be counted because that would cause a library plugin to never unload, as + * its ClassExit() entry points are only called if no object is referenced, which + * would never happen as the library references itself. + * rgerhards, 2008-03-11 + */ +#define CORE_COMPONENT NULL /* use this to indicate this is a core component */ +#define DONT_LOAD_LIB NULL /* do not load a library to obtain object interface (currently same as CORE_COMPONENT) */ +#define objUse(objName, FILENAME) \ + obj.UseObj(__FILE__, (uchar*)#objName, (uchar*)FILENAME, (void*) &objName) +#define objRelease(objName, FILENAME) \ + obj.ReleaseObj(__FILE__, (uchar*)#objName, (uchar*) FILENAME, (void*) &objName) + +/* defines data that must always be present at the very begin of the interface structure */ +#define ifBEGIN \ + int ifVersion; /* must be set to version requested */ \ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes; if no, functions can NOT be called! */ + + +/* use the following define some place in your static data (suggested right at + * the beginning + */ +#define DEFobjCurrIf(obj) \ + static obj##_if_t obj = { .ifVersion = obj##CURR_IF_VERSION, .ifIsLoaded = 0 }; + +/* define the prototypes for a class - when we use interfaces, we just have few + * functions that actually need to be non-static. + */ +#define PROTOTYPEObj(obj) \ + PROTOTYPEObjClassInit(obj); \ + PROTOTYPEObjClassExit(obj) + +/* ------------------------------ end object loader system ------------------------------ */ + + +#include "modules.h" +#endif /* #ifndef OBJ_TYPES_H_INCLUDED */ diff --git a/runtime/obj.c b/runtime/obj.c new file mode 100644 index 00000000..082aa691 --- /dev/null +++ b/runtime/obj.c @@ -0,0 +1,1334 @@ +/* obj.c + * + * This file implements a generic object "class". All other classes can + * use the service of this base class here to include auto-destruction and + * other capabilities in a generic manner. + * + * As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable + * objects. In essence, each object will soon be available via its interface, + * only. Before any object's code is accessed (including global static methods), + * the caller needs to obtain an object interface. To do so, it needs to provide + * the object name and the file where the object is expected to reside in. A + * file may not be given, in which case the object is expected to reside in + * the rsyslog core. The caller than receives an interface pointer which can + * be utilized to access all the object's methods. This method enables rsyslog + * to load library modules on demand. In order to keep overhead low, callers + * should request object interface only once in the object Init function and + * free them when they exit. The only exception is when a caller needs to + * access an object only conditional, in which case a pointer to its interface + * shall be aquired as need first arises but still be released only on exit + * or when there definitely is no further need. The whole idea is to limit + * the very performance-intense act of dynamically loading an objects library. + * Of course, it is possible to violate this suggestion, but than you should + * have very good reasoning to do so. + * + * Please note that there is one trick we need to do. Each object queries + * the object interfaces and it does so via objUse(). objUse, however, is + * part of the obj object's interface (implemented via the file you are + * just reading). So in order to obtain a pointer to objUse, we need to + * call it - obviously not possible. One solution would be that objUse is + * hardcoded into all callers. That, however, would bring us into slight + * trouble with actually dynamically loaded modules, as we should NOT + * rely on the OS loader to resolve symbols back to the caller (this + * is a feature not universally available and highly importable). Of course, + * we can solve this with a pHostQueryEtryPoint() call. It still sounds + * somewhat unnatural to call a regular interface function via a special + * method. So what we do instead is define a special function called + * objGetObjInterface() which delivers our own interface. That function + * than will be defined global and be queriable via pHostQueryEtryPoint(). + * I agree, technically this is much the same, but from an architecture + * point of view it looks cleaner (at least to me). + * + * Please note that there is another egg-hen problem: we use a linked list, + * which is provided by the linkedList object. However, we need to + * initialize the linked list before we can provide the UseObj() + * functionality. That, in turn, would probably be required by the + * linkedList object. So the solution is to use a backdoor just to + * init the linked list and from then on use the usual interfaces. + * + * File begun on 2008-01-04 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +/* how many objects are supported by rsyslogd? */ +#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */ + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" +#include "modules.h" +#include "errmsg.h" +#include "cfsysline.h" + +/* static data */ +DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */ +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */ + + +/* cookies for serialized lines */ +#define COOKIE_OBJLINE '<' +#define COOKIE_PROPLINE '+' +#define COOKIE_ENDLINE '>' +#define COOKIE_BLANKLINE '.' + +/* forward definitions */ +static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo); + +/* methods */ + +/* This is a dummy method to be used when a standard method has not been + * implemented by an object. Having it allows us to simply call via the + * jump table without any NULL pointer checks - which gains quite + * some performance. -- rgerhards, 2008-01-04 + */ +static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis) +{ + return RS_RET_NOT_IMPLEMENTED; +} + +/* and now the macro to check if something is not implemented + * must be provided an objInfo_t pointer. + */ +#define objInfoIsImplemented(pThis, method) \ + (pThis->objMethods[method] != objInfoNotImplementedDummy) + +/* construct an object Info object. Each class shall do this on init. The + * resulting object shall be cached during the lifetime of the class and each + * object shall receive a reference. A constructor and destructor MUST be provided for all + * objects, thus they are in the parameter list. + * pszID is the identifying object name and must point to constant pool memory. It is never freed. + */ +static rsRetVal +InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo) +{ + DEFiRet; + int i; + objInfo_t *pThis; + + assert(ppThis != NULL); + + if((pThis = calloc(1, sizeof(objInfo_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pThis->pszID = pszID; + pThis->lenID = strlen((char*)pszID); + pThis->pszName = (uchar*)strdup((char*)pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */ + pThis->iObjVers = iObjVers; + pThis->QueryIF = pQueryIF; + pThis->pModInfo = pModInfo; + + pThis->objMethods[0] = pConstruct; + pThis->objMethods[1] = pDestruct; + for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) { + pThis->objMethods[i] = objInfoNotImplementedDummy; + } + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* destruct the objInfo object - must be done only when no more instances exist. + * rgerhards, 2008-03-10 + */ +static rsRetVal +InfoDestruct(objInfo_t **ppThis) +{ + DEFiRet; + objInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + if(pThis->pszName != NULL) + free(pThis->pszName); + free(pThis); + *ppThis = NULL; + + RETiRet; +} + + +/* set a method handler */ +static rsRetVal +InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)) +{ + assert(pThis != NULL); + assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS); + pThis->objMethods[objMethod] = pHandler; + + return RS_RET_OK; +} + +/* destruct the base object properties. + * rgerhards, 2008-01-29 + */ +static rsRetVal +DestructObjSelf(obj_t *pThis) +{ + DEFiRet; + + ISOBJ_assert(pThis); + if(pThis->pszName != NULL) { + free(pThis->pszName); + } + + RETiRet; +} + + +/* --------------- object serializiation / deserialization support --------------- */ + + +/* serialize the header of an object + * pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag) + */ +static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* object cookie and serializer version (so far always 1) */ + CHKiRet(strmWriteChar(pStrm, COOKIE_OBJLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '1')); + + /* object type, version and string length */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWrite(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)); + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteLong(pStrm, objGetVersion(pObj))); + + /* record trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object + * rgerhards, 2008-01-06 + */ +static rsRetVal +BeginSerialize(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strmRecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj")); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object's property bag + * Note: a property bag is used to serialize some of an objects + * properties, but not necessarily all. A good example is the queue + * object, which at some stage needs to serialize a number of its + * properties, but not the queue data itself. From the object point + * of view, a property bag can not be used to re-instantiate an object. + * Otherwise, the serialization is exactly the same. + * rgerhards, 2008-01-11 + */ +static rsRetVal +BeginSerializePropBag(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strmRecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB")); + +finalize_it: + RETiRet; +} + + +/* append a property + */ +static rsRetVal +SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr) +{ + DEFiRet; + uchar *pszBuf = NULL; + size_t lenBuf = 0; + uchar szBuf[64]; + varType_t vType = VARTYPE_NONE; + + ISOBJ_TYPE_assert(pStrm, strm); + assert(pszPropName != NULL); + + /*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/ + /* if we have no user pointer, there is no need to write this property. + * TODO: think if that's the righ point of view + * rgerhards, 2008-01-06 + */ + if(pUsr == NULL) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* TODO: use the stream functions for data conversion here - should be quicker */ + + switch(propType) { + case PROPTYPE_PSZ: + pszBuf = (uchar*) pUsr; + lenBuf = strlen((char*) pszBuf); + vType = VARTYPE_STR; + break; + case PROPTYPE_SHORT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_LONG: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT64: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_CSTR: + pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr); + lenBuf = rsCStrLen((cstr_t*) pUsr); + vType = VARTYPE_STR; + break; + case PROPTYPE_SYSLOGTIME: + lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d", + ((syslogTime_t*)pUsr)->timeType, + ((syslogTime_t*)pUsr)->year, + ((syslogTime_t*)pUsr)->month, + ((syslogTime_t*)pUsr)->day, + ((syslogTime_t*)pUsr)->hour, + ((syslogTime_t*)pUsr)->minute, + ((syslogTime_t*)pUsr)->second, + ((syslogTime_t*)pUsr)->secfrac, + ((syslogTime_t*)pUsr)->secfracPrecision, + ((syslogTime_t*)pUsr)->OffsetMode, + ((syslogTime_t*)pUsr)->OffsetHour, + ((syslogTime_t*)pUsr)->OffsetMinute); + if(lenBuf > sizeof(szBuf) - 1) + ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL); + vType = VARTYPE_SYSLOGTIME; + pszBuf = szBuf; + break; + default: + dbgprintf("invalid PROPTYPE %d\n", propType); + break; + } + + /* cookie */ + CHKiRet(strmWriteChar(pStrm, COOKIE_PROPLINE)); + /* name */ + CHKiRet(strmWrite(pStrm, pszPropName, strlen((char*)pszPropName))); + CHKiRet(strmWriteChar(pStrm, ':')); + /* type */ + CHKiRet(strmWriteLong(pStrm, (int) vType)); + CHKiRet(strmWriteChar(pStrm, ':')); + /* length */ + CHKiRet(strmWriteLong(pStrm, lenBuf)); + CHKiRet(strmWriteChar(pStrm, ':')); + + /* data */ + CHKiRet(strmWrite(pStrm, (uchar*) pszBuf, lenBuf)); + + /* trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* end serialization of an object. The caller receives a + * standard C string, which he must free when no longer needed. + */ +static rsRetVal +EndSerialize(strm_t *pStrm) +{ + DEFiRet; + + assert(pStrm != NULL); + + CHKiRet(strmWriteChar(pStrm, COOKIE_ENDLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) "End\n", sizeof("END\n") - 1)); + CHKiRet(strmWriteChar(pStrm, COOKIE_BLANKLINE)); + CHKiRet(strmWriteChar(pStrm, '\n')); + + CHKiRet(strmRecordEnd(pStrm)); + +finalize_it: + RETiRet; +} + + +/* define a helper to make code below a bit cleaner (and quicker to write) */ +#define NEXTC CHKiRet(strmReadChar(pStrm, &c))/*;dbgprintf("c: %c\n", c)*/ + + +/* de-serialize an embedded, non-octect-counted string. This is useful + * for deserializing the object name inside the header. The string is + * terminated by the first occurence of the ':' character. + * rgerhards, 2008-02-29 + */ +static rsRetVal +objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm) +{ + DEFiRet; + uchar c; + cstr_t *pStr = NULL; + + assert(ppStr != NULL); + + CHKiRet(rsCStrConstruct(&pStr)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK && pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* de-serialize a number */ +static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm) +{ + DEFiRet; + number_t i; + int bIsNegative; + uchar c; + + assert(pNum != NULL); + + NEXTC; + if(c == '-') { + bIsNegative = 1; + NEXTC; + } else { + bIsNegative = 0; + } + + /* we check this so that we get more meaningful error codes */ + if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER); + + i = 0; + while(isdigit(c)) { + i = i * 10 + c - '0'; + NEXTC; + } + + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + if(bIsNegative) + i *= -1; + + *pNum = i; +finalize_it: + RETiRet; +} + + +/* de-serialize a string, length must be provided but may be 0 */ +static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm) +{ + DEFiRet; + int i; + uchar c; + cstr_t *pCStr = NULL; + + assert(ppCStr != NULL); + assert(iLen >= 0); + + CHKiRet(rsCStrConstruct(&pCStr)); + + NEXTC; + for(i = 0 ; i < iLen ; ++i) { + CHKiRet(rsCStrAppendChar(pCStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pCStr)); + + /* check terminator */ + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK && pCStr != NULL) + rsCStrDestruct(&pCStr); + + RETiRet; +} + + +/* de-serialize a syslogTime -- rgerhards,2008-01-08 */ +#define GETVAL(var) \ + CHKiRet(objDeserializeNumber(&l, pStrm)); \ + pTime->var = l; +static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm) +{ + DEFiRet; + number_t l; + uchar c; + + assert(pTime != NULL); + + GETVAL(timeType); + GETVAL(year); + GETVAL(month); + GETVAL(day); + GETVAL(hour); + GETVAL(minute); + GETVAL(second); + GETVAL(secfrac); + GETVAL(secfracPrecision); + /* OffsetMode is a single character! */ + NEXTC; pTime->OffsetMode = c; + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + GETVAL(OffsetHour); + GETVAL(OffsetMinute); + +finalize_it: + RETiRet; +} +#undef GETVAL + +/* de-serialize an object header + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm) +{ + DEFiRet; + number_t oVers; + uchar c; + + assert(ppstrID != NULL); + assert(poVers != NULL); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* check header cookie */ + NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + + /* object type and version */ + CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm)); + CHKiRet(objDeserializeNumber(&oVers, pStrm)); + + /* and now we skip over the rest until the delemiting \n */ + NEXTC; + while(c != '\n') { + NEXTC; + } + + *poVers = oVers; + +finalize_it: + RETiRet; +} + + +/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line + * up until the \n is read. + */ +static rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm) +{ + DEFiRet; + number_t i; + number_t iLen; + uchar c; + + assert(pProp != NULL); + + /* check cookie */ + NEXTC; + if(c != COOKIE_PROPLINE) { + /* oops, we've read one char that does not belong to use - unget it first */ + CHKiRet(strmUnreadChar(pStrm, c)); + ABORT_FINALIZE(RS_RET_NO_PROPLINE); + } + + /* get the property name first */ + CHKiRet(rsCStrConstruct(&pProp->pcsName)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pProp->pcsName, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pProp->pcsName)); + + /* property type */ + CHKiRet(objDeserializeNumber(&i, pStrm)); + pProp->varType = i; + + /* size (needed for strings) */ + CHKiRet(objDeserializeNumber(&iLen, pStrm)); + + /* we now need to deserialize the value */ + switch(pProp->varType) { + case VARTYPE_STR: + CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm)); + break; + case VARTYPE_NUMBER: + CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm)); + break; + case VARTYPE_SYSLOGTIME: + CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm)); + break; + default: + dbgprintf("invalid VARTYPE %d\n", pProp->varType); + break; + } + + /* we should now be at the end of the line. So the next char must be \n */ + NEXTC; + if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME); + +finalize_it: + RETiRet; +} + + +/* de-serialize an object trailer. This does not get any data but checks if the + * format is ok. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTrailer(strm_t *pStrm) +{ + DEFiRet; + uchar c; + + /* check header cookie */ + NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + +finalize_it: + RETiRet; +} + + + +/* This method tries to recover a serial store if it got out of sync. + * To do so, it scans the line beginning cookies and waits for the object + * cookie. If that is found, control is returned. If the store is exhausted, + * we will receive an RS_RET_EOF error as part of NEXTC, which will also + * terminate this function. So we may either return with somehting that + * looks like a valid object or end of store. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTryRecover(strm_t *pStrm) +{ + DEFiRet; + uchar c; + int bWasNL; + int bRun; + + assert(pStrm != NULL); + bRun = 1; + bWasNL = 0; + + while(bRun) { + NEXTC; + if(c == '\n') + bWasNL = 1; + else { + if(bWasNL == 1 && c == COOKIE_OBJLINE) + bRun = 0; /* we found it! */ + else + bWasNL = 0; + } + } + + CHKiRet(strmUnreadChar(pStrm, c)); + +finalize_it: + dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet); + RETiRet; +} + + +/* De-serialize the properties of an object. This includes processing + * of the trailer. Header must already have been processed. + * rgerhards, 2008-01-11 + */ +static rsRetVal objDeserializeProperties(obj_t *pObj, objInfo_t *pObjInfo, strm_t *pStrm) +{ + DEFiRet; + var_t *pVar = NULL; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + ASSERT(pObjInfo != NULL); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + iRet = objDeserializeProperty(pVar, pStrm); + while(iRet == RS_RET_OK) { + CHKiRet(pObjInfo->objMethods[objMethod_SETPROPERTY](pObj, pVar)); + /* re-init var object - TODO: method of var! */ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } + iRet = objDeserializeProperty(pVar, pStrm); + } + + if(iRet != RS_RET_NO_PROPLINE) + FINALIZE; + + CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */ +finalize_it: + if(pVar != NULL) + var.Destruct(&pVar); + + RETiRet; +} + + +/* De-Serialize an object. + * Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning) + * expected object ID (to check against), a fixup function that can modify the object before it is finalized + * and a user pointer that is to be passed to that function in addition to the object. The fixup function + * pointer may be NULL, in which case none is called. + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + obj_t *pObj = NULL; + int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ + cstr_t *pstrID = NULL; + objInfo_t *pObjInfo; + + assert(ppObj != NULL); + assert(pszTypeExpected != NULL); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state, + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pszTypeExpected, strlen((char*)pszTypeExpected))) /* TODO: optimize strlen() - caller shall provide */ + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + + /* check if we need to call a fixup function that modifies the object + * before it is finalized. -- rgerhards, 2008-01-13 + */ + if(fFixup != NULL) + CHKiRet(fFixup(pObj, pUsr)); + + /* we have a valid object, let's finalize our work and return */ + if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER)) + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj)); + + *((obj_t**) ppObj) = pObj; + +finalize_it: + if(iRet != RS_RET_OK && pObj != NULL) + free(pObj); /* TODO: check if we can call destructor 2008-01-13 rger */ + + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + +/* De-Serialize an object, but treat it as property bag. + * rgerhards, 2008-01-11 + */ +rsRetVal +objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + + +/* De-Serialize an object property bag. As a property bag contains only partial properties, + * it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated + * object of the correct type. + * Params: Pointer to object (pObj) + * Pointer to be passed to the function + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +DeserializePropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers; + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + +#undef NEXTC /* undef helper macro */ + + +/* --------------- end object serializiation / deserialization support --------------- */ + + +/* set the object (instance) name + * rgerhards, 2008-01-29 + * TODO: change the naming to a rsCStr obj! (faster) + */ +static rsRetVal +SetName(obj_t *pThis, uchar *pszName) +{ + DEFiRet; + + if(pThis->pszName != NULL) + free(pThis->pszName); + + pThis->pszName = (uchar*) strdup((char*) pszName); + + if(pThis->pszName == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + +finalize_it: + RETiRet; +} + + +/* get the object (instance) name + * Note that we use a non-standard calling convention. Thus function must never + * fail, else we run into real big problems. So it must make sure that at least someting + * is returned. + * rgerhards, 2008-01-30 + */ +static uchar * +GetName(obj_t *pThis) +{ + uchar *ret; + uchar szName[128]; + + BEGINfunc + ISOBJ_assert(pThis); + + if(pThis->pszName == NULL) { + snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis); + SetName(pThis, szName); + /* looks strange, but we NEED to re-check because if there was an + * error in objSetName(), the pointer may still be NULL + */ + if(pThis->pszName == NULL) { + ret = objGetClassName(pThis); + } else { + ret = pThis->pszName; + } + } else { + ret = pThis->pszName; + } + + ENDfunc + return ret; +} + + +/* Find the objInfo object for the current object + * rgerhards, 2008-02-29 + */ +static rsRetVal +FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pstrOID != NULL); + assert(ppInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_NOT_FOUND); + + *ppInfo = arrObjInfo[i]; + +finalize_it: + if(iRet == RS_RET_OK) { + /* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/ + /*EMPTY BY INTENSION*/; + } else { + dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet); + } + + RETiRet; +} + + +/* register a classes' info pointer, so that we can reference it later, if needed to + * (e.g. for de-serialization support). + * rgerhards, 2008-01-07 + * In this function, we look for a free space in the object table. While we do so, we + * also detect if the same object has already been registered, which is not valid. + * rgerhards, 2008-02-29 + */ +static rsRetVal +RegisterObj(uchar *pszObjName, objInfo_t *pInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + assert(pInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) { + if( arrObjInfo[i] != NULL + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED); + if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE); + + arrObjInfo[i] = pInfo; + /* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */ + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet); + } + + RETiRet; +} + + +/* deregister a classes' info pointer, usually called because the class is unloaded. + * After deregistration, the class can no longer be accessed, except if it is reloaded. + * rgerhards, 2008-03-10 + */ +static rsRetVal +UnregisterObj(uchar *pszObjName) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if( arrObjInfo[i] != NULL + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED); + + InfoDestruct(&arrObjInfo[i]); + /* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */ + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet); + } + + RETiRet; +} + + +/* This function shall be called by anyone who would like to use an object. It will + * try to locate the object, load it into memory if not already present and return + * a pointer to the objects interface. + * rgerhards, 2008-02-29 + */ +static rsRetVal +UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + /* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ + + if(pIf->ifIsLoaded == 1) { + ABORT_FINALIZE(RS_RET_OK); /* we are already set */ + } + if(pIf->ifIsLoaded == 2) { + ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */ + } + + /* we must be careful that we do not enter in infinite loop if an error occurs during + * loading a module. ModLoad emits an error message in such cases and that potentially + * can trigger the same code here. So we initially set the module state to "load error" + * and set it to "fully initialized" when the load succeeded. It's a bit hackish, but + * looks like a good solution. -- rgerhards, 2008-03-07 + */ + pIf->ifIsLoaded = 2; + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + iRet = FindObjInfo(pStr, &pObjInfo); + if(iRet == RS_RET_NOT_FOUND) { + /* in this case, we need to see if we can dynamically load the object */ + if(pObjFile == NULL) { + FINALIZE; /* no chance, we have lost... */ + } else { + CHKiRet(module.Load(pObjFile)); + /* NOW, we must find it or we have a problem... */ + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + } + } else if(iRet != RS_RET_OK) { + FINALIZE; /* give up */ + } + + /* if we reach this point, we have a valid pObjInfo */ + if(pObjFile != NULL) { /* NULL means core module */ + module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */ + } + + CHKiRet(pObjInfo->QueryIF(pIf)); + pIf->ifIsLoaded = 1; /* we are happy */ + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* This function shall be called when a caller is done with an object. Its primary + * purpose is to keep the reference count correct, which is highly important for + * modules residing in loadable modules. + * rgerhards, 2008-03-10 + */ +static rsRetVal +ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + /* dev debug only dbgprintf("source file %s releasing object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ + + if(pObjFile == NULL) + FINALIZE; /* if it is not a lodable module, we do not need to do anything... */ + + if(pIf->ifIsLoaded == 0) { + ABORT_FINALIZE(RS_RET_OK); /* we are not loaded - this is perfectly OK... */ + } else if(pIf->ifIsLoaded == 2) { + pIf->ifIsLoaded = 0; /* clean up */ + ABORT_FINALIZE(RS_RET_OK); /* we had a load error and can not continue */ + } + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + + /* if we reach this point, we have a valid pObjInfo */ + module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */ + + pIf->ifIsLoaded = 0; /* indicated "no longer valid" */ + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(obj) +CODESTARTobjQueryInterface(obj) + if(pIf->ifVersion != objCURR_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->UseObj = UseObj; + pIf->ReleaseObj = ReleaseObj; + pIf->InfoConstruct = InfoConstruct; + pIf->DestructObjSelf = DestructObjSelf; + pIf->BeginSerializePropBag = BeginSerializePropBag; + pIf->InfoSetMethod = InfoSetMethod; + pIf->BeginSerialize = BeginSerialize; + pIf->SerializeProp = SerializeProp; + pIf->EndSerialize = EndSerialize; + pIf->RegisterObj = RegisterObj; + pIf->UnregisterObj = UnregisterObj; + pIf->Deserialize = Deserialize; + pIf->DeserializePropBag = DeserializePropBag; + pIf->SetName = SetName; + pIf->GetName = GetName; +finalize_it: +ENDobjQueryInterface(obj) + + +/* This function returns a pointer to our own interface. It is used as the + * hook that every object (including dynamically loaded ones) can use to + * obtain a pointer to our interface which than can be used to obtain + * pointers to any other interface in the system. This function must be + * externally visible because of its special nature. + * rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)] + */ +rsRetVal +objGetObjInterface(obj_if_t *pIf) +{ + DEFiRet; + assert(pIf != NULL); + objQueryInterface(pIf); + RETiRet; +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +rsRetVal +objClassExit(void) +{ + DEFiRet; + /* release objects we no longer need */ + objRelease(var, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + + /* TODO: implement the class exits! */ +#if 0 + cfsyslineInit(pModInfo); + varClassInit(pModInfo); +#endif + errmsgClassExit(); + moduleClassExit(); + RETiRet; +} + + +/* initialize our own class + * Please note that this also initializes those classes that we rely on. + * Though this is a bit dirty, we need to do it - otherwise we can't get + * around that bootstrap problem. We need to face the fact the the obj + * class is a little different from the rest of the system, as it provides + * the core class loader functionality. + * rgerhards, 2008-02-29 + */ +rsRetVal +objClassInit(modInfo_t *pModInfo) +{ + DEFiRet; + int i; + + /* first, initialize the object system itself. This must be done + * before any other object is created. + */ + for(i = 0 ; i < OBJ_NUM_IDS ; ++i) { + arrObjInfo[i] = NULL; + } + + /* request objects we use */ + CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ + + /* init classes we use (limit to as few as possible!) */ + CHKiRet(errmsgClassInit(pModInfo)); + CHKiRet(cfsyslineInit()); + CHKiRet(varClassInit(pModInfo)); + CHKiRet(moduleClassInit(pModInfo)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} + +/* vi:set ai: + */ diff --git a/runtime/obj.h b/runtime/obj.h new file mode 100644 index 00000000..dc04203b --- /dev/null +++ b/runtime/obj.h @@ -0,0 +1,125 @@ +/* Definition of the generic obj class module. + * + * This module relies heavily on preprocessor macros in order to + * provide fast execution time AND ease of use. + * + * Each object that uses this base class MUST provide a constructor with + * the following interface: + * + * Destruct(pThis); + * + * A constructor is not necessary (except for some features, e.g. de-serialization). + * If it is provided, it is a three-part constructor (to handle all cases with a + * generic interface): + * + * Construct(&pThis); + * SetProperty(pThis, property_t *); + * ConstructFinalize(pThis); + * + * SetProperty() and ConstructFinalize() may also be called on an object + * instance which has been Construct()'ed outside of this module. + * + * pThis always references to a pointer of the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJ_H_INCLUDED +#define OBJ_H_INCLUDED + +#include "obj-types.h" +#include "var.h" +#include "stream.h" + +/* macros */ +/* the following one is a helper that prevents us from writing the + * ever-same code at the end of Construct() + */ +#define OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + if(iRet == RS_RET_OK) { \ + *ppThis = pThis; \ + } else { \ + if(pThis != NULL) \ + free(pThis); \ + } + +#define objSerializeSCALAR_VAR(strm, propName, propType, var) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &var)); +#define objSerializeSCALAR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &pThis->propName)); +#define objSerializePTR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) pThis->propName)); +#define DEFobjStaticHelpers \ + static objInfo_t *pObjInfoOBJ = NULL; \ + DEFobjCurrIf(obj) + + +#define objGetClassName(pThis) (((obj_t*) (pThis))->pObjInfo->pszID) +#define objGetVersion(pThis) (((obj_t*) (pThis))->pObjInfo->iObjVers) +/* the next macro MUST be called in Constructors: */ +#ifndef NDEBUG /* this means if debug... */ +# define objConstructSetObjInfo(pThis) \ + ASSERT(((obj_t*) (pThis))->pObjInfo == NULL); \ + ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ + ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE +#else +# define objConstructSetObjInfo(pThis) ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ +#endif +#define objDestruct(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DESTRUCT])(&pThis) +#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE]) +#define objGetSeverity(pThis, piSever) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_GETSEVERITY])(pThis, piSever) +#define objDebugPrint(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DEBUGPRINT])(pThis) + +#define OBJSetMethodHandler(methodID, pHdlr) \ + CHKiRet(obj.InfoSetMethod(pObjInfoOBJ, methodID, (rsRetVal (*)(void*)) pHdlr)) + +/* interfaces */ +BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*UseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*ReleaseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*InfoConstruct)(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t*); + rsRetVal (*DestructObjSelf)(obj_t *pThis); + rsRetVal (*BeginSerializePropBag)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*InfoSetMethod)(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)); + rsRetVal (*BeginSerialize)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*SerializeProp)(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr); + rsRetVal (*EndSerialize)(strm_t *pStrm); + rsRetVal (*RegisterObj)(uchar *pszObjName, objInfo_t *pInfo); + rsRetVal (*UnregisterObj)(uchar *pszObjName); + rsRetVal (*Deserialize)(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr); + rsRetVal (*DeserializePropBag)(obj_t *pObj, strm_t *pStrm); + rsRetVal (*SetName)(obj_t *pThis, uchar *pszName); + uchar * (*GetName)(obj_t *pThis); +ENDinterface(obj) +#define objCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +/* the following define *is* necessary, because it provides the root way of obtaining + * interfaces (at some place we need to start our query... + */ +rsRetVal objGetObjInterface(obj_if_t *pIf); +PROTOTYPEObjClassInit(obj); +PROTOTYPEObjClassExit(obj); + +#endif /* #ifndef OBJ_H_INCLUDED */ diff --git a/runtime/objomsr.c b/runtime/objomsr.c new file mode 100644 index 00000000..21d284f3 --- /dev/null +++ b/runtime/objomsr.c @@ -0,0 +1,145 @@ +/* objomsr.c + * Implementation of the omsr (omodStringRequest) object. + * + * File begun on 2007-07-27 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "rsyslog.h" +#include "objomsr.h" + + +/* destructor + */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis) +{ + int i; + + assert(pThis != NULL); + /* free the strings */ + if(pThis->ppTplName != NULL) { + for(i = 0 ; i < pThis->iNumEntries ; ++i) { + if(pThis->ppTplName[i] != NULL) { + free(pThis->ppTplName[i]); + } + } + free(pThis->ppTplName); + } + if(pThis->piTplOpts != NULL) + free(pThis->piTplOpts); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor + */ +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries) +{ + omodStringRequest_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + assert(iNumEntries >= 0); + if((pThis = calloc(1, sizeof(omodStringRequest_t))) == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + + /* got the structure, so fill it */ + pThis->iNumEntries = iNumEntries; + /* allocate string for template name array. The individual strings will be + * allocated as the code progresses (we do not yet know the string sizes) + */ + if((pThis->ppTplName = calloc(iNumEntries, sizeof(uchar*))) == NULL) { + OMSRdestruct(pThis); + pThis = NULL; + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + /* allocate the template options array. */ + if((pThis->piTplOpts = calloc(iNumEntries, sizeof(int))) == NULL) { + OMSRdestruct(pThis); + pThis = NULL; + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + +abort_it: + *ppThis = pThis; + RETiRet; +} + +/* set a template name and option to the object. Index must be given. The pTplName must be + * pointing to memory that can be freed. If in doubt, the caller must strdup() the value. + */ +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts) +{ + assert(pThis != NULL); + assert(pTplName != NULL); + assert(iEntry < pThis->iNumEntries); + + if(pThis->ppTplName[iEntry] != NULL) + free(pThis->ppTplName[iEntry]); + pThis->ppTplName[iEntry] = pTplName; + pThis->piTplOpts[iEntry] = iTplOpts; + + return RS_RET_OK; +} + + +/* get number of entries for this object + */ +int OMSRgetEntryCount(omodStringRequest_t *pThis) +{ + assert(pThis != NULL); + return pThis->iNumEntries; +} + + +/* return data for a specific entry. All data returned is + * read-only and lasts only as long as the object lives. If the caller + * needs it for an extended period of time, the caller must copy the + * strings. Please note that the string pointer may be NULL, which is the + * case when it was never set. + */ +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts) +{ + assert(pThis != NULL); + assert(ppTplName != NULL); + assert(piTplOpts != NULL); + assert(iEntry < pThis->iNumEntries); + + *ppTplName = pThis->ppTplName[iEntry]; + *piTplOpts = pThis->piTplOpts[iEntry]; + + return RS_RET_OK; +} +/* vim:set ai: + */ diff --git a/runtime/objomsr.h b/runtime/objomsr.h new file mode 100644 index 00000000..2255e4f3 --- /dev/null +++ b/runtime/objomsr.h @@ -0,0 +1,46 @@ +/* Definition of the omsr (omodStringRequest) object. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJOMSR_H_INCLUDED +#define OBJOMSR_H_INCLUDED + +/* define flags for required template options */ +#define OMSR_NO_RQD_TPL_OPTS 0 +#define OMSR_RQD_TPL_OPT_SQL 1 +/* next option is 2, 4, 8, ... */ + +struct omodStringRequest_s { /* strings requested by output module for doAction() */ + int iNumEntries; /* number of array entries for data elements below */ + uchar **ppTplName; /* pointer to array of template names */ + int *piTplOpts;/* pointer to array of check-options when pulling template */ +}; +typedef struct omodStringRequest_s omodStringRequest_t; + +/* prototypes */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis); +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries); +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts); +int OMSRgetEntryCount(omodStringRequest_t *pThis); +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts); + +#endif /* #ifndef OBJOMSR_H_INCLUDED */ diff --git a/runtime/queue.c b/runtime/queue.c new file mode 100644 index 00000000..00f811a0 --- /dev/null +++ b/runtime/queue.c @@ -0,0 +1,2310 @@ +/* queue.c + * + * This file implements the queue object and its several queueing methods. + * + * File begun on 2008-01-03 by RGerhards + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> /* required for HP UX */ +#include <time.h> +#include <errno.h> + +#include "rsyslog.h" +#include "queue.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "wtp.h" +#include "wti.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) + +/* forward-definitions */ +rsRetVal queueChkPersist(queue_t *pThis); +static rsRetVal queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex); +static rsRetVal queueRateLimiter(queue_t *pThis); +static int queueChkStopWrkrDA(queue_t *pThis); +static int queueIsIdleDA(queue_t *pThis); +static rsRetVal queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave); +static rsRetVal queueConsumerCancelCleanup(void *arg1, void *arg2); +static rsRetVal queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex); + +/* some constants for queuePersist () */ +#define QUEUE_CHECKPOINT 1 +#define QUEUE_NO_CHECKPOINT 0 + +/* methods */ + + +/* get the overall queue size, which includes ungotten objects. Must only be called + * while mutex is locked! + * rgerhards, 2008-01-29 + */ +static inline int +queueGetOverallQueueSize(queue_t *pThis) +{ +#if 0 /* leave a bit in for debugging -- rgerhards, 2008-01-30 */ +BEGINfunc +dbgoprint((obj_t*) pThis, "queue size: %d (regular %d, ungotten %d)\n", + pThis->iQueueSize + pThis->iUngottenObjs, pThis->iQueueSize, pThis->iUngottenObjs); +ENDfunc +#endif + return pThis->iQueueSize + pThis->iUngottenObjs; +} + +/* --------------- code for disk-assisted (DA) queue modes -------------------- */ + + +/* returns the number of workers that should be advised at + * this point in time. The mutex must be locked when + * ths function is called. -- rgerhards, 2008-01-25 + */ +static inline rsRetVal queueAdviseMaxWorkers(queue_t *pThis) +{ + DEFiRet; + int iMaxWorkers; + + ISOBJ_TYPE_assert(pThis, queue); + + if(!pThis->bEnqOnly) { + if(pThis->bRunsDA) { + /* if we have not yet reached the high water mark, there is no need to start a + * worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) >= pThis->iHighWtrMrk || pThis->bQueueStarted == 0) { + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */ + } + } else { + if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) { + iMaxWorkers = 1; + } else { + iMaxWorkers = queueGetOverallQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1; + } + wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers); /* disk queues have always one worker */ + } + } + + RETiRet; +} + + +/* wait until we have a fully initialized DA queue. Sometimes, we need to + * sync with it, as we expect it for some function. + * rgerhards, 2008-02-27 + */ +static rsRetVal +queueWaitDAModeInitialized(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + while(pThis->bRunsDA != 2) { + d_pthread_cond_wait(&pThis->condDAReady, pThis->mut); + } + + RETiRet; +} + + +/* Destruct DA queue. This is the last part of DA-to-normal-mode + * transistion. This is called asynchronously and some time quite a + * while after the actual transistion. The key point is that we need to + * do it at some later time, because we need to destruct the DA queue. That, + * however, can not be done in a thread that has been signalled + * This is to be called when we revert back to our own queue. + * This function must be called with the queue mutex locked (the wti + * class ensures this). + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueTurnOffDAMode(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + /* at this point, we need a fully initialized DA queue. So if it isn't, we finally need + * to wait for its startup... -- rgerhards, 2008-01-25 + */ + queueWaitDAModeInitialized(pThis); + + /* if we need to pull any data that we still need from the (child) disk queue, + * now would be the time to do so. At present, we do not need this, but I'd like to + * keep that comment if future need arises. + */ + + /* we need to check if the DA queue is empty because the DA worker may simply have + * terminated do to no new messages arriving. That does not, however, mean that the + * DA queue is empty. If there is still data in that queue, we do nothing and leave + * that for a later incarnation of this function (it will be called multiple times + * during the lifetime of DA-mode, depending on how often the DA worker receives an + * inactivity timeout. -- rgerhards, 2008-01-25 + */ + if(pThis->pqDA->iQueueSize == 0) { + pThis->bRunsDA = 0; /* tell the world we are back in non-DA mode */ + /* we destruct the queue object, which will also shutdown the queue worker. As the queue is empty, + * this will be quick. + */ + queueDestruct(&pThis->pqDA); /* and now we are ready to destruct the DA queue */ + dbgoprint((obj_t*) pThis, "disk-assistance has been turned off, disk queue was empty (iRet %d)\n", + iRet); + /* now we need to check if the regular queue has some messages. This may be the case + * when it is waiting that the high water mark is reached again. If so, we need to start up + * a regular worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) > 0) { + queueAdviseMaxWorkers(pThis); + } + } + + /* TODO: we have a *really biiiiig* memory leak here: if the queue could not be persisted, all of + * its data elements are still in memory. That doesn't really matter if we are terminated, but on + * HUP this memory leaks. We MUST add a loop of destructor calls here. However, this takes time + * (possibly a lot), so it is probably best to have a config variable for that. + * Something for 3.11.1! + * rgerhards, 2008-01-30 + */ + + RETiRet; +} + + +/* check if we run in disk-assisted mode and record that + * setting for easy (and quick!) access in the future. This + * function must only be called from constructors and only + * from those that support disk-assisted modes (aka memory- + * based queue drivers). + * rgerhards, 2008-01-14 + */ +static rsRetVal +queueChkIsDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + if(pThis->pszFilePrefix != NULL) { + pThis->bIsDA = 1; + dbgoprint((obj_t*) pThis, "is disk-assisted, disk will be used on demand\n"); + } else { + dbgoprint((obj_t*) pThis, "is NOT disk-assisted\n"); + } + + RETiRet; +} + + +/* Start disk-assisted queue mode. All internal settings are changed. This is supposed + * to be called from the DA worker, which must have been started before. The most important + * chore of this function is to create the DA queue object. If that function fails, + * the DA worker should return with an appropriate state, which in turn should lead to + * a re-set to non-DA mode in the Enq process. The queue mutex must be locked when this + * function is called, else a number of races will happen. + * Please note that this function may be called *while* we in DA mode. This is due to the + * fact that the DA worker calls it and the DA worker may be suspended (and restarted) due + * to inactivity timeouts. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueStartDA(queue_t *pThis) +{ + DEFiRet; + uchar pszDAQName[128]; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->bRunsDA == 2) /* check if already in (fully initialized) DA mode... */ + FINALIZE; /* ... then we are already done! */ + + /* create message queue */ + CHKiRet(queueConstruct(&pThis->pqDA, QUEUETYPE_DISK , 1, 0, pThis->pConsumer)); + + /* give it a name */ + snprintf((char*) pszDAQName, sizeof(pszDAQName)/sizeof(uchar), "%s[DA]", obj.GetName((obj_t*) pThis)); + obj.SetName((obj_t*) pThis->pqDA, pszDAQName); + + /* as the created queue is the same object class, we take the + * liberty to access its properties directly. + */ + pThis->pqDA->pqParent = pThis; + + CHKiRet(queueSetpUsr(pThis->pqDA, pThis->pUsr)); + CHKiRet(queueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax)); + CHKiRet(queueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown)); + CHKiRet(queueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize)); + CHKiRet(queueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(queueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt)); + CHKiRet(queueSettoActShutdown(pThis->pqDA, pThis->toActShutdown)); + CHKiRet(queueSettoEnq(pThis->pqDA, pThis->toEnq)); + CHKiRet(queueSetEnqOnly(pThis->pqDA, pThis->bDAEnqOnly, MUTEX_ALREADY_LOCKED)); + CHKiRet(queueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr)); + CHKiRet(queueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr)); + CHKiRet(queueSetiHighWtrMrk(pThis->pqDA, 0)); + CHKiRet(queueSetiDiscardMrk(pThis->pqDA, 0)); + if(pThis->toQShutdown == 0) { + CHKiRet(queueSettoQShutdown(pThis->pqDA, 0)); /* if the user really wants... */ + } else { + /* we use the shortest possible shutdown (0 is endless!) because when we run on disk AND + * have an obviously large backlog, we can't finish it in any case. So there is no point + * in holding shutdown longer than necessary. -- rgerhards, 2008-01-15 + */ + CHKiRet(queueSettoQShutdown(pThis->pqDA, 1)); + } + + iRet = queueStart(pThis->pqDA); + /* file not found is expected, that means it is no previous QIF available */ + if(iRet != RS_RET_OK && iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; /* something is wrong */ + + /* as we are right now starting DA mode because we are so busy, it is + * extremely unlikely that any regular worker is sleeping on empty queue. HOWEVER, + * we want to be on the safe side, and so we awake anyone that is waiting + * on one. So even if the scheduler plays badly with us, things should be + * quite well. -- rgerhards, 2008-01-15 + */ + wtpWakeupWrkr(pThis->pWtpReg); /* awake all workers, but not ourselves ;) */ + + pThis->bRunsDA = 2; /* we are now in DA mode, but not fully initialized */ + pThis->bChildIsDone = 0;/* set to 1 when child's worker detect queue is finished */ + pthread_cond_broadcast(&pThis->condDAReady); /* signal we are now initialized and ready to go ;) */ + + dbgoprint((obj_t*) pThis, "is now running in disk assisted mode, disk queue 0x%lx\n", + queueGetID(pThis->pqDA)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pqDA != NULL) { + queueDestruct(&pThis->pqDA); + } + dbgoprint((obj_t*) pThis, "error %d creating disk queue - giving up.\n", iRet); + pThis->bIsDA = 0; + } + + RETiRet; +} + + +/* initiate DA mode + * param bEnqOnly tells if the disk queue is to be run in enqueue-only mode. This may + * be needed during shutdown of memory queues which need to be persisted to disk. + * If this function fails (should not happen), DA mode is not turned on. + * rgerhards, 2008-01-16 + */ +static inline rsRetVal +queueInitDA(queue_t *pThis, int bEnqOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + uchar pszBuf[64]; + size_t lenBuf; + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + /* check if we already have a DA worker pool. If not, initiate one. Please note that the + * pool is created on first need but never again destructed (until the queue is). This + * is intentional. We assume that when we need it once, we may also need it on another + * occasion. Ressources used are quite minimal when no worker is running. + * rgerhards, 2008-01-24 + */ + if(pThis->pWtpDA == NULL) { + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DA", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpDA)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrDA)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueIsIdleDA)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerDA)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void*pWti)) queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueStartDA)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueTurnOffDAMode)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpDA, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpDA, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpDA, 1)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpDA, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpDA, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpDA)); + } + /* if we reach this point, we have a "good" DA worker pool */ + + /* indicate we now run in DA mode - this is reset by the DA worker if it fails */ + pThis->bRunsDA = 1; + pThis->bDAEnqOnly = bEnqOnly; + + /* now we must now adivse the wtp that we need one worker. If none is yet active, + * that will also start one up. If we forgot that step, everything would be stalled + * until the next enqueue request. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* DA queues alsways have just one worker max */ + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + RETiRet; +} + + +/* check if we need to start disk assisted mode and send some signals to + * keep it running if we are already in it. It also checks if DA mode is + * partially initialized, in which case it waits for initialization to + * complete. + * rgerhards, 2008-01-14 + */ +static inline rsRetVal +queueChkStrtDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + /* if we do not hit the high water mark, we have nothing to do */ + if(queueGetOverallQueueSize(pThis) != pThis->iHighWtrMrk) + ABORT_FINALIZE(RS_RET_OK); + + if(pThis->bRunsDA) { + /* then we need to signal that we are at the high water mark again. If that happens + * on our way down the queue, that doesn't matter, because then nobody is waiting + * on the condition variable. + * (Remember that a DA queue stops draining the queue once it has reached the low + * water mark and restarts it when the high water mark is reached again - this is + * what this code here is responsible for. Please note that all workers may have been + * terminated due to the inactivity timeout, thus we need to advise the pool that + * we need at least one). + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark in DA mode, send notify\n", + queueGetOverallQueueSize(pThis)); + queueAdviseMaxWorkers(pThis); + } else { + /* this is the case when we are currently not running in DA mode. So it is time + * to turn it back on. + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark for disk-assisted mode, initiating...\n", + queueGetOverallQueueSize(pThis)); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, MUTEX_ALREADY_LOCKED); /* initiate DA mode */ + } + +finalize_it: + RETiRet; +} + + +/* --------------- end code for disk-assisted queue modes -------------------- */ + + +/* Now, we define type-specific handlers. The provide a generic functionality, + * but for this specific type of queue. The mapping to these handlers happens during + * queue construction. Later on, handlers are called by pointers present in the + * queue instance object. + */ + +/* -------------------- fixed array -------------------- */ +static rsRetVal qConstructFixedArray(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->iMaxQueueSize == 0) + ABORT_FINALIZE(RS_RET_QSIZE_ZERO); + + if((pThis->tVars.farray.pBuf = malloc(sizeof(void *) * pThis->iMaxQueueSize)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->tVars.farray.head = 0; + pThis->tVars.farray.tail = 0; + + queueChkIsDA(pThis); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructFixedArray(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->tVars.farray.pBuf != NULL) + free(pThis->tVars.farray.pBuf); + + RETiRet; +} + +static rsRetVal qAddFixedArray(queue_t *pThis, void* in) +{ + DEFiRet; + + ASSERT(pThis != NULL); + pThis->tVars.farray.pBuf[pThis->tVars.farray.tail] = in; + pThis->tVars.farray.tail++; + if (pThis->tVars.farray.tail == pThis->iMaxQueueSize) + pThis->tVars.farray.tail = 0; + + RETiRet; +} + +static rsRetVal qDelFixedArray(queue_t *pThis, void **out) +{ + DEFiRet; + + ASSERT(pThis != NULL); + *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.head]; + + pThis->tVars.farray.head++; + if (pThis->tVars.farray.head == pThis->iMaxQueueSize) + pThis->tVars.farray.head = 0; + + RETiRet; +} + + +/* -------------------- linked list -------------------- */ + +/* first some generic functions which are also used for the unget linked list */ + +static inline rsRetVal queueAddLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, void* pUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(*ppRoot == NULL) { + *ppRoot = *ppLast = pEntry; + } else { + (*ppLast)->pNext = pEntry; + *ppLast = pEntry; + } + +finalize_it: + RETiRet; +} + +static inline rsRetVal queueDelLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, obj_t **ppUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + ASSERT(ppUsr != NULL); + ASSERT(*ppRoot != NULL); + + pEntry = *ppRoot; + *ppUsr = pEntry->pUsr; + + if(*ppRoot == *ppLast) { + *ppRoot = NULL; + *ppLast = NULL; + } else { + *ppRoot = pEntry->pNext; + } + free(pEntry); + + RETiRet; +} + +/* end generic functions which are also used for the unget linked list */ + + +static rsRetVal qConstructLinkedList(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->tVars.linklist.pRoot = 0; + pThis->tVars.linklist.pLast = 0; + + queueChkIsDA(pThis); + + RETiRet; +} + + +static rsRetVal qDestructLinkedList(queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + /* with the linked list type, there is nothing to do here. The + * reason is that the Destructor is only called after all entries + * have bene taken off the queue. In this case, there is nothing + * dynamic left with the linked list. + */ + + RETiRet; +} + +static rsRetVal qAddLinkedList(queue_t *pThis, void* pUsr) +{ + DEFiRet; + + iRet = queueAddLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, pUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(pThis->tVars.linklist.pRoot == NULL) { + pThis->tVars.linklist.pRoot = pThis->tVars.linklist.pLast = pEntry; + } else { + pThis->tVars.linklist.pLast->pNext = pEntry; + pThis->tVars.linklist.pLast = pEntry; + } + +finalize_it: +#endif + RETiRet; +} + +static rsRetVal qDelLinkedList(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + iRet = queueDelLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, ppUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + ASSERT(pThis->tVars.linklist.pRoot != NULL); + + pEntry = pThis->tVars.linklist.pRoot; + *ppUsr = pEntry->pUsr; + + if(pThis->tVars.linklist.pRoot == pThis->tVars.linklist.pLast) { + pThis->tVars.linklist.pRoot = NULL; + pThis->tVars.linklist.pLast = NULL; + } else { + pThis->tVars.linklist.pRoot = pEntry->pNext; + } + free(pEntry); + +#endif + RETiRet; +} + + +/* -------------------- disk -------------------- */ + + +static rsRetVal +queueLoadPersStrmInfoFixup(strm_t *pStrm, queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_TYPE_assert(pThis, queue); + CHKiRet(strmSetDir(pStrm, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); +finalize_it: + RETiRet; +} + + +/* This method checks if we have a QIF file for the current queue (no matter of + * queue mode). Returns RS_RET_OK if we have a QIF file or an error status otherwise. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueHaveQIF(queue_t *pThis) +{ + DEFiRet; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pszFilePrefix == NULL) + ABORT_FINALIZE(RS_RET_NO_FILEPREFIX); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glbl.GetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgoprint((obj_t*) pThis, "no .qi file found\n"); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + /* If we reach this point, we have a .qi file */ + +finalize_it: + RETiRet; +} + + +/* The method loads the persistent queue information. + * rgerhards, 2008-01-11 + */ +static rsRetVal +queueTryLoadPersistedInfo(queue_t *pThis) +{ + DEFiRet; + strm_t *psQIF = NULL; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + int iUngottenObjs; + obj_t *pUsr; + + ISOBJ_TYPE_assert(pThis, queue); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glbl.GetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgoprint((obj_t*) pThis, "clean startup, no .qi file found\n"); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .qi file */ + + CHKiRet(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_READ)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(psQIF)); + + /* first, we try to read the property bag for ourselfs */ + CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF)); + + /* then the ungotten object queue */ + iUngottenObjs = pThis->iUngottenObjs; + pThis->iUngottenObjs = 0; /* will be incremented when we add objects! */ + + while(iUngottenObjs > 0) { + /* fill the queue from disk */ + CHKiRet(obj.Deserialize((void*) &pUsr, (uchar*)"msg", psQIF, NULL, NULL)); + queueUngetObj(pThis, pUsr, MUTEX_ALREADY_LOCKED); + --iUngottenObjs; /* one less */ + } + + /* and now the stream objects (some order as when persisted!) */ + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pRead, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pWrite)); + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pRead)); + + /* OK, we could successfully read the file, so we now can request that it be + * deleted when we are done with the persisted information. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strmDestruct(&psQIF); + + if(iRet != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "error %d reading .qi file - can not read persisted info (if any)\n", + iRet); + } + + RETiRet; +} + + +/* disk queue constructor. + * Note that we use a file limit of 10,000,000 files. That number should never pose a + * problem. If so, I guess the user has a design issue... But of course, the code can + * always be changed (though it would probably be more appropriate to increase the + * allowed file size at this point - that should be a config setting... + * rgerhards, 2008-01-10 + */ +static rsRetVal qConstructDisk(queue_t *pThis) +{ + DEFiRet; + int bRestarted = 0; + + ASSERT(pThis != NULL); + + /* and now check if there is some persistent information that needs to be read in */ + iRet = queueTryLoadPersistedInfo(pThis); + if(iRet == RS_RET_OK) + bRestarted = 1; + else if(iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; + + if(bRestarted == 1) { + ; + } else { + CHKiRet(strmConstruct(&pThis->tVars.disk.pWrite)); + CHKiRet(strmSetDir(pThis->tVars.disk.pWrite, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pWrite, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE)); + CHKiRet(strmSetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pWrite)); + + CHKiRet(strmConstruct(&pThis->tVars.disk.pRead)); + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + CHKiRet(strmSetDir(pThis->tVars.disk.pRead, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pRead, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pRead, STREAMMODE_READ)); + CHKiRet(strmSetsType(pThis->tVars.disk.pRead, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pRead)); + + + CHKiRet(strmSetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(strmSetFName(pThis->tVars.disk.pRead, pThis->pszFilePrefix, pThis->lenFilePrefix)); + } + + /* now we set (and overwrite in case of a persisted restart) some parameters which + * should always reflect the current configuration variables. Be careful by doing so, + * for example file name generation must not be changed as that would break the + * ability to read existing queue files. -- rgerhards, 2008-01-12 + */ + CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize)); + CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pRead, pThis->iMaxFileSize)); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructDisk(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + strmDestruct(&pThis->tVars.disk.pWrite); + strmDestruct(&pThis->tVars.disk.pRead); + + RETiRet; +} + +static rsRetVal qAddDisk(queue_t *pThis, void* pUsr) +{ + DEFiRet; + number_t nWriteCount; + + ASSERT(pThis != NULL); + + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, &nWriteCount)); + CHKiRet((objSerialize(pUsr))(pUsr, pThis->tVars.disk.pWrite)); + CHKiRet(strmFlush(pThis->tVars.disk.pWrite)); + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */ + + pThis->tVars.disk.sizeOnDisk += nWriteCount; + + /* we have enqueued the user element to disk. So we now need to destruct + * the in-memory representation. The instance will be re-created upon + * dequeue. -- rgerhards, 2008-07-09 + */ + objDestruct(pUsr); + + dbgoprint((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets\n", + nWriteCount, pThis->tVars.disk.sizeOnDisk); + +finalize_it: + RETiRet; +} + +static rsRetVal qDelDisk(queue_t *pThis, void **ppUsr) +{ + DEFiRet; + + int64 offsIn; + int64 offsOut; + + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsIn)); + CHKiRet(obj.Deserialize(ppUsr, (uchar*) "msg", pThis->tVars.disk.pRead, NULL, NULL)); + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsOut)); + + /* This time it is a bit tricky: we free disk space only upon file deletion. So we need + * to keep track of what we have read until we get an out-offset that is lower than the + * in-offset (which indicates file change). Then, we can subtract the whole thing from + * the on-disk size. -- rgerhards, 2008-01-30 + */ + if(offsIn < offsOut) { + pThis->tVars.disk.bytesRead += offsOut - offsIn; + } else { + pThis->tVars.disk.sizeOnDisk -= pThis->tVars.disk.bytesRead; + pThis->tVars.disk.bytesRead = offsOut; + dbgoprint((obj_t*) pThis, "a file has been deleted, now %lld octets disk space used\n", pThis->tVars.disk.sizeOnDisk); + /* awake possibly waiting enq process */ + pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */ + } + +finalize_it: + RETiRet; +} + +/* -------------------- direct (no queueing) -------------------- */ +static rsRetVal qConstructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + + +static rsRetVal qDestructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + +static rsRetVal qAddDirect(queue_t *pThis, void* pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* calling the consumer is quite different here than it is from a worker thread */ + /* we need to provide the consumer's return value back to the caller because in direct + * mode the consumer probably has a lot to convey (which get's lost in the other modes + * because they are asynchronous. But direct mode is deliberately synchronous. + * rgerhards, 2008-02-12 + */ + iRet = pThis->pConsumer(pThis->pUsr, pUsr); + + RETiRet; +} + +static rsRetVal qDelDirect(queue_t __attribute__((unused)) *pThis, __attribute__((unused)) void **out) +{ + return RS_RET_OK; +} + + +/* --------------- end type-specific handlers -------------------- */ + + +/* unget a user pointer that has been dequeued. This functionality is especially important + * for consumer cancel cleanup handlers. To support it, a short list of ungotten user pointers + * is maintened in memory. + * rgerhards, 2008-01-20 + */ +static rsRetVal +queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); /* TODO: we aborted right at this place at least 3 times -- race? 2008-02-28, -03-10, -03-15 + The second time I noticed it the queue was in destruction with NO worker threads + running. The pUsr ptr was totally off and provided no clue what it may be pointing + at (except that it looked like the static data pool). Both times, the abort happend + inside an action queue */ + + dbgoprint((obj_t*) pThis, "ungetting user object %s\n", obj.GetName(pUsr)); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + iRet = queueAddLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, pUsr); + ++pThis->iUngottenObjs; /* indicate one more */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + RETiRet; +} + + +/* dequeues a user pointer from the ungotten queue. Pointers from there should always be + * dequeued first. + * + * This function must only be called when the mutex is locked! + * + * rgerhards, 2008-01-29 + */ +static rsRetVal +queueGetUngottenObj(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(ppUsr != NULL); + + iRet = queueDelLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, ppUsr); + --pThis->iUngottenObjs; /* indicate one less */ + dbgoprint((obj_t*) pThis, "dequeued ungotten user object %s\n", obj.GetName(*ppUsr)); + + RETiRet; +} + + +/* generic code to add a queue entry + * We use some specific code to most efficiently support direct mode + * queues. This is justified in spite of the gain and the need to do some + * things truely different. -- rgerhards, 2008-02-12 + */ +static rsRetVal +queueAdd(queue_t *pThis, void *pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + CHKiRet(pThis->qAdd(pThis, pUsr)); + + if(pThis->qType != QUEUETYPE_DIRECT) { + ++pThis->iQueueSize; + dbgoprint((obj_t*) pThis, "entry added, size now %d entries\n", pThis->iQueueSize); + } + +finalize_it: + RETiRet; +} + + +/* generic code to remove a queue entry + * rgerhards, 2008-01-29: we must first see if there is any object in the + * ungotten list and, if so, dequeue it first. + */ +static rsRetVal +queueDel(queue_t *pThis, void *pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* we do NOT abort if we encounter an error, because otherwise the queue + * will not be decremented, what will most probably result in an endless loop. + * If we decrement, however, we may lose a message. But that is better than + * losing the whole process because it loops... -- rgerhards, 2008-01-03 + */ + if(pThis->iUngottenObjs > 0) { + iRet = queueGetUngottenObj(pThis, (obj_t**) pUsr); + } else { + iRet = pThis->qDel(pThis, pUsr); + --pThis->iQueueSize; + } + + dbgoprint((obj_t*) pThis, "entry deleted, state %d, size now %d entries\n", + iRet, pThis->iQueueSize); + + RETiRet; +} + + +/* This function shuts down all worker threads and waits until they + * have terminated. If they timeout, they are cancelled. Parameters have been set + * before this function is called so that DA queues will be fully persisted to + * disk (if configured to do so). + * rgerhards, 2008-01-24 + * Please note that this function shuts down BOTH the parent AND the child queue + * in DA case. This is necessary because their timeouts are tightly coupled. Most + * importantly, the timeouts would be applied twice (or logic be extremely + * complex) if each would have its own shutdown. The function does not self check + * this condition - the caller must make sure it is not called with a parent. + */ +static rsRetVal queueShutdownWorkers(queue_t *pThis) +{ + DEFiRet; + DEFVARS_mutexProtection; + struct timespec tTimeout; + rsRetVal iRetLocal; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + dbgoprint((obj_t*) pThis, "initiating worker thread shutdown sequence\n"); + + /* we reduce the low water mark in any case. This is not absolutely necessary, but + * it is useful because we enable DA mode at several spots below and so we do not need + * to think about the low water mark each time. + */ + pThis->iHighWtrMrk = 1; /* if we do not do this, the DA queue will not stop! */ + pThis->iLowWtrMrk = 0; + + /* first try to shutdown the queue within the regular shutdown period */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + if(pThis->bRunsDA) { + /* We may have waited on the low water mark. As it may have changed, we + * see if we reactivate the worker. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); + } + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + /* Now wait for the queue's workers to shut down. Note that we run into the code even if we just found + * out there are no active workers - that doesn't matter: the wtp knows about that and so will + * return immediately. + * We do not yet care about the DA worker - that will be handled down later in the process. + * Note that we must not request shutdown right now - that may introduce a race: if the regular queue + * still runs DA assisted and the DA worker gets scheduled first, it will terminate itself (if the DA + * queue happens to be empty at that instant). Then the regular worker enqueues messages, what will lead + * to a restart of the worker. Of course, everything will continue to run, but in a bit sub-optimal way + * (from a performance point of view). So we don't do anything right now. The DA queue will continue to + * process messages and shutdown itself in any case if there is nothing to do. So we don't loose anything + * by not requesting shutdown now. + * rgerhards, 2008-01-25 + */ + /* first calculate absolute timeout - we need the absolute value here, because we need to coordinate + * shutdown of both the regular and DA queue on *the same* timeout. + */ + timeoutComp(&tTimeout, pThis->toQShutdown); + dbgoprint((obj_t*) pThis, "trying shutdown of regular workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "regular shutdown timed out on primary queue (this is OK)\n"); + } else { + /* OK, the regular queue is now shut down. So we can now wait for the DA queue (if running DA) */ + dbgoprint((obj_t*) pThis, "regular queue workers shut down.\n"); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(pThis->bRunsDA) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n", + queueGetID(pThis->pqDA)); + /* we use the same absolute timeout as above, so we do not use more than the configured + * timeout interval! + */ + dbgoprint((obj_t*) pThis, "trying shutdown of DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "shutdown timed out on DA queue (this is OK)\n"); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } + + /* when we reach this point, both queues are either empty or the regular queue shutdown timeout + * has expired. Now we need to check if we are configured to not loose messages. If so, we need + * to persist the queue to disk (this is only possible if the queue is DA-enabled). We must also + * set the primary queue to SHUTDOWN_IMMEDIATE, as it shall now terminate as soon as its consumer + * is done. This is especially important as we otherwise may interfere with queue order while the + * DA consumer is running. -- rgerhards, 2008-01-27 + * Note: there was a note that we should not wait eternally on the DA worker if we run in + * enqueue-only note. I have reviewed the code and think there is no need for this check. Howerver, + * I'd like to keep this note in here should we happen to run into some related trouble. + * rgerhards, 2008-01-28 + */ + wtpSetState(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE); /* set primary queue to shutdown only */ + + /* at this stage, we need to have the DA worker properly initialized and running (if there is one) */ + if(pThis->bRunsDA) + queueWaitDAModeInitialized(pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + /* optimize parameters for shutdown of DA-enabled queues */ + if(pThis->bIsDA && queueGetOverallQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) { + /* switch to enqueue-only mode so that no more actions happen */ + if(pThis->bRunsDA == 0) { + queueInitDA(pThis, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to DA mode */ + } else { + /* TODO: RACE: we may reach this point when the DA worker has been initialized (state 1) + * but is not yet running (state 2). In this case, pThis->pqDA is NULL! rgerhards, 2008-02-27 + */ + queueSetEnqOnly(pThis->pqDA, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to enqueue-only mode */ + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + /* make sure we do not timeout before we are done */ + dbgoprint((obj_t*) pThis, "bSaveOnShutdown configured, eternal timeout set\n"); + timeoutComp(&tTimeout, QUEUE_TIMEOUT_ETERNAL); + /* and run the primary queue's DA worker to drain the queue */ + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying to shut down primary queue in disk save mode, " + "continuing, but results are unpredictable\n", iRetLocal); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* now the primary queue is either empty, persisted to disk - or set to loose messages. So we + * can now request immediate shutdown of any remaining workers. Note that if bSaveOnShutdown was set, + * the queue is now empty. If regular workers are still running, and try to pull the next message, + * they will automatically terminate as there no longer is any message left to process. + */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + timeoutComp(&tTimeout, pThis->toActShutdown); + if(wtpGetCurNumWrkr(pThis->pWtpReg, LOCK_MUTEX) > 0) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of regular workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "immediate shutdown timed out on primary queue (this is acceptable and " + "triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the primary queue " + "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + /* we need to re-aquire the mutex for the next check in this case! */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + } + if(pThis->bIsDA && wtpGetCurNumWrkr(pThis->pWtpDA, LOCK_MUTEX) > 0) { + /* and now the same for the DA queue */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "immediate shutdown timed out on DA queue (this is acceptable and " + "triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the DA queue " + "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* Now queue workers should have terminated. If not, we need to cancel them as we have applied + * all timeout setting. If any worker in any queue still executes, its consumer is possibly + * long-running and cancelling is the only way to get rid of it. Note that the + * cancellation handler will probably re-queue a user pointer, so the queue's enqueue + * function is still needed (what is no problem as we do not yet destroy the queue - but I + * thought it's a good idea to mention that fact). -- rgerhards, 2008-01-25 + */ + dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the primary queue\n"); + iRetLocal = wtpCancelAll(pThis->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel primary queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + + + /* TODO: think: do we really need to do this here? Can't it happen on DA queue destruction? If we + * disable it, we get an assertion... I think this is OK, as we need to have a certain order and + * canceling the DA workers here ensures that order. But in any instant, we may have a look at this + * code after we have reaced the milestone. -- rgerhards, 2008-01-27 + */ + /* ... and now the DA queue, if it exists (should always be after the primary one) */ + if(pThis->pqDA != NULL) { + dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the DA queue\n"); + iRetLocal = wtpCancelAll(pThis->pqDA->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel DA queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + } + + /* ... finally ... all worker threads have terminated :-) + * Well, more precisely, they *are in termination*. Some cancel cleanup handlers + * may still be running. + */ + dbgoprint((obj_t*) pThis, "worker threads terminated, remaining queue size %d.\n", queueGetOverallQueueSize(pThis)); + + RETiRet; +} + + + +/* Constructor for the queue object + * This constructs the data structure, but does not yet start the queue. That + * is done by queueStart(). The reason is that we want to give the caller a chance + * to modify some parameters before the queue is actually started. + */ +rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)) +{ + DEFiRet; + queue_t *pThis; + + ASSERT(ppThis != NULL); + ASSERT(pConsumer != NULL); + ASSERT(iWorkerThreads >= 0); + + if((pThis = (queue_t *)calloc(1, sizeof(queue_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we have an object, so let's fill the properties */ + objConstructSetObjInfo(pThis); + if((pThis->pszSpoolDir = (uchar*) strdup((char*)glbl.GetWorkDir())) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* set some water marks so that we have useful defaults if none are set specifically */ + pThis->iFullDlyMrk = (iMaxQueueSize < 100) ? iMaxQueueSize : 100; /* 100 should be far sufficient */ + pThis->iLightDlyMrk = iMaxQueueSize - (iMaxQueueSize / 100) * 70; /* default 70% */ + + pThis->lenSpoolDir = strlen((char*)pThis->pszSpoolDir); + pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */ + pThis->iQueueSize = 0; + pThis->iMaxQueueSize = iMaxQueueSize; + pThis->pConsumer = pConsumer; + pThis->iNumWorkerThreads = iWorkerThreads; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ + + pThis->pszFilePrefix = NULL; + pThis->qType = qType; + + /* set type-specific handlers and other very type-specific things (we can not totally hide it...) */ + switch(qType) { + case QUEUETYPE_FIXED_ARRAY: + pThis->qConstruct = qConstructFixedArray; + pThis->qDestruct = qDestructFixedArray; + pThis->qAdd = qAddFixedArray; + pThis->qDel = qDelFixedArray; + break; + case QUEUETYPE_LINKEDLIST: + pThis->qConstruct = qConstructLinkedList; + pThis->qDestruct = qDestructLinkedList; + pThis->qAdd = qAddLinkedList; + pThis->qDel = (rsRetVal (*)(queue_t*,void**)) qDelLinkedList; + break; + case QUEUETYPE_DISK: + pThis->qConstruct = qConstructDisk; + pThis->qDestruct = qDestructDisk; + pThis->qAdd = qAddDisk; + pThis->qDel = qDelDisk; + /* special handling */ + pThis->iNumWorkerThreads = 1; /* we need exactly one worker */ + break; + case QUEUETYPE_DIRECT: + pThis->qConstruct = qConstructDirect; + pThis->qDestruct = qDestructDirect; + pThis->qAdd = qAddDirect; + pThis->qDel = qDelDirect; + break; + } + +finalize_it: + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Updates admin structure and frees ressources. + * Params: + * arg1 - user pointer (in this case a queue_t) + * arg2 - user data pointer (in this case a queue data element, any object [queue's pUsr ptr!]) + * Note that arg2 may be NULL, in which case no dequeued but unprocessed pUsr exists! + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueConsumerCancelCleanup(void *arg1, void *arg2) +{ + DEFiRet; + + queue_t *pThis = (queue_t*) arg1; + obj_t *pUsr = (obj_t*) arg2; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pUsr != NULL) { + /* make sure the data element is not lost */ + dbgoprint((obj_t*) pThis, "cancelation cleanup handler consumer called, we need to unget one user data element\n"); + CHKiRet(queueUngetObj(pThis, pUsr, LOCK_MUTEX)); + } + +finalize_it: + RETiRet; +} + + + +/* This function checks if the provided message shall be discarded and does so, if needed. + * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to + * provide real-time creation of spool files. + * Note: cached copies of iQueueSize and bRunsDA are provided so that no mutex locks are required. + * The caller must have obtained them while the mutex was locked. Of course, these values may no + * longer be current, but that is OK for the discard check. At worst, the message is either processed + * or discarded when it should not have been. As discarding is in itself somewhat racy and erratic, + * that is no problems for us. This function MUST NOT lock the queue mutex, it could result in + * deadlocks! + * If the message is discarded, it can no longer be processed by the caller. So be sure to check + * the return state! + * rgerhards, 2008-01-24 + */ +static int queueChkDiscardMsg(queue_t *pThis, int iQueueSize, int bRunsDA, void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + int iSeverity; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); + + if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk && bRunsDA == 0) { + iRetLocal = objGetSeverity(pUsr, &iSeverity); + if(iRetLocal == RS_RET_OK && iSeverity >= pThis->iDiscardSeverity) { + dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), discarded severity %d message\n", + iQueueSize, iSeverity); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } else { + dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), but could not drop msg " + "(iRet: %d, severity %d)\n", iQueueSize, iRetLocal, iSeverity); + } + } + +finalize_it: + RETiRet; +} + + +/* dequeue the queued object for the queue consumers. + * rgerhards, 2008-10-21 + */ +static rsRetVal +queueDequeueConsumable(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + void *pUsr; + int iQueueSize; + int bRunsDA; /* cache for early mutex release */ + + /* dequeue element (still protected from mutex) */ + iRet = queueDel(pThis, &pUsr); + queueChkPersist(pThis); + iQueueSize = queueGetOverallQueueSize(pThis); /* cache this for after mutex release */ + bRunsDA = pThis->bRunsDA; /* cache this for after mutex release */ + + /* We now need to save the user pointer for the cancel cleanup handler, BUT ONLY + * if we could successfully obtain a user pointer. Otherwise, we would bring the + * cancel cleanup handler into big troubles (and we did ;)). Note that we can + * NOT set the variable further below, as this may lead to an object leak. We + * may get cancelled before we reach that part of the code, so the only + * solution is to do it here. -- rgerhards, 2008-02-27 + */ + if(iRet == RS_RET_OK) { + pWti->pUsrp = pUsr; + } + + /* awake some flow-controlled sources if we can do this right now */ + /* TODO: this could be done better from a performance point of view -- do it only if + * we have someone waiting for the condition (or only when we hit the watermark right + * on the nail [exact value]) -- rgerhards, 2008-03-14 + */ + if(iQueueSize < pThis->iFullDlyMrk) { + pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk); + } + + if(iQueueSize < pThis->iLightDlyMrk) { + pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk); + } + + d_pthread_mutex_unlock(pThis->mut); + pthread_cond_signal(&pThis->notFull); + pthread_setcancelstate(iCancelStateSave, NULL); + /* WE ARE NO LONGER PROTECTED BY THE MUTEX */ + + /* do actual processing (the lengthy part, runs in parallel) + * If we had a problem while dequeing, we do not call the consumer, + * but we otherwise ignore it. This is in the hopes that it will be + * self-healing. However, this is really not a good thing. + * rgerhards, 2008-01-03 + */ + if(iRet != RS_RET_OK) + FINALIZE; + + /* we are running in normal, non-disk-assisted mode do a quick check if we need to drain the queue. + * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to + * provide real-time creation of spool files. + * Note: It is OK to use the cached iQueueSize here, because it does not hurt if it is slightly wrong. + */ + CHKiRet(queueChkDiscardMsg(pThis, iQueueSize, bRunsDA, pUsr)); + +finalize_it: + if(iRet != RS_RET_OK && iRet != RS_RET_DISCARDMSG) { + dbgoprint((obj_t*) pThis, "error %d dequeueing element - ignoring, but strange things " + "may happen\n", iRet); + } + RETiRet; +} + + +/* The rate limiter + * + * Here we may wait if a dequeue time window is defined or if we are + * rate-limited. TODO: If we do so, we should also look into the + * way new worker threads are spawned. Obviously, it doesn't make much + * sense to spawn additional worker threads when none of them can do any + * processing. However, it is deemed acceptable to allow this for an initial + * implementation of the timeframe/rate limiting feature. + * Please also note that these feature could also be implemented at the action + * level. However, that would limit them to be used together with actions. We have + * taken the broader approach, moving it right into the queue. This is even + * necessary if we want to prevent spawning of multiple unnecessary worker + * threads as described above. -- rgerhards, 2008-04-02 + * + * + * time window: tCurr is current time; tFrom is start time, tTo is end time (in mil 24h format). + * We may have tFrom = 4, tTo = 10 --> run from 4 to 10 hrs. nice and happy + * we may also have tFrom= 22, tTo = 4 -> run from 10pm to 4am, which is actually two + * windows: 0-4; 22-23:59 + * so when to run? Let's assume we have 3am + * + * if(tTo < tFrom) { + * if(tCurr < tTo [3 < 4] || tCurr > tFrom [3 > 22]) + * do work + * else + * sleep for tFrom - tCurr "hours" [22 - 5 --> 17] + * } else { + * if(tCurr >= tFrom [3 >= 4] && tCurr < tTo [3 < 10]) + * do work + * else + * sleep for tTo - tCurr "hours" [4 - 3 --> 1] + * } + * + * Bottom line: we need to check which type of window we have and need to adjust our + * logic accordingly. Of course, sleep calculations need to be done up to the minute, + * but you get the idea from the code above. + */ +static rsRetVal +queueRateLimiter(queue_t *pThis) +{ + DEFiRet; + int iDelay; + int iHrCurr; + time_t tCurr; + struct tm m; + + ISOBJ_TYPE_assert(pThis, queue); + + dbgoprint((obj_t*) pThis, "entering rate limiter\n"); + + iDelay = 0; + if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */ + /* time calls are expensive, so only do them when needed */ + time(&tCurr); + localtime_r(&tCurr, &m); + iHrCurr = m.tm_hour; + + if(pThis->iDeqtWinToHr < pThis->iDeqtWinFromHr) { + if(iHrCurr < pThis->iDeqtWinToHr || iHrCurr > pThis->iDeqtWinFromHr) { + ; /* do not delay */ + } else { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } else { + if(iHrCurr >= pThis->iDeqtWinFromHr && iHrCurr < pThis->iDeqtWinToHr) { + ; /* do not delay */ + } else { + if(iHrCurr < pThis->iDeqtWinFromHr) { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr - 1) * 3600; /* -1 as we are already in the hour */ + iDelay += (60 - m.tm_min) * 60; + iDelay += 60 - m.tm_sec; + } else { + iDelay = (24 - iHrCurr + pThis->iDeqtWinFromHr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } + } + } + + if(iDelay > 0) { + dbgoprint((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay); + srSleep(iDelay, 0); + } + + RETiRet; +} + + + +/* This is the queue consumer in the regular (non-DA) case. It is + * protected by the queue mutex, but MUST release it as soon as possible. + * rgerhards, 2008-01-21 + */ +static rsRetVal +queueConsumerReg(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(pThis->pConsumer(pThis->pUsr, pWti->pUsrp)); + + /* we now need to check if we should deliberately delay processing a bit + * and, if so, do that. -- rgerhards, 2008-01-30 + */ + if(pThis->iDeqSlowdown) { + dbgoprint((obj_t*) pThis, "sleeping %d microseconds as requested by config params\n", + pThis->iDeqSlowdown); + srSleep(pThis->iDeqSlowdown / 1000000, pThis->iDeqSlowdown % 1000000); + } + +finalize_it: + RETiRet; +} + + +/* This is a special consumer to feed the disk-queue in disk-assited mode. + * When active, our own queue more or less acts as a memory buffer to the disk. + * So this consumer just needs to drain the memory queue and submit entries + * to the disk queue. The disk queue will then call the actual consumer from + * the app point of view (we chain two queues here). + * When this method is entered, the mutex is always locked and needs to be unlocked + * as part of the processing. + * rgerhards, 2008-01-14 + */ +static rsRetVal +queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(queueEnqObj(pThis->pqDA, eFLOWCTL_NO_DELAY, pWti->pUsrp)); + +finalize_it: + dbgoprint((obj_t*) pThis, "DAConsumer returns with iRet %d\n", iRet); + RETiRet; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. + * Version for the DA worker thread. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrDA(queue_t *pThis) +{ + /* if our queue is in destruction, we drain to the DA queue and so we shall not terminate + * until we have done so. + */ + int bStopWrkr; + + BEGINfunc + + if(pThis->bEnqOnly) { + bStopWrkr = 1; + } else { + if(pThis->bRunsDA) { + ASSERT(pThis->pqDA != NULL); + if( pThis->pqDA->bEnqOnly + && pThis->pqDA->sizeOnDiskMax > 0 + && pThis->pqDA->tVars.disk.sizeOnDisk > pThis->pqDA->sizeOnDiskMax) { + /* this queue can never grow, so we can give up... */ + bStopWrkr = 1; + } else if(queueGetOverallQueueSize(pThis) < pThis->iHighWtrMrk && pThis->bQueueStarted == 1) { + bStopWrkr = 1; + } else { + bStopWrkr = 0; + } + } else { + bStopWrkr = 1; + } + } + + ENDfunc + return bStopWrkr; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. + * Version for the regular worker thread. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrReg(queue_t *pThis) +{ + return pThis->bEnqOnly || pThis->bRunsDA || (pThis->pqParent != NULL && queueGetOverallQueueSize(pThis) == 0); +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! DA queue version + */ +static int +queueIsIdleDA(queue_t *pThis) +{ + /* remember: iQueueSize is the DA queue size, not the main queue! */ + /* TODO: I think we need just a single function for DA and non-DA mode - but I leave it for now as is */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +} +/* must only be called when the queue mutex is locked, else results + * are not stable! Regular queue version + */ +static int +queueIsIdleReg(queue_t *pThis) +{ +#if 0 /* enable for performance testing */ + int ret; + ret = queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk); + if(ret) fprintf(stderr, "queue is idle\n"); + return ret; +#else + /* regular code! */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +#endif +} + + +/* This function is called when a worker thread for the regular queue is shut down. + * If we are the primary queue, this is not really interesting to us. If, however, + * we are the DA (child) queue, that means the DA queue is empty. In that case, we + * need to signal the parent queue's DA worker, so that it can terminate DA mode. + * rgerhards, 2008-01-26 + * rgerhards, 2008-02-27: HOWEVER, in a shutdown condition, it may be that the parent's worker thread pool + * has already been terminated and destructed. This *is* a legal condition and happens + * from time to time in practice. So we need to signal only if there still is a + * parent DA worker queue. Please keep in mind that the the parent's DA worker + * pool is DIFFERENT from our (DA queue) regular worker pool. So when the parent's + * pWtpDA is destructed, there can still be some of our (DAq/wtp) threads be running. + * I am telling this, because I, too, always get confused by those... + */ +static rsRetVal +queueRegOnWrkrShutdown(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 1; /* indicate we are done */ + if(pThis->pqParent->pWtpDA != NULL) { /* see comment in function header from 2008-02-27 */ + wtpAdviseMaxWorkers(pThis->pqParent->pWtpDA, 1); /* reactivate DA worker (always 1) */ + } + } + + RETiRet; +} + + +/* The following function is called when a regular queue worker starts up. We need this + * hook to indicate in the parent queue (if we are a child) that we are not done yet. + */ +static rsRetVal +queueRegOnWrkrStartup(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 0; + } + + RETiRet; +} + + +/* start up the queue - it must have been constructed and parameters defined + * before. + */ +rsRetVal queueStart(queue_t *pThis) /* this is the ConstructionFinalizer */ +{ + DEFiRet; + rsRetVal iRetLocal; + int bInitialized = 0; /* is queue already initialized? */ + uchar pszBuf[64]; + size_t lenBuf; + + ASSERT(pThis != NULL); + + /* we need to do a quick check if our water marks are set plausible. If not, + * we correct the most important shortcomings. TODO: do that!!!! -- rgerhards, 2008-03-14 + */ + + /* finalize some initializations that could not yet be done because it is + * influenced by properties which might have been set after queueConstruct () + */ + if(pThis->pqParent == NULL) { + pThis->mut = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init(pThis->mut, NULL); + } else { + /* child queue, we need to use parent's mutex */ + dbgoprint((obj_t*) pThis, "I am a child\n"); + pThis->mut = pThis->pqParent->mut; + } + + pthread_mutex_init(&pThis->mutThrdMgmt, NULL); + pthread_cond_init (&pThis->condDAReady, NULL); + pthread_cond_init (&pThis->notFull, NULL); + pthread_cond_init (&pThis->notEmpty, NULL); + pthread_cond_init (&pThis->belowFullDlyWtrMrk, NULL); + pthread_cond_init (&pThis->belowLightDlyWtrMrk, NULL); + + /* call type-specific constructor */ + CHKiRet(pThis->qConstruct(pThis)); /* this also sets bIsDA */ + + dbgoprint((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, qsize %d, child %d starting\n", + pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize, + queueGetOverallQueueSize(pThis), pThis->pqParent == NULL ? 0 : 1); + + if(pThis->qType == QUEUETYPE_DIRECT) + FINALIZE; /* with direct queues, we are already finished... */ + + /* create worker thread pools for regular operation. The DA pool is created on an as-needed + * basis, which potentially means never under most circumstances. + */ + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:Reg", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpReg)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpReg, pszBuf, lenBuf)); + CHKiRet(wtpSetpfRateLimiter (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRateLimiter)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrReg)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueIsIdleReg)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerReg)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void*pWti))queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrStartup)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrShutdown)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpReg, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpReg, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpReg, pThis->iNumWorkerThreads)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpReg, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpReg, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpReg)); + + /* initialize worker thread instances */ + if(pThis->bIsDA) { + /* If we are disk-assisted, we need to check if there is a QIF file + * which we need to load. -- rgerhards, 2008-01-15 + */ + iRetLocal = queueHaveQIF(pThis); + if(iRetLocal == RS_RET_OK) { + dbgoprint((obj_t*) pThis, "on-disk queue present, needs to be reloaded\n"); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, LOCK_MUTEX); /* initiate DA mode */ + bInitialized = 1; /* we are done */ + } else { + /* TODO: use logerror? -- rgerhards, 2008-01-16 */ + dbgoprint((obj_t*) pThis, "error %d trying to access on-disk queue files, starting without them. " + "Some data may be lost\n", iRetLocal); + } + } + + if(!bInitialized) { + dbgoprint((obj_t*) pThis, "queue starts up without (loading) any DA disk state (this is normal for the DA " + "queue itself!)\n"); + } + + /* if the queue already contains data, we need to start the correct number of worker threads. This can be + * the case when a disk queue has been loaded. If we did not start it here, it would never start. + */ + queueAdviseMaxWorkers(pThis); + pThis->bQueueStarted = 1; + +finalize_it: + RETiRet; +} + + +/* persist the queue to disk. If we have something to persist, we first + * save the information on the queue properties itself and then we call + * the queue-type specific drivers. + * Variable bIsCheckpoint is set to 1 if the persist is for a checkpoint, + * and 0 otherwise. + * rgerhards, 2008-01-10 + */ +static rsRetVal queuePersist(queue_t *pThis, int bIsCheckpoint) +{ + DEFiRet; + strm_t *psQIF = NULL; /* Queue Info File */ + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + obj_t *pUsr; + + ASSERT(pThis != NULL); + + if(pThis->qType != QUEUETYPE_DISK) { + if(queueGetOverallQueueSize(pThis) > 0) { + /* This error code is OK, but we will probably not implement this any time + * The reason is that persistence happens via DA queues. But I would like to + * leave the code as is, as we so have a hook in case we need one. + * -- rgerhards, 2008-01-28 + */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + } else + FINALIZE; /* if the queue is empty, we are happy and done... */ + } + + dbgoprint((obj_t*) pThis, "persisting queue to disk, %d entries...\n", queueGetOverallQueueSize(pThis)); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glbl.GetWorkDir(), (char*)pThis->pszFilePrefix); + + if((bIsCheckpoint != QUEUE_CHECKPOINT) && (queueGetOverallQueueSize(pThis) == 0)) { + if(pThis->bNeedDelQIF) { + unlink((char*)pszQIFNam); + pThis->bNeedDelQIF = 0; + } + /* indicate spool file needs to be deleted */ + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + FINALIZE; /* nothing left to do, so be happy */ + } + + CHKiRet(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_WRITE)); + CHKiRet(strmSetiAddtlOpenFlags(psQIF, O_TRUNC)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(psQIF)); + + /* first, write the property bag for ourselfs + * And, surprisingly enough, we currently need to persist only the size of the + * queue. All the rest is re-created with then-current config parameters when the + * queue is re-created. Well, we'll also save the current queue type, just so that + * we know when somebody has changed the queue type... -- rgerhards, 2008-01-11 + */ + CHKiRet(obj.BeginSerializePropBag(psQIF, (obj_t*) pThis)); + objSerializeSCALAR(psQIF, iQueueSize, INT); + objSerializeSCALAR(psQIF, iUngottenObjs, INT); + objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64); + objSerializeSCALAR(psQIF, tVars.disk.bytesRead, INT64); + CHKiRet(obj.EndSerialize(psQIF)); + + /* now we must persist all objects on the ungotten queue - they can not go to + * to the regular files. -- rgerhards, 2008-01-29 + */ + while(pThis->iUngottenObjs > 0) { + CHKiRet(queueGetUngottenObj(pThis, &pUsr)); + CHKiRet((objSerialize(pUsr))(pUsr, psQIF)); + objDestruct(pUsr); + } + + /* now persist the stream info */ + CHKiRet(strmSerialize(pThis->tVars.disk.pWrite, psQIF)); + CHKiRet(strmSerialize(pThis->tVars.disk.pRead, psQIF)); + + /* tell the input file object that it must not delete the file on close if the queue + * is non-empty - but only if we are not during a simple checkpoint + */ + if(bIsCheckpoint != QUEUE_CHECKPOINT) { + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 0)); + } + + /* we have persisted the queue object. So whenever it comes to an empty queue, + * we need to delete the QIF. Thus, we indicte that need. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strmDestruct(&psQIF); + + RETiRet; +} + + +/* check if we need to persist the current queue info. If an + * error occurs, thus should be ignored by caller (but we still + * abide to our regular call interface)... + * rgerhards, 2008-01-13 + */ +rsRetVal queueChkPersist(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->iPersistUpdCnt && ++pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) { + queuePersist(pThis, QUEUE_CHECKPOINT); + pThis->iUpdsSincePersist = 0; + } + + RETiRet; +} + + +/* destructor for the queue object */ +BEGINobjDestruct(queue) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(queue) + pThis->bQueueInDestruction = 1; /* indicate we are in destruction (modifies some behaviour) */ + + /* shut down all workers (handles *all* of the persistence logic) + * See function head comment of queueShutdownWorkers () on why we don't call it + * We also do not need to shutdown workers when we are in enqueue-only mode or we are a + * direct queue - because in both cases we have none... ;) + * with a child! -- rgerhards, 2008-01-28 + */ + if(pThis->qType != QUEUETYPE_DIRECT && !pThis->bEnqOnly && pThis->pqParent == NULL) + queueShutdownWorkers(pThis); + + /* finally destruct our (regular) worker thread pool + * Note: currently pWtpReg is never NULL, but if we optimize our logic, this may happen, + * e.g. when they are not created in enqueue-only mode. We already check the condition + * as this may otherwise be very hard to find once we optimize (and have long forgotten + * about this condition here ;) + * rgerhards, 2008-01-25 + */ + if(pThis->qType != QUEUETYPE_DIRECT && pThis->pWtpReg != NULL) { + wtpDestruct(&pThis->pWtpReg); + } + + /* Now check if we actually have a DA queue and, if so, destruct it. + * Note that the wtp must be destructed first, it may be in cancel cleanup handler + * *right now* and actually *need* to access the queue object to persist some final + * data (re-queueing case). So we need to destruct the wtp first, which will make + * sure all workers have terminated. Please note that this also generates a situation + * where it is possible that the DA queue has a parent pointer but the parent has + * no WtpDA associated with it - which is perfectly legal thanks to this code here. + */ + if(pThis->pWtpDA != NULL) { + wtpDestruct(&pThis->pWtpDA); + } + if(pThis->pqDA != NULL) { + queueDestruct(&pThis->pqDA); + } + + /* persist the queue (we always do that - queuePersits() does cleanup if the queue is empty) + * This handler is most important for disk queues, it will finally persist the necessary + * on-disk structures. In theory, other queueing modes may implement their other (non-DA) + * methods of persisting a queue between runs, but in practice all of this is done via + * disk queues and DA mode. Anyhow, it doesn't hurt to know that we could extend it here + * if need arises (what I doubt...) -- rgerhards, 2008-01-25 + */ + CHKiRet_Hdlr(queuePersist(pThis, QUEUE_NO_CHECKPOINT)) { + dbgoprint((obj_t*) pThis, "error %d persisting queue - data lost!\n", iRet); + } + + /* finally, clean up some simple things... */ + if(pThis->pqParent == NULL) { + /* if we are not a child, we allocated our own mutex, which we now need to destroy */ + pthread_mutex_destroy(pThis->mut); + free(pThis->mut); + } + pthread_mutex_destroy(&pThis->mutThrdMgmt); + pthread_cond_destroy(&pThis->condDAReady); + pthread_cond_destroy(&pThis->notFull); + pthread_cond_destroy(&pThis->notEmpty); + pthread_cond_destroy(&pThis->belowFullDlyWtrMrk); + pthread_cond_destroy(&pThis->belowLightDlyWtrMrk); + + /* type-specific destructor */ + iRet = pThis->qDestruct(pThis); + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + if(pThis->pszSpoolDir != NULL) + free(pThis->pszSpoolDir); +ENDobjDestruct(queue) + + +/* set the queue's file prefix + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +rsRetVal +queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix) +{ + DEFiRet; + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + if(pszPrefix == NULL) /* just unset the prefix! */ + ABORT_FINALIZE(RS_RET_OK); + + if((pThis->pszFilePrefix = malloc(sizeof(uchar) * iLenPrefix + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pThis->pszFilePrefix, pszPrefix, iLenPrefix + 1); + pThis->lenFilePrefix = iLenPrefix; + +finalize_it: + RETiRet; +} + +/* set the queue's maximum file size + * rgerhards, 2008-01-09 + */ +rsRetVal +queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(iMaxFileSize < 1024) { + ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW); + } + + pThis->iMaxFileSize = iMaxFileSize; + +finalize_it: + RETiRet; +} + + +/* enqueue a new user data element + * Enqueues the new element and awakes worker thread. + */ +rsRetVal +queueEnqObj(queue_t *pThis, flowControl_t flowCtlType, void *pUsr) +{ + DEFiRet; + int iCancelStateSave; + int i; + struct timespec t; + + ISOBJ_TYPE_assert(pThis, queue); + + /* Please note that this function is not cancel-safe and consequently + * sets the calling thread's cancelibility state to PTHREAD_CANCEL_DISABLE + * during its execution. If that is not done, race conditions occur if the + * thread is canceled (most important use case is input module termination). + * rgerhards, 2008-01-08 + */ + if(pThis->qType != QUEUETYPE_DIRECT) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(pThis->mut); + } + + /* first check if we need to discard this message (which will cause CHKiRet() to exit) */ + CHKiRet(queueChkDiscardMsg(pThis, pThis->iQueueSize, pThis->bRunsDA, pUsr)); + + /* then check if we need to add an assistance disk queue */ + if(pThis->bIsDA) + CHKiRet(queueChkStrtDA(pThis)); + + + /* handle flow control + * There are two different flow control mechanisms: basic and advanced flow control. + * Basic flow control has always been implemented and protects the queue structures + * in that it makes sure no more data is enqueued than the queue is configured to + * support. Enhanced flow control is being added today. There are some sources which + * can easily be stopped, e.g. a file reader. This is the case because it is unlikely + * that blocking those sources will have negative effects (after all, the file is + * continued to be written). Other sources can somewhat be blocked (e.g. the kernel + * log reader or the local log stream reader): in general, nothing is lost if messages + * from these sources are not picked up immediately. HOWEVER, they can not block for + * an extended period of time, as this either causes message loss or - even worse - some + * other bad effects (e.g. unresponsive system in respect to the main system log socket). + * Finally, there are some (few) sources which can not be blocked at all. UDP syslog is + * a prime example. If a UDP message is not received, it is simply lost. So we can't + * do anything against UDP sockets that come in too fast. The core idea of advanced + * flow control is that we take into account the different natures of the sources and + * select flow control mechanisms that fit these needs. This also means, in the end + * result, that non-blockable sources like UDP syslog receive priority in the system. + * It's a side effect, but a good one ;) -- rgerhards, 2008-03-14 + */ + if(flowCtlType == eFLOWCTL_FULL_DELAY) { + while(pThis->iQueueSize >= pThis->iFullDlyMrk) { + dbgoprint((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayble message - blocking.\n"); + pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut); /* TODO error check? But what do then? */ + } + } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY) { + while(pThis->iQueueSize >= pThis->iLightDlyMrk) { + dbgoprint((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light delayble message - blocking a bit.\n"); + timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */ + pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); /* TODO error check? But what do then? */ + } + } + + /* from our regular flow control settings, we are now ready to enqueue the object. + * However, we now need to do a check if the queue permits to add more data. If that + * is not the case, basic flow control enters the field, which means we wait for + * the queue to become ready or drop the new message. -- rgerhards, 2008-03-14 + */ + while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) + || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 + && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { + dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + } + + /* and finally enqueue the message */ + CHKiRet(queueAdd(pThis, pUsr)); + queueChkPersist(pThis); + +finalize_it: + if(pThis->qType != QUEUETYPE_DIRECT) { + d_pthread_mutex_unlock(pThis->mut); + i = pthread_cond_signal(&pThis->notEmpty); + dbgoprint((obj_t*) pThis, "EnqueueMsg signaled condition (%d)\n", i); + pthread_setcancelstate(iCancelStateSave, NULL); + } + + /* make sure at least one worker is running. */ + if(pThis->qType != QUEUETYPE_DIRECT) { + queueAdviseMaxWorkers(pThis); + } + + RETiRet; +} + + +/* set queue mode to enqueue only or not + * There is one subtle issue: this method may be called during queue + * construction or while it is running. In the former case, the queue + * mutex does not yet exist (it is NULL), while in the later case it + * must be locked. The function detects the state and operates as + * required. + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + + /* for simplicity, we do one big mutex lock. This method is extremely seldom + * called, so that doesn't matter... -- rgerhards, 2008-01-16 + */ + if(pThis->mut != NULL) { + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + } + + if(bEnqOnly == pThis->bEnqOnly) + FINALIZE; /* no change, nothing to do */ + + if(pThis->bQueueStarted) { + /* we need to adjust queue operation only if we are not during initial param setup */ + if(bEnqOnly == 1) { + /* switch to enqueue-only mode */ + /* this means we need to terminate all workers - that's it... */ + dbgoprint((obj_t*) pThis, "switching to enqueue-only mode, terminating all worker threads\n"); + if(pThis->pWtpReg != NULL) + wtpWakeupAllWrkr(pThis->pWtpReg); + if(pThis->pWtpDA != NULL) + wtpWakeupAllWrkr(pThis->pWtpDA); + } else { + /* switch back to regular mode */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); /* we don't need this so far... */ + } + } + + pThis->bEnqOnly = bEnqOnly; + +finalize_it: + if(pThis->mut != NULL) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + RETiRet; +} + + +/* some simple object access methods */ +DEFpropSetMeth(queue, iPersistUpdCnt, int) +DEFpropSetMeth(queue, iDeqtWinFromHr, int) +DEFpropSetMeth(queue, iDeqtWinToHr, int) +DEFpropSetMeth(queue, toQShutdown, long) +DEFpropSetMeth(queue, toActShutdown, long) +DEFpropSetMeth(queue, toWrkShutdown, long) +DEFpropSetMeth(queue, toEnq, long) +DEFpropSetMeth(queue, iHighWtrMrk, int) +DEFpropSetMeth(queue, iLowWtrMrk, int) +DEFpropSetMeth(queue, iDiscardMrk, int) +DEFpropSetMeth(queue, iFullDlyMrk, int) +DEFpropSetMeth(queue, iDiscardSeverity, int) +DEFpropSetMeth(queue, bIsDA, int) +DEFpropSetMeth(queue, iMinMsgsPerWrkr, int) +DEFpropSetMeth(queue, bSaveOnShutdown, int) +DEFpropSetMeth(queue, pUsr, void*) +DEFpropSetMeth(queue, iDeqSlowdown, int) +DEFpropSetMeth(queue, sizeOnDiskMax, int64) + + +/* This function can be used as a generic way to set properties. Only the subset + * of properties required to read persisted property bags is supported. This + * functions shall only be called by the property bag reader, thus it is static. + * rgerhards, 2008-01-11 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +static rsRetVal queueSetProperty(queue_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pProp != NULL); + + if(isProp("iQueueSize")) { + pThis->iQueueSize = pProp->val.num; + } else if(isProp("iUngottenObjs")) { + pThis->iUngottenObjs = pProp->val.num; + } else if(isProp("tVars.disk.sizeOnDisk")) { + pThis->tVars.disk.sizeOnDisk = pProp->val.num; + } else if(isProp("tVars.disk.bytesRead")) { + pThis->tVars.disk.bytesRead = pProp->val.num; + } else if(isProp("qType")) { + if(pThis->qType != pProp->val.num) + ABORT_FINALIZE(RS_RET_QTYPE_MISMATCH); + } + +finalize_it: + RETiRet; +} +#undef isProp + +/* dummy */ +rsRetVal queueQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(queue, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_SETPROPERTY, queueSetProperty); +ENDObjClassInit(queue) + +/* vi:set ai: + */ diff --git a/runtime/queue.h b/runtime/queue.h new file mode 100644 index 00000000..9e75b31b --- /dev/null +++ b/runtime/queue.h @@ -0,0 +1,205 @@ +/* Definition of the queue support module. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef QUEUE_H_INCLUDED +#define QUEUE_H_INCLUDED + +#include <pthread.h> +#include "obj.h" +#include "wtp.h" +#include "stream.h" + +/* queue types */ +typedef enum { + QUEUETYPE_FIXED_ARRAY = 0,/* a simple queue made out of a fixed (initially malloced) array fast but memoryhog */ + QUEUETYPE_LINKEDLIST = 1, /* linked list used as buffer, lower fixed memory overhead but slower */ + QUEUETYPE_DISK = 2, /* disk files used as buffer */ + QUEUETYPE_DIRECT = 3 /* no queuing happens, consumer is directly called */ +} queueType_t; + +/* list member definition for linked list types of queues: */ +typedef struct qLinkedList_S { + struct qLinkedList_S *pNext; + void *pUsr; +} qLinkedList_t; + + +typedef struct qWrkThrd_s { + pthread_t thrdID; /* thread ID */ + qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ + obj_t *pUsr; /* current user object being processed (or NULL if none) */ + struct queue_s *pQueue; /* my queue (important if only the work thread instance is passed! */ + int iThrd; /* my worker thread array index */ + pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ + pthread_mutex_t mut; +} qWrkThrd_t; /* type for queue worker threads */ + +/* the queue object */ +typedef struct queue_s { + BEGINobjInstance; + queueType_t qType; + int bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */ + int bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */ + int bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */ + int bQueueInDestruction;/* 1 if queue is in destruction process, 0 otherwise */ + int iQueueSize; /* Current number of elements in the queue */ + int iMaxQueueSize; /* how large can the queue grow? */ + int iNumWorkerThreads;/* number of worker threads to use */ + int iCurNumWrkThrd;/* current number of active worker threads */ + int iMinMsgsPerWrkr;/* minimum nbr of msgs per worker thread, if more, a new worker is started until max wrkrs */ + wtp_t *pWtpDA; + wtp_t *pWtpReg; + void *pUsr; /* a global, user-supplied pointer. Is passed back to consumer. */ + int iUpdsSincePersist;/* nbr of queue updates since the last persist call */ + int iPersistUpdCnt; /* persits queue info after this nbr of updates - 0 -> persist only on shutdown */ + int iHighWtrMrk; /* high water mark for disk-assisted memory queues */ + int iLowWtrMrk; /* low water mark for disk-assisted memory queues */ + int iDiscardMrk; /* if the queue is above this mark, low-severity messages are discarded */ + int iFullDlyMrk; /* if the queue is above this mark, FULL_DELAYable message are put on hold */ + int iLightDlyMrk; /* if the queue is above this mark, LIGHT_DELAYable message are put on hold */ + int iDiscardSeverity;/* messages of this severity above are discarded on too-full queue */ + int bNeedDelQIF; /* does the QIF file need to be deleted when queue becomes empty? */ + int toQShutdown; /* timeout for regular queue shutdown in ms */ + int toActShutdown; /* timeout for long-running action shutdown in ms */ + int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ + int toEnq; /* enqueue timeout */ + /* rate limiting settings (will be expanded) */ + int iDeqSlowdown; /* slow down dequeue by specified nbr of microseconds */ + /* end rate limiting */ + /* dequeue time window settings (may also be expanded) */ + int iDeqtWinFromHr; /* begin of dequeue time window (hour only) */ + int iDeqtWinToHr; /* end of dequeue time window (hour only), set to 25 to disable deq window! */ + /* note that begin and end have specific semantics. It is a big difference if we have + * begin 4, end 22 or begin 22, end 4. In the later case, dequeuing will run from 10p, + * throughout the night and stop at 4 in the morning. In the first case, it will start + * at 4am, run throughout the day, and stop at 10 in the evening! So far, not logic is + * applied to detect user configuration errors (and tell me how should we detect what + * the user really wanted...). -- rgerhards, 2008-04-02 + */ + /* ane dequeue time window */ + rsRetVal (*pConsumer)(void *,void*); /* user-supplied consumer function for dequeued messages */ + /* calling interface for pConsumer: arg1 is the global user pointer from this structure, arg2 is the + * user pointer that was dequeued (actual sample: for actions, arg1 is the pAction and arg2 is pointer + * to message) + * rgerhards, 2008-01-28 + */ + /* type-specific handlers (set during construction) */ + rsRetVal (*qConstruct)(struct queue_s *pThis); + rsRetVal (*qDestruct)(struct queue_s *pThis); + rsRetVal (*qAdd)(struct queue_s *pThis, void *pUsr); + rsRetVal (*qDel)(struct queue_s *pThis, void **ppUsr); + /* end type-specific handler */ + /* synchronization variables */ + pthread_mutex_t mutThrdMgmt; /* mutex for the queue's thread management */ + pthread_mutex_t *mut; /* mutex for enqueing and dequeueing messages */ + pthread_cond_t notFull, notEmpty; + pthread_cond_t belowFullDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + pthread_cond_t belowLightDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + pthread_cond_t condDAReady;/* signalled when the DA queue is fully initialized and ready for processing */ + int bChildIsDone; /* set to 1 when the child DA queue has finished processing, 0 otherwise */ + int bThrdStateChanged; /* at least one thread state has changed if 1 */ + /* end sync variables */ + /* the following variables are always present, because they + * are not only used for the "disk" queueing mode but also for + * any other queueing mode if it is set to "disk assisted". + * rgerhards, 2008-01-09 + */ + uchar *pszSpoolDir; + size_t lenSpoolDir; + uchar *pszFilePrefix; + size_t lenFilePrefix; + int iNumberFiles; /* how many files make up the queue? */ + int64 iMaxFileSize; /* max size for a single queue file */ + int64 sizeOnDiskMax; /* maximum size on disk allowed */ + int bIsDA; /* is this queue disk assisted? */ + int bRunsDA; /* is this queue actually *running* disk assisted? */ + struct queue_s *pqDA; /* queue for disk-assisted modes */ + struct queue_s *pqParent;/* pointer to the parent (if this is a child queue) */ + int bDAEnqOnly; /* EnqOnly setting for DA queue */ + /* some data elements for the queueUngetObj() functionality. This list should always be short + * and is always kept in memory + */ + qLinkedList_t *pUngetRoot; + qLinkedList_t *pUngetLast; + int iUngottenObjs; /* number of objects currently in the "ungotten" list */ + /* now follow queueing mode specific data elements */ + union { /* different data elements based on queue type (qType) */ + struct { + long head, tail; + void** pBuf; /* the queued user data structure */ + } farray; + struct { + qLinkedList_t *pRoot; + qLinkedList_t *pLast; + } linklist; + struct { + int64 sizeOnDisk; /* current amount of disk space used */ + int64 bytesRead; /* number of bytes read from current (undeleted!) file */ + strm_t *pWrite; /* current file to be written */ + strm_t *pRead; /* current file to be read */ + } disk; + } tVars; +} queue_t; + +/* some symbolic constants for easier reference */ +#define QUEUE_MODE_ENQDEQ 0 +#define QUEUE_MODE_ENQONLY 1 + +#define QUEUE_IDX_DA_WORKER 0 /* index for the DA worker (fixed) */ +#define QUEUE_PTR_DA_WORKER(x) (&((pThis)->pWrkThrds[0])) + +/* the define below is an "eternal" timeout for the timeout settings which require a value. + * It is one day, which is not really eternal, but comes close to it if we think about + * rsyslog (e.g.: do you want to wait on shutdown for more than a day? ;)) + * rgerhards, 2008-01-17 + */ +#define QUEUE_TIMEOUT_ETERNAL 24 * 60 * 60 * 1000 + +/* prototypes */ +rsRetVal queueDestruct(queue_t **ppThis); +rsRetVal queueEnqObj(queue_t *pThis, flowControl_t flwCtlType, void *pUsr); +rsRetVal queueStart(queue_t *pThis); +rsRetVal queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize); +rsRetVal queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)); +PROTOTYPEObjClassInit(queue); +PROTOTYPEpropSetMeth(queue, iPersistUpdCnt, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinFromHr, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinToHr, int); +PROTOTYPEpropSetMeth(queue, toQShutdown, long); +PROTOTYPEpropSetMeth(queue, toActShutdown, long); +PROTOTYPEpropSetMeth(queue, toWrkShutdown, long); +PROTOTYPEpropSetMeth(queue, toEnq, long); +PROTOTYPEpropSetMeth(queue, iHighWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iLowWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardSeverity, int); +PROTOTYPEpropSetMeth(queue, iMinMsgsPerWrkr, int); +PROTOTYPEpropSetMeth(queue, bSaveOnShutdown, int); +PROTOTYPEpropSetMeth(queue, pUsr, void*); +PROTOTYPEpropSetMeth(queue, iDeqSlowdown, int); +PROTOTYPEpropSetMeth(queue, sizeOnDiskMax, int64); +#define queueGetID(pThis) ((unsigned long) pThis) + +#endif /* #ifndef QUEUE_H_INCLUDED */ diff --git a/runtime/regexp.c b/runtime/regexp.c new file mode 100644 index 00000000..86b3e6c4 --- /dev/null +++ b/runtime/regexp.c @@ -0,0 +1,102 @@ +/* The regexp object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <regex.h> +#include <string.h> +#include <assert.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "regexp.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(regexp) +CODESTARTobjQueryInterface(regexp) + if(pIf->ifVersion != regexpCURR_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->regcomp = regcomp; + pIf->regexec = regexec; + pIf->regerror = regerror; + pIf->regfree = regfree; +finalize_it: +ENDobjQueryInterface(regexp) + + +/* Initialize the regexp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(regexp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(regexp) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + CHKiRet(regexpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + /* Initialize all classes that are in our module - this includes ourselfs */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/regexp.h b/runtime/regexp.h new file mode 100644 index 00000000..8f6ac891 --- /dev/null +++ b/runtime/regexp.h @@ -0,0 +1,46 @@ +/* The regexp object. It encapsulates the C regexp functionality. The primary + * purpose of this wrapper class is to enable rsyslogd core to be build without + * regexp libraries. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_REGEXP_H +#define INCLUDED_REGEXP_H + +#include <regex.h> + +/* interfaces */ +BEGINinterface(regexp) /* name must also be changed in ENDinterface macro! */ + int (*regcomp)(regex_t *preg, const char *regex, int cflags); + int (*regexec)(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); + size_t (*regerror)(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); + void (*regfree)(regex_t *preg); +ENDinterface(regexp) +#define regexpCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(regexp); + +/* the name of our library binary */ +#define LM_REGEXP_FILENAME "lmregexp" + +#endif /* #ifndef INCLUDED_REGEXP_H */ diff --git a/runtime/rsyslog.c b/runtime/rsyslog.c new file mode 100644 index 00000000..54db12c2 --- /dev/null +++ b/runtime/rsyslog.c @@ -0,0 +1,237 @@ +/* rsyslog.c - the main entry point into rsyslog's runtime library (RTL) + * + * This module contains all function which work on a RTL global level. It's + * name is abbreviated to "rsrt" (rsyslog runtime). + * + * Please note that the runtime library tends to be plugin-safe. That is, it must be + * initialized by calling a global initialization function. However, that + * function checks if the library is already initialized and, if so, does + * nothing except incrementing a refeence count. Similarly, the deinit + * function does nothing as long as there are still other users (which + * is tracked via the refcount). As such, it is safe to call init and + * exit multiple times, as long as this are always matching calls. This + * capability is needed for a plugin system, where one plugin never + * knows what the other did. HOWEVER, as of this writing, not all runtime + * library objects may work cleanly without static global data (the + * debug system is a very good example of this). So while we aim at the + * ability to work well in a plugin environment, things may not really work + * out. If you intend to use the rsyslog runtime library inside plugins, + * you should investigate the situation in detail. Please note that the + * rsyslog project itself does not yet need this functionality - thus you + * can safely assume it is totally untested ;). + * + * rgerhards, 2008-04-17: I have now once again checked on the plugin-safety. + * Unfortunately, there is currently no hook at all with which we could + * abstract a global data instance class. As such, we can NOT make the + * runtime plugin-safe in the above-described sense. As the rsyslog + * project itself does not need this functionality (and it is quesationable + * if someone else ever will), we do currently do not make an effort to + * support it. So if you intend to use rsyslog runtime inside a non-rsyslog + * plugin system, be careful! + * + * The rsyslog runtime library is in general reentrant and thread-safe. There + * are some intentional exceptions (e.g. inside the msg object). These are + * documented. Any other threading and reentrency issue can be considered a bug. + * + * Module begun 2008-04-16 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vm.h" +#include "sysvar.h" +#include "stringbuf.h" +#include "wti.h" +#include "wtp.h" +#include "expr.h" +#include "ctok.h" +#include "vmop.h" +#include "vmstk.h" +#include "vmprg.h" +#include "datetime.h" +#include "queue.h" +#include "conf.h" +#include "glbl.h" +#include "errmsg.h" + +/* forward definitions */ +static rsRetVal dfltErrLogger(int, uchar *errMsg); + +/* globally visible static data - see comment in rsyslog.h for details */ +uchar *glblModPath; /* module load path */ +rsRetVal (*glblErrLogger)(int, uchar*) = dfltErrLogger; /* the error logger to use by the errmsg module */ + +/* static data */ +static int iRefCount = 0; /* our refcount - it MUST exist only once inside a process (not thread) + thus it is perfectly OK to use a static. MUST be initialized to 0! */ + +/* This is the default instance of the error logger. It simply writes the message + * to stderr. It is expected that this is replaced by the runtime user very early + * during startup (at least if the default is unsuitable). However, we provide a + * default so that we can log errors during the intial phase, most importantly + * during initialization. -- rgerhards. 2008-04-17 + */ +static rsRetVal dfltErrLogger(int iErr, uchar *errMsg) +{ + DEFiRet; + fprintf(stderr, "rsyslog runtime error(%d): %s\n", iErr, errMsg); + RETiRet; +} + + +/* set the error log function + * rgerhards, 2008-04-18 + */ +rsRetVal +rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*)) +{ + DEFiRet; + assert(errLogger != NULL); + glblErrLogger = errLogger; + RETiRet; +} + + +/* globally initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * If ppErrObj is provided, it receives a char pointer to the name of the object that + * caused the problem (if one occured). The caller must never free this pointer. If + * ppErrObj is NULL, no such information will be provided. pObjIF is the pointer to + * the "obj" object interface, which may be used to query any other rsyslog objects. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtInit(char **ppErrObj, obj_if_t *pObjIF) +{ + DEFiRet; + + if(iRefCount == 0) { + /* init runtime only if not yet done */ + if(ppErrObj != NULL) *ppErrObj = "obj"; + CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */ + CHKiRet(objGetObjInterface(pObjIF)); /* this provides the root pointer for all other queries */ + + /* initialize core classes. We must be very careful with the order of events. Some + * classes use others and if we do not initialize them in the right order, we may end + * up with an invalid call. The most important thing that can happen is that an error + * is detected and needs to be logged, wich in turn requires a broader number of classes + * to be available. The solution is that we take care in the order of calls AND use a + * class immediately after it is initialized. And, of course, we load those classes + * first that we use ourselfs... -- rgerhards, 2008-03-07 + */ + if(ppErrObj != NULL) *ppErrObj = "glbl"; + CHKiRet(glblClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "datetime"; + CHKiRet(datetimeClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "msg"; + CHKiRet(msgClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "str,"; + CHKiRet(strmClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wti"; + CHKiRet(wtiClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wtp"; + CHKiRet(wtpClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "queue"; + CHKiRet(queueClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmstk"; + CHKiRet(vmstkClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "sysvar"; + CHKiRet(sysvarClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vm"; + CHKiRet(vmClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmop"; + CHKiRet(vmopClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmprg"; + CHKiRet(vmprgClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "ctok_token"; + CHKiRet(ctok_tokenClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "ctok"; + CHKiRet(ctokClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "expr"; + CHKiRet(exprClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "conf"; + CHKiRet(confClassInit(NULL)); + + /* dummy "classes" */ + if(ppErrObj != NULL) *ppErrObj = "str"; + CHKiRet(strInit()); + } + + ++iRefCount; + dbgprintf("rsyslog runtime initialized, version %s, current users %d\n", VERSION, iRefCount); + +finalize_it: + RETiRet; +} + + +/* globally de-initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * This function must be provided with the caller's obj object pointer. This is + * automatically deinitialized by the runtime system. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtExit(void) +{ + DEFiRet; + + if(iRefCount == 1) { + /* do actual de-init only if we are the last runtime user */ + confClassExit(); + glblClassExit(); + objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + } + + --iRefCount; + /* TODO we must deinit this pointer! pObjIF = NULL; / * no longer exists for this caller */ + + dbgprintf("rsyslog runtime de-initialized, current users %d\n", iRefCount); + + RETiRet; +} + + +/* returns 0 if the rsyslog runtime is not initialized and another value + * if it is. This function is primarily meant to be used by runtime functions + * itself. However, it is safe to call it before initializing the runtime. + * Plugins should NOT rely on this function. The reason is that another caller + * may have already initialized it but deinits it before this plugin is done. + * So for plugins and like architectures, the right course of action is to + * call rsrtInit() and rsrtExit(), which can be called by multiple callers. + * rgerhards, 2008-04-16 + */ +int rsrtIsInit(void) +{ + return iRefCount; +} + + +/* vim:set ai: + */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h new file mode 100644 index 00000000..8843df85 --- /dev/null +++ b/runtime/rsyslog.h @@ -0,0 +1,360 @@ +/* This is the header file for the rsyslog runtime. It must be included + * if someone intends to use the runtime. + * + * Begun 2005-09-15 RGerhards + * + * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_RSYSLOG_H +#define INCLUDED_RSYSLOG_H + +/* ############################################################# * + * # Config Settings # * + * ############################################################# */ +#define RS_STRINGBUF_ALLOC_INCREMENT 128 + +/* ############################################################# * + * # End Config Settings # * + * ############################################################# */ + +#ifndef NOLARGEFILE +# undef _LARGEFILE_SOURCE +# undef _LARGEFILE64_SOURCE +# undef _FILE_OFFSET_BITS +# define _LARGEFILE_SOURCE +# define _LARGEFILE64_SOURCE +# define _FILE_OFFSET_BITS 64 +#endif + +/* portability: not all platforms have these defines, so we + * define them here if they are missing. -- rgerhards, 2008-03-04 + */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#ifndef LOG_PRI +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +#endif +#ifndef LOG_FAC +# define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) +#endif + + +/* define some base data types */ +typedef unsigned char uchar;/* get rid of the unhandy "unsigned char" */ +typedef struct thrdInfo thrdInfo_t; +typedef struct obj_s obj_t; +typedef struct filed selector_t;/* TODO: this so far resides in syslogd.c, think about modularization */ +typedef struct NetAddr netAddr_t; +typedef struct netstrms_s netstrms_t; +typedef struct netstrm_s netstrm_t; +typedef struct nssel_s nssel_t; +typedef enum nsdsel_waitOp_e nsdsel_waitOp_t; +typedef struct nsd_ptcp_s nsd_ptcp_t; +typedef struct nsd_gtls_s nsd_gtls_t; +typedef struct nsd_gsspi_s nsd_gsspi_t; +typedef struct nsd_nss_s nsd_nss_t; +typedef struct nsdsel_ptcp_s nsdsel_ptcp_t; +typedef struct nsdsel_gtls_s nsdsel_gtls_t; +typedef obj_t nsd_t; +typedef obj_t nsdsel_t; +typedef struct msg msg_t; +typedef struct interface_s interface_t; +typedef struct objInfo_s objInfo_t; +typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ +typedef rsRetVal (*errLogFunc_t)(uchar*); /* this is a trick to store a function ptr to a function returning a function ptr... */ +typedef struct permittedPeers_s permittedPeers_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct permittedPeerWildcard_s permittedPeerWildcard_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct tcpsrv_s tcpsrv_t; + +/* some universal 64 bit define... */ +typedef long long int64; +typedef long long unsigned uint64; +typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ + +#ifdef __hpux +typedef unsigned int u_int32_t; /* TODO: is this correct? */ +typedef int socklen_t; +#endif + +/* settings for flow control + * TODO: is there a better place for them? -- rgerhards, 2008-03-14 + */ +typedef enum { + eFLOWCTL_NO_DELAY = 0, /**< UDP and other non-delayable sources */ + eFLOWCTL_LIGHT_DELAY = 1, /**< some light delay possible, but no extended period of time */ + eFLOWCTL_FULL_DELAY = 2 /**< delay possible for extended period of time */ +} flowControl_t; + + +/* The error codes below are orginally "borrowed" from + * liblogging. As such, we reserve values up to -2999 + * just in case we need to borrow something more ;) +*/ +enum rsRetVal_ /** return value. All methods return this if not specified otherwise */ +{ + /* the first two define are for errmsg.logError(), so that we can use the rsRetVal + * as an rsyslog error code. -- rgerhards, 20080-06-27 + */ + RS_RET_NO_ERRCODE = -1, /**< RESERVED for NO_ERRCODE errmsg.logError status name */ + RS_RET_INCLUDE_ERRNO = 1073741824, /* 2**30 - do NOT use error codes above this! */ + /* begin regular error codes */ + RS_RET_NOT_IMPLEMENTED = -7, /**< implementation is missing (probably internal error or lazyness ;)) */ + RS_RET_OUT_OF_MEMORY = -6, /**< memory allocation failed */ + RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,/**< the caller provided a buffer, but the called function sees the size of this buffer is too small - operation not carried out */ + RS_RET_TRUE = -3, /**< to indicate a true state (can be used as TRUE, legacy) */ + RS_RET_FALSE = -2, /**< to indicate a false state (can be used as FALSE, legacy) */ + RS_RET_NO_IRET = -8, /**< This is a trick for the debuging system - it means no iRet is provided */ + RS_RET_ERR = -3000, /**< generic failure */ + RS_TRUNCAT_TOO_LARGE = -3001, /**< truncation operation where too many chars should be truncated */ + RS_RET_FOUND_AT_STRING_END = -3002, /**< some value found, but at the last pos of string */ + RS_RET_NOT_FOUND = -3003, /**< some requested value not found */ + RS_RET_MISSING_TRAIL_QUOTE = -3004, /**< an expected trailing quote is missing */ + RS_RET_NO_DIGIT = -3005, /**< an digit was expected, but none found (mostly parsing) */ + RS_RET_NO_MORE_DATA = -3006, /**< insufficient data, e.g. end of string during parsing */ + RS_RET_INVALID_IP = -3007, /**< invalid ip found where valid was expected */ + RS_RET_OBJ_CREATION_FAILED = - 3008, /**< the creation of an object failed (no details available) */ + RS_RET_PARAM_ERROR = -1000, /**< invalid parameter in call to function */ + RS_RET_MISSING_INTERFACE = -1001,/**< interface version mismatch, required missing */ + RS_RET_INVALID_CORE_INTERFACE = -1002,/**< interface provided by host invalid, can not be used */ + RS_RET_ENTRY_POINT_NOT_FOUND = -1003,/**< a requested entry point was not found */ + RS_RET_MODULE_ENTRY_POINT_NOT_FOUND = -1004,/**< a entry point requested from a module was not present in it */ + RS_RET_OBJ_NOT_AVAILABLE = -1005,/**< something could not be completed because the required object is not available*/ + RS_RET_LOAD_ERROR = -1006,/**< we had an error loading the object/interface and can not continue */ + RS_RET_MODULE_STILL_REFERENCED = -1007,/**< module could not be unloaded because it still is referenced by someone */ + RS_RET_OBJ_UNKNOWN = -1008,/**< object is unknown where required */ + RS_RET_OBJ_NOT_REGISTERED = -1009,/**< tried to unregister an object that is not registered */ + /* return states for config file processing */ + RS_RET_NONE = -2000, /**< some value is not available - not necessarily an error */ + RS_RET_CONFLINE_UNPROCESSED = -2001,/**< config line was not processed, pass to other module */ + RS_RET_DISCARDMSG = -2002, /**< discard message (no error state, processing request!) */ + RS_RET_INCOMPATIBLE = -2003, /**< function not compatible with requested feature */ + RS_RET_NOENTRY = -2004, /**< do not create an entry for (whatever) - not necessary an error */ + RS_RET_NO_SQL_STRING = -2005, /**< string is not suitable for use as SQL */ + RS_RET_DISABLE_ACTION = -2006, /**< action requests that it be disabled */ + RS_RET_SUSPENDED = -2007, /**< something was suspended, not neccesarily an error */ + RS_RET_RQD_TPLOPT_MISSING = -2008,/**< a required template option is missing */ + RS_RET_INVALID_VALUE = -2009,/**< some value is invalid (e.g. user-supplied data) */ + RS_RET_INVALID_INT = -2010,/**< invalid integer */ + RS_RET_INVALID_CMD = -2011,/**< invalid command */ + RS_RET_VAL_OUT_OF_RANGE = -2012, /**< value out of range */ + RS_RET_FOPEN_FAILURE = -2013, /**< failure during fopen, for example file not found - see errno */ + RS_RET_END_OF_LINKEDLIST = -2014, /**< end of linked list, not an error, but a status */ + RS_RET_CHAIN_NOT_PERMITTED = -2015, /**< chaining (e.g. of config command handlers) not permitted */ + RS_RET_INVALID_PARAMS = -2016,/**< supplied parameters are invalid */ + RS_RET_EMPTY_LIST = -2017, /**< linked list is empty */ + RS_RET_FINISHED = -2018, /**< some opertion is finished, not an error state */ + RS_RET_INVALID_SOURCE = -2019, /**< source (address) invalid for some reason */ + RS_RET_ADDRESS_UNKNOWN = -2020, /**< an address is unknown - not necessarily an error */ + RS_RET_MALICIOUS_ENTITY = -2021, /**< there is an malicious entity involved */ + RS_RET_NO_KERNEL_LOGSRC = -2022, /**< no source for kernel logs can be obtained */ + RS_RET_TCP_SEND_ERROR = -2023, /**< error during TCP send process */ + RS_RET_GSS_SEND_ERROR = -2024, /**< error during GSS (via TCP) send process */ + RS_RET_TCP_SOCKCREATE_ERR = -2025, /**< error during creation of TCP socket */ + RS_RET_GSS_SENDINIT_ERROR = -2024, /**< error during GSS (via TCP) send initialization process */ + RS_RET_EOF = -2026, /**< end of file reached, not necessarily an error */ + RS_RET_IO_ERROR = -2027, /**< some kind of IO error happened */ + RS_RET_INVALID_OID = -2028, /**< invalid object ID */ + RS_RET_INVALID_HEADER = -2029, /**< invalid header */ + RS_RET_INVALID_HEADER_VERS = -2030, /**< invalid header version */ + RS_RET_INVALID_DELIMITER = -2031, /**< invalid delimiter, e.g. between params */ + RS_RET_INVALID_PROPFRAME = -2032, /**< invalid framing in serialized property */ + RS_RET_NO_PROPLINE = -2033, /**< line is not a property line */ + RS_RET_INVALID_TRAILER = -2034, /**< invalid trailer */ + RS_RET_VALUE_TOO_LOW = -2035, /**< a provided value is too low */ + RS_RET_FILE_PREFIX_MISSING = -2036, /**< a required file prefix (parameter?) is missing */ + RS_RET_INVALID_HEADER_RECTYPE = -2037, /**< invalid record type in header or invalid header */ + RS_RET_QTYPE_MISMATCH = -2038, /**< different qType when reading back a property type */ + RS_RET_NO_FILE_ACCESS = -2039, /**< covers EACCES error on file open() */ + RS_RET_FILE_NOT_FOUND = -2040, /**< file not found */ + RS_RET_TIMED_OUT = -2041, /**< timeout occured (not necessarily an error) */ + RS_RET_QSIZE_ZERO = -2042, /**< queue size is zero where this is not supported */ + RS_RET_ALREADY_STARTING = -2043, /**< something (a thread?) is already starting - not necessarily an error */ + RS_RET_NO_MORE_THREADS = -2044, /**< no more threads available, not necessarily an error */ + RS_RET_NO_FILEPREFIX = -2045, /**< file prefix is not specified where one is needed */ + RS_RET_CONFIG_ERROR = -2046, /**< there is a problem with the user-provided config settigs */ + RS_RET_OUT_OF_DESRIPTORS = -2047, /**< a descriptor table's space has been exhausted */ + RS_RET_NO_DRIVERS = -2048, /**< a required drivers missing */ + RS_RET_NO_DRIVERNAME = -2049, /**< driver name missing where one was required */ + RS_RET_EOS = -2050, /**< end of stream (of whatever) */ + RS_RET_SYNTAX_ERROR = -2051, /**< syntax error, eg. during parsing */ + RS_RET_INVALID_OCTAL_DIGIT = -2052, /**< invalid octal digit during parsing */ + RS_RET_INVALID_HEX_DIGIT = -2053, /**< invalid hex digit during parsing */ + RS_RET_INTERFACE_NOT_SUPPORTED = -2054, /**< interface not supported */ + RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */ + RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */ + RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */ + RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ + RS_RET_INVALID_NUMBER = -2059, /**< number invalid during parsing */ + RS_RET_NOT_A_NUMBER = -2060, /**< e.g. conversion impossible because the string is not a number */ + RS_RET_OBJ_ALREADY_REGISTERED = -2061, /**< object (name) is already registered */ + RS_RET_OBJ_REGISTRY_OUT_OF_SPACE = -2062, /**< the object registry has run out of space */ + RS_RET_HOST_NOT_PERMITTED = -2063, /**< a host is not permitted to perform an action it requested */ + RS_RET_MODULE_LOAD_ERR = -2064, /**< module could not be loaded */ + RS_RET_MODULE_LOAD_ERR_PATHLEN = -2065, /**< module could not be loaded - path to long */ + RS_RET_MODULE_LOAD_ERR_DLOPEN = -2066, /**< module could not be loaded - problem in dlopen() */ + RS_RET_MODULE_LOAD_ERR_NO_INIT = -2067, /**< module could not be loaded - init() missing */ + RS_RET_MODULE_LOAD_ERR_INIT_FAILED = -2068, /**< module could not be loaded - init() failed */ + RS_RET_NO_SOCKET = -2069, /**< socket could not be obtained or was not provided */ + RS_RET_SMTP_ERROR = -2070, /**< error during SMTP transation */ + RS_RET_MAIL_NO_TO = -2071, /**< recipient for mail destination is missing */ + RS_RET_MAIL_NO_FROM = -2072, /**< sender for mail destination is missing */ + RS_RET_INVALID_PRI = -2073, /**< PRI value is invalid */ + RS_RET_MALICIOUS_HNAME = -2074, /**< remote peer is trying malicious things with its hostname */ + RS_RET_ACCEPT_ERR = -2074, /**< error during accept() system call */ + RS_RET_INVALID_HNAME = -2075, /**< remote peer's hostname invalid or unobtainable */ + RS_RET_INVALID_PORT = -2076, /**< invalid port value */ + RS_RET_COULD_NOT_BIND = -2077, /**< could not bind socket, defunct */ + RS_RET_GNUTLS_ERR = -2078, /**< (unexpected) error in GnuTLS call */ + RS_RET_MAX_SESS_REACHED = -2079, /**< max nbr of sessions reached, can not create more */ + RS_RET_MAX_LSTN_REACHED = -2080, /**< max nbr of listeners reached, can not create more */ + RS_RET_INVALID_DRVR_MODE = -2081, /**< tried to set mode not supported by driver */ + RS_RET_DRVRNAME_TOO_LONG = -2082, /**< driver name too long - should never happen */ + RS_RET_TLS_HANDSHAKE_ERR = -2083, /**< TLS handshake failed */ + RS_RET_TLS_CERT_ERR = -2084, /**< generic TLS certificate error */ + RS_RET_TLS_NO_CERT = -2085, /**< no TLS certificate available where one was expected */ + RS_RET_VALUE_NOT_SUPPORTED = -2086, /**< a provided value is not supported */ + RS_RET_VALUE_NOT_IN_THIS_MODE = -2087, /**< a provided value is invalid for the curret mode */ + RS_RET_INVALID_FINGERPRINT = -2088, /**< a fingerprint is not valid for this use case */ + RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */ + RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */ + RS_RET_CERT_INVALID_DN = -2091, /**< distinguised name in x509 certificate is invalid (e.g. wrong escaping) */ + RS_RET_CERT_EXPIRED = -2092, /**< we are past a x.509 cert's expiration time */ + RS_RET_CERT_NOT_YET_ACTIVE = -2094, /**< x.509 cert's activation time not yet reached */ + RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ + RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */ + RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */ + RS_RET_INVALID_WILDCARD = -2098, /**< a wildcard entry is invalid */ + RS_RET_CLOSED = -2099, /**< connection was closed */ + RS_RET_RETRY = -2100, /**< call should be retried (e.g. EGAIN on recv) */ + RS_RET_GSS_ERR = -2101, /**< generic error occured in GSSAPI subsystem */ + RS_RET_CERTLESS = -2102, /**< state: we run without machine cert (this may be OK) */ + RS_RET_QUEUE_FULL = -2103, /**< queue is full, operation could not be completed */ + + /* RainerScript error messages (range 1000.. 1999) */ + RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ + + /* some generic error/status codes */ + RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */ + RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */ + RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */ + RS_RET_OK = 0 /**< operation successful */ +}; + +/* some helpful macros to work with srRetVals. + * Be sure to call the to-be-returned variable always "iRet" and + * the function finalizer always "finalize_it". + */ +#define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it +/* macro below is to be used if we need our own handling, eg for cleanup */ +#define CHKiRet_Hdlr(code) if((iRet = code) != RS_RET_OK) +/* macro below is to handle failing malloc/calloc/strdup... which we almost always handle in the same way... */ +#define CHKmalloc(operation) if((operation) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY) +/* macro below is used in conjunction with CHKiRet_Hdlr, else use ABORT_FINALIZE */ +#define FINALIZE goto finalize_it; +#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK +#define RETiRet do{ ENDfuncIRet return iRet; }while(0) + +#define ABORT_FINALIZE(errCode) \ + do { \ + iRet = errCode; \ + goto finalize_it; \ + } while (0) + +/** Object ID. These are for internal checking. Each + * object is assigned a specific ID. This is contained in + * all Object structs (just like C++ RTTI). We can use + * this field to see if we have been passed a correct ID. + * Other than that, there is currently no other use for + * the object id. + */ +enum rsObjectID +{ + OIDrsFreed = -1, /**< assigned, when an object is freed. If this + * is seen during a method call, this is an + * invalid object pointer! + */ + OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */ + /* The 0x3412 is a debug aid. It helps us find object IDs in memory + * dumps (on X86, this is 1234 in the dump ;) + * If you are on an embedded device and you would like to save space + * make them 1 byte only. + */ + OIDrsCStr = 0x34120001, + OIDrsPars = 0x34120002 +}; +typedef enum rsObjectID rsObjID; + +/* support to set object types */ +#ifdef NDEBUG +#define rsSETOBJTYPE(pObj, type) +#define rsCHECKVALIDOBJECT(x, type) +#else +#define rsSETOBJTYPE(pObj, type) pObj->OID = type; +#define rsCHECKVALIDOBJECT(x, type) {assert(x != NULL); assert(x->OID == type);} +#endif + +/** + * This macro should be used to free objects. + * It aids in interpreting dumps during debugging. + */ +#ifdef NDEBUG +#define RSFREEOBJ(x) free(x) +#else +#define RSFREEOBJ(x) {(x)->OID = OIDrsFreed; free(x);} +#endif + + +/* for the time being, we do our own portability handling here. It + * looks like autotools either does not yet support checks for it, or + * I wasn't smart enough to find them ;) rgerhards, 2007-07-18 + */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +/* The following prototype is convenient, even though it may not be the 100% correct place.. -- rgerhards 2008-01-07 */ +void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2))); + +#include "debug.h" +#include "obj.h" + +/* the variable below is a trick: before we can init the runtime, the caller + * may want to set a module load path. We can not do this via the glbl class + * because it needs an initialized runtime system (and may at some point in time + * even be loaded itself). So this is a no-go. What we do is use a single global + * variable which may be provided with a pointer by the caller. This variable + * resides in rsyslog.c, the main runtime file. We have not seen any realy valule + * in providing object access functions. If you don't like that, feel free to + * add them. -- rgerhards, 2008-04-17 + */ +extern uchar *glblModPath; /* module load path */ +extern rsRetVal (*glblErrLogger)(int, uchar*); + +/* some runtime prototypes */ +rsRetVal rsrtInit(char **ppErrObj, obj_if_t *pObjIF); +rsRetVal rsrtExit(void); +int rsrtIsInit(void); +rsRetVal rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*)); + +#endif /* multi-include protection */ +/* vim:set ai: + */ diff --git a/runtime/srUtils.h b/runtime/srUtils.h new file mode 100644 index 00000000..bfce4cbb --- /dev/null +++ b/runtime/srUtils.h @@ -0,0 +1,127 @@ +/*! \file srUtils.h + * \brief General, small utilities that fit nowhere else. + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef __SRUTILS_H_INCLUDED__ +#define __SRUTILS_H_INCLUDED__ 1 + + +/* syslog names */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ +#define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */ +#define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ +#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ + +typedef struct syslogName_s { + char *c_name; + int c_val; +} syslogName_t; + +extern syslogName_t syslogPriNames[]; +extern syslogName_t syslogFacNames[]; + +/** + * A reimplementation of itoa(), as this is not available + * on all platforms. We used the chance to make an interface + * that fits us well, so it is no longer plain itoa(). + * + * This method works with the US-ASCII alphabet. If you port this + * to e.g. EBCDIC, you need to make a small adjustment. Keep in mind, + * that on the wire it MUST be US-ASCII, so basically all you need + * to do is replace the constant '0' with 0x30 ;). + * + * \param pBuf Caller-provided buffer that will receive the + * generated ASCII string. + * + * \param iLenBuf Length of the caller-provided buffer. + * + * \param iToConv The integer to be converted. + */ +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv); + +/** + * A method to duplicate a string for which the length is known. + * Len must be the length in characters WITHOUT the trailing + * '\0' byte. + * rgerhards, 2007-07-10 + */ +unsigned char *srUtilStrDup(unsigned char *pOld, size_t len); +/** + * A method to create a directory and all its missing parents for + * a given file name. Please not that the rightmost element is + * considered to be a file name and thus NO directory is being created + * for it. + * added 2007-07-17 by rgerhards + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, uid_t uid, gid_t gid, int bFailOnChown); +int execProg(uchar *program, int bWait, uchar *arg); +void skipWhiteSpace(uchar **pp); +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits); +int getNumberDigits(long lNum); +rsRetVal timeoutComp(struct timespec *pt, long iTimeout); +long timeoutVal(struct timespec *pt); +void mutexCancelCleanup(void *arg); +void srSleep(int iSeconds, int iuSeconds); +char *rs_strerror_r(int errnum, char *buf, size_t buflen); +int decodeSyslogName(uchar *name, syslogName_t *codetab); +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); + +/* mutex operations */ +/* some macros to cancel-safe lock a mutex (it will automatically be released + * when the thread is cancelled. This needs to be done as macros because + * pthread_cleanup_push sometimes is a macro that can not be used inside a function. + * It's a bit ugly, but works well... rgerhards, 2008-01-20 + */ +#define DEFVARS_mutex_cancelsafeLock int iCancelStateSave +#define mutex_cancelsafe_lock(mut) \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + pthread_cleanup_push(mutexCancelCleanup, mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); +#define mutex_cancelsafe_unlock(mut) pthread_cleanup_pop(1) + +/* some useful constants */ +#define MUTEX_ALREADY_LOCKED 0 +#define LOCK_MUTEX 1 +#define DEFVARS_mutexProtection\ + int iCancelStateSave; \ + int bLockedOpIsLocked=0 +#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \ + if(bMustLock == LOCK_MUTEX) { \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + bLockedOpIsLocked = 1; \ + } +#define END_MTX_PROTECTED_OPERATIONS(mut) \ + if(bLockedOpIsLocked) { \ + d_pthread_mutex_unlock(mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); \ + } +#endif diff --git a/runtime/srutils.c b/runtime/srutils.c new file mode 100644 index 00000000..97cc3252 --- /dev/null +++ b/runtime/srutils.c @@ -0,0 +1,554 @@ +/**\file srUtils.c + * \brief General utilties that fit nowhere else. + * + * The namespace for this file is "srUtil". + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <signal.h> +#include <assert.h> +#include <sys/wait.h> +#include <ctype.h> +#define TRUE 1 +#define FALSE 0 +#include "srUtils.h" +#include "obj.h" + + +/* here we host some syslog specific names. There currently is no better place + * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14 + * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in + * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL. + */ +syslogName_t syslogPriNames[] = { + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"debug", LOG_DEBUG}, + {"emerg", LOG_EMERG}, + {"err", LOG_ERR}, + {"error", LOG_ERR}, /* DEPRECATED */ + {"info", LOG_INFO}, + {"none", INTERNAL_NOPRI}, /* INTERNAL */ + {"notice", LOG_NOTICE}, + {"panic", LOG_EMERG}, /* DEPRECATED */ + {"warn", LOG_WARNING}, /* DEPRECATED */ + {"warning", LOG_WARNING}, + {"*", TABLE_ALLPRI}, + {NULL, -1} +}; + +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif +syslogName_t syslogFacNames[] = { + {"auth", LOG_AUTH}, + {"authpriv", LOG_AUTHPRIV}, + {"cron", LOG_CRON}, + {"daemon", LOG_DAEMON}, + {"kern", LOG_KERN}, + {"lpr", LOG_LPR}, + {"mail", LOG_MAIL}, + {"mark", LOG_MARK}, /* INTERNAL */ + {"news", LOG_NEWS}, + {"security", LOG_AUTH}, /* DEPRECATED */ + {"syslog", LOG_SYSLOG}, + {"user", LOG_USER}, + {"uucp", LOG_UUCP}, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif + {"local0", LOG_LOCAL0}, + {"local1", LOG_LOCAL1}, + {"local2", LOG_LOCAL2}, + {"local3", LOG_LOCAL3}, + {"local4", LOG_LOCAL4}, + {"local5", LOG_LOCAL5}, + {"local6", LOG_LOCAL6}, + {"local7", LOG_LOCAL7}, + {NULL, -1}, +}; + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* As this is not a "real" object, there won't be any private + * members in this file. + */ + +/* ################################################################# * + * public members * + * ################################################################# */ + +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) +{ + int i; + int bIsNegative; + char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */ + + assert(pBuf != NULL); + assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */ + + if(iToConv < 0) + { + bIsNegative = TRUE; + iToConv *= -1; + } + else + bIsNegative = FALSE; + + /* first generate a string with the digits in the reverse direction */ + i = 0; + do + { + szBuf[i++] = iToConv % 10 + '0'; + iToConv /= 10; + } while(iToConv > 0); /* warning: do...while()! */ + --i; /* undo last increment - we were pointing at NEXT location */ + + /* make sure we are within bounds... */ + if(i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */ + return RS_RET_PROVIDED_BUFFER_TOO_SMALL; + + /* then move it to the right direction... */ + if(bIsNegative == TRUE) + *pBuf++ = '-'; + while(i >= 0) + *pBuf++ = szBuf[i--]; + *pBuf = '\0'; /* terminate it!!! */ + + return RS_RET_OK; +} + +uchar *srUtilStrDup(uchar *pOld, size_t len) +{ + uchar *pNew; + + assert(pOld != NULL); + + if((pNew = malloc(len + 1)) != NULL) + memcpy(pNew, pOld, len + 1); + + return pNew; +} + + +/* creates a path recursively + * Return 0 on success, -1 otherwise. On failure, errno + * hold the last OS error. + * Param "mode" holds the mode that all non-existing directories + * are to be created with. + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, + uid_t uid, gid_t gid, int bFailOnChownFail) +{ + uchar *p; + uchar *pszWork; + size_t len; + int bErr = 0; + + assert(szFile != NULL); + assert(lenFile > 0); + + len = lenFile + 1; /* add one for '\0'-byte */ + if((pszWork = malloc(sizeof(uchar) * len)) == NULL) + return -1; + memcpy(pszWork, szFile, len); + for(p = pszWork+1 ; *p ; p++) + if(*p == '/') { + /* temporarily terminate string, create dir and go on */ + *p = '\0'; + if(access((char*)pszWork, F_OK)) { + if(mkdir((char*)pszWork, mode) == 0) { + if(uid != (uid_t) -1 || gid != (gid_t) -1) { + /* we need to set owner/group */ + if(chown((char*)pszWork, uid, gid) != 0) + if(bFailOnChownFail) + bErr = 1; + /* silently ignore if configured + * to do so. + */ + } + } else + bErr = 1; + if(bErr) { + int eSave = errno; + free(pszWork); + errno = eSave; + return -1; + } + } + *p = '/'; + } + free(pszWork); + return 0; +} + + +/* execute a program with a single argument + * returns child pid if everything ok, 0 on failure. if + * it fails, errno is set. if it fails after the fork(), the caller + * can not be notfied for obvious reasons. if bwait is set to 1, + * the code waits until the child terminates - that potentially takes + * a lot of time. + * implemented 2007-07-20 rgerhards + */ +int execProg(uchar *program, int bWait, uchar *arg) +{ + int pid; + int sig; + struct sigaction sigAct; + + dbgprintf("exec program '%s' with param '%s'\n", program, arg); + pid = fork(); + if (pid < 0) { + return 0; + } + + if(pid) { /* Parent */ + if(bWait) + if(waitpid(pid, NULL, 0) == -1) + if(errno != ECHILD) { + /* we do not use logerror(), because + * that might bring us into an endless + * loop. At some time, we may + * reconsider this behaviour. + */ + dbgprintf("could not wait on child after executing '%s'", + (char*)program); + } + return pid; + } + /* Child */ + alarm(0); /* create a clean environment before we exec the real child */ + + memset(&sigAct, 0, sizeof(sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + + for(sig = 1 ; sig < NSIG ; ++sig) + sigaction(sig, &sigAct, NULL); + + execlp((char*)program, (char*) program, (char*)arg, NULL); + /* In the long term, it's a good idea to implement some enhanced error + * checking here. However, it can not easily be done. For starters, we + * may run into endless loops if we log to syslog. The next problem is + * that output is typically not seen by the user. For the time being, + * we use no error reporting, which is quite consitent with the old + * system() way of doing things. rgerhards, 2007-07-20 + */ + perror("exec"); + exit(1); /* not much we can do in this case */ +} + + +/* skip over whitespace in a standard C string. The + * provided pointer is advanced to the first non-whitespace + * charater or the \0 byte, if there is none. It is never + * moved past the \0. + */ +void skipWhiteSpace(uchar **pp) +{ + register uchar *p; + + assert(pp != NULL); + assert(*pp != NULL); + + p = *pp; + while(*p && isspace((int) *p)) + ++p; + *pp = p; +} + + +/* generate a file name from four parts: + * <directory name>/<name>.<number> + * If number is negative, it is not used. If any of the strings is + * NULL, an empty string is used instead. Length must be provided. + * lNumDigits is the minimum number of digits that lNum should have. This + * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will + * result in "0003" being used inside the file name. Set lNumDigits to 0 + * to use as few space as possible. + * rgerhards, 2008-01-03 + */ +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits) +{ + DEFiRet; + uchar *pName; + uchar *pNameWork; + size_t lenName; + uchar szBuf[128]; /* buffer for number */ + char szFmtBuf[32]; /* buffer for snprintf format */ + size_t lenBuf; + + if(lNum < 0) { + szBuf[0] = '\0'; + lenBuf = 0; + } else { + if(lNumDigits > 0) { + snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits); + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum); + } else + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum); + } + + lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */ + if((pName = malloc(sizeof(uchar) * lenName)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* got memory, now construct string */ + memcpy(pName, pDirName, lenDirName); + pNameWork = pName + lenDirName; + *pNameWork++ = '/'; + memcpy(pNameWork, pFName, lenFName); + pNameWork += lenFName; + if(lenBuf > 0) { + memcpy(pNameWork, szBuf, lenBuf); + pNameWork += lenBuf; + } + *pNameWork = '\0'; + + *ppName = pName; + +finalize_it: + RETiRet; +} + +/* get the number of digits required to represent a given number. We use an + * iterative approach as we do not like to draw in the floating point + * library just for log(). -- rgerhards, 2008-01-10 + */ +int getNumberDigits(long lNum) +{ + int iDig; + + if(lNum == 0) + iDig = 1; + else + for(iDig = 0 ; lNum != 0 ; ++iDig) + lNum /= 10; + + return iDig; +} + + +/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait() + * rgerhards, 2008-01-14 + */ +rsRetVal +timeoutComp(struct timespec *pt, long iTimeout) +{ + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, pt); + pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ + if(pt->tv_nsec > 999999999) { /* overrun? */ + pt->tv_nsec -= 1000000000; + } + pt->tv_sec += iTimeout / 1000; + return RS_RET_OK; /* so far, this is static... */ +} + + +/* This function is kind of the reverse of timeoutComp() - it takes an absolute + * timeout value and computes how far this is in the future. If the value is already + * in the past, 0 is returned. The return value is in ms. + * rgerhards, 2008-01-25 + */ +long +timeoutVal(struct timespec *pt) +{ + struct timespec t; + long iTimeout; + + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, &t); + iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; + iTimeout += (pt->tv_sec - t.tv_sec) * 1000; + + if(iTimeout < 0) + iTimeout = 0; + + return iTimeout; +} + + +/* cancellation cleanup handler - frees provided mutex + * rgerhards, 2008-01-14 + */ +void +mutexCancelCleanup(void *arg) +{ + BEGINfunc + assert(arg != NULL); + d_pthread_mutex_unlock((pthread_mutex_t*) arg); + ENDfunc +} + + +/* rsSleep() - a fairly portable way to to sleep. It + * will wake up when + * a) the wake-time is over + * rgerhards, 2008-01-28 + */ +void +srSleep(int iSeconds, int iuSeconds) +{ + struct timeval tvSelectTimeout; + + BEGINfunc + tvSelectTimeout.tv_sec = iSeconds; + tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ + select(0, NULL, NULL, NULL, &tvSelectTimeout); + ENDfunc +} + + +/* From varmojfekoj's mail on why he provided rs_strerror_r(): + * There are two problems with strerror_r(): + * I see you've rewritten some of the code which calls it to use only + * the supplied buffer; unfortunately the GNU implementation sometimes + * doesn't use the buffer at all and returns a pointer to some + * immutable string instead, as noted in the man page. + * + * The other problem is that on some systems strerror_r() has a return + * type of int. + * + * So I've written a wrapper function rs_strerror_r(), which should + * take care of all this and be used instead. + * + * Added 2008-01-30 + */ +char *rs_strerror_r(int errnum, char *buf, size_t buflen) { +#ifdef __hpux + char *pszErr; + pszErr = strerror(errnum); + snprintf(buf, buflen, "%s", pszErr); +#else +# ifdef STRERROR_R_CHAR_P + char *p = strerror_r(errnum, buf, buflen); + if (p != buf) { + strncpy(buf, p, buflen); + buf[buflen - 1] = '\0'; + } +# else + strerror_r(errnum, buf, buflen); +# endif +#endif /* #ifdef __hpux */ + return buf; +} + + +/* Decode a symbolic name to a numeric value + */ +int decodeSyslogName(uchar *name, syslogName_t *codetab) +{ + register syslogName_t *c; + register uchar *p; + uchar buf[80]; + + ASSERT(name != NULL); + ASSERT(codetab != NULL); + + dbgprintf("symbolic name: %s", name); + if (isdigit((int) *name)) + { + dbgprintf("\n"); + return (atoi((char*) name)); + } + strncpy((char*) buf, (char*) name, 79); + for (p = buf; *p; p++) + if (isupper((int) *p)) + *p = tolower((int) *p); + for (c = codetab; c->c_name; c++) + if (!strcmp((char*) buf, (char*) c->c_name)) + { + dbgprintf(" ==> %d\n", c->c_val); + return (c->c_val); + } + return (-1); +} + + +/** + * getSubString + * + * Copy a string byte by byte until the occurrence + * of a given separator. + * + * \param ppSrc Pointer to a pointer of the source array of characters. If a + separator detected the Pointer points to the next char after the + separator. Except if the end of the string is dedected ('\n'). + Then it points to the terminator char. + * \param pDst Pointer to the destination array of characters. Here the substing + will be stored. + * \param DstSize Maximum numbers of characters to store. + * \param cSep Separator char. + * \ret int Returns 0 if no error occured. + * + * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time + * so that it treats ' ' as a request for whitespace. But in general, the function and its callers + * should be changed over time, this is not really very good code... + */ +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) +{ + uchar *pSrc = *ppSrc; + int iErr = 0; /* 0 = no error, >0 = error */ + while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { + *pDst++ = *(pSrc)++; + DstSize--; + } + /* check if the Dst buffer was to small */ + if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { + dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); + iErr = 1; + } + if (*pSrc == '\0' || *pSrc == '\n') + /* this line was missing, causing ppSrc to be invalid when it + * was returned in case of end-of-string. rgerhards 2005-07-29 + */ + *ppSrc = pSrc; + else + *ppSrc = pSrc+1; + *pDst = '\0'; + return iErr; +} + + + +/* vim:set ai: + */ diff --git a/runtime/stream.c b/runtime/stream.c new file mode 100644 index 00000000..f1f69cc8 --- /dev/null +++ b/runtime/stream.c @@ -0,0 +1,933 @@ +//TODO: O_TRUC mode! +/* The serial stream class. + * + * A serial stream provides serial data access. In theory, serial streams + * can be implemented via a number of methods (e.g. files or in-memory + * streams). In practice, there currently only exist the file type (aka + * "driver"). + * + * File begun on 2008-01-09 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> /* required for HP UX */ +#include <errno.h> + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" + +/* static data */ +DEFobjStaticHelpers + +/* methods */ + +/* first, we define type-specific handlers. The provide a generic functionality, + * but for this specific type of strm. The mapping to these handlers happens during + * strm construction. Later on, handlers are called by pointers present in the + * strm instance object. + */ + +/* open a strm file + * It is OK to call this function when the stream is already open. In that + * case, it returns immediately with RS_RET_OK + */ +static rsRetVal strmOpenFile(strm_t *pThis) +{ + DEFiRet; + int iFlags; + + ASSERT(pThis != NULL); + ASSERT(pThis->tOperationsMode == STREAMMODE_READ || pThis->tOperationsMode == STREAMMODE_WRITE); + + if(pThis->fd != -1) + ABORT_FINALIZE(RS_RET_OK); + + if(pThis->pszFName == NULL) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) { + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, pThis->iCurrFNum, pThis->iFileNumDigits)); + } else { + if(pThis->pszDir == NULL) { + if((pThis->pszCurrFName = (uchar*) strdup((char*) pThis->pszFName)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } else { + CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir, + pThis->pszFName, pThis->lenFName, -1, 0)); + } + } + + /* compute which flags we need to provide to open */ + if(pThis->tOperationsMode == STREAMMODE_READ) + iFlags = O_RDONLY; + else + iFlags = O_WRONLY | O_CREAT; + + iFlags |= pThis->iAddtlOpenFlags; + + pThis->fd = open((char*)pThis->pszCurrFName, iFlags, pThis->tOpenMode); + if(pThis->fd == -1) { + int ierrnoSave = errno; + dbgoprint((obj_t*) pThis, "open error %d, file '%s'\n", errno, pThis->pszCurrFName); + if(ierrnoSave == ENOENT) + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + else + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + pThis->iCurrOffs = 0; + + dbgoprint((obj_t*) pThis, "opened file '%s' for %s (0x%x) as %d\n", pThis->pszCurrFName, + (pThis->tOperationsMode == STREAMMODE_READ) ? "READ" : "WRITE", iFlags, pThis->fd); + +finalize_it: + RETiRet; +} + + +/* close a strm file + * Note that the bDeleteOnClose flag is honored. If it is set, the file will be + * deleted after close. This is in support for the qRead thread. + */ +static rsRetVal strmCloseFile(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pThis->fd != -1); + dbgoprint((obj_t*) pThis, "file %d closing\n", pThis->fd); + + if(pThis->tOperationsMode == STREAMMODE_WRITE) + strmFlush(pThis); + + close(pThis->fd); // TODO: error check + pThis->fd = -1; + + if(pThis->bDeleteOnClose) { + unlink((char*) pThis->pszCurrFName); // TODO: check returncode + } + + pThis->iCurrOffs = 0; /* we are back at begin of file */ + if(pThis->pszCurrFName != NULL) { + free(pThis->pszCurrFName); /* no longer needed in any case (just for open) */ + pThis->pszCurrFName = NULL; + } + + RETiRet; +} + + +/* switch to next strm file + * This method must only be called if we are in a multi-file mode! + */ +static rsRetVal +strmNextFile(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pThis->iMaxFiles != 0); + ASSERT(pThis->fd != -1); + + CHKiRet(strmCloseFile(pThis)); + + /* we do modulo operation to ensure we obey the iMaxFile property. This will always + * result in a file number lower than iMaxFile, so it if wraps, the name is back to + * 0, which results in the first file being overwritten. Not desired for queues, so + * make sure their iMaxFiles is large enough. But it is well-desired for other + * use cases, e.g. a circular output log file. -- rgerhards, 2008-01-10 + */ + pThis->iCurrFNum = (pThis->iCurrFNum + 1) % pThis->iMaxFiles; + +finalize_it: + RETiRet; +} + + +/* handle the eof case for monitored files. + * If we are monitoring a file, someone may have rotated it. In this case, we + * also need to close it and reopen it under the same name. + * rgerhards, 2008-02-13 + */ +static rsRetVal +strmHandleEOFMonitor(strm_t *pThis) +{ + DEFiRet; + struct stat statOpen; + struct stat statName; + + ISOBJ_TYPE_assert(pThis, strm); + /* find inodes of both current descriptor as well as file now in file + * system. If they are different, the file has been rotated (or + * otherwise rewritten). We also check the size, because the inode + * does not change if the file is truncated (this, BTW, is also a case + * where we actually loose log lines, because we can not do anything + * against truncation...). We do NOT rely on the time of last + * modificaton because that may not be available under all + * circumstances. -- rgerhards, 2008-02-13 + */ + if(fstat(pThis->fd, &statOpen) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + if(stat((char*) pThis->pszCurrFName, &statName) == -1) + ABORT_FINALIZE(RS_RET_IO_ERROR); + if(statOpen.st_ino == statName.st_ino && pThis->iCurrOffs == statName.st_size) { + ABORT_FINALIZE(RS_RET_EOF); + } else { + /* we had a file change! */ + CHKiRet(strmCloseFile(pThis)); + CHKiRet(strmOpenFile(pThis)); + } + +finalize_it: + RETiRet; +} + + +/* handle the EOF case of a stream + * The EOF case is somewhat complicated, as the proper action depends on the + * mode the stream is in. If there are multiple files (circular logs, most + * important use case is queue files!), we need to close the current file and + * try to open the next one. + * rgerhards, 2008-02-13 + */ +static rsRetVal +strmHandleEOF(strm_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + switch(pThis->sType) { + case STREAMTYPE_FILE_SINGLE: + ABORT_FINALIZE(RS_RET_EOF); + break; + case STREAMTYPE_FILE_CIRCULAR: + /* we have multiple files and need to switch to the next one */ + /* TODO: think about emulating EOF in this case (not yet needed) */ +#if 0 + if(pThis->iMaxFiles == 0) /* TODO: why do we need this? ;) */ + ABORT_FINALIZE(RS_RET_EOF); +#endif + dbgoprint((obj_t*) pThis, "file %d EOF\n", pThis->fd); + CHKiRet(strmNextFile(pThis)); + break; + case STREAMTYPE_FILE_MONITOR: + CHKiRet(strmHandleEOFMonitor(pThis)); + break; + } + +finalize_it: + RETiRet; +} + +/* read the next buffer from disk + * rgerhards, 2008-02-13 + */ +static rsRetVal +strmReadBuf(strm_t *pThis) +{ + DEFiRet; + int bRun; + long iLenRead; + + ISOBJ_TYPE_assert(pThis, strm); + /* We need to try read at least twice because we may run into EOF and need to switch files. */ + bRun = 1; + while(bRun) { + /* first check if we need to (re)open the file. We may have switched to a new one in + * circular mode or it may have been rewritten (rotated) if we monitor a file + * rgerhards, 2008-02-13 + */ + CHKiRet(strmOpenFile(pThis)); + iLenRead = read(pThis->fd, pThis->pIOBuf, pThis->sIOBufSize); + dbgoprint((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead); + if(iLenRead == 0) { + CHKiRet(strmHandleEOF(pThis)); + } else if(iLenRead < 0) + ABORT_FINALIZE(RS_RET_IO_ERROR); + else { /* good read */ + pThis->iBufPtrMax = iLenRead; + bRun = 0; /* exit loop */ + } + } + /* if we reach this point, we had a good read */ + pThis->iBufPtr = 0; + +finalize_it: + RETiRet; +} + + +/* logically "read" a character from a file. What actually happens is that + * data is taken from the buffer. Only if the buffer is full, data is read + * directly from file. In that case, a read is performed blockwise. + * rgerhards, 2008-01-07 + * NOTE: needs to be enhanced to support sticking with a strm entry (if not + * deleted). + */ +rsRetVal strmReadChar(strm_t *pThis, uchar *pC) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pC != NULL); + + /* DEV debug only: dbgoprint((obj_t*) pThis, "strmRead index %d, max %d\n", pThis->iBufPtr, pThis->iBufPtrMax); */ + if(pThis->iUngetC != -1) { /* do we have an "unread" char that we need to provide? */ + *pC = pThis->iUngetC; + ++pThis->iCurrOffs; /* one more octet read */ + pThis->iUngetC = -1; + ABORT_FINALIZE(RS_RET_OK); + } + + /* do we need to obtain a new buffer? */ + if(pThis->iBufPtr >= pThis->iBufPtrMax) { + CHKiRet(strmReadBuf(pThis)); + } + + /* if we reach this point, we have data available in the buffer */ + + *pC = pThis->pIOBuf[pThis->iBufPtr++]; + ++pThis->iCurrOffs; /* one more octet read */ + +finalize_it: + RETiRet; +} + + +/* unget a single character just like ungetc(). As with that call, there is only a single + * character buffering capability. + * rgerhards, 2008-01-07 + */ +rsRetVal strmUnreadChar(strm_t *pThis, uchar c) +{ + ASSERT(pThis != NULL); + ASSERT(pThis->iUngetC == -1); + pThis->iUngetC = c; + --pThis->iCurrOffs; /* one less octet read - NOTE: this can cause problems if we got a file change + and immediately do an unread and the file is on a buffer boundary and the stream is then persisted. + With the queue, this can not happen as an Unread is only done on record begin, which is never split + accross files. For other cases we accept the very remote risk. -- rgerhards, 2008-01-12 */ + + return RS_RET_OK; +} + + +/* read a line from a strm file. A line is terminated by LF. The LF is read, but it + * is not returned in the buffer (it is discared). The caller is responsible for + * destruction of the returned CStr object! -- rgerhards, 2008-01-07 + * rgerhards, 2008-03-27: I now use the ppCStr directly, without any interim + * string pointer. The reason is that this function my be called by inputs, which + * are pthread_killed() upon termination. So if we use their native pointer, they + * can cleanup (but only then). + */ +rsRetVal +strmReadLine(strm_t *pThis, cstr_t **ppCStr) +{ + DEFiRet; + uchar c; + + ASSERT(pThis != NULL); + ASSERT(ppCStr != NULL); + + CHKiRet(rsCStrConstruct(ppCStr)); + + /* now read the line */ + CHKiRet(strmReadChar(pThis, &c)); + while(c != '\n') { + CHKiRet(rsCStrAppendChar(*ppCStr, c)); + CHKiRet(strmReadChar(pThis, &c)); + } + CHKiRet(rsCStrFinish(*ppCStr)); + +finalize_it: + if(iRet != RS_RET_OK && *ppCStr != NULL) + rsCStrDestruct(ppCStr); + + RETiRet; +} + + +/* Standard-Constructor for the strm object + */ +BEGINobjConstruct(strm) /* be sure to specify the object type also in END macro! */ + pThis->iCurrFNum = 1; + pThis->fd = -1; + pThis->iUngetC = -1; + pThis->sType = STREAMTYPE_FILE_SINGLE; + pThis->sIOBufSize = glblGetIOBufSize(); + pThis->tOpenMode = 0600; /* TODO: make configurable */ +ENDobjConstruct(strm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal strmConstructFinalize(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->pIOBuf == NULL) { /* allocate our io buffer in case we have not yet */ + if((pThis->pIOBuf = (uchar*) malloc(sizeof(uchar) * pThis->sIOBufSize)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + pThis->iBufPtrMax = 0; /* results in immediate read request */ + } + +finalize_it: + RETiRet; +} + + +/* destructor for the strm object */ +BEGINobjDestruct(strm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(strm) + if(pThis->tOperationsMode == STREAMMODE_WRITE) + strmFlush(pThis); + + /* ... then free resources */ + if(pThis->fd != -1) + strmCloseFile(pThis); + + if(pThis->pszDir != NULL) + free(pThis->pszDir); + if(pThis->pIOBuf != NULL) + free(pThis->pIOBuf); + if(pThis->pszCurrFName != NULL) + free(pThis->pszCurrFName); + if(pThis->pszFName != NULL) + free(pThis->pszFName); +ENDobjDestruct(strm) + + +/* check if we need to open a new file (in output mode only). + * The decision is based on file size AND record delimition state. + * This method may also be called on a closed file, in which case + * it immediately returns. + */ +static rsRetVal strmCheckNextOutputFile(strm_t *pThis) +{ + DEFiRet; + + if(pThis->fd == -1) + FINALIZE; + + if(pThis->iCurrOffs >= pThis->iMaxFileSize) { + dbgoprint((obj_t*) pThis, "max file size %ld reached for %d, now %ld - starting new file\n", + (long) pThis->iMaxFileSize, pThis->fd, (long) pThis->iCurrOffs); + CHKiRet(strmNextFile(pThis)); + } + +finalize_it: + RETiRet; +} + +/* write memory buffer to a stream object. + * To support direct writes of large objects, this method may be called + * with a buffer pointing to some region other than the stream buffer itself. + * However, in that case the stream buffer must be empty (strmFlush() has to + * be called before), because we would otherwise mess up with the sequence + * inside the stream. -- rgerhards, 2008-01-10 + */ +static rsRetVal strmWriteInternal(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + DEFiRet; + int iWritten; + + ASSERT(pThis != NULL); + ASSERT(pBuf == pThis->pIOBuf || pThis->iBufPtr == 0); + + if(pThis->fd == -1) + CHKiRet(strmOpenFile(pThis)); + + iWritten = write(pThis->fd, pBuf, lenBuf); + dbgoprint((obj_t*) pThis, "file %d write wrote %d bytes\n", pThis->fd, iWritten); + /* TODO: handle error case -- rgerhards, 2008-01-07 */ + + /* Now indicate buffer empty again. We do this in any case, because there + * is no way we could react more intelligently to an error during write. + * This MUST be done BEFORE strCheckNextOutputFile(), otherwise we have an + * endless loop. We reset the buffer pointer also in finalize_it - this is + * necessary if we run into problems. Not resetting it would again cause an + * endless loop. So it is better to loose some data (which also justifies + * duplicating that code, too...) -- rgerhards, 2008-01-10 + */ + pThis->iBufPtr = 0; + pThis->iCurrOffs += iWritten; + /* update user counter, if provided */ + if(pThis->pUsrWCntr != NULL) + *pThis->pUsrWCntr += iWritten; + + if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) + CHKiRet(strmCheckNextOutputFile(pThis)); + +finalize_it: + pThis->iBufPtr = 0; /* see comment above */ + + RETiRet; +} + + +/* flush stream output buffer to persistent storage. This can be called at any time + * and is automatically called when the output buffer is full. + * rgerhards, 2008-01-10 + */ +rsRetVal strmFlush(strm_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + dbgoprint((obj_t*) pThis, "file %d flush, buflen %ld\n", pThis->fd, (long) pThis->iBufPtr); + + if(pThis->tOperationsMode == STREAMMODE_WRITE && pThis->iBufPtr > 0) { + iRet = strmWriteInternal(pThis, pThis->pIOBuf, pThis->iBufPtr); + } + + RETiRet; +} + + +/* seek a stream to a specific location. Pending writes are flushed, read data + * is invalidated. + * rgerhards, 2008-01-12 + */ +static rsRetVal strmSeek(strm_t *pThis, off_t offs) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pThis->fd == -1) + strmOpenFile(pThis); + else + strmFlush(pThis); + int i; + dbgoprint((obj_t*) pThis, "file %d seek, pos %ld\n", pThis->fd, (long) offs); + i = lseek(pThis->fd, offs, SEEK_SET); // TODO: check error! + pThis->iCurrOffs = offs; /* we are now at *this* offset */ + pThis->iBufPtr = 0; /* buffer invalidated */ + + RETiRet; +} + + +/* seek to current offset. This is primarily a helper to readjust the OS file + * pointer after a strm object has been deserialized. + */ +rsRetVal strmSeekCurrOffs(strm_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + iRet = strmSeek(pThis, pThis->iCurrOffs); + RETiRet; +} + + +/* write a *single* character to a stream object -- rgerhards, 2008-01-10 + */ +rsRetVal strmWriteChar(strm_t *pThis, uchar c) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* if the buffer is full, we need to flush before we can write */ + if(pThis->iBufPtr == pThis->sIOBufSize) { + CHKiRet(strmFlush(pThis)); + } + /* we now always have space for one character, so we simply copy it */ + *(pThis->pIOBuf + pThis->iBufPtr) = c; + pThis->iBufPtr++; + +finalize_it: + RETiRet; +} + + +/* write an integer value (actually a long) to a stream object */ +rsRetVal strmWriteLong(strm_t *pThis, long i) +{ + DEFiRet; + uchar szBuf[32]; + + ASSERT(pThis != NULL); + + CHKiRet(srUtilItoA((char*)szBuf, sizeof(szBuf), i)); + CHKiRet(strmWrite(pThis, szBuf, strlen((char*)szBuf))); + +finalize_it: + RETiRet; +} + + +/* write memory buffer to a stream object + */ +rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf) +{ + DEFiRet; + size_t iPartial; + + ASSERT(pThis != NULL); + ASSERT(pBuf != NULL); + + /* check if the to-be-written data is larger than our buffer size */ + if(lenBuf >= pThis->sIOBufSize) { + /* it is - so we do a direct write, that is most efficient. + * TODO: is it really? think about disk block sizes! + */ + CHKiRet(strmFlush(pThis)); /* we need to flush first!!! */ + CHKiRet(strmWriteInternal(pThis, pBuf, lenBuf)); + } else { + /* data fits into a buffer - we just need to see if it + * fits into the current buffer... + */ + if(pThis->iBufPtr + lenBuf > pThis->sIOBufSize) { + /* nope, so we must split it */ + iPartial = pThis->sIOBufSize - pThis->iBufPtr; /* this fits in current buf */ + if(iPartial > 0) { /* the buffer was exactly full, can not write anything! */ + memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf, iPartial); + pThis->iBufPtr += iPartial; + } + CHKiRet(strmFlush(pThis)); /* get a new buffer for rest of data */ + memcpy(pThis->pIOBuf, pBuf + iPartial, lenBuf - iPartial); + pThis->iBufPtr = lenBuf - iPartial; + } else { + /* we have space, so we simply copy over the string */ + memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf, lenBuf); + pThis->iBufPtr += lenBuf; + } + } + +finalize_it: + RETiRet; +} + + +/* property set methods */ +/* simple ones first */ +DEFpropSetMeth(strm, bDeleteOnClose, int) +DEFpropSetMeth(strm, iMaxFileSize, int) +DEFpropSetMeth(strm, iFileNumDigits, int) +DEFpropSetMeth(strm, tOperationsMode, int) +DEFpropSetMeth(strm, tOpenMode, mode_t) +DEFpropSetMeth(strm, sType, strmType_t) + +rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal) +{ + pThis->iMaxFiles = iNewVal; + pThis->iFileNumDigits = getNumberDigits(iNewVal); + return RS_RET_OK; +} + +rsRetVal strmSetiAddtlOpenFlags(strm_t *pThis, int iNewVal) +{ + DEFiRet; + + if(iNewVal & O_APPEND) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + pThis->iAddtlOpenFlags = iNewVal; + +finalize_it: + RETiRet; +} + + +/* set the stream's file prefix + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +rsRetVal +strmSetFName(strm_t *pThis, uchar *pszName, size_t iLenName) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pszName != NULL); + + if(iLenName < 1) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + if(pThis->pszFName != NULL) + free(pThis->pszFName); + + if((pThis->pszFName = malloc(sizeof(uchar) * iLenName + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszFName, pszName, iLenName + 1); /* always think about the \0! */ + pThis->lenFName = iLenName; + +finalize_it: + RETiRet; +} + + +/* set the stream's directory + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +rsRetVal +strmSetDir(strm_t *pThis, uchar *pszDir, size_t iLenDir) +{ + DEFiRet; + + ASSERT(pThis != NULL); + ASSERT(pszDir != NULL); + + if(iLenDir < 1) + ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING); + + if((pThis->pszDir = malloc(sizeof(uchar) * iLenDir + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszDir, pszDir, iLenDir + 1); /* always think about the \0! */ + pThis->lenDir = iLenDir; + +finalize_it: + RETiRet; +} + + +/* support for data records + * The stream class is able to write to multiple files. However, there are + * situation (actually quite common), where a single data record should not + * be split across files. This may be problematic if multiple stream write + * calls are used to create the record. To support that, we provide the + * bInRecord status variable. If it is set, no file spliting occurs. Once + * it is set to 0, a check is done if a split is necessary and it then + * happens. For a record-oriented caller, the proper sequence is: + * + * strmRecordBegin() + * strmWrite...() + * strmRecordEnd() + * + * Please note that records do not affect the writing of output buffers. They + * are always written when full. The only thing affected is circular files + * creation. So it is safe to write large records. + * + * IMPORTANT: RecordBegin() can not be nested! It is a programming error + * if RecordBegin() is called while already in a record! + * + * rgerhards, 2008-01-10 + */ +rsRetVal strmRecordBegin(strm_t *pThis) +{ + ASSERT(pThis != NULL); + ASSERT(pThis->bInRecord == 0); + pThis->bInRecord = 1; + return RS_RET_OK; +} + +rsRetVal strmRecordEnd(strm_t *pThis) +{ + DEFiRet; + ASSERT(pThis != NULL); + ASSERT(pThis->bInRecord == 1); + + pThis->bInRecord = 0; + iRet = strmCheckNextOutputFile(pThis); /* check if we need to switch files */ + + RETiRet; +} +/* end stream record support functions */ + + +/* This method serializes a stream object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object. + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the dynamic properties. + * rgerhards, 2008-01-10 + */ +rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm) +{ + DEFiRet; + int i; + long l; + + ISOBJ_TYPE_assert(pThis, strm); + ISOBJ_TYPE_assert(pStrm, strm); + + strmFlush(pThis); + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + + objSerializeSCALAR(pStrm, iCurrFNum, INT); + objSerializePTR(pStrm, pszFName, PSZ); + objSerializeSCALAR(pStrm, iMaxFiles, INT); + objSerializeSCALAR(pStrm, bDeleteOnClose, INT); + + i = pThis->sType; + objSerializeSCALAR_VAR(pStrm, sType, INT, i); + + i = pThis->tOperationsMode; + objSerializeSCALAR_VAR(pStrm, tOperationsMode, INT, i); + + i = pThis->tOpenMode; + objSerializeSCALAR_VAR(pStrm, tOpenMode, INT, i); + + l = (long) pThis->iCurrOffs; + objSerializeSCALAR_VAR(pStrm, iCurrOffs, LONG, l); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + + +/* set a user write-counter. This counter is initialized to zero and + * receives the number of bytes written. It is accurate only after a + * flush(). This hook is provided as a means to control disk size usage. + * The pointer must be valid at all times (so if it is on the stack, be sure + * to remove it when you exit the function). Pointers are removed by + * calling strmSetWCntr() with a NULL param. Only one pointer is settable, + * any new set overwrites the previous one. + * rgerhards, 2008-02-27 + */ +rsRetVal +strmSetWCntr(strm_t *pThis, number_t *pWCnt) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + + if(pWCnt != NULL) + *pWCnt = 0; + pThis->pUsrWCntr = pWCnt; + + RETiRet; +} + + +#include "stringbuf.h" + +/* This function can be used as a generic way to set properties. + * rgerhards, 2008-01-11 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal strmSetProperty(strm_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + ASSERT(pProp != NULL); + + if(isProp("sType")) { + CHKiRet(strmSetsType(pThis, (strmType_t) pProp->val.num)); + } else if(isProp("iCurrFNum")) { + pThis->iCurrFNum = pProp->val.num; + } else if(isProp("pszFName")) { + CHKiRet(strmSetFName(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr))); + } else if(isProp("tOperationsMode")) { + CHKiRet(strmSettOperationsMode(pThis, pProp->val.num)); + } else if(isProp("tOpenMode")) { + CHKiRet(strmSettOpenMode(pThis, pProp->val.num)); + } else if(isProp("iCurrOffs")) { + pThis->iCurrOffs = pProp->val.num; + } else if(isProp("iMaxFileSize")) { + CHKiRet(strmSetiMaxFileSize(pThis, pProp->val.num)); + } else if(isProp("iMaxFiles")) { + CHKiRet(strmSetiMaxFiles(pThis, pProp->val.num)); + } else if(isProp("iFileNumDigits")) { + CHKiRet(strmSetiFileNumDigits(pThis, pProp->val.num)); + } else if(isProp("bDeleteOnClose")) { + CHKiRet(strmSetbDeleteOnClose(pThis, pProp->val.num)); + } + +finalize_it: + RETiRet; +} +#undef isProp + + +/* return the current offset inside the stream. Note that on two consequtive calls, the offset + * reported on the second call may actually be lower than on the first call. This is due to + * file circulation. A caller must deal with that. -- rgerhards, 2008-01-30 + */ +rsRetVal +strmGetCurrOffset(strm_t *pThis, int64 *pOffs) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, strm); + ASSERT(pOffs != NULL); + + *pOffs = pThis->iCurrOffs; + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(strm) +CODESTARTobjQueryInterface(strm) + if(pIf->ifVersion != strmCURR_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). + */ + /*xxxpIf->oID = OBJvm; SAMPLE */ + +finalize_it: +ENDobjQueryInterface(strm) + + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(strm, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + + OBJSetMethodHandler(objMethod_SERIALIZE, strmSerialize); + OBJSetMethodHandler(objMethod_SETPROPERTY, strmSetProperty); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strmConstructFinalize); +ENDObjClassInit(strm) + + +/* + * vi:set ai: + */ diff --git a/runtime/stream.h b/runtime/stream.h new file mode 100644 index 00000000..371358ab --- /dev/null +++ b/runtime/stream.h @@ -0,0 +1,131 @@ +/* Definition of serial stream class (strm). + * + * A serial stream provides serial data access. In theory, serial streams + * can be implemented via a number of methods (e.g. files or in-memory + * streams). In practice, there currently only exist the file type (aka + * "driver"). + * + * In practice, many stream features are bound to files. I have not yet made + * any serious effort, except for the naming of this class, to try to make + * the interfaces very generic. However, I assume that we could work much + * like in the strm class, where some properties are simply ignored when + * the wrong strm mode is selected (which would translate here to the wrong + * stream mode). + * + * Most importantly, this class provides generic input and output functions + * which can directly be used to work with the strms and file output. It + * provides such useful things like a circular file buffer and, hopefully + * at a later stage, a lazy writer. The object is also seriazable and thus + * can easily be persistet. The bottom line is that it makes much sense to + * use this class whereever possible as its features may grow in the future. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef STREAM_H_INCLUDED +#define STREAM_H_INCLUDED + +#include <pthread.h> +#include "obj-types.h" +#include "glbl.h" +#include "stream.h" + +/* stream types */ +typedef enum { + STREAMTYPE_FILE_SINGLE = 0, /**< read a single file */ + STREAMTYPE_FILE_CIRCULAR = 1, /**< circular files */ + STREAMTYPE_FILE_MONITOR = 2 /**< monitor a (third-party) file */ +} strmType_t; + +typedef enum { + STREAMMMODE_INVALID = 0, + STREAMMODE_READ = 1, + STREAMMODE_WRITE = 2 +} strmMode_t; + +/* The strm_t data structure */ +typedef struct strm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + strmType_t sType; + /* descriptive properties */ + int iCurrFNum;/* current file number (NOT descriptor, but the number in the file name!) */ + uchar *pszFName; /* prefix for generated filenames */ + int lenFName; + strmMode_t tOperationsMode; + mode_t tOpenMode; + int iAddtlOpenFlags; /* can be used to specifiy additional (compatible!) open flags */ + int64 iMaxFileSize;/* maximum size a file may grow to */ + int iMaxFiles; /* maximum number of files if a circular mode is in use */ + int iFileNumDigits;/* min number of digits to use in file number (only in circular mode) */ + int bDeleteOnClose; /* set to 1 to auto-delete on close -- be careful with that setting! */ + int64 iCurrOffs;/* current offset */ + int64 *pUsrWCntr; /* NULL or a user-provided counter that receives the nbr of bytes written since the last CntrSet() */ + /* dynamic properties, valid only during file open, not to be persistet */ + size_t sIOBufSize;/* size of IO buffer */ + uchar *pszDir; /* Directory */ + int lenDir; + int fd; /* the file descriptor, -1 if closed */ + uchar *pszCurrFName; /* name of current file (if open) */ + uchar *pIOBuf; /* io Buffer */ + size_t iBufPtrMax; /* current max Ptr in Buffer (if partial read!) */ + size_t iBufPtr; /* pointer into current buffer */ + int iUngetC; /* char set via UngetChar() call or -1 if none set */ + int bInRecord; /* if 1, indicates that we are currently writing a not-yet complete record */ +} strm_t; + +/* interfaces */ +BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */ +ENDinterface(strm) +#define strmCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +rsRetVal strmConstruct(strm_t **ppThis); +rsRetVal strmConstructFinalize(strm_t __attribute__((unused)) *pThis); +rsRetVal strmDestruct(strm_t **ppThis); +rsRetVal strmSetMaxFileSize(strm_t *pThis, int64 iMaxFileSize); +rsRetVal strmSetFileName(strm_t *pThis, uchar *pszName, size_t iLenName); +rsRetVal strmReadChar(strm_t *pThis, uchar *pC); +rsRetVal strmUnreadChar(strm_t *pThis, uchar c); +rsRetVal strmReadLine(strm_t *pThis, cstr_t **ppCStr); +rsRetVal strmSeekCurrOffs(strm_t *pThis); +rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf); +rsRetVal strmWriteChar(strm_t *pThis, uchar c); +rsRetVal strmWriteLong(strm_t *pThis, long i); +rsRetVal strmSetFName(strm_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal strmSetDir(strm_t *pThis, uchar *pszDir, size_t iLenDir); +rsRetVal strmFlush(strm_t *pThis); +rsRetVal strmRecordBegin(strm_t *pThis); +rsRetVal strmRecordEnd(strm_t *pThis); +rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm); +rsRetVal strmSetiAddtlOpenFlags(strm_t *pThis, int iNewVal); +rsRetVal strmGetCurrOffset(strm_t *pThis, int64 *pOffs); +rsRetVal strmSetWCntr(strm_t *pThis, number_t *pWCnt); +PROTOTYPEObjClassInit(strm); +PROTOTYPEpropSetMeth(strm, bDeleteOnClose, int); +PROTOTYPEpropSetMeth(strm, iMaxFileSize, int); +PROTOTYPEpropSetMeth(strm, iMaxFiles, int); +PROTOTYPEpropSetMeth(strm, iFileNumDigits, int); +PROTOTYPEpropSetMeth(strm, tOperationsMode, int); +PROTOTYPEpropSetMeth(strm, tOpenMode, mode_t); +PROTOTYPEpropSetMeth(strm, sType, strmType_t); + +#endif /* #ifndef STREAM_H_INCLUDED */ diff --git a/runtime/stringbuf.c b/runtime/stringbuf.c new file mode 100644 index 00000000..93d1e1ef --- /dev/null +++ b/runtime/stringbuf.c @@ -0,0 +1,1080 @@ +/* This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * Please see syslogd.c for license information. + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * begun 2005-09-07 rgerhards + * + * Copyright (C) 2007-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "regexp.h" +#include "obj.h" + + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(regexp) + +/* ################################################################# * + * public members * + * ################################################################# */ + + +rsRetVal rsCStrConstruct(cstr_t **ppThis) +{ + DEFiRet; + cstr_t *pThis; + + ASSERT(ppThis != NULL); + + if((pThis = (cstr_t*) calloc(1, sizeof(cstr_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + rsSETOBJTYPE(pThis, OIDrsCStr); + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + pThis->iBufSize = 0; + pThis->iStrLen = 0; + pThis->iAllocIncrement = RS_STRINGBUF_ALLOC_INCREMENT; + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* construct from sz string + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = strlen((char*)(char *) sz); + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, sz, pThis->iStrLen); + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + +/* construct from CStr object. only the counted string is + * copied, not the szString. + * rgerhards 2005-10-18 + */ +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + rsCHECKVALIDOBJECT(pFrom, OIDrsCStr); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen; + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* copy properties */ + memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen); + + *ppThis = pThis; +finalize_it: + RETiRet; +} + + +void rsCStrDestruct(cstr_t **ppThis) +{ + cstr_t *pThis = *ppThis; + + /* rgerhards 2005-10-19: The free of pBuf was contained in conditional compilation. + * The code was only compiled if STRINGBUF_TRIM_ALLOCSIZE was set to 1. I honestly + * do not know why it was so, I think it was an artifact. Anyhow, I have changed this + * now. Should there any issue occur, this comment hopefully will shed some light + * on what happened. I re-verified, and this function has never before been called + * by anyone. So changing it can have no impact for obvious reasons... + * + * rgerhards, 2008-02-20: I changed the interface to the new calling conventions, where + * the destructor receives a pointer to the object, so that it can set it to NULL. + */ + if(pThis->pBuf != NULL) { + free(pThis->pBuf); + } + + if(pThis->pszBuf != NULL) { + free(pThis->pszBuf); + } + + RSFREEOBJ(pThis); + *ppThis = NULL; +} + + +/* extend the string buffer if its size is insufficient. + * Param iMinNeeded is the minumum free space needed. If it is larger + * than the default alloc increment, space for at least this amount is + * allocated. In practice, a bit more is allocated because we envision that + * some more characters may be added after these. + * rgerhards, 2008-01-07 + */ +static rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded) +{ + DEFiRet; + uchar *pNewBuf; + size_t iNewSize; + + /* first compute the new size needed */ + if(iMinNeeded > pThis->iAllocIncrement) { + /* we allocate "n" iAllocIncrements. Usually, that should + * leave some room after the absolutely needed one. It also + * reduces memory fragmentation. Note that all of this are + * integer operations (very important to understand what is + * going on)! Parenthesis are for better readibility. + */ + iNewSize = ((iMinNeeded / pThis->iAllocIncrement) + 1) * pThis->iAllocIncrement; + } else { + iNewSize = pThis->iBufSize + pThis->iAllocIncrement; + } + iNewSize += pThis->iBufSize; /* add current size */ + + /* and then allocate and copy over */ + /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */ + if((pNewBuf = (uchar*) malloc(iNewSize * sizeof(uchar))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pNewBuf, pThis->pBuf, pThis->iBufSize); + pThis->iBufSize = iNewSize; + if(pThis->pBuf != NULL) { + free(pThis->pBuf); + } + pThis->pBuf = pNewBuf; + +finalize_it: + RETiRet; +} + + +/* append a string of known length. In this case, we make sure we do at most + * one additional memory allocation. + * I optimized this function to use memcpy(), among others. Consider it a + * rewrite (which may be good to know in case of bugs) -- rgerhards, 2008-01-07 + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen) +{ + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(psz != NULL); + + /* does the string fit? */ + if(pThis->iStrLen + iStrLen > pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */ + } + + /* ok, now we always have sufficient continues memory to do a memcpy() */ + memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen); + pThis->iStrLen += iStrLen; + +finalize_it: + RETiRet; +} + + +/* changed to be a wrapper to rsCStrAppendStrWithLen() so that + * we can save some time when we have the length but do not + * need to change existing code. + * rgerhards, 2007-07-03 + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz) +{ + return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz)); +} + + +/* append the contents of one cstr_t object to another + * rgerhards, 2008-02-25 + */ +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) +{ + return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen); +} + + +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) +{ + DEFiRet; + uchar szBuf[32]; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i)); + + iRet = rsCStrAppendStr(pThis, szBuf); +finalize_it: + RETiRet; +} + + +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c) +{ + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen >= pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */ + } + + /* ok, when we reach this, we have sufficient memory */ + *(pThis->pBuf + pThis->iStrLen++) = c; + + /* check if we need to invalidate an sz representation! */ + if(pThis->pszBuf != NULL) { + free(pThis->pszBuf); + pThis->pszBuf = NULL; + } + +finalize_it: + RETiRet; +} + + +/* Sets the string object to the classigal sz-string provided. + * Any previously stored vlaue is discarded. If a NULL pointer + * the the new value (pszNew) is provided, an empty string is + * created (this is NOT an error!). Property iAllocIncrement is + * not modified by this function. + * rgerhards, 2005-10-18 + */ +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->pBuf != NULL) + free(pThis->pBuf); + if(pThis->pszBuf != NULL) + free(pThis->pszBuf); + if(pszNew == NULL) { + pThis->iStrLen = 0; + pThis->iBufSize = 0; + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + } else { + pThis->iStrLen = strlen((char*)pszNew); + pThis->iBufSize = pThis->iStrLen; + pThis->pszBuf = NULL; + /* iAllocIncrement is NOT modified! */ + + /* now save the new value */ + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + return RS_RET_OUT_OF_MEMORY; + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, pszNew, pThis->iStrLen); + } + + return RS_RET_OK; +} + +/* Converts the CStr object to a classical sz string and returns that. + * Same restrictions as in rsCStrGetSzStr() applies (see there!). This + * function here guarantees that a valid string is returned, even if + * the CStr object currently holds a NULL pointer string buffer. If so, + * "" is returned. + * rgerhards 2005-10-19 + * WARNING: The returned pointer MUST NOT be freed, as it may be + * obtained from that constant memory pool (in case of NULL!) + */ +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + if(pThis->pBuf == NULL) + return (uchar*) ""; + else + return rsCStrGetSzStr(pThis); +} + + +/* Converts the CStr object to a classical zero-terminated C string + * and returns that string. The caller must not free it and must not + * destroy the CStr object as long as the ascii string is used. + * This function may return NULL, if the string is currently NULL. This + * is a feature, not a bug. If you need non-NULL in any case, use + * rsCStrGetSzStrNoNULL() instead. + * rgerhards, 2005-09-15 + */ +uchar* rsCStrGetSzStr(cstr_t *pThis) +{ + size_t i; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->pBuf != NULL) + if(pThis->pszBuf == NULL) { + /* we do not yet have a usable sz version - so create it... */ + if((pThis->pszBuf = malloc((pThis->iStrLen + 1) * sizeof(uchar))) == NULL) { + /* TODO: think about what to do - so far, I have no bright + * idea... rgerhards 2005-09-07 + */ + } + else { /* we can create the sz String */ + /* now copy it while doing a sanity check. The string might contain a + * \0 byte. There is no way how a sz string can handle this. For + * the time being, we simply replace it with space - something that + * could definitely be improved (TODO). + * 2005-09-15 rgerhards + */ + for(i = 0 ; i < pThis->iStrLen ; ++i) { + if(pThis->pBuf[i] == '\0') + pThis->pszBuf[i] = ' '; + else + pThis->pszBuf[i] = pThis->pBuf[i]; + } + /* write terminator... */ + pThis->pszBuf[i] = '\0'; + } + } + + return(pThis->pszBuf); +} + + +/* Converts the CStr object to a classical zero-terminated C string, + * returns that string and destroys the CStr object. The returned string + * MUST be freed by the caller. The function might return NULL if + * no memory can be allocated. + * + * TODO: + * This function should at some time become special. The base idea is to + * add one extra byte to the end of the regular buffer, so that we can + * convert it to an szString without the need to copy. The extra memory + * footprint is not hefty, but the performance gain is potentially large. + * To get it done now, I am not doing the optimiziation right now. + * rgerhards, 2005-09-07 + * + * rgerhards, 2007-09-04: I have changed the interface of this function. It now + * returns an rsRetVal, so that we can communicate back if we have an error. + * Using the standard method is much better than returning NULL. Secondly, NULL + * was not actually an error - it was in indication if the string was empty. + * This was needed in some parts of the code, in others not. I have now added + * a second parameter to specify what the caller needs. I hope these changes + * will make it less likely that the function is called incorrectly, what + * previously happend quite often and was the cause of a number of program + * aborts. So the parameters are now: + * pointer to the object, pointer to string-pointer to receive string and + * bRetNULL: 0 - must not return NULL on empty string, return "" in that + * case, 1 - return NULL instead of an empty string. + * PLEASE NOTE: the caller must free the memory returned in ppSz in any case + * (except, of course, if it is NULL). + */ +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL) +{ + DEFiRet; + uchar* pRetBuf; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(ppSz != NULL); + assert(bRetNULL == 0 || bRetNULL == 1); + + if(pThis->pBuf == NULL) { + if(bRetNULL == 0) { + if((pRetBuf = malloc(sizeof(uchar))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + *pRetBuf = '\0'; + } else { + pRetBuf = NULL; + } + } else + pRetBuf = rsCStrGetSzStr(pThis); + + *ppSz = pRetBuf; + +finalize_it: + /* We got it, now free the object ourselfs. Please note + * that we can NOT use the rsCStrDestruct function as it would + * also free the sz String buffer, which we pass on to the user. + */ + if(pThis->pBuf != NULL) + free(pThis->pBuf); + RSFREEOBJ(pThis); + + RETiRet; +} + + +#if STRINGBUF_TRIM_ALLOCSIZE == 1 + /* Only in this mode, we need to trim the string. To do + * so, we must allocate a new buffer of the exact + * string size, and then copy the old one over. + */ + /* WARNING + * STRINGBUF_TRIM_ALLOCSIZE can, in theory, be used to trim + * memory buffers. This part of the code was inherited from + * liblogging (where it is used in a different context) but + * never put to use in rsyslog. The reason is that it is hardly + * imaginable where the extra performance cost is worth the save + * in memory alloc. Then Anders Blomdel rightfully pointed out that + * the code does not work at all - and nobody even know that it + * probably shouldn't. Rather than removing, I deciced to somewhat + * fix the code, so that this feature may be enabled if somebody + * really has a need for it. Be warned, however, that I NEVER + * tested the fix. So if you intend to use this feature, you must + * do full testing before you rely on it. -- rgerhards, 2008-02-12 + */ +rsRetVal rsCStrFinish(cstr_t __attribute__((unused)) *pThis) +{ + DEFiRet; + uchar* pBuf; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if((pBuf = malloc((pThis->iStrLen) * sizeof(uchar))) == NULL) + { /* OK, in this case we use the previous buffer. At least + * we have it ;) + */ + } + else + { /* got the new buffer, so let's use it */ + memcpy(pBuf, pThis->pBuf, pThis->iStrLen); + pThis->pBuf = pBuf; + } + + RETiRet; +} +#endif /* #if STRINGBUF_TRIM_ALLOCSIZE == 1 */ + + +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(iNewIncrement > 0); + + pThis->iAllocIncrement = iNewIncrement; +} + + +/* return the length of the current string + * 2005-09-09 rgerhards + * Please note: this is only a function in a debug build. + * For release builds, it is a macro defined in stringbuf.h. + * This is due to performance reasons. + */ +#ifndef NDEBUG +int rsCStrLen(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + return(pThis->iStrLen); +} +#endif + +/* Truncate characters from the end of the string. + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen < nTrunc) + return RS_TRUNCAT_TOO_LARGE; + + pThis->iStrLen -= nTrunc; + + if(pThis->pszBuf != NULL) { + /* in this case, we adjust the psz representation + * by writing a new \0 terminator - this is by far + * the fastest way and outweights the additional memory + * required. 2005-9-19 rgerhards. + */ + pThis->pszBuf[pThis->iStrLen] = '\0'; + } + + return RS_RET_OK; +} + +/* Trim trailing whitespace from a given string + */ +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis) +{ + register int i; + register uchar *pC; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + i = pThis->iStrLen; + pC = pThis->pBuf + i - 1; + while(i > 0 && isspace((int)*pC)) { + --pC; + --i; + } + /* i now is the new string length! */ + pThis->iStrLen = i; + + return RS_RET_OK; +} + +/* compare two string objects - works like strcmp(), but operates + * on CStr objects. Please note that this version here is + * faster in the majority of cases, simply because it can + * rely on StrLen. + * rgerhards 2005-09-19 + * fixed bug, in which only the last byte was actually compared + * in equal-size strings. + * rgerhards, 2005-09-26 + */ +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + rsCHECKVALIDOBJECT(pCS2, OIDrsCStr); + if(pCS1->iStrLen == pCS2->iStrLen) + if(pCS1->iStrLen == 0) + return 0; /* zero-sized string are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < pCS1->iStrLen ; ++i) { + if(pCS1->pBuf[i] != pCS2->pBuf[i]) + return pCS1->pBuf[i] - pCS2->pBuf[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + } + else + return pCS1->iStrLen - pCS2->iStrLen; +} + + +/* check if a sz-type string starts with a CStr object. This function + * is initially written to support the "startswith" property-filter + * comparison operation. Maybe it also has other needs. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-10-19 + */ +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register int i; + int iMax; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(iLenSz >= pCS1->iStrLen) { + /* we need to checkusing pCS1->iStrLen charactes at maximum, thus + * we move it to iMax. + */ + iMax = pCS1->iStrLen; + if(iMax == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iMax ; ++i) { + if(psz[i] != pCS1->pBuf[i]) + return psz[i] - pCS1->pBuf[i]; + } + /* if we arrive here, the string actually starts with pCS1 */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* check if a CStr object starts with a sz-type string. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-09-26 + */ +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i] != psz[i]) + return pCS1->pBuf[i] - psz[i]; + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* The same as rsCStrStartsWithSzStr(), but does a case-insensitive + * comparison. TODO: consolidate the two. + * rgerhards 2008-02-28 + */ +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(tolower(pCS1->pBuf[i]) != tolower(psz[i])) + return tolower(pCS1->pBuf[i]) - tolower(psz[i]); + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + +/* check if a CStr object matches a regex. + * msamia@redhat.com 2007-07-12 + * @return returns 0 if matched + * bug: doesn't work for CStr containing \0 + * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there + * never is a \0 *inside* a property string. + * Note that the function returns -1 if regexp functionality is not available. + * TODO: change calling interface! -- rgerhards, 2008-03-07 + */ +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz) +{ + regex_t preq; + int ret; + + BEGINfunc + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regcomp(&preq, (char*) rsCStrGetSzStr(pCS1), 0); + ret = regexp.regexec(&preq, (char*) psz, 0, NULL, 0); + regexp.regfree(&preq); + } else { + ret = 1; /* simulate "not found" */ + } + + ENDfunc + return ret; +} + + +/* compare a rsCStr object with a classical sz string. This function + * is almost identical to rsCStrZsStrCmp(), but it also takes an offset + * to the CStr object from where the comparison is to start. + * I have thought quite a while if it really makes sense to more or + * less duplicate the code. After all, if you call it with an offset of + * zero, the functionality is exactly the same. So it looks natural to + * just have a single function. However, supporting the offset requires + * some (few) additional integer operations. While they are few, they + * happen at places in the code that is run very frequently. All in all, + * I have opted for performance and thus duplicated the code. I hope + * this is a good, or at least acceptable, compromise. + * rgerhards, 2005-09-26 + * This function also has an offset-pointer which allows to + * specify *where* the compare operation should begin in + * the CStr. If everything is to be compared, it must be set + * to 0. If some leading bytes are to be skipped, it must be set + * to the first index that is to be compared. It must not be + * set higher than the string length (this is considered a + * program bug and will lead to unpredictable results and program aborts). + * rgerhards 2005-09-26 + */ +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) +{ + BEGINfunc + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(iOffset < pCS1->iStrLen); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if((pCS1->iStrLen - iOffset) == iLenSz) { + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) { + return 0; /* zero-sized strings are equal ;) */ + ENDfunc + } else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i+iOffset] != psz[i]) + return pCS1->pBuf[i+iOffset] - psz[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + ENDfunc + } + } + else { + return pCS1->iStrLen - iOffset - iLenSz; + ENDfunc + } +} + + +/* Converts a string to a number. If the string dos not contain a number, + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber) +{ + DEFiRet; + number_t n; + int bIsNegative; + size_t i; + + ASSERT(pStr != NULL); + ASSERT(pNumber != NULL); + + if(pStr->iStrLen == 0) { + /* can be converted to 0! (by convention) */ + pNumber = 0; + FINALIZE; + } + + /* first skip whitespace (if present) */ + for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) { + /*DO NOTHING*/ + } + + /* we have a string, so let's check its syntax */ + if(pStr->pBuf[i] == '+') { + ++i; /* skip that char */ + bIsNegative = 0; + } else if(pStr->pBuf[0] == '-') { + ++i; /* skip that char */ + bIsNegative = 1; + } else { + bIsNegative = 0; + } + + /* TODO: octal? hex? */ + n = 0; + while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) { + n = n * 10 + pStr->pBuf[i] * 10; + ++i; + } + + if(i < pStr->iStrLen) /* non-digits before end of string? */ + ABORT_FINALIZE(RS_RET_NOT_A_NUMBER); + + if(bIsNegative) + n *= -1; + + /* we got it, so return the number */ + *pNumber = n; + +finalize_it: + RETiRet; +} + + +/* Converts a string to a boolen. First tries to convert to a number. If + * that succeeds, we are done (number is then used as boolean value). If + * that fails, we look if the string is "yes" or "true". If so, a value + * of 1 is returned. In all other cases, a value of 0 is returned. Please + * note that we do not have a specific boolean type, so we return a number. + * so, these are + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToBool(cstr_t *pStr, number_t *pBool) +{ + DEFiRet; + + ASSERT(pStr != NULL); + ASSERT(pBool != NULL); + + iRet = rsCStrConvertToNumber(pStr, pBool); + + if(iRet != RS_RET_NOT_A_NUMBER) { + FINALIZE; /* in any case, we have nothing left to do */ + } + + /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */ + if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) { + *pBool = 1; + } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) { + *pBool = 1; + } else { + *pBool = 0; + } + +finalize_it: + RETiRet; +} + + +/* compare a rsCStr object with a classical sz string. + * Just like rsCStrCStrCmp, just for a different data type. + * There must not only the sz string but also its length be + * provided. If the caller does not know the length he can + * call with + * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz)); + * we are not doing the strlen((char*)) ourselfs as the caller might + * already know the length and in such cases we can save the + * overhead of doing it one more time (strelen() is costly!). + * The bottom line is that the provided length MUST be correct! + * The to sz string pointer must not be NULL! + * rgerhards 2005-09-26 + */ +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen == iLenSz) + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) + return 0; /* zero-sized strings are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i] != psz[i]) + return pCS1->pBuf[i] - psz[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + } + else + return pCS1->iStrLen - iLenSz; +} + + +/* Locate the first occurence of this rsCStr object inside a standard sz string. + * Returns the offset (0-bound) of this first occurrence. If not found, -1 is + * returned. Both parameters MUST be given (NULL is not allowed). + * rgerhards 2005-09-19 + */ +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(*(pComp + iCheck) != *(pThis->pBuf + iCheck)) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +/* This is the same as rsCStrLocateInSzStr(), but does a case-insensitve + * comparison. + * TODO: over time, consolidate the two. + * rgerhards, 2008-02-28 + */ +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(tolower(*(pComp + iCheck)) != tolower(*(pThis->pBuf + iCheck))) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +#if 0 /* read comment below why this is commented out. In short: for future use! */ +/* locate the first occurence of a standard sz string inside a rsCStr object. + * Returns the offset (0-bound) of this first occurrence. If not found, -1 is + * returned. + * rgerhards 2005-09-19 + * WARNING: I accidently created this function (I later noticed I didn't relly + * need it... I will not remove the function, as it probably is useful + * some time later. However, it is not fully tested, so start with testing + * it before you put it to first use). + */ +int rsCStrLocateSzStr(cstr_t *pThis, uchar *sz) +{ + int iLenSz; + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(sz == NULL) + return 0; + + iLenSz = strlen((char*)sz); + if(iLenSz == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = pThis->iStrLen - iLenSz; + + bFound = 0; + i = 0; + while(i < iMax && !bFound) { + int iCheck; + uchar *pComp = pThis->pBuf + i; + for(iCheck = 0 ; iCheck < iLenSz ; ++iCheck) + if(*(pComp + iCheck) != *(sz + iCheck)) + break; + if(iCheck == iLenSz) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} +#endif /* end comment out */ + + +/* our exit function. TODO: remove once converted to a class + * rgerhards, 2008-03-11 + */ +rsRetVal strExit() +{ + DEFiRet; + objRelease(regexp, LM_REGEXP_FILENAME); + RETiRet; +} + + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal strInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + +finalize_it: + RETiRet; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + * vi:set ai: + */ diff --git a/runtime/stringbuf.h b/runtime/stringbuf.h new file mode 100644 index 00000000..c1966449 --- /dev/null +++ b/runtime/stringbuf.h @@ -0,0 +1,169 @@ +/*! \file stringbuf.h + * \brief The counted string object + * + * This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * \date 2005-09-07 + * Initial version begun. + * + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * Copyright 2005 + * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef _STRINGBUF_H_INCLUDED__ +#define _STRINGBUF_H_INCLUDED__ 1 + +/** + * The dynamic string buffer object. + */ +typedef struct cstr_s +{ +#ifndef NDEBUG + rsObjID OID; /**< object ID */ +#endif + uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */ + uchar *pszBuf; /**< pointer to the sz version of the string (after it has been created )*/ + size_t iBufSize; /**< current maximum size of the string buffer */ + size_t iStrLen; /**< length of the string in characters. */ + size_t iAllocIncrement; /**< the amount of bytes the string should be expanded if it needs to */ +} cstr_t; + + +/** + * Construct a rsCStr object. + */ +rsRetVal rsCStrConstruct(cstr_t **ppThis); +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz); +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom); + +/** + * Destruct the string buffer object. + */ +void rsCStrDestruct(cstr_t **ppThis); + +/** + * Append a character to an existing string. If necessary, the + * method expands the string buffer. + * + * \param c Character to append to string. + */ +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c); + +/** + * Truncate "n" number of characters from the end of the + * string. The buffer remains unchanged, just the + * string length is manipulated. This is for performance + * reasons. + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc); + +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis); + +/** + * Append a string to the buffer. For performance reasons, + * use rsCStrAppenStrWithLen() if you know the length. + * + * \param psz pointer to string to be appended. Must not be NULL. + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz); + +/** + * Append a string to the buffer. + * + * \param psz pointer to string to be appended. Must not be NULL. + * \param iStrLen the length of the string pointed to by psz + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen); + +/** + * Set a new allocation incremet. This will influence + * the allocation the next time the string will be expanded. + * It can be set and changed at any time. If done immediately + * after custructing the StrB object, this will also be + * the inital allocation. + * + * \param iNewIncrement The new increment size + * + * \note It is possible to use a very low increment, e.g. 1 byte. + * This can generate a considerable overhead. We highly + * advise not to use an increment below 32 bytes, except + * if you are very well aware why you are doing it ;) + */ +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement); +#define rsCStrGetAllocIncrement(pThis) ((pThis)->iAllocIncrement) + +/** + * Append an integer to the string. No special formatting is + * done. + */ +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i); + + +rsRetVal strExit(void); /* TODO: remove once we have a real object interface! */ +uchar* rsCStrGetSzStr(cstr_t *pThis); +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis); +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew); +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL); +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2); +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz); +int rsCStrLocateSzStr(cstr_t *pCStr, uchar *sz); +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz); +rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); +rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend); + +/* now come inline-like functions */ +#ifdef NDEBUG +# define rsCStrLen(x) ((int)((x)->iStrLen)) +#else + int rsCStrLen(cstr_t *pThis); +#endif + +#if STRINGBUF_TRIM_ALLOCSIZE != 1 +/* This is the normal case (see comment in rsCStrFinish!). In those cases, the function + * simply needs to do nothing, so that we can save us the function call. + * rgerhards, 2008-02-12 + */ +# define rsCStrFinish(pThis) RS_RET_OK +#else + /** + * Finish the string buffer dynamic allocation. + */ + rsRetVal rsCStrFinish(cstr_t *pThis); +#endif + +#define rsCStrGetBufBeg(x) ((x)->pBuf) + +rsRetVal strInit(); +rsRetVal strExit(); + +#endif /* single include */ diff --git a/runtime/sync.c b/runtime/sync.c new file mode 100644 index 00000000..a3053e28 --- /dev/null +++ b/runtime/sync.c @@ -0,0 +1,56 @@ +/* synrchonization-related stuff. In theory, that should + * help porting to something different from pthreads. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" + +#include <stdlib.h> + +#include "rsyslog.h" +#include "sync.h" + + +void +SyncObjInit(pthread_mutex_t **mut) +{ + *mut = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init(*mut, NULL); +} + + +/* This function destroys the mutex and also sets the mutex object + * to NULL. While the later is not strictly necessary, it is a good + * aid when debugging problems. As this function is not exepected to + * be called quite frequently, the additional overhead can well be + * accepted. If this changes over time, setting to NULL may be + * reconsidered. - rgerhards, 2007-11-12 + */ +void +SyncObjExit(pthread_mutex_t **mut) +{ + if(*mut != NULL) { + pthread_mutex_destroy(*mut); + free(*mut); + *mut = NULL; + } +} diff --git a/runtime/sync.h b/runtime/sync.h new file mode 100644 index 00000000..57144fee --- /dev/null +++ b/runtime/sync.h @@ -0,0 +1,50 @@ +/* Definitions syncrhonization-related stuff. In theory, that should + * help porting to something different from pthreads. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_SYNC_H +#define INCLUDED_SYNC_H + +#include <pthread.h> + +/* SYNC_OBJ_TOOL definition must be placed in object to be synced! + * SYNC_OBJ_TOOL_INIT must be called upon of object construction and + * SUNC_OBJ_TOOL_EXIT must be called upon object destruction + */ +#define SYNC_OBJ_TOOL pthread_mutex_t *Sync_mut +#define SYNC_OBJ_TOOL_INIT(x) SyncObjInit(&((x)->Sync_mut)) +#define SYNC_OBJ_TOOL_EXIT(x) SyncObjExit(&((x)->Sync_mut)) + +/* If we run in non-debug (release) mode, we use inline code for the mutex + * operations. If we run in debug mode, we use functions, because they + * are better to trace in the stackframe. + */ +#define LockObj(x) d_pthread_mutex_lock((x)->Sync_mut) +#define UnlockObj(x) d_pthread_mutex_unlock((x)->Sync_mut) + +void SyncObjInit(pthread_mutex_t **mut); +void SyncObjExit(pthread_mutex_t **mut); +extern void lockObj(pthread_mutex_t *mut); +extern void unlockObj(pthread_mutex_t *mut); + +#endif /* #ifndef INCLUDED_SYNC_H */ diff --git a/runtime/syslogd-types.h b/runtime/syslogd-types.h new file mode 100644 index 00000000..be0dfdd8 --- /dev/null +++ b/runtime/syslogd-types.h @@ -0,0 +1,103 @@ +/* syslogd-type.h + * This file contains type defintions used by syslogd and its modules. + * It is a required input for any module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef SYSLOGD_TYPES_INCLUDED +#define SYSLOGD_TYPES_INCLUDED 1 + +#include "stringbuf.h" +#include <sys/param.h> +#if HAVE_SYSLOG_H +#include <syslog.h> +#endif + +#define FALSE 0 +#define TRUE 1 + +#ifdef UT_NAMESIZE +# define UNAMESZ UT_NAMESIZE /* length of a login name */ +#else +# define UNAMESZ 8 /* length of a login name */ +#endif +#define MAXUNAMES 20 /* maximum number of user names */ +#define MAXFNAME 200 /* max file pathname length */ + +#define _DB_MAXDBLEN 128 /* maximum number of db */ +#define _DB_MAXUNAMELEN 128 /* maximum number of user name */ +#define _DB_MAXPWDLEN 128 /* maximum number of user's pass */ +#define _DB_DELAYTIMEONERROR 20 /* If an error occur we stop logging until + a delayed time is over */ + + +/* we define features of the syslog code. This features can be used + * to check if modules are compatible with them - and possible other + * applications I do not yet envision. -- rgerhards, 2007-07-24 + */ +typedef enum _syslogFeature { + sFEATURERepeatedMsgReduction = 1 +} syslogFeature; + +/* we define our own facility and severities */ +/* facility and severity codes */ +typedef struct _syslogCode { + char *c_name; + int c_val; +} syslogCODE; + +/* values for host comparisons specified with host selector blocks + * (+host, -host). rgerhards 2005-10-18. + */ +enum _EHostnameCmpMode { + HN_NO_COMP = 0, /* do not compare hostname */ + HN_COMP_MATCH = 1, /* hostname must match */ + HN_COMP_NOMATCH = 2 /* hostname must NOT match */ +}; +typedef enum _EHostnameCmpMode EHostnameCmpMode; + +/* rgerhards 2004-11-11: the following structure represents + * a time as it is used in syslog. + */ +struct syslogTime { + int timeType; /* 0 - unitinialized , 1 - RFC 3164, 2 - syslog-protocol */ + int year; + int month; + int day; + int hour; /* 24 hour clock */ + int minute; + int second; + int secfrac; /* fractional seconds (must be 32 bit!) */ + int secfracPrecision; + char OffsetMode; /* UTC offset + or - */ + char OffsetHour; /* UTC offset in hours */ + int OffsetMinute; /* UTC offset in minutes */ + /* full UTC offset minutes = OffsetHours*60 + OffsetMinute. Then use + * OffsetMode to know the direction. + */ +}; +typedef struct syslogTime syslogTime_t; + +#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */ +/* vi:set ai: + */ diff --git a/runtime/sysvar.c b/runtime/sysvar.c new file mode 100644 index 00000000..5eec8f67 --- /dev/null +++ b/runtime/sysvar.c @@ -0,0 +1,202 @@ +/* sysvar.c - imlements rsyslog system variables + * + * At least for now, this class only has static functions and no + * instances. + * + * Module begun 2008-02-25 by Rainer Gerhards + * + * Copyright (C) 2008 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "stringbuf.h" +#include "sysvar.h" +#include "datetime.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) +DEFobjCurrIf(datetime) + + +/* Standard-Constructor + */ +BEGINobjConstruct(sysvar) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(sysvar) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +sysvarConstructFinalize(sysvar_t __attribute__((unused)) *pThis) +{ + DEFiRet; + RETiRet; +} + + +/* destructor for the sysvar object */ +BEGINobjDestruct(sysvar) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(sysvar) +ENDobjDestruct(sysvar) + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + * TODO: this was taken from msg.c and we should consolidate it with the code + * there. This is especially important when we increase the number of system + * variables (what we definitely want to do). + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_MINUTE } eNOWType; +static rsRetVal +getNOW(eNOWType eNow, cstr_t **ppStr) +{ + DEFiRet; + uchar szBuf[16]; + struct syslogTime t; + + datetime.getCurrTime(&t); + switch(eNow) { + case NOW_NOW: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d", t.year); + break; + case NOW_MONTH: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.month); + break; + case NOW_DAY: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.day); + break; + case NOW_HOUR: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.hour); + break; + case NOW_MINUTE: + snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.minute); + break; + } + + /* now create a string object out of it and hand that over to the var */ + CHKiRet(rsCStrConstructFromszStr(ppStr, szBuf)); + +finalize_it: + RETiRet; +} + + +/* The function returns a system variable suitable for use with RainerScript. Most importantly, this means + * that the value is returned in a var_t object. The var_t is constructed inside this function and + * MUST be freed by the caller. + * rgerhards, 2008-02-25 + */ +static rsRetVal +GetVar(cstr_t *pstrVarName, var_t **ppVar) +{ + DEFiRet; + var_t *pVar; + cstr_t *pstrProp; + + ASSERT(pstrVarName != NULL); + ASSERT(ppVar != NULL); + + /* make sure we have a var_t instance */ + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + /* now begin the actual variable evaluation */ + if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"now", sizeof("now") - 1)) { + CHKiRet(getNOW(NOW_NOW, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"year", sizeof("year") - 1)) { + CHKiRet(getNOW(NOW_YEAR, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"month", sizeof("month") - 1)) { + CHKiRet(getNOW(NOW_MONTH, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"day", sizeof("day") - 1)) { + CHKiRet(getNOW(NOW_DAY, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"hour", sizeof("hour") - 1)) { + CHKiRet(getNOW(NOW_HOUR, &pstrProp)); + } else if(!rsCStrSzStrCmp(pstrVarName, (uchar*)"minute", sizeof("minute") - 1)) { + CHKiRet(getNOW(NOW_MINUTE, &pstrProp)); + } else { + ABORT_FINALIZE(RS_RET_SYSVAR_NOT_FOUND); + } + + /* now hand the string over to the var object */ + CHKiRet(var.SetString(pVar, pstrProp)); + + /* finally store var */ + *ppVar = pVar; + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(sysvar) +CODESTARTobjQueryInterface(sysvar) + if(pIf->ifVersion != sysvarCURR_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). + */ + //xxxpIf->oID = "sysvar";//OBJsysvar; + + pIf->Construct = sysvarConstruct; + pIf->ConstructFinalize = sysvarConstructFinalize; + pIf->Destruct = sysvarDestruct; + pIf->GetVar = GetVar; +finalize_it: +ENDobjQueryInterface(sysvar) + + +/* Initialize the sysvar class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(sysvar, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, sysvarConstructFinalize); +ENDObjClassInit(sysvar) + +/* vi:set ai: + */ diff --git a/runtime/sysvar.h b/runtime/sysvar.h new file mode 100644 index 00000000..35051b64 --- /dev/null +++ b/runtime/sysvar.h @@ -0,0 +1,47 @@ +/* The sysvar object. So far, no instance can be defined (makes logically no + * sense). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_SYSVAR_H +#define INCLUDED_SYSVAR_H + +/* the sysvar object - not really used... */ +typedef struct sysvar_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ +} sysvar_t; + + +/* interfaces */ +BEGINinterface(sysvar) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(sysvar); + rsRetVal (*Construct)(sysvar_t **ppThis); + rsRetVal (*ConstructFinalize)(sysvar_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(sysvar_t **ppThis); + rsRetVal (*GetVar)(cstr_t *pstrPropName, var_t **ppVar); +ENDinterface(sysvar) +#define sysvarCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(sysvar); + +#endif /* #ifndef INCLUDED_SYSVAR_H */ diff --git a/runtime/var.c b/runtime/var.c new file mode 100644 index 00000000..7e51fc6d --- /dev/null +++ b/runtime/var.c @@ -0,0 +1,414 @@ +/* var.c - a typeless variable class + * + * This class is used to represent variable values, which may have any type. + * Among others, it will be used inside rsyslog's expression system, but + * also internally at any place where a typeless variable is needed. + * + * Module begun 2008-02-20 by Rainer Gerhards, with some code taken + * from the obj.c/.h files. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "srUtils.h" +#include "var.h" + +/* static data */ +DEFobjStaticHelpers + + +/* Standard-Constructor + */ +BEGINobjConstruct(var) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(var) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal varConstructFinalize(var_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + RETiRet; +} + + +/* destructor for the var object */ +BEGINobjDestruct(var) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(var) + if(pThis->pcsName != NULL) + rsCStrDestruct(&pThis->pcsName); + if(pThis->varType == VARTYPE_STR) { + if(pThis->val.pStr != NULL) + rsCStrDestruct(&pThis->val.pStr); + } +ENDobjDestruct(var) + + +/* DebugPrint support for the var object */ +BEGINobjDebugPrint(var) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(var) + switch(pThis->varType) { + case VARTYPE_STR: + dbgoprint((obj_t*) pThis, "type: cstr, val '%s'\n", rsCStrGetSzStr(pThis->val.pStr)); + break; + case VARTYPE_NUMBER: + dbgoprint((obj_t*) pThis, "type: number, val %lld\n", pThis->val.num); + break; + default: + dbgoprint((obj_t*) pThis, "type %d currently not suppored in debug output\n", pThis->varType); + break; + } +ENDobjDebugPrint(var) + + +/* duplicates a var instance + * rgerhards, 2008-02-25 + */ +static rsRetVal +Duplicate(var_t *pThis, var_t **ppNew) +{ + DEFiRet; + var_t *pNew = NULL; + cstr_t *pstr; + + ISOBJ_TYPE_assert(pThis, var); + assert(ppNew != NULL); + + CHKiRet(varConstruct(&pNew)); + CHKiRet(varConstructFinalize(pNew)); + + /* we have the object, now copy value */ + pNew->varType = pThis->varType; + if(pThis->varType == VARTYPE_NUMBER) { + pNew->val.num = pThis->val.num; + } else if(pThis->varType == VARTYPE_STR) { + CHKiRet(rsCStrConstructFromCStr(&pstr, pThis->val.pStr)); + pNew->val.pStr = pstr; + } + + *ppNew = pNew; + +finalize_it: + if(iRet != RS_RET_OK && pNew != NULL) + varDestruct(&pNew); + + RETiRet; +} + + +/* free the current values (destructs objects if necessary) + */ +static rsRetVal +varUnsetValues(var_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + if(pThis->varType == VARTYPE_STR) + rsCStrDestruct(&pThis->val.pStr); + + pThis->varType = VARTYPE_NONE; + + RETiRet; +} + + +/* set a string value + * The caller hands over the string and must n longer use it after this method + * has been called. + */ +static rsRetVal +varSetString(var_t *pThis, cstr_t *pStr) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + CHKiRet(varUnsetValues(pThis)); + pThis->varType = VARTYPE_STR; + pThis->val.pStr = pStr; + +finalize_it: + RETiRet; +} + + +/* set an int64 value */ +static rsRetVal +varSetNumber(var_t *pThis, number_t iVal) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, var); + + CHKiRet(varUnsetValues(pThis)); + pThis->varType = VARTYPE_NUMBER; + pThis->val.num = iVal; + +finalize_it: + RETiRet; +} + + +/* Change the provided object to be of type number. + * rgerhards, 2008-02-22 + */ +rsRetVal +ConvToNumber(var_t *pThis) +{ + DEFiRet; + number_t n; + + if(pThis->varType == VARTYPE_NUMBER) { + FINALIZE; + } else if(pThis->varType == VARTYPE_STR) { + iRet = rsCStrConvertToNumber(pThis->val.pStr, &n); + if(iRet == RS_RET_NOT_A_NUMBER) { + n = 0; + iRet = RS_RET_OK; /* we accept this as part of the language definition */ + } else if (iRet != RS_RET_OK) { + FINALIZE; + } + + /* we need to destruct the string first, because string and number are + * inside a union and share the memory area! -- rgerhards, 2008-04-03 + */ + rsCStrDestruct(&pThis->val.pStr); + + pThis->val.num = n; + pThis->varType = VARTYPE_NUMBER; + } + +finalize_it: + RETiRet; +} + + +/* convert the provided var to type string. This is always possible + * (except, of course, for things like out of memory...) + * TODO: finish implementation!!!!!!!!! + * rgerhards, 2008-02-24 + */ +rsRetVal +ConvToString(var_t *pThis) +{ + DEFiRet; + uchar szNumBuf[64]; + + if(pThis->varType == VARTYPE_STR) { + FINALIZE; + } else if(pThis->varType == VARTYPE_NUMBER) { + CHKiRet(srUtilItoA((char*)szNumBuf, sizeof(szNumBuf)/sizeof(uchar), pThis->val.num)); + CHKiRet(rsCStrConstructFromszStr(&pThis->val.pStr, szNumBuf)); + pThis->varType = VARTYPE_STR; + } + +finalize_it: + RETiRet; +} + + +/* convert (if necessary) the value to a boolean. In essence, this means the + * value must be a number, but in case of a string special logic is used as + * some string-values may represent a boolean (e.g. "true"). + * rgerhards, 2008-02-25 + */ +rsRetVal +ConvToBool(var_t *pThis) +{ + DEFiRet; + number_t n; + + if(pThis->varType == VARTYPE_NUMBER) { + FINALIZE; + } else if(pThis->varType == VARTYPE_STR) { + iRet = rsCStrConvertToBool(pThis->val.pStr, &n); + if(iRet == RS_RET_NOT_A_NUMBER) { + n = 0; + iRet = RS_RET_OK; /* we accept this as part of the language definition */ + } else if (iRet != RS_RET_OK) { + FINALIZE; + } + + /* we need to destruct the string first, because string and number are + * inside a union and share the memory area! -- rgerhards, 2008-04-03 + */ + rsCStrDestruct(&pThis->val.pStr); + pThis->val.num = n; + pThis->varType = VARTYPE_NUMBER; + } + +finalize_it: + RETiRet; +} + + +/* This function is used to prepare two var_t objects for a common operation, + * e.g before they are added, compared. The function looks at + * the data types of both operands and finds the best data type suitable for + * the operation (in respect to current types). Then, it converts those + * operands that need conversion. Please note that the passed-in var objects + * *are* modified and returned as new type. So do call this function only if + * you actually need the conversion. + * + * This is how the common data type is selected. Note that op1 and op2 are + * just the two operands, their order is irrelevant (this would just take up + * more table space - so string/number is the same thing as number/string). + * + * Common Types: + * op1 op2 operation data type + * string string string + * string number number if op1 can be converted to number, string else + * date string date if op1 can be converted to date, string else + * number number number + * date number string (maybe we can do better?) + * date date date + * none n/a error + * + * If a boolean value is required, we need to have a number inside the + * operand. If it is not, conversion rules to number apply. Once we + * have a number, things get easy: 0 is false, anything else is true. + * Please note that due to this conversion rules, "0" becomes false + * while "-4712" becomes true. Using a date as boolen is not a good + * idea. Depending on the ultimate conversion rules, it may always + * become true or false. As such, using dates as booleans is + * prohibited and the result defined to be undefined. + * + * rgerhards, 2008-02-22 + */ +static rsRetVal +ConvForOperation(var_t *pThis, var_t *pOther) +{ + DEFiRet; + + if(pThis->varType == VARTYPE_NONE || pOther->varType == VARTYPE_NONE) + ABORT_FINALIZE(RS_RET_INVALID_VAR); + + switch(pThis->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + switch(pOther->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + FINALIZE; /* two strings, we are all set */ + break; + case VARTYPE_NUMBER: + /* check if we can convert pThis to a number, if so use number format. */ + iRet = ConvToNumber(pThis); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pOther)); + } else { + FINALIZE; /* OK or error */ + } + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + break; + case VARTYPE_NUMBER: + switch(pOther->varType) { + case VARTYPE_NONE: + ABORT_FINALIZE(RS_RET_INVALID_VAR); + break; + case VARTYPE_STR: + iRet = ConvToNumber(pOther); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pThis)); + } else { + FINALIZE; /* OK or error */ + } + break; + case VARTYPE_NUMBER: + FINALIZE; /* two numbers, so we are all set */ + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + break; + case VARTYPE_SYSLOGTIME: + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + break; + } + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(var) +CODESTARTobjQueryInterface(var) + if(pIf->ifVersion != varCURR_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->Construct = varConstruct; + pIf->ConstructFinalize = varConstructFinalize; + pIf->Destruct = varDestruct; + pIf->DebugPrint = varDebugPrint; + pIf->SetNumber = varSetNumber; + pIf->SetString = varSetString; + pIf->ConvForOperation = ConvForOperation; + pIf->ConvToNumber = ConvToNumber; + pIf->ConvToBool = ConvToBool; + pIf->ConvToString = ConvToString; + pIf->Duplicate = Duplicate; +finalize_it: +ENDobjQueryInterface(var) + + +/* Initialize the var class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(var, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, varDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, varConstructFinalize); +ENDObjClassInit(var) + +/* vi:set ai: + */ diff --git a/runtime/var.h b/runtime/var.h new file mode 100644 index 00000000..bbe7ba33 --- /dev/null +++ b/runtime/var.h @@ -0,0 +1,70 @@ +/* The var object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_VAR_H +#define INCLUDED_VAR_H + +#include "stringbuf.h" + +/* data types */ +typedef enum { + VARTYPE_NONE = 0, /* currently no value set */ + VARTYPE_STR = 1, + VARTYPE_NUMBER = 2, + VARTYPE_SYSLOGTIME = 3 +} varType_t; + +/* the var object */ +typedef struct var_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + cstr_t *pcsName; + varType_t varType; + union { + number_t num; + cstr_t *pStr; + syslogTime_t vSyslogTime; + + } val; +} var_t; + + +/* interfaces */ +BEGINinterface(var) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(var); + rsRetVal (*Construct)(var_t **ppThis); + rsRetVal (*ConstructFinalize)(var_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(var_t **ppThis); + rsRetVal (*SetNumber)(var_t *pThis, number_t iVal); + rsRetVal (*SetString)(var_t *pThis, cstr_t *pCStr); + rsRetVal (*ConvForOperation)(var_t *pThis, var_t *pOther); + rsRetVal (*ConvToNumber)(var_t *pThis); + rsRetVal (*ConvToBool)(var_t *pThis); + rsRetVal (*ConvToString)(var_t *pThis); + rsRetVal (*Duplicate)(var_t *pThis, var_t **ppNew); +ENDinterface(var) +#define varCURR_IF_VERSION 1 /* increment whenever you change the interface above! */ + + +/* prototypes */ +PROTOTYPEObj(var); + +#endif /* #ifndef INCLUDED_VAR_H */ diff --git a/runtime/vm.c b/runtime/vm.c new file mode 100644 index 00000000..bc6c3dd2 --- /dev/null +++ b/runtime/vm.c @@ -0,0 +1,525 @@ +/* vm.c - the arithmetic stack of a virtual machine. + * + * Module begun 2008-02-22 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vm.h" +#include "sysvar.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmstk) +DEFobjCurrIf(var) +DEFobjCurrIf(sysvar) + + +/* ------------------------------ instruction set implementation ------------------------------ * + * The following functions implement the VM's instruction set. + */ +#define BEGINop(instruction) \ + static rsRetVal op##instruction(vm_t *pThis, __attribute__((unused)) vmop_t *pOp) \ + { \ + DEFiRet; + +#define CODESTARTop(instruction) \ + ISOBJ_TYPE_assert(pThis, vm); + +#define PUSHRESULTop(operand, res) \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand, res); \ + vmstk.Push(pThis->pStk, operand); /* result */ + +#define ENDop(instruction) \ + RETiRet; \ + } + +/* code generator for boolean operations */ +#define BOOLOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopBool(pThis->pStk, &operand1); \ + vmstk.PopBool(pThis->pStk, &operand2); \ + if(operand1->val.num OPERATION operand2->val.num) { \ + CHKiRet(var.SetNumber(operand1, 1)); \ + } else { \ + CHKiRet(var.SetNumber(operand1, 0)); \ + } \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) +BOOLOP(OR, ||) +BOOLOP(AND, &&) +#undef BOOLOP + + +/* code generator for numerical operations */ +#define NUMOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopNumber(pThis->pStk, &operand1); \ + vmstk.PopNumber(pThis->pStk, &operand2); \ + operand1->val.num = operand1->val.num OPERATION operand2->val.num; \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +ENDop(name) +NUMOP(PLUS, +) +NUMOP(MINUS, -) +NUMOP(TIMES, *) +NUMOP(DIV, /) +NUMOP(MOD, %) +#undef BOOLOP + + +/* code generator for compare operations */ +#define BEGINCMPOP(name) \ +BEGINop(name) \ + var_t *operand1; \ + var_t *operand2; \ + number_t bRes; \ +CODESTARTop(name) \ + CHKiRet(vmstk.Pop2CommOp(pThis->pStk, &operand1, &operand2)); \ + /* data types are equal (so we look only at operand1), but we must \ + * check which type we have to deal with... \ + */ \ + switch(operand1->varType) { +#define ENDCMPOP(name) \ + default: \ + bRes = 0; /* we do not abort just so that we have a value. TODO: reconsider */ \ + break; \ + } \ + \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand1, bRes); \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) + +BEGINCMPOP(CMP_EQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num == operand2->val.num; + break; + case VARTYPE_STR: + bRes = !rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_NEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num != operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_LT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num < operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) < 0; + break; +ENDCMPOP(CMP_LT) + +BEGINCMPOP(CMP_GT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num > operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) > 0; + break; +ENDCMPOP(CMP_GT) + +BEGINCMPOP(CMP_LTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num <= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) <= 0; + break; +ENDCMPOP(CMP_LTEQ) + +BEGINCMPOP(CMP_GTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num >= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) >= 0; + break; +ENDCMPOP(CMP_GTEQ) + +#undef BEGINCMPOP +#undef ENDCMPOP +/* end regular compare operations */ + +/* comare operations that work on strings, only */ +BEGINop(CMP_CONTAINS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINS) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINS) + + +BEGINop(CMP_CONTAINSI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINSI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitiveLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINSI) + + +BEGINop(CMP_STARTSWITH) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITH) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITH) + + +BEGINop(CMP_STARTSWITHI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITHI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitveStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITHI) + +/* end comare operations that work on strings, only */ + +BEGINop(STRADD) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; +CODESTARTop(STRADD) + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + + CHKiRet(rsCStrAppendCStr(operand1->val.pStr, operand2->val.pStr)); + + /* we have a result, so let's push it */ + vmstk.Push(pThis->pStk, operand1); + var.Destruct(&operand2); /* no longer needed */ +finalize_it: +ENDop(STRADD) + +BEGINop(NOT) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(NOT) + vmstk.PopBool(pThis->pStk, &operand); + PUSHRESULTop(operand, !operand->val.num); +ENDop(NOT) + +BEGINop(UNARY_MINUS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(UNARY_MINUS) + vmstk.PopNumber(pThis->pStk, &operand); + PUSHRESULTop(operand, -operand->val.num); +ENDop(UNARY_MINUS) + + +BEGINop(PUSHCONSTANT) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVarDup; /* we need to duplicate the var, as we need to hand it over */ +CODESTARTop(PUSHCONSTANT) + CHKiRet(var.Duplicate(pOp->operand.pVar, &pVarDup)); + vmstk.Push(pThis->pStk, pVarDup); +finalize_it: +ENDop(PUSHCONSTANT) + + +BEGINop(PUSHMSGVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ + cstr_t *pstrVal; +CODESTARTop(PUSHMSGVAR) + if(pThis->pMsg == NULL) { + /* TODO: flag an error message! As a work-around, we permit + * execution to continue here with an empty string + */ + /* TODO: create a method in var to create a string var? */ + CHKiRet(var.Construct(&pVal)); + CHKiRet(var.ConstructFinalize(pVal)); + CHKiRet(rsCStrConstructFromszStr(&pstrVal, (uchar*)"")); + CHKiRet(var.SetString(pVal, pstrVal)); + } else { + /* we have a message, so pull value from there */ + CHKiRet(msgGetMsgVar(pThis->pMsg, pOp->operand.pVar->val.pStr, &pVal)); + } + + /* if we reach this point, we have a valid pVal and can push it */ + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHMSGVAR) + + +BEGINop(PUSHSYSVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ +CODESTARTop(PUSHSYSVAR) + CHKiRet(sysvar.GetVar(pOp->operand.pVar->val.pStr, &pVal)); + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHSYSVAR) + + +/* ------------------------------ end instruction set implementation ------------------------------ */ + + +/* Standard-Constructor + */ +BEGINobjConstruct(vm) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmConstructFinalize(vm_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vm); + + CHKiRet(vmstk.Construct(&pThis->pStk)); + CHKiRet(vmstk.ConstructFinalize(pThis->pStk)); + +finalize_it: + RETiRet; +} + + +/* destructor for the vm object */ +BEGINobjDestruct(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vm) + if(pThis->pStk != NULL) + vmstk.Destruct(&pThis->pStk); + if(pThis->pMsg != NULL) + msgDestruct(&pThis->pMsg); +ENDobjDestruct(vm) + + +/* debugprint for the vm object */ +BEGINobjDebugPrint(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(vm) + dbgoprint((obj_t*) pThis, "rsyslog virtual machine, currently no state info available\n"); +ENDobjDebugPrint(vm) + + +/* execute a program + */ +static rsRetVal +execProg(vm_t *pThis, vmprg_t *pProg) +{ + DEFiRet; + vmop_t *pCurrOp; /* virtual instruction pointer */ + + ISOBJ_TYPE_assert(pThis, vm); + ISOBJ_TYPE_assert(pProg, vmprg); + +#define doOP(OP) case opcode_##OP: CHKiRet(op##OP(pThis, pCurrOp)); break + pCurrOp = pProg->vmopRoot; /* TODO: do this via a method! */ + while(pCurrOp != NULL && pCurrOp->opcode != opcode_END_PROG) { + switch(pCurrOp->opcode) { + doOP(OR); + doOP(AND); + doOP(CMP_EQ); + doOP(CMP_NEQ); + doOP(CMP_LT); + doOP(CMP_GT); + doOP(CMP_LTEQ); + doOP(CMP_GTEQ); + doOP(CMP_CONTAINS); + doOP(CMP_CONTAINSI); + doOP(CMP_STARTSWITH); + doOP(CMP_STARTSWITHI); + doOP(NOT); + doOP(PUSHCONSTANT); + doOP(PUSHMSGVAR); + doOP(PUSHSYSVAR); + doOP(STRADD); + doOP(PLUS); + doOP(MINUS); + doOP(TIMES); + doOP(DIV); + doOP(MOD); + doOP(UNARY_MINUS); + default: + ABORT_FINALIZE(RS_RET_INVALID_VMOP); + dbgoprint((obj_t*) pThis, "invalid instruction %d in vmprg\n", pCurrOp->opcode); + break; + } + /* so far, we have plain sequential execution, so on to next... */ + pCurrOp = pCurrOp->pNext; + } +#undef doOP + + /* if we reach this point, our program has intintionally terminated + * (no error state). + */ + +finalize_it: + RETiRet; +} + + +/* Set the current message object for the VM. It *is* valid to set a + * NULL message object, what simply means there is none. Message + * objects are properly reference counted. + */ +static rsRetVal +SetMsg(vm_t *pThis, msg_t *pMsg) +{ + DEFiRet; + if(pThis->pMsg != NULL) { + msgDestruct(&pThis->pMsg); + } + + if(pMsg != NULL) { + pThis->pMsg = MsgAddRef(pMsg); + } + + RETiRet; +} + + +/* Pop a var from the stack and return it to caller. The variable type is not + * changed, it is taken from the stack as is. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopVarFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.Pop(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* Pop a boolean from the stack and return it to caller. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopBoolFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.PopBool(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vm) +CODESTARTobjQueryInterface(vm) + if(pIf->ifVersion != vmCURR_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->Construct = vmConstruct; + pIf->ConstructFinalize = vmConstructFinalize; + pIf->Destruct = vmDestruct; + pIf->DebugPrint = vmDebugPrint; + pIf->ExecProg = execProg; + pIf->PopBoolFromStack = PopBoolFromStack; + pIf->PopVarFromStack = PopVarFromStack; + pIf->SetMsg = SetMsg; +finalize_it: +ENDobjQueryInterface(vm) + + +/* Initialize the vm class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vm, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmstk, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(sysvar, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmConstructFinalize); +ENDObjClassInit(vm) + +/* vi:set ai: + */ diff --git a/runtime/vm.h b/runtime/vm.h new file mode 100644 index 00000000..d2458220 --- /dev/null +++ b/runtime/vm.h @@ -0,0 +1,65 @@ +/* The vm object. + * + * This implements the rsyslog virtual machine. The initial implementation is + * done to support complex user-defined expressions, but it may evolve into a + * much more useful thing over time. + * + * The virtual machine uses rsyslog variables as its memory storage system. + * All computation is done on a stack (vmstk). The vm supports a given + * instruction set and executes programs of type vmprg, which consist of + * single operations defined in vmop (which hold the instruction and the + * data). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_VM_H +#define INCLUDED_VM_H + +#include "msg.h" +#include "vmstk.h" +#include "vmprg.h" + +/* the vm object */ +typedef struct vm_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmstk_t *pStk; /* The stack */ + msg_t *pMsg; /* the current message (or NULL, if we have none) */ +} vm_t; + + +/* interfaces */ +BEGINinterface(vm) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vm); + rsRetVal (*Construct)(vm_t **ppThis); + rsRetVal (*ConstructFinalize)(vm_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vm_t **ppThis); + rsRetVal (*ExecProg)(vm_t *pThis, vmprg_t *pProg); + rsRetVal (*PopBoolFromStack)(vm_t *pThis, var_t **ppVar); /* there are a few cases where we need this... */ + rsRetVal (*PopVarFromStack)(vm_t *pThis, var_t **ppVar); /* there are a few cases where we need this... */ + rsRetVal (*SetMsg)(vm_t *pThis, msg_t *pMsg); /* there are a few cases where we need this... */ +ENDinterface(vm) +#define vmCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vm); + +#endif /* #ifndef INCLUDED_VM_H */ diff --git a/runtime/vmop.c b/runtime/vmop.c new file mode 100644 index 00000000..219315c4 --- /dev/null +++ b/runtime/vmop.c @@ -0,0 +1,235 @@ +/* vmop.c - abstracts an operation (instructed) supported by the + * rsyslog virtual machine + * + * Module begun 2008-02-20 by Rainer Gerhards + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmop.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* forward definitions */ +static rsRetVal vmopOpcode2Str(vmop_t *pThis, uchar **ppName); + +/* Standard-Constructor + */ +BEGINobjConstruct(vmop) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmop) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +rsRetVal vmopConstructFinalize(vmop_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + RETiRet; +} + + +/* destructor for the vmop object */ +BEGINobjDestruct(vmop) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vmop) + if( pThis->opcode == opcode_PUSHSYSVAR + || pThis->opcode == opcode_PUSHMSGVAR + || pThis->opcode == opcode_PUSHCONSTANT) { + if(pThis->operand.pVar != NULL) + var.Destruct(&pThis->operand.pVar); + } +ENDobjDestruct(vmop) + + +/* DebugPrint support for the vmop object */ +BEGINobjDebugPrint(vmop) /* be sure to specify the object type also in END and CODESTART macros! */ + uchar *pOpcodeName; +CODESTARTobjDebugPrint(vmop) + vmopOpcode2Str(pThis, &pOpcodeName); + dbgoprint((obj_t*) pThis, "opcode: %d\t(%s), next %p, var in next line\n", (int) pThis->opcode, pOpcodeName, + pThis->pNext); + if(pThis->operand.pVar != NULL) + var.DebugPrint(pThis->operand.pVar); +ENDobjDebugPrint(vmop) + + +/* set operand (variant case) + * rgerhards, 2008-02-20 + */ +static rsRetVal +vmopSetVar(vmop_t *pThis, var_t *pVar) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + ISOBJ_TYPE_assert(pVar, var); + pThis->operand.pVar = pVar; + RETiRet; +} + + +/* set operation + * rgerhards, 2008-02-20 + */ +static rsRetVal +vmopSetOpcode(vmop_t *pThis, opcode_t opcode) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + pThis->opcode = opcode; + RETiRet; +} + + +/* a way to turn an opcode into a readable string + */ +static rsRetVal +vmopOpcode2Str(vmop_t *pThis, uchar **ppName) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmop); + + switch(pThis->opcode) { + case opcode_OR: + *ppName = (uchar*) "or"; + break; + case opcode_AND: + *ppName = (uchar*) "and"; + break; + case opcode_PLUS: + *ppName = (uchar*) "+"; + break; + case opcode_MINUS: + *ppName = (uchar*) "-"; + break; + case opcode_TIMES: + *ppName = (uchar*) "*"; + break; + case opcode_DIV: + *ppName = (uchar*) "/"; + break; + case opcode_MOD: + *ppName = (uchar*) "%"; + break; + case opcode_NOT: + *ppName = (uchar*) "not"; + break; + case opcode_CMP_EQ: + *ppName = (uchar*) "=="; + break; + case opcode_CMP_NEQ: + *ppName = (uchar*) "!="; + break; + case opcode_CMP_LT: + *ppName = (uchar*) "<"; + break; + case opcode_CMP_GT: + *ppName = (uchar*) ">"; + break; + case opcode_CMP_LTEQ: + *ppName = (uchar*) "<="; + break; + case opcode_CMP_CONTAINS: + *ppName = (uchar*) "contains"; + break; + case opcode_CMP_STARTSWITH: + *ppName = (uchar*) "startswith"; + break; + case opcode_CMP_GTEQ: + *ppName = (uchar*) ">="; + break; + case opcode_PUSHSYSVAR: + *ppName = (uchar*) "PUSHSYSVAR"; + break; + case opcode_PUSHMSGVAR: + *ppName = (uchar*) "PUSHMSGVAR"; + break; + case opcode_PUSHCONSTANT: + *ppName = (uchar*) "PUSHCONSTANT"; + break; + case opcode_POP: + *ppName = (uchar*) "POP"; + break; + case opcode_UNARY_MINUS: + *ppName = (uchar*) "UNARY_MINUS"; + break; + case opcode_STRADD: + *ppName = (uchar*) "STRADD"; + break; + default: + *ppName = (uchar*) "INVALID opcode"; + break; + } + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmop) +CODESTARTobjQueryInterface(vmop) + if(pIf->ifVersion != vmopCURR_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). + */ + //xxxpIf->oID = OBJvmop; + + pIf->Construct = vmopConstruct; + pIf->ConstructFinalize = vmopConstructFinalize; + pIf->Destruct = vmopDestruct; + pIf->DebugPrint = vmopDebugPrint; + pIf->SetOpcode = vmopSetOpcode; + pIf->SetVar = vmopSetVar; + pIf->Opcode2Str = vmopOpcode2Str; +finalize_it: +ENDobjQueryInterface(vmop) + + +/* Initialize the vmop class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmop, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmopDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmopConstructFinalize); +ENDObjClassInit(vmop) + +/* vi:set ai: + */ diff --git a/runtime/vmop.h b/runtime/vmop.h new file mode 100644 index 00000000..97f924d7 --- /dev/null +++ b/runtime/vmop.h @@ -0,0 +1,92 @@ +/* The vmop object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_VMOP_H +#define INCLUDED_VMOP_H + +#include "ctok_token.h" + +/* machine instructions types */ +typedef enum { /* do NOT start at 0 to detect uninitialized types after calloc() */ + opcode_INVALID = 0, + /* for simplicity of debugging and reading dumps, we use the same IDs + * that the tokenizer uses where this applicable. + */ + opcode_OR = ctok_OR, + opcode_AND = ctok_AND, + opcode_STRADD= ctok_STRADD, + opcode_PLUS = ctok_PLUS, + opcode_MINUS = ctok_MINUS, + opcode_TIMES = ctok_TIMES, /* "*" */ + opcode_DIV = ctok_DIV, + opcode_MOD = ctok_MOD, + opcode_NOT = ctok_NOT, + opcode_CMP_EQ = ctok_CMP_EQ, /* all compare operations must be in a row */ + opcode_CMP_NEQ = ctok_CMP_NEQ, + opcode_CMP_LT = ctok_CMP_LT, + opcode_CMP_GT = ctok_CMP_GT, + opcode_CMP_LTEQ = ctok_CMP_LTEQ, + opcode_CMP_CONTAINS = ctok_CMP_CONTAINS, + opcode_CMP_STARTSWITH = ctok_CMP_STARTSWITH, + opcode_CMP_CONTAINSI = ctok_CMP_CONTAINSI, + opcode_CMP_STARTSWITHI = ctok_CMP_STARTSWITHI, + opcode_CMP_GTEQ = ctok_CMP_GTEQ, /* end compare operations */ + /* here we start our own codes */ + opcode_POP = 1000, /* requires var operand to receive result */ + opcode_PUSHSYSVAR = 1001, /* requires var operand */ + opcode_PUSHMSGVAR = 1002, /* requires var operand */ + opcode_PUSHCONSTANT = 1003, /* requires var operand */ + opcode_UNARY_MINUS = 1010, + opcode_END_PROG = 1011 +} opcode_t; + + +/* the vmop object */ +typedef struct vmop_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + opcode_t opcode; + union { + var_t *pVar; + /* TODO: add function pointer */ + } operand; + struct vmop_s *pNext; /* next operation or NULL, if end of program (logically this belongs to vmprg) */ +} vmop_t; + + +/* interfaces */ +BEGINinterface(vmop) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmop); + rsRetVal (*Construct)(vmop_t **ppThis); + rsRetVal (*ConstructFinalize)(vmop_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmop_t **ppThis); + rsRetVal (*SetOpcode)(vmop_t *pThis, opcode_t opcode); + rsRetVal (*SetVar)(vmop_t *pThis, var_t *pVar); + rsRetVal (*Opcode2Str)(vmop_t *pThis, uchar **ppName); +ENDinterface(vmop) +#define vmopCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* the remaining prototypes */ +PROTOTYPEObj(vmop); + +#endif /* #ifndef INCLUDED_VMOP_H */ diff --git a/runtime/vmprg.c b/runtime/vmprg.c new file mode 100644 index 00000000..a2b744d7 --- /dev/null +++ b/runtime/vmprg.c @@ -0,0 +1,175 @@ +/* vmprg.c - abstracts a program (bytecode) for the rsyslog virtual machine + * + * Module begun 2008-02-20 by Rainer Gerhards + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmprg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmop) + + +/* Standard-Constructor + */ +BEGINobjConstruct(vmprg) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmprg) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmprgConstructFinalize(vmprg_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmprg); + RETiRet; +} + + +/* destructor for the vmprg object */ +BEGINobjDestruct(vmprg) /* be sure to specify the object type also in END and CODESTART macros! */ + vmop_t *pOp; + vmop_t *pTmp; +CODESTARTobjDestruct(vmprg) + /* we need to destruct the program elements! */ + for(pOp = pThis->vmopRoot ; pOp != NULL ; ) { + pTmp = pOp; + pOp = pOp->pNext; + vmop.Destruct(&pTmp); + } +ENDobjDestruct(vmprg) + + +/* destructor for the vmop object */ +BEGINobjDebugPrint(vmprg) /* be sure to specify the object type also in END and CODESTART macros! */ + vmop_t *pOp; +CODESTARTobjDebugPrint(vmprg) + dbgoprint((obj_t*) pThis, "program contents:\n"); + for(pOp = pThis->vmopRoot ; pOp != NULL ; pOp = pOp->pNext) { + vmop.DebugPrint(pOp); + } +ENDobjDebugPrint(vmprg) + + +/* add an operation (instruction) to the end of the current program. This + * function is expected to be called while creating the program, but never + * again after this is done and it is being executed. Results are undefined if + * it is called after execution. + */ +static rsRetVal +vmprgAddOperation(vmprg_t *pThis, vmop_t *pOp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmprg); + ISOBJ_TYPE_assert(pOp, vmop); + + if(pThis->vmopRoot == NULL) { + pThis->vmopRoot = pOp; + } else { + pThis->vmopLast->pNext = pOp; + } + pThis->vmopLast = pOp; + + RETiRet; +} + + +/* this is a shortcut for high-level callers. It creates a new vmop, sets its + * parameters and adds it to the program - all in one big step. If there is no + * var associated with this operation, the caller can simply supply NULL as + * pVar. + */ +static rsRetVal +vmprgAddVarOperation(vmprg_t *pThis, opcode_t opcode, var_t *pVar) +{ + DEFiRet; + vmop_t *pOp; + + ISOBJ_TYPE_assert(pThis, vmprg); + + /* construct and fill vmop */ + CHKiRet(vmop.Construct(&pOp)); + CHKiRet(vmop.ConstructFinalize(pOp)); + CHKiRet(vmop.ConstructFinalize(pOp)); + CHKiRet(vmop.SetOpcode(pOp, opcode)); + if(pVar != NULL) + CHKiRet(vmop.SetVar(pOp, pVar)); + + /* and add it to the program */ + CHKiRet(vmprgAddOperation(pThis, pOp)); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmprg) +CODESTARTobjQueryInterface(vmprg) + if(pIf->ifVersion != vmprgCURR_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). + */ + //xxxpIf->oID = OBJvmprg; + + pIf->Construct = vmprgConstruct; + pIf->ConstructFinalize = vmprgConstructFinalize; + pIf->Destruct = vmprgDestruct; + pIf->DebugPrint = vmprgDebugPrint; + pIf->AddOperation = vmprgAddOperation; + pIf->AddVarOperation = vmprgAddVarOperation; +finalize_it: +ENDobjQueryInterface(vmprg) + + +/* Initialize the vmprg class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmprg, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmop, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmprgDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmprgConstructFinalize); +ENDObjClassInit(vmprg) + +/* vi:set ai: + */ diff --git a/runtime/vmprg.h b/runtime/vmprg.h new file mode 100644 index 00000000..db1f62f0 --- /dev/null +++ b/runtime/vmprg.h @@ -0,0 +1,66 @@ +/* The vmprg object. + * + * The program is made up of vmop_t's, one after another. When we support + * branching (or user-defined functions) at some time, well do this via + * special branch opcodes. They will then contain the actual memory + * address of a logical program entry that we shall branch to. Other than + * that, all execution is serial - that is one opcode is executed after + * the other. This class implements a logical program store, modelled + * after real main memory. A linked list of opcodes is used to implement it. + * In the future, we may use linked lists of array's to enhance performance, + * but for the time being we have taken the simplistic approach (which also + * reduces risk of bugs during initial development). The necessary pointers + * for this are already implemented in vmop. Though this is not the 100% + * correct place, we have opted this time in favor of performance, which + * made them go there. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_VMPRG_H +#define INCLUDED_VMPRG_H + +#include "vmop.h" + + +/* the vmprg object */ +typedef struct vmprg_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + vmop_t *vmopRoot; /* start of program */ + vmop_t *vmopLast; /* last vmop of program (for adding new ones) */ +} vmprg_t; + + +/* interfaces */ +BEGINinterface(vmprg) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmprg); + rsRetVal (*Construct)(vmprg_t **ppThis); + rsRetVal (*ConstructFinalize)(vmprg_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmprg_t **ppThis); + rsRetVal (*AddOperation)(vmprg_t *pThis, vmop_t *pOp); + rsRetVal (*AddVarOperation)(vmprg_t *pThis, opcode_t opcode, var_t *pVar); +ENDinterface(vmprg) +#define vmprgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vmprg); + +#endif /* #ifndef INCLUDED_VMPRG_H */ diff --git a/runtime/vmstk.c b/runtime/vmstk.c new file mode 100644 index 00000000..1ee3d485 --- /dev/null +++ b/runtime/vmstk.c @@ -0,0 +1,234 @@ +/* vmstk.c - the arithmetic stack of a virtual machine. + * + * Module begun 2008-02-21 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vmstk.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) + + +/* Standard-Constructor + */ +BEGINobjConstruct(vmstk) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vmstk) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmstkConstructFinalize(vmstk_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vmstk); + RETiRet; +} + + +/* destructor for the vmstk object */ +BEGINobjDestruct(vmstk) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vmstk) +ENDobjDestruct(vmstk) + + +/* debugprint for the vmstk object */ +BEGINobjDebugPrint(vmstk) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(vmstk) + dbgoprint((obj_t*) pThis, "stack contents:\n"); +ENDobjDebugPrint(vmstk) + + +/* push a value on the stack. The provided pVar is now owned + * by the stack. If the user intends to continue use it, it + * must be duplicated. + */ +static rsRetVal +push(vmstk_t *pThis, var_t *pVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmstk); + ISOBJ_TYPE_assert(pVar, var); + + if(pThis->iStkPtr >= VMSTK_SIZE) + ABORT_FINALIZE(RS_RET_OUT_OF_STACKSPACE); + + pThis->vStk[pThis->iStkPtr++] = pVar; + +finalize_it: + RETiRet; +} + + +/* pop a value from the stack + * IMPORTANT: the stack pointer always points to the NEXT FREE entry. So in + * order to pop, we must access the element one below the stack pointer. + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +pop(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, vmstk); + ASSERT(ppVar != NULL); + + if(pThis->iStkPtr == 0) + ABORT_FINALIZE(RS_RET_STACK_EMPTY); + + *ppVar = pThis->vStk[--pThis->iStkPtr]; + +finalize_it: + RETiRet; +} + + +/* pop a boolean value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popBool(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToBool(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop a number value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popNumber(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToNumber(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop a number value from the stack + * The user is responsible for destructing the ppVar returned. + */ +static rsRetVal +popString(vmstk_t *pThis, var_t **ppVar) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + CHKiRet(pop(pThis, ppVar)); + CHKiRet(var.ConvToString(*ppVar)); + +finalize_it: + RETiRet; +} + + +/* pop two variables for a common operation, e.g. a compare. When this + * functions returns, both variables have the same type, but the type + * is not set to anything specific. + * The user is responsible for destructing the ppVar's returned. + * A quick note on the name: it means pop 2 variable for a common + * opertion - just in case you wonder (I don't really like the name, + * but I didn't come up with a better one...). + * rgerhards, 2008-02-25 + */ +static rsRetVal +pop2CommOp(vmstk_t *pThis, var_t **ppVar1, var_t **ppVar2) +{ + DEFiRet; + + /* assertions are done in pop(), we do not duplicate here */ + /* operand two must be popped first, because it is at the top of stack */ + CHKiRet(pop(pThis, ppVar2)); + CHKiRet(pop(pThis, ppVar1)); + CHKiRet(var.ConvForOperation(*ppVar1, *ppVar2)); + +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vmstk) +CODESTARTobjQueryInterface(vmstk) + if(pIf->ifVersion != vmstkCURR_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->Construct = vmstkConstruct; + pIf->ConstructFinalize = vmstkConstructFinalize; + pIf->Destruct = vmstkDestruct; + pIf->DebugPrint = vmstkDebugPrint; + pIf->Push = push; + pIf->Pop = pop; + pIf->PopBool = popBool; + pIf->PopNumber = popNumber; + pIf->PopString = popString; + pIf->Pop2CommOp = pop2CommOp; + +finalize_it: +ENDobjQueryInterface(vmstk) + + +/* Initialize the vmstk class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vmstk, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmstkDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmstkConstructFinalize); +ENDObjClassInit(vmstk) + +/* vi:set ai: + */ diff --git a/runtime/vmstk.h b/runtime/vmstk.h new file mode 100644 index 00000000..2d45ee4d --- /dev/null +++ b/runtime/vmstk.h @@ -0,0 +1,56 @@ +/* The vmstk object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_VMSTK_H +#define INCLUDED_VMSTK_H + +/* The max size of the stack - TODO: make configurable */ +#define VMSTK_SIZE 256 + +/* the vmstk object */ +typedef struct vmstk_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + var_t *vStk[VMSTK_SIZE];/* the actual stack */ + int iStkPtr; /* stack pointer, points to next free location, grows from 0 --> topend */ +} vmstk_t; + + +/* interfaces */ +BEGINinterface(vmstk) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(vmstk); + rsRetVal (*Construct)(vmstk_t **ppThis); + rsRetVal (*ConstructFinalize)(vmstk_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(vmstk_t **ppThis); + rsRetVal (*Push)(vmstk_t *pThis, var_t *pVar); + rsRetVal (*Pop)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopBool)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopNumber)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*PopString)(vmstk_t *pThis, var_t **ppVar); + rsRetVal (*Pop2CommOp)(vmstk_t *pThis, var_t **ppVar1, var_t **ppVar2); +ENDinterface(vmstk) +#define vmstkCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(vmstk); + +#endif /* #ifndef INCLUDED_VMSTK_H */ diff --git a/runtime/wti.c b/runtime/wti.c new file mode 100644 index 00000000..13554232 --- /dev/null +++ b/runtime/wti.c @@ -0,0 +1,481 @@ +/* wti.c + * + * This file implements the worker thread instance (wti) class. + * + * File begun on 2008-01-20 by RGerhards based on functions from the + * previous queue object class (the wti functions have been extracted) + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" + +/* static data */ +DEFobjStaticHelpers + +/* forward-definitions */ + +/* methods */ + +/* get the header for debug messages + * The caller must NOT free or otherwise modify the returned string! + */ +static inline uchar * +wtiGetDbgHdr(wti_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, wti); + + if(pThis->pszDbgHdr == NULL) + return (uchar*) "wti"; /* should not normally happen */ + else + return pThis->pszDbgHdr; +} + + +/* get the current worker state. For simplicity and speed, we have + * NOT used our regular calling interface this time. I hope that won't + * bite in the long term... -- rgerhards, 2008-01-17 + * TODO: may be performance optimized by atomic operations + */ +qWrkCmd_t +wtiGetState(wti_t *pThis, int bLockMutex) +{ + DEFVARS_mutexProtection; + qWrkCmd_t tCmd; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wti); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + tCmd = pThis->tCurrCmd; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + return tCmd; +} + + +/* send a command to a specific thread + * bActiveOnly specifies if the command should be sent only when the worker is + * in an active state. -- rgerhards, 2008-01-20 + */ +rsRetVal +wtiSetState(wti_t *pThis, qWrkCmd_t tCmd, int bActiveOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wti); + assert(tCmd <= eWRKTHRD_SHUTDOWN_IMMEDIATE); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + + /* all worker states must be followed sequentially, only termination can be set in any state */ + if( (bActiveOnly && (pThis->tCurrCmd < eWRKTHRD_RUN_CREATED)) + || (pThis->tCurrCmd > tCmd && !(tCmd == eWRKTHRD_TERMINATING || tCmd == eWRKTHRD_STOPPED))) { + dbgprintf("%s: command %d can not be accepted in current %d processing state - ignored\n", + wtiGetDbgHdr(pThis), tCmd, pThis->tCurrCmd); + } else { + dbgprintf("%s: receiving command %d\n", wtiGetDbgHdr(pThis), tCmd); + switch(tCmd) { + case eWRKTHRD_TERMINATING: + /* TODO: re-enable meaningful debug msg! (via function callback?) + dbgprintf("%s: thread terminating with %d entries left in queue, %d workers running.\n", + wtiGetDbgHdr(pThis->pQueue), pThis->pQueue->iQueueSize, + pThis->pQueue->iCurNumWrkThrd); + */ + pthread_cond_signal(&pThis->condExitDone); + dbgprintf("%s: worker terminating\n", wtiGetDbgHdr(pThis)); + break; + case eWRKTHRD_RUNNING: + pthread_cond_signal(&pThis->condInitDone); + break; + /* these cases just to satisfy the compiler, we do (yet) not act an them: */ + case eWRKTHRD_STOPPED: + case eWRKTHRD_RUN_CREATED: + case eWRKTHRD_RUN_INIT: + case eWRKTHRD_SHUTDOWN: + case eWRKTHRD_SHUTDOWN_IMMEDIATE: + /* DO NOTHING */ + break; + } + pThis->tCurrCmd = tCmd; /* apply the new state */ + } + + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + RETiRet; +} + + +/* Cancel the thread. If the thread is already cancelled or termination, + * we do not again cancel it. But it is save and legal to call wtiCancelThrd() in + * such situations. + * rgerhards, 2008-02-26 + */ +rsRetVal +wtiCancelThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + d_pthread_mutex_lock(&pThis->mut); + + if(pThis->tCurrCmd >= eWRKTHRD_TERMINATING) { + dbgoprint((obj_t*) pThis, "canceling worker thread\n"); + pthread_cancel(pThis->thrdID); + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + pThis->pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + } + + d_pthread_mutex_unlock(&pThis->mut); + + RETiRet; +} + + +/* Destructor */ +BEGINobjDestruct(wti) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(wti) + /* if we reach this point, we must make sure the associated worker has terminated. It is + * the callers duty to make sure the worker already knows it shall terminate. + * TODO: is it *really* the caller's duty? ...mmmhhhh.... smells bad... rgerhards, 2008-01-25 + */ + wtiProcessThrdChanges(pThis, LOCK_MUTEX); /* process state change one last time */ + + d_pthread_mutex_lock(&pThis->mut); + if(wtiGetState(pThis, MUTEX_ALREADY_LOCKED) != eWRKTHRD_STOPPED) { + dbgprintf("%s: WARNING: worker %p shall be destructed but is still running (might be OK) - joining it\n", + wtiGetDbgHdr(pThis), pThis); + /* let's hope the caller actually instructed it to shutdown... */ + pthread_cond_wait(&pThis->condExitDone, &pThis->mut); + wtiJoinThrd(pThis); + } + d_pthread_mutex_unlock(&pThis->mut); + + /* actual destruction */ + pthread_cond_destroy(&pThis->condInitDone); + pthread_cond_destroy(&pThis->condExitDone); + pthread_mutex_destroy(&pThis->mut); + + if(pThis->pszDbgHdr != NULL) + free(pThis->pszDbgHdr); +ENDobjDestruct(wti) + + +/* Standard-Constructor for the wti object + */ +BEGINobjConstruct(wti) /* be sure to specify the object type also in END macro! */ + pthread_cond_init(&pThis->condInitDone, NULL); + pthread_cond_init(&pThis->condExitDone, NULL); + pthread_mutex_init(&pThis->mut, NULL); +ENDobjConstruct(wti) + + +/* Construction finalizer + * rgerhards, 2008-01-17 + */ +rsRetVal +wtiConstructFinalize(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + + dbgprintf("%s: finalizing construction of worker instance data\n", wtiGetDbgHdr(pThis)); + + /* initialize our thread instance descriptor */ + pThis->pUsrp = NULL; + pThis->tCurrCmd = eWRKTHRD_STOPPED; + + RETiRet; +} + + +/* join a specific worker thread + * we do not lock the mutex, because join will sync anyways... + */ +rsRetVal +wtiJoinThrd(wti_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + dbgprintf("waiting for worker %s termination, current state %d\n", wtiGetDbgHdr(pThis), pThis->tCurrCmd); + pthread_join(pThis->thrdID, NULL); + wtiSetState(pThis, eWRKTHRD_STOPPED, 0, MUTEX_ALREADY_LOCKED); /* back to virgin... */ + pThis->thrdID = 0; /* invalidate the thread ID so that we do not accidently find reused ones */ + dbgprintf("worker %s has stopped\n", wtiGetDbgHdr(pThis)); + + RETiRet; +} + +/* check if we had a worker thread changes and, if so, act + * on it. At a minimum, terminated threads are harvested (joined). + */ +rsRetVal +wtiProcessThrdChanges(wti_t *pThis, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wti); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + switch(pThis->tCurrCmd) { + case eWRKTHRD_TERMINATING: + /* we need to at least temporarily release the mutex, because otherwise + * we may deadlock with the thread we intend to join (it aquires the mutex + * during termination processing). -- rgerhards, 2008-02-26 + */ + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + iRet = wtiJoinThrd(pThis); + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + break; + /* these cases just to satisfy the compiler, we do not act an them: */ + case eWRKTHRD_STOPPED: + case eWRKTHRD_RUN_CREATED: + case eWRKTHRD_RUN_INIT: + case eWRKTHRD_RUNNING: + case eWRKTHRD_SHUTDOWN: + case eWRKTHRD_SHUTDOWN_IMMEDIATE: + /* DO NOTHING */ + break; + } + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Updates admin structure and frees ressources. + * rgerhards, 2008-01-16 + */ +static void +wtiWorkerCancelCleanup(void *arg) +{ + wti_t *pThis = (wti_t*) arg; + wtp_t *pWtp; + int iCancelStateSave; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; + ISOBJ_TYPE_assert(pWtp, wtp); + + dbgprintf("%s: cancelation cleanup handler called.\n", wtiGetDbgHdr(pThis)); + + /* call user supplied handler (that one e.g. requeues the element) */ + pWtp->pfOnWorkerCancel(pThis->pWtp->pUsr, pThis->pUsrp); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pWtp->mut); + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + /* TODO: sync access? I currently think it is NOT needed -- rgerhards, 2008-01-28 */ + pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + + d_pthread_mutex_unlock(&pWtp->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + ENDfunc +} + + +/* generic worker thread framework + * + * Some special comments below, so that they do not clutter the main function code: + * + * On the use of pthread_testcancel(): + * Now make sure we can get canceled - it is not specified if pthread_setcancelstate() is + * a cancellation point in itself. As we run most of the time without cancel enabled, I fear + * we may never get cancelled if we do not create a cancellation point ourselfs. + * + * On the use of pthread_yield(): + * We yield to give the other threads a chance to obtain the mutex. If we do not + * do that, this thread may very well aquire the mutex again before another thread + * has even a chance to run. The reason is that mutex operations are free to be + * implemented in the quickest possible way (and they typically are!). That is, the + * mutex lock/unlock most probably just does an atomic memory swap and does not necessarily + * schedule other threads waiting on the same mutex. That can lead to the same thread + * aquiring the mutex ever and ever again while all others are starving for it. We + * have exactly seen this behaviour when we deliberately introduced a long-running + * test action which basically did a sleep. I understand that with real actions the + * likelihood of this starvation condition is very low - but it could still happen + * and would be very hard to debug. The yield() is a sure fix, its performance overhead + * should be well accepted given the above facts. -- rgerhards, 2008-01-10 + */ +#pragma GCC diagnostic ignored "-Wempty-body" +rsRetVal +wtiWorker(wti_t *pThis) +{ + DEFiRet; + DEFVARS_mutexProtection; + struct timespec t; + wtp_t *pWtp; /* our worker thread pool */ + int bInactivityTOOccured = 0; + + ISOBJ_TYPE_assert(pThis, wti); + pWtp = pThis->pWtp; /* shortcut */ + ISOBJ_TYPE_assert(pWtp, wtp); + + dbgSetThrdName(pThis->pszDbgHdr); + pThis->pUsrp = NULL; + pthread_cleanup_push(wtiWorkerCancelCleanup, pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr, LOCK_MUTEX); + pWtp->pfOnWorkerStartup(pWtp->pUsr); + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + + /* now we have our identity, on to real processing */ + while(1) { /* loop will be broken below - need to do mutex locks */ + /* process any pending thread requests */ + wtpProcessThrdChanges(pWtp); + pthread_testcancel(); /* see big comment in function header */ +# if !defined(__hpux) /* pthread_yield is missing there! */ + pthread_yield(); /* see big comment in function header */ +# endif + + /* if we have a rate-limiter set for this worker pool, let's call it. Please + * keep in mind that the rate-limiter may hold us for an extended period + * of time. -- rgerhards, 2008-04-02 + */ + if(pWtp->pfRateLimiter != NULL) { + pWtp->pfRateLimiter(pWtp->pUsr); + } + + wtpSetInactivityGuard(pThis->pWtp, 0, LOCK_MUTEX); /* must be set before usr mutex is locked! */ + BEGIN_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr, LOCK_MUTEX); + + if( (bInactivityTOOccured && pWtp->pfIsIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED)) + || wtpChkStopWrkr(pWtp, LOCK_MUTEX, MUTEX_ALREADY_LOCKED)) { + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + break; /* end worker thread run */ + } + bInactivityTOOccured = 0; /* reset for next run */ + + /* if we reach this point, we are still protected by the mutex */ + + if(pWtp->pfIsIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED)) { + dbgprintf("%s: worker IDLE, waiting for work.\n", wtiGetDbgHdr(pThis)); + pWtp->pfOnIdle(pWtp->pUsr, MUTEX_ALREADY_LOCKED); + + if(pWtp->toWrkShutdown == -1) { + /* never shut down any started worker */ + d_pthread_cond_wait(pWtp->pcondBusy, pWtp->pmutUsr); + } else { + timeoutComp(&t, pWtp->toWrkShutdown);/* get absolute timeout */ + if(d_pthread_cond_timedwait(pWtp->pcondBusy, pWtp->pmutUsr, &t) != 0) { + dbgprintf("%s: inactivity timeout, worker terminating...\n", wtiGetDbgHdr(pThis)); + bInactivityTOOccured = 1; /* indicate we had a timeout */ + } + } + END_MTX_PROTECTED_OPERATIONS(pWtp->pmutUsr); + continue; /* request next iteration */ + } + + /* if we reach this point, we have a non-empty queue (and are still protected by mutex) */ + pWtp->pfDoWork(pWtp->pUsr, pThis, iCancelStateSave); + } + + /* indicate termination */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pThis->mut); + pthread_cleanup_pop(0); /* remove cleanup handler */ + + pWtp->pfOnWorkerShutdown(pWtp->pUsr); + + wtiSetState(pThis, eWRKTHRD_TERMINATING, 0, MUTEX_ALREADY_LOCKED); + pWtp->bThrdStateChanged = 1; /* indicate change, so harverster will be called */ + d_pthread_mutex_unlock(&pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* some simple object access methods */ +DEFpropSetMeth(wti, pWtp, wtp_t*) + +/* set the debug header message + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. Must be called only before object is finalized. + * rgerhards, 2008-01-09 + */ +rsRetVal +wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wti); + assert(pszMsg != NULL); + + if(lenMsg < 1) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + if(pThis->pszDbgHdr != NULL) { + free(pThis->pszDbgHdr); + pThis->pszDbgHdr = NULL; + } + + if((pThis->pszDbgHdr = malloc(sizeof(uchar) * lenMsg + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */ + +finalize_it: + RETiRet; +} + + +/* dummy */ +rsRetVal wtiQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + + +/* Initialize the wti class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(wti, 1, OBJ_IS_CORE_MODULE) /* one is the object version (most important for persisting) */ + /* request objects we use */ +ENDObjClassInit(wti) + +/* + * vi:set ai: + */ diff --git a/runtime/wti.h b/runtime/wti.h new file mode 100644 index 00000000..b3d92473 --- /dev/null +++ b/runtime/wti.h @@ -0,0 +1,63 @@ +/* Definition of the worker thread instance (wti) class. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef WTI_H_INCLUDED +#define WTI_H_INCLUDED + +#include <pthread.h> +#include "wtp.h" +#include "obj.h" + +/* the worker thread instance class */ +typedef struct wti_s { + BEGINobjInstance; + pthread_t thrdID; /* thread ID */ + qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ + obj_t *pUsrp; /* pointer to an object meaningful for current user pointer (e.g. queue pUsr data elemt) */ + wtp_t *pWtp; /* my worker thread pool (important if only the work thread instance is passed! */ + pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ + pthread_cond_t condExitDone; /* signaled when the thread exit is done (once per thread existance) */ + pthread_mutex_t mut; + int bShutdownRqtd; /* shutdown for this thread requested? 0 - no , 1 - yes */ + uchar *pszDbgHdr; /* header string for debug messages */ +} wti_t; + +/* some symbolic constants for easier reference */ + + +/* prototypes */ +rsRetVal wtiConstruct(wti_t **ppThis); +rsRetVal wtiConstructFinalize(wti_t *pThis); +rsRetVal wtiDestruct(wti_t **ppThis); +rsRetVal wtiWorker(wti_t *pThis); +rsRetVal wtiProcessThrdChanges(wti_t *pThis, int bLockMutex); +rsRetVal wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtiSetState(wti_t *pThis, qWrkCmd_t tCmd, int bActiveOnly, int bLockMutex); +rsRetVal wtiJoinThrd(wti_t *pThis); +rsRetVal wtiCancelThrd(wti_t *pThis); +qWrkCmd_t wtiGetState(wti_t *pThis, int bLockMutex); +PROTOTYPEObjClassInit(wti); +PROTOTYPEpropSetMeth(wti, pszDbgHdr, uchar*); +PROTOTYPEpropSetMeth(wti, pWtp, wtp_t*); + +#endif /* #ifndef WTI_H_INCLUDED */ diff --git a/runtime/wtp.c b/runtime/wtp.c new file mode 100644 index 00000000..8b041ea2 --- /dev/null +++ b/runtime/wtp.c @@ -0,0 +1,627 @@ +/* wtp.c + * + * This file implements the worker thread pool (wtp) class. + * + * File begun on 2008-01-20 by RGerhards + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <pthread.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "wtp.h" +#include "wti.h" +#include "obj.h" + +/* static data */ +DEFobjStaticHelpers + +/* forward-definitions */ + +/* methods */ + +/* get the header for debug messages + * The caller must NOT free or otherwise modify the returned string! + */ +static inline uchar * +wtpGetDbgHdr(wtp_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, wtp); + + if(pThis->pszDbgHdr == NULL) + return (uchar*) "wtp"; /* should not normally happen */ + else + return pThis->pszDbgHdr; +} + + + +/* Not implemented dummy function for constructor */ +static rsRetVal NotImplementedDummy() { return RS_RET_OK; } +/* Standard-Constructor for the wtp object + */ +BEGINobjConstruct(wtp) /* be sure to specify the object type also in END macro! */ + pthread_mutex_init(&pThis->mut, NULL); + pthread_cond_init(&pThis->condThrdTrm, NULL); + /* set all function pointers to "not implemented" dummy so that we can safely call them */ + pThis->pfChkStopWrkr = NotImplementedDummy; + pThis->pfIsIdle = NotImplementedDummy; + pThis->pfDoWork = NotImplementedDummy; + pThis->pfOnIdle = NotImplementedDummy; + pThis->pfOnWorkerCancel = NotImplementedDummy; + pThis->pfOnWorkerStartup = NotImplementedDummy; + pThis->pfOnWorkerShutdown = NotImplementedDummy; +ENDobjConstruct(wtp) + + +/* Construction finalizer + * rgerhards, 2008-01-17 + */ +rsRetVal +wtpConstructFinalize(wtp_t *pThis) +{ + DEFiRet; + int i; + uchar pszBuf[64]; + size_t lenBuf; + wti_t *pWti; + + ISOBJ_TYPE_assert(pThis, wtp); + + dbgprintf("%s: finalizing construction of worker thread pool\n", wtpGetDbgHdr(pThis)); + /* alloc and construct workers - this can only be done in finalizer as we previously do + * not know the max number of workers + */ + if((pThis->pWrkr = malloc(sizeof(wti_t*) * pThis->iNumWorkerThreads)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + CHKiRet(wtiConstruct(&pThis->pWrkr[i])); + pWti = pThis->pWrkr[i]; + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s/w%d", wtpGetDbgHdr(pThis), i); + CHKiRet(wtiSetDbgHdr(pWti, pszBuf, lenBuf)); + CHKiRet(wtiSetpWtp(pWti, pThis)); + CHKiRet(wtiConstructFinalize(pWti)); + } + + +finalize_it: + RETiRet; +} + + +/* Destructor */ +BEGINobjDestruct(wtp) /* be sure to specify the object type also in END and CODESTART macros! */ + int i; +CODESTARTobjDestruct(wtp) + wtpProcessThrdChanges(pThis); /* process thread changes one last time */ + + /* destruct workers */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) + wtiDestruct(&pThis->pWrkr[i]); + + free(pThis->pWrkr); + pThis->pWrkr = NULL; + + /* actual destruction */ + pthread_cond_destroy(&pThis->condThrdTrm); + pthread_mutex_destroy(&pThis->mut); + + if(pThis->pszDbgHdr != NULL) + free(pThis->pszDbgHdr); +ENDobjDestruct(wtp) + + +/* wake up at least one worker thread. + * rgerhards, 2008-01-20 + */ +rsRetVal +wtpWakeupWrkr(wtp_t *pThis) +{ + DEFiRet; + + /* TODO; mutex? I think not needed, as we do not need predictable exec order -- rgerhards, 2008-01-28 */ + ISOBJ_TYPE_assert(pThis, wtp); + pthread_cond_signal(pThis->pcondBusy); + RETiRet; +} + +/* wake up all worker threads. + * rgerhards, 2008-01-16 + */ +rsRetVal +wtpWakeupAllWrkr(wtp_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + pthread_cond_broadcast(pThis->pcondBusy); + RETiRet; +} + + +/* check if we had any worker thread changes and, if so, act + * on them. At a minimum, terminated threads are harvested (joined). + * This function MUST NEVER block on the queue mutex! + */ +rsRetVal +wtpProcessThrdChanges(wtp_t *pThis) +{ + DEFiRet; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + if(pThis->bThrdStateChanged == 0) + FINALIZE; + + /* go through all threads */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + wtiProcessThrdChanges(pThis->pWrkr[i], LOCK_MUTEX); + } + +finalize_it: + RETiRet; +} + + +/* Sent a specific state for the worker thread pool. + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpSetState(wtp_t *pThis, wtpState_t iNewState) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + pThis->wtpState = iNewState; + /* TODO: must wakeup workers? seen to be not needed -- rgerhards, 2008-01-28 */ + + RETiRet; +} + + +/* check if the worker shall shutdown (1 = yes, 0 = no) + * TODO: check if we can use atomic operations to enhance performance + * Note: there may be two mutexes locked, the bLockUsrMutex is the one in our "user" + * (e.g. the queue clas) + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpChkStopWrkr(wtp_t *pThis, int bLockMutex, int bLockUsrMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, wtp); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + if( (pThis->wtpState == wtpState_SHUTDOWN_IMMEDIATE) + || ((pThis->wtpState == wtpState_SHUTDOWN) && pThis->pfIsIdle(pThis->pUsr, bLockUsrMutex))) + iRet = RS_RET_TERMINATE_NOW; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + /* try customer handler if one was set and we do not yet have a definite result */ + if(iRet == RS_RET_OK && pThis->pfChkStopWrkr != NULL) { + iRet = pThis->pfChkStopWrkr(pThis->pUsr, bLockUsrMutex); + } + + RETiRet; +} + + +#pragma GCC diagnostic ignored "-Wempty-body" +/* Send a shutdown command to all workers and see if they terminate. + * A timeout may be specified. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout) +{ + DEFiRet; + int bTimedOut; + int iCancelStateSave; + + ISOBJ_TYPE_assert(pThis, wtp); + + wtpSetState(pThis, tShutdownCmd); + wtpWakeupAllWrkr(pThis); + + /* see if we need to harvest (join) any terminated threads (even in timeout case, + * some may have terminated... + */ + wtpProcessThrdChanges(pThis); + + /* and wait for their termination */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(&pThis->mut); + pthread_cleanup_push(mutexCancelCleanup, &pThis->mut); + pthread_setcancelstate(iCancelStateSave, NULL); + bTimedOut = 0; + while(pThis->iCurNumWrkThrd > 0 && !bTimedOut) { + dbgprintf("%s: waiting %ldms on worker thread termination, %d still running\n", + wtpGetDbgHdr(pThis), timeoutVal(ptTimeout), pThis->iCurNumWrkThrd); + + if(d_pthread_cond_timedwait(&pThis->condThrdTrm, &pThis->mut, ptTimeout) != 0) { + dbgprintf("%s: timeout waiting on worker thread termination\n", wtpGetDbgHdr(pThis)); + bTimedOut = 1; /* we exit the loop on timeout */ + } + } + pthread_cleanup_pop(1); + + if(bTimedOut) + iRet = RS_RET_TIMED_OUT; + + /* see if we need to harvest (join) any terminated threads (even in timeout case, + * some may have terminated... + */ + wtpProcessThrdChanges(pThis); + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* indicate that a thread has terminated and awake anyone waiting on it + * rgerhards, 2008-01-23 + */ +rsRetVal wtpSignalWrkrTermination(wtp_t *pThis) +{ + DEFiRet; + /* I leave the mutex code here out as it give as deadlocks. I think it is not really + * needed and we are on the safe side. I leave this comment in if practice proves us + * wrong. The whole thing should be removed after half a your or year if we see there + * actually is no issue (or revisit it from a theoretical POV). + * rgerhards, 2008-01-28 + */ + /*TODO: mutex or not mutex, that's the question ;)DEFVARS_mutexProtection;*/ + + ISOBJ_TYPE_assert(pThis, wtp); + + /*BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX);*/ + pthread_cond_signal(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */ + /*END_MTX_PROTECTED_OPERATIONS(&pThis->mut);*/ + RETiRet; +} + + +/* Unconditionally cancel all running worker threads. + * rgerhards, 2008-01-14 + */ +rsRetVal +wtpCancelAll(wtp_t *pThis) +{ + DEFiRet; + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + /* process any pending thread requests so that we know who actually is still running */ + wtpProcessThrdChanges(pThis); + + /* go through all workers and cancel those that are active */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + dbgprintf("%s: try canceling worker thread %d\n", wtpGetDbgHdr(pThis), i); + wtiCancelThrd(pThis->pWrkr[i]); + } + + RETiRet; +} + + + +/* Set the Inactivity Guard + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpSetInactivityGuard(wtp_t *pThis, int bNewState, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + pThis->bInactivityGuard = bNewState; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + RETiRet; +} + + +/* cancellation cleanup handler for executing worker + * decrements the worker counter + * rgerhards, 2008-01-20 + */ +void +wtpWrkrExecCancelCleanup(void *arg) +{ + wtp_t *pThis = (wtp_t*) arg; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wtp); + pThis->iCurNumWrkThrd--; + wtpSignalWrkrTermination(pThis); + + dbgprintf("%s: thread CANCELED with %d workers running.\n", wtpGetDbgHdr(pThis), pThis->iCurNumWrkThrd); + ENDfunc +} + + +/* wtp worker shell. This is started and calls into the actual + * wti worker. + * rgerhards, 2008-01-21 + */ +#pragma GCC diagnostic ignored "-Wempty-body" +static void * +wtpWorker(void *arg) /* the arg is actually a wti object, even though we are in wtp! */ +{ + DEFiRet; + DEFVARS_mutexProtection; + wti_t *pWti = (wti_t*) arg; + wtp_t *pThis; + sigset_t sigSet; + + ISOBJ_TYPE_assert(pWti, wti); + pThis = pWti->pWtp; + ISOBJ_TYPE_assert(pThis, wtp); + + sigfillset(&sigSet); + pthread_sigmask(SIG_BLOCK, &sigSet, NULL); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + + /* do some late initialization */ + + pthread_cleanup_push(wtpWrkrExecCancelCleanup, pThis); + + /* finally change to RUNNING state. We need to check if we actually should still run, + * because someone may have requested us to shut down even before we got a chance to do + * our init. That would be a bad race... -- rgerhards, 2008-01-16 + */ + wtiSetState(pWti, eWRKTHRD_RUNNING, 0, MUTEX_ALREADY_LOCKED); /* we are running now! */ + + do { + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + iRet = wtiWorker(pWti); /* just to make sure: this is NOT protected by the mutex! */ + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + } while(pThis->iCurNumWrkThrd == 1 && pThis->bInactivityGuard == 1); + /* inactivity guard prevents shutdown of all workers while one should be running due to race + * condition. It can lead to one more worker running than desired, but that is acceptable. After + * all, that worker will shutdown itself due to inactivity timeout. If, however, none were running + * when one was required, processing could come to a halt. -- rgerhards, 2008-01-21 + */ + + pthread_cleanup_pop(0); + pThis->iCurNumWrkThrd--; + wtpSignalWrkrTermination(pThis); + + dbgprintf("%s: Worker thread %lx, terminated, num workers now %d\n", + wtpGetDbgHdr(pThis), (unsigned long) pWti, pThis->iCurNumWrkThrd); + + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + pthread_exit(0); +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* start a new worker */ +static rsRetVal +wtpStartWrkr(wtp_t *pThis, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + wti_t *pWti; + int i; + int iState; + + ISOBJ_TYPE_assert(pThis, wtp); + + wtpProcessThrdChanges(pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + + pThis->iCurNumWrkThrd++; + + /* find free spot in thread table. If we find at least one worker that is in initialization, + * we do NOT start a new one. Let's give the other one a chance, first. + */ + for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) { + if(wtiGetState(pThis->pWrkr[i], LOCK_MUTEX) == eWRKTHRD_STOPPED) { + break; + } + } + + if(i == pThis->iNumWorkerThreads) + ABORT_FINALIZE(RS_RET_NO_MORE_THREADS); + + pWti = pThis->pWrkr[i]; + wtiSetState(pWti, eWRKTHRD_RUN_CREATED, 0, LOCK_MUTEX); + iState = pthread_create(&(pWti->thrdID), NULL, wtpWorker, (void*) pWti); + dbgprintf("%s: started with state %d, num workers now %d\n", + wtpGetDbgHdr(pThis), iState, pThis->iCurNumWrkThrd); + + /* we try to give the starting worker a little boost. It won't help much as we still + * hold the queue's mutex, but at least it has a chance to start on a single-CPU system. + */ +# if !defined(__hpux) /* pthread_yield is missing there! */ + pthread_yield(); +# endif + + /* indicate we just started a worker and would like to see it running */ + wtpSetInactivityGuard(pThis, 1, MUTEX_ALREADY_LOCKED); + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + RETiRet; +} + + +/* set the number of worker threads that should be running. If less than currently running, + * a new worker may be started. Please note that there is no guarantee the number of workers + * said will be running after we exit this function. It is just a hint. If the number is + * higher than one, and no worker is started, the "busy" condition is signaled to awake a worker. + * So the caller can assume that there is at least one worker re-checking if there is "work to do" + * after this function call. + * rgerhards, 2008-01-21 + */ +rsRetVal +wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr) +{ + DEFiRet; + DEFVARS_mutexProtection; + int nMissing; /* number workers missing to run */ + int i; + + ISOBJ_TYPE_assert(pThis, wtp); + + if(nMaxWrkr == 0) + FINALIZE; + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, LOCK_MUTEX); + + if(nMaxWrkr > pThis->iNumWorkerThreads) /* limit to configured maximum */ + nMaxWrkr = pThis->iNumWorkerThreads; + + nMissing = nMaxWrkr - pThis->iCurNumWrkThrd; + + if(nMissing > 0) { + dbgprintf("%s: high activity - starting %d additional worker thread(s).\n", wtpGetDbgHdr(pThis), nMissing); + /* start the rqtd nbr of workers */ + for(i = 0 ; i < nMissing ; ++i) { + CHKiRet(wtpStartWrkr(pThis, MUTEX_ALREADY_LOCKED)); + } + } else { + if(nMaxWrkr > 0) { + dbgprintf("wtpAdviseMaxWorkers signals busy\n"); + wtpWakeupWrkr(pThis); + } + } + + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + RETiRet; +} + + +/* some simple object access methods */ +DEFpropSetMeth(wtp, toWrkShutdown, long) +DEFpropSetMeth(wtp, wtpState, wtpState_t) +DEFpropSetMeth(wtp, iNumWorkerThreads, int) +DEFpropSetMeth(wtp, pUsr, void*) +DEFpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t) +DEFpropSetMethPTR(wtp, pcondBusy, pthread_cond_t) +DEFpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int)) +DEFpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*)) +DEFpropSetMethFP(wtp, pfIsIdle, rsRetVal(*pVal)(void*, int)) +DEFpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*, int)) +DEFpropSetMethFP(wtp, pfOnIdle, rsRetVal(*pVal)(void*, int)) +DEFpropSetMethFP(wtp, pfOnWorkerCancel, rsRetVal(*pVal)(void*, void*)) +DEFpropSetMethFP(wtp, pfOnWorkerStartup, rsRetVal(*pVal)(void*)) +DEFpropSetMethFP(wtp, pfOnWorkerShutdown, rsRetVal(*pVal)(void*)) + + +/* return the current number of worker threads. + * TODO: atomic operation would bring a nice performance + * enhancemcent + * rgerhards, 2008-01-27 + */ +int +wtpGetCurNumWrkr(wtp_t *pThis, int bLockMutex) +{ + DEFVARS_mutexProtection; + int iNumWrkr; + + BEGINfunc + ISOBJ_TYPE_assert(pThis, wtp); + + BEGIN_MTX_PROTECTED_OPERATIONS(&pThis->mut, bLockMutex); + iNumWrkr = pThis->iCurNumWrkThrd; + END_MTX_PROTECTED_OPERATIONS(&pThis->mut); + + ENDfunc + return iNumWrkr; +} + + +/* set the debug header message + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. Must be called only before object is finalized. + * rgerhards, 2008-01-09 + */ +rsRetVal +wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, wtp); + assert(pszMsg != NULL); + + if(lenMsg < 1) + ABORT_FINALIZE(RS_RET_PARAM_ERROR); + + if(pThis->pszDbgHdr != NULL) { + free(pThis->pszDbgHdr); + pThis->pszDbgHdr = NULL; + } + + if((pThis->pszDbgHdr = malloc(sizeof(uchar) * lenMsg + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */ + +finalize_it: + RETiRet; +} + +/* dummy */ +rsRetVal wtpQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(wtp, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ +ENDObjClassInit(wtp) + +/* + * vi:set ai: + */ diff --git a/runtime/wtp.h b/runtime/wtp.h new file mode 100644 index 00000000..13ebe536 --- /dev/null +++ b/runtime/wtp.h @@ -0,0 +1,119 @@ +/* Definition of the worker thread pool (wtp) object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef WTP_H_INCLUDED +#define WTP_H_INCLUDED + +#include <pthread.h> +#include "obj.h" + +/* commands and states for worker threads. */ +typedef enum { + eWRKTHRD_STOPPED = 0, /* worker thread is not running (either actually never ran or was shut down) */ + eWRKTHRD_TERMINATING = 1,/* worker thread has shut down, but some finalzing is still needed */ + /* ALL active states MUST be numerically higher than eWRKTHRD_TERMINATED and NONE must be lower! */ + eWRKTHRD_RUN_CREATED = 2,/* worker thread has been created, but not yet begun initialization (prob. not yet scheduled) */ + eWRKTHRD_RUN_INIT = 3, /* worker thread is initializing, but not yet fully running */ + eWRKTHRD_RUNNING = 4, /* worker thread is up and running and shall continue to do so */ + eWRKTHRD_SHUTDOWN = 5, /* worker thread is running but shall terminate when wtp is empty */ + eWRKTHRD_SHUTDOWN_IMMEDIATE = 6/* worker thread is running but shall terminate even if wtp is full */ + /* SHUTDOWN_IMMEDIATE MUST alsways be the numerically highest state! */ +} qWrkCmd_t; + + +/* possible states of a worker thread pool */ +typedef enum { + wtpState_RUNNING = 0, /* runs in regular mode */ + wtpState_SHUTDOWN = 1, /* worker threads shall shutdown when idle */ + wtpState_SHUTDOWN_IMMEDIATE = 2 /* worker threads shall shutdown ASAP, even if not idle */ +} wtpState_t; + + +/* the worker thread pool (wtp) object */ +typedef struct wtp_s { + BEGINobjInstance; + wtpState_t wtpState; + int iNumWorkerThreads;/* number of worker threads to use */ + int iCurNumWrkThrd;/* current number of active worker threads */ + struct wti_s **pWrkr;/* array with control structure for the worker thread(s) associated with this wtp */ + int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ + int bInactivityGuard;/* prevents inactivity due to race condition */ + rsRetVal (*pConsumer)(void *); /* user-supplied consumer function for dewtpd messages */ + /* synchronization variables */ + pthread_mutex_t mut; /* mutex for the wtp's thread management */ + pthread_cond_t condThrdTrm;/* signalled when threads terminate */ + int bThrdStateChanged; /* at least one thread state has changed if 1 */ + /* end sync variables */ + /* user objects */ + void *pUsr; /* pointer to user object */ + pthread_mutex_t *pmutUsr; + pthread_cond_t *pcondBusy; /* condition the user will signal "busy again, keep runing" on (awakes worker) */ + rsRetVal (*pfChkStopWrkr)(void *pUsr, int); + rsRetVal (*pfRateLimiter)(void *pUsr); + rsRetVal (*pfIsIdle)(void *pUsr, int); + rsRetVal (*pfDoWork)(void *pUsr, void *pWti, int); + rsRetVal (*pfOnIdle)(void *pUsr, int); + rsRetVal (*pfOnWorkerCancel)(void *pUsr, void*pWti); + rsRetVal (*pfOnWorkerStartup)(void *pUsr); + rsRetVal (*pfOnWorkerShutdown)(void *pUsr); + /* end user objects */ + uchar *pszDbgHdr; /* header string for debug messages */ +} wtp_t; + +/* some symbolic constants for easier reference */ + + +/* prototypes */ +rsRetVal wtpConstruct(wtp_t **ppThis); +rsRetVal wtpConstructFinalize(wtp_t *pThis); +rsRetVal wtpDestruct(wtp_t **ppThis); +rsRetVal wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr); +rsRetVal wtpProcessThrdChanges(wtp_t *pThis); +rsRetVal wtpSetInactivityGuard(wtp_t *pThis, int bNewState, int bLockMutex); +rsRetVal wtpChkStopWrkr(wtp_t *pThis, int bLockMutex, int bLockUsrMutex); +rsRetVal wtpSetState(wtp_t *pThis, wtpState_t iNewState); +rsRetVal wtpWakeupWrkr(wtp_t *pThis); +rsRetVal wtpWakeupAllWrkr(wtp_t *pThis); +rsRetVal wtpCancelAll(wtp_t *pThis); +rsRetVal wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg); +rsRetVal wtpSignalWrkrTermination(wtp_t *pWtp); +rsRetVal wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout); +int wtpGetCurNumWrkr(wtp_t *pThis, int bLockMutex); +PROTOTYPEObjClassInit(wtp); +PROTOTYPEpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMethFP(wtp, pfIsIdle, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfOnIdle, rsRetVal(*pVal)(void*, int)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerCancel, rsRetVal(*pVal)(void*,void*)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerStartup, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMethFP(wtp, pfOnWorkerShutdown, rsRetVal(*pVal)(void*)); +PROTOTYPEpropSetMeth(wtp, toWrkShutdown, long); +PROTOTYPEpropSetMeth(wtp, wtpState, wtpState_t); +PROTOTYPEpropSetMeth(wtp, iMaxWorkerThreads, int); +PROTOTYPEpropSetMeth(wtp, pUsr, void*); +PROTOTYPEpropSetMeth(wtp, iNumWorkerThreads, int); +PROTOTYPEpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t); +PROTOTYPEpropSetMethPTR(wtp, pcondBusy, pthread_cond_t); + +#endif /* #ifndef WTP_H_INCLUDED */ |