summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Makefile.am137
-rw-r--r--runtime/atomic.h51
-rw-r--r--runtime/cfsysline.c996
-rw-r--r--runtime/cfsysline.h76
-rw-r--r--runtime/conf.c1217
-rw-r--r--runtime/conf.h53
-rw-r--r--runtime/ctok.c593
-rw-r--r--runtime/ctok.h56
-rw-r--r--runtime/ctok_token.c129
-rw-r--r--runtime/ctok_token.h87
-rw-r--r--runtime/datetime.c681
-rw-r--r--runtime/datetime.h58
-rw-r--r--runtime/debug.c1334
-rw-r--r--runtime/debug.h146
-rw-r--r--runtime/errmsg.c144
-rw-r--r--runtime/errmsg.h46
-rw-r--r--runtime/expr.c418
-rw-r--r--runtime/expr.h56
-rw-r--r--runtime/glbl.c258
-rw-r--r--runtime/glbl.h62
-rw-r--r--runtime/linkedlist.c414
-rw-r--r--runtime/linkedlist.h73
-rw-r--r--runtime/module-template.h486
-rw-r--r--runtime/modules.c809
-rw-r--r--runtime/modules.h150
-rw-r--r--runtime/msg.c2443
-rw-r--r--runtime/msg.h182
-rw-r--r--runtime/net.c1487
-rw-r--r--runtime/net.h164
-rw-r--r--runtime/netstrm.c353
-rw-r--r--runtime/netstrm.h73
-rw-r--r--runtime/netstrms.c324
-rw-r--r--runtime/netstrms.h64
-rw-r--r--runtime/nsd.h76
-rw-r--r--runtime/nsd_gtls.c1711
-rw-r--r--runtime/nsd_gtls.h92
-rw-r--r--runtime/nsd_ptcp.c781
-rw-r--r--runtime/nsd_ptcp.h47
-rw-r--r--runtime/nsdsel_gtls.c260
-rw-r--r--runtime/nsdsel_gtls.h43
-rw-r--r--runtime/nsdsel_ptcp.c196
-rw-r--r--runtime/nsdsel_ptcp.h44
-rw-r--r--runtime/nssel.c227
-rw-r--r--runtime/nssel.h56
-rw-r--r--runtime/obj-types.h412
-rw-r--r--runtime/obj.c1334
-rw-r--r--runtime/obj.h125
-rw-r--r--runtime/objomsr.c145
-rw-r--r--runtime/objomsr.h46
-rw-r--r--runtime/queue.c2310
-rw-r--r--runtime/queue.h205
-rw-r--r--runtime/regexp.c102
-rw-r--r--runtime/regexp.h46
-rw-r--r--runtime/rsyslog.c237
-rw-r--r--runtime/rsyslog.h360
-rw-r--r--runtime/srUtils.h127
-rw-r--r--runtime/srutils.c554
-rw-r--r--runtime/stream.c933
-rw-r--r--runtime/stream.h131
-rw-r--r--runtime/stringbuf.c1080
-rw-r--r--runtime/stringbuf.h169
-rw-r--r--runtime/sync.c56
-rw-r--r--runtime/sync.h50
-rw-r--r--runtime/syslogd-types.h103
-rw-r--r--runtime/sysvar.c202
-rw-r--r--runtime/sysvar.h47
-rw-r--r--runtime/var.c414
-rw-r--r--runtime/var.h70
-rw-r--r--runtime/vm.c525
-rw-r--r--runtime/vm.h65
-rw-r--r--runtime/vmop.c235
-rw-r--r--runtime/vmop.h92
-rw-r--r--runtime/vmprg.c175
-rw-r--r--runtime/vmprg.h66
-rw-r--r--runtime/vmstk.c234
-rw-r--r--runtime/vmstk.h56
-rw-r--r--runtime/wti.c481
-rw-r--r--runtime/wti.h63
-rw-r--r--runtime/wtp.c627
-rw-r--r--runtime/wtp.h119
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 */