summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Makefile.am42
-rw-r--r--runtime/ctok.c591
-rw-r--r--runtime/ctok.h56
-rw-r--r--runtime/ctok_token.c131
-rw-r--r--runtime/ctok_token.h89
-rw-r--r--runtime/expr.c418
-rw-r--r--runtime/expr.h56
-rw-r--r--runtime/regexp.c102
-rw-r--r--runtime/regexp.h46
-rw-r--r--runtime/stream.c934
-rw-r--r--runtime/stream.h131
-rw-r--r--runtime/sync.c56
-rw-r--r--runtime/sync.h50
-rw-r--r--runtime/sysvar.c200
-rw-r--r--runtime/sysvar.h47
-rw-r--r--runtime/var.c414
-rw-r--r--runtime/var.h70
-rw-r--r--runtime/vm.c528
-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.c480
-rw-r--r--runtime/wti.h63
-rw-r--r--runtime/wtp.c624
-rw-r--r--runtime/wtp.h119
29 files changed, 6168 insertions, 2 deletions
diff --git a/runtime/Makefile.am b/runtime/Makefile.am
index 813a4c68..cd8a19c2 100644
--- a/runtime/Makefile.am
+++ b/runtime/Makefile.am
@@ -1,7 +1,35 @@
+sbin_PROGRAMS =
+man_MANS =
noinst_LTLIBRARIES = librsyslog.la
#pkglib_LTLIBRARIES = librsyslog.la
librsyslog_la_SOURCES = \
+ 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
@@ -9,5 +37,15 @@ librsyslog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags)
#librsyslog_la_LDFLAGS = -module -avoid-version
librsyslog_la_LIBADD =
-sbin_PROGRAMS =
-man_MANS =
+#
+# regular expression support
+#
+if ENABLE_REGEXP
+noinst_LTLIBRARIES += lmregexp.la
+#pkglib_LTLIBRARIES += lmregexp.la
+lmregexp_la_SOURCES = regexp.c regexp.h
+lmregexp_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags)
+lmregexp_la_LDFLAGS = -module -avoid-version $(rsrt_libs)
+lmregexp_la_LIBADD =
+endif
+
diff --git a/runtime/ctok.c b/runtime/ctok.c
new file mode 100644
index 00000000..98d5b63b
--- /dev/null
+++ b/runtime/ctok.c
@@ -0,0 +1,591 @@
+/* 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
+ *
+ * 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 whitepsace, 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 charater */
+ pToken->tok = (c == '=')? ctok_CMP_EQ : ctok_INVALID;
+ break;
+ case '!': /* != */
+ CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */
+ pToken->tok = (c == '=')? ctok_CMP_NEQ : ctok_INVALID;
+ break;
+ case '<': /* <, <=, <> */
+ CHKiRet(ctokGetCharFromStream(pThis, &c)); /* read a charater */
+ 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 charater */
+ 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... */
+ pToken->tok = ctok_INVALID;
+ }
+ }
+ }
+ break;
+ }
+ } while(bRetry); /* warning: do ... while()! */
+
+ *ppToken = pToken;
+ dbgoprint((obj_t*) pToken, "token: %d\n", pToken->tok);
+
+finalize_it:
+ 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).
+ */
+ //xxxpIf->oID = OBJctok;
+
+ 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..0f340675
--- /dev/null
+++ b/runtime/ctok_token.c
@@ -0,0 +1,131 @@
+/* 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).
+ */
+ //xxxpIf->oID = OBJctok_token;
+
+ 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..346d5acd
--- /dev/null
+++ b/runtime/ctok_token.h
@@ -0,0 +1,89 @@
+/* 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;
+ //cstr_t *pstrVal;
+ //int64 intVal;
+} 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/expr.c b/runtime/expr.c
new file mode 100644
index 00000000..9c357404
--- /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");
+ // 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) {
+ CHKiRet(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/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/stream.c b/runtime/stream.c
new file mode 100644
index 00000000..1be4571a
--- /dev/null
+++ b/runtime/stream.c
@@ -0,0 +1,934 @@
+//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 "syslogd.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;
+
+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/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/sysvar.c b/runtime/sysvar.c
new file mode 100644
index 00000000..14e32b96
--- /dev/null
+++ b/runtime/sysvar.c
@@ -0,0 +1,200 @@
+/* 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
+ *
+ * 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..bcd331ec
--- /dev/null
+++ b/runtime/vm.c
@@ -0,0 +1,528 @@
+/* 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 */
+RUNLOG_VAR("%lld", bRes); \
+ 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 */
+RUNLOG_VAR("%lld", bRes); \
+ 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 */
+RUNLOG_VAR("%lld", bRes); \
+ 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..82cd2165
--- /dev/null
+++ b/runtime/wti.c
@@ -0,0 +1,480 @@
+/* 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 "syslogd.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
+ */
+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;
+}
+
+
+/* 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..fcc7589c
--- /dev/null
+++ b/runtime/wtp.c
@@ -0,0 +1,624 @@
+/* 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 "syslogd.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;
+}
+
+
+/* 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;
+}
+
+
+/* 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
+ */
+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);
+}
+
+
+/* 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 */