From 4226f0dd4813277819406a4f13b460195d798f1a Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 15 Apr 2008 16:28:44 +0200 Subject: begin building runtime convenience library (does not build!) --- Makefile.am | 6 +- configure.ac | 19 + plugins/immark/Makefile.am | 2 +- queue.c | 2322 -------------------------------------------- queue.h | 205 ---- runtime/Makefile.am | 13 + runtime/queue.c | 2322 ++++++++++++++++++++++++++++++++++++++++++++ runtime/queue.h | 205 ++++ 8 files changed, 2562 insertions(+), 2532 deletions(-) delete mode 100644 queue.c delete mode 100644 queue.h create mode 100644 runtime/Makefile.am create mode 100644 runtime/queue.c create mode 100644 runtime/queue.h diff --git a/Makefile.am b/Makefile.am index 0e75710c..28e960e6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,8 +48,6 @@ rsyslogd_SOURCES = \ wtp.h \ wti.c \ wti.h \ - queue.c \ - queue.h \ sync.c \ sync.h \ obj.c \ @@ -93,7 +91,7 @@ rsyslogd_SOURCES = \ atomic.h rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) -rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) +rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) runtime/librsyslog.la rsyslogd_LDFLAGS = -export-dynamic man_MANS += rsyslogd.8 rsyslog.conf.5 @@ -179,7 +177,7 @@ EXTRA_DIST = \ COPYING.LESSER \ $(man_MANS) -SUBDIRS = . doc +SUBDIRS = doc runtime . SUBDIRS += plugins/immark plugins/imuxsock plugins/imtcp plugins/imudp plugins/omtesting diff --git a/configure.ac b/configure.ac index 2a5ef16b..9675a9b5 100644 --- a/configure.ac +++ b/configure.ac @@ -498,6 +498,23 @@ AC_SUBST(snmp_cflags) AC_SUBST(snmp_libs) +# support for building the rsyslogd runtime +AC_ARG_ENABLE(rsyslogrt, + [AS_HELP_STRING([--enable-rsyslogrt],[Build rsyslogrt @<:@default=yes@:>@])], + [case "${enableval}" in + yes) enable_rsyslogrt="yes" ;; + no) enable_rsyslogrt="no" ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-rsyslogrt) ;; + esac], + [enable_rsyslogrt=yes] +) +AM_CONDITIONAL(ENABLE_RSYSLOGRT, test x$enable_rsyslogrt = xyes) +CFLAGS="-I$(top_srcdir)/runtime $CFLAGS$ +rsrt_libs="" +AC_SUBST(rsrt_cflags) +AC_SUBST(rsrt_libs) + + # support for NOT building rsyslogd (useful for source-based packaging systems) AC_ARG_ENABLE(rsyslogd, [AS_HELP_STRING([--enable-rsyslogd],[Build rsyslogd @<:@default=yes@:>@])], @@ -615,6 +632,7 @@ AM_CONDITIONAL(ENABLE_IMTEMPLATE, test x$enable_imtemplate = xyes) AC_CONFIG_FILES([Makefile \ + runtime/Makefile \ doc/Makefile \ plugins/imudp/Makefile \ plugins/imtcp/Makefile \ @@ -657,5 +675,6 @@ echo "Debug mode enabled: $enable_debug" echo "Runtime Instrumentation enabled: $enable_rtinst" echo "openssl enabled: $enable_openssl" echo "valgrind support settings enabled: $enable_valgrind" +echo "rsyslogd runtime will be built: $enable_rsyslogrt" echo "rsyslogd will be built: $enable_rsyslogd" diff --git a/plugins/immark/Makefile.am b/plugins/immark/Makefile.am index 3dc0e408..477f45c9 100644 --- a/plugins/immark/Makefile.am +++ b/plugins/immark/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = immark.la immark_la_SOURCES = immark.c immark.h -immark_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +immark_la_CPPFLAGS = -I$(rsrt_cflags) -I$(top_srcdir) $(pthreads_cflags) immark_la_LDFLAGS = -module -avoid-version immark_la_LIBADD = diff --git a/queue.c b/queue.c deleted file mode 100644 index 0f58c545..00000000 --- a/queue.c +++ /dev/null @@ -1,2322 +0,0 @@ -/* queue.c - * - * This file implements the queue object and its several queueing methods. - * - * File begun on 2008-01-03 by RGerhards - * - * There is some in-depth documentation available in doc/dev_queue.html - * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it - * if you are getting aquainted to the object. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of the rsyslog runtime library. - * - * The rsyslog runtime library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The rsyslog runtime library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the rsyslog runtime library. If not, see . - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include /* required for HP UX */ -#include -#include - -#include "rsyslog.h" -#include "syslogd.h" -#include "queue.h" -#include "stringbuf.h" -#include "srUtils.h" -#include "obj.h" -#include "wtp.h" -#include "wti.h" - -/* static data */ -DEFobjStaticHelpers - -/* forward-definitions */ -rsRetVal queueChkPersist(queue_t *pThis); -static rsRetVal queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex); -static rsRetVal queueRateLimiter(queue_t *pThis); -static int queueChkStopWrkrDA(queue_t *pThis); -static int queueIsIdleDA(queue_t *pThis); -static rsRetVal queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave); -static rsRetVal queueConsumerCancelCleanup(void *arg1, void *arg2); -static rsRetVal queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex); - -/* some constants for queuePersist () */ -#define QUEUE_CHECKPOINT 1 -#define QUEUE_NO_CHECKPOINT 0 - -/* methods */ - - -/* get the overall queue size, which includes ungotten objects. Must only be called - * while mutex is locked! - * rgerhards, 2008-01-29 - */ -static inline int -queueGetOverallQueueSize(queue_t *pThis) -{ -#if 0 /* leave a bit in for debugging -- rgerhards, 2008-01-30 */ -BEGINfunc -dbgoprint((obj_t*) pThis, "queue size: %d (regular %d, ungotten %d)\n", - pThis->iQueueSize + pThis->iUngottenObjs, pThis->iQueueSize, pThis->iUngottenObjs); -ENDfunc -#endif - return pThis->iQueueSize + pThis->iUngottenObjs; -} - -/* --------------- code for disk-assisted (DA) queue modes -------------------- */ - - -/* returns the number of workers that should be advised at - * this point in time. The mutex must be locked when - * ths function is called. -- rgerhards, 2008-01-25 - */ -static inline rsRetVal queueAdviseMaxWorkers(queue_t *pThis) -{ - DEFiRet; - int iMaxWorkers; - - ISOBJ_TYPE_assert(pThis, queue); - - if(!pThis->bEnqOnly) { - if(pThis->bRunsDA) { - /* if we have not yet reached the high water mark, there is no need to start a - * worker. -- rgerhards, 2008-01-26 - */ - if(queueGetOverallQueueSize(pThis) >= pThis->iHighWtrMrk || pThis->bQueueStarted == 0) { - wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */ - } - } else { - if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) { - iMaxWorkers = 1; - } else { - iMaxWorkers = queueGetOverallQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1; - } - wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers); /* disk queues have always one worker */ - } - } - - RETiRet; -} - - -/* wait until we have a fully initialized DA queue. Sometimes, we need to - * sync with it, as we expect it for some function. - * rgerhards, 2008-02-27 - */ -static rsRetVal -queueWaitDAModeInitialized(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ASSERT(pThis->bRunsDA); - - while(pThis->bRunsDA != 2) { - d_pthread_cond_wait(&pThis->condDAReady, pThis->mut); - } - - RETiRet; -} - - -/* Destruct DA queue. This is the last part of DA-to-normal-mode - * transistion. This is called asynchronously and some time quite a - * while after the actual transistion. The key point is that we need to - * do it at some later time, because we need to destruct the DA queue. That, - * however, can not be done in a thread that has been signalled - * This is to be called when we revert back to our own queue. - * This function must be called with the queue mutex locked (the wti - * class ensures this). - * rgerhards, 2008-01-15 - */ -static rsRetVal -queueTurnOffDAMode(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ASSERT(pThis->bRunsDA); - - /* at this point, we need a fully initialized DA queue. So if it isn't, we finally need - * to wait for its startup... -- rgerhards, 2008-01-25 - */ - queueWaitDAModeInitialized(pThis); - - /* if we need to pull any data that we still need from the (child) disk queue, - * now would be the time to do so. At present, we do not need this, but I'd like to - * keep that comment if future need arises. - */ - - /* we need to check if the DA queue is empty because the DA worker may simply have - * terminated do to no new messages arriving. That does not, however, mean that the - * DA queue is empty. If there is still data in that queue, we do nothing and leave - * that for a later incarnation of this function (it will be called multiple times - * during the lifetime of DA-mode, depending on how often the DA worker receives an - * inactivity timeout. -- rgerhards, 2008-01-25 - */ - if(pThis->pqDA->iQueueSize == 0) { - pThis->bRunsDA = 0; /* tell the world we are back in non-DA mode */ - /* we destruct the queue object, which will also shutdown the queue worker. As the queue is empty, - * this will be quick. - */ - queueDestruct(&pThis->pqDA); /* and now we are ready to destruct the DA queue */ - dbgoprint((obj_t*) pThis, "disk-assistance has been turned off, disk queue was empty (iRet %d)\n", - iRet); - /* now we need to check if the regular queue has some messages. This may be the case - * when it is waiting that the high water mark is reached again. If so, we need to start up - * a regular worker. -- rgerhards, 2008-01-26 - */ - if(queueGetOverallQueueSize(pThis) > 0) { - queueAdviseMaxWorkers(pThis); - } - } - - /* TODO: we have a *really biiiiig* memory leak here: if the queue could not be persisted, all of - * its data elements are still in memory. That doesn't really matter if we are terminated, but on - * HUP this memory leaks. We MUST add a loop of destructor calls here. However, this takes time - * (possibly a lot), so it is probably best to have a config variable for that. - * Something for 3.11.1! - * rgerhards, 2008-01-30 - */ - - RETiRet; -} - - -/* check if we run in disk-assisted mode and record that - * setting for easy (and quick!) access in the future. This - * function must only be called from constructors and only - * from those that support disk-assisted modes (aka memory- - * based queue drivers). - * rgerhards, 2008-01-14 - */ -static rsRetVal -queueChkIsDA(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - if(pThis->pszFilePrefix != NULL) { - pThis->bIsDA = 1; - dbgoprint((obj_t*) pThis, "is disk-assisted, disk will be used on demand\n"); - } else { - dbgoprint((obj_t*) pThis, "is NOT disk-assisted\n"); - } - - RETiRet; -} - - -/* Start disk-assisted queue mode. All internal settings are changed. This is supposed - * to be called from the DA worker, which must have been started before. The most important - * chore of this function is to create the DA queue object. If that function fails, - * the DA worker should return with an appropriate state, which in turn should lead to - * a re-set to non-DA mode in the Enq process. The queue mutex must be locked when this - * function is called, else a number of races will happen. - * Please note that this function may be called *while* we in DA mode. This is due to the - * fact that the DA worker calls it and the DA worker may be suspended (and restarted) due - * to inactivity timeouts. - * rgerhards, 2008-01-15 - */ -static rsRetVal -queueStartDA(queue_t *pThis) -{ - DEFiRet; - uchar pszDAQName[128]; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pThis->bRunsDA == 2) /* check if already in (fully initialized) DA mode... */ - FINALIZE; /* ... then we are already done! */ - - /* create message queue */ - CHKiRet(queueConstruct(&pThis->pqDA, QUEUETYPE_DISK , 1, 0, pThis->pConsumer)); - - /* give it a name */ - snprintf((char*) pszDAQName, sizeof(pszDAQName)/sizeof(uchar), "%s[DA]", obj.GetName((obj_t*) pThis)); - obj.SetName((obj_t*) pThis->pqDA, pszDAQName); - - /* as the created queue is the same object class, we take the - * liberty to access its properties directly. - */ - pThis->pqDA->pqParent = pThis; - - CHKiRet(queueSetpUsr(pThis->pqDA, pThis->pUsr)); - CHKiRet(queueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax)); - CHKiRet(queueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown)); - CHKiRet(queueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize)); - CHKiRet(queueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix)); - CHKiRet(queueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt)); - CHKiRet(queueSettoActShutdown(pThis->pqDA, pThis->toActShutdown)); - CHKiRet(queueSettoEnq(pThis->pqDA, pThis->toEnq)); - CHKiRet(queueSetEnqOnly(pThis->pqDA, pThis->bDAEnqOnly, MUTEX_ALREADY_LOCKED)); - CHKiRet(queueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr)); - CHKiRet(queueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr)); - CHKiRet(queueSetiHighWtrMrk(pThis->pqDA, 0)); - CHKiRet(queueSetiDiscardMrk(pThis->pqDA, 0)); - if(pThis->toQShutdown == 0) { - CHKiRet(queueSettoQShutdown(pThis->pqDA, 0)); /* if the user really wants... */ - } else { - /* we use the shortest possible shutdown (0 is endless!) because when we run on disk AND - * have an obviously large backlog, we can't finish it in any case. So there is no point - * in holding shutdown longer than necessary. -- rgerhards, 2008-01-15 - */ - CHKiRet(queueSettoQShutdown(pThis->pqDA, 1)); - } - - iRet = queueStart(pThis->pqDA); - /* file not found is expected, that means it is no previous QIF available */ - if(iRet != RS_RET_OK && iRet != RS_RET_FILE_NOT_FOUND) - FINALIZE; /* something is wrong */ - - /* as we are right now starting DA mode because we are so busy, it is - * extremely unlikely that any regular worker is sleeping on empty queue. HOWEVER, - * we want to be on the safe side, and so we awake anyone that is waiting - * on one. So even if the scheduler plays badly with us, things should be - * quite well. -- rgerhards, 2008-01-15 - */ - wtpWakeupWrkr(pThis->pWtpReg); /* awake all workers, but not ourselves ;) */ - - pThis->bRunsDA = 2; /* we are now in DA mode, but not fully initialized */ - pThis->bChildIsDone = 0;/* set to 1 when child's worker detect queue is finished */ - pthread_cond_broadcast(&pThis->condDAReady); /* signal we are now initialized and ready to go ;) */ - - dbgoprint((obj_t*) pThis, "is now running in disk assisted mode, disk queue 0x%lx\n", - queueGetID(pThis->pqDA)); - -finalize_it: - if(iRet != RS_RET_OK) { - if(pThis->pqDA != NULL) { - queueDestruct(&pThis->pqDA); - } - dbgoprint((obj_t*) pThis, "error %d creating disk queue - giving up.\n", iRet); - pThis->bIsDA = 0; - } - - RETiRet; -} - - -/* initiate DA mode - * param bEnqOnly tells if the disk queue is to be run in enqueue-only mode. This may - * be needed during shutdown of memory queues which need to be persisted to disk. - * If this function fails (should not happen), DA mode is not turned on. - * rgerhards, 2008-01-16 - */ -static inline rsRetVal -queueInitDA(queue_t *pThis, int bEnqOnly, int bLockMutex) -{ - DEFiRet; - DEFVARS_mutexProtection; - uchar pszBuf[64]; - size_t lenBuf; - - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); - /* check if we already have a DA worker pool. If not, initiate one. Please note that the - * pool is created on first need but never again destructed (until the queue is). This - * is intentional. We assume that when we need it once, we may also need it on another - * occasion. Ressources used are quite minimal when no worker is running. - * rgerhards, 2008-01-24 - */ - if(pThis->pWtpDA == NULL) { - lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DA", obj.GetName((obj_t*) pThis)); - CHKiRet(wtpConstruct (&pThis->pWtpDA)); - CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf)); - CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrDA)); - CHKiRet(wtpSetpfIsIdle (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueIsIdleDA)); - CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerDA)); - CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void*pWti)) queueConsumerCancelCleanup)); - CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueStartDA)); - CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueTurnOffDAMode)); - CHKiRet(wtpSetpmutUsr (pThis->pWtpDA, pThis->mut)); - CHKiRet(wtpSetpcondBusy (pThis->pWtpDA, &pThis->notEmpty)); - CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpDA, 1)); - CHKiRet(wtpSettoWrkShutdown (pThis->pWtpDA, pThis->toWrkShutdown)); - CHKiRet(wtpSetpUsr (pThis->pWtpDA, pThis)); - CHKiRet(wtpConstructFinalize (pThis->pWtpDA)); - } - /* if we reach this point, we have a "good" DA worker pool */ - - /* indicate we now run in DA mode - this is reset by the DA worker if it fails */ - pThis->bRunsDA = 1; - pThis->bDAEnqOnly = bEnqOnly; - - /* now we must now adivse the wtp that we need one worker. If none is yet active, - * that will also start one up. If we forgot that step, everything would be stalled - * until the next enqueue request. - */ - wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* DA queues alsways have just one worker max */ - -finalize_it: - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - RETiRet; -} - - -/* check if we need to start disk assisted mode and send some signals to - * keep it running if we are already in it. It also checks if DA mode is - * partially initialized, in which case it waits for initialization to - * complete. - * rgerhards, 2008-01-14 - */ -static inline rsRetVal -queueChkStrtDA(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - - /* if we do not hit the high water mark, we have nothing to do */ - if(queueGetOverallQueueSize(pThis) != pThis->iHighWtrMrk) - ABORT_FINALIZE(RS_RET_OK); - - if(pThis->bRunsDA) { - /* then we need to signal that we are at the high water mark again. If that happens - * on our way down the queue, that doesn't matter, because then nobody is waiting - * on the condition variable. - * (Remember that a DA queue stops draining the queue once it has reached the low - * water mark and restarts it when the high water mark is reached again - this is - * what this code here is responsible for. Please note that all workers may have been - * terminated due to the inactivity timeout, thus we need to advise the pool that - * we need at least one). - */ - dbgoprint((obj_t*) pThis, "%d entries - passed high water mark in DA mode, send notify\n", - queueGetOverallQueueSize(pThis)); - queueAdviseMaxWorkers(pThis); - } else { - /* this is the case when we are currently not running in DA mode. So it is time - * to turn it back on. - */ - dbgoprint((obj_t*) pThis, "%d entries - passed high water mark for disk-assisted mode, initiating...\n", - queueGetOverallQueueSize(pThis)); - queueInitDA(pThis, QUEUE_MODE_ENQDEQ, MUTEX_ALREADY_LOCKED); /* initiate DA mode */ - } - -finalize_it: - RETiRet; -} - - -/* --------------- end code for disk-assisted queue modes -------------------- */ - - -/* Now, we define type-specific handlers. The provide a generic functionality, - * but for this specific type of queue. The mapping to these handlers happens during - * queue construction. Later on, handlers are called by pointers present in the - * queue instance object. - */ - -/* -------------------- fixed array -------------------- */ -static rsRetVal qConstructFixedArray(queue_t *pThis) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - if(pThis->iMaxQueueSize == 0) - ABORT_FINALIZE(RS_RET_QSIZE_ZERO); - - if((pThis->tVars.farray.pBuf = malloc(sizeof(void *) * pThis->iMaxQueueSize)) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - pThis->tVars.farray.head = 0; - pThis->tVars.farray.tail = 0; - - queueChkIsDA(pThis); - -finalize_it: - RETiRet; -} - - -static rsRetVal qDestructFixedArray(queue_t *pThis) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - if(pThis->tVars.farray.pBuf != NULL) - free(pThis->tVars.farray.pBuf); - - RETiRet; -} - -static rsRetVal qAddFixedArray(queue_t *pThis, void* in) -{ - DEFiRet; - - ASSERT(pThis != NULL); - pThis->tVars.farray.pBuf[pThis->tVars.farray.tail] = in; - pThis->tVars.farray.tail++; - if (pThis->tVars.farray.tail == pThis->iMaxQueueSize) - pThis->tVars.farray.tail = 0; - - RETiRet; -} - -static rsRetVal qDelFixedArray(queue_t *pThis, void **out) -{ - DEFiRet; - - ASSERT(pThis != NULL); - *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.head]; - - pThis->tVars.farray.head++; - if (pThis->tVars.farray.head == pThis->iMaxQueueSize) - pThis->tVars.farray.head = 0; - - RETiRet; -} - - -/* -------------------- linked list -------------------- */ - -/* first some generic functions which are also used for the unget linked list */ - -static inline rsRetVal queueAddLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, void* pUsr) -{ - DEFiRet; - qLinkedList_t *pEntry; - - ASSERT(ppRoot != NULL); - ASSERT(ppLast != NULL); - - if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - pEntry->pNext = NULL; - pEntry->pUsr = pUsr; - - if(*ppRoot == NULL) { - *ppRoot = *ppLast = pEntry; - } else { - (*ppLast)->pNext = pEntry; - *ppLast = pEntry; - } - -finalize_it: - RETiRet; -} - -static inline rsRetVal queueDelLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, obj_t **ppUsr) -{ - DEFiRet; - qLinkedList_t *pEntry; - - ASSERT(ppRoot != NULL); - ASSERT(ppLast != NULL); - ASSERT(ppUsr != NULL); - ASSERT(*ppRoot != NULL); - - pEntry = *ppRoot; - *ppUsr = pEntry->pUsr; - - if(*ppRoot == *ppLast) { - *ppRoot = NULL; - *ppLast = NULL; - } else { - *ppRoot = pEntry->pNext; - } - free(pEntry); - - RETiRet; -} - -/* end generic functions which are also used for the unget linked list */ - - -static rsRetVal qConstructLinkedList(queue_t *pThis) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - pThis->tVars.linklist.pRoot = 0; - pThis->tVars.linklist.pLast = 0; - - queueChkIsDA(pThis); - - RETiRet; -} - - -static rsRetVal qDestructLinkedList(queue_t __attribute__((unused)) *pThis) -{ - DEFiRet; - - /* with the linked list type, there is nothing to do here. The - * reason is that the Destructor is only called after all entries - * have bene taken off the queue. In this case, there is nothing - * dynamic left with the linked list. - */ - - RETiRet; -} - -static rsRetVal qAddLinkedList(queue_t *pThis, void* pUsr) -{ - DEFiRet; - - iRet = queueAddLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, pUsr); -#if 0 - qLinkedList_t *pEntry; - - ASSERT(pThis != NULL); - if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - pEntry->pNext = NULL; - pEntry->pUsr = pUsr; - - if(pThis->tVars.linklist.pRoot == NULL) { - pThis->tVars.linklist.pRoot = pThis->tVars.linklist.pLast = pEntry; - } else { - pThis->tVars.linklist.pLast->pNext = pEntry; - pThis->tVars.linklist.pLast = pEntry; - } - -finalize_it: -#endif - RETiRet; -} - -static rsRetVal qDelLinkedList(queue_t *pThis, obj_t **ppUsr) -{ - DEFiRet; - iRet = queueDelLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, ppUsr); -#if 0 - qLinkedList_t *pEntry; - - ASSERT(pThis != NULL); - ASSERT(pThis->tVars.linklist.pRoot != NULL); - - pEntry = pThis->tVars.linklist.pRoot; - *ppUsr = pEntry->pUsr; - - if(pThis->tVars.linklist.pRoot == pThis->tVars.linklist.pLast) { - pThis->tVars.linklist.pRoot = NULL; - pThis->tVars.linklist.pLast = NULL; - } else { - pThis->tVars.linklist.pRoot = pEntry->pNext; - } - free(pEntry); - -#endif - RETiRet; -} - - -/* -------------------- disk -------------------- */ - - -static rsRetVal -queueLoadPersStrmInfoFixup(strm_t *pStrm, queue_t __attribute__((unused)) *pThis) -{ - DEFiRet; - ISOBJ_TYPE_assert(pStrm, strm); - ISOBJ_TYPE_assert(pThis, queue); - CHKiRet(strmSetDir(pStrm, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); -finalize_it: - RETiRet; -} - - -/* This method checks if we have a QIF file for the current queue (no matter of - * queue mode). Returns RS_RET_OK if we have a QIF file or an error status otherwise. - * rgerhards, 2008-01-15 - */ -static rsRetVal -queueHaveQIF(queue_t *pThis) -{ - DEFiRet; - uchar pszQIFNam[MAXFNAME]; - size_t lenQIFNam; - struct stat stat_buf; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pThis->pszFilePrefix == NULL) - ABORT_FINALIZE(RS_RET_NO_FILEPREFIX); - - /* Construct file name */ - lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", - (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); - - /* check if the file exists */ - if(stat((char*) pszQIFNam, &stat_buf) == -1) { - if(errno == ENOENT) { - dbgoprint((obj_t*) pThis, "no .qi file found\n"); - ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); - } else { - dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); - ABORT_FINALIZE(RS_RET_IO_ERROR); - } - } - /* If we reach this point, we have a .qi file */ - -finalize_it: - RETiRet; -} - - -/* The method loads the persistent queue information. - * rgerhards, 2008-01-11 - */ -static rsRetVal -queueTryLoadPersistedInfo(queue_t *pThis) -{ - DEFiRet; - strm_t *psQIF = NULL; - uchar pszQIFNam[MAXFNAME]; - size_t lenQIFNam; - struct stat stat_buf; - int iUngottenObjs; - obj_t *pUsr; - - ISOBJ_TYPE_assert(pThis, queue); - - /* Construct file name */ - lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", - (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); - - /* check if the file exists */ - if(stat((char*) pszQIFNam, &stat_buf) == -1) { - if(errno == ENOENT) { - dbgoprint((obj_t*) pThis, "clean startup, no .qi file found\n"); - ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); - } else { - dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); - ABORT_FINALIZE(RS_RET_IO_ERROR); - } - } - - /* If we reach this point, we have a .qi file */ - - CHKiRet(strmConstruct(&psQIF)); - CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_READ)); - CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); - CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); - CHKiRet(strmConstructFinalize(psQIF)); - - /* first, we try to read the property bag for ourselfs */ - CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF)); - - /* then the ungotten object queue */ - iUngottenObjs = pThis->iUngottenObjs; - pThis->iUngottenObjs = 0; /* will be incremented when we add objects! */ - - while(iUngottenObjs > 0) { - /* fill the queue from disk */ - CHKiRet(obj.Deserialize((void*) &pUsr, (uchar*)"msg", psQIF, NULL, NULL)); - queueUngetObj(pThis, pUsr, MUTEX_ALREADY_LOCKED); - --iUngottenObjs; /* one less */ - } - - /* and now the stream objects (some order as when persisted!) */ - CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF, - (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); - CHKiRet(obj.Deserialize(&pThis->tVars.disk.pRead, (uchar*) "strm", psQIF, - (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); - - CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pWrite)); - CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pRead)); - - /* OK, we could successfully read the file, so we now can request that it be - * deleted when we are done with the persisted information. - */ - pThis->bNeedDelQIF = 1; - -finalize_it: - if(psQIF != NULL) - strmDestruct(&psQIF); - - if(iRet != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "error %d reading .qi file - can not read persisted info (if any)\n", - iRet); - } - - RETiRet; -} - - -/* disk queue constructor. - * Note that we use a file limit of 10,000,000 files. That number should never pose a - * problem. If so, I guess the user has a design issue... But of course, the code can - * always be changed (though it would probably be more appropriate to increase the - * allowed file size at this point - that should be a config setting... - * rgerhards, 2008-01-10 - */ -static rsRetVal qConstructDisk(queue_t *pThis) -{ - DEFiRet; - int bRestarted = 0; - - ASSERT(pThis != NULL); - - /* and now check if there is some persistent information that needs to be read in */ - iRet = queueTryLoadPersistedInfo(pThis); - if(iRet == RS_RET_OK) - bRestarted = 1; - else if(iRet != RS_RET_FILE_NOT_FOUND) - FINALIZE; - - if(bRestarted == 1) { - ; - } else { - CHKiRet(strmConstruct(&pThis->tVars.disk.pWrite)); - CHKiRet(strmSetDir(pThis->tVars.disk.pWrite, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); - CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pWrite, 10000000)); - CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE)); - CHKiRet(strmSetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR)); - CHKiRet(strmConstructFinalize(pThis->tVars.disk.pWrite)); - - CHKiRet(strmConstruct(&pThis->tVars.disk.pRead)); - CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); - CHKiRet(strmSetDir(pThis->tVars.disk.pRead, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); - CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pRead, 10000000)); - CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pRead, STREAMMODE_READ)); - CHKiRet(strmSetsType(pThis->tVars.disk.pRead, STREAMTYPE_FILE_CIRCULAR)); - CHKiRet(strmConstructFinalize(pThis->tVars.disk.pRead)); - - - CHKiRet(strmSetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix)); - CHKiRet(strmSetFName(pThis->tVars.disk.pRead, pThis->pszFilePrefix, pThis->lenFilePrefix)); - } - - /* now we set (and overwrite in case of a persisted restart) some parameters which - * should always reflect the current configuration variables. Be careful by doing so, - * for example file name generation must not be changed as that would break the - * ability to read existing queue files. -- rgerhards, 2008-01-12 - */ -CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize)); -CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pRead, pThis->iMaxFileSize)); - -finalize_it: - RETiRet; -} - - -static rsRetVal qDestructDisk(queue_t *pThis) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - strmDestruct(&pThis->tVars.disk.pWrite); - strmDestruct(&pThis->tVars.disk.pRead); - - RETiRet; -} - -static rsRetVal qAddDisk(queue_t *pThis, void* pUsr) -{ - DEFiRet; - number_t nWriteCount; - - ASSERT(pThis != NULL); - - CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, &nWriteCount)); - CHKiRet((objSerialize(pUsr))(pUsr, pThis->tVars.disk.pWrite)); - CHKiRet(strmFlush(pThis->tVars.disk.pWrite)); - CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */ - - pThis->tVars.disk.sizeOnDisk += nWriteCount; - - dbgoprint((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets\n", - nWriteCount, pThis->tVars.disk.sizeOnDisk); - -finalize_it: - RETiRet; -} - -static rsRetVal qDelDisk(queue_t *pThis, void **ppUsr) -{ - DEFiRet; - - int64 offsIn; - int64 offsOut; - - CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsIn)); - CHKiRet(obj.Deserialize(ppUsr, (uchar*) "msg", pThis->tVars.disk.pRead, NULL, NULL)); - CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsOut)); - - /* This time it is a bit tricky: we free disk space only upon file deletion. So we need - * to keep track of what we have read until we get an out-offset that is lower than the - * in-offset (which indicates file change). Then, we can subtract the whole thing from - * the on-disk size. -- rgerhards, 2008-01-30 - */ - if(offsIn < offsOut) { - pThis->tVars.disk.bytesRead += offsOut - offsIn; - } else { - pThis->tVars.disk.sizeOnDisk -= pThis->tVars.disk.bytesRead; - pThis->tVars.disk.bytesRead = offsOut; - dbgoprint((obj_t*) pThis, "a file has been deleted, now %lld octets disk space used\n", pThis->tVars.disk.sizeOnDisk); - /* awake possibly waiting enq process */ - pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */ - } - -finalize_it: - RETiRet; -} - -/* -------------------- direct (no queueing) -------------------- */ -static rsRetVal qConstructDirect(queue_t __attribute__((unused)) *pThis) -{ - return RS_RET_OK; -} - - -static rsRetVal qDestructDirect(queue_t __attribute__((unused)) *pThis) -{ - return RS_RET_OK; -} - -static rsRetVal qAddDirect(queue_t *pThis, void* pUsr) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - /* calling the consumer is quite different here than it is from a worker thread */ - /* we need to provide the consumer's return value back to the caller because in direct - * mode the consumer probably has a lot to convey (which get's lost in the other modes - * because they are asynchronous. But direct mode is deliberately synchronous. - * rgerhards, 2008-02-12 - */ - iRet = pThis->pConsumer(pThis->pUsr, pUsr); - - RETiRet; -} - -static rsRetVal qDelDirect(queue_t __attribute__((unused)) *pThis, __attribute__((unused)) void **out) -{ - return RS_RET_OK; -} - - -/* --------------- end type-specific handlers -------------------- */ - - -/* unget a user pointer that has been dequeued. This functionality is especially important - * for consumer cancel cleanup handlers. To support it, a short list of ungotten user pointers - * is maintened in memory. - * rgerhards, 2008-01-20 - */ -static rsRetVal -queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex) -{ - DEFiRet; - DEFVARS_mutexProtection; - - ISOBJ_TYPE_assert(pThis, queue); - ISOBJ_assert(pUsr); /* TODO: we aborted right at this place at least 3 times -- race? 2008-02-28, -03-10, -03-15 - The second time I noticed it the queue was in destruction with NO worker threads - running. The pUsr ptr was totally off and provided no clue what it may be pointing - at (except that it looked like the static data pool). Both times, the abort happend - inside an action queue */ - - dbgoprint((obj_t*) pThis, "ungetting user object %s\n", obj.GetName(pUsr)); - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); - iRet = queueAddLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, pUsr); - ++pThis->iUngottenObjs; /* indicate one more */ - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - - RETiRet; -} - - -/* dequeues a user pointer from the ungotten queue. Pointers from there should always be - * dequeued first. - * - * This function must only be called when the mutex is locked! - * - * rgerhards, 2008-01-29 - */ -static rsRetVal -queueGetUngottenObj(queue_t *pThis, obj_t **ppUsr) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ASSERT(ppUsr != NULL); - - iRet = queueDelLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, ppUsr); - --pThis->iUngottenObjs; /* indicate one less */ - dbgoprint((obj_t*) pThis, "dequeued ungotten user object %s\n", obj.GetName(*ppUsr)); - - RETiRet; -} - - -/* generic code to add a queue entry - * We use some specific code to most efficiently support direct mode - * queues. This is justified in spite of the gain and the need to do some - * things truely different. -- rgerhards, 2008-02-12 - */ -static rsRetVal -queueAdd(queue_t *pThis, void *pUsr) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - CHKiRet(pThis->qAdd(pThis, pUsr)); - - if(pThis->qType != QUEUETYPE_DIRECT) { - ++pThis->iQueueSize; - dbgoprint((obj_t*) pThis, "entry added, size now %d entries\n", pThis->iQueueSize); - } - -finalize_it: - RETiRet; -} - - -/* generic code to remove a queue entry - * rgerhards, 2008-01-29: we must first see if there is any object in the - * ungotten list and, if so, dequeue it first. - */ -static rsRetVal -queueDel(queue_t *pThis, void *pUsr) -{ - DEFiRet; - - ASSERT(pThis != NULL); - - /* we do NOT abort if we encounter an error, because otherwise the queue - * will not be decremented, what will most probably result in an endless loop. - * If we decrement, however, we may lose a message. But that is better than - * losing the whole process because it loops... -- rgerhards, 2008-01-03 - */ - if(pThis->iUngottenObjs > 0) { - iRet = queueGetUngottenObj(pThis, (obj_t**) pUsr); - } else { - iRet = pThis->qDel(pThis, pUsr); - --pThis->iQueueSize; - } - - dbgoprint((obj_t*) pThis, "entry deleted, state %d, size now %d entries\n", - iRet, pThis->iQueueSize); - - RETiRet; -} - - -/* This function shuts down all worker threads and waits until they - * have terminated. If they timeout, they are cancelled. Parameters have been set - * before this function is called so that DA queues will be fully persisted to - * disk (if configured to do so). - * rgerhards, 2008-01-24 - * Please note that this function shuts down BOTH the parent AND the child queue - * in DA case. This is necessary because their timeouts are tightly coupled. Most - * importantly, the timeouts would be applied twice (or logic be extremely - * complex) if each would have its own shutdown. The function does not self check - * this condition - the caller must make sure it is not called with a parent. - */ -static rsRetVal queueShutdownWorkers(queue_t *pThis) -{ - DEFiRet; - DEFVARS_mutexProtection; - struct timespec tTimeout; - rsRetVal iRetLocal; - - ISOBJ_TYPE_assert(pThis, queue); - ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ - - dbgoprint((obj_t*) pThis, "initiating worker thread shutdown sequence\n"); - - /* we reduce the low water mark in any case. This is not absolutely necessary, but - * it is useful because we enable DA mode at several spots below and so we do not need - * to think about the low water mark each time. - */ - pThis->iHighWtrMrk = 1; /* if we do not do this, the DA queue will not stop! */ - pThis->iLowWtrMrk = 0; - - /* first try to shutdown the queue within the regular shutdown period */ - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ - if(queueGetOverallQueueSize(pThis) > 0) { - if(pThis->bRunsDA) { - /* We may have waited on the low water mark. As it may have changed, we - * see if we reactivate the worker. - */ - wtpAdviseMaxWorkers(pThis->pWtpDA, 1); - } - } - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - - /* Now wait for the queue's workers to shut down. Note that we run into the code even if we just found - * out there are no active workers - that doesn't matter: the wtp knows about that and so will - * return immediately. - * We do not yet care about the DA worker - that will be handled down later in the process. - * Note that we must not request shutdown right now - that may introduce a race: if the regular queue - * still runs DA assisted and the DA worker gets scheduled first, it will terminate itself (if the DA - * queue happens to be empty at that instant). Then the regular worker enqueues messages, what will lead - * to a restart of the worker. Of course, everything will continue to run, but in a bit sub-optimal way - * (from a performance point of view). So we don't do anything right now. The DA queue will continue to - * process messages and shutdown itself in any case if there is nothing to do. So we don't loose anything - * by not requesting shutdown now. - * rgerhards, 2008-01-25 - */ - /* first calculate absolute timeout - we need the absolute value here, because we need to coordinate - * shutdown of both the regular and DA queue on *the same* timeout. - */ - timeoutComp(&tTimeout, pThis->toQShutdown); - dbgoprint((obj_t*) pThis, "trying shutdown of regular workers\n"); - iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN, &tTimeout); - if(iRetLocal == RS_RET_TIMED_OUT) { - dbgoprint((obj_t*) pThis, "regular shutdown timed out on primary queue (this is OK)\n"); - } else { - /* OK, the regular queue is now shut down. So we can now wait for the DA queue (if running DA) */ - dbgoprint((obj_t*) pThis, "regular queue workers shut down.\n"); - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ - if(pThis->bRunsDA) { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - dbgoprint((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n", - queueGetID(pThis->pqDA)); - /* we use the same absolute timeout as above, so we do not use more than the configured - * timeout interval! - */ - dbgoprint((obj_t*) pThis, "trying shutdown of DA workers\n"); - iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); - if(iRetLocal == RS_RET_TIMED_OUT) { - dbgoprint((obj_t*) pThis, "shutdown timed out on DA queue (this is OK)\n"); - } - } else { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - } - } - - /* when we reach this point, both queues are either empty or the regular queue shutdown timeout - * has expired. Now we need to check if we are configured to not loose messages. If so, we need - * to persist the queue to disk (this is only possible if the queue is DA-enabled). We must also - * set the primary queue to SHUTDOWN_IMMEDIATE, as it shall now terminate as soon as its consumer - * is done. This is especially important as we otherwise may interfere with queue order while the - * DA consumer is running. -- rgerhards, 2008-01-27 - * Note: there was a note that we should not wait eternally on the DA worker if we run in - * enqueue-only note. I have reviewed the code and think there is no need for this check. Howerver, - * I'd like to keep this note in here should we happen to run into some related trouble. - * rgerhards, 2008-01-28 - */ - wtpSetState(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE); /* set primary queue to shutdown only */ - - /* at this stage, we need to have the DA worker properly initialized and running (if there is one) */ - if(pThis->bRunsDA) - queueWaitDAModeInitialized(pThis); - - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ - /* optimize parameters for shutdown of DA-enabled queues */ - if(pThis->bIsDA && queueGetOverallQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) { - /* switch to enqueue-only mode so that no more actions happen */ - if(pThis->bRunsDA == 0) { - queueInitDA(pThis, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to DA mode */ - } else { - /* TODO: RACE: we may reach this point when the DA worker has been initialized (state 1) - * but is not yet running (state 2). In this case, pThis->pqDA is NULL! rgerhards, 2008-02-27 - */ - queueSetEnqOnly(pThis->pqDA, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to enqueue-only mode */ - } - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - /* make sure we do not timeout before we are done */ - dbgoprint((obj_t*) pThis, "bSaveOnShutdown configured, eternal timeout set\n"); - timeoutComp(&tTimeout, QUEUE_TIMEOUT_ETERNAL); - /* and run the primary queue's DA worker to drain the queue */ - iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); - if(iRetLocal != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying to shut down primary queue in disk save mode, " - "continuing, but results are unpredictable\n", iRetLocal); - } - } else { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - } - - /* now the primary queue is either empty, persisted to disk - or set to loose messages. So we - * can now request immediate shutdown of any remaining workers. Note that if bSaveOnShutdown was set, - * the queue is now empty. If regular workers are still running, and try to pull the next message, - * they will automatically terminate as there no longer is any message left to process. - */ - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ - if(queueGetOverallQueueSize(pThis) > 0) { - timeoutComp(&tTimeout, pThis->toActShutdown); - if(wtpGetCurNumWrkr(pThis->pWtpReg, LOCK_MUTEX) > 0) { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - dbgoprint((obj_t*) pThis, "trying immediate shutdown of regular workers\n"); - iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); - if(iRetLocal == RS_RET_TIMED_OUT) { - dbgoprint((obj_t*) pThis, "immediate shutdown timed out on primary queue (this is acceptable and " - "triggers cancellation)\n"); - } else if(iRetLocal != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the primary queue " - "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); - } - /* we need to re-aquire the mutex for the next check in this case! */ - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ - } - if(pThis->bIsDA && wtpGetCurNumWrkr(pThis->pWtpDA, LOCK_MUTEX) > 0) { - /* and now the same for the DA queue */ - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - dbgoprint((obj_t*) pThis, "trying immediate shutdown of DA workers\n"); - iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); - if(iRetLocal == RS_RET_TIMED_OUT) { - dbgoprint((obj_t*) pThis, "immediate shutdown timed out on DA queue (this is acceptable and " - "triggers cancellation)\n"); - } else if(iRetLocal != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the DA queue " - "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); - } - } else { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - } - } else { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - } - - /* Now queue workers should have terminated. If not, we need to cancel them as we have applied - * all timeout setting. If any worker in any queue still executes, its consumer is possibly - * long-running and cancelling is the only way to get rid of it. Note that the - * cancellation handler will probably re-queue a user pointer, so the queue's enqueue - * function is still needed (what is no problem as we do not yet destroy the queue - but I - * thought it's a good idea to mention that fact). -- rgerhards, 2008-01-25 - */ - dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the primary queue\n"); - iRetLocal = wtpCancelAll(pThis->pWtpReg); /* returns immediately if all threads already have terminated */ - if(iRetLocal != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel primary queue worker " - "threads, continuing, but results are unpredictable\n", iRetLocal); - } - - - /* TODO: think: do we really need to do this here? Can't it happen on DA queue destruction? If we - * disable it, we get an assertion... I think this is OK, as we need to have a certain order and - * canceling the DA workers here ensures that order. But in any instant, we may have a look at this - * code after we have reaced the milestone. -- rgerhards, 2008-01-27 - */ - /* ... and now the DA queue, if it exists (should always be after the primary one) */ - if(pThis->pqDA != NULL) { - dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the DA queue\n"); - iRetLocal = wtpCancelAll(pThis->pqDA->pWtpReg); /* returns immediately if all threads already have terminated */ - if(iRetLocal != RS_RET_OK) { - dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel DA queue worker " - "threads, continuing, but results are unpredictable\n", iRetLocal); - } - } - - /* ... finally ... all worker threads have terminated :-) - * Well, more precisely, they *are in termination*. Some cancel cleanup handlers - * may still be running. - */ - dbgoprint((obj_t*) pThis, "worker threads terminated, remaining queue size %d.\n", queueGetOverallQueueSize(pThis)); - - RETiRet; -} - - - -/* Constructor for the queue object - * This constructs the data structure, but does not yet start the queue. That - * is done by queueStart(). The reason is that we want to give the caller a chance - * to modify some parameters before the queue is actually started. - */ -rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, - int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)) -{ - DEFiRet; - queue_t *pThis; - - ASSERT(ppThis != NULL); - ASSERT(pConsumer != NULL); - ASSERT(iWorkerThreads >= 0); - - if((pThis = (queue_t *)calloc(1, sizeof(queue_t))) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - /* we have an object, so let's fill the properties */ - objConstructSetObjInfo(pThis); - if((pThis->pszSpoolDir = (uchar*) strdup((char*)glblGetWorkDir())) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - /* set some water marks so that we have useful defaults if none are set specifically */ - pThis->iFullDlyMrk = (iMaxQueueSize < 100) ? iMaxQueueSize : 100; /* 100 should be far sufficient */ - pThis->iLightDlyMrk = iMaxQueueSize - (iMaxQueueSize / 100) * 70; /* default 70% */ - - pThis->lenSpoolDir = strlen((char*)pThis->pszSpoolDir); - pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */ - pThis->iQueueSize = 0; - pThis->iMaxQueueSize = iMaxQueueSize; - pThis->pConsumer = pConsumer; - pThis->iNumWorkerThreads = iWorkerThreads; - pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ - - pThis->pszFilePrefix = NULL; - pThis->qType = qType; - - /* set type-specific handlers and other very type-specific things (we can not totally hide it...) */ - switch(qType) { - case QUEUETYPE_FIXED_ARRAY: - pThis->qConstruct = qConstructFixedArray; - pThis->qDestruct = qDestructFixedArray; - pThis->qAdd = qAddFixedArray; - pThis->qDel = qDelFixedArray; - break; - case QUEUETYPE_LINKEDLIST: - pThis->qConstruct = qConstructLinkedList; - pThis->qDestruct = qDestructLinkedList; - pThis->qAdd = qAddLinkedList; - pThis->qDel = (rsRetVal (*)(queue_t*,void**)) qDelLinkedList; - break; - case QUEUETYPE_DISK: - pThis->qConstruct = qConstructDisk; - pThis->qDestruct = qDestructDisk; - pThis->qAdd = qAddDisk; - pThis->qDel = qDelDisk; - /* special handling */ - pThis->iNumWorkerThreads = 1; /* we need exactly one worker */ - break; - case QUEUETYPE_DIRECT: - pThis->qConstruct = qConstructDirect; - pThis->qDestruct = qDestructDirect; - pThis->qAdd = qAddDirect; - pThis->qDel = qDelDirect; - break; - } - -finalize_it: - OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP - RETiRet; -} - - -/* cancellation cleanup handler for queueWorker () - * Updates admin structure and frees ressources. - * Params: - * arg1 - user pointer (in this case a queue_t) - * arg2 - user data pointer (in this case a queue data element, any object [queue's pUsr ptr!]) - * Note that arg2 may be NULL, in which case no dequeued but unprocessed pUsr exists! - * rgerhards, 2008-01-16 - */ -static rsRetVal -queueConsumerCancelCleanup(void *arg1, void *arg2) -{ - DEFiRet; - - queue_t *pThis = (queue_t*) arg1; - obj_t *pUsr = (obj_t*) arg2; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pUsr != NULL) { - /* make sure the data element is not lost */ - dbgoprint((obj_t*) pThis, "cancelation cleanup handler consumer called, we need to unget one user data element\n"); - CHKiRet(queueUngetObj(pThis, pUsr, LOCK_MUTEX)); - } - -finalize_it: - RETiRet; -} - - - -/* This function checks if the provided message shall be discarded and does so, if needed. - * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to - * provide real-time creation of spool files. - * Note: cached copies of iQueueSize and bRunsDA are provided so that no mutex locks are required. - * The caller must have obtained them while the mutex was locked. Of course, these values may no - * longer be current, but that is OK for the discard check. At worst, the message is either processed - * or discarded when it should not have been. As discarding is in itself somewhat racy and erratic, - * that is no problems for us. This function MUST NOT lock the queue mutex, it could result in - * deadlocks! - * If the message is discarded, it can no longer be processed by the caller. So be sure to check - * the return state! - * rgerhards, 2008-01-24 - */ -static int queueChkDiscardMsg(queue_t *pThis, int iQueueSize, int bRunsDA, void *pUsr) -{ - DEFiRet; - rsRetVal iRetLocal; - int iSeverity; - - ISOBJ_TYPE_assert(pThis, queue); - ISOBJ_assert(pUsr); - - if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk && bRunsDA == 0) { - iRetLocal = objGetSeverity(pUsr, &iSeverity); - if(iRetLocal == RS_RET_OK && iSeverity >= pThis->iDiscardSeverity) { - dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), discarded severity %d message\n", - iQueueSize, iSeverity); - objDestruct(pUsr); - ABORT_FINALIZE(RS_RET_QUEUE_FULL); - } else { - dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), but could not drop msg " - "(iRet: %d, severity %d)\n", iQueueSize, iRetLocal, iSeverity); - } - } - -finalize_it: - RETiRet; -} - - -/* dequeue the queued object for the queue consumers. - * rgerhards, 2008-10-21 - */ -static rsRetVal -queueDequeueConsumable(queue_t *pThis, wti_t *pWti, int iCancelStateSave) -{ - DEFiRet; - void *pUsr; - int iQueueSize; - int bRunsDA; /* cache for early mutex release */ - - /* dequeue element (still protected from mutex) */ - iRet = queueDel(pThis, &pUsr); - queueChkPersist(pThis); - iQueueSize = queueGetOverallQueueSize(pThis); /* cache this for after mutex release */ - bRunsDA = pThis->bRunsDA; /* cache this for after mutex release */ - - /* We now need to save the user pointer for the cancel cleanup handler, BUT ONLY - * if we could successfully obtain a user pointer. Otherwise, we would bring the - * cancel cleanup handler into big troubles (and we did ;)). Note that we can - * NOT set the variable further below, as this may lead to an object leak. We - * may get cancelled before we reach that part of the code, so the only - * solution is to do it here. -- rgerhards, 2008-02-27 - */ - if(iRet == RS_RET_OK) { - pWti->pUsrp = pUsr; - } - - /* awake some flow-controlled sources if we can do this right now */ - /* TODO: this could be done better from a performance point of view -- do it only if - * we have someone waiting for the condition (or only when we hit the watermark right - * on the nail [exact value]) -- rgerhards, 2008-03-14 - */ - if(iQueueSize < pThis->iFullDlyMrk) { - pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk); - } - - if(iQueueSize < pThis->iLightDlyMrk) { - pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk); - } - - d_pthread_mutex_unlock(pThis->mut); - pthread_cond_signal(&pThis->notFull); - pthread_setcancelstate(iCancelStateSave, NULL); - /* WE ARE NO LONGER PROTECTED BY THE MUTEX */ - - /* do actual processing (the lengthy part, runs in parallel) - * If we had a problem while dequeing, we do not call the consumer, - * but we otherwise ignore it. This is in the hopes that it will be - * self-healing. However, this is really not a good thing. - * rgerhards, 2008-01-03 - */ - if(iRet != RS_RET_OK) - FINALIZE; - - /* we are running in normal, non-disk-assisted mode do a quick check if we need to drain the queue. - * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to - * provide real-time creation of spool files. - * Note: It is OK to use the cached iQueueSize here, because it does not hurt if it is slightly wrong. - */ - CHKiRet(queueChkDiscardMsg(pThis, iQueueSize, bRunsDA, pUsr)); - -finalize_it: - if(iRet != RS_RET_OK && iRet != RS_RET_DISCARDMSG) { - dbgoprint((obj_t*) pThis, "error %d dequeueing element - ignoring, but strange things " - "may happen\n", iRet); - } - RETiRet; -} - - -/* The rate limiter - * - * Here we may wait if a dequeue time window is defined or if we are - * rate-limited. TODO: If we do so, we should also look into the - * way new worker threads are spawned. Obviously, it doesn't make much - * sense to spawn additional worker threads when none of them can do any - * processing. However, it is deemed acceptable to allow this for an initial - * implementation of the timeframe/rate limiting feature. - * Please also note that these feature could also be implemented at the action - * level. However, that would limit them to be used together with actions. We have - * taken the broader approach, moving it right into the queue. This is even - * necessary if we want to prevent spawning of multiple unnecessary worker - * threads as described above. -- rgerhards, 2008-04-02 - * - * - * time window: tCurr is current time; tFrom is start time, tTo is end time (in mil 24h format). - * We may have tFrom = 4, tTo = 10 --> run from 4 to 10 hrs. nice and happy - * we may also have tFrom= 22, tTo = 4 -> run from 10pm to 4am, which is actually two - * windows: 0-4; 22-23:59 - * so when to run? Let's assume we have 3am - * - * if(tTo < tFrom) { - * if(tCurr < tTo [3 < 4] || tCurr > tFrom [3 > 22]) - * do work - * else - * sleep for tFrom - tCurr "hours" [22 - 5 --> 17] - * } else { - * if(tCurr >= tFrom [3 >= 4] && tCurr < tTo [3 < 10]) - * do work - * else - * sleep for tTo - tCurr "hours" [4 - 3 --> 1] - * } - * - * Bottom line: we need to check which type of window we have and need to adjust our - * logic accordingly. Of course, sleep calculations need to be done up to the minute, - * but you get the idea from the code above. - */ -static rsRetVal -queueRateLimiter(queue_t *pThis) -{ - DEFiRet; - int iDelay; - int iHrCurr; - time_t tCurr; - struct tm m; - - ISOBJ_TYPE_assert(pThis, queue); - - dbgoprint((obj_t*) pThis, "entering rate limiter\n"); - - iDelay = 0; - if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */ - /* time calls are expensive, so only do them when needed */ - time(&tCurr); - localtime_r(&tCurr, &m); - iHrCurr = m.tm_hour; - - if(pThis->iDeqtWinToHr < pThis->iDeqtWinFromHr) { - if(iHrCurr < pThis->iDeqtWinToHr || iHrCurr > pThis->iDeqtWinFromHr) { - ; /* do not delay */ - } else { - iDelay = (pThis->iDeqtWinFromHr - iHrCurr) * 3600; - /* this time, we are already into the next hour, so we need - * to subtract our current minute and seconds. - */ - iDelay -= m.tm_min * 60; - iDelay -= m.tm_sec; - } - } else { - if(iHrCurr >= pThis->iDeqtWinFromHr && iHrCurr < pThis->iDeqtWinToHr) { - ; /* do not delay */ - } else { - if(iHrCurr < pThis->iDeqtWinFromHr) { - iDelay = (pThis->iDeqtWinFromHr - iHrCurr - 1) * 3600; /* -1 as we are already in the hour */ - iDelay += (60 - m.tm_min) * 60; - iDelay += 60 - m.tm_sec; - } else { - iDelay = (24 - iHrCurr + pThis->iDeqtWinFromHr) * 3600; - /* this time, we are already into the next hour, so we need - * to subtract our current minute and seconds. - */ - iDelay -= m.tm_min * 60; - iDelay -= m.tm_sec; - } - } - } - } - - if(iDelay > 0) { - dbgoprint((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay); - srSleep(iDelay, 0); - } - - RETiRet; -} - - - -/* This is the queue consumer in the regular (non-DA) case. It is - * protected by the queue mutex, but MUST release it as soon as possible. - * rgerhards, 2008-01-21 - */ -static rsRetVal -queueConsumerReg(queue_t *pThis, wti_t *pWti, int iCancelStateSave) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ISOBJ_TYPE_assert(pWti, wti); - - CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); - CHKiRet(pThis->pConsumer(pThis->pUsr, pWti->pUsrp)); - - /* we now need to check if we should deliberately delay processing a bit - * and, if so, do that. -- rgerhards, 2008-01-30 - */ - if(pThis->iDeqSlowdown) { - dbgoprint((obj_t*) pThis, "sleeping %d microseconds as requested by config params\n", - pThis->iDeqSlowdown); - srSleep(pThis->iDeqSlowdown / 1000000, pThis->iDeqSlowdown % 1000000); - } - -finalize_it: - RETiRet; -} - - -/* This is a special consumer to feed the disk-queue in disk-assited mode. - * When active, our own queue more or less acts as a memory buffer to the disk. - * So this consumer just needs to drain the memory queue and submit entries - * to the disk queue. The disk queue will then call the actual consumer from - * the app point of view (we chain two queues here). - * When this method is entered, the mutex is always locked and needs to be unlocked - * as part of the processing. - * rgerhards, 2008-01-14 - */ -static rsRetVal -queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ISOBJ_TYPE_assert(pWti, wti); - - CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); - CHKiRet(queueEnqObj(pThis->pqDA, eFLOWCTL_NO_DELAY, pWti->pUsrp)); - -finalize_it: - dbgoprint((obj_t*) pThis, "DAConsumer returns with iRet %d\n", iRet); - RETiRet; -} - - -/* must only be called when the queue mutex is locked, else results - * are not stable! - * If we are a child, we have done our duty when the queue is empty. In that case, - * we can terminate. - * Version for the DA worker thread. NOTE: the pThis->bRunsDA is different from - * the DA queue - */ -static int -queueChkStopWrkrDA(queue_t *pThis) -{ - /* if our queue is in destruction, we drain to the DA queue and so we shall not terminate - * until we have done so. - */ - int bStopWrkr; - - BEGINfunc - - if(pThis->bEnqOnly) { - bStopWrkr = 1; - } else { - if(pThis->bRunsDA) { - ASSERT(pThis->pqDA != NULL); - if( pThis->pqDA->bEnqOnly - && pThis->pqDA->sizeOnDiskMax > 0 - && pThis->pqDA->tVars.disk.sizeOnDisk > pThis->pqDA->sizeOnDiskMax) { - /* this queue can never grow, so we can give up... */ - bStopWrkr = 1; - } else if(queueGetOverallQueueSize(pThis) < pThis->iHighWtrMrk && pThis->bQueueStarted == 1) { - bStopWrkr = 1; - } else { - bStopWrkr = 0; - } - } else { - bStopWrkr = 1; - } - } - - ENDfunc - return bStopWrkr; -} - - -/* must only be called when the queue mutex is locked, else results - * are not stable! - * If we are a child, we have done our duty when the queue is empty. In that case, - * we can terminate. - * Version for the regular worker thread. NOTE: the pThis->bRunsDA is different from - * the DA queue - */ -static int -queueChkStopWrkrReg(queue_t *pThis) -{ - return pThis->bEnqOnly || pThis->bRunsDA || (pThis->pqParent != NULL && queueGetOverallQueueSize(pThis) == 0); -} - - -/* must only be called when the queue mutex is locked, else results - * are not stable! DA queue version - */ -static int -queueIsIdleDA(queue_t *pThis) -{ - /* remember: iQueueSize is the DA queue size, not the main queue! */ - /* TODO: I think we need just a single function for DA and non-DA mode - but I leave it for now as is */ - return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); -} -/* must only be called when the queue mutex is locked, else results - * are not stable! Regular queue version - */ -static int -queueIsIdleReg(queue_t *pThis) -{ -#if 0 /* enable for performance testing */ - int ret; - ret = queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk); - if(ret) fprintf(stderr, "queue is idle\n"); - return ret; -#else - /* regular code! */ - return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); -#endif -} - - -/* This function is called when a worker thread for the regular queue is shut down. - * If we are the primary queue, this is not really interesting to us. If, however, - * we are the DA (child) queue, that means the DA queue is empty. In that case, we - * need to signal the parent queue's DA worker, so that it can terminate DA mode. - * rgerhards, 2008-01-26 - * rgerhards, 2008-02-27: HOWEVER, in a shutdown condition, it may be that the parent's worker thread pool - * has already been terminated and destructed. This *is* a legal condition and happens - * from time to time in practice. So we need to signal only if there still is a - * parent DA worker queue. Please keep in mind that the the parent's DA worker - * pool is DIFFERENT from our (DA queue) regular worker pool. So when the parent's - * pWtpDA is destructed, there can still be some of our (DAq/wtp) threads be running. - * I am telling this, because I, too, always get confused by those... - */ -static rsRetVal -queueRegOnWrkrShutdown(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pThis->pqParent != NULL) { - pThis->pqParent->bChildIsDone = 1; /* indicate we are done */ - if(pThis->pqParent->pWtpDA != NULL) { /* see comment in function header from 2008-02-27 */ - wtpAdviseMaxWorkers(pThis->pqParent->pWtpDA, 1); /* reactivate DA worker (always 1) */ - } - } - - RETiRet; -} - - -/* The following function is called when a regular queue worker starts up. We need this - * hook to indicate in the parent queue (if we are a child) that we are not done yet. - */ -static rsRetVal -queueRegOnWrkrStartup(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pThis->pqParent != NULL) { - pThis->pqParent->bChildIsDone = 0; - } - - RETiRet; -} - - -/* start up the queue - it must have been constructed and parameters defined - * before. - */ -rsRetVal queueStart(queue_t *pThis) /* this is the ConstructionFinalizer */ -{ - DEFiRet; - rsRetVal iRetLocal; - int bInitialized = 0; /* is queue already initialized? */ - uchar pszBuf[64]; - size_t lenBuf; - - ASSERT(pThis != NULL); - - /* we need to do a quick check if our water marks are set plausible. If not, - * we correct the most important shortcomings. TODO: do that!!!! -- rgerhards, 2008-03-14 - */ - - /* finalize some initializations that could not yet be done because it is - * influenced by properties which might have been set after queueConstruct () - */ - if(pThis->pqParent == NULL) { - pThis->mut = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); - pthread_mutex_init(pThis->mut, NULL); - } else { - /* child queue, we need to use parent's mutex */ - dbgoprint((obj_t*) pThis, "I am a child\n"); - pThis->mut = pThis->pqParent->mut; - } - - pthread_mutex_init(&pThis->mutThrdMgmt, NULL); - pthread_cond_init (&pThis->condDAReady, NULL); - pthread_cond_init (&pThis->notFull, NULL); - pthread_cond_init (&pThis->notEmpty, NULL); - pthread_cond_init (&pThis->belowFullDlyWtrMrk, NULL); - pthread_cond_init (&pThis->belowLightDlyWtrMrk, NULL); - - /* call type-specific constructor */ - CHKiRet(pThis->qConstruct(pThis)); /* this also sets bIsDA */ - - dbgoprint((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, qsize %d, child %d starting\n", - pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize, - queueGetOverallQueueSize(pThis), pThis->pqParent == NULL ? 0 : 1); - - if(pThis->qType == QUEUETYPE_DIRECT) - FINALIZE; /* with direct queues, we are already finished... */ - - /* create worker thread pools for regular operation. The DA pool is created on an as-needed - * basis, which potentially means never under most circumstances. - */ - lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:Reg", obj.GetName((obj_t*) pThis)); - CHKiRet(wtpConstruct (&pThis->pWtpReg)); - CHKiRet(wtpSetDbgHdr (pThis->pWtpReg, pszBuf, lenBuf)); - CHKiRet(wtpSetpfRateLimiter (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRateLimiter)); - CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrReg)); - CHKiRet(wtpSetpfIsIdle (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueIsIdleReg)); - CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerReg)); - CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void*pWti))queueConsumerCancelCleanup)); - CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrStartup)); - CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrShutdown)); - CHKiRet(wtpSetpmutUsr (pThis->pWtpReg, pThis->mut)); - CHKiRet(wtpSetpcondBusy (pThis->pWtpReg, &pThis->notEmpty)); - CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpReg, pThis->iNumWorkerThreads)); - CHKiRet(wtpSettoWrkShutdown (pThis->pWtpReg, pThis->toWrkShutdown)); - CHKiRet(wtpSetpUsr (pThis->pWtpReg, pThis)); - CHKiRet(wtpConstructFinalize (pThis->pWtpReg)); - - /* initialize worker thread instances */ - if(pThis->bIsDA) { - /* If we are disk-assisted, we need to check if there is a QIF file - * which we need to load. -- rgerhards, 2008-01-15 - */ - iRetLocal = queueHaveQIF(pThis); - if(iRetLocal == RS_RET_OK) { - dbgoprint((obj_t*) pThis, "on-disk queue present, needs to be reloaded\n"); - queueInitDA(pThis, QUEUE_MODE_ENQDEQ, LOCK_MUTEX); /* initiate DA mode */ - bInitialized = 1; /* we are done */ - } else { - /* TODO: use logerror? -- rgerhards, 2008-01-16 */ - dbgoprint((obj_t*) pThis, "error %d trying to access on-disk queue files, starting without them. " - "Some data may be lost\n", iRetLocal); - } - } - - if(!bInitialized) { - dbgoprint((obj_t*) pThis, "queue starts up without (loading) any DA disk state (this is normal for the DA " - "queue itself!)\n"); - } - - /* if the queue already contains data, we need to start the correct number of worker threads. This can be - * the case when a disk queue has been loaded. If we did not start it here, it would never start. - */ - queueAdviseMaxWorkers(pThis); - pThis->bQueueStarted = 1; - -finalize_it: - RETiRet; -} - - -/* persist the queue to disk. If we have something to persist, we first - * save the information on the queue properties itself and then we call - * the queue-type specific drivers. - * Variable bIsCheckpoint is set to 1 if the persist is for a checkpoint, - * and 0 otherwise. - * rgerhards, 2008-01-10 - */ -static rsRetVal queuePersist(queue_t *pThis, int bIsCheckpoint) -{ - DEFiRet; - strm_t *psQIF = NULL; /* Queue Info File */ - uchar pszQIFNam[MAXFNAME]; - size_t lenQIFNam; - obj_t *pUsr; - - ASSERT(pThis != NULL); - - if(pThis->qType != QUEUETYPE_DISK) { - if(queueGetOverallQueueSize(pThis) > 0) { - /* This error code is OK, but we will probably not implement this any time - * The reason is that persistence happens via DA queues. But I would like to - * leave the code as is, as we so have a hook in case we need one. - * -- rgerhards, 2008-01-28 - */ - ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); - } else - FINALIZE; /* if the queue is empty, we are happy and done... */ - } - - dbgoprint((obj_t*) pThis, "persisting queue to disk, %d entries...\n", queueGetOverallQueueSize(pThis)); - - /* Construct file name */ - lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", - (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); - - if((bIsCheckpoint != QUEUE_CHECKPOINT) && (queueGetOverallQueueSize(pThis) == 0)) { - if(pThis->bNeedDelQIF) { - unlink((char*)pszQIFNam); - pThis->bNeedDelQIF = 0; - } - /* indicate spool file needs to be deleted */ - CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); - FINALIZE; /* nothing left to do, so be happy */ - } - - CHKiRet(strmConstruct(&psQIF)); - CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_WRITE)); - CHKiRet(strmSetiAddtlOpenFlags(psQIF, O_TRUNC)); - CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); - CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); - CHKiRet(strmConstructFinalize(psQIF)); - - /* first, write the property bag for ourselfs - * And, surprisingly enough, we currently need to persist only the size of the - * queue. All the rest is re-created with then-current config parameters when the - * queue is re-created. Well, we'll also save the current queue type, just so that - * we know when somebody has changed the queue type... -- rgerhards, 2008-01-11 - */ - CHKiRet(obj.BeginSerializePropBag(psQIF, (obj_t*) pThis)); - objSerializeSCALAR(psQIF, iQueueSize, INT); - objSerializeSCALAR(psQIF, iUngottenObjs, INT); - objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64); - objSerializeSCALAR(psQIF, tVars.disk.bytesRead, INT64); - CHKiRet(obj.EndSerialize(psQIF)); - - /* now we must persist all objects on the ungotten queue - they can not go to - * to the regular files. -- rgerhards, 2008-01-29 - */ - while(pThis->iUngottenObjs > 0) { - CHKiRet(queueGetUngottenObj(pThis, &pUsr)); - CHKiRet((objSerialize(pUsr))(pUsr, psQIF)); - objDestruct(pUsr); - } - - /* now persist the stream info */ - CHKiRet(strmSerialize(pThis->tVars.disk.pWrite, psQIF)); - CHKiRet(strmSerialize(pThis->tVars.disk.pRead, psQIF)); - - /* tell the input file object that it must not delete the file on close if the queue - * is non-empty - but only if we are not during a simple checkpoint - */ - if(bIsCheckpoint != QUEUE_CHECKPOINT) { - CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 0)); - } - - /* we have persisted the queue object. So whenever it comes to an empty queue, - * we need to delete the QIF. Thus, we indicte that need. - */ - pThis->bNeedDelQIF = 1; - -finalize_it: - if(psQIF != NULL) - strmDestruct(&psQIF); - - RETiRet; -} - - -/* check if we need to persist the current queue info. If an - * error occurs, thus should be ignored by caller (but we still - * abide to our regular call interface)... - * rgerhards, 2008-01-13 - */ -rsRetVal queueChkPersist(queue_t *pThis) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - - if(pThis->iPersistUpdCnt && ++pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) { - queuePersist(pThis, QUEUE_CHECKPOINT); - pThis->iUpdsSincePersist = 0; - } - - RETiRet; -} - - -/* destructor for the queue object */ -BEGINobjDestruct(queue) /* be sure to specify the object type also in END and CODESTART macros! */ -CODESTARTobjDestruct(queue) - pThis->bQueueInDestruction = 1; /* indicate we are in destruction (modifies some behaviour) */ - - /* shut down all workers (handles *all* of the persistence logic) - * See function head comment of queueShutdownWorkers () on why we don't call it - * We also do not need to shutdown workers when we are in enqueue-only mode or we are a - * direct queue - because in both cases we have none... ;) - * with a child! -- rgerhards, 2008-01-28 - */ - if(pThis->qType != QUEUETYPE_DIRECT && !pThis->bEnqOnly && pThis->pqParent == NULL) - queueShutdownWorkers(pThis); - - /* finally destruct our (regular) worker thread pool - * Note: currently pWtpReg is never NULL, but if we optimize our logic, this may happen, - * e.g. when they are not created in enqueue-only mode. We already check the condition - * as this may otherwise be very hard to find once we optimize (and have long forgotten - * about this condition here ;) - * rgerhards, 2008-01-25 - */ - if(pThis->qType != QUEUETYPE_DIRECT && pThis->pWtpReg != NULL) { - wtpDestruct(&pThis->pWtpReg); - } - - /* Now check if we actually have a DA queue and, if so, destruct it. - * Note that the wtp must be destructed first, it may be in cancel cleanup handler - * *right now* and actually *need* to access the queue object to persist some final - * data (re-queueing case). So we need to destruct the wtp first, which will make - * sure all workers have terminated. Please note that this also generates a situation - * where it is possible that the DA queue has a parent pointer but the parent has - * no WtpDA associated with it - which is perfectly legal thanks to this code here. - */ - if(pThis->pWtpDA != NULL) { - wtpDestruct(&pThis->pWtpDA); - } - if(pThis->pqDA != NULL) { - queueDestruct(&pThis->pqDA); - } - - /* persist the queue (we always do that - queuePersits() does cleanup if the queue is empty) - * This handler is most important for disk queues, it will finally persist the necessary - * on-disk structures. In theory, other queueing modes may implement their other (non-DA) - * methods of persisting a queue between runs, but in practice all of this is done via - * disk queues and DA mode. Anyhow, it doesn't hurt to know that we could extend it here - * if need arises (what I doubt...) -- rgerhards, 2008-01-25 - */ - CHKiRet_Hdlr(queuePersist(pThis, QUEUE_NO_CHECKPOINT)) { - dbgoprint((obj_t*) pThis, "error %d persisting queue - data lost!\n", iRet); - } - - /* finally, clean up some simple things... */ - if(pThis->pqParent == NULL) { - /* if we are not a child, we allocated our own mutex, which we now need to destroy */ - pthread_mutex_destroy(pThis->mut); - free(pThis->mut); - } - pthread_mutex_destroy(&pThis->mutThrdMgmt); - pthread_cond_destroy(&pThis->condDAReady); - pthread_cond_destroy(&pThis->notFull); - pthread_cond_destroy(&pThis->notEmpty); - pthread_cond_destroy(&pThis->belowFullDlyWtrMrk); - pthread_cond_destroy(&pThis->belowLightDlyWtrMrk); - - /* type-specific destructor */ - iRet = pThis->qDestruct(pThis); - - if(pThis->pszFilePrefix != NULL) - free(pThis->pszFilePrefix); - - if(pThis->pszSpoolDir != NULL) - free(pThis->pszSpoolDir); -ENDobjDestruct(queue) - - -/* set the queue's file prefix - * The passed-in string is duplicated. So if the caller does not need - * it any longer, it must free it. - * rgerhards, 2008-01-09 - */ -rsRetVal -queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix) -{ - DEFiRet; - - if(pThis->pszFilePrefix != NULL) - free(pThis->pszFilePrefix); - - if(pszPrefix == NULL) /* just unset the prefix! */ - ABORT_FINALIZE(RS_RET_OK); - - if((pThis->pszFilePrefix = malloc(sizeof(uchar) * iLenPrefix + 1)) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - memcpy(pThis->pszFilePrefix, pszPrefix, iLenPrefix + 1); - pThis->lenFilePrefix = iLenPrefix; - -finalize_it: - RETiRet; -} - -/* set the queue's maximum file size - * rgerhards, 2008-01-09 - */ -rsRetVal -queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - - if(iMaxFileSize < 1024) { - ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW); - } - - pThis->iMaxFileSize = iMaxFileSize; - -finalize_it: - RETiRet; -} - - -/* enqueue a new user data element - * Enqueues the new element and awakes worker thread. - * TODO: this code still uses the "discard if queue full" approach from - * the main queue. This needs to be reconsidered or, better, done via a - * caller-selectable parameter mode. For the time being, I leave it in. - * rgerhards, 2008-01-03 - */ -rsRetVal -queueEnqObj(queue_t *pThis, flowControl_t flowCtlType, void *pUsr) -{ - DEFiRet; - int iCancelStateSave; - int i; - struct timespec t; - - ISOBJ_TYPE_assert(pThis, queue); - - /* Please note that this function is not cancel-safe and consequently - * sets the calling thread's cancelibility state to PTHREAD_CANCEL_DISABLE - * during its execution. If that is not done, race conditions occur if the - * thread is canceled (most important use case is input module termination). - * rgerhards, 2008-01-08 - */ - if(pThis->qType != QUEUETYPE_DIRECT) { - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); - d_pthread_mutex_lock(pThis->mut); - } - - /* first check if we need to discard this message (which will cause CHKiRet() to exit) */ - CHKiRet(queueChkDiscardMsg(pThis, pThis->iQueueSize, pThis->bRunsDA, pUsr)); - - /* then check if we need to add an assistance disk queue */ - if(pThis->bIsDA) - CHKiRet(queueChkStrtDA(pThis)); - - - /* handle flow control - * There are two different flow control mechanisms: basic and advanced flow control. - * Basic flow control has always been implemented and protects the queue structures - * in that it makes sure no more data is enqueued than the queue is configured to - * support. Enhanced flow control is being added today. There are some sources which - * can easily be stopped, e.g. a file reader. This is the case because it is unlikely - * that blocking those sources will have negative effects (after all, the file is - * continued to be written). Other sources can somewhat be blocked (e.g. the kernel - * log reader or the local log stream reader): in general, nothing is lost if messages - * from these sources are not picked up immediately. HOWEVER, they can not block for - * an extended period of time, as this either causes message loss or - even worse - some - * other bad effects (e.g. unresponsive system in respect to the main system log socket). - * Finally, there are some (few) sources which can not be blocked at all. UDP syslog is - * a prime example. If a UDP message is not received, it is simply lost. So we can't - * do anything against UDP sockets that come in too fast. The core idea of advanced - * flow control is that we take into account the different natures of the sources and - * select flow control mechanisms that fit these needs. This also means, in the end - * result, that non-blockable sources like UDP syslog receive priority in the system. - * It's a side effect, but a good one ;) -- rgerhards, 2008-03-14 - */ - if(flowCtlType == eFLOWCTL_FULL_DELAY) { - while(pThis->iQueueSize >= pThis->iFullDlyMrk) { - dbgoprint((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayble message - blocking.\n"); - pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut); /* TODO error check? But what do then? */ - } - } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY) { - while(pThis->iQueueSize >= pThis->iLightDlyMrk) { - dbgoprint((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light delayble message - blocking a bit.\n"); - timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */ - pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); /* TODO error check? But what do then? */ - } - } - - /* from our regular flow control settings, we are now ready to enqueue the object. - * However, we now need to do a check if the queue permits to add more data. If that - * is not the case, basic flow control enters the field, which means we wait for - * the queue to become ready or drop the new message. -- rgerhards, 2008-03-14 - */ - while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) - || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 - && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { - dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); - timeoutComp(&t, pThis->toEnq); - if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { - dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); - objDestruct(pUsr); - ABORT_FINALIZE(RS_RET_QUEUE_FULL); - } - } - -#if 0 // previous code, remove when done with advanced flow control - /* wait for the queue to be ready... */ - while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) - || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 - && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { - dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); - timeoutComp(&t, pThis->toEnq); - if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { - dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); - objDestruct(pUsr); - ABORT_FINALIZE(RS_RET_QUEUE_FULL); - } - } -#endif - - /* and finally enqueue the message */ - CHKiRet(queueAdd(pThis, pUsr)); - queueChkPersist(pThis); - -finalize_it: - if(pThis->qType != QUEUETYPE_DIRECT) { - d_pthread_mutex_unlock(pThis->mut); - i = pthread_cond_signal(&pThis->notEmpty); - dbgoprint((obj_t*) pThis, "EnqueueMsg signaled condition (%d)\n", i); - pthread_setcancelstate(iCancelStateSave, NULL); - } - - /* make sure at least one worker is running. */ - if(pThis->qType != QUEUETYPE_DIRECT) { - queueAdviseMaxWorkers(pThis); - } - - RETiRet; -} - - -/* set queue mode to enqueue only or not - * There is one subtle issue: this method may be called during queue - * construction or while it is running. In the former case, the queue - * mutex does not yet exist (it is NULL), while in the later case it - * must be locked. The function detects the state and operates as - * required. - * rgerhards, 2008-01-16 - */ -static rsRetVal -queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex) -{ - DEFiRet; - DEFVARS_mutexProtection; - - ISOBJ_TYPE_assert(pThis, queue); - - /* for simplicity, we do one big mutex lock. This method is extremely seldom - * called, so that doesn't matter... -- rgerhards, 2008-01-16 - */ - if(pThis->mut != NULL) { - BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); - } - - if(bEnqOnly == pThis->bEnqOnly) - FINALIZE; /* no change, nothing to do */ - - if(pThis->bQueueStarted) { - /* we need to adjust queue operation only if we are not during initial param setup */ - if(bEnqOnly == 1) { - /* switch to enqueue-only mode */ - /* this means we need to terminate all workers - that's it... */ - dbgoprint((obj_t*) pThis, "switching to enqueue-only mode, terminating all worker threads\n"); - if(pThis->pWtpReg != NULL) - wtpWakeupAllWrkr(pThis->pWtpReg); - if(pThis->pWtpDA != NULL) - wtpWakeupAllWrkr(pThis->pWtpDA); - } else { - /* switch back to regular mode */ - ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); /* we don't need this so far... */ - } - } - - pThis->bEnqOnly = bEnqOnly; - -finalize_it: - if(pThis->mut != NULL) { - END_MTX_PROTECTED_OPERATIONS(pThis->mut); - } - RETiRet; -} - - -/* some simple object access methods */ -DEFpropSetMeth(queue, iPersistUpdCnt, int); -DEFpropSetMeth(queue, iDeqtWinFromHr, int); -DEFpropSetMeth(queue, iDeqtWinToHr, int); -DEFpropSetMeth(queue, toQShutdown, long); -DEFpropSetMeth(queue, toActShutdown, long); -DEFpropSetMeth(queue, toWrkShutdown, long); -DEFpropSetMeth(queue, toEnq, long); -DEFpropSetMeth(queue, iHighWtrMrk, int); -DEFpropSetMeth(queue, iLowWtrMrk, int); -DEFpropSetMeth(queue, iDiscardMrk, int); -DEFpropSetMeth(queue, iFullDlyMrk, int); -DEFpropSetMeth(queue, iDiscardSeverity, int); -DEFpropSetMeth(queue, bIsDA, int); -DEFpropSetMeth(queue, iMinMsgsPerWrkr, int); -DEFpropSetMeth(queue, bSaveOnShutdown, int); -DEFpropSetMeth(queue, pUsr, void*); -DEFpropSetMeth(queue, iDeqSlowdown, int); -DEFpropSetMeth(queue, sizeOnDiskMax, int64); - - -/* This function can be used as a generic way to set properties. Only the subset - * of properties required to read persisted property bags is supported. This - * functions shall only be called by the property bag reader, thus it is static. - * rgerhards, 2008-01-11 - */ -#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) -static rsRetVal queueSetProperty(queue_t *pThis, var_t *pProp) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, queue); - ASSERT(pProp != NULL); - - if(isProp("iQueueSize")) { - pThis->iQueueSize = pProp->val.num; - } else if(isProp("iUngottenObjs")) { - pThis->iUngottenObjs = pProp->val.num; - } else if(isProp("tVars.disk.sizeOnDisk")) { - pThis->tVars.disk.sizeOnDisk = pProp->val.num; - } else if(isProp("tVars.disk.bytesRead")) { - pThis->tVars.disk.bytesRead = pProp->val.num; - } else if(isProp("qType")) { - if(pThis->qType != pProp->val.num) - ABORT_FINALIZE(RS_RET_QTYPE_MISMATCH); - } - -finalize_it: - RETiRet; -} -#undef isProp - -/* dummy */ -rsRetVal queueQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } - -/* Initialize the stream class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-01-09 - */ -BEGINObjClassInit(queue, 1, OBJ_IS_CORE_MODULE) - /* request objects we use */ - - /* now set our own handlers */ - OBJSetMethodHandler(objMethod_SETPROPERTY, queueSetProperty); -ENDObjClassInit(queue) - -/* vi:set ai: - */ diff --git a/queue.h b/queue.h deleted file mode 100644 index 9e75b31b..00000000 --- a/queue.h +++ /dev/null @@ -1,205 +0,0 @@ -/* Definition of the queue support module. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of the rsyslog runtime library. - * - * The rsyslog runtime library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The rsyslog runtime library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the rsyslog runtime library. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. - */ - -#ifndef QUEUE_H_INCLUDED -#define QUEUE_H_INCLUDED - -#include -#include "obj.h" -#include "wtp.h" -#include "stream.h" - -/* queue types */ -typedef enum { - QUEUETYPE_FIXED_ARRAY = 0,/* a simple queue made out of a fixed (initially malloced) array fast but memoryhog */ - QUEUETYPE_LINKEDLIST = 1, /* linked list used as buffer, lower fixed memory overhead but slower */ - QUEUETYPE_DISK = 2, /* disk files used as buffer */ - QUEUETYPE_DIRECT = 3 /* no queuing happens, consumer is directly called */ -} queueType_t; - -/* list member definition for linked list types of queues: */ -typedef struct qLinkedList_S { - struct qLinkedList_S *pNext; - void *pUsr; -} qLinkedList_t; - - -typedef struct qWrkThrd_s { - pthread_t thrdID; /* thread ID */ - qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ - obj_t *pUsr; /* current user object being processed (or NULL if none) */ - struct queue_s *pQueue; /* my queue (important if only the work thread instance is passed! */ - int iThrd; /* my worker thread array index */ - pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ - pthread_mutex_t mut; -} qWrkThrd_t; /* type for queue worker threads */ - -/* the queue object */ -typedef struct queue_s { - BEGINobjInstance; - queueType_t qType; - int bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */ - int bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */ - int bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */ - int bQueueInDestruction;/* 1 if queue is in destruction process, 0 otherwise */ - int iQueueSize; /* Current number of elements in the queue */ - int iMaxQueueSize; /* how large can the queue grow? */ - int iNumWorkerThreads;/* number of worker threads to use */ - int iCurNumWrkThrd;/* current number of active worker threads */ - int iMinMsgsPerWrkr;/* minimum nbr of msgs per worker thread, if more, a new worker is started until max wrkrs */ - wtp_t *pWtpDA; - wtp_t *pWtpReg; - void *pUsr; /* a global, user-supplied pointer. Is passed back to consumer. */ - int iUpdsSincePersist;/* nbr of queue updates since the last persist call */ - int iPersistUpdCnt; /* persits queue info after this nbr of updates - 0 -> persist only on shutdown */ - int iHighWtrMrk; /* high water mark for disk-assisted memory queues */ - int iLowWtrMrk; /* low water mark for disk-assisted memory queues */ - int iDiscardMrk; /* if the queue is above this mark, low-severity messages are discarded */ - int iFullDlyMrk; /* if the queue is above this mark, FULL_DELAYable message are put on hold */ - int iLightDlyMrk; /* if the queue is above this mark, LIGHT_DELAYable message are put on hold */ - int iDiscardSeverity;/* messages of this severity above are discarded on too-full queue */ - int bNeedDelQIF; /* does the QIF file need to be deleted when queue becomes empty? */ - int toQShutdown; /* timeout for regular queue shutdown in ms */ - int toActShutdown; /* timeout for long-running action shutdown in ms */ - int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ - int toEnq; /* enqueue timeout */ - /* rate limiting settings (will be expanded) */ - int iDeqSlowdown; /* slow down dequeue by specified nbr of microseconds */ - /* end rate limiting */ - /* dequeue time window settings (may also be expanded) */ - int iDeqtWinFromHr; /* begin of dequeue time window (hour only) */ - int iDeqtWinToHr; /* end of dequeue time window (hour only), set to 25 to disable deq window! */ - /* note that begin and end have specific semantics. It is a big difference if we have - * begin 4, end 22 or begin 22, end 4. In the later case, dequeuing will run from 10p, - * throughout the night and stop at 4 in the morning. In the first case, it will start - * at 4am, run throughout the day, and stop at 10 in the evening! So far, not logic is - * applied to detect user configuration errors (and tell me how should we detect what - * the user really wanted...). -- rgerhards, 2008-04-02 - */ - /* ane dequeue time window */ - rsRetVal (*pConsumer)(void *,void*); /* user-supplied consumer function for dequeued messages */ - /* calling interface for pConsumer: arg1 is the global user pointer from this structure, arg2 is the - * user pointer that was dequeued (actual sample: for actions, arg1 is the pAction and arg2 is pointer - * to message) - * rgerhards, 2008-01-28 - */ - /* type-specific handlers (set during construction) */ - rsRetVal (*qConstruct)(struct queue_s *pThis); - rsRetVal (*qDestruct)(struct queue_s *pThis); - rsRetVal (*qAdd)(struct queue_s *pThis, void *pUsr); - rsRetVal (*qDel)(struct queue_s *pThis, void **ppUsr); - /* end type-specific handler */ - /* synchronization variables */ - pthread_mutex_t mutThrdMgmt; /* mutex for the queue's thread management */ - pthread_mutex_t *mut; /* mutex for enqueing and dequeueing messages */ - pthread_cond_t notFull, notEmpty; - pthread_cond_t belowFullDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ - pthread_cond_t belowLightDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ - pthread_cond_t condDAReady;/* signalled when the DA queue is fully initialized and ready for processing */ - int bChildIsDone; /* set to 1 when the child DA queue has finished processing, 0 otherwise */ - int bThrdStateChanged; /* at least one thread state has changed if 1 */ - /* end sync variables */ - /* the following variables are always present, because they - * are not only used for the "disk" queueing mode but also for - * any other queueing mode if it is set to "disk assisted". - * rgerhards, 2008-01-09 - */ - uchar *pszSpoolDir; - size_t lenSpoolDir; - uchar *pszFilePrefix; - size_t lenFilePrefix; - int iNumberFiles; /* how many files make up the queue? */ - int64 iMaxFileSize; /* max size for a single queue file */ - int64 sizeOnDiskMax; /* maximum size on disk allowed */ - int bIsDA; /* is this queue disk assisted? */ - int bRunsDA; /* is this queue actually *running* disk assisted? */ - struct queue_s *pqDA; /* queue for disk-assisted modes */ - struct queue_s *pqParent;/* pointer to the parent (if this is a child queue) */ - int bDAEnqOnly; /* EnqOnly setting for DA queue */ - /* some data elements for the queueUngetObj() functionality. This list should always be short - * and is always kept in memory - */ - qLinkedList_t *pUngetRoot; - qLinkedList_t *pUngetLast; - int iUngottenObjs; /* number of objects currently in the "ungotten" list */ - /* now follow queueing mode specific data elements */ - union { /* different data elements based on queue type (qType) */ - struct { - long head, tail; - void** pBuf; /* the queued user data structure */ - } farray; - struct { - qLinkedList_t *pRoot; - qLinkedList_t *pLast; - } linklist; - struct { - int64 sizeOnDisk; /* current amount of disk space used */ - int64 bytesRead; /* number of bytes read from current (undeleted!) file */ - strm_t *pWrite; /* current file to be written */ - strm_t *pRead; /* current file to be read */ - } disk; - } tVars; -} queue_t; - -/* some symbolic constants for easier reference */ -#define QUEUE_MODE_ENQDEQ 0 -#define QUEUE_MODE_ENQONLY 1 - -#define QUEUE_IDX_DA_WORKER 0 /* index for the DA worker (fixed) */ -#define QUEUE_PTR_DA_WORKER(x) (&((pThis)->pWrkThrds[0])) - -/* the define below is an "eternal" timeout for the timeout settings which require a value. - * It is one day, which is not really eternal, but comes close to it if we think about - * rsyslog (e.g.: do you want to wait on shutdown for more than a day? ;)) - * rgerhards, 2008-01-17 - */ -#define QUEUE_TIMEOUT_ETERNAL 24 * 60 * 60 * 1000 - -/* prototypes */ -rsRetVal queueDestruct(queue_t **ppThis); -rsRetVal queueEnqObj(queue_t *pThis, flowControl_t flwCtlType, void *pUsr); -rsRetVal queueStart(queue_t *pThis); -rsRetVal queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize); -rsRetVal queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix); -rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, - int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)); -PROTOTYPEObjClassInit(queue); -PROTOTYPEpropSetMeth(queue, iPersistUpdCnt, int); -PROTOTYPEpropSetMeth(queue, iDeqtWinFromHr, int); -PROTOTYPEpropSetMeth(queue, iDeqtWinToHr, int); -PROTOTYPEpropSetMeth(queue, toQShutdown, long); -PROTOTYPEpropSetMeth(queue, toActShutdown, long); -PROTOTYPEpropSetMeth(queue, toWrkShutdown, long); -PROTOTYPEpropSetMeth(queue, toEnq, long); -PROTOTYPEpropSetMeth(queue, iHighWtrMrk, int); -PROTOTYPEpropSetMeth(queue, iLowWtrMrk, int); -PROTOTYPEpropSetMeth(queue, iDiscardMrk, int); -PROTOTYPEpropSetMeth(queue, iDiscardSeverity, int); -PROTOTYPEpropSetMeth(queue, iMinMsgsPerWrkr, int); -PROTOTYPEpropSetMeth(queue, bSaveOnShutdown, int); -PROTOTYPEpropSetMeth(queue, pUsr, void*); -PROTOTYPEpropSetMeth(queue, iDeqSlowdown, int); -PROTOTYPEpropSetMeth(queue, sizeOnDiskMax, int64); -#define queueGetID(pThis) ((unsigned long) pThis) - -#endif /* #ifndef QUEUE_H_INCLUDED */ diff --git a/runtime/Makefile.am b/runtime/Makefile.am new file mode 100644 index 00000000..813a4c68 --- /dev/null +++ b/runtime/Makefile.am @@ -0,0 +1,13 @@ +noinst_LTLIBRARIES = librsyslog.la +#pkglib_LTLIBRARIES = librsyslog.la + +librsyslog_la_SOURCES = \ + queue.c \ + queue.h + +librsyslog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +#librsyslog_la_LDFLAGS = -module -avoid-version +librsyslog_la_LIBADD = + +sbin_PROGRAMS = +man_MANS = diff --git a/runtime/queue.c b/runtime/queue.c new file mode 100644 index 00000000..0f58c545 --- /dev/null +++ b/runtime/queue.c @@ -0,0 +1,2322 @@ +/* queue.c + * + * This file implements the queue object and its several queueing methods. + * + * File begun on 2008-01-03 by RGerhards + * + * There is some in-depth documentation available in doc/dev_queue.html + * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it + * if you are getting aquainted to the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include /* required for HP UX */ +#include +#include + +#include "rsyslog.h" +#include "syslogd.h" +#include "queue.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "obj.h" +#include "wtp.h" +#include "wti.h" + +/* static data */ +DEFobjStaticHelpers + +/* forward-definitions */ +rsRetVal queueChkPersist(queue_t *pThis); +static rsRetVal queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex); +static rsRetVal queueRateLimiter(queue_t *pThis); +static int queueChkStopWrkrDA(queue_t *pThis); +static int queueIsIdleDA(queue_t *pThis); +static rsRetVal queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave); +static rsRetVal queueConsumerCancelCleanup(void *arg1, void *arg2); +static rsRetVal queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex); + +/* some constants for queuePersist () */ +#define QUEUE_CHECKPOINT 1 +#define QUEUE_NO_CHECKPOINT 0 + +/* methods */ + + +/* get the overall queue size, which includes ungotten objects. Must only be called + * while mutex is locked! + * rgerhards, 2008-01-29 + */ +static inline int +queueGetOverallQueueSize(queue_t *pThis) +{ +#if 0 /* leave a bit in for debugging -- rgerhards, 2008-01-30 */ +BEGINfunc +dbgoprint((obj_t*) pThis, "queue size: %d (regular %d, ungotten %d)\n", + pThis->iQueueSize + pThis->iUngottenObjs, pThis->iQueueSize, pThis->iUngottenObjs); +ENDfunc +#endif + return pThis->iQueueSize + pThis->iUngottenObjs; +} + +/* --------------- code for disk-assisted (DA) queue modes -------------------- */ + + +/* returns the number of workers that should be advised at + * this point in time. The mutex must be locked when + * ths function is called. -- rgerhards, 2008-01-25 + */ +static inline rsRetVal queueAdviseMaxWorkers(queue_t *pThis) +{ + DEFiRet; + int iMaxWorkers; + + ISOBJ_TYPE_assert(pThis, queue); + + if(!pThis->bEnqOnly) { + if(pThis->bRunsDA) { + /* if we have not yet reached the high water mark, there is no need to start a + * worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) >= pThis->iHighWtrMrk || pThis->bQueueStarted == 0) { + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */ + } + } else { + if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) { + iMaxWorkers = 1; + } else { + iMaxWorkers = queueGetOverallQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1; + } + wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers); /* disk queues have always one worker */ + } + } + + RETiRet; +} + + +/* wait until we have a fully initialized DA queue. Sometimes, we need to + * sync with it, as we expect it for some function. + * rgerhards, 2008-02-27 + */ +static rsRetVal +queueWaitDAModeInitialized(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + while(pThis->bRunsDA != 2) { + d_pthread_cond_wait(&pThis->condDAReady, pThis->mut); + } + + RETiRet; +} + + +/* Destruct DA queue. This is the last part of DA-to-normal-mode + * transistion. This is called asynchronously and some time quite a + * while after the actual transistion. The key point is that we need to + * do it at some later time, because we need to destruct the DA queue. That, + * however, can not be done in a thread that has been signalled + * This is to be called when we revert back to our own queue. + * This function must be called with the queue mutex locked (the wti + * class ensures this). + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueTurnOffDAMode(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->bRunsDA); + + /* at this point, we need a fully initialized DA queue. So if it isn't, we finally need + * to wait for its startup... -- rgerhards, 2008-01-25 + */ + queueWaitDAModeInitialized(pThis); + + /* if we need to pull any data that we still need from the (child) disk queue, + * now would be the time to do so. At present, we do not need this, but I'd like to + * keep that comment if future need arises. + */ + + /* we need to check if the DA queue is empty because the DA worker may simply have + * terminated do to no new messages arriving. That does not, however, mean that the + * DA queue is empty. If there is still data in that queue, we do nothing and leave + * that for a later incarnation of this function (it will be called multiple times + * during the lifetime of DA-mode, depending on how often the DA worker receives an + * inactivity timeout. -- rgerhards, 2008-01-25 + */ + if(pThis->pqDA->iQueueSize == 0) { + pThis->bRunsDA = 0; /* tell the world we are back in non-DA mode */ + /* we destruct the queue object, which will also shutdown the queue worker. As the queue is empty, + * this will be quick. + */ + queueDestruct(&pThis->pqDA); /* and now we are ready to destruct the DA queue */ + dbgoprint((obj_t*) pThis, "disk-assistance has been turned off, disk queue was empty (iRet %d)\n", + iRet); + /* now we need to check if the regular queue has some messages. This may be the case + * when it is waiting that the high water mark is reached again. If so, we need to start up + * a regular worker. -- rgerhards, 2008-01-26 + */ + if(queueGetOverallQueueSize(pThis) > 0) { + queueAdviseMaxWorkers(pThis); + } + } + + /* TODO: we have a *really biiiiig* memory leak here: if the queue could not be persisted, all of + * its data elements are still in memory. That doesn't really matter if we are terminated, but on + * HUP this memory leaks. We MUST add a loop of destructor calls here. However, this takes time + * (possibly a lot), so it is probably best to have a config variable for that. + * Something for 3.11.1! + * rgerhards, 2008-01-30 + */ + + RETiRet; +} + + +/* check if we run in disk-assisted mode and record that + * setting for easy (and quick!) access in the future. This + * function must only be called from constructors and only + * from those that support disk-assisted modes (aka memory- + * based queue drivers). + * rgerhards, 2008-01-14 + */ +static rsRetVal +queueChkIsDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + if(pThis->pszFilePrefix != NULL) { + pThis->bIsDA = 1; + dbgoprint((obj_t*) pThis, "is disk-assisted, disk will be used on demand\n"); + } else { + dbgoprint((obj_t*) pThis, "is NOT disk-assisted\n"); + } + + RETiRet; +} + + +/* Start disk-assisted queue mode. All internal settings are changed. This is supposed + * to be called from the DA worker, which must have been started before. The most important + * chore of this function is to create the DA queue object. If that function fails, + * the DA worker should return with an appropriate state, which in turn should lead to + * a re-set to non-DA mode in the Enq process. The queue mutex must be locked when this + * function is called, else a number of races will happen. + * Please note that this function may be called *while* we in DA mode. This is due to the + * fact that the DA worker calls it and the DA worker may be suspended (and restarted) due + * to inactivity timeouts. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueStartDA(queue_t *pThis) +{ + DEFiRet; + uchar pszDAQName[128]; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->bRunsDA == 2) /* check if already in (fully initialized) DA mode... */ + FINALIZE; /* ... then we are already done! */ + + /* create message queue */ + CHKiRet(queueConstruct(&pThis->pqDA, QUEUETYPE_DISK , 1, 0, pThis->pConsumer)); + + /* give it a name */ + snprintf((char*) pszDAQName, sizeof(pszDAQName)/sizeof(uchar), "%s[DA]", obj.GetName((obj_t*) pThis)); + obj.SetName((obj_t*) pThis->pqDA, pszDAQName); + + /* as the created queue is the same object class, we take the + * liberty to access its properties directly. + */ + pThis->pqDA->pqParent = pThis; + + CHKiRet(queueSetpUsr(pThis->pqDA, pThis->pUsr)); + CHKiRet(queueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax)); + CHKiRet(queueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown)); + CHKiRet(queueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize)); + CHKiRet(queueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(queueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt)); + CHKiRet(queueSettoActShutdown(pThis->pqDA, pThis->toActShutdown)); + CHKiRet(queueSettoEnq(pThis->pqDA, pThis->toEnq)); + CHKiRet(queueSetEnqOnly(pThis->pqDA, pThis->bDAEnqOnly, MUTEX_ALREADY_LOCKED)); + CHKiRet(queueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr)); + CHKiRet(queueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr)); + CHKiRet(queueSetiHighWtrMrk(pThis->pqDA, 0)); + CHKiRet(queueSetiDiscardMrk(pThis->pqDA, 0)); + if(pThis->toQShutdown == 0) { + CHKiRet(queueSettoQShutdown(pThis->pqDA, 0)); /* if the user really wants... */ + } else { + /* we use the shortest possible shutdown (0 is endless!) because when we run on disk AND + * have an obviously large backlog, we can't finish it in any case. So there is no point + * in holding shutdown longer than necessary. -- rgerhards, 2008-01-15 + */ + CHKiRet(queueSettoQShutdown(pThis->pqDA, 1)); + } + + iRet = queueStart(pThis->pqDA); + /* file not found is expected, that means it is no previous QIF available */ + if(iRet != RS_RET_OK && iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; /* something is wrong */ + + /* as we are right now starting DA mode because we are so busy, it is + * extremely unlikely that any regular worker is sleeping on empty queue. HOWEVER, + * we want to be on the safe side, and so we awake anyone that is waiting + * on one. So even if the scheduler plays badly with us, things should be + * quite well. -- rgerhards, 2008-01-15 + */ + wtpWakeupWrkr(pThis->pWtpReg); /* awake all workers, but not ourselves ;) */ + + pThis->bRunsDA = 2; /* we are now in DA mode, but not fully initialized */ + pThis->bChildIsDone = 0;/* set to 1 when child's worker detect queue is finished */ + pthread_cond_broadcast(&pThis->condDAReady); /* signal we are now initialized and ready to go ;) */ + + dbgoprint((obj_t*) pThis, "is now running in disk assisted mode, disk queue 0x%lx\n", + queueGetID(pThis->pqDA)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pqDA != NULL) { + queueDestruct(&pThis->pqDA); + } + dbgoprint((obj_t*) pThis, "error %d creating disk queue - giving up.\n", iRet); + pThis->bIsDA = 0; + } + + RETiRet; +} + + +/* initiate DA mode + * param bEnqOnly tells if the disk queue is to be run in enqueue-only mode. This may + * be needed during shutdown of memory queues which need to be persisted to disk. + * If this function fails (should not happen), DA mode is not turned on. + * rgerhards, 2008-01-16 + */ +static inline rsRetVal +queueInitDA(queue_t *pThis, int bEnqOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + uchar pszBuf[64]; + size_t lenBuf; + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + /* check if we already have a DA worker pool. If not, initiate one. Please note that the + * pool is created on first need but never again destructed (until the queue is). This + * is intentional. We assume that when we need it once, we may also need it on another + * occasion. Ressources used are quite minimal when no worker is running. + * rgerhards, 2008-01-24 + */ + if(pThis->pWtpDA == NULL) { + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DA", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpDA)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrDA)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) queueIsIdleDA)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerDA)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void*pWti)) queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueStartDA)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpDA, (rsRetVal (*)(void *pUsr)) queueTurnOffDAMode)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpDA, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpDA, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpDA, 1)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpDA, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpDA, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpDA)); + } + /* if we reach this point, we have a "good" DA worker pool */ + + /* indicate we now run in DA mode - this is reset by the DA worker if it fails */ + pThis->bRunsDA = 1; + pThis->bDAEnqOnly = bEnqOnly; + + /* now we must now adivse the wtp that we need one worker. If none is yet active, + * that will also start one up. If we forgot that step, everything would be stalled + * until the next enqueue request. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* DA queues alsways have just one worker max */ + +finalize_it: + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + RETiRet; +} + + +/* check if we need to start disk assisted mode and send some signals to + * keep it running if we are already in it. It also checks if DA mode is + * partially initialized, in which case it waits for initialization to + * complete. + * rgerhards, 2008-01-14 + */ +static inline rsRetVal +queueChkStrtDA(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + /* if we do not hit the high water mark, we have nothing to do */ + if(queueGetOverallQueueSize(pThis) != pThis->iHighWtrMrk) + ABORT_FINALIZE(RS_RET_OK); + + if(pThis->bRunsDA) { + /* then we need to signal that we are at the high water mark again. If that happens + * on our way down the queue, that doesn't matter, because then nobody is waiting + * on the condition variable. + * (Remember that a DA queue stops draining the queue once it has reached the low + * water mark and restarts it when the high water mark is reached again - this is + * what this code here is responsible for. Please note that all workers may have been + * terminated due to the inactivity timeout, thus we need to advise the pool that + * we need at least one). + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark in DA mode, send notify\n", + queueGetOverallQueueSize(pThis)); + queueAdviseMaxWorkers(pThis); + } else { + /* this is the case when we are currently not running in DA mode. So it is time + * to turn it back on. + */ + dbgoprint((obj_t*) pThis, "%d entries - passed high water mark for disk-assisted mode, initiating...\n", + queueGetOverallQueueSize(pThis)); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, MUTEX_ALREADY_LOCKED); /* initiate DA mode */ + } + +finalize_it: + RETiRet; +} + + +/* --------------- end code for disk-assisted queue modes -------------------- */ + + +/* Now, we define type-specific handlers. The provide a generic functionality, + * but for this specific type of queue. The mapping to these handlers happens during + * queue construction. Later on, handlers are called by pointers present in the + * queue instance object. + */ + +/* -------------------- fixed array -------------------- */ +static rsRetVal qConstructFixedArray(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->iMaxQueueSize == 0) + ABORT_FINALIZE(RS_RET_QSIZE_ZERO); + + if((pThis->tVars.farray.pBuf = malloc(sizeof(void *) * pThis->iMaxQueueSize)) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->tVars.farray.head = 0; + pThis->tVars.farray.tail = 0; + + queueChkIsDA(pThis); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructFixedArray(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + if(pThis->tVars.farray.pBuf != NULL) + free(pThis->tVars.farray.pBuf); + + RETiRet; +} + +static rsRetVal qAddFixedArray(queue_t *pThis, void* in) +{ + DEFiRet; + + ASSERT(pThis != NULL); + pThis->tVars.farray.pBuf[pThis->tVars.farray.tail] = in; + pThis->tVars.farray.tail++; + if (pThis->tVars.farray.tail == pThis->iMaxQueueSize) + pThis->tVars.farray.tail = 0; + + RETiRet; +} + +static rsRetVal qDelFixedArray(queue_t *pThis, void **out) +{ + DEFiRet; + + ASSERT(pThis != NULL); + *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.head]; + + pThis->tVars.farray.head++; + if (pThis->tVars.farray.head == pThis->iMaxQueueSize) + pThis->tVars.farray.head = 0; + + RETiRet; +} + + +/* -------------------- linked list -------------------- */ + +/* first some generic functions which are also used for the unget linked list */ + +static inline rsRetVal queueAddLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, void* pUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(*ppRoot == NULL) { + *ppRoot = *ppLast = pEntry; + } else { + (*ppLast)->pNext = pEntry; + *ppLast = pEntry; + } + +finalize_it: + RETiRet; +} + +static inline rsRetVal queueDelLinkedList(qLinkedList_t **ppRoot, qLinkedList_t **ppLast, obj_t **ppUsr) +{ + DEFiRet; + qLinkedList_t *pEntry; + + ASSERT(ppRoot != NULL); + ASSERT(ppLast != NULL); + ASSERT(ppUsr != NULL); + ASSERT(*ppRoot != NULL); + + pEntry = *ppRoot; + *ppUsr = pEntry->pUsr; + + if(*ppRoot == *ppLast) { + *ppRoot = NULL; + *ppLast = NULL; + } else { + *ppRoot = pEntry->pNext; + } + free(pEntry); + + RETiRet; +} + +/* end generic functions which are also used for the unget linked list */ + + +static rsRetVal qConstructLinkedList(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + pThis->tVars.linklist.pRoot = 0; + pThis->tVars.linklist.pLast = 0; + + queueChkIsDA(pThis); + + RETiRet; +} + + +static rsRetVal qDestructLinkedList(queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + + /* with the linked list type, there is nothing to do here. The + * reason is that the Destructor is only called after all entries + * have bene taken off the queue. In this case, there is nothing + * dynamic left with the linked list. + */ + + RETiRet; +} + +static rsRetVal qAddLinkedList(queue_t *pThis, void* pUsr) +{ + DEFiRet; + + iRet = queueAddLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, pUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + if((pEntry = (qLinkedList_t*) malloc(sizeof(qLinkedList_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pEntry->pNext = NULL; + pEntry->pUsr = pUsr; + + if(pThis->tVars.linklist.pRoot == NULL) { + pThis->tVars.linklist.pRoot = pThis->tVars.linklist.pLast = pEntry; + } else { + pThis->tVars.linklist.pLast->pNext = pEntry; + pThis->tVars.linklist.pLast = pEntry; + } + +finalize_it: +#endif + RETiRet; +} + +static rsRetVal qDelLinkedList(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + iRet = queueDelLinkedList(&pThis->tVars.linklist.pRoot, &pThis->tVars.linklist.pLast, ppUsr); +#if 0 + qLinkedList_t *pEntry; + + ASSERT(pThis != NULL); + ASSERT(pThis->tVars.linklist.pRoot != NULL); + + pEntry = pThis->tVars.linklist.pRoot; + *ppUsr = pEntry->pUsr; + + if(pThis->tVars.linklist.pRoot == pThis->tVars.linklist.pLast) { + pThis->tVars.linklist.pRoot = NULL; + pThis->tVars.linklist.pLast = NULL; + } else { + pThis->tVars.linklist.pRoot = pEntry->pNext; + } + free(pEntry); + +#endif + RETiRet; +} + + +/* -------------------- disk -------------------- */ + + +static rsRetVal +queueLoadPersStrmInfoFixup(strm_t *pStrm, queue_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_TYPE_assert(pThis, queue); + CHKiRet(strmSetDir(pStrm, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); +finalize_it: + RETiRet; +} + + +/* This method checks if we have a QIF file for the current queue (no matter of + * queue mode). Returns RS_RET_OK if we have a QIF file or an error status otherwise. + * rgerhards, 2008-01-15 + */ +static rsRetVal +queueHaveQIF(queue_t *pThis) +{ + DEFiRet; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pszFilePrefix == NULL) + ABORT_FINALIZE(RS_RET_NO_FILEPREFIX); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgoprint((obj_t*) pThis, "no .qi file found\n"); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + /* If we reach this point, we have a .qi file */ + +finalize_it: + RETiRet; +} + + +/* The method loads the persistent queue information. + * rgerhards, 2008-01-11 + */ +static rsRetVal +queueTryLoadPersistedInfo(queue_t *pThis) +{ + DEFiRet; + strm_t *psQIF = NULL; + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + struct stat stat_buf; + int iUngottenObjs; + obj_t *pUsr; + + ISOBJ_TYPE_assert(pThis, queue); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + /* check if the file exists */ + if(stat((char*) pszQIFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + dbgoprint((obj_t*) pThis, "clean startup, no .qi file found\n"); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + dbgoprint((obj_t*) pThis, "error %d trying to access .qi file\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .qi file */ + + CHKiRet(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_READ)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(psQIF)); + + /* first, we try to read the property bag for ourselfs */ + CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF)); + + /* then the ungotten object queue */ + iUngottenObjs = pThis->iUngottenObjs; + pThis->iUngottenObjs = 0; /* will be incremented when we add objects! */ + + while(iUngottenObjs > 0) { + /* fill the queue from disk */ + CHKiRet(obj.Deserialize((void*) &pUsr, (uchar*)"msg", psQIF, NULL, NULL)); + queueUngetObj(pThis, pUsr, MUTEX_ALREADY_LOCKED); + --iUngottenObjs; /* one less */ + } + + /* and now the stream objects (some order as when persisted!) */ + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + CHKiRet(obj.Deserialize(&pThis->tVars.disk.pRead, (uchar*) "strm", psQIF, + (rsRetVal(*)(obj_t*,void*))queueLoadPersStrmInfoFixup, pThis)); + + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pWrite)); + CHKiRet(strmSeekCurrOffs(pThis->tVars.disk.pRead)); + + /* OK, we could successfully read the file, so we now can request that it be + * deleted when we are done with the persisted information. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strmDestruct(&psQIF); + + if(iRet != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "error %d reading .qi file - can not read persisted info (if any)\n", + iRet); + } + + RETiRet; +} + + +/* disk queue constructor. + * Note that we use a file limit of 10,000,000 files. That number should never pose a + * problem. If so, I guess the user has a design issue... But of course, the code can + * always be changed (though it would probably be more appropriate to increase the + * allowed file size at this point - that should be a config setting... + * rgerhards, 2008-01-10 + */ +static rsRetVal qConstructDisk(queue_t *pThis) +{ + DEFiRet; + int bRestarted = 0; + + ASSERT(pThis != NULL); + + /* and now check if there is some persistent information that needs to be read in */ + iRet = queueTryLoadPersistedInfo(pThis); + if(iRet == RS_RET_OK) + bRestarted = 1; + else if(iRet != RS_RET_FILE_NOT_FOUND) + FINALIZE; + + if(bRestarted == 1) { + ; + } else { + CHKiRet(strmConstruct(&pThis->tVars.disk.pWrite)); + CHKiRet(strmSetDir(pThis->tVars.disk.pWrite, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pWrite, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE)); + CHKiRet(strmSetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pWrite)); + + CHKiRet(strmConstruct(&pThis->tVars.disk.pRead)); + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + CHKiRet(strmSetDir(pThis->tVars.disk.pRead, glblGetWorkDir(), strlen((char*)glblGetWorkDir()))); + CHKiRet(strmSetiMaxFiles(pThis->tVars.disk.pRead, 10000000)); + CHKiRet(strmSettOperationsMode(pThis->tVars.disk.pRead, STREAMMODE_READ)); + CHKiRet(strmSetsType(pThis->tVars.disk.pRead, STREAMTYPE_FILE_CIRCULAR)); + CHKiRet(strmConstructFinalize(pThis->tVars.disk.pRead)); + + + CHKiRet(strmSetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix)); + CHKiRet(strmSetFName(pThis->tVars.disk.pRead, pThis->pszFilePrefix, pThis->lenFilePrefix)); + } + + /* now we set (and overwrite in case of a persisted restart) some parameters which + * should always reflect the current configuration variables. Be careful by doing so, + * for example file name generation must not be changed as that would break the + * ability to read existing queue files. -- rgerhards, 2008-01-12 + */ +CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize)); +CHKiRet(strmSetiMaxFileSize(pThis->tVars.disk.pRead, pThis->iMaxFileSize)); + +finalize_it: + RETiRet; +} + + +static rsRetVal qDestructDisk(queue_t *pThis) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + strmDestruct(&pThis->tVars.disk.pWrite); + strmDestruct(&pThis->tVars.disk.pRead); + + RETiRet; +} + +static rsRetVal qAddDisk(queue_t *pThis, void* pUsr) +{ + DEFiRet; + number_t nWriteCount; + + ASSERT(pThis != NULL); + + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, &nWriteCount)); + CHKiRet((objSerialize(pUsr))(pUsr, pThis->tVars.disk.pWrite)); + CHKiRet(strmFlush(pThis->tVars.disk.pWrite)); + CHKiRet(strmSetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */ + + pThis->tVars.disk.sizeOnDisk += nWriteCount; + + dbgoprint((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets\n", + nWriteCount, pThis->tVars.disk.sizeOnDisk); + +finalize_it: + RETiRet; +} + +static rsRetVal qDelDisk(queue_t *pThis, void **ppUsr) +{ + DEFiRet; + + int64 offsIn; + int64 offsOut; + + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsIn)); + CHKiRet(obj.Deserialize(ppUsr, (uchar*) "msg", pThis->tVars.disk.pRead, NULL, NULL)); + CHKiRet(strmGetCurrOffset(pThis->tVars.disk.pRead, &offsOut)); + + /* This time it is a bit tricky: we free disk space only upon file deletion. So we need + * to keep track of what we have read until we get an out-offset that is lower than the + * in-offset (which indicates file change). Then, we can subtract the whole thing from + * the on-disk size. -- rgerhards, 2008-01-30 + */ + if(offsIn < offsOut) { + pThis->tVars.disk.bytesRead += offsOut - offsIn; + } else { + pThis->tVars.disk.sizeOnDisk -= pThis->tVars.disk.bytesRead; + pThis->tVars.disk.bytesRead = offsOut; + dbgoprint((obj_t*) pThis, "a file has been deleted, now %lld octets disk space used\n", pThis->tVars.disk.sizeOnDisk); + /* awake possibly waiting enq process */ + pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */ + } + +finalize_it: + RETiRet; +} + +/* -------------------- direct (no queueing) -------------------- */ +static rsRetVal qConstructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + + +static rsRetVal qDestructDirect(queue_t __attribute__((unused)) *pThis) +{ + return RS_RET_OK; +} + +static rsRetVal qAddDirect(queue_t *pThis, void* pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* calling the consumer is quite different here than it is from a worker thread */ + /* we need to provide the consumer's return value back to the caller because in direct + * mode the consumer probably has a lot to convey (which get's lost in the other modes + * because they are asynchronous. But direct mode is deliberately synchronous. + * rgerhards, 2008-02-12 + */ + iRet = pThis->pConsumer(pThis->pUsr, pUsr); + + RETiRet; +} + +static rsRetVal qDelDirect(queue_t __attribute__((unused)) *pThis, __attribute__((unused)) void **out) +{ + return RS_RET_OK; +} + + +/* --------------- end type-specific handlers -------------------- */ + + +/* unget a user pointer that has been dequeued. This functionality is especially important + * for consumer cancel cleanup handlers. To support it, a short list of ungotten user pointers + * is maintened in memory. + * rgerhards, 2008-01-20 + */ +static rsRetVal +queueUngetObj(queue_t *pThis, obj_t *pUsr, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); /* TODO: we aborted right at this place at least 3 times -- race? 2008-02-28, -03-10, -03-15 + The second time I noticed it the queue was in destruction with NO worker threads + running. The pUsr ptr was totally off and provided no clue what it may be pointing + at (except that it looked like the static data pool). Both times, the abort happend + inside an action queue */ + + dbgoprint((obj_t*) pThis, "ungetting user object %s\n", obj.GetName(pUsr)); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + iRet = queueAddLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, pUsr); + ++pThis->iUngottenObjs; /* indicate one more */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + RETiRet; +} + + +/* dequeues a user pointer from the ungotten queue. Pointers from there should always be + * dequeued first. + * + * This function must only be called when the mutex is locked! + * + * rgerhards, 2008-01-29 + */ +static rsRetVal +queueGetUngottenObj(queue_t *pThis, obj_t **ppUsr) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(ppUsr != NULL); + + iRet = queueDelLinkedList(&pThis->pUngetRoot, &pThis->pUngetLast, ppUsr); + --pThis->iUngottenObjs; /* indicate one less */ + dbgoprint((obj_t*) pThis, "dequeued ungotten user object %s\n", obj.GetName(*ppUsr)); + + RETiRet; +} + + +/* generic code to add a queue entry + * We use some specific code to most efficiently support direct mode + * queues. This is justified in spite of the gain and the need to do some + * things truely different. -- rgerhards, 2008-02-12 + */ +static rsRetVal +queueAdd(queue_t *pThis, void *pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + CHKiRet(pThis->qAdd(pThis, pUsr)); + + if(pThis->qType != QUEUETYPE_DIRECT) { + ++pThis->iQueueSize; + dbgoprint((obj_t*) pThis, "entry added, size now %d entries\n", pThis->iQueueSize); + } + +finalize_it: + RETiRet; +} + + +/* generic code to remove a queue entry + * rgerhards, 2008-01-29: we must first see if there is any object in the + * ungotten list and, if so, dequeue it first. + */ +static rsRetVal +queueDel(queue_t *pThis, void *pUsr) +{ + DEFiRet; + + ASSERT(pThis != NULL); + + /* we do NOT abort if we encounter an error, because otherwise the queue + * will not be decremented, what will most probably result in an endless loop. + * If we decrement, however, we may lose a message. But that is better than + * losing the whole process because it loops... -- rgerhards, 2008-01-03 + */ + if(pThis->iUngottenObjs > 0) { + iRet = queueGetUngottenObj(pThis, (obj_t**) pUsr); + } else { + iRet = pThis->qDel(pThis, pUsr); + --pThis->iQueueSize; + } + + dbgoprint((obj_t*) pThis, "entry deleted, state %d, size now %d entries\n", + iRet, pThis->iQueueSize); + + RETiRet; +} + + +/* This function shuts down all worker threads and waits until they + * have terminated. If they timeout, they are cancelled. Parameters have been set + * before this function is called so that DA queues will be fully persisted to + * disk (if configured to do so). + * rgerhards, 2008-01-24 + * Please note that this function shuts down BOTH the parent AND the child queue + * in DA case. This is necessary because their timeouts are tightly coupled. Most + * importantly, the timeouts would be applied twice (or logic be extremely + * complex) if each would have its own shutdown. The function does not self check + * this condition - the caller must make sure it is not called with a parent. + */ +static rsRetVal queueShutdownWorkers(queue_t *pThis) +{ + DEFiRet; + DEFVARS_mutexProtection; + struct timespec tTimeout; + rsRetVal iRetLocal; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */ + + dbgoprint((obj_t*) pThis, "initiating worker thread shutdown sequence\n"); + + /* we reduce the low water mark in any case. This is not absolutely necessary, but + * it is useful because we enable DA mode at several spots below and so we do not need + * to think about the low water mark each time. + */ + pThis->iHighWtrMrk = 1; /* if we do not do this, the DA queue will not stop! */ + pThis->iLowWtrMrk = 0; + + /* first try to shutdown the queue within the regular shutdown period */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + if(pThis->bRunsDA) { + /* We may have waited on the low water mark. As it may have changed, we + * see if we reactivate the worker. + */ + wtpAdviseMaxWorkers(pThis->pWtpDA, 1); + } + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + + /* Now wait for the queue's workers to shut down. Note that we run into the code even if we just found + * out there are no active workers - that doesn't matter: the wtp knows about that and so will + * return immediately. + * We do not yet care about the DA worker - that will be handled down later in the process. + * Note that we must not request shutdown right now - that may introduce a race: if the regular queue + * still runs DA assisted and the DA worker gets scheduled first, it will terminate itself (if the DA + * queue happens to be empty at that instant). Then the regular worker enqueues messages, what will lead + * to a restart of the worker. Of course, everything will continue to run, but in a bit sub-optimal way + * (from a performance point of view). So we don't do anything right now. The DA queue will continue to + * process messages and shutdown itself in any case if there is nothing to do. So we don't loose anything + * by not requesting shutdown now. + * rgerhards, 2008-01-25 + */ + /* first calculate absolute timeout - we need the absolute value here, because we need to coordinate + * shutdown of both the regular and DA queue on *the same* timeout. + */ + timeoutComp(&tTimeout, pThis->toQShutdown); + dbgoprint((obj_t*) pThis, "trying shutdown of regular workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "regular shutdown timed out on primary queue (this is OK)\n"); + } else { + /* OK, the regular queue is now shut down. So we can now wait for the DA queue (if running DA) */ + dbgoprint((obj_t*) pThis, "regular queue workers shut down.\n"); + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(pThis->bRunsDA) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n", + queueGetID(pThis->pqDA)); + /* we use the same absolute timeout as above, so we do not use more than the configured + * timeout interval! + */ + dbgoprint((obj_t*) pThis, "trying shutdown of DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "shutdown timed out on DA queue (this is OK)\n"); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } + + /* when we reach this point, both queues are either empty or the regular queue shutdown timeout + * has expired. Now we need to check if we are configured to not loose messages. If so, we need + * to persist the queue to disk (this is only possible if the queue is DA-enabled). We must also + * set the primary queue to SHUTDOWN_IMMEDIATE, as it shall now terminate as soon as its consumer + * is done. This is especially important as we otherwise may interfere with queue order while the + * DA consumer is running. -- rgerhards, 2008-01-27 + * Note: there was a note that we should not wait eternally on the DA worker if we run in + * enqueue-only note. I have reviewed the code and think there is no need for this check. Howerver, + * I'd like to keep this note in here should we happen to run into some related trouble. + * rgerhards, 2008-01-28 + */ + wtpSetState(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE); /* set primary queue to shutdown only */ + + /* at this stage, we need to have the DA worker properly initialized and running (if there is one) */ + if(pThis->bRunsDA) + queueWaitDAModeInitialized(pThis); + + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + /* optimize parameters for shutdown of DA-enabled queues */ + if(pThis->bIsDA && queueGetOverallQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) { + /* switch to enqueue-only mode so that no more actions happen */ + if(pThis->bRunsDA == 0) { + queueInitDA(pThis, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to DA mode */ + } else { + /* TODO: RACE: we may reach this point when the DA worker has been initialized (state 1) + * but is not yet running (state 2). In this case, pThis->pqDA is NULL! rgerhards, 2008-02-27 + */ + queueSetEnqOnly(pThis->pqDA, QUEUE_MODE_ENQONLY, MUTEX_ALREADY_LOCKED); /* switch to enqueue-only mode */ + } + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + /* make sure we do not timeout before we are done */ + dbgoprint((obj_t*) pThis, "bSaveOnShutdown configured, eternal timeout set\n"); + timeoutComp(&tTimeout, QUEUE_TIMEOUT_ETERNAL); + /* and run the primary queue's DA worker to drain the queue */ + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout); + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying to shut down primary queue in disk save mode, " + "continuing, but results are unpredictable\n", iRetLocal); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* now the primary queue is either empty, persisted to disk - or set to loose messages. So we + * can now request immediate shutdown of any remaining workers. Note that if bSaveOnShutdown was set, + * the queue is now empty. If regular workers are still running, and try to pull the next message, + * they will automatically terminate as there no longer is any message left to process. + */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + if(queueGetOverallQueueSize(pThis) > 0) { + timeoutComp(&tTimeout, pThis->toActShutdown); + if(wtpGetCurNumWrkr(pThis->pWtpReg, LOCK_MUTEX) > 0) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of regular workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "immediate shutdown timed out on primary queue (this is acceptable and " + "triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the primary queue " + "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + /* we need to re-aquire the mutex for the next check in this case! */ + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, LOCK_MUTEX); /* some workers may be running in parallel! */ + } + if(pThis->bIsDA && wtpGetCurNumWrkr(pThis->pWtpDA, LOCK_MUTEX) > 0) { + /* and now the same for the DA queue */ + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + dbgoprint((obj_t*) pThis, "trying immediate shutdown of DA workers\n"); + iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout); + if(iRetLocal == RS_RET_TIMED_OUT) { + dbgoprint((obj_t*) pThis, "immediate shutdown timed out on DA queue (this is acceptable and " + "triggers cancellation)\n"); + } else if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the DA queue " + "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + } else { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + + /* Now queue workers should have terminated. If not, we need to cancel them as we have applied + * all timeout setting. If any worker in any queue still executes, its consumer is possibly + * long-running and cancelling is the only way to get rid of it. Note that the + * cancellation handler will probably re-queue a user pointer, so the queue's enqueue + * function is still needed (what is no problem as we do not yet destroy the queue - but I + * thought it's a good idea to mention that fact). -- rgerhards, 2008-01-25 + */ + dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the primary queue\n"); + iRetLocal = wtpCancelAll(pThis->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel primary queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + + + /* TODO: think: do we really need to do this here? Can't it happen on DA queue destruction? If we + * disable it, we get an assertion... I think this is OK, as we need to have a certain order and + * canceling the DA workers here ensures that order. But in any instant, we may have a look at this + * code after we have reaced the milestone. -- rgerhards, 2008-01-27 + */ + /* ... and now the DA queue, if it exists (should always be after the primary one) */ + if(pThis->pqDA != NULL) { + dbgoprint((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the DA queue\n"); + iRetLocal = wtpCancelAll(pThis->pqDA->pWtpReg); /* returns immediately if all threads already have terminated */ + if(iRetLocal != RS_RET_OK) { + dbgoprint((obj_t*) pThis, "unexpected iRet state %d trying to cancel DA queue worker " + "threads, continuing, but results are unpredictable\n", iRetLocal); + } + } + + /* ... finally ... all worker threads have terminated :-) + * Well, more precisely, they *are in termination*. Some cancel cleanup handlers + * may still be running. + */ + dbgoprint((obj_t*) pThis, "worker threads terminated, remaining queue size %d.\n", queueGetOverallQueueSize(pThis)); + + RETiRet; +} + + + +/* Constructor for the queue object + * This constructs the data structure, but does not yet start the queue. That + * is done by queueStart(). The reason is that we want to give the caller a chance + * to modify some parameters before the queue is actually started. + */ +rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)) +{ + DEFiRet; + queue_t *pThis; + + ASSERT(ppThis != NULL); + ASSERT(pConsumer != NULL); + ASSERT(iWorkerThreads >= 0); + + if((pThis = (queue_t *)calloc(1, sizeof(queue_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we have an object, so let's fill the properties */ + objConstructSetObjInfo(pThis); + if((pThis->pszSpoolDir = (uchar*) strdup((char*)glblGetWorkDir())) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* set some water marks so that we have useful defaults if none are set specifically */ + pThis->iFullDlyMrk = (iMaxQueueSize < 100) ? iMaxQueueSize : 100; /* 100 should be far sufficient */ + pThis->iLightDlyMrk = iMaxQueueSize - (iMaxQueueSize / 100) * 70; /* default 70% */ + + pThis->lenSpoolDir = strlen((char*)pThis->pszSpoolDir); + pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */ + pThis->iQueueSize = 0; + pThis->iMaxQueueSize = iMaxQueueSize; + pThis->pConsumer = pConsumer; + pThis->iNumWorkerThreads = iWorkerThreads; + pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */ + + pThis->pszFilePrefix = NULL; + pThis->qType = qType; + + /* set type-specific handlers and other very type-specific things (we can not totally hide it...) */ + switch(qType) { + case QUEUETYPE_FIXED_ARRAY: + pThis->qConstruct = qConstructFixedArray; + pThis->qDestruct = qDestructFixedArray; + pThis->qAdd = qAddFixedArray; + pThis->qDel = qDelFixedArray; + break; + case QUEUETYPE_LINKEDLIST: + pThis->qConstruct = qConstructLinkedList; + pThis->qDestruct = qDestructLinkedList; + pThis->qAdd = qAddLinkedList; + pThis->qDel = (rsRetVal (*)(queue_t*,void**)) qDelLinkedList; + break; + case QUEUETYPE_DISK: + pThis->qConstruct = qConstructDisk; + pThis->qDestruct = qDestructDisk; + pThis->qAdd = qAddDisk; + pThis->qDel = qDelDisk; + /* special handling */ + pThis->iNumWorkerThreads = 1; /* we need exactly one worker */ + break; + case QUEUETYPE_DIRECT: + pThis->qConstruct = qConstructDirect; + pThis->qDestruct = qDestructDirect; + pThis->qAdd = qAddDirect; + pThis->qDel = qDelDirect; + break; + } + +finalize_it: + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP + RETiRet; +} + + +/* cancellation cleanup handler for queueWorker () + * Updates admin structure and frees ressources. + * Params: + * arg1 - user pointer (in this case a queue_t) + * arg2 - user data pointer (in this case a queue data element, any object [queue's pUsr ptr!]) + * Note that arg2 may be NULL, in which case no dequeued but unprocessed pUsr exists! + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueConsumerCancelCleanup(void *arg1, void *arg2) +{ + DEFiRet; + + queue_t *pThis = (queue_t*) arg1; + obj_t *pUsr = (obj_t*) arg2; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pUsr != NULL) { + /* make sure the data element is not lost */ + dbgoprint((obj_t*) pThis, "cancelation cleanup handler consumer called, we need to unget one user data element\n"); + CHKiRet(queueUngetObj(pThis, pUsr, LOCK_MUTEX)); + } + +finalize_it: + RETiRet; +} + + + +/* This function checks if the provided message shall be discarded and does so, if needed. + * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to + * provide real-time creation of spool files. + * Note: cached copies of iQueueSize and bRunsDA are provided so that no mutex locks are required. + * The caller must have obtained them while the mutex was locked. Of course, these values may no + * longer be current, but that is OK for the discard check. At worst, the message is either processed + * or discarded when it should not have been. As discarding is in itself somewhat racy and erratic, + * that is no problems for us. This function MUST NOT lock the queue mutex, it could result in + * deadlocks! + * If the message is discarded, it can no longer be processed by the caller. So be sure to check + * the return state! + * rgerhards, 2008-01-24 + */ +static int queueChkDiscardMsg(queue_t *pThis, int iQueueSize, int bRunsDA, void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + int iSeverity; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_assert(pUsr); + + if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk && bRunsDA == 0) { + iRetLocal = objGetSeverity(pUsr, &iSeverity); + if(iRetLocal == RS_RET_OK && iSeverity >= pThis->iDiscardSeverity) { + dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), discarded severity %d message\n", + iQueueSize, iSeverity); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } else { + dbgoprint((obj_t*) pThis, "queue nearly full (%d entries), but could not drop msg " + "(iRet: %d, severity %d)\n", iQueueSize, iRetLocal, iSeverity); + } + } + +finalize_it: + RETiRet; +} + + +/* dequeue the queued object for the queue consumers. + * rgerhards, 2008-10-21 + */ +static rsRetVal +queueDequeueConsumable(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + void *pUsr; + int iQueueSize; + int bRunsDA; /* cache for early mutex release */ + + /* dequeue element (still protected from mutex) */ + iRet = queueDel(pThis, &pUsr); + queueChkPersist(pThis); + iQueueSize = queueGetOverallQueueSize(pThis); /* cache this for after mutex release */ + bRunsDA = pThis->bRunsDA; /* cache this for after mutex release */ + + /* We now need to save the user pointer for the cancel cleanup handler, BUT ONLY + * if we could successfully obtain a user pointer. Otherwise, we would bring the + * cancel cleanup handler into big troubles (and we did ;)). Note that we can + * NOT set the variable further below, as this may lead to an object leak. We + * may get cancelled before we reach that part of the code, so the only + * solution is to do it here. -- rgerhards, 2008-02-27 + */ + if(iRet == RS_RET_OK) { + pWti->pUsrp = pUsr; + } + + /* awake some flow-controlled sources if we can do this right now */ + /* TODO: this could be done better from a performance point of view -- do it only if + * we have someone waiting for the condition (or only when we hit the watermark right + * on the nail [exact value]) -- rgerhards, 2008-03-14 + */ + if(iQueueSize < pThis->iFullDlyMrk) { + pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk); + } + + if(iQueueSize < pThis->iLightDlyMrk) { + pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk); + } + + d_pthread_mutex_unlock(pThis->mut); + pthread_cond_signal(&pThis->notFull); + pthread_setcancelstate(iCancelStateSave, NULL); + /* WE ARE NO LONGER PROTECTED BY THE MUTEX */ + + /* do actual processing (the lengthy part, runs in parallel) + * If we had a problem while dequeing, we do not call the consumer, + * but we otherwise ignore it. This is in the hopes that it will be + * self-healing. However, this is really not a good thing. + * rgerhards, 2008-01-03 + */ + if(iRet != RS_RET_OK) + FINALIZE; + + /* we are running in normal, non-disk-assisted mode do a quick check if we need to drain the queue. + * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to + * provide real-time creation of spool files. + * Note: It is OK to use the cached iQueueSize here, because it does not hurt if it is slightly wrong. + */ + CHKiRet(queueChkDiscardMsg(pThis, iQueueSize, bRunsDA, pUsr)); + +finalize_it: + if(iRet != RS_RET_OK && iRet != RS_RET_DISCARDMSG) { + dbgoprint((obj_t*) pThis, "error %d dequeueing element - ignoring, but strange things " + "may happen\n", iRet); + } + RETiRet; +} + + +/* The rate limiter + * + * Here we may wait if a dequeue time window is defined or if we are + * rate-limited. TODO: If we do so, we should also look into the + * way new worker threads are spawned. Obviously, it doesn't make much + * sense to spawn additional worker threads when none of them can do any + * processing. However, it is deemed acceptable to allow this for an initial + * implementation of the timeframe/rate limiting feature. + * Please also note that these feature could also be implemented at the action + * level. However, that would limit them to be used together with actions. We have + * taken the broader approach, moving it right into the queue. This is even + * necessary if we want to prevent spawning of multiple unnecessary worker + * threads as described above. -- rgerhards, 2008-04-02 + * + * + * time window: tCurr is current time; tFrom is start time, tTo is end time (in mil 24h format). + * We may have tFrom = 4, tTo = 10 --> run from 4 to 10 hrs. nice and happy + * we may also have tFrom= 22, tTo = 4 -> run from 10pm to 4am, which is actually two + * windows: 0-4; 22-23:59 + * so when to run? Let's assume we have 3am + * + * if(tTo < tFrom) { + * if(tCurr < tTo [3 < 4] || tCurr > tFrom [3 > 22]) + * do work + * else + * sleep for tFrom - tCurr "hours" [22 - 5 --> 17] + * } else { + * if(tCurr >= tFrom [3 >= 4] && tCurr < tTo [3 < 10]) + * do work + * else + * sleep for tTo - tCurr "hours" [4 - 3 --> 1] + * } + * + * Bottom line: we need to check which type of window we have and need to adjust our + * logic accordingly. Of course, sleep calculations need to be done up to the minute, + * but you get the idea from the code above. + */ +static rsRetVal +queueRateLimiter(queue_t *pThis) +{ + DEFiRet; + int iDelay; + int iHrCurr; + time_t tCurr; + struct tm m; + + ISOBJ_TYPE_assert(pThis, queue); + + dbgoprint((obj_t*) pThis, "entering rate limiter\n"); + + iDelay = 0; + if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */ + /* time calls are expensive, so only do them when needed */ + time(&tCurr); + localtime_r(&tCurr, &m); + iHrCurr = m.tm_hour; + + if(pThis->iDeqtWinToHr < pThis->iDeqtWinFromHr) { + if(iHrCurr < pThis->iDeqtWinToHr || iHrCurr > pThis->iDeqtWinFromHr) { + ; /* do not delay */ + } else { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } else { + if(iHrCurr >= pThis->iDeqtWinFromHr && iHrCurr < pThis->iDeqtWinToHr) { + ; /* do not delay */ + } else { + if(iHrCurr < pThis->iDeqtWinFromHr) { + iDelay = (pThis->iDeqtWinFromHr - iHrCurr - 1) * 3600; /* -1 as we are already in the hour */ + iDelay += (60 - m.tm_min) * 60; + iDelay += 60 - m.tm_sec; + } else { + iDelay = (24 - iHrCurr + pThis->iDeqtWinFromHr) * 3600; + /* this time, we are already into the next hour, so we need + * to subtract our current minute and seconds. + */ + iDelay -= m.tm_min * 60; + iDelay -= m.tm_sec; + } + } + } + } + + if(iDelay > 0) { + dbgoprint((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay); + srSleep(iDelay, 0); + } + + RETiRet; +} + + + +/* This is the queue consumer in the regular (non-DA) case. It is + * protected by the queue mutex, but MUST release it as soon as possible. + * rgerhards, 2008-01-21 + */ +static rsRetVal +queueConsumerReg(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(pThis->pConsumer(pThis->pUsr, pWti->pUsrp)); + + /* we now need to check if we should deliberately delay processing a bit + * and, if so, do that. -- rgerhards, 2008-01-30 + */ + if(pThis->iDeqSlowdown) { + dbgoprint((obj_t*) pThis, "sleeping %d microseconds as requested by config params\n", + pThis->iDeqSlowdown); + srSleep(pThis->iDeqSlowdown / 1000000, pThis->iDeqSlowdown % 1000000); + } + +finalize_it: + RETiRet; +} + + +/* This is a special consumer to feed the disk-queue in disk-assited mode. + * When active, our own queue more or less acts as a memory buffer to the disk. + * So this consumer just needs to drain the memory queue and submit entries + * to the disk queue. The disk queue will then call the actual consumer from + * the app point of view (we chain two queues here). + * When this method is entered, the mutex is always locked and needs to be unlocked + * as part of the processing. + * rgerhards, 2008-01-14 + */ +static rsRetVal +queueConsumerDA(queue_t *pThis, wti_t *pWti, int iCancelStateSave) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ISOBJ_TYPE_assert(pWti, wti); + + CHKiRet(queueDequeueConsumable(pThis, pWti, iCancelStateSave)); + CHKiRet(queueEnqObj(pThis->pqDA, eFLOWCTL_NO_DELAY, pWti->pUsrp)); + +finalize_it: + dbgoprint((obj_t*) pThis, "DAConsumer returns with iRet %d\n", iRet); + RETiRet; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. + * Version for the DA worker thread. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrDA(queue_t *pThis) +{ + /* if our queue is in destruction, we drain to the DA queue and so we shall not terminate + * until we have done so. + */ + int bStopWrkr; + + BEGINfunc + + if(pThis->bEnqOnly) { + bStopWrkr = 1; + } else { + if(pThis->bRunsDA) { + ASSERT(pThis->pqDA != NULL); + if( pThis->pqDA->bEnqOnly + && pThis->pqDA->sizeOnDiskMax > 0 + && pThis->pqDA->tVars.disk.sizeOnDisk > pThis->pqDA->sizeOnDiskMax) { + /* this queue can never grow, so we can give up... */ + bStopWrkr = 1; + } else if(queueGetOverallQueueSize(pThis) < pThis->iHighWtrMrk && pThis->bQueueStarted == 1) { + bStopWrkr = 1; + } else { + bStopWrkr = 0; + } + } else { + bStopWrkr = 1; + } + } + + ENDfunc + return bStopWrkr; +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! + * If we are a child, we have done our duty when the queue is empty. In that case, + * we can terminate. + * Version for the regular worker thread. NOTE: the pThis->bRunsDA is different from + * the DA queue + */ +static int +queueChkStopWrkrReg(queue_t *pThis) +{ + return pThis->bEnqOnly || pThis->bRunsDA || (pThis->pqParent != NULL && queueGetOverallQueueSize(pThis) == 0); +} + + +/* must only be called when the queue mutex is locked, else results + * are not stable! DA queue version + */ +static int +queueIsIdleDA(queue_t *pThis) +{ + /* remember: iQueueSize is the DA queue size, not the main queue! */ + /* TODO: I think we need just a single function for DA and non-DA mode - but I leave it for now as is */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +} +/* must only be called when the queue mutex is locked, else results + * are not stable! Regular queue version + */ +static int +queueIsIdleReg(queue_t *pThis) +{ +#if 0 /* enable for performance testing */ + int ret; + ret = queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk); + if(ret) fprintf(stderr, "queue is idle\n"); + return ret; +#else + /* regular code! */ + return(queueGetOverallQueueSize(pThis) == 0 || (pThis->bRunsDA && queueGetOverallQueueSize(pThis) <= pThis->iLowWtrMrk)); +#endif +} + + +/* This function is called when a worker thread for the regular queue is shut down. + * If we are the primary queue, this is not really interesting to us. If, however, + * we are the DA (child) queue, that means the DA queue is empty. In that case, we + * need to signal the parent queue's DA worker, so that it can terminate DA mode. + * rgerhards, 2008-01-26 + * rgerhards, 2008-02-27: HOWEVER, in a shutdown condition, it may be that the parent's worker thread pool + * has already been terminated and destructed. This *is* a legal condition and happens + * from time to time in practice. So we need to signal only if there still is a + * parent DA worker queue. Please keep in mind that the the parent's DA worker + * pool is DIFFERENT from our (DA queue) regular worker pool. So when the parent's + * pWtpDA is destructed, there can still be some of our (DAq/wtp) threads be running. + * I am telling this, because I, too, always get confused by those... + */ +static rsRetVal +queueRegOnWrkrShutdown(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 1; /* indicate we are done */ + if(pThis->pqParent->pWtpDA != NULL) { /* see comment in function header from 2008-02-27 */ + wtpAdviseMaxWorkers(pThis->pqParent->pWtpDA, 1); /* reactivate DA worker (always 1) */ + } + } + + RETiRet; +} + + +/* The following function is called when a regular queue worker starts up. We need this + * hook to indicate in the parent queue (if we are a child) that we are not done yet. + */ +static rsRetVal +queueRegOnWrkrStartup(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->pqParent != NULL) { + pThis->pqParent->bChildIsDone = 0; + } + + RETiRet; +} + + +/* start up the queue - it must have been constructed and parameters defined + * before. + */ +rsRetVal queueStart(queue_t *pThis) /* this is the ConstructionFinalizer */ +{ + DEFiRet; + rsRetVal iRetLocal; + int bInitialized = 0; /* is queue already initialized? */ + uchar pszBuf[64]; + size_t lenBuf; + + ASSERT(pThis != NULL); + + /* we need to do a quick check if our water marks are set plausible. If not, + * we correct the most important shortcomings. TODO: do that!!!! -- rgerhards, 2008-03-14 + */ + + /* finalize some initializations that could not yet be done because it is + * influenced by properties which might have been set after queueConstruct () + */ + if(pThis->pqParent == NULL) { + pThis->mut = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t)); + pthread_mutex_init(pThis->mut, NULL); + } else { + /* child queue, we need to use parent's mutex */ + dbgoprint((obj_t*) pThis, "I am a child\n"); + pThis->mut = pThis->pqParent->mut; + } + + pthread_mutex_init(&pThis->mutThrdMgmt, NULL); + pthread_cond_init (&pThis->condDAReady, NULL); + pthread_cond_init (&pThis->notFull, NULL); + pthread_cond_init (&pThis->notEmpty, NULL); + pthread_cond_init (&pThis->belowFullDlyWtrMrk, NULL); + pthread_cond_init (&pThis->belowLightDlyWtrMrk, NULL); + + /* call type-specific constructor */ + CHKiRet(pThis->qConstruct(pThis)); /* this also sets bIsDA */ + + dbgoprint((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, qsize %d, child %d starting\n", + pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize, + queueGetOverallQueueSize(pThis), pThis->pqParent == NULL ? 0 : 1); + + if(pThis->qType == QUEUETYPE_DIRECT) + FINALIZE; /* with direct queues, we are already finished... */ + + /* create worker thread pools for regular operation. The DA pool is created on an as-needed + * basis, which potentially means never under most circumstances. + */ + lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:Reg", obj.GetName((obj_t*) pThis)); + CHKiRet(wtpConstruct (&pThis->pWtpReg)); + CHKiRet(wtpSetDbgHdr (pThis->pWtpReg, pszBuf, lenBuf)); + CHKiRet(wtpSetpfRateLimiter (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRateLimiter)); + CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueChkStopWrkrReg)); + CHKiRet(wtpSetpfIsIdle (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) queueIsIdleReg)); + CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti, int)) queueConsumerReg)); + CHKiRet(wtpSetpfOnWorkerCancel (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void*pWti))queueConsumerCancelCleanup)); + CHKiRet(wtpSetpfOnWorkerStartup (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrStartup)); + CHKiRet(wtpSetpfOnWorkerShutdown(pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) queueRegOnWrkrShutdown)); + CHKiRet(wtpSetpmutUsr (pThis->pWtpReg, pThis->mut)); + CHKiRet(wtpSetpcondBusy (pThis->pWtpReg, &pThis->notEmpty)); + CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpReg, pThis->iNumWorkerThreads)); + CHKiRet(wtpSettoWrkShutdown (pThis->pWtpReg, pThis->toWrkShutdown)); + CHKiRet(wtpSetpUsr (pThis->pWtpReg, pThis)); + CHKiRet(wtpConstructFinalize (pThis->pWtpReg)); + + /* initialize worker thread instances */ + if(pThis->bIsDA) { + /* If we are disk-assisted, we need to check if there is a QIF file + * which we need to load. -- rgerhards, 2008-01-15 + */ + iRetLocal = queueHaveQIF(pThis); + if(iRetLocal == RS_RET_OK) { + dbgoprint((obj_t*) pThis, "on-disk queue present, needs to be reloaded\n"); + queueInitDA(pThis, QUEUE_MODE_ENQDEQ, LOCK_MUTEX); /* initiate DA mode */ + bInitialized = 1; /* we are done */ + } else { + /* TODO: use logerror? -- rgerhards, 2008-01-16 */ + dbgoprint((obj_t*) pThis, "error %d trying to access on-disk queue files, starting without them. " + "Some data may be lost\n", iRetLocal); + } + } + + if(!bInitialized) { + dbgoprint((obj_t*) pThis, "queue starts up without (loading) any DA disk state (this is normal for the DA " + "queue itself!)\n"); + } + + /* if the queue already contains data, we need to start the correct number of worker threads. This can be + * the case when a disk queue has been loaded. If we did not start it here, it would never start. + */ + queueAdviseMaxWorkers(pThis); + pThis->bQueueStarted = 1; + +finalize_it: + RETiRet; +} + + +/* persist the queue to disk. If we have something to persist, we first + * save the information on the queue properties itself and then we call + * the queue-type specific drivers. + * Variable bIsCheckpoint is set to 1 if the persist is for a checkpoint, + * and 0 otherwise. + * rgerhards, 2008-01-10 + */ +static rsRetVal queuePersist(queue_t *pThis, int bIsCheckpoint) +{ + DEFiRet; + strm_t *psQIF = NULL; /* Queue Info File */ + uchar pszQIFNam[MAXFNAME]; + size_t lenQIFNam; + obj_t *pUsr; + + ASSERT(pThis != NULL); + + if(pThis->qType != QUEUETYPE_DISK) { + if(queueGetOverallQueueSize(pThis) > 0) { + /* This error code is OK, but we will probably not implement this any time + * The reason is that persistence happens via DA queues. But I would like to + * leave the code as is, as we so have a hook in case we need one. + * -- rgerhards, 2008-01-28 + */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); + } else + FINALIZE; /* if the queue is empty, we are happy and done... */ + } + + dbgoprint((obj_t*) pThis, "persisting queue to disk, %d entries...\n", queueGetOverallQueueSize(pThis)); + + /* Construct file name */ + lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar), "%s/%s.qi", + (char*) glblGetWorkDir(), (char*)pThis->pszFilePrefix); + + if((bIsCheckpoint != QUEUE_CHECKPOINT) && (queueGetOverallQueueSize(pThis) == 0)) { + if(pThis->bNeedDelQIF) { + unlink((char*)pszQIFNam); + pThis->bNeedDelQIF = 0; + } + /* indicate spool file needs to be deleted */ + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 1)); + FINALIZE; /* nothing left to do, so be happy */ + } + + CHKiRet(strmConstruct(&psQIF)); + CHKiRet(strmSettOperationsMode(psQIF, STREAMMODE_WRITE)); + CHKiRet(strmSetiAddtlOpenFlags(psQIF, O_TRUNC)); + CHKiRet(strmSetsType(psQIF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psQIF, pszQIFNam, lenQIFNam)); + CHKiRet(strmConstructFinalize(psQIF)); + + /* first, write the property bag for ourselfs + * And, surprisingly enough, we currently need to persist only the size of the + * queue. All the rest is re-created with then-current config parameters when the + * queue is re-created. Well, we'll also save the current queue type, just so that + * we know when somebody has changed the queue type... -- rgerhards, 2008-01-11 + */ + CHKiRet(obj.BeginSerializePropBag(psQIF, (obj_t*) pThis)); + objSerializeSCALAR(psQIF, iQueueSize, INT); + objSerializeSCALAR(psQIF, iUngottenObjs, INT); + objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64); + objSerializeSCALAR(psQIF, tVars.disk.bytesRead, INT64); + CHKiRet(obj.EndSerialize(psQIF)); + + /* now we must persist all objects on the ungotten queue - they can not go to + * to the regular files. -- rgerhards, 2008-01-29 + */ + while(pThis->iUngottenObjs > 0) { + CHKiRet(queueGetUngottenObj(pThis, &pUsr)); + CHKiRet((objSerialize(pUsr))(pUsr, psQIF)); + objDestruct(pUsr); + } + + /* now persist the stream info */ + CHKiRet(strmSerialize(pThis->tVars.disk.pWrite, psQIF)); + CHKiRet(strmSerialize(pThis->tVars.disk.pRead, psQIF)); + + /* tell the input file object that it must not delete the file on close if the queue + * is non-empty - but only if we are not during a simple checkpoint + */ + if(bIsCheckpoint != QUEUE_CHECKPOINT) { + CHKiRet(strmSetbDeleteOnClose(pThis->tVars.disk.pRead, 0)); + } + + /* we have persisted the queue object. So whenever it comes to an empty queue, + * we need to delete the QIF. Thus, we indicte that need. + */ + pThis->bNeedDelQIF = 1; + +finalize_it: + if(psQIF != NULL) + strmDestruct(&psQIF); + + RETiRet; +} + + +/* check if we need to persist the current queue info. If an + * error occurs, thus should be ignored by caller (but we still + * abide to our regular call interface)... + * rgerhards, 2008-01-13 + */ +rsRetVal queueChkPersist(queue_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(pThis->iPersistUpdCnt && ++pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) { + queuePersist(pThis, QUEUE_CHECKPOINT); + pThis->iUpdsSincePersist = 0; + } + + RETiRet; +} + + +/* destructor for the queue object */ +BEGINobjDestruct(queue) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(queue) + pThis->bQueueInDestruction = 1; /* indicate we are in destruction (modifies some behaviour) */ + + /* shut down all workers (handles *all* of the persistence logic) + * See function head comment of queueShutdownWorkers () on why we don't call it + * We also do not need to shutdown workers when we are in enqueue-only mode or we are a + * direct queue - because in both cases we have none... ;) + * with a child! -- rgerhards, 2008-01-28 + */ + if(pThis->qType != QUEUETYPE_DIRECT && !pThis->bEnqOnly && pThis->pqParent == NULL) + queueShutdownWorkers(pThis); + + /* finally destruct our (regular) worker thread pool + * Note: currently pWtpReg is never NULL, but if we optimize our logic, this may happen, + * e.g. when they are not created in enqueue-only mode. We already check the condition + * as this may otherwise be very hard to find once we optimize (and have long forgotten + * about this condition here ;) + * rgerhards, 2008-01-25 + */ + if(pThis->qType != QUEUETYPE_DIRECT && pThis->pWtpReg != NULL) { + wtpDestruct(&pThis->pWtpReg); + } + + /* Now check if we actually have a DA queue and, if so, destruct it. + * Note that the wtp must be destructed first, it may be in cancel cleanup handler + * *right now* and actually *need* to access the queue object to persist some final + * data (re-queueing case). So we need to destruct the wtp first, which will make + * sure all workers have terminated. Please note that this also generates a situation + * where it is possible that the DA queue has a parent pointer but the parent has + * no WtpDA associated with it - which is perfectly legal thanks to this code here. + */ + if(pThis->pWtpDA != NULL) { + wtpDestruct(&pThis->pWtpDA); + } + if(pThis->pqDA != NULL) { + queueDestruct(&pThis->pqDA); + } + + /* persist the queue (we always do that - queuePersits() does cleanup if the queue is empty) + * This handler is most important for disk queues, it will finally persist the necessary + * on-disk structures. In theory, other queueing modes may implement their other (non-DA) + * methods of persisting a queue between runs, but in practice all of this is done via + * disk queues and DA mode. Anyhow, it doesn't hurt to know that we could extend it here + * if need arises (what I doubt...) -- rgerhards, 2008-01-25 + */ + CHKiRet_Hdlr(queuePersist(pThis, QUEUE_NO_CHECKPOINT)) { + dbgoprint((obj_t*) pThis, "error %d persisting queue - data lost!\n", iRet); + } + + /* finally, clean up some simple things... */ + if(pThis->pqParent == NULL) { + /* if we are not a child, we allocated our own mutex, which we now need to destroy */ + pthread_mutex_destroy(pThis->mut); + free(pThis->mut); + } + pthread_mutex_destroy(&pThis->mutThrdMgmt); + pthread_cond_destroy(&pThis->condDAReady); + pthread_cond_destroy(&pThis->notFull); + pthread_cond_destroy(&pThis->notEmpty); + pthread_cond_destroy(&pThis->belowFullDlyWtrMrk); + pthread_cond_destroy(&pThis->belowLightDlyWtrMrk); + + /* type-specific destructor */ + iRet = pThis->qDestruct(pThis); + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + if(pThis->pszSpoolDir != NULL) + free(pThis->pszSpoolDir); +ENDobjDestruct(queue) + + +/* set the queue's file prefix + * The passed-in string is duplicated. So if the caller does not need + * it any longer, it must free it. + * rgerhards, 2008-01-09 + */ +rsRetVal +queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix) +{ + DEFiRet; + + if(pThis->pszFilePrefix != NULL) + free(pThis->pszFilePrefix); + + if(pszPrefix == NULL) /* just unset the prefix! */ + ABORT_FINALIZE(RS_RET_OK); + + if((pThis->pszFilePrefix = malloc(sizeof(uchar) * iLenPrefix + 1)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pThis->pszFilePrefix, pszPrefix, iLenPrefix + 1); + pThis->lenFilePrefix = iLenPrefix; + +finalize_it: + RETiRet; +} + +/* set the queue's maximum file size + * rgerhards, 2008-01-09 + */ +rsRetVal +queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + + if(iMaxFileSize < 1024) { + ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW); + } + + pThis->iMaxFileSize = iMaxFileSize; + +finalize_it: + RETiRet; +} + + +/* enqueue a new user data element + * Enqueues the new element and awakes worker thread. + * TODO: this code still uses the "discard if queue full" approach from + * the main queue. This needs to be reconsidered or, better, done via a + * caller-selectable parameter mode. For the time being, I leave it in. + * rgerhards, 2008-01-03 + */ +rsRetVal +queueEnqObj(queue_t *pThis, flowControl_t flowCtlType, void *pUsr) +{ + DEFiRet; + int iCancelStateSave; + int i; + struct timespec t; + + ISOBJ_TYPE_assert(pThis, queue); + + /* Please note that this function is not cancel-safe and consequently + * sets the calling thread's cancelibility state to PTHREAD_CANCEL_DISABLE + * during its execution. If that is not done, race conditions occur if the + * thread is canceled (most important use case is input module termination). + * rgerhards, 2008-01-08 + */ + if(pThis->qType != QUEUETYPE_DIRECT) { + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + d_pthread_mutex_lock(pThis->mut); + } + + /* first check if we need to discard this message (which will cause CHKiRet() to exit) */ + CHKiRet(queueChkDiscardMsg(pThis, pThis->iQueueSize, pThis->bRunsDA, pUsr)); + + /* then check if we need to add an assistance disk queue */ + if(pThis->bIsDA) + CHKiRet(queueChkStrtDA(pThis)); + + + /* handle flow control + * There are two different flow control mechanisms: basic and advanced flow control. + * Basic flow control has always been implemented and protects the queue structures + * in that it makes sure no more data is enqueued than the queue is configured to + * support. Enhanced flow control is being added today. There are some sources which + * can easily be stopped, e.g. a file reader. This is the case because it is unlikely + * that blocking those sources will have negative effects (after all, the file is + * continued to be written). Other sources can somewhat be blocked (e.g. the kernel + * log reader or the local log stream reader): in general, nothing is lost if messages + * from these sources are not picked up immediately. HOWEVER, they can not block for + * an extended period of time, as this either causes message loss or - even worse - some + * other bad effects (e.g. unresponsive system in respect to the main system log socket). + * Finally, there are some (few) sources which can not be blocked at all. UDP syslog is + * a prime example. If a UDP message is not received, it is simply lost. So we can't + * do anything against UDP sockets that come in too fast. The core idea of advanced + * flow control is that we take into account the different natures of the sources and + * select flow control mechanisms that fit these needs. This also means, in the end + * result, that non-blockable sources like UDP syslog receive priority in the system. + * It's a side effect, but a good one ;) -- rgerhards, 2008-03-14 + */ + if(flowCtlType == eFLOWCTL_FULL_DELAY) { + while(pThis->iQueueSize >= pThis->iFullDlyMrk) { + dbgoprint((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayble message - blocking.\n"); + pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut); /* TODO error check? But what do then? */ + } + } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY) { + while(pThis->iQueueSize >= pThis->iLightDlyMrk) { + dbgoprint((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light delayble message - blocking a bit.\n"); + timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */ + pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t); /* TODO error check? But what do then? */ + } + } + + /* from our regular flow control settings, we are now ready to enqueue the object. + * However, we now need to do a check if the queue permits to add more data. If that + * is not the case, basic flow control enters the field, which means we wait for + * the queue to become ready or drop the new message. -- rgerhards, 2008-03-14 + */ + while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) + || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 + && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { + dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + } + +#if 0 // previous code, remove when done with advanced flow control + /* wait for the queue to be ready... */ + while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize) + || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0 + && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) { + dbgoprint((obj_t*) pThis, "enqueueMsg: queue FULL - waiting to drain.\n"); + timeoutComp(&t, pThis->toEnq); + if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) { + dbgoprint((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n"); + objDestruct(pUsr); + ABORT_FINALIZE(RS_RET_QUEUE_FULL); + } + } +#endif + + /* and finally enqueue the message */ + CHKiRet(queueAdd(pThis, pUsr)); + queueChkPersist(pThis); + +finalize_it: + if(pThis->qType != QUEUETYPE_DIRECT) { + d_pthread_mutex_unlock(pThis->mut); + i = pthread_cond_signal(&pThis->notEmpty); + dbgoprint((obj_t*) pThis, "EnqueueMsg signaled condition (%d)\n", i); + pthread_setcancelstate(iCancelStateSave, NULL); + } + + /* make sure at least one worker is running. */ + if(pThis->qType != QUEUETYPE_DIRECT) { + queueAdviseMaxWorkers(pThis); + } + + RETiRet; +} + + +/* set queue mode to enqueue only or not + * There is one subtle issue: this method may be called during queue + * construction or while it is running. In the former case, the queue + * mutex does not yet exist (it is NULL), while in the later case it + * must be locked. The function detects the state and operates as + * required. + * rgerhards, 2008-01-16 + */ +static rsRetVal +queueSetEnqOnly(queue_t *pThis, int bEnqOnly, int bLockMutex) +{ + DEFiRet; + DEFVARS_mutexProtection; + + ISOBJ_TYPE_assert(pThis, queue); + + /* for simplicity, we do one big mutex lock. This method is extremely seldom + * called, so that doesn't matter... -- rgerhards, 2008-01-16 + */ + if(pThis->mut != NULL) { + BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex); + } + + if(bEnqOnly == pThis->bEnqOnly) + FINALIZE; /* no change, nothing to do */ + + if(pThis->bQueueStarted) { + /* we need to adjust queue operation only if we are not during initial param setup */ + if(bEnqOnly == 1) { + /* switch to enqueue-only mode */ + /* this means we need to terminate all workers - that's it... */ + dbgoprint((obj_t*) pThis, "switching to enqueue-only mode, terminating all worker threads\n"); + if(pThis->pWtpReg != NULL) + wtpWakeupAllWrkr(pThis->pWtpReg); + if(pThis->pWtpDA != NULL) + wtpWakeupAllWrkr(pThis->pWtpDA); + } else { + /* switch back to regular mode */ + ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); /* we don't need this so far... */ + } + } + + pThis->bEnqOnly = bEnqOnly; + +finalize_it: + if(pThis->mut != NULL) { + END_MTX_PROTECTED_OPERATIONS(pThis->mut); + } + RETiRet; +} + + +/* some simple object access methods */ +DEFpropSetMeth(queue, iPersistUpdCnt, int); +DEFpropSetMeth(queue, iDeqtWinFromHr, int); +DEFpropSetMeth(queue, iDeqtWinToHr, int); +DEFpropSetMeth(queue, toQShutdown, long); +DEFpropSetMeth(queue, toActShutdown, long); +DEFpropSetMeth(queue, toWrkShutdown, long); +DEFpropSetMeth(queue, toEnq, long); +DEFpropSetMeth(queue, iHighWtrMrk, int); +DEFpropSetMeth(queue, iLowWtrMrk, int); +DEFpropSetMeth(queue, iDiscardMrk, int); +DEFpropSetMeth(queue, iFullDlyMrk, int); +DEFpropSetMeth(queue, iDiscardSeverity, int); +DEFpropSetMeth(queue, bIsDA, int); +DEFpropSetMeth(queue, iMinMsgsPerWrkr, int); +DEFpropSetMeth(queue, bSaveOnShutdown, int); +DEFpropSetMeth(queue, pUsr, void*); +DEFpropSetMeth(queue, iDeqSlowdown, int); +DEFpropSetMeth(queue, sizeOnDiskMax, int64); + + +/* This function can be used as a generic way to set properties. Only the subset + * of properties required to read persisted property bags is supported. This + * functions shall only be called by the property bag reader, thus it is static. + * rgerhards, 2008-01-11 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +static rsRetVal queueSetProperty(queue_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, queue); + ASSERT(pProp != NULL); + + if(isProp("iQueueSize")) { + pThis->iQueueSize = pProp->val.num; + } else if(isProp("iUngottenObjs")) { + pThis->iUngottenObjs = pProp->val.num; + } else if(isProp("tVars.disk.sizeOnDisk")) { + pThis->tVars.disk.sizeOnDisk = pProp->val.num; + } else if(isProp("tVars.disk.bytesRead")) { + pThis->tVars.disk.bytesRead = pProp->val.num; + } else if(isProp("qType")) { + if(pThis->qType != pProp->val.num) + ABORT_FINALIZE(RS_RET_QTYPE_MISMATCH); + } + +finalize_it: + RETiRet; +} +#undef isProp + +/* dummy */ +rsRetVal queueQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the stream class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-09 + */ +BEGINObjClassInit(queue, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + + /* now set our own handlers */ + OBJSetMethodHandler(objMethod_SETPROPERTY, queueSetProperty); +ENDObjClassInit(queue) + +/* vi:set ai: + */ diff --git a/runtime/queue.h b/runtime/queue.h new file mode 100644 index 00000000..9e75b31b --- /dev/null +++ b/runtime/queue.h @@ -0,0 +1,205 @@ +/* Definition of the queue support module. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef QUEUE_H_INCLUDED +#define QUEUE_H_INCLUDED + +#include +#include "obj.h" +#include "wtp.h" +#include "stream.h" + +/* queue types */ +typedef enum { + QUEUETYPE_FIXED_ARRAY = 0,/* a simple queue made out of a fixed (initially malloced) array fast but memoryhog */ + QUEUETYPE_LINKEDLIST = 1, /* linked list used as buffer, lower fixed memory overhead but slower */ + QUEUETYPE_DISK = 2, /* disk files used as buffer */ + QUEUETYPE_DIRECT = 3 /* no queuing happens, consumer is directly called */ +} queueType_t; + +/* list member definition for linked list types of queues: */ +typedef struct qLinkedList_S { + struct qLinkedList_S *pNext; + void *pUsr; +} qLinkedList_t; + + +typedef struct qWrkThrd_s { + pthread_t thrdID; /* thread ID */ + qWrkCmd_t tCurrCmd; /* current command to be carried out by worker */ + obj_t *pUsr; /* current user object being processed (or NULL if none) */ + struct queue_s *pQueue; /* my queue (important if only the work thread instance is passed! */ + int iThrd; /* my worker thread array index */ + pthread_cond_t condInitDone; /* signaled when the thread startup is done (once per thread existance) */ + pthread_mutex_t mut; +} qWrkThrd_t; /* type for queue worker threads */ + +/* the queue object */ +typedef struct queue_s { + BEGINobjInstance; + queueType_t qType; + int bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */ + int bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */ + int bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */ + int bQueueInDestruction;/* 1 if queue is in destruction process, 0 otherwise */ + int iQueueSize; /* Current number of elements in the queue */ + int iMaxQueueSize; /* how large can the queue grow? */ + int iNumWorkerThreads;/* number of worker threads to use */ + int iCurNumWrkThrd;/* current number of active worker threads */ + int iMinMsgsPerWrkr;/* minimum nbr of msgs per worker thread, if more, a new worker is started until max wrkrs */ + wtp_t *pWtpDA; + wtp_t *pWtpReg; + void *pUsr; /* a global, user-supplied pointer. Is passed back to consumer. */ + int iUpdsSincePersist;/* nbr of queue updates since the last persist call */ + int iPersistUpdCnt; /* persits queue info after this nbr of updates - 0 -> persist only on shutdown */ + int iHighWtrMrk; /* high water mark for disk-assisted memory queues */ + int iLowWtrMrk; /* low water mark for disk-assisted memory queues */ + int iDiscardMrk; /* if the queue is above this mark, low-severity messages are discarded */ + int iFullDlyMrk; /* if the queue is above this mark, FULL_DELAYable message are put on hold */ + int iLightDlyMrk; /* if the queue is above this mark, LIGHT_DELAYable message are put on hold */ + int iDiscardSeverity;/* messages of this severity above are discarded on too-full queue */ + int bNeedDelQIF; /* does the QIF file need to be deleted when queue becomes empty? */ + int toQShutdown; /* timeout for regular queue shutdown in ms */ + int toActShutdown; /* timeout for long-running action shutdown in ms */ + int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */ + int toEnq; /* enqueue timeout */ + /* rate limiting settings (will be expanded) */ + int iDeqSlowdown; /* slow down dequeue by specified nbr of microseconds */ + /* end rate limiting */ + /* dequeue time window settings (may also be expanded) */ + int iDeqtWinFromHr; /* begin of dequeue time window (hour only) */ + int iDeqtWinToHr; /* end of dequeue time window (hour only), set to 25 to disable deq window! */ + /* note that begin and end have specific semantics. It is a big difference if we have + * begin 4, end 22 or begin 22, end 4. In the later case, dequeuing will run from 10p, + * throughout the night and stop at 4 in the morning. In the first case, it will start + * at 4am, run throughout the day, and stop at 10 in the evening! So far, not logic is + * applied to detect user configuration errors (and tell me how should we detect what + * the user really wanted...). -- rgerhards, 2008-04-02 + */ + /* ane dequeue time window */ + rsRetVal (*pConsumer)(void *,void*); /* user-supplied consumer function for dequeued messages */ + /* calling interface for pConsumer: arg1 is the global user pointer from this structure, arg2 is the + * user pointer that was dequeued (actual sample: for actions, arg1 is the pAction and arg2 is pointer + * to message) + * rgerhards, 2008-01-28 + */ + /* type-specific handlers (set during construction) */ + rsRetVal (*qConstruct)(struct queue_s *pThis); + rsRetVal (*qDestruct)(struct queue_s *pThis); + rsRetVal (*qAdd)(struct queue_s *pThis, void *pUsr); + rsRetVal (*qDel)(struct queue_s *pThis, void **ppUsr); + /* end type-specific handler */ + /* synchronization variables */ + pthread_mutex_t mutThrdMgmt; /* mutex for the queue's thread management */ + pthread_mutex_t *mut; /* mutex for enqueing and dequeueing messages */ + pthread_cond_t notFull, notEmpty; + pthread_cond_t belowFullDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + pthread_cond_t belowLightDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */ + pthread_cond_t condDAReady;/* signalled when the DA queue is fully initialized and ready for processing */ + int bChildIsDone; /* set to 1 when the child DA queue has finished processing, 0 otherwise */ + int bThrdStateChanged; /* at least one thread state has changed if 1 */ + /* end sync variables */ + /* the following variables are always present, because they + * are not only used for the "disk" queueing mode but also for + * any other queueing mode if it is set to "disk assisted". + * rgerhards, 2008-01-09 + */ + uchar *pszSpoolDir; + size_t lenSpoolDir; + uchar *pszFilePrefix; + size_t lenFilePrefix; + int iNumberFiles; /* how many files make up the queue? */ + int64 iMaxFileSize; /* max size for a single queue file */ + int64 sizeOnDiskMax; /* maximum size on disk allowed */ + int bIsDA; /* is this queue disk assisted? */ + int bRunsDA; /* is this queue actually *running* disk assisted? */ + struct queue_s *pqDA; /* queue for disk-assisted modes */ + struct queue_s *pqParent;/* pointer to the parent (if this is a child queue) */ + int bDAEnqOnly; /* EnqOnly setting for DA queue */ + /* some data elements for the queueUngetObj() functionality. This list should always be short + * and is always kept in memory + */ + qLinkedList_t *pUngetRoot; + qLinkedList_t *pUngetLast; + int iUngottenObjs; /* number of objects currently in the "ungotten" list */ + /* now follow queueing mode specific data elements */ + union { /* different data elements based on queue type (qType) */ + struct { + long head, tail; + void** pBuf; /* the queued user data structure */ + } farray; + struct { + qLinkedList_t *pRoot; + qLinkedList_t *pLast; + } linklist; + struct { + int64 sizeOnDisk; /* current amount of disk space used */ + int64 bytesRead; /* number of bytes read from current (undeleted!) file */ + strm_t *pWrite; /* current file to be written */ + strm_t *pRead; /* current file to be read */ + } disk; + } tVars; +} queue_t; + +/* some symbolic constants for easier reference */ +#define QUEUE_MODE_ENQDEQ 0 +#define QUEUE_MODE_ENQONLY 1 + +#define QUEUE_IDX_DA_WORKER 0 /* index for the DA worker (fixed) */ +#define QUEUE_PTR_DA_WORKER(x) (&((pThis)->pWrkThrds[0])) + +/* the define below is an "eternal" timeout for the timeout settings which require a value. + * It is one day, which is not really eternal, but comes close to it if we think about + * rsyslog (e.g.: do you want to wait on shutdown for more than a day? ;)) + * rgerhards, 2008-01-17 + */ +#define QUEUE_TIMEOUT_ETERNAL 24 * 60 * 60 * 1000 + +/* prototypes */ +rsRetVal queueDestruct(queue_t **ppThis); +rsRetVal queueEnqObj(queue_t *pThis, flowControl_t flwCtlType, void *pUsr); +rsRetVal queueStart(queue_t *pThis); +rsRetVal queueSetMaxFileSize(queue_t *pThis, size_t iMaxFileSize); +rsRetVal queueSetFilePrefix(queue_t *pThis, uchar *pszPrefix, size_t iLenPrefix); +rsRetVal queueConstruct(queue_t **ppThis, queueType_t qType, int iWorkerThreads, + int iMaxQueueSize, rsRetVal (*pConsumer)(void*,void*)); +PROTOTYPEObjClassInit(queue); +PROTOTYPEpropSetMeth(queue, iPersistUpdCnt, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinFromHr, int); +PROTOTYPEpropSetMeth(queue, iDeqtWinToHr, int); +PROTOTYPEpropSetMeth(queue, toQShutdown, long); +PROTOTYPEpropSetMeth(queue, toActShutdown, long); +PROTOTYPEpropSetMeth(queue, toWrkShutdown, long); +PROTOTYPEpropSetMeth(queue, toEnq, long); +PROTOTYPEpropSetMeth(queue, iHighWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iLowWtrMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardMrk, int); +PROTOTYPEpropSetMeth(queue, iDiscardSeverity, int); +PROTOTYPEpropSetMeth(queue, iMinMsgsPerWrkr, int); +PROTOTYPEpropSetMeth(queue, bSaveOnShutdown, int); +PROTOTYPEpropSetMeth(queue, pUsr, void*); +PROTOTYPEpropSetMeth(queue, iDeqSlowdown, int); +PROTOTYPEpropSetMeth(queue, sizeOnDiskMax, int64); +#define queueGetID(pThis) ((unsigned long) pThis) + +#endif /* #ifndef QUEUE_H_INCLUDED */ -- cgit v1.2.3 From 3285f4391000b40b7c8adead2e2999de15ff8da0 Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Wed, 16 Apr 2008 08:33:12 +0200 Subject: made runtime include directory available to rest of rsyslog Signed-off-by: Rainer Gerhards --- Makefile.am | 9 ++++----- configure.ac | 6 ++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile.am b/Makefile.am index 28e960e6..58c66cef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,3 @@ -#sbin_PROGRAMS = rfc3195d rsyslogd sbin_PROGRAMS = man_MANS = @@ -90,8 +89,8 @@ rsyslogd_SOURCES = \ action.h \ atomic.h -rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) -rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) runtime/librsyslog.la +rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) $(rsrt_cflags) +rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) $(rsrt_libs) rsyslogd_LDFLAGS = -export-dynamic man_MANS += rsyslogd.8 rsyslog.conf.5 @@ -115,8 +114,8 @@ pkglib_LTLIBRARIES += lmnet.la lmtcpsrv.la lmtcpclt.la # network support # lmnet_la_SOURCES = net.c net.h -lmnet_la_CPPFLAGS = $(pthreads_cflags) -lmnet_la_LDFLAGS = -module -avoid-version +lmnet_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmnet_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmnet_la_LIBADD = # # diff --git a/configure.ac b/configure.ac index 9675a9b5..b0cc3e2e 100644 --- a/configure.ac +++ b/configure.ac @@ -508,9 +508,11 @@ AC_ARG_ENABLE(rsyslogrt, esac], [enable_rsyslogrt=yes] ) +if test "x$enable_rsyslogrt" = "xyes"; then + rsrt_cflags="-I\$(top_srcdir)/runtime -I\$(top_srcdir)" + rsrt_libs="\$(top_builddir)/runtime/librsyslog.la" +fi AM_CONDITIONAL(ENABLE_RSYSLOGRT, test x$enable_rsyslogrt = xyes) -CFLAGS="-I$(top_srcdir)/runtime $CFLAGS$ -rsrt_libs="" AC_SUBST(rsrt_cflags) AC_SUBST(rsrt_libs) -- cgit v1.2.3 From d7f33053da7eb73a8c475956af6e3847e596c80a Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 08:42:00 +0200 Subject: made everything compile with the new runtime subdirectory --- Makefile.am | 16 ++++++++-------- plugins/imfile/Makefile.am | 2 +- plugins/imgssapi/Makefile.am | 2 +- plugins/imklog/Makefile.am | 2 +- plugins/immark/Makefile.am | 2 +- plugins/imrelp/Makefile.am | 2 +- plugins/imtcp/Makefile.am | 2 +- plugins/imudp/Makefile.am | 2 +- plugins/imuxsock/Makefile.am | 2 +- plugins/omgssapi/Makefile.am | 2 +- plugins/omlibdbi/Makefile.am | 2 +- plugins/ommail/Makefile.am | 2 +- plugins/ommysql/Makefile.am | 2 +- plugins/ompgsql/Makefile.am | 2 +- plugins/omrelp/Makefile.am | 2 +- plugins/omsnmp/Makefile.am | 2 +- plugins/omtesting/Makefile.am | 2 +- 17 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Makefile.am b/Makefile.am index 58c66cef..c2685963 100644 --- a/Makefile.am +++ b/Makefile.am @@ -126,8 +126,8 @@ lmtcpsrv_la_SOURCES = \ tcps_sess.h \ tcpsrv.c \ tcpsrv.h -lmtcpsrv_la_CPPFLAGS = $(pthreads_cflags) -lmtcpsrv_la_LDFLAGS = -module -avoid-version +lmtcpsrv_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmtcpsrv_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmtcpsrv_la_LIBADD = # @@ -136,8 +136,8 @@ lmtcpsrv_la_LIBADD = lmtcpclt_la_SOURCES = \ tcpclt.c \ tcpclt.h -lmtcpclt_la_CPPFLAGS = $(pthreads_cflags) -lmtcpclt_la_LDFLAGS = -module -avoid-version +lmtcpclt_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmtcpclt_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmtcpclt_la_LIBADD = endif # if ENABLE_INET @@ -148,8 +148,8 @@ endif # if ENABLE_INET if ENABLE_REGEXP pkglib_LTLIBRARIES += lmregexp.la lmregexp_la_SOURCES = regexp.c regexp.h -lmregexp_la_CPPFLAGS = $(pthreads_cflags) -lmregexp_la_LDFLAGS = -module -avoid-version +lmregexp_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmregexp_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmregexp_la_LIBADD = endif @@ -159,8 +159,8 @@ endif if ENABLE_GSSAPI pkglib_LTLIBRARIES += lmgssutil.la lmgssutil_la_SOURCES = gss-misc.c gss-misc.h -lmgssutil_la_CPPFLAGS = $(pthreads_cflags) -lmgssutil_la_LDFLAGS = -module -avoid-version +lmgssutil_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmgssutil_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmgssutil_la_LIBADD = $(gss_libs) endif diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am index 23b64d1b..a4011d12 100644 --- a/plugins/imfile/Makefile.am +++ b/plugins/imfile/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imfile.la imfile_la_SOURCES = imfile.c -imfile_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imfile_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imfile_la_LDFLAGS = -module -avoid-version imfile_la_LIBADD = diff --git a/plugins/imgssapi/Makefile.am b/plugins/imgssapi/Makefile.am index 42a243f4..a5cce320 100644 --- a/plugins/imgssapi/Makefile.am +++ b/plugins/imgssapi/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imgssapi.la imgssapi_la_SOURCES = imgssapi.c -imgssapi_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imgssapi_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imgssapi_la_LDFLAGS = -module -avoid-version imgssapi_la_LIBADD = $(gss_libs) diff --git a/plugins/imklog/Makefile.am b/plugins/imklog/Makefile.am index 246b3306..8f50cfb2 100644 --- a/plugins/imklog/Makefile.am +++ b/plugins/imklog/Makefile.am @@ -11,6 +11,6 @@ if ENABLE_IMKLOG_LINUX imklog_la_SOURCES += linux.c module.h ksym.c ksyms.h ksym_mod.c endif -imklog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imklog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imklog_la_LDFLAGS = -module -avoid-version imklog_la_LIBADD = diff --git a/plugins/immark/Makefile.am b/plugins/immark/Makefile.am index 477f45c9..9c0f8f64 100644 --- a/plugins/immark/Makefile.am +++ b/plugins/immark/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = immark.la immark_la_SOURCES = immark.c immark.h -immark_la_CPPFLAGS = -I$(rsrt_cflags) -I$(top_srcdir) $(pthreads_cflags) +immark_la_CPPFLAGS = $(rsrt_cflags) -I$(top_srcdir) $(pthreads_cflags) immark_la_LDFLAGS = -module -avoid-version immark_la_LIBADD = diff --git a/plugins/imrelp/Makefile.am b/plugins/imrelp/Makefile.am index 53c9322c..a96e2b42 100644 --- a/plugins/imrelp/Makefile.am +++ b/plugins/imrelp/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imrelp.la imrelp_la_SOURCES = imrelp.c -imrelp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(RELP_CFLAGS) +imrelp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(RELP_CFLAGS) $(rsrt_cflags) imrelp_la_LDFLAGS = -module -avoid-version imrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/imtcp/Makefile.am b/plugins/imtcp/Makefile.am index fe43cd98..de746a95 100644 --- a/plugins/imtcp/Makefile.am +++ b/plugins/imtcp/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imtcp.la imtcp_la_SOURCES = imtcp.c -imtcp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imtcp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imtcp_la_LDFLAGS = -module -avoid-version imtcp_la_LIBADD = diff --git a/plugins/imudp/Makefile.am b/plugins/imudp/Makefile.am index 53fdad16..28ee9853 100644 --- a/plugins/imudp/Makefile.am +++ b/plugins/imudp/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imudp.la imudp_la_SOURCES = imudp.c -imudp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imudp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imudp_la_LDFLAGS = -module -avoid-version imudp_la_LIBADD = diff --git a/plugins/imuxsock/Makefile.am b/plugins/imuxsock/Makefile.am index e165bb7d..11a0ba3a 100644 --- a/plugins/imuxsock/Makefile.am +++ b/plugins/imuxsock/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = imuxsock.la imuxsock_la_SOURCES = imuxsock.c -imuxsock_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +imuxsock_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) imuxsock_la_LDFLAGS = -module -avoid-version imuxsock_la_LIBADD = diff --git a/plugins/omgssapi/Makefile.am b/plugins/omgssapi/Makefile.am index 5280a1ce..c2cbe387 100644 --- a/plugins/omgssapi/Makefile.am +++ b/plugins/omgssapi/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omgssapi.la omgssapi_la_SOURCES = omgssapi.c -omgssapi_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +omgssapi_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) omgssapi_la_LDFLAGS = -module -avoid-version omgssapi_la_LIBADD = $(gss_libs) diff --git a/plugins/omlibdbi/Makefile.am b/plugins/omlibdbi/Makefile.am index 872fc67c..d224f9e4 100644 --- a/plugins/omlibdbi/Makefile.am +++ b/plugins/omlibdbi/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omlibdbi.la omlibdbi_la_SOURCES = omlibdbi.c -omlibdbi_la_CPPFLAGS = -I$(top_srcdir) $(libdbi_cflags) $(pthreads_cflags) +omlibdbi_la_CPPFLAGS = -I$(top_srcdir) $(libdbi_cflags) $(pthreads_cflags) $(rsrt_cflags) omlibdbi_la_LDFLAGS = -module -avoid-version omlibdbi_la_LIBADD = $(libdbi_libs) diff --git a/plugins/ommail/Makefile.am b/plugins/ommail/Makefile.am index 7e9f5f13..fa470a43 100644 --- a/plugins/ommail/Makefile.am +++ b/plugins/ommail/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = ommail.la ommail_la_SOURCES = ommail.c -ommail_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +ommail_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) ommail_la_LDFLAGS = -module -avoid-version ommail_la_LIBADD = diff --git a/plugins/ommysql/Makefile.am b/plugins/ommysql/Makefile.am index 3b4e6d75..329c2119 100644 --- a/plugins/ommysql/Makefile.am +++ b/plugins/ommysql/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = ommysql.la ommysql_la_SOURCES = ommysql.c ommysql.h -ommysql_la_CPPFLAGS = -I$(top_srcdir) $(mysql_cflags) $(pthreads_cflags) +ommysql_la_CPPFLAGS = -I$(top_srcdir) $(mysql_cflags) $(pthreads_cflags) $(rsrt_cflags) ommysql_la_LDFLAGS = -module -avoid-version ommysql_la_LIBADD = $(mysql_libs) diff --git a/plugins/ompgsql/Makefile.am b/plugins/ompgsql/Makefile.am index b2e3effa..cc1c5f49 100644 --- a/plugins/ompgsql/Makefile.am +++ b/plugins/ompgsql/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = ompgsql.la ompgsql_la_SOURCES = ompgsql.c ompgsql.h -ompgsql_la_CPPFLAGS = -I$(top_srcdir) $(pgsql_cflags) +ompgsql_la_CPPFLAGS = -I$(top_srcdir) $(pgsql_cflags) $(rsrt_cflags) ompgsql_la_LDFLAGS = -module -avoid-version ompgsql_la_LIBADD = $(pgsql_libs) diff --git a/plugins/omrelp/Makefile.am b/plugins/omrelp/Makefile.am index dfc2111f..f8384f42 100644 --- a/plugins/omrelp/Makefile.am +++ b/plugins/omrelp/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omrelp.la omrelp_la_SOURCES = omrelp.c -omrelp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(RELP_CFLAGS) +omrelp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(RELP_CFLAGS) $(rsrt_cflags) omrelp_la_LDFLAGS = -module -avoid-version omrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/omsnmp/Makefile.am b/plugins/omsnmp/Makefile.am index d74f7bb4..d784faca 100644 --- a/plugins/omsnmp/Makefile.am +++ b/plugins/omsnmp/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omsnmp.la omsnmp_la_SOURCES = omsnmp.c omsnmp.h -omsnmp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +omsnmp_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) omsnmp_la_LDFLAGS = -module -avoid-version omsnmp_la_LIBADD = $(snmp_libs) diff --git a/plugins/omtesting/Makefile.am b/plugins/omtesting/Makefile.am index 7e376683..8e98ca63 100644 --- a/plugins/omtesting/Makefile.am +++ b/plugins/omtesting/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omtesting.la omtesting_la_SOURCES = omtesting.c -omtesting_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +omtesting_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) $(rsrt_cflags) omtesting_la_LDFLAGS = -module -avoid-version omtesting_la_LIBADD = -- cgit v1.2.3 From 3af28bbd2dc4c79b242aad3b9e3f45cffe0f19d0 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 08:56:48 +0200 Subject: moved runtime files into their own directory --- Makefile.am | 37 -- configure.ac | 2 +- ctok.c | 591 -------------------------------- ctok.h | 56 --- ctok_token.c | 131 -------- ctok_token.h | 89 ----- expr.c | 418 ----------------------- expr.h | 56 --- regexp.c | 102 ------ regexp.h | 46 --- runtime/Makefile.am | 42 ++- runtime/ctok.c | 591 ++++++++++++++++++++++++++++++++ runtime/ctok.h | 56 +++ runtime/ctok_token.c | 131 ++++++++ runtime/ctok_token.h | 89 +++++ runtime/expr.c | 418 +++++++++++++++++++++++ runtime/expr.h | 56 +++ runtime/regexp.c | 102 ++++++ runtime/regexp.h | 46 +++ runtime/stream.c | 934 +++++++++++++++++++++++++++++++++++++++++++++++++++ runtime/stream.h | 131 ++++++++ runtime/sync.c | 56 +++ runtime/sync.h | 50 +++ runtime/sysvar.c | 200 +++++++++++ runtime/sysvar.h | 47 +++ runtime/var.c | 414 +++++++++++++++++++++++ runtime/var.h | 70 ++++ runtime/vm.c | 528 +++++++++++++++++++++++++++++ runtime/vm.h | 65 ++++ runtime/vmop.c | 235 +++++++++++++ runtime/vmop.h | 92 +++++ runtime/vmprg.c | 175 ++++++++++ runtime/vmprg.h | 66 ++++ runtime/vmstk.c | 234 +++++++++++++ runtime/vmstk.h | 56 +++ runtime/wti.c | 480 ++++++++++++++++++++++++++ runtime/wti.h | 63 ++++ runtime/wtp.c | 624 ++++++++++++++++++++++++++++++++++ runtime/wtp.h | 119 +++++++ stream.c | 934 --------------------------------------------------- stream.h | 131 -------- sync.c | 56 --- sync.h | 50 --- sysvar.c | 200 ----------- sysvar.h | 47 --- var.c | 414 ----------------------- var.h | 70 ---- vm.c | 528 ----------------------------- vm.h | 65 ---- vmop.c | 235 ------------- vmop.h | 92 ----- vmprg.c | 175 ---------- vmprg.h | 66 ---- vmstk.c | 234 ------------- vmstk.h | 56 --- wti.c | 480 -------------------------- wti.h | 63 ---- wtp.c | 624 ---------------------------------- wtp.h | 119 ------- 59 files changed, 6169 insertions(+), 6168 deletions(-) delete mode 100644 ctok.c delete mode 100644 ctok.h delete mode 100644 ctok_token.c delete mode 100644 ctok_token.h delete mode 100644 expr.c delete mode 100644 expr.h delete mode 100644 regexp.c delete mode 100644 regexp.h create mode 100644 runtime/ctok.c create mode 100644 runtime/ctok.h create mode 100644 runtime/ctok_token.c create mode 100644 runtime/ctok_token.h create mode 100644 runtime/expr.c create mode 100644 runtime/expr.h create mode 100644 runtime/regexp.c create mode 100644 runtime/regexp.h create mode 100644 runtime/stream.c create mode 100644 runtime/stream.h create mode 100644 runtime/sync.c create mode 100644 runtime/sync.h create mode 100644 runtime/sysvar.c create mode 100644 runtime/sysvar.h create mode 100644 runtime/var.c create mode 100644 runtime/var.h create mode 100644 runtime/vm.c create mode 100644 runtime/vm.h create mode 100644 runtime/vmop.c create mode 100644 runtime/vmop.h create mode 100644 runtime/vmprg.c create mode 100644 runtime/vmprg.h create mode 100644 runtime/vmstk.c create mode 100644 runtime/vmstk.h create mode 100644 runtime/wti.c create mode 100644 runtime/wti.h create mode 100644 runtime/wtp.c create mode 100644 runtime/wtp.h delete mode 100644 stream.c delete mode 100644 stream.h delete mode 100644 sync.c delete mode 100644 sync.h delete mode 100644 sysvar.c delete mode 100644 sysvar.h delete mode 100644 var.c delete mode 100644 var.h delete mode 100644 vm.c delete mode 100644 vm.h delete mode 100644 vmop.c delete mode 100644 vmop.h delete mode 100644 vmprg.c delete mode 100644 vmprg.h delete mode 100644 vmstk.c delete mode 100644 vmstk.h delete mode 100644 wti.c delete mode 100644 wti.h delete mode 100644 wtp.c delete mode 100644 wtp.h diff --git a/Makefile.am b/Makefile.am index c2685963..a4d1ea5b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,16 +10,6 @@ rsyslogd_SOURCES = \ errmsg.h \ syslogd.c \ syslogd.h \ - sysvar.c \ - sysvar.h \ - vm.c \ - vm.h \ - vmstk.c \ - vmstk.h \ - vmprg.c \ - vmprg.h \ - vmop.c \ - vmop.h \ debug.c \ debug.h \ glbl.h \ @@ -39,27 +29,11 @@ rsyslogd_SOURCES = \ liblogging-stub.h \ threads.c \ threads.h \ - stream.c \ - stream.h \ - var.c \ - var.h \ - wtp.c \ - wtp.h \ - wti.c \ - wti.h \ - sync.c \ - sync.h \ obj.c \ obj.h \ obj-types.h \ msg.c \ msg.h \ - expr.c \ - expr.h \ - ctok.c \ - ctok.h \ - ctok_token.c \ - ctok_token.h \ conf.c \ conf.h \ omshell.c \ @@ -142,17 +116,6 @@ lmtcpclt_la_LIBADD = endif # if ENABLE_INET -# -# regular expression support -# -if ENABLE_REGEXP -pkglib_LTLIBRARIES += lmregexp.la -lmregexp_la_SOURCES = regexp.c regexp.h -lmregexp_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) -lmregexp_la_LDFLAGS = -module -avoid-version $(rsrt_libs) -lmregexp_la_LIBADD = -endif - # # gssapi support # diff --git a/configure.ac b/configure.ac index b0cc3e2e..8ea017ee 100644 --- a/configure.ac +++ b/configure.ac @@ -677,6 +677,6 @@ echo "Debug mode enabled: $enable_debug" echo "Runtime Instrumentation enabled: $enable_rtinst" echo "openssl enabled: $enable_openssl" echo "valgrind support settings enabled: $enable_valgrind" -echo "rsyslogd runtime will be built: $enable_rsyslogrt" +echo "rsyslog runtime will be built: $enable_rsyslogrt" echo "rsyslogd will be built: $enable_rsyslogd" diff --git a/ctok.c b/ctok.c deleted file mode 100644 index 98d5b63b..00000000 --- a/ctok.c +++ /dev/null @@ -1,591 +0,0 @@ -/* 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 . - * - * 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 -#include -#include -#include - -#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/ctok.h b/ctok.h deleted file mode 100644 index 591f0838..00000000 --- a/ctok.h +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 . - * - * 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/ctok_token.c b/ctok_token.c deleted file mode 100644 index 0f340675..00000000 --- a/ctok_token.c +++ /dev/null @@ -1,131 +0,0 @@ -/* 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 . - * - * 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 -#include -#include -#include - -#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/ctok_token.h b/ctok_token.h deleted file mode 100644 index 346d5acd..00000000 --- a/ctok_token.h +++ /dev/null @@ -1,89 +0,0 @@ -/* 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 . - * - * 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/expr.c b/expr.c deleted file mode 100644 index 9c357404..00000000 --- a/expr.c +++ /dev/null @@ -1,418 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/expr.h b/expr.h deleted file mode 100644 index 974b71ec..00000000 --- a/expr.h +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 . - * - * 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/regexp.c b/regexp.c deleted file mode 100644 index 86b3e6c4..00000000 --- a/regexp.c +++ /dev/null @@ -1,102 +0,0 @@ -/* 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 . - * - * 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 -#include -#include - -#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/regexp.h b/regexp.h deleted file mode 100644 index 8f6ac891..00000000 --- a/regexp.h +++ /dev/null @@ -1,46 +0,0 @@ -/* 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 . - * - * 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 - -/* 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/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 . + * + * 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 +#include +#include +#include + +#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 . + * + * 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 . + * + * 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 +#include +#include +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include +#include + +#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 . + * + * 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 + +/* 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include /* required for HP UX */ +#include + +#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 . + * + * 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 +#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 . + * + * 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 + +#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 . + * + * 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 + +/* 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 . + * + * 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 +#include +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include + +#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 . + * + * 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 . + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#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 */ diff --git a/stream.c b/stream.c deleted file mode 100644 index 1be4571a..00000000 --- a/stream.c +++ /dev/null @@ -1,934 +0,0 @@ -//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 . - * - * 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 -#include -#include -#include -#include -#include -#include -#include /* required for HP UX */ -#include - -#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/stream.h b/stream.h deleted file mode 100644 index 371358ab..00000000 --- a/stream.h +++ /dev/null @@ -1,131 +0,0 @@ -/* 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 . - * - * 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 -#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/sync.c b/sync.c deleted file mode 100644 index a3053e28..00000000 --- a/sync.c +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 . - * - * 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 - -#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/sync.h b/sync.h deleted file mode 100644 index 57144fee..00000000 --- a/sync.h +++ /dev/null @@ -1,50 +0,0 @@ -/* 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 . - * - * 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 - -/* 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/sysvar.c b/sysvar.c deleted file mode 100644 index 14e32b96..00000000 --- a/sysvar.c +++ /dev/null @@ -1,200 +0,0 @@ -/* 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 . - * - * 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 -#include -#include - -#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/sysvar.h b/sysvar.h deleted file mode 100644 index 35051b64..00000000 --- a/sysvar.h +++ /dev/null @@ -1,47 +0,0 @@ -/* 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 . - * - * 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/var.c b/var.c deleted file mode 100644 index 7e51fc6d..00000000 --- a/var.c +++ /dev/null @@ -1,414 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/var.h b/var.h deleted file mode 100644 index bbe7ba33..00000000 --- a/var.h +++ /dev/null @@ -1,70 +0,0 @@ -/* 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 . - * - * 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/vm.c b/vm.c deleted file mode 100644 index bcd331ec..00000000 --- a/vm.c +++ /dev/null @@ -1,528 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/vm.h b/vm.h deleted file mode 100644 index d2458220..00000000 --- a/vm.h +++ /dev/null @@ -1,65 +0,0 @@ -/* 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 . - * - * 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/vmop.c b/vmop.c deleted file mode 100644 index 219315c4..00000000 --- a/vmop.c +++ /dev/null @@ -1,235 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/vmop.h b/vmop.h deleted file mode 100644 index 97f924d7..00000000 --- a/vmop.h +++ /dev/null @@ -1,92 +0,0 @@ -/* 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 . - * - * 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/vmprg.c b/vmprg.c deleted file mode 100644 index a2b744d7..00000000 --- a/vmprg.c +++ /dev/null @@ -1,175 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/vmprg.h b/vmprg.h deleted file mode 100644 index db1f62f0..00000000 --- a/vmprg.h +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 . - * - * 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/vmstk.c b/vmstk.c deleted file mode 100644 index 1ee3d485..00000000 --- a/vmstk.c +++ /dev/null @@ -1,234 +0,0 @@ -/* 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 . - * - * 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 -#include - -#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/vmstk.h b/vmstk.h deleted file mode 100644 index 2d45ee4d..00000000 --- a/vmstk.h +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 . - * - * 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/wti.c b/wti.c deleted file mode 100644 index 82cd2165..00000000 --- a/wti.c +++ /dev/null @@ -1,480 +0,0 @@ -/* 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 . - * - * 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 -#include -#include -#include -#include -#include -#include - -#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/wti.h b/wti.h deleted file mode 100644 index b3d92473..00000000 --- a/wti.h +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 . - * - * 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 -#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/wtp.c b/wtp.c deleted file mode 100644 index fcc7589c..00000000 --- a/wtp.c +++ /dev/null @@ -1,624 +0,0 @@ -/* 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 . - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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/wtp.h b/wtp.h deleted file mode 100644 index 13ebe536..00000000 --- a/wtp.h +++ /dev/null @@ -1,119 +0,0 @@ -/* 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 . - * - * 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 -#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 */ -- cgit v1.2.3 From 8f8f65abb66d1a7839c30c2d1b4b4d653a8990cc Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 10:26:54 +0200 Subject: moved files to the runtime there are still some files left which could go into the runtime, but I think we will delete most of them once we are done with the full modularization. --- Makefile.am | 63 +- atomic.h | 50 - conf.c | 1 + datetime.c | 629 ------------ datetime.h | 51 - debug.c | 1331 ------------------------- debug.h | 145 --- errmsg.c | 120 --- errmsg.h | 45 - glbl.h | 15 +- liblogging-stub.h | 26 - linkedlist.c | 413 -------- linkedlist.h | 72 -- module-template.h | 481 --------- modules.c | 802 --------------- modules.h | 150 --- msg.c | 2293 ------------------------------------------ msg.h | 177 ---- obj-types.h | 405 -------- obj.c | 1342 ------------------------- obj.h | 124 --- objomsr.c | 145 --- objomsr.h | 45 - parse.c | 2 +- plugins/ommysql/Makefile.am | 2 +- rsyslog.h | 270 ----- runtime/Makefile.am | 32 +- runtime/atomic.h | 51 + runtime/datetime.c | 630 ++++++++++++ runtime/datetime.h | 52 + runtime/debug.c | 1332 +++++++++++++++++++++++++ runtime/debug.h | 146 +++ runtime/errmsg.c | 122 +++ runtime/errmsg.h | 46 + runtime/linkedlist.c | 414 ++++++++ runtime/linkedlist.h | 73 ++ runtime/module-template.h | 482 +++++++++ runtime/modules.c | 803 +++++++++++++++ runtime/modules.h | 150 +++ runtime/msg.c | 2294 +++++++++++++++++++++++++++++++++++++++++++ runtime/msg.h | 178 ++++ runtime/obj-types.h | 406 ++++++++ runtime/obj.c | 1336 +++++++++++++++++++++++++ runtime/obj.h | 125 +++ runtime/objomsr.c | 145 +++ runtime/objomsr.h | 46 + runtime/rsyslog.h | 272 +++++ runtime/srUtils.h | 126 +++ runtime/srutils.c | 509 ++++++++++ runtime/stringbuf.c | 1080 ++++++++++++++++++++ runtime/stringbuf.h | 169 ++++ runtime/syslogd-types.h | 103 ++ srUtils.c | 506 ---------- srUtils.h | 125 --- stringbuf.c | 1079 -------------------- stringbuf.h | 168 ---- syslogd-types.h | 104 -- threads.h | 5 +- 58 files changed, 11152 insertions(+), 11156 deletions(-) delete mode 100644 atomic.h delete mode 100644 datetime.c delete mode 100644 datetime.h delete mode 100644 debug.c delete mode 100644 debug.h delete mode 100644 errmsg.c delete mode 100644 errmsg.h delete mode 100644 liblogging-stub.h delete mode 100644 linkedlist.c delete mode 100644 linkedlist.h delete mode 100644 module-template.h delete mode 100644 modules.c delete mode 100644 modules.h delete mode 100644 msg.c delete mode 100644 msg.h delete mode 100644 obj-types.h delete mode 100644 obj.c delete mode 100644 obj.h delete mode 100644 objomsr.c delete mode 100644 objomsr.h delete mode 100644 rsyslog.h create mode 100644 runtime/atomic.h create mode 100644 runtime/datetime.c create mode 100644 runtime/datetime.h create mode 100644 runtime/debug.c create mode 100644 runtime/debug.h create mode 100644 runtime/errmsg.c create mode 100644 runtime/errmsg.h create mode 100644 runtime/linkedlist.c create mode 100644 runtime/linkedlist.h create mode 100644 runtime/module-template.h create mode 100644 runtime/modules.c create mode 100644 runtime/modules.h create mode 100644 runtime/msg.c create mode 100644 runtime/msg.h create mode 100644 runtime/obj-types.h create mode 100644 runtime/obj.c create mode 100644 runtime/obj.h create mode 100644 runtime/objomsr.c create mode 100644 runtime/objomsr.h create mode 100644 runtime/rsyslog.h create mode 100644 runtime/srUtils.h create mode 100644 runtime/srutils.c create mode 100644 runtime/stringbuf.c create mode 100644 runtime/stringbuf.h create mode 100644 runtime/syslogd-types.h delete mode 100644 srUtils.c delete mode 100644 srUtils.h delete mode 100644 stringbuf.c delete mode 100644 stringbuf.h delete mode 100644 syslogd-types.h diff --git a/Makefile.am b/Makefile.am index a4d1ea5b..5829ff86 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,66 +4,43 @@ man_MANS = if ENABLE_RSYSLOGD sbin_PROGRAMS += rsyslogd rsyslogd_SOURCES = \ - datetime.c \ - datetime.h \ - errmsg.c \ - errmsg.h \ syslogd.c \ syslogd.h \ - debug.c \ - debug.h \ - glbl.h \ - pidfile.c \ - pidfile.h \ - template.c \ - outchannel.c \ - stringbuf.c \ - stringbuf.h \ - srUtils.c \ - srUtils.h \ - parse.c \ - parse.h \ - syslogd-types.h \ - template.h \ - outchannel.h \ - liblogging-stub.h \ - threads.c \ - threads.h \ - obj.c \ - obj.h \ - obj-types.h \ - msg.c \ - msg.h \ - conf.c \ - conf.h \ omshell.c \ omshell.h \ omusrmsg.c \ omusrmsg.h \ omfwd.c \ omfwd.h \ - tcpsyslog.c \ - tcpsyslog.h \ omfile.c \ omfile.h \ omdiscard.c \ omdiscard.h \ - modules.c \ - modules.h \ - module-template.h \ - objomsr.c \ - objomsr.h \ - cfsysline.c \ - cfsysline.h \ - linkedlist.c \ - linkedlist.h \ iminternal.c \ iminternal.h \ + pidfile.c \ + pidfile.h \ + \ action.c \ action.h \ - atomic.h + threads.c \ + threads.h \ + \ + parse.c \ + parse.h \ + \ + outchannel.c \ + outchannel.h \ + template.c \ + template.h \ + conf.c \ + conf.h \ + tcpsyslog.c \ + tcpsyslog.h \ + cfsysline.c \ + cfsysline.h -rsyslogd_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" $(pthreads_cflags) $(rsrt_cflags) +rsyslogd_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) $(rsrt_libs) rsyslogd_LDFLAGS = -export-dynamic diff --git a/atomic.h b/atomic.h deleted file mode 100644 index 2421c826..00000000 --- a/atomic.h +++ /dev/null @@ -1,50 +0,0 @@ -/* This header supplies atomic operations. So far, we rely on GCC's - * atomic builtins. I have no idea if we can check them via autotools, - * but I am making the necessary provisioning to live without them if - * they are not available. Please note that you should only use the macros - * here if you think you can actually live WITHOUT an explicit atomic operation, - * because in the non-presence of them, we simply do it without atomicitiy. - * Which, for word-aligned data types, usually (but only usually!) should work. - * - * We are using the functions described in - * http:/gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html - * - * THESE MACROS MUST ONLY BE USED WITH WORD-SIZED DATA TYPES! - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" /* autotools! */ - -#ifndef INCLUDED_ATOMIC_H -#define INCLUDED_ATOMIC_H - -/* set the following to 1 if we have atomic operations (and #undef it otherwise) */ -/* #define DO_HAVE_ATOMICS 1 */ -/* for this release, we disable atomic calls because there seem to be some - * portability problems and we can not fix that without destabilizing the build. - * They simply came in too late. -- rgerhards, 2008-04-02 - */ -/* make sure they are not used! -#define ATOMIC_INC(data) ((void) __sync_fetch_and_add(&data, 1)) -#define ATOMIC_DEC_AND_FETCH(data) __sync_sub_and_fetch(&data, 1) -*/ -#define ATOMIC_INC(data) (++(data)) - -#endif /* #ifndef INCLUDED_ATOMIC_H */ diff --git a/conf.c b/conf.c index dac46970..098448c1 100644 --- a/conf.c +++ b/conf.c @@ -563,6 +563,7 @@ cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int * passed back to the caller. * rgerhards 2005-09-15 */ +/* GPLv3 - stems back to sysklogd */ static rsRetVal cflineProcessTradPRIFilter(uchar **pline, register selector_t *f) { uchar *p; diff --git a/datetime.c b/datetime.c deleted file mode 100644 index a4817a6d..00000000 --- a/datetime.c +++ /dev/null @@ -1,629 +0,0 @@ -/* The datetime object. It contains date and time related functions. - * - * Module begun 2008-03-05 by Rainer Gerhards, based on some code - * from syslogd.c. The main intension was to move code out of syslogd.c - * in a useful manner. It is still undecided if all functions will continue - * to stay here or some will be moved into parser modules (once we have them). - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#include "config.h" -#include -#include -#include -#include -#include -#ifdef HAVE_SYS_TIME_H -# include -#endif - -#include "rsyslog.h" -#include "obj.h" -#include "modules.h" -#include "datetime.h" -#include "sysvar.h" -#include "srUtils.h" -#include "stringbuf.h" -#include "errmsg.h" - -/* static data */ -DEFobjStaticHelpers -DEFobjCurrIf(errmsg) - - -/* ------------------------------ methods ------------------------------ */ - - -/** - * Get the current date/time in the best resolution the operating - * system has to offer (well, actually at most down to the milli- - * second level. - * - * The date and time is returned in separate fields as this is - * most portable and removes the need for additional structures - * (but I have to admit it is somewhat "bulky";)). - * - * Obviously, all caller-provided pointers must not be NULL... - */ -static void getCurrTime(struct syslogTime *t) -{ - struct timeval tp; - struct tm *tm; - struct tm tmBuf; - long lBias; -# if defined(__hpux) - struct timezone tz; -# endif - - assert(t != NULL); -# if defined(__hpux) - /* TODO: check this: under HP UX, the tz information is actually valid - * data. So we need to obtain and process it there. - */ - gettimeofday(&tp, &tz); -# else - gettimeofday(&tp, NULL); -# endif - tm = localtime_r((time_t*) &(tp.tv_sec), &tmBuf); - - t->year = tm->tm_year + 1900; - t->month = tm->tm_mon + 1; - t->day = tm->tm_mday; - t->hour = tm->tm_hour; - t->minute = tm->tm_min; - t->second = tm->tm_sec; - t->secfrac = tp.tv_usec; - t->secfracPrecision = 6; - -# if __sun - /* Solaris uses a different method of exporting the time zone. - * It is UTC - localtime, which is the opposite sign of mins east of GMT. - */ - lBias = -(daylight ? altzone : timezone); -# elif defined(__hpux) - lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0; -# else - lBias = tm->tm_gmtoff; -# endif - if(lBias < 0) - { - t->OffsetMode = '-'; - lBias *= -1; - } - else - t->OffsetMode = '+'; - t->OffsetHour = lBias / 3600; - t->OffsetMinute = lBias % 3600; -} - - - - -/******************************************************************* - * BEGIN CODE-LIBLOGGING * - ******************************************************************* - * Code in this section is borrowed from liblogging. This is an - * interim solution. Once liblogging is fully integrated, this is - * to be removed (see http://www.monitorware.com/liblogging for - * more details. 2004-11-16 rgerhards - * - * Please note that the orginal liblogging code is modified so that - * it fits into the context of the current version of syslogd.c. - * - * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!! - */ - -/** - * Parse a 32 bit integer number from a string. - * - * \param ppsz Pointer to the Pointer to the string being parsed. It - * must be positioned at the first digit. Will be updated - * so that on return it points to the first character AFTER - * the integer parsed. - * \retval The number parsed. - */ - -static int srSLMGParseInt32(char** ppsz) -{ - int i; - - i = 0; - while(isdigit((int) **ppsz)) - { - i = i * 10 + **ppsz - '0'; - ++(*ppsz); - } - - return i; -} - - -/** - * Parse a TIMESTAMP-3339. - * updates the parse pointer position. - */ -static int -ParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) -{ - char *pszTS = *ppszTS; - - assert(pTime != NULL); - assert(ppszTS != NULL); - assert(pszTS != NULL); - - pTime->year = srSLMGParseInt32(&pszTS); - - /* We take the liberty to accept slightly malformed timestamps e.g. in - * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course, - * with the current state of affairs, we would never run into this code - * here because at postion 11, there is no "T" in such cases ;) - */ - if(*pszTS++ != '-') - return FALSE; - pTime->month = srSLMGParseInt32(&pszTS); - if(pTime->month < 1 || pTime->month > 12) - return FALSE; - - if(*pszTS++ != '-') - return FALSE; - pTime->day = srSLMGParseInt32(&pszTS); - if(pTime->day < 1 || pTime->day > 31) - return FALSE; - - if(*pszTS++ != 'T') - return FALSE; - - pTime->hour = srSLMGParseInt32(&pszTS); - if(pTime->hour < 0 || pTime->hour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->minute = srSLMGParseInt32(&pszTS); - if(pTime->minute < 0 || pTime->minute > 59) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->second = srSLMGParseInt32(&pszTS); - if(pTime->second < 0 || pTime->second > 60) - return FALSE; - - /* Now let's see if we have secfrac */ - if(*pszTS == '.') - { - char *pszStart = ++pszTS; - pTime->secfrac = srSLMGParseInt32(&pszTS); - pTime->secfracPrecision = (int) (pszTS - pszStart); - } - else - { - pTime->secfracPrecision = 0; - pTime->secfrac = 0; - } - - /* check the timezone */ - if(*pszTS == 'Z') - { - pszTS++; /* eat Z */ - pTime->OffsetMode = 'Z'; - pTime->OffsetHour = 0; - pTime->OffsetMinute = 0; - } - else if((*pszTS == '+') || (*pszTS == '-')) - { - pTime->OffsetMode = *pszTS; - pszTS++; - - pTime->OffsetHour = srSLMGParseInt32(&pszTS); - if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->OffsetMinute = srSLMGParseInt32(&pszTS); - if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) - return FALSE; - } - else - /* there MUST be TZ information */ - return FALSE; - - /* OK, we actually have a 3339 timestamp, so let's indicated this */ - if(*pszTS == ' ') - ++pszTS; - else - return FALSE; - - /* update parse pointer */ - *ppszTS = pszTS; - - return TRUE; -} - - -/** - * Parse a TIMESTAMP-3164. - * Returns TRUE on parse OK, FALSE on parse error. - */ -static int -ParseTIMESTAMP3164(struct syslogTime *pTime, char* pszTS) -{ - assert(pTime != NULL); - assert(pszTS != NULL); - - getCurrTime(pTime); /* obtain the current year and UTC offsets! */ - - /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), - * we may see the following character sequences occur: - * - * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec - * - * We will use this for parsing, as it probably is the - * fastest way to parse it. - * - * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C... - * Fixed a bug that lead to invalid detection of the data. The issue was that - * we had an if(++pszTS == 'x') inside of some of the consturcts below. However, - * there were also some elseifs (doing the same ++), which than obviously did not - * check the orginal character but the next one. Now removed the ++ and put it - * into the statements below. Was a really nasty bug... I didn't detect it before - * june, when it first manifested. This also lead to invalid parsing of the rest - * of the message, as the time stamp was not detected to be correct. - rgerhards - */ - switch(*pszTS++) - { - case 'J': - if(*pszTS == 'a') { - ++pszTS; - if(*pszTS == 'n') { - ++pszTS; - pTime->month = 1; - } else - return FALSE; - } else if(*pszTS == 'u') { - ++pszTS; - if(*pszTS == 'n') { - ++pszTS; - pTime->month = 6; - } else if(*pszTS == 'l') { - ++pszTS; - pTime->month = 7; - } else - return FALSE; - } else - return FALSE; - break; - case 'F': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'b') { - ++pszTS; - pTime->month = 2; - } else - return FALSE; - } else - return FALSE; - break; - case 'M': - if(*pszTS == 'a') { - ++pszTS; - if(*pszTS == 'r') { - ++pszTS; - pTime->month = 3; - } else if(*pszTS == 'y') { - ++pszTS; - pTime->month = 5; - } else - return FALSE; - } else - return FALSE; - break; - case 'A': - if(*pszTS == 'p') { - ++pszTS; - if(*pszTS == 'r') { - ++pszTS; - pTime->month = 4; - } else - return FALSE; - } else if(*pszTS == 'u') { - ++pszTS; - if(*pszTS == 'g') { - ++pszTS; - pTime->month = 8; - } else - return FALSE; - } else - return FALSE; - break; - case 'S': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'p') { - ++pszTS; - pTime->month = 9; - } else - return FALSE; - } else - return FALSE; - break; - case 'O': - if(*pszTS == 'c') { - ++pszTS; - if(*pszTS == 't') { - ++pszTS; - pTime->month = 10; - } else - return FALSE; - } else - return FALSE; - break; - case 'N': - if(*pszTS == 'o') { - ++pszTS; - if(*pszTS == 'v') { - ++pszTS; - pTime->month = 11; - } else - return FALSE; - } else - return FALSE; - break; - case 'D': - if(*pszTS == 'e') { - ++pszTS; - if(*pszTS == 'c') { - ++pszTS; - pTime->month = 12; - } else - return FALSE; - } else - return FALSE; - break; - default: - return FALSE; - } - - /* done month */ - - if(*pszTS++ != ' ') - return FALSE; - - /* we accept a slightly malformed timestamp when receiving. This is - * we accept one-digit days - */ - if(*pszTS == ' ') - ++pszTS; - - pTime->day = srSLMGParseInt32(&pszTS); - if(pTime->day < 1 || pTime->day > 31) - return FALSE; - - if(*pszTS++ != ' ') - return FALSE; - pTime->hour = srSLMGParseInt32(&pszTS); - if(pTime->hour < 0 || pTime->hour > 23) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->minute = srSLMGParseInt32(&pszTS); - if(pTime->minute < 0 || pTime->minute > 59) - return FALSE; - - if(*pszTS++ != ':') - return FALSE; - pTime->second = srSLMGParseInt32(&pszTS); - if(pTime->second < 0 || pTime->second > 60) - return FALSE; - if(*pszTS++ != ':') - - /* OK, we actually have a 3164 timestamp, so let's indicate this - * and fill the rest of the properties. */ - pTime->timeType = 1; - pTime->secfracPrecision = 0; - pTime->secfrac = 0; - return TRUE; -} - -/******************************************************************* - * END CODE-LIBLOGGING * - *******************************************************************/ - -/** - * Format a syslogTimestamp into format required by MySQL. - * We are using the 14 digits format. For example 20041111122600 - * is interpreted as '2004-11-11 12:26:00'. - * The caller must provide the timestamp as well as a character - * buffer that will receive the resulting string. The function - * returns the size of the timestamp written in bytes (without - * the string terminator). If 0 is returend, an error occured. - */ -int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst) -{ - /* currently we do not consider localtime/utc. This may later be - * added. If so, I recommend using a property replacer option - * and/or a global configuration option. However, we should wait - * on user requests for this feature before doing anything. - * rgerhards, 2007-06-26 - */ - assert(ts != NULL); - assert(pDst != NULL); - - if (iLenDst < 15) /* we need at least 14 bytes - 14 digits for timestamp + '\n' */ - return(0); - - return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", - ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); - -} - -int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) -{ - /* see note in formatTimestampToMySQL, applies here as well */ - assert(ts != NULL); - assert(pDst != NULL); - - if (iLenDst < 21) /* we need 20 bytes + '\n' */ - return(0); - - return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", - ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); -} - -/** - * Format a syslogTimestamp to a RFC3339 timestamp string (as - * specified in syslog-protocol). - * The caller must provide the timestamp as well as a character - * buffer that will receive the resulting string. The function - * returns the size of the timestamp written in bytes (without - * the string terminator). If 0 is returend, an error occured. - */ -int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf) -{ - int iRet; - char szTZ[7]; /* buffer for TZ information */ - - assert(ts != NULL); - assert(pBuf != NULL); - - if(iLenBuf < 20) - return(0); /* we NEED at least 20 bytes */ - - /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ - if(ts->OffsetMode == 'Z') { - szTZ[0] = 'Z'; - szTZ[1] = '\0'; - } else { - snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", - ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); - } - - if(ts->secfracPrecision > 0) - { /* we now need to include fractional seconds. While doing so, we must look at - * the precision specified. For example, if we have millisec precision (3 digits), a - * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this - * is a huge difference ;). To avoid this, we first create a format string with - * the specific precision and *then* use that format string to do the actual - * formating (mmmmhhh... kind of self-modifying code... ;)). - */ - char szFmtStr[64]; - /* be careful: there is ONE actual %d in the format string below ;) */ - snprintf(szFmtStr, sizeof(szFmtStr), - "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", - ts->secfracPrecision); - iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, - ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); - } - else - iRet = snprintf(pBuf, iLenBuf, - "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", - ts->year, ts->month, ts->day, - ts->hour, ts->minute, ts->second, szTZ); - return(iRet); -} - -/** - * Format a syslogTimestamp to a RFC3164 timestamp sring. - * The caller must provide the timestamp as well as a character - * buffer that will receive the resulting string. The function - * returns the size of the timestamp written in bytes (without - * the string termnator). If 0 is returend, an error occured. - */ -int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) -{ - static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", - "Apr", "May", "Jun", "Jul", - "Aug", "Sep", "Oct", "Nov", "Dec"}; - assert(ts != NULL); - assert(pBuf != NULL); - - if(iLenBuf < 16) - return(0); /* we NEED 16 bytes */ - return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", - monthNames[ts->month], ts->day, ts->hour, - ts->minute, ts->second - )); -} - -/** - * Format a syslogTimestamp to a text format. - * The caller must provide the timestamp as well as a character - * buffer that will receive the resulting string. The function - * returns the size of the timestamp written in bytes (without - * the string termnator). If 0 is returend, an error occured. - */ -#if 0 /* This method is currently not called, be we like to preserve it */ -static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) -{ - assert(ts != NULL); - assert(pBuf != NULL); - - if(ts->timeType == 1) { - return(formatTimestamp3164(ts, pBuf, iLenBuf)); - } - - if(ts->timeType == 2) { - return(formatTimestamp3339(ts, pBuf, iLenBuf)); - } - - return(0); -} -#endif -/* queryInterface function - * rgerhards, 2008-03-05 - */ -BEGINobjQueryInterface(datetime) -CODESTARTobjQueryInterface(datetime) - if(pIf->ifVersion != datetimeCURR_IF_VERSION) { /* check for current version, increment on each change */ - ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); - } - - /* ok, we have the right interface, so let's fill it - * Please note that we may also do some backwards-compatibility - * work here (if we can support an older interface version - that, - * of course, also affects the "if" above). - */ - pIf->getCurrTime = getCurrTime; - pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339; - pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164; - pIf->formatTimestampToMySQL = formatTimestampToMySQL; - pIf->formatTimestampToPgSQL = formatTimestampToPgSQL; - pIf->formatTimestamp3339 = formatTimestamp3339; - pIf->formatTimestamp3164 = formatTimestamp3164; -finalize_it: -ENDobjQueryInterface(datetime) - - -/* Initialize the datetime class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-02-19 - */ -BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */ - /* request objects we use */ - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - -ENDObjClassInit(datetime) - -/* vi:set ai: - */ diff --git a/datetime.h b/datetime.h deleted file mode 100644 index a35dfe8a..00000000 --- a/datetime.h +++ /dev/null @@ -1,51 +0,0 @@ -/* The datetime object. Contains time-related functions. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef INCLUDED_DATETIME_H -#define INCLUDED_DATETIME_H - -#include "datetime.h" - -/* TODO: define error codes */ -#define NO_ERRCODE -1 - -/* the datetime object */ -typedef struct datetime_s { -} datetime_t; - - -/* interfaces */ -BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */ - void (*getCurrTime)(struct syslogTime *t); - //static int srSLMGParseInt32(char** ppsz); - int (*ParseTIMESTAMP3339)(struct syslogTime *pTime, char** ppszTS); - int (*ParseTIMESTAMP3164)(struct syslogTime *pTime, char* pszTS); - int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst, size_t iLenDst); - int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst, size_t iLenDst); - int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); - int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); -ENDinterface(datetime) -#define datetimeCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ - -/* prototypes */ -PROTOTYPEObj(datetime); - -#endif /* #ifndef INCLUDED_DATETIME_H */ diff --git a/debug.c b/debug.c deleted file mode 100644 index 29c65cf1..00000000 --- a/debug.c +++ /dev/null @@ -1,1331 +0,0 @@ -/* debug.c - * - * This file proides debug and run time error analysis support. Some of the - * settings are very performance intense and my be turned off during a release - * build. - * - * File begun on 2008-01-22 by RGerhards - * - * Some functions are controlled by environment variables: - * - * RSYSLOG_DEBUGLOG if set, a debug log file is written to that location - * RSYSLOG_DEBUG specific debug options - * - * For details, visit doc/debug.html - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" /* autotools! */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rsyslog.h" -#include "debug.h" -#include "atomic.h" -#include "obj.h" - - -/* static data (some time to be replaced) */ -DEFobjCurrIf(obj) -int Debug; /* debug flag - read-only after startup */ -int debugging_on = 0; /* read-only, except on sig USR1 */ -static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */ -static int bLogAllocFree = 0; /* shall calls to (m/c)alloc and free be logged to the debug log? */ -static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logged to the debug log? */ -static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */ -static int bPrintTime = 1; /* print a timestamp together with debug message */ -static int bPrintAllDebugOnExit = 0; -static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */ -static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ -static FILE *altdbg = NULL; /* and the handle for alternate debug output */ -static FILE *stddbg; - -/* list of files/objects that should be printed */ -typedef struct dbgPrintName_s { - uchar *pName; - struct dbgPrintName_s *pNext; -} dbgPrintName_t; - - -/* forward definitions */ -static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID); -static dbgThrdInfo_t *dbgGetThrdInfo(void); -static int dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot); - - -/* This lists are single-linked and members are added at the top */ -static dbgPrintName_t *printNameFileRoot = NULL; - - -/* list of all known FuncDBs. We use a special list, because it must only be single-linked. As - * functions never disappear, we only need to add elements when we see a new one and never need - * to remove anything. For this, we simply add at the top, which saves us a Last pointer. The goal - * is to use as few memory as possible. - */ -typedef struct dbgFuncDBListEntry_s { - dbgFuncDB_t *pFuncDB; - struct dbgFuncDBListEntry_s *pNext; -} dbgFuncDBListEntry_t; -dbgFuncDBListEntry_t *pFuncDBListRoot; - -static pthread_mutex_t mutFuncDBList; - -typedef struct dbgMutLog_s { - struct dbgMutLog_s *pNext; - struct dbgMutLog_s *pPrev; - pthread_mutex_t *mut; - pthread_t thrd; - dbgFuncDB_t *pFuncDB; - int lockLn; /* the actual line where the mutex was locked */ - short mutexOp; -} dbgMutLog_t; -static dbgMutLog_t *dbgMutLogListRoot = NULL; -static dbgMutLog_t *dbgMutLogListLast = NULL; -static pthread_mutex_t mutMutLog; - - -static dbgThrdInfo_t *dbgCallStackListRoot = NULL; -static dbgThrdInfo_t *dbgCallStackListLast = NULL; -static pthread_mutex_t mutCallStack; - -static pthread_mutex_t mutdbgprintf; -static pthread_mutex_t mutdbgoprint; - -static pthread_key_t keyCallStack; - - -/* we do not have templates, so we use some macros to create linked list handlers - * for the several types - * DLL means "doubly linked list" - * rgerhards, 2008-01-23 - */ -#define DLL_Del(type, pThis) \ - if(pThis->pPrev != NULL) \ - pThis->pPrev->pNext = pThis->pNext; \ - if(pThis->pNext != NULL) \ - pThis->pNext->pPrev = pThis->pPrev; \ - if(pThis == dbg##type##ListRoot) \ - dbg##type##ListRoot = pThis->pNext; \ - if(pThis == dbg##type##ListLast) \ - dbg##type##ListLast = pThis->pPrev; \ - free(pThis); - -#define DLL_Add(type, pThis) \ - if(dbg##type##ListRoot == NULL) { \ - dbg##type##ListRoot = pThis; \ - dbg##type##ListLast = pThis; \ - } else { \ - pThis->pPrev = dbg##type##ListLast; \ - dbg##type##ListLast->pNext = pThis; \ - dbg##type##ListLast = pThis; \ - } - -/* we need to do our own mutex cancel cleanup handler as it shall not - * be subject to the debugging instrumentation (that would probably run us - * into an infinite loop - */ -static void dbgMutexCancelCleanupHdlr(void *pmut) -{ - pthread_mutex_unlock((pthread_mutex_t*) pmut); -} - - -/* handler to update the last execution location seen - * rgerhards, 2008-01-28 - */ -static inline void -dbgRecordExecLocation(int iStackPtr, int line) -{ - dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); - pThrd->lastLine[iStackPtr] = line; -} - - -/* ------------------------- mutex tracking code ------------------------- */ - -/* ------------------------- FuncDB utility functions ------------------------- */ - -#define SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ((int) (sizeof(pFuncDB->mutInfo) / sizeof(dbgFuncDBmutInfoEntry_t))) - -/* print a FuncDB - */ -static void dbgFuncDBPrint(dbgFuncDB_t *pFuncDB) -{ - assert(pFuncDB != NULL); - assert(pFuncDB->magic == dbgFUNCDB_MAGIC); - /* make output suitable for sorting on invocation count */ - dbgprintf("%10.10ld times called: %s:%d:%s\n", pFuncDB->nTimesCalled, pFuncDB->file, pFuncDB->line, pFuncDB->func); -} - - -/* print all funcdb entries - */ -static void dbgFuncDBPrintAll(void) -{ - dbgFuncDBListEntry_t *pFuncDBList; - int nFuncs = 0; - - for(pFuncDBList = pFuncDBListRoot ; pFuncDBList != NULL ; pFuncDBList = pFuncDBList->pNext) { - dbgFuncDBPrint(pFuncDBList->pFuncDB); - nFuncs++; - } - - dbgprintf("%d unique functions called\n", nFuncs); -} - - -/* find a mutex inside the FuncDB mutex table. Returns NULL if not found. Only mutexes from the same thread - * are found. - */ -static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBGetMutexInfo(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) -{ - int i; - int iFound = -1; - pthread_t ourThrd = pthread_self(); - - for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { - if(pFuncDB->mutInfo[i].pmut == pmut && pFuncDB->mutInfo[i].lockLn != -1 && pFuncDB->mutInfo[i].thrd == ourThrd) { - iFound = i; - break; - } - } - - return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; -} - - -/* print any mutex that can be found in the FuncDB. Custom header is provided. - * "thrd" is the thread that is searched. If it is 0, mutexes for all threads - * shall be printed. - */ -static inline void -dbgFuncDBPrintActiveMutexes(dbgFuncDB_t *pFuncDB, char *pszHdrText, pthread_t thrd) -{ - int i; - char pszThrdName[64]; - - for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { - if(pFuncDB->mutInfo[i].lockLn != -1 && (thrd == 0 || thrd == pFuncDB->mutInfo[i].thrd)) { - dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pFuncDB->mutInfo[i].thrd, 1); - dbgprintf("%s:%d:%s:invocation %ld: %s %p[%d/%s]\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, - pFuncDB->mutInfo[i].lInvocation, pszHdrText, (void*)pFuncDB->mutInfo[i].pmut, i, - pszThrdName); - } - } -} - -/* find a free mutex info spot in FuncDB. NULL is returned if table is full. - */ -static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBFindFreeMutexInfo(dbgFuncDB_t *pFuncDB) -{ - int i; - int iFound = -1; - - for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { - if(pFuncDB->mutInfo[i].lockLn == -1) { - iFound = i; - break; - } - } - - if(iFound == -1) { - dbgprintf("%s:%d:%s: INFO: out of space in FuncDB for mutex info (max %d entries) - ignoring\n", - pFuncDB->file, pFuncDB->line, pFuncDB->func, SIZE_FUNCDB_MUTEX_TABLE(pFuncDB)); - } - - return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; -} - -/* add a mutex lock to the FuncDB. If the size is exhausted, info is discarded. - */ -static inline void dbgFuncDBAddMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut, int lockLn) -{ - dbgFuncDBmutInfoEntry_t *pMutInfo; - - if((pMutInfo = dbgFuncDBFindFreeMutexInfo(pFuncDB)) != NULL) { - pMutInfo->pmut = pmut; - pMutInfo->lockLn = lockLn; - pMutInfo->lInvocation = pFuncDB->nTimesCalled; - pMutInfo->thrd = pthread_self(); - } -} - -/* remove a locked mutex from the FuncDB (unlock case!). - */ -static inline void dbgFuncDBRemoveMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) -{ - dbgFuncDBmutInfoEntry_t *pMutInfo; - - if((pMutInfo = dbgFuncDBGetMutexInfo(pFuncDB, pmut)) != NULL) { - pMutInfo->lockLn = -1; - } -} - - -/* ------------------------- END FuncDB utility functions ------------------------- */ - -/* ########################################################################### - * IMPORTANT NOTE - * Mutex instrumentation reduces the code's concurrency and thus affects its - * order of execution. It is vital to test the code also with mutex - * instrumentation turned off! Some bugs may not show up while it on... - * ########################################################################### - */ - -/* constructor & add new entry to list - */ -dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, dbgFuncDB_t *pFuncDB, int lockLn) -{ - dbgMutLog_t *pLog; - - pLog = calloc(1, sizeof(dbgMutLog_t)); - assert(pLog != NULL); - - /* fill data members */ - pLog->mut = pmut; - pLog->thrd = pthread_self(); - pLog->mutexOp = mutexOp; - pLog->lockLn = lockLn; - pLog->pFuncDB = pFuncDB; - - DLL_Add(MutLog, pLog); - - return pLog; -} - - -/* destruct log entry - */ -void dbgMutLogDelEntry(dbgMutLog_t *pLog) -{ - assert(pLog != NULL); - DLL_Del(MutLog, pLog); -} - - -/* print a single mutex log entry */ -static void dbgMutLogPrintOne(dbgMutLog_t *pLog) -{ - char *strmutop; - char buf[64]; - char pszThrdName[64]; - - assert(pLog != NULL); - switch(pLog->mutexOp) { - case MUTOP_LOCKWAIT: - strmutop = "waited on"; - break; - case MUTOP_LOCK: - strmutop = "owned"; - break; - default: - snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp); - strmutop = buf; - break; - } - - dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pLog->thrd, 1); - dbgprintf("mutex 0x%lx is being %s by code at %s:%d, thread %s\n", (unsigned long) pLog->mut, - strmutop, pLog->pFuncDB->file, - (pLog->mutexOp == MUTOP_LOCK) ? pLog->lockLn : pLog->pFuncDB->line, - pszThrdName); -} - -/* print the complete mutex log */ -static void dbgMutLogPrintAll(void) -{ - dbgMutLog_t *pLog; - - dbgprintf("Mutex log for all known mutex operations:\n"); - for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext) - dbgMutLogPrintOne(pLog); - -} - - -/* find the last log entry for that specific mutex object. Is used to delete - * a thread's own requests. Searches occur from the back. - * The pFuncDB is optional and may be NULL to indicate no specific funciont is - * reqested (aka "it is ignored" ;)). This is important for the unlock case. - */ -dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, dbgFuncDB_t *pFuncDB) -{ - dbgMutLog_t *pLog; - pthread_t mythrd = pthread_self(); - - pLog = dbgMutLogListLast; - while(pLog != NULL) { - if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop - && (pFuncDB == NULL || pLog->pFuncDB == pFuncDB)) - break; - pLog = pLog->pPrev; - } - - return pLog; -} - - -/* find mutex object from the back of the list */ -dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast) -{ - dbgMutLog_t *pLog; - - if(pLast == NULL) - pLog = dbgMutLogListLast; - else - pLog = pLast->pPrev; /* if we get the last processed one, we need to go one before it, else its an endless loop */ - - while(pLog != NULL) { - if(pLog->mut == pmut) { - break; - } - pLog = pLog->pPrev; - } - - return pLog; -} - - -/* find lock aquire for mutex from back of list */ -dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut) -{ - dbgMutLog_t *pLog; - - pLog = dbgMutLogFindFromBack(pmut, NULL); - while(pLog != NULL) { - if(pLog->mutexOp == MUTOP_LOCK) - break; - pLog = dbgMutLogFindFromBack(pmut, pLog); - } - - return pLog; -} - -/* report wait on a mutex and add it to the mutex log */ -static inline void dbgMutexPreLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln) -{ - dbgMutLog_t *pHolder; - dbgMutLog_t *pLog; - char pszBuf[128]; - char pszHolderThrdName[64]; - char *pszHolder; - - pthread_mutex_lock(&mutMutLog); - pHolder = dbgMutLogFindHolder(pmut); - pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, pFuncDB, ln); - - if(pHolder == NULL) - pszHolder = "[NONE]"; - else { - dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1); - snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName); - pszHolder = pszBuf; - } - - if(bPrintMutexAction) - dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder); - pthread_mutex_unlock(&mutMutLog); -} - - -/* report aquired mutex */ -static inline void dbgMutexLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn) -{ - dbgMutLog_t *pLog; - - pthread_mutex_lock(&mutMutLog); - - /* find and delete "waiting" entry */ - pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, pFuncDB); - assert(pLog != NULL); - dbgMutLogDelEntry(pLog); - - /* add "lock" entry */ - pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn); - dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn); - pthread_mutex_unlock(&mutMutLog); - if(bPrintMutexAction) - dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut); -} - -/* if we unlock, we just remove the lock aquired entry from the log list */ -static inline void dbgMutexUnlockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int unlockLn) -{ - dbgMutLog_t *pLog; - - pthread_mutex_lock(&mutMutLog); - pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL); - assert(pLog != NULL); - - /* we found the last lock entry. We now need to see from which FuncDB we need to - * remove it. This is recorded inside the mutex log entry. - */ - dbgFuncDBRemoveMutexLock(pLog->pFuncDB, pmut); - - /* donw with the log entry, get rid of it... */ - dbgMutLogDelEntry(pLog); - - pthread_mutex_unlock(&mutMutLog); - if(bPrintMutexAction) - dbgprintf("%s:%d:%s: mutex %p UNlocked\n", pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut); -} - - -/* wrapper for pthread_mutex_lock() */ -int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) -{ - int ret; - - dbgRecordExecLocation(iStackPtr, ln); - dbgMutexPreLockLog(pmut, pFuncDB, ln); - ret = pthread_mutex_lock(pmut); - if(ret == 0) { - dbgMutexLockLog(pmut, pFuncDB, ln); - } else { - dbgprintf("%s:%d:%s: ERROR: pthread_mutex_lock() for mutex %p failed with error %d\n", - pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret); - } - - return ret; -} - - -/* wrapper for pthread_mutex_unlock() */ -int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) -{ - int ret; - dbgRecordExecLocation(iStackPtr, ln); - dbgMutexUnlockLog(pmut, pFuncDB, ln); - ret = pthread_mutex_unlock(pmut); - return ret; -} - - -/* wrapper for pthread_cond_wait() */ -int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) -{ - int ret; - dbgRecordExecLocation(iStackPtr, ln); - dbgMutexUnlockLog(pmut, pFuncDB, ln); - if(bPrintMutexAction) { - dbgprintf("%s:%d:%s: mutex %p waiting on condition %p\n", pFuncDB->file, pFuncDB->line, - pFuncDB->func, (void*)pmut, (void*)cond); - } - dbgMutexPreLockLog(pmut, pFuncDB, ln); - ret = pthread_cond_wait(cond, pmut); - return ret; -} - - -/* wrapper for pthread_cond_timedwait() */ -int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) -{ - int ret; - dbgRecordExecLocation(iStackPtr, ln); - dbgMutexUnlockLog(pmut, pFuncDB, ln); - dbgMutexPreLockLog(pmut, pFuncDB, ln); - if(bPrintMutexAction) { - dbgprintf("%s:%d:%s: mutex %p waiting on condition %p (with timeout)\n", pFuncDB->file, - pFuncDB->line, pFuncDB->func, (void*)pmut, (void*)cond); - } - ret = pthread_cond_timedwait(cond, pmut, abstime); - dbgMutexLockLog(pmut, pFuncDB, ln); - return ret; -} - - -/* ------------------------- end mutex tracking code ------------------------- */ - - -/* ------------------------- malloc/free tracking code ------------------------- */ - -/* wrapper for free() */ -void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) -{ - dbgRecordExecLocation(iStackPtr, ln); - if(bLogAllocFree) { - dbgprintf("%s:%d:%s: free %p\n", pFuncDB->file, ln, pFuncDB->func, (void*) pMem); - } - free(pMem); -} - - -/* ------------------------- end malloc/free tracking code ------------------------- */ - -/* ------------------------- thread tracking code ------------------------- */ - -/* get ptr to call stack - if none exists, create a new stack - */ -static dbgThrdInfo_t *dbgGetThrdInfo(void) -{ - dbgThrdInfo_t *pThrd; - - pthread_mutex_lock(&mutCallStack); - if((pThrd = pthread_getspecific(keyCallStack)) == NULL) { - /* construct object */ - pThrd = calloc(1, sizeof(dbgThrdInfo_t)); - pThrd->thrd = pthread_self(); - (void) pthread_setspecific(keyCallStack, pThrd); - DLL_Add(CallStack, pThrd); - } - pthread_mutex_unlock(&mutCallStack); - return pThrd; -} - - - -/* find a specific thread ID. It must be present, else something is wrong - */ -static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd) -{ - dbgThrdInfo_t *pThrd; - - for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { - if(pThrd->thrd == thrd) - break; - } - return pThrd; -} - - -/* build a string with the thread name. If none is set, the thread ID is - * used instead. Caller must provide buffer space. If bIncludeNumID is set - * to 1, the numerical ID is always included. - * rgerhards 2008-01-23 - */ -static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID) -{ - dbgThrdInfo_t *pThrd; - - assert(pszBuf != NULL); - - pThrd = dbgFindThrd(thrd); - - if(pThrd == 0 || pThrd->pszThrdName == NULL) { - /* no thread name, use numeric value */ - snprintf(pszBuf, lenBuf, "%lx", (long) thrd); - } else { - if(bIncludeNumID) { - snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, (long) thrd); - } else { - snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName); - } - } - -} - - -/* set a name for the current thread. The caller provided string is duplicated. - */ -void dbgSetThrdName(uchar *pszName) -{ - dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); - if(pThrd->pszThrdName != NULL) - free(pThrd->pszThrdName); - pThrd->pszThrdName = strdup((char*)pszName); -} - - -/* destructor for a call stack object */ -static void dbgCallStackDestruct(void *arg) -{ - dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg; - - dbgprintf("destructor for debug call stack %p called\n", pThrd); - if(pThrd->pszThrdName != NULL) { - free(pThrd->pszThrdName); - } - - pthread_mutex_lock(&mutCallStack); - DLL_Del(CallStack, pThrd); - pthread_mutex_unlock(&mutCallStack); -} - - -/* print a thread's call stack - */ -static void dbgCallStackPrint(dbgThrdInfo_t *pThrd) -{ - int i; - char pszThrdName[64]; - - pthread_mutex_lock(&mutCallStack); - dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1); - dbgprintf("\n"); - dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName); - for(i = 0 ; i < pThrd->stackPtr ; i++) { - dbgprintf("%d: %s:%d:%s:\n", i, pThrd->callStack[i]->file, pThrd->lastLine[i], pThrd->callStack[i]->func); - } - dbgprintf("maximum number of nested calls for this thread: %d.\n", pThrd->stackPtrMax); - dbgprintf("NOTE: not all calls may have been recorded, code does not currently guarantee that!\n"); - pthread_mutex_unlock(&mutCallStack); -} - -/* print all threads call stacks - */ -static void dbgCallStackPrintAll(void) -{ - dbgThrdInfo_t *pThrd; - /* stack info */ - for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { - dbgCallStackPrint(pThrd); - } -} - - -/* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat - * more meaningful way. - * rgerhards, 2008-01-22 - */ -void -sigsegvHdlr(int signum) -{ - char *signame; - struct sigaction sigAct; - - /* first, restore the default abort handler */ - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = SIG_DFL; - sigaction(SIGABRT, &sigAct, NULL); - - /* then do our actual processing */ - if(signum == SIGSEGV) { - signame = " (SIGSEGV)"; - } else if(signum == SIGABRT) { - signame = " (SIGABRT)"; - } else { - signame = ""; - } - - dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame); - - if(bAbortTrace) { - dbgPrintAllDebugInfo(); - dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); - dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); - } - - dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); - if(stddbg != NULL) fflush(stddbg); - if(altdbg != NULL) fflush(altdbg); - - /* and finally abort... */ - /* TODO: think about restarting rsyslog in this case: may be a good idea, - * but may also be a very bad one (restart loops!) - */ - abort(); -} - - -/* print some debug output when an object is given - * This is mostly a copy of dbgprintf, but I do not know how to combine it - * into a single function as we have variable arguments and I don't know how to call - * from one vararg function into another. I don't dig in this, it is OK for the - * time being. -- rgerhards, 2008-01-29 - */ -void -dbgoprint(obj_t *pObj, char *fmt, ...) -{ - static pthread_t ptLastThrdID = 0; - static int bWasNL = 0; - va_list ap; - static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ - static char pszWriteBuf[1024]; - size_t lenWriteBuf; - struct timespec t; - - if(!(Debug && debugging_on)) - return; - - /* a quick and very dirty hack to enable us to display just from those objects - * that we are interested in. So far, this must be changed at compile time (and - * chances are great it is commented out while you read it. Later, this shall - * be selectable via the environment. -- rgerhards, 2008-02-20 - */ -#if 0 - if(objGetObjID(pObj) != OBJexpr) - return; -#endif - - - pthread_mutex_lock(&mutdbgoprint); - pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgoprint); - - /* The bWasNL handler does not really work. It works if no thread - * switching occurs during non-NL messages. Else, things are messed - * up. Anyhow, it works well enough to provide useful help during - * getting this up and running. It is questionable if the extra effort - * is worth fixing it, giving the limited appliability. - * rgerhards, 2005-10-25 - * I have decided that it is not worth fixing it - especially as it works - * pretty well. - * rgerhards, 2007-06-15 - */ - if(ptLastThrdID != pthread_self()) { - if(!bWasNL) { - if(stddbg != NULL) fprintf(stddbg, "\n"); - if(altdbg != NULL) fprintf(altdbg, "\n"); - bWasNL = 1; - } - ptLastThrdID = pthread_self(); - } - - /* do not cache the thread name, as the caller might have changed it - * TODO: optimized, invalidate cache when new name is set - */ - dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); - - if(bWasNL) { - if(bPrintTime) { - clock_gettime(CLOCK_REALTIME, &t); - if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); - if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); - } - if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); - if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); - /* print object name header if we have an object */ - if(pObj != NULL) { - if(stddbg != NULL) fprintf(stddbg, "%s: ", obj.GetName(pObj)); - if(altdbg != NULL) fprintf(altdbg, "%s: ", obj.GetName(pObj)); - } - } - bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; - va_start(ap, fmt); - lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); - if(lenWriteBuf >= sizeof(pszWriteBuf)) { - /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ - lenWriteBuf = sizeof(pszWriteBuf) - 1; - } - va_end(ap); - /* - if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); - if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); - */ - if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); - if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); - - if(stddbg != NULL) fflush(stddbg); - if(altdbg != NULL) fflush(altdbg); - pthread_cleanup_pop(1); -} - - -/* print some debug output when no object is given - * WARNING: duplicate code, see dbgoprin above! - */ -void -dbgprintf(char *fmt, ...) -{ - static pthread_t ptLastThrdID = 0; - static int bWasNL = 0; - va_list ap; - static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ - static char pszWriteBuf[1024]; - size_t lenWriteBuf; - struct timespec t; - - if(!(Debug && debugging_on)) - return; - - pthread_mutex_lock(&mutdbgprintf); - pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprintf); - - /* The bWasNL handler does not really work. It works if no thread - * switching occurs during non-NL messages. Else, things are messed - * up. Anyhow, it works well enough to provide useful help during - * getting this up and running. It is questionable if the extra effort - * is worth fixing it, giving the limited appliability. - * rgerhards, 2005-10-25 - * I have decided that it is not worth fixing it - especially as it works - * pretty well. - * rgerhards, 2007-06-15 - */ - if(ptLastThrdID != pthread_self()) { - if(!bWasNL) { - if(stddbg != NULL) fprintf(stddbg, "\n"); - if(altdbg != NULL) fprintf(altdbg, "\n"); - bWasNL = 1; - } - ptLastThrdID = pthread_self(); - } - - /* do not cache the thread name, as the caller might have changed it - * TODO: optimized, invalidate cache when new name is set - */ - dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); - - if(bWasNL) { - if(bPrintTime) { - clock_gettime(CLOCK_REALTIME, &t); - if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); - if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); - } - if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); - if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); - } - bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; - va_start(ap, fmt); - lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); - if(lenWriteBuf >= sizeof(pszWriteBuf)) { - /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ - lenWriteBuf = sizeof(pszWriteBuf) - 1; - } - va_end(ap); - /* - if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); - if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); - */ - if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); - if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); - - if(stddbg != NULL) fflush(stddbg); - if(altdbg != NULL) fflush(altdbg); - pthread_cleanup_pop(1); -} - -void tester(void) -{ -BEGINfunc -ENDfunc -} - -/* handler called when a function is entered. This function creates a new - * funcDB on the heap if the passed-in pointer is NULL. - */ -int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line) -{ - int iStackPtr = 0; /* TODO: find some better default, this one hurts the least, but it is not clean */ - dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); - dbgFuncDBListEntry_t *pFuncDBListEntry; - unsigned int i; - dbgFuncDB_t *pFuncDB; - - assert(ppFuncDB != NULL); - assert(file != NULL); - assert(func != NULL); - pFuncDB = *ppFuncDB; - assert((pFuncDB == NULL) || (pFuncDB->magic == dbgFUNCDB_MAGIC)); - - if(pFuncDB == NULL) { - /* we do not yet have a funcDB and need to create a new one. We also add it - * to the linked list of funcDBs. Please note that when a module is unloaded and - * then reloaded again, we currently do not try to find its previous funcDB but - * instead create a duplicate. While finding the past one is straightforward, it - * opens up the question what to do with e.g. mutex data left in it. We do not - * yet see any need to handle these questions, so duplicaton seems to be the right - * thing to do. -- rgerhards, 2008-03-10 - */ - /* dbgprintf("%s:%d:%s: called first time, initializing FuncDB\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); */ - /* get a new funcDB and add it to the list (all of this is protected by the mutex) */ - pthread_mutex_lock(&mutFuncDBList); - if((pFuncDBListEntry = calloc(1, sizeof(dbgFuncDBListEntry_t))) == NULL) { - dbgprintf("Error %d allocating memory for FuncDB List entry, not adding\n", errno); - pthread_mutex_unlock(&mutFuncDBList); - goto exit_it; - } else { - if((pFuncDB = calloc(1, sizeof(dbgFuncDB_t))) == NULL) { - dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); - free(pFuncDBListEntry); - pthread_mutex_unlock(&mutFuncDBList); - goto exit_it; - } else { - pFuncDBListEntry->pFuncDB = pFuncDB; - pFuncDBListEntry->pNext = pFuncDBListRoot; - pFuncDBListRoot = pFuncDBListEntry; - } - } - /* now intialize the funcDB - * note that we duplicate the strings, because the address provided may go away - * if a loadable module is unloaded! - */ - pFuncDB->magic = dbgFUNCDB_MAGIC; - pFuncDB->file = strdup(file); - pFuncDB->func = strdup(func); - pFuncDB->line = line; - pFuncDB->nTimesCalled = 0; - for(i = 0 ; i < sizeof(pFuncDB->mutInfo)/sizeof(dbgFuncDBmutInfoEntry_t) ; ++i) { - pFuncDB->mutInfo[i].lockLn = -1; /* set to not Locked */ - } - - /* a round of safety checks... */ - if(pFuncDB->file == NULL || pFuncDB->func == NULL) { - dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); - /* do a little bit of cleanup */ - if(pFuncDB->file != NULL) - free(pFuncDB->file); - if(pFuncDB->func != NULL) - free(pFuncDB->func); - free(pFuncDB); - free(pFuncDBListEntry); - pthread_mutex_unlock(&mutFuncDBList); - goto exit_it; - } - - /* done mutex-protected operations */ - pthread_mutex_unlock(&mutFuncDBList); - - *ppFuncDB = pFuncDB; /* all went well, so we can update the caller */ - } - - /* when we reach this point, we have a fully-initialized FuncDB! */ - ATOMIC_INC(pFuncDB->nTimesCalled); - if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) - dbgprintf("%s:%d: %s: enter\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); - if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(dbgFuncDB_t*))) { - dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n", - pFuncDB->file, pFuncDB->line, pFuncDB->func); - iStackPtr = pThrd->stackPtr; - } else { - iStackPtr = pThrd->stackPtr++; - if(pThrd->stackPtr > pThrd->stackPtrMax) - pThrd->stackPtrMax = pThrd->stackPtr; - pThrd->callStack[iStackPtr] = pFuncDB; - pThrd->lastLine[iStackPtr] = line; - } - -exit_it: - return iStackPtr; -} - - -/* handler called when a function is exited - */ -void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet) -{ - dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); - - assert(iStackPtrRestore >= 0); - assert(pFuncDB != NULL); - assert(pFuncDB->magic == dbgFUNCDB_MAGIC); - - dbgFuncDBPrintActiveMutexes(pFuncDB, "WARNING: mutex still owned by us as we exit function, mutex: ", pthread_self()); - if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) { - if(iRet == RS_RET_NO_IRET) - dbgprintf("%s:%d: %s: exit: (no iRet)\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); - else - dbgprintf("%s:%d: %s: exit: %d\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, iRet); - } - pThrd->stackPtr = iStackPtrRestore; - if(pThrd->stackPtr < 0) { - dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", (long) pthread_self()); - pThrd->stackPtr = 0; - } -} - - -/* externally-callable handler to record the last exec location. We use a different function - * so that the internal one can be inline. - */ -void -dbgSetExecLocation(int iStackPtr, int line) -{ - dbgRecordExecLocation(iStackPtr, line); -} - - -void dbgPrintAllDebugInfo(void) -{ - dbgCallStackPrintAll(); - dbgMutLogPrintAll(); - if(bPrintFuncDBOnExit) - dbgFuncDBPrintAll(); -} - - -/* Handler for SIGUSR2. Dumps all available debug output - */ -static void sigusr2Hdlr(int __attribute__((unused)) signum) -{ - dbgprintf("SIGUSR2 received, dumping debug information\n"); - dbgPrintAllDebugInfo(); -} - -/* support system to set debug options at runtime */ - - -/* parse a param/value pair from the current location of the - * option string. Returns 1 if an option was found, 0 - * otherwise. 0 means there are NO MORE options to be - * processed. -- rgerhards, 2008-02-28 - */ -static int -dbgGetRTOptNamVal(uchar **ppszOpt, uchar **ppOptName, uchar **ppOptVal) -{ - int bRet = 0; - uchar *p; - size_t i; - static uchar optname[128]; /* not thread- or reentrant-safe, but that */ - static uchar optval[1024]; /* doesn't matter (called only once at startup) */ - - assert(ppszOpt != NULL); - assert(*ppszOpt != NULL); - - /* make sure we have some initial values */ - optname[0] = '\0'; - optval[0] = '\0'; - - p = *ppszOpt; - /* skip whitespace */ - while(*p && isspace(*p)) - ++p; - - /* name - up until '=' or whitespace */ - i = 0; - while(i < (sizeof(optname)/sizeof(uchar) - 1) && *p && *p != '=' && !isspace(*p)) { - optname[i++] = *p++; - } - - if(i > 0) { - bRet = 1; - optname[i] = '\0'; - if(*p == '=') { - /* we have a value, get it */ - ++p; - i = 0; - while(i < (sizeof(optval)/sizeof(uchar) - 1) && *p && !isspace(*p)) { - optval[i++] = *p++; - } - optval[i] = '\0'; - } - } - - /* done */ - *ppszOpt = p; - *ppOptName = optname; - *ppOptVal = optval; - return bRet; -} - - -/* create new PrintName list entry and add it to list (they will never - * be removed. -- rgerhards, 2008-02-28 - */ -static void -dbgPrintNameAdd(uchar *pName, dbgPrintName_t **ppRoot) -{ - dbgPrintName_t *pEntry; - - if((pEntry = calloc(1, sizeof(dbgPrintName_t))) == NULL) { - fprintf(stderr, "ERROR: out of memory during debug setup\n"); - exit(1); - } - - if((pEntry->pName = (uchar*) strdup((char*) pName)) == NULL) { - fprintf(stderr, "ERROR: out of memory during debug setup\n"); - exit(1); - } - - if(*ppRoot != NULL) { - pEntry->pNext = *ppRoot; /* we enqueue at the front */ - } - *ppRoot = pEntry; - -printf("Name %s added to %p\n", pName, *ppRoot); -} - - -/* check if name is in a printName list - returns 1 if so, 0 otherwise. - * There is one special handling: if the root pointer is NULL, the function - * always returns 1. This is because when no name is set, output shall be - * unrestricted. - * rgerhards, 2008-02-28 - */ -static int -dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot) -{ - int bFound = 0; - dbgPrintName_t *pEntry = pRoot; - - if(pRoot == NULL) - bFound = 1; - - while(pEntry != NULL && !bFound) { - if(!strcasecmp((char*)pEntry->pName, (char*)pName)) { - bFound = 1; - } else { - pEntry = pEntry->pNext; - } - } - - return bFound; -} - - -/* read in the runtime options - * rgerhards, 2008-02-28 - */ -static void -dbgGetRuntimeOptions(void) -{ - uchar *pszOpts; - uchar *optval; - uchar *optname; - - /* set some defaults */ - stddbg = stdout; - - if((pszOpts = (uchar*) getenv("RSYSLOG_DEBUG")) != NULL) { - /* we have options set, so let's process them */ - while(dbgGetRTOptNamVal(&pszOpts, &optname, &optval)) { - if(!strcasecmp((char*)optname, "help")) { - fprintf(stderr, - "rsyslogd runtime debug support - help requested, rsyslog terminates\n\n" - "environment variables:\n" - "addional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n" - "to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n" - "Commands are (all case-insensitive):\n" - "help (this list, terminates rsyslogd\n" - "LogFuncFlow\n" - "LogAllocFree (very partly implemented)\n" - "PrintFuncDB\n" - "PrintMutexAction\n" - "PrintAllDebugInfoOnExit (not yet implemented)\n" - "NoLogTimestamp\n" - "Nostdoout\n" - "filetrace=file (may be provided multiple times)\n" - "\nSee debug.html in your doc set or http://www.rsyslog.com for details\n"); - exit(1); - } else if(!strcasecmp((char*)optname, "debug")) { - /* this is earlier in the process than the -d option, as such it - * allows us to spit out debug messages from the very beginning. - */ - Debug = 1; - debugging_on = 1; - } else if(!strcasecmp((char*)optname, "logfuncflow")) { - bLogFuncFlow = 1; - } else if(!strcasecmp((char*)optname, "logallocfree")) { - bLogAllocFree = 1; - } else if(!strcasecmp((char*)optname, "printfuncdb")) { - bPrintFuncDBOnExit = 1; - } else if(!strcasecmp((char*)optname, "printmutexaction")) { - bPrintMutexAction = 1; - } else if(!strcasecmp((char*)optname, "printalldebuginfoonexit")) { - bPrintAllDebugOnExit = 1; - } else if(!strcasecmp((char*)optname, "nologtimestamp")) { - bPrintTime = 0; - } else if(!strcasecmp((char*)optname, "nostdout")) { - stddbg = NULL; - } else if(!strcasecmp((char*)optname, "noaborttrace")) { - bAbortTrace = 0; - } else if(!strcasecmp((char*)optname, "filetrace")) { - if(*optval == '\0') { - fprintf(stderr, "Error: logfile debug option requires filename, " - "e.g. \"logfile=debug.c\"\n"); - exit(1); - } else { - /* create new entry and add it to list */ - dbgPrintNameAdd(optval, &printNameFileRoot); - } - } else { - fprintf(stderr, "Error: invalid debug option '%s', value '%s' - ignored\n", - optval, optname); - } - } - } -} - - -/* end support system to set debug options at runtime */ - -rsRetVal dbgClassInit(void) -{ - DEFiRet; - - struct sigaction sigAct; - sigset_t sigSet; - - (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ - - /* we initialize all Mutexes with code, as some platforms seem to have - * bugs in the static initializer macros. So better be on the safe side... - * rgerhards, 2008-03-06 - */ - pthread_mutex_init(&mutFuncDBList, NULL); - pthread_mutex_init(&mutMutLog, NULL); - pthread_mutex_init(&mutCallStack, NULL); - pthread_mutex_init(&mutdbgprintf, NULL); - pthread_mutex_init(&mutdbgoprint, NULL); - - /* while we try not to use any of the real rsyslog code (to avoid infinite loops), we - * need to have the ability to query object names. Thus, we need to obtain a pointer to - * the object interface. -- rgerhards, 2008-02-29 - */ - CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = sigusr2Hdlr; - sigaction(SIGUSR2, &sigAct, NULL); - - sigemptyset(&sigSet); - sigaddset(&sigSet, SIGUSR2); - pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); - - dbgGetRuntimeOptions(); /* init debug system from environment */ - pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG"); - - if(pszAltDbgFileName != NULL) { - /* we have a secondary file, so let's open it) */ - if((altdbg = fopen(pszAltDbgFileName, "w")) == NULL) { - fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); - } - } - - dbgSetThrdName((uchar*)"main thread"); - -finalize_it: - RETiRet; -} - - -rsRetVal dbgClassExit(void) -{ - dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel; - pthread_key_delete(keyCallStack); - - if(bPrintAllDebugOnExit) - dbgPrintAllDebugInfo(); - - if(altdbg != NULL) - fclose(altdbg); - - /* now free all of our memory to make the memory debugger happy... */ - pFuncDBListEtry = pFuncDBListRoot; - while(pFuncDBListEtry != NULL) { - pToDel = pFuncDBListEtry; - pFuncDBListEtry = pFuncDBListEtry->pNext; - free(pToDel->pFuncDB->file); - free(pToDel->pFuncDB->func); - free(pToDel->pFuncDB); - free(pToDel); - } - - return RS_RET_OK; -} -/* vi:set ai: - */ diff --git a/debug.h b/debug.h deleted file mode 100644 index 4dcc593a..00000000 --- a/debug.h +++ /dev/null @@ -1,145 +0,0 @@ -/* debug.h - * - * Definitions for the debug and run-time analysis support module. - * Contains a lot of macros. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef DEBUG_H_INCLUDED -#define DEBUG_H_INCLUDED - -#include -#include "obj-types.h" - -/* external static data elements (some time to be replaced) */ -extern int Debug; /* debug flag - read-only after startup */ -extern int debugging_on; /* read-only, except on sig USR1 */ - -/* data types */ - -/* the function database. It is used as a static var inside each function. That provides - * us the fast access to it that we need to make the instrumentation work. It's address - * also serves as a unique function identifier and can be used inside other structures - * to refer to the function (e.g. for pretty-printing names). - * rgerhards, 2008-01-24 - */ -typedef struct dbgFuncDBmutInfoEntry_s { - pthread_mutex_t *pmut; - int lockLn; /* line where it was locked (inside our func): -1 means mutex is not locked */ - pthread_t thrd; /* thrd where the mutex was locked */ - unsigned long lInvocation; /* invocation (unique during program run!) of this function that locked the mutex */ -} dbgFuncDBmutInfoEntry_t; -typedef struct dbgFuncDB_s { - unsigned magic; - unsigned long nTimesCalled; - char *func; - char *file; - int line; - dbgFuncDBmutInfoEntry_t mutInfo[5]; - /* remember to update the initializer if you add anything or change the order! */ -} dbgFuncDB_t; -#define dbgFUNCDB_MAGIC 0xA1B2C3D4 -#define dbgFuncDB_t_INITIALIZER \ - { \ - .magic = dbgFUNCDB_MAGIC,\ - .nTimesCalled = 0,\ - .func = __func__, \ - .file = __FILE__, \ - .line = __LINE__ \ - } - -/* the structure below was originally just the thread's call stack, but it has - * a bit evolved over time. So we have now ended up with the fact that it - * all debug info we know about the thread. - */ -typedef struct dbgCallStack_s { - pthread_t thrd; - dbgFuncDB_t *callStack[500]; - int lastLine[500]; /* last line where code execution was seen */ - int stackPtr; - int stackPtrMax; - char *pszThrdName; - struct dbgCallStack_s *pNext; - struct dbgCallStack_s *pPrev; -} dbgThrdInfo_t; - - -/* prototypes */ -rsRetVal dbgClassInit(void); -rsRetVal dbgClassExit(void); -void sigsegvHdlr(int signum); -void dbgoprint(obj_t *pObj, char *fmt, ...) __attribute__((format(printf, 2, 3))); -void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); -int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); -int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); -int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); -int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); -void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr); -int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line); -void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet); -void dbgSetExecLocation(int iStackPtr, int line); -void dbgSetThrdName(uchar *pszName); -void dbgPrintAllDebugInfo(void); - -/* macros */ -#ifdef RTINST -# define BEGINfunc static dbgFuncDB_t *pdbgFuncDB; int dbgCALLStaCK_POP_POINT = dbgEntrFunc(&pdbgFuncDB, __FILE__, __func__, __LINE__); -# define ENDfunc dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, RS_RET_NO_IRET); -# define ENDfuncIRet dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, iRet); -# define ASSERT(x) assert(x) -#else -# define BEGINfunc -# define ENDfunc -# define ENDfuncIRet -# define ASSERT(x) -#endif -#ifdef RTINST -# define RUNLOG dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__); dbgprintf("%s:%d: %s: log point\n", __FILE__, __LINE__, __func__) -# define RUNLOG_VAR(fmt, x) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ - dbgprintf("%s:%d: %s: var '%s'[%s]: " fmt "\n", __FILE__, __LINE__, __func__, #x, fmt, x) -# define RUNLOG_STR(str) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ - dbgprintf("%s:%d: %s: %s\n", __FILE__, __LINE__, __func__, str) -#else -# define RUNLOG -# define RUNLOG_VAR(fmt, x) -# define RUNLOG_STR(str) -#endif - -/* mutex operations */ -#define MUTOP_LOCKWAIT 1 -#define MUTOP_LOCK 2 -#define MUTOP_UNLOCK 3 - - -/* debug aides */ -#ifdef RTINST -#define d_pthread_mutex_lock(x) dbgMutexLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) -#define d_pthread_mutex_unlock(x) dbgMutexUnlock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) -#define d_pthread_cond_wait(cond, mut) dbgCondWait(cond, mut, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) -#define d_pthread_cond_timedwait(cond, mut, to) dbgCondTimedWait(cond, mut, to, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) -#define d_free(x) dbgFree(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) -#else -#define d_pthread_mutex_lock(x) pthread_mutex_lock(x) -#define d_pthread_mutex_unlock(x) pthread_mutex_unlock(x) -#define d_pthread_cond_wait(cond, mut) pthread_cond_wait(cond, mut) -#define d_pthread_cond_timedwait(cond, mut, to) pthread_cond_timedwait(cond, mut, to) -#define d_free(x) free(x) -#endif -#endif /* #ifndef DEBUG_H_INCLUDED */ diff --git a/errmsg.c b/errmsg.c deleted file mode 100644 index 907046b9..00000000 --- a/errmsg.c +++ /dev/null @@ -1,120 +0,0 @@ -/* The errmsg 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 rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#include "config.h" -#include -#include -#include -#include -#include - -#include "rsyslog.h" -#include "syslogd.h" -#include "obj.h" -#include "errmsg.h" -#include "sysvar.h" -#include "srUtils.h" -#include "stringbuf.h" - -/* static data */ -DEFobjStaticHelpers - - -/* ------------------------------ methods ------------------------------ */ - - -/* TODO: restructure this code some time. Especially look if we need - * to check errno and, if so, how to do that in a clean way. - */ -static void __attribute__((format(printf, 2, 3))) -LogError(int __attribute__((unused)) iErrCode, char *fmt, ... ) -{ - va_list ap; - char buf[1024]; - char msg[1024]; - char errStr[1024]; - size_t lenBuf; - - BEGINfunc - assert(fmt != NULL); - /* Format parameters */ - va_start(ap, fmt); - lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap); - if(lenBuf >= sizeof(buf)) { - /* if our buffer was too small, we simply truncate. */ - lenBuf--; - } - va_end(ap); - - /* Log the error now */ - buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ - - dbgprintf("Called LogError, msg: %s\n", buf); - - if (errno == 0) { - snprintf(msg, sizeof(msg), "%s", buf); - } else { - rs_strerror_r(errno, errStr, sizeof(errStr)); - snprintf(msg, sizeof(msg), "%s: %s", buf, errStr); - } - msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ - errno = 0; - logmsgInternal(LOG_SYSLOG|LOG_ERR, msg, ADDDATE); - - ENDfunc -} - - -/* queryInterface function - * rgerhards, 2008-03-05 - */ -BEGINobjQueryInterface(errmsg) -CODESTARTobjQueryInterface(errmsg) - if(pIf->ifVersion != errmsgCURR_IF_VERSION) { /* check for current version, increment on each change */ - ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); - } - - /* ok, we have the right interface, so let's fill it - * Please note that we may also do some backwards-compatibility - * work here (if we can support an older interface version - that, - * of course, also affects the "if" above). - */ - pIf->LogError = LogError; -finalize_it: -ENDobjQueryInterface(errmsg) - - -/* Initialize the errmsg class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-02-19 - */ -BEGINAbstractObjClassInit(errmsg, 1, OBJ_IS_CORE_MODULE) /* class, version */ - /* request objects we use */ - - /* set our own handlers */ -ENDObjClassInit(errmsg) - -/* vi:set ai: - */ diff --git a/errmsg.h b/errmsg.h deleted file mode 100644 index 12469581..00000000 --- a/errmsg.h +++ /dev/null @@ -1,45 +0,0 @@ -/* The errmsg object. It is used to emit error message inside rsyslog. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef INCLUDED_ERRMSG_H -#define INCLUDED_ERRMSG_H - -#include "errmsg.h" - -/* TODO: define error codes */ -#define NO_ERRCODE -1 - -/* the errmsg object */ -typedef struct errmsg_s { -} errmsg_t; - - -/* interfaces */ -BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */ - void __attribute__((format(printf, 2, 3))) (*LogError)(int iErrCode, char *pszErrFmt, ... ); -ENDinterface(errmsg) -#define errmsgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ - - -/* prototypes */ -PROTOTYPEObj(errmsg); - -#endif /* #ifndef INCLUDED_ERRMSG_H */ diff --git a/glbl.h b/glbl.h index 6d08ddd5..5385006a 100644 --- a/glbl.h +++ b/glbl.h @@ -10,22 +10,23 @@ * * Copyright 2008 Rainer Gerhards and Adiscon GmbH. * - * This file is part of rsyslog. + * This file is part of the rsyslog runtime library. * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by + * The 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. * - * Rsyslog is distributed in the hope that it will be useful, + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . * * 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 GLOBALS_H_INCLUDED diff --git a/liblogging-stub.h b/liblogging-stub.h deleted file mode 100644 index 03315f08..00000000 --- a/liblogging-stub.h +++ /dev/null @@ -1,26 +0,0 @@ -/* This is a (now *very slim*) stub for some liblogging - * code we use in rsyslog. - * - * Copyright (C) 2004, 2007 by Rainer Gerhards and Adiscon GmbH - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef __LIB3195_LIBLOGGINGSTUB_H_INCLUDED__ -#define __LIB3195_LIBLOGGINGSTUB_H_INCLUDED__ 1 -#include -#endif diff --git a/linkedlist.c b/linkedlist.c deleted file mode 100644 index 9adf40c4..00000000 --- a/linkedlist.c +++ /dev/null @@ -1,413 +0,0 @@ -/* linkedlist.c - * This file set implements a generic linked list object. It can be used - * wherever a linke list is required. - * - * NOTE: we do not currently provide a constructor and destructor for the - * object itself as we assume it will always be part of another strucuture. - * Having a pointer to it, I think, does not really make sense but costs - * performance. Consequently, there is is llInit() and llDestroy() and they - * do what a constructor and destructur do, except for creating the - * linkedList_t structure itself. - * - * File begun on 2007-07-31 by RGerhards - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include -#include -#include - -#include "rsyslog.h" -#include "linkedlist.h" - - -/* Initialize an existing linkedList_t structure - * pKey destructor may be zero to take care of non-keyed lists. - */ -rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()) -{ - assert(pThis != NULL); - assert(pEltDestructor != NULL); - - pThis->pEltDestruct = pEltDestructor; - pThis->pKeyDestruct = pKeyDestructor; - pThis->cmpOp = pCmpOp; - pThis->pKey = NULL; - pThis->iNumElts = 0; - pThis->pRoot = NULL; - pThis->pLast = NULL; - - return RS_RET_OK; -}; - - -/* llDestroyEltData - destroys a list element - * It is a separate function as the - * functionality is needed in multiple code-pathes. - */ -static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt) -{ - DEFiRet; - - assert(pList != NULL); - assert(pElt != NULL); - - /* we ignore errors during destruction, as we need to try - * free the element in any case. - */ - if(pElt->pData != NULL) - pList->pEltDestruct(pElt->pData); - if(pElt->pKey != NULL) - pList->pKeyDestruct(pElt->pKey); - free(pElt); - pList->iNumElts--; /* one less */ - - RETiRet; -} - - -/* llDestroy - destroys a COMPLETE linkedList - */ -rsRetVal llDestroy(linkedList_t *pThis) -{ - DEFiRet; - llElt_t *pElt; - llElt_t *pEltPrev; - - assert(pThis != NULL); - - pElt = pThis->pRoot; - while(pElt != NULL) { - pEltPrev = pElt; - pElt = pElt->pNext; - /* we ignore errors during destruction, as we need to try - * finish the linked list in any case. - */ - llDestroyElt(pThis, pEltPrev); - } - /* now clean up the pointers */ - pThis->pRoot = NULL; - pThis->pLast = NULL; - - RETiRet; -} - -/* llDestroyRootElt - destroy the root element but otherwise - * keeps this list intact. -- rgerhards, 2007-08-03 - */ -rsRetVal llDestroyRootElt(linkedList_t *pThis) -{ - DEFiRet; - llElt_t *pPrev; - - if(pThis->pRoot == NULL) { - ABORT_FINALIZE(RS_RET_EMPTY_LIST); - } - - pPrev = pThis->pRoot; - if(pPrev->pNext == NULL) { - /* it was the only list element */ - pThis->pLast = NULL; - pThis->pRoot = NULL; - } else { - /* there are other list elements */ - pThis->pRoot = pPrev->pNext; - } - - CHKiRet(llDestroyElt(pThis, pPrev)); - -finalize_it: - RETiRet; -} - - -/* get next user data element of a linked list. The caller must also - * provide a "cookie" to the function. On initial call, it must be - * NULL. Other than that, the caller is not allowed to to modify the - * cookie. In the current implementation, the cookie is an actual - * pointer to the current list element, but this is nothing that the - * caller should rely on. - */ -rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr) -{ - llElt_t *pElt; - DEFiRet; - - assert(pThis != NULL); - assert(ppElt != NULL); - assert(ppUsr != NULL); - - pElt = *ppElt; - - pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext; - - if(pElt == NULL) { - iRet = RS_RET_END_OF_LINKEDLIST; - } else { - *ppUsr = pElt->pData; - } - - *ppElt = pElt; - - RETiRet; -} - - -/* return the key of an Elt - * rgerhards, 2007-09-11: note that ppDatea is actually a void**, - * but I need to make it a void* to avoid lots of compiler warnings. - * It will be converted later down in the code. - */ -rsRetVal llGetKey(llElt_t *pThis, void *ppData) -{ - assert(pThis != NULL); - assert(ppData != NULL); - - *(void**) ppData = pThis->pKey; - - return RS_RET_OK; -} - - -/* construct a new llElt_t - */ -static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData) -{ - DEFiRet; - llElt_t *pThis; - - assert(ppThis != NULL); - - if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - pThis->pKey = pKey; - pThis->pData = pData; - -finalize_it: - *ppThis = pThis; - RETiRet; -} - - -/* append a user element to the end of the linked list. This includes setting a key. If no - * key is desired, simply pass in a NULL pointer for it. - */ -rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData) -{ - llElt_t *pElt; - DEFiRet; - - CHKiRet(llEltConstruct(&pElt, pKey, pData)); - - pThis->iNumElts++; /* one more */ - if(pThis->pLast == NULL) { - pThis->pRoot = pElt; - } else { - pThis->pLast->pNext = pElt; - } - pThis->pLast = pElt; - -finalize_it: - RETiRet; -} - - -/* unlink a requested element. As we have singly-linked lists, the - * caller also needs to pass in the previous element (or NULL, if it is the - * root element). - * rgerhards, 2007-11-21 - */ -static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) -{ - assert(pElt != NULL); - - if(pEltPrev == NULL) { /* root element? */ - pThis->pRoot = pElt->pNext; - } else { /* regular element */ - pEltPrev->pNext = pElt->pNext; - } - - if(pElt == pThis->pLast) - pThis->pLast = pEltPrev; - - return RS_RET_OK; -} - - -/* unlinks and immediately deletes an element. Previous element must - * be given (or zero if the root element is to be deleted). - * rgerhards, 2007-11-21 - */ -static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) -{ - DEFiRet; - - assert(pElt != NULL); - - CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev)); - CHKiRet(llDestroyElt(pThis, pElt)); - -finalize_it: - RETiRet; -} - -/* find a user element based on the provided key - this is the - * internal variant, which also tracks the last element pointer - * before the found element. This is necessary to delete elements. - * NULL means there is no element in front of it, aka the found elt - * is the root elt. - * rgerhards, 2007-11-21 - */ -static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev) -{ - DEFiRet; - llElt_t *pElt; - llElt_t *pEltPrev = NULL; - int bFound = 0; - - assert(pThis != NULL); - assert(pKey != NULL); - assert(ppElt != NULL); - assert(ppEltPrev != NULL); - - pElt = pThis->pRoot; - while(pElt != NULL && bFound == 0) { - if(pThis->cmpOp(pKey, pElt->pKey) == 0) - bFound = 1; - else { - pEltPrev = pElt; - pElt = pElt->pNext; - } - } - - if(bFound == 1) { - *ppElt = pElt; - *ppEltPrev = pEltPrev; - } else - iRet = RS_RET_NOT_FOUND; - - RETiRet; -} - - -/* find a user element based on the provided key - */ -rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData) -{ - DEFiRet; - llElt_t *pElt; - llElt_t *pEltPrev; - - CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); - - /* if we reach this point, we have found the element */ - *ppData = pElt->pData; - -finalize_it: - RETiRet; -} - - -/* find a delete an element based on user-provided key. The element is - * delete, the caller does not receive anything. If we need to receive - * the element before destruction, we may implement an llFindAndUnlink() - * at that time. - * rgerhards, 2007-11-21 - */ -rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey) -{ - DEFiRet; - llElt_t *pElt; - llElt_t *pEltPrev; - - CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); - - /* if we reach this point, we have found an element */ - CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev)); - -finalize_it: - RETiRet; -} - - -/* provide the count of linked list elements - */ -rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt) -{ - DEFiRet; - - assert(pThis != NULL); - assert(piCnt != NULL); - - *piCnt = pThis->iNumElts; - - RETiRet; -} - - -/* execute a function on all list members. The functions receives a - * user-supplied parameter, which may be either a simple value - * or a pointer to a structure with more data. If the user-supplied - * function does not return RS_RET_OK, this function here terminates. - * rgerhards, 2007-08-02 - * rgerhards, 2007-11-21: added functionality to delete a list element. - * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element - * is deleted. - */ -rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam) -{ - DEFiRet; - rsRetVal iRetLL; - void *pData; - linkedListCookie_t llCookie = NULL; - linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */ - - assert(pThis != NULL); - assert(pFunc != NULL); - - while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) { - iRet = pFunc(pData, pParam); - if(iRet == RS_RET_OK_DELETE_LISTENTRY) { - /* delete element */ - CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev)); - /* we need to revert back, as we have just deleted the current element. - * So the actual current element is the one before it, which happens to be - * stored in llCookiePrev. -- rgerhards, 2007-11-21 - */ - llCookie = llCookiePrev; - } else if (iRet != RS_RET_OK) { - goto finalize_it; - } - llCookiePrev = llCookie; - } - - if(iRetLL != RS_RET_END_OF_LINKEDLIST) - iRet = iRetLL; - -finalize_it: - RETiRet; -} - - -/* - * vi:set ai: - */ diff --git a/linkedlist.h b/linkedlist.h deleted file mode 100644 index 98fb76a5..00000000 --- a/linkedlist.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Definition of the linkedlist object. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef LINKEDLIST_H_INCLUDED -#define LINKEDLIST_H_INCLUDED - -/* this is a single entry for a parse routine. It describes exactly - * one entry point/handler. - * The short name is cslch (Configfile SysLine CommandHandler) - */ -struct llElt_s { /* config file sysline parse entry */ - struct llElt_s *pNext; - void *pKey; /* key for this element */ - void *pData; /* user-supplied data pointer */ -}; -typedef struct llElt_s llElt_t; - - -/* this is the list of known configuration commands with pointers to - * their handlers. - * The short name is cslc (Configfile SysLine Command) - */ -struct linkedList_s { /* config file sysline parse entry */ - int iNumElts; /* number of elements in list */ - rsRetVal (*pEltDestruct)(void*pData); /* destructor for user pointer in llElt_t's */ - rsRetVal (*pKeyDestruct)(void*pKey); /* destructor for key pointer in llElt_t's */ - int (*cmpOp)(void*, void*); /* pointer to key compare operation function, retval like strcmp */ - void *pKey; /* the list key (searchable, if set) */ - llElt_t *pRoot; /* list root */ - llElt_t *pLast; /* list tail */ -}; -typedef struct linkedList_s linkedList_t; - -typedef llElt_t* linkedListCookie_t; /* this type avoids exposing internals and keeps us flexible */ - -/* prototypes */ -rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()); -rsRetVal llDestroy(linkedList_t *pThis); -rsRetVal llDestroyRootElt(linkedList_t *pThis); -rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr); -rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData); -rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData); -rsRetVal llGetKey(llElt_t *pThis, void *ppData); -rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt); -rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam); -rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey); -/* use the macro below to define a function that will be executed by - * llExecFunc() - */ -#define DEFFUNC_llExecFunc(funcName)\ - static rsRetVal funcName(void __attribute__((unused)) *pData, void __attribute__((unused)) *pParam) - -#endif /* #ifndef LINKEDLIST_H_INCLUDED */ diff --git a/module-template.h b/module-template.h deleted file mode 100644 index d5e142b4..00000000 --- a/module-template.h +++ /dev/null @@ -1,481 +0,0 @@ -/* module-template.h - * This header contains macros that can be used to implement the - * plumbing of modules. - * - * File begun on 2007-07-25 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef MODULE_TEMPLATE_H_INCLUDED -#define MODULE_TEMPLATE_H_INCLUDED 1 - -#include "modules.h" -#include "obj.h" -#include "objomsr.h" -#include "threads.h" - -/* macro to define standard output-module static data members - */ -#define DEF_MOD_STATIC_DATA \ - static __attribute__((unused)) rsRetVal (*omsdRegCFSLineHdlr)(); - -#define DEF_OMOD_STATIC_DATA \ - DEF_MOD_STATIC_DATA \ - DEFobjCurrIf(obj) -#define DEF_IMOD_STATIC_DATA \ - DEF_MOD_STATIC_DATA \ - DEFobjCurrIf(obj) -#define DEF_LMOD_STATIC_DATA \ - DEF_MOD_STATIC_DATA - - -/* Macro to define the module type. Each module can only have a single type. If - * a module provides multiple types, several separate modules must be created which - * then should share a single library containing the majority of code. This macro - * must be present in each module. -- rgerhards, 2007-12-14 - */ -#define MODULE_TYPE(x)\ -static rsRetVal modGetType(eModType_t *modType) \ - { \ - *modType = x; \ - return RS_RET_OK;\ - } - -#define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN) -#define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT) -#define MODULE_TYPE_LIB \ - DEF_LMOD_STATIC_DATA \ - MODULE_TYPE(eMOD_LIB) - -/* macro to define a unique module id. This must be able to fit in a void*. The - * module id must be unique inside a running rsyslogd application. It is used to - * track ownership of several objects. Most importantly, when the module is - * unloaded the module id value is used to find what needs to be destroyed. - * We currently use a pointer to modExit() as the module id. This sounds to be - * reasonable save, as each module must have this entry point AND there is no valid - * reason for twice this entry point being in memory. - * rgerhards, 2007-11-21 - */ -#define STD_LOADABLE_MODULE_ID ((void*) modExit) - - -/* macro to implement the "modGetID()" interface function - * rgerhards 2007-11-21 - */ -#define DEFmodGetID \ -static rsRetVal modGetID(void **pID) \ - { \ - *pID = STD_LOADABLE_MODULE_ID;\ - return RS_RET_OK;\ - } - -/* to following macros are used to generate function headers and standard - * functionality. It works as follows (described on the sample case of - * createInstance()): - * - * BEGINcreateInstance - * ... custom variable definitions (on stack) ... (if any) - * CODESTARTcreateInstance - * ... custom code ... (if any) - * ENDcreateInstance - */ - -/* createInstance() - */ -#define BEGINcreateInstance \ -static rsRetVal createInstance(instanceData **ppData)\ - {\ - DEFiRet; /* store error code here */\ - instanceData *pData; /* use this to point to data elements */ - -#define CODESTARTcreateInstance \ - if((pData = calloc(1, sizeof(instanceData))) == NULL) {\ - *ppData = NULL;\ - ENDfunc \ - return RS_RET_OUT_OF_MEMORY;\ - } - -#define ENDcreateInstance \ - *ppData = pData;\ - RETiRet;\ -} - -/* freeInstance() - * This is the cleanup function for the module instance. It is called immediately before - * the module instance is destroyed (unloaded). The module should do any cleanup - * here, e.g. close file, free instantance heap memory and the like. Control will - * not be passed back to the module once this function is finished. Keep in mind, - * however, that other instances may still be loaded and used. So do not destroy - * anything that may be used by another instance. If you have such a ressource, you - * currently need to do the instance counting yourself. - */ -#define BEGINfreeInstance \ -static rsRetVal freeInstance(void* pModData)\ -{\ - DEFiRet;\ - instanceData *pData; - -#define CODESTARTfreeInstance \ - pData = (instanceData*) pModData; - -#define ENDfreeInstance \ - if(pData != NULL)\ - free(pData); /* we need to free this in any case */\ - RETiRet;\ -} - -/* isCompatibleWithFeature() - */ -#define BEGINisCompatibleWithFeature \ -static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\ -{\ - rsRetVal iRet = RS_RET_INCOMPATIBLE; \ - BEGINfunc - -#define CODESTARTisCompatibleWithFeature - -#define ENDisCompatibleWithFeature \ - RETiRet;\ -} - -/* doAction() - */ -#define BEGINdoAction \ -static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __attribute__((unused)) iMsgOpts, instanceData __attribute__((unused)) *pData)\ -{\ - DEFiRet; - -#define CODESTARTdoAction \ - /* ppString may be NULL if the output module requested no strings */ - -#define ENDdoAction \ - RETiRet;\ -} - - -/* dbgPrintInstInfo() - * Extra comments: - * Print debug information about this instance. - */ -#define BEGINdbgPrintInstInfo \ -static rsRetVal dbgPrintInstInfo(void *pModData)\ -{\ - DEFiRet;\ - instanceData *pData = NULL; - -#define CODESTARTdbgPrintInstInfo \ - pData = (instanceData*) pModData; - -#define ENDdbgPrintInstInfo \ - RETiRet;\ -} - - -/* parseSelectorAct() - * Extra comments: - * try to process a selector action line. Checks if the action - * applies to this module and, if so, processed it. If not, it - * is left untouched. The driver will then call another module. - * On exit, ppModData must point to instance data. Also, a string - * request object must be created and filled. A macro is defined - * for that. - * For the most usual case, we have defined a macro below. - * If more than one string is requested, the macro can be used together - * with own code that overwrites the entry count. In this case, the - * macro must come before the own code. It is recommended to be - * placed right after CODESTARTparseSelectorAct. - */ -#define BEGINparseSelectorAct \ -static rsRetVal parseSelectorAct(uchar **pp, void **ppModData, omodStringRequest_t **ppOMSR)\ -{\ - DEFiRet;\ - uchar *p;\ - instanceData *pData = NULL; - -#define CODESTARTparseSelectorAct \ - assert(pp != NULL);\ - assert(ppModData != NULL);\ - assert(ppOMSR != NULL);\ - p = *pp; - -#define CODE_STD_STRING_REQUESTparseSelectorAct(NumStrReqEntries) \ - CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries)); - -#define CODE_STD_FINALIZERparseSelectorAct \ -finalize_it:\ - if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {\ - *ppModData = pData;\ - *pp = p;\ - } else {\ - /* cleanup, we failed */\ - if(*ppOMSR != NULL) {\ - OMSRdestruct(*ppOMSR);\ - *ppOMSR = NULL;\ - }\ - if(pData != NULL) {\ - freeInstance(pData);\ - } \ - } - -#define ENDparseSelectorAct \ - RETiRet;\ -} - - -/* tryResume() - * This entry point is called to check if a module can resume operations. This - * happens when a module requested that it be suspended. In suspended state, - * the engine periodically tries to resume the module. If that succeeds, normal - * processing continues. If not, the module will not be called unless a - * tryResume() call succeeds. - * Returns RS_RET_OK, if resumption succeeded, RS_RET_SUSPENDED otherwise - * rgerhard, 2007-08-02 - */ -#define BEGINtryResume \ -static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\ -{\ - DEFiRet; - -#define CODESTARTtryResume \ - assert(pData != NULL); - -#define ENDtryResume \ - RETiRet;\ -} - - - -/* queryEtryPt() - */ -#define BEGINqueryEtryPt \ -DEFmodGetID \ -static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ -{\ - DEFiRet; - -#define CODESTARTqueryEtryPt \ - if((name == NULL) || (pEtryPoint == NULL)) {\ - ENDfunc \ - return RS_RET_PARAM_ERROR;\ - } \ - *pEtryPoint = NULL; - -#define ENDqueryEtryPt \ - if(iRet == RS_RET_OK)\ - if(*pEtryPoint == NULL) { \ - dbgprintf("entry point '%s' not present in module\n", name); \ - iRet = RS_RET_MODULE_ENTRY_POINT_NOT_FOUND;\ - } \ - RETiRet;\ -} - -/* the following definition is the standard block for queryEtryPt for all types - * of modules. It should be included in any module, and typically is so by calling - * the module-type specific macros. - */ -#define CODEqueryEtryPt_STD_MOD_QUERIES \ - if(!strcmp((char*) name, "modExit")) {\ - *pEtryPoint = modExit;\ - } else if(!strcmp((char*) name, "modGetID")) {\ - *pEtryPoint = modGetID;\ - } else if(!strcmp((char*) name, "getType")) {\ - *pEtryPoint = modGetType;\ - } - -/* the following definition is the standard block for queryEtryPt for output - * modules. This can be used if no specific handling (e.g. to cover version - * differences) is needed. - */ -#define CODEqueryEtryPt_STD_OMOD_QUERIES \ - CODEqueryEtryPt_STD_MOD_QUERIES \ - else if(!strcmp((char*) name, "doAction")) {\ - *pEtryPoint = doAction;\ - } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\ - *pEtryPoint = dbgPrintInstInfo;\ - } else if(!strcmp((char*) name, "freeInstance")) {\ - *pEtryPoint = freeInstance;\ - } else if(!strcmp((char*) name, "parseSelectorAct")) {\ - *pEtryPoint = parseSelectorAct;\ - } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ - *pEtryPoint = isCompatibleWithFeature;\ - } else if(!strcmp((char*) name, "tryResume")) {\ - *pEtryPoint = tryResume;\ - } - -/* the following definition is the standard block for queryEtryPt for INPUT - * modules. This can be used if no specific handling (e.g. to cover version - * differences) is needed. - */ -#define CODEqueryEtryPt_STD_IMOD_QUERIES \ - CODEqueryEtryPt_STD_MOD_QUERIES \ - else if(!strcmp((char*) name, "runInput")) {\ - *pEtryPoint = runInput;\ - } else if(!strcmp((char*) name, "willRun")) {\ - *pEtryPoint = willRun;\ - } else if(!strcmp((char*) name, "afterRun")) {\ - *pEtryPoint = afterRun;\ - } - -/* the following definition is the standard block for queryEtryPt for LIBRARY - * modules. This can be used if no specific handling (e.g. to cover version - * differences) is needed. - */ -#define CODEqueryEtryPt_STD_LIB_QUERIES \ - CODEqueryEtryPt_STD_MOD_QUERIES - -/* modInit() - * This has an extra parameter, which is the specific name of the modInit - * function. That is needed for built-in modules, which must have unique - * names in order to link statically. Please note that this is alwaysy only - * the case with modInit() and NO other entry point. The reason is that only - * modInit() is visible form a linker/loader point of view. All other entry - * points are passed via rsyslog-internal query functions and are defined - * static inside the modules source. This is an important concept, as it allows - * us to support different interface versions within a single module. (Granted, - * we do not currently have different interface versions, so we can not put - * it to a test - but our firm believe is that we can do all abstraction needed...) - * - * Extra Comments: - * initialize the module - * - * Later, much more must be done. So far, we only return a pointer - * to the queryEtryPt() function - * TODO: do interface version checking & handshaking - * iIfVersRequetsed is the version of the interface specification that the - * caller would like to see being used. ipIFVersProvided is what we - * decide to provide. - * rgerhards, 2007-11-21: see modExit() comment below for important information - * on the need to initialize static data with code. modInit() may be called on a - * cached, left-in-memory copy of a previous incarnation. - */ -#define BEGINmodInit(uniqName) \ -rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\ -{\ - DEFiRet; \ - rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf); - -#define CODESTARTmodInit \ - assert(pHostQueryEtryPt != NULL);\ - iRet = pHostQueryEtryPt((uchar*)"objGetObjInterface", &pObjGetObjInterface); \ - if((iRet != RS_RET_OK) || (pQueryEtryPt == NULL) || (ipIFVersProvided == NULL) || (pObjGetObjInterface == NULL)) { \ - ENDfunc \ - return (iRet == RS_RET_OK) ? RS_RET_PARAM_ERROR : iRet; \ - } \ - /* now get the obj interface so that we can access other objects */ \ - CHKiRet(pObjGetObjInterface(&obj)); - -#define ENDmodInit \ -finalize_it:\ - *pQueryEtryPt = queryEtryPt;\ - RETiRet;\ -} - - -/* definitions for host API queries */ -#define CODEmodInit_QueryRegCFSLineHdlr \ - CHKiRet(pHostQueryEtryPt((uchar*)"regCfSysLineHdlr", &omsdRegCFSLineHdlr)); - -#endif /* #ifndef MODULE_TEMPLATE_H_INCLUDED */ - -/* modExit() - * This is the counterpart to modInit(). It destroys a module and makes it ready for - * unloading. It is similiar to freeInstance() for the instance data. Please note that - * this entry point needs to free any module-globale data structures and registrations. - * For example, the CfSysLineHandlers a module has registered need to be unregistered - * here. This entry point is only called immediately before unloading of the module. So - * it is likely to be destroyed. HOWEVER, the caller may decide to keep the module cached. - * So a module must never assume that it is actually destroyed. A call to modInit() may - * happen immediately after modExit(). So a module can NOT assume that static data elements - * are being re-initialized by the loader - this must always be done by module code itself. - * It is suggested to do this in modInit(). - rgerhards, 2007-11-21 - */ -#define BEGINmodExit \ -static rsRetVal modExit(void)\ -{\ - DEFiRet; - -#define CODESTARTmodExit - -#define ENDmodExit \ - RETiRet;\ -} - - -/* runInput() - * This is the main function for input modules. It is used to gather data from the - * input source and submit it to the message queue. Each runInput() instance has its own - * thread. This is handled by the rsyslog engine. It needs to spawn off new threads only - * if there is a module-internal need to do so. - */ -#define BEGINrunInput \ -static rsRetVal runInput(thrdInfo_t __attribute__((unused)) *pThrd)\ -{\ - DEFiRet; - -#define CODESTARTrunInput \ - dbgSetThrdName((uchar*)__FILE__); /* we need to provide something better later */ - -#define ENDrunInput \ - RETiRet;\ -} - - -/* willRun() - * This is a function that will be replaced in the longer term. It is used so - * that a module can tell the caller if it will run or not. This is to be replaced - * when we introduce input module instances. However, these require config syntax - * changes and I may (or may not... ;)) hold that until another config file - * format is available. -- rgerhards, 2007-12-17 - * returns RS_RET_NO_RUN if it will not run (RS_RET_OK or error otherwise) - */ -#define BEGINwillRun \ -static rsRetVal willRun(void)\ -{\ - DEFiRet; - -#define CODESTARTwillRun - -#define ENDwillRun \ - RETiRet;\ -} - - -/* afterRun() - * This function is called after an input module has been run and its thread has - * been terminated. It shall do any necessary cleanup. - * This is expected to evolve into a freeInstance type of call once the input module - * interface evolves to support multiple instances. - * rgerhards, 2007-12-17 - */ -#define BEGINafterRun \ -static rsRetVal afterRun(void)\ -{\ - DEFiRet; - -#define CODESTARTafterRun - -#define ENDafterRun \ - RETiRet;\ -} - - -/* - * vi:set ai: - */ diff --git a/modules.c b/modules.c deleted file mode 100644 index 32a71c0c..00000000 --- a/modules.c +++ /dev/null @@ -1,802 +0,0 @@ -/* modules.c - * This is the implementation of syslogd modules object. - * This object handles plug-ins and build-in modules of all kind. - * - * Modules are reference-counted. Anyone who access a module must call - * Use() before any function is accessed and Release() when he is done. - * When the reference count reaches 0, rsyslog unloads the module (that - * may be changed in the future to cache modules). Rsyslog does NOT - * unload modules with a reference count > 0, even if the unload - * method is called! - * - * File begun on 2007-07-22 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#ifdef OS_BSD -# include "libgen.h" -#endif - -#include /* TODO: replace this with the libtools equivalent! */ - -#include -#include - -#include "syslogd.h" -#include "cfsysline.h" -#include "modules.h" -#include "errmsg.h" - -/* static data */ -DEFobjStaticHelpers -DEFobjCurrIf(errmsg) - -static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */ -static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */ - -/* config settings */ -uchar *pModDir = NULL; /* read-only after startup */ - - -#ifdef DEBUG -/* we add some home-grown support to track our users (and detect who does not free us). In - * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 - */ - -/* add a user to the current list of users (always at the root) */ -static void -modUsrAdd(modInfo_t *pThis, char *pszUsr) -{ - modUsr_t *pUsr; - - BEGINfunc - if((pUsr = calloc(1, sizeof(modUsr_t))) == NULL) - goto finalize_it; - - if((pUsr->pszFile = strdup(pszUsr)) == NULL) { - free(pUsr); - goto finalize_it; - } - - if(pThis->pModUsrRoot != NULL) { - pUsr->pNext = pThis->pModUsrRoot; - } - pThis->pModUsrRoot = pUsr; - -finalize_it: - ENDfunc; -} - - -/* remove a user from the current user list - * rgerhards, 2008-03-11 - */ -static void -modUsrDel(modInfo_t *pThis, char *pszUsr) -{ - modUsr_t *pUsr; - modUsr_t *pPrev = NULL; - - for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { - if(!strcmp(pUsr->pszFile, pszUsr)) - break; - else - pPrev = pUsr; - } - - if(pUsr == NULL) { - dbgprintf("oops - tried to delete user %s from module %s and it wasn't registered as one...\n", - pszUsr, pThis->pszName); - } else { - if(pPrev == NULL) { - /* This was at the root! */ - pThis->pModUsrRoot = pUsr->pNext; - } else { - pPrev->pNext = pUsr->pNext; - } - /* free ressources */ - free(pUsr->pszFile); - free(pUsr); - pUsr = NULL; /* just to make sure... */ - } -} - - -/* print a short list all all source files using the module in question - * rgerhards, 2008-03-11 - */ -static void -modUsrPrint(modInfo_t *pThis) -{ - modUsr_t *pUsr; - - for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { - dbgprintf("\tmodule %s is currently in use by file %s\n", - pThis->pszName, pUsr->pszFile); - } -} - - -/* print all loaded modules and who is accessing them. This is primarily intended - * to be called at end of run to detect "module leaks" and who is causing them. - * rgerhards, 2008-03-11 - */ -//static void -void -modUsrPrintAll(void) -{ - modInfo_t *pMod; - - BEGINfunc - for(pMod = pLoadedModules ; pMod != NULL ; pMod = pMod->pNext) { - dbgprintf("printing users of loadable module %s, refcount %u, ptr %p, type %d\n", pMod->pszName, pMod->uRefCnt, pMod, pMod->eType); - modUsrPrint(pMod); - } - ENDfunc -} - -#endif /* #ifdef DEBUG */ - - -/* Construct a new module object - */ -static rsRetVal moduleConstruct(modInfo_t **pThis) -{ - modInfo_t *pNew; - - if((pNew = calloc(1, sizeof(modInfo_t))) == NULL) - return RS_RET_OUT_OF_MEMORY; - - /* OK, we got the element, now initialize members that should - * not be zero-filled. - */ - - *pThis = pNew; - return RS_RET_OK; -} - - -/* Destructs a module object. The object must not be linked to the - * linked list of modules. Please note that all other dependencies on this - * modules must have been removed before (e.g. CfSysLineHandlers!) - */ -static void moduleDestruct(modInfo_t *pThis) -{ - assert(pThis != NULL); - if(pThis->pszName != NULL) - free(pThis->pszName); - if(pThis->pModHdlr != NULL) { -# ifdef VALGRIND -# warning "dlclose disabled for valgrind" -# else - dlclose(pThis->pModHdlr); -# endif - } - - free(pThis); -} - - -/* The following function is the queryEntryPoint for host-based entry points. - * Modules may call it to get access to core interface functions. Please note - * that utility functions can be accessed via shared libraries - at least this - * is my current shool of thinking. - * Please note that the implementation as a query interface allows to take - * care of plug-in interface version differences. -- rgerhards, 2007-07-31 - */ -static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) -{ - DEFiRet; - - if((name == NULL) || (pEtryPoint == NULL)) - return RS_RET_PARAM_ERROR; - - if(!strcmp((char*) name, "regCfSysLineHdlr")) { - *pEtryPoint = regCfSysLineHdlr; - } else if(!strcmp((char*) name, "objGetObjInterface")) { - *pEtryPoint = objGetObjInterface; - } else { - *pEtryPoint = NULL; /* to be on the safe side */ - ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND); - } - -finalize_it: - RETiRet; -} - - -/* get the name of a module - */ -static uchar *modGetName(modInfo_t *pThis) -{ - return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); -} - - -/* get the state-name of a module. The state name is its name - * together with a short description of the module state (which - * is pulled from the module itself. - * rgerhards, 2007-07-24 - * TODO: the actual state name is not yet pulled - */ -static uchar *modGetStateName(modInfo_t *pThis) -{ - return(modGetName(pThis)); -} - - -/* Add a module to the loaded module linked list - */ -static inline void -addModToList(modInfo_t *pThis) -{ - assert(pThis != NULL); - - if(pLoadedModules == NULL) { - pLoadedModules = pLoadedModulesLast = pThis; - } else { - /* there already exist entries */ - pThis->pPrev = pLoadedModulesLast; - pLoadedModulesLast->pNext = pThis; - pLoadedModulesLast = pThis; - } -} - - -/* Get the next module pointer - this is used to traverse the list. - * The function returns the next pointer or NULL, if there is no next one. - * The last object must be provided to the function. If NULL is provided, - * it starts at the root of the list. Even in this case, NULL may be - * returned - then, the list is empty. - * rgerhards, 2007-07-23 - */ -static modInfo_t *GetNxt(modInfo_t *pThis) -{ - modInfo_t *pNew; - - if(pThis == NULL) - pNew = pLoadedModules; - else - pNew = pThis->pNext; - - return(pNew); -} - - -/* this function is like GetNxt(), but it returns pointers to - * modules of specific type only. As we currently deal just with output modules, - * it is a dummy, to be filled with real code later. - * rgerhards, 2007-07-24 - */ -static modInfo_t *GetNxtType(modInfo_t *pThis, eModType_t rqtdType) -{ - modInfo_t *pMod = pThis; - - do { - pMod = GetNxt(pMod); - } while(!(pMod == NULL || pMod->eType == rqtdType)); /* warning: do ... while() */ - - return pMod; -} - - -/* Prepare a module for unloading. - * This is currently a dummy, to be filled when we have a plug-in - * interface - rgerhards, 2007-08-09 - * rgerhards, 2007-11-21: - * When this function is called, all instance-data must already have - * been destroyed. In the case of output modules, this happens when the - * rule set is being destroyed. When we implement other module types, we - * need to think how we handle it there (and if we have any instance data). - * rgerhards, 2008-03-10: reject unload request if the module has a reference - * count > 0. - */ -static rsRetVal -modPrepareUnload(modInfo_t *pThis) -{ - DEFiRet; - void *pModCookie; - - assert(pThis != NULL); - - if(pThis->uRefCnt > 0) { - dbgprintf("rejecting unload of module '%s' because it has a refcount of %d\n", - pThis->pszName, pThis->uRefCnt); - ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); - } - - CHKiRet(pThis->modGetID(&pModCookie)); - pThis->modExit(); /* tell the module to get ready for unload */ - CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie)); - -finalize_it: - RETiRet; -} - - -/* Add an already-loaded module to the module linked list. This function does - * everything needed to fully initialize the module. - */ -static rsRetVal -doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), uchar *name, void *pModHdlr) -{ - DEFiRet; - modInfo_t *pNew = NULL; - rsRetVal (*modGetType)(eModType_t *pType); - - assert(modInit != NULL); - - if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) { - pNew = NULL; - ABORT_FINALIZE(iRet); - } - - CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); - - if(pNew->iIFVers != CURR_MOD_IF_VERSION) { - ABORT_FINALIZE(RS_RET_MISSING_INTERFACE); - } - - /* We now poll the module to see what type it is. We do this only once as this - * can never change in the lifetime of an module. -- rgerhards, 2007-12-14 - */ - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getType", &modGetType)); - CHKiRet((iRet = (*modGetType)(&pNew->eType)) != RS_RET_OK); - dbgprintf("module of type %d being loaded.\n", pNew->eType); - - /* OK, we know we can successfully work with the module. So we now fill the - * rest of the data elements. First we load the interfaces common to all - * module types. - */ - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)); - - /* ... and now the module-specific interfaces */ - switch(pNew->eType) { - case eMOD_IN: - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"runInput", &pNew->mod.im.runInput)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"willRun", &pNew->mod.im.willRun)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"afterRun", &pNew->mod.im.afterRun)); - break; - case eMOD_OUT: - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", &pNew->dbgPrintInstInfo)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature)); - CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)); - break; - case eMOD_LIB: - break; - } - - pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */ - pNew->pModHdlr = pModHdlr; - /* TODO: take this from module */ - if(pModHdlr == NULL) - pNew->eLinkType = eMOD_LINK_STATIC; - else - pNew->eLinkType = eMOD_LINK_DYNAMIC_LOADED; - - /* we initialized the structure, now let's add it to the linked list of modules */ - addModToList(pNew); - -finalize_it: - if(iRet != RS_RET_OK) { - if(pNew != NULL) - moduleDestruct(pNew); - } - - RETiRet; -} - -/* Print loaded modules. This is more or less a - * debug or test aid, but anyhow I think it's worth it... - * This only works if the dbgprintf() subsystem is initialized. - * TODO: update for new input modules! - */ -static void modPrintList(void) -{ - modInfo_t *pMod; - - pMod = GetNxt(NULL); - while(pMod != NULL) { - dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ", - (char*) modGetName(pMod), pMod->iIFVers); - dbgprintf("type="); - switch(pMod->eType) { - case eMOD_OUT: - dbgprintf("output"); - break; - case eMOD_IN: - dbgprintf("input"); - break; - case eMOD_LIB: - dbgprintf("library"); - break; - } - dbgprintf(" module.\n"); - dbgprintf("Entry points:\n"); - dbgprintf("\tqueryEtryPt: 0x%lx\n", (unsigned long) pMod->modQueryEtryPt); - dbgprintf("\tdoAction: 0x%lx\n", (unsigned long) pMod->mod.om.doAction); - dbgprintf("\tparseSelectorAct: 0x%lx\n", (unsigned long) pMod->mod.om.parseSelectorAct); - dbgprintf("\tdbgPrintInstInfo: 0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo); - dbgprintf("\tfreeInstance: 0x%lx\n", (unsigned long) pMod->freeInstance); - dbgprintf("\n"); - pMod = GetNxt(pMod); /* done, go next */ - } -} - - -/* unlink and destroy a module. The caller must provide a pointer to the module - * itself as well as one to its immediate predecessor. - * rgerhards, 2008-02-26 - */ -static rsRetVal -modUnlinkAndDestroy(modInfo_t **ppThis) -{ - DEFiRet; - modInfo_t *pThis; - - assert(ppThis != NULL); - pThis = *ppThis; - assert(pThis != NULL); - - /* first check if we are permitted to unload */ - if(pThis->eType == eMOD_LIB) { - if(pThis->uRefCnt > 0) { - dbgprintf("module %s NOT unloaded because it still has a refcount of %u\n", - pThis->pszName, pThis->uRefCnt); -# ifdef DEBUG - //modUsrPrintAll(); -# endif - ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); - } - } - - /* we need to unlink the module before we can destruct it -- rgerhards, 2008-02-26 */ - if(pThis->pPrev == NULL) { - /* module is root, so we need to set a new root */ - pLoadedModules = pThis->pNext; - } else { - pThis->pPrev->pNext = pThis->pNext; - } - - if(pThis->pNext == NULL) { - pLoadedModulesLast = pThis->pPrev; - } else { - pThis->pNext->pPrev = pThis->pPrev; - } - - /* finally, we are ready for the module to go away... */ - dbgprintf("Unloading module %s\n", modGetName(pThis)); - CHKiRet(modPrepareUnload(pThis)); - *ppThis = pThis->pNext; - - moduleDestruct(pThis); - -finalize_it: - RETiRet; -} - - -/* unload all loaded modules of a specific type (use eMOD_ALL if you want to - * unload all module types). The unload happens only if the module is no longer - * referenced. So some modules may survive this call. - * rgerhards, 2008-03-11 - */ -static rsRetVal -modUnloadAndDestructAll(eModLinkType_t modLinkTypesToUnload) -{ - DEFiRet; - modInfo_t *pModCurr; /* module currently being processed */ - - pModCurr = GetNxt(NULL); - while(pModCurr != NULL) { - if(modLinkTypesToUnload == eMOD_LINK_ALL || pModCurr->eLinkType == modLinkTypesToUnload) { - if(modUnlinkAndDestroy(&pModCurr) == RS_RET_MODULE_STILL_REFERENCED) { - pModCurr = GetNxt(pModCurr); - } - /* Note: if the module was successfully unloaded, it has updated the - * pModCurr pointer to the next module. So we do NOT need to advance - * to the next module on successful unload. - */ - } else { - pModCurr = GetNxt(pModCurr); - } - } - -# ifdef DEBUG - if(pLoadedModules != NULL) { - dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); - modUsrPrintAll(); - } -# endif - - RETiRet; -} - - -/* load a module and initialize it, based on doModLoad() from conf.c - * rgerhards, 2008-03-05 - * varmojfekoj added support for dynamically loadable modules on 2007-08-13 - * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is - * called below. This is ok because modules are currently only loaded during - * configuration file processing, which is executed on a single thread. Should we - * change that design at any stage (what is unlikely), we need to find a - * replacement. - */ -static rsRetVal -Load(uchar *pModName) -{ - DEFiRet; - - size_t iPathLen, iModNameLen; - uchar szPath[PATH_MAX]; - uchar *pModNameCmp; - int bHasExtension; - void *pModHdlr, *pModInit; - modInfo_t *pModInfo; - - assert(pModName != NULL); - dbgprintf("Requested to load module '%s'\n", pModName); - - iModNameLen = strlen((char *) pModName); - if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) { - iModNameLen -= 3; - bHasExtension = TRUE; - } else - bHasExtension = FALSE; - - pModInfo = GetNxt(NULL); - while(pModInfo != NULL) { - if(!strncmp((char *) pModName, (char *) (pModNameCmp = modGetName(pModInfo)), iModNameLen) && - (!*(pModNameCmp + iModNameLen) || !strcmp((char *) pModNameCmp + iModNameLen, ".so"))) { - dbgprintf("Module '%s' already loaded\n", pModName); - ABORT_FINALIZE(RS_RET_OK); - } - pModInfo = GetNxt(pModInfo); - } - - /* now build our load module name */ - if(*pModName == '/') { - *szPath = '\0'; /* we do not need to append the path - its already in the module name */ - iPathLen = 0; - } else { - *szPath = '\0'; - strncat((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath) - 1); - iPathLen = strlen((char*) szPath); - if((szPath[iPathLen - 1] != '/')) { - if((iPathLen <= sizeof(szPath) - 2)) { - szPath[iPathLen++] = '/'; - szPath[iPathLen] = '\0'; - } else { - errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); - ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); - } - } - } - - /* ... add actual name ... */ - strncat((char *) szPath, (char *) pModName, sizeof(szPath) - iPathLen - 1); - - /* now see if we have an extension and, if not, append ".so" */ - if(!bHasExtension) { - /* we do not have an extension and so need to add ".so" - * TODO: I guess this is highly importable, so we should change the - * algo over time... -- rgerhards, 2008-03-05 - */ - /* ... so now add the extension */ - strncat((char *) szPath, ".so", sizeof(szPath) - strlen((char*) szPath) - 1); - iPathLen += 3; - } - - if(iPathLen + strlen((char*) pModName) >= sizeof(szPath)) { - errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); - ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); - } - - /* complete load path constructed, so ... GO! */ - dbgprintf("loading module '%s'\n", szPath); - if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { - errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlopen: %s\n", szPath, dlerror()); - ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN); - } - if(!(pModInit = dlsym(pModHdlr, "modInit"))) { - errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlsym: %s\n", szPath, dlerror()); - dlclose(pModHdlr); - ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT); - } - if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) { - errmsg.LogError(NO_ERRCODE, "could not load module '%s', rsyslog error %d\n", szPath, iRet); - dlclose(pModHdlr); - ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); - } - -finalize_it: - RETiRet; -} - - -/* set the default module load directory. A NULL value may be provided, in - * which case any previous value is deleted but no new one set. The caller-provided - * string is duplicated. If it needs to be freed, that's the caller's duty. - * rgerhards, 2008-03-07 - */ -static rsRetVal -SetModDir(uchar *pszModDir) -{ - DEFiRet; - - dbgprintf("setting default module load directory '%s'\n", pszModDir); - if(pModDir != NULL) { - free(pModDir); - } - - pModDir = (uchar*) strdup((char*)pszModDir); - - RETiRet; -} - - -/* Reference-Counting object access: add 1 to the current reference count. Must be - * called by anyone interested in using a module. -- rgerhards, 20080-03-10 - */ -static rsRetVal -Use(char *srcFile, modInfo_t *pThis) -{ - DEFiRet; - - assert(pThis != NULL); - pThis->uRefCnt++; - dbgprintf("source file %s requested reference for module '%s', reference count now %u\n", - srcFile, pThis->pszName, pThis->uRefCnt); - -# ifdef DEBUG - modUsrAdd(pThis, srcFile); -# endif - - RETiRet; - -} - - -/* Reference-Counting object access: subract one from the current refcount. Must - * by called by anyone who no longer needs a module. If count reaches 0, the - * module is unloaded. -- rgerhards, 20080-03-10 - */ -static rsRetVal -Release(char *srcFile, modInfo_t **ppThis) -{ - DEFiRet; - modInfo_t *pThis; - - assert(ppThis != NULL); - pThis = *ppThis; - assert(pThis != NULL); - if(pThis->uRefCnt == 0) { - /* oops, we are already at 0? */ - dbgprintf("internal error: module '%s' already has a refcount of 0 (released by %s)!\n", - pThis->pszName, srcFile); - } else { - --pThis->uRefCnt; - dbgprintf("file %s released module '%s', reference count now %u\n", - srcFile, pThis->pszName, pThis->uRefCnt); -# ifdef DEBUG - modUsrDel(pThis, srcFile); - modUsrPrint(pThis); -# endif - } - - if(pThis->uRefCnt == 0) { - /* we have a zero refcount, so we must unload the module */ - dbgprintf("module '%s' has zero reference count, unloading...\n", pThis->pszName); - modUnlinkAndDestroy(&pThis); - /* we must NOT do a *ppThis = NULL, because ppThis now points into freed memory! - * If in doubt, see obj.c::ReleaseObj() for how we are called. - */ - } - - RETiRet; - -} - - -/* exit our class - * rgerhards, 2008-03-11 - */ -BEGINObjClassExit(module, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ -CODESTARTObjClassExit(module) - /* release objects we no longer need */ - objRelease(errmsg, CORE_COMPONENT); - -# ifdef DEBUG - modUsrPrintAll(); /* debug aid - TODO: integrate with debug.c, at least the settings! */ -# endif -ENDObjClassExit(module) - - -/* queryInterface function - * rgerhards, 2008-03-05 - */ -BEGINobjQueryInterface(module) -CODESTARTobjQueryInterface(module) - if(pIf->ifVersion != moduleCURR_IF_VERSION) { /* check for current version, increment on each change */ - ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); - } - - /* ok, we have the right interface, so let's fill it - * Please note that we may also do some backwards-compatibility - * work here (if we can support an older interface version - that, - * of course, also affects the "if" above). - */ - pIf->GetNxt = GetNxt; - pIf->GetNxtType = GetNxtType; - pIf->GetName = modGetName; - pIf->GetStateName = modGetStateName; - pIf->PrintList = modPrintList; - pIf->UnloadAndDestructAll = modUnloadAndDestructAll; - pIf->doModInit = doModInit; - pIf->SetModDir = SetModDir; - pIf->Load = Load; - pIf->Use = Use; - pIf->Release = Release; -finalize_it: -ENDobjQueryInterface(module) - - -/* Initialize our class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-03-05 - */ -BEGINAbstractObjClassInit(module, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ - uchar *pModPath; - - /* use any module load path specified in the environment */ - if((pModPath = (uchar*) getenv("RSYSLOG_MODDIR")) != NULL) { - SetModDir(pModPath); - } - - /* now check if another module path was set via the command line (-M) - * if so, that overrides the environment. Please note that we must use - * a global setting here because the command line parser can NOT call - * into the module object, because it is not initialized at that point. So - * instead a global setting is changed and we pick it up as soon as we - * initialize -- rgerhards, 2008-04-04 - */ - if(glblModPath != NULL) { - SetModDir(glblModPath); - } - - /* request objects we use */ - CHKiRet(objUse(errmsg, CORE_COMPONENT)); -ENDObjClassInit(module) - -/* vi:set ai: - */ diff --git a/modules.h b/modules.h deleted file mode 100644 index a8371d05..00000000 --- a/modules.h +++ /dev/null @@ -1,150 +0,0 @@ -/* modules.h - * - * Definition for build-in and plug-ins module handler. This file is the base - * for all dynamically loadable module support. In theory, in v3 all modules - * are dynamically loaded, in practice we currently do have a few build-in - * once. This may become removed. - * - * The loader keeps track of what is loaded. For library modules, it is also - * used to find objects (libraries) and to obtain the queryInterface function - * for them. A reference count is maintened for libraries, so that they are - * unloaded only when nobody still accesses them. - * - * File begun on 2007-07-22 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef MODULES_H_INCLUDED -#define MODULES_H_INCLUDED 1 - -#include "objomsr.h" -#include "threads.h" - - -/* the following define defines the current version of the module interface. - * It can be used by any module which want's to simply prevent version conflicts - * and does not intend to do specific old-version emulations. - * rgerhards, 2008-03-04 - * version 3 adds modInfo_t ptr to call of modInit -- rgerhards, 2008-03-10 - * version 4 removes needUDPSocket OM callback -- rgerhards, 2008-03-22 - */ -#define CURR_MOD_IF_VERSION 4 - -typedef enum eModType_ { - eMOD_IN, /* input module */ - eMOD_OUT, /* output module */ - eMOD_LIB /* library module - this module provides one or many interfaces */ -} eModType_t; - - -#ifdef DEBUG -typedef struct modUsr_s { - struct modUsr_s *pNext; - char *pszFile; -} modUsr_t; -#endif - - -/* how is this module linked? */ -typedef enum eModLinkType_ { - eMOD_LINK_STATIC, - eMOD_LINK_DYNAMIC_UNLOADED, /* dynalink module, currently not loaded */ - eMOD_LINK_DYNAMIC_LOADED, /* dynalink module, currently loaded */ - eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */ -} eModLinkType_t; - -typedef struct modInfo_s { - struct modInfo_s *pPrev; /* support for creating a double linked module list */ - struct modInfo_s *pNext; /* support for creating a linked module list */ - int iIFVers; /* Interface version of module */ - eModType_t eType; /* type of this module */ - eModLinkType_t eLinkType; - uchar* pszName; /* printable module name, e.g. for dbgprintf */ - unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */ - /* functions supported by all types of modules */ - rsRetVal (*modInit)(int, int*, rsRetVal(**)()); /* initialize the module */ - /* be sure to support version handshake! */ - rsRetVal (*modQueryEtryPt)(uchar *name, rsRetVal (**EtryPoint)()); /* query entry point addresses */ - rsRetVal (*isCompatibleWithFeature)(syslogFeature); - rsRetVal (*freeInstance)(void*);/* called before termination or module unload */ - rsRetVal (*dbgPrintInstInfo)(void*);/* called before termination or module unload */ - rsRetVal (*tryResume)(void*);/* called to see if module actin can be resumed now */ - rsRetVal (*modExit)(void); /* called before termination or module unload */ - rsRetVal (*modGetID)(void **); /* get its unique ID from module */ - /* below: parse a configuration line - return if processed - * or not. If not, must be parsed to next module. - */ - rsRetVal (*parseConfigLine)(uchar **pConfLine); - /* below: create an instance of this module. Most importantly the module - * can allocate instance memory in this call. - */ - rsRetVal (*createInstance)(); - /* TODO: pass pointer to msg submit function to IM rger, 2007-12-14 */ - union { - struct {/* data for input modules */ - rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */ - rsRetVal (*willRun)(void); /* function to gather input and submit to queue */ - rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */ - } im; - struct {/* data for output modules */ - /* below: perform the configured action - */ - rsRetVal (*doAction)(uchar**, unsigned, void*); - rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**); - } om; - struct { /* data for library modules */ - } fm; - } mod; - void *pModHdlr; /* handler to the dynamic library holding the module */ -# ifdef DEBUG - /* we add some home-grown support to track our users (and detect who does not free us). In - * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 - */ - modUsr_t *pModUsrRoot; -# endif -} modInfo_t; - -/* interfaces */ -BEGINinterface(module) /* name must also be changed in ENDinterface macro! */ - modInfo_t *(*GetNxt)(modInfo_t *pThis); - modInfo_t *(*GetNxtType)(modInfo_t *pThis, eModType_t rqtdType); - uchar *(*GetName)(modInfo_t *pThis); - uchar *(*GetStateName)(modInfo_t *pThis); - rsRetVal (*Use)(char *srcFile, modInfo_t *pThis); /**< must be called before a module is used (ref counting) */ - rsRetVal (*Release)(char *srcFile, modInfo_t **ppThis); /**< release a module (ref counting) */ - void (*PrintList)(void); - rsRetVal (*UnloadAndDestructAll)(eModLinkType_t modLinkTypesToUnload); - rsRetVal (*doModInit)(rsRetVal (*modInit)(), uchar *name, void *pModHdlr); - rsRetVal (*Load)(uchar *name); - rsRetVal (*SetModDir)(uchar *name); -ENDinterface(module) -#define moduleCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ - -/* prototypes */ -PROTOTYPEObj(module); - -/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ -extern uchar *pModDir; /* read-only after startup */ - - -#endif /* #ifndef MODULES_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/msg.c b/msg.c deleted file mode 100644 index 9a12d572..00000000 --- a/msg.c +++ /dev/null @@ -1,2293 +0,0 @@ -/* msg.c - * The msg object. Implementation of all msg-related functions - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include -#include -#include -#define SYSLOG_NAMES -#include -#include -#include -#include "rsyslog.h" -#include "syslogd.h" -#include "srUtils.h" -#include "stringbuf.h" -#include "template.h" -#include "msg.h" -#include "var.h" -#include "datetime.h" -#include "regexp.h" -#include "atomic.h" - -/* static data */ -DEFobjStaticHelpers -DEFobjCurrIf(var) -DEFobjCurrIf(datetime) -DEFobjCurrIf(regexp) - -static syslogCODE rs_prioritynames[] = - { - { "alert", LOG_ALERT }, - { "crit", LOG_CRIT }, - { "debug", LOG_DEBUG }, - { "emerg", LOG_EMERG }, - { "err", LOG_ERR }, - { "error", LOG_ERR }, /* DEPRECATED */ - { "info", LOG_INFO }, - { "none", INTERNAL_NOPRI }, /* INTERNAL */ - { "notice", LOG_NOTICE }, - { "panic", LOG_EMERG }, /* DEPRECATED */ - { "warn", LOG_WARNING }, /* DEPRECATED */ - { "warning", LOG_WARNING }, - { NULL, -1 } - }; - -#ifndef LOG_AUTHPRIV -# define LOG_AUTHPRIV LOG_AUTH -#endif -static syslogCODE rs_facilitynames[] = - { - { "auth", LOG_AUTH }, - { "authpriv", LOG_AUTHPRIV }, - { "cron", LOG_CRON }, - { "daemon", LOG_DAEMON }, -#if defined(LOG_FTP) - {"ftp", LOG_FTP}, -#endif - { "kern", LOG_KERN }, - { "lpr", LOG_LPR }, - { "mail", LOG_MAIL }, - //{ "mark", INTERNAL_MARK }, /* INTERNAL */ - { "news", LOG_NEWS }, - { "security", LOG_AUTH }, /* DEPRECATED */ - { "syslog", LOG_SYSLOG }, - { "user", LOG_USER }, - { "uucp", LOG_UUCP }, - { "local0", LOG_LOCAL0 }, - { "local1", LOG_LOCAL1 }, - { "local2", LOG_LOCAL2 }, - { "local3", LOG_LOCAL3 }, - { "local4", LOG_LOCAL4 }, - { "local5", LOG_LOCAL5 }, - { "local6", LOG_LOCAL6 }, - { "local7", LOG_LOCAL7 }, - { NULL, -1 } - }; - -/* some forward declarations */ -static int getAPPNAMELen(msg_t *pM); - -/* The following functions will support advanced output module - * multithreading, once this is implemented. Currently, we - * include them as hooks only. The idea is that we need to guard - * some msg objects data fields against concurrent access if - * we run on multiple threads. Please note that in any case this - * is not necessary for calls from INPUT modules, because they - * construct the message object and do this serially. Only when - * the message is in the processing queue, multiple threads may - * access a single object. Consequently, there are no guard functions - * for "set" methods, as these are called during input. Only "get" - * functions that modify important structures have them. - * rgerhards, 2007-07-20 - * We now support locked and non-locked operations, depending on - * the configuration of rsyslog. To support this, we use function - * pointers. Initially, we start in non-locked mode. There, all - * locking operations call into dummy functions. When locking is - * enabled, the function pointers are changed to functions doing - * actual work. We also introduced another MsgPrepareEnqueue() function - * which initializes the locking structures, if needed. This is - * necessary because internal messages during config file startup - * processing are always created in non-locking mode. So we can - * not initialize locking structures during constructions. We now - * postpone this until when the message is fully constructed and - * enqueued. Then we know the status of locking. This has a nice - * side effect, and that is that during the initial creation of - * the Msg object no locking needs to be done, which results in better - * performance. -- rgerhards, 2008-01-05 - */ -static void (*funcLock)(msg_t *pMsg); -static void (*funcUnlock)(msg_t *pMsg); -static void (*funcDeleteMutex)(msg_t *pMsg); -void (*funcMsgPrepareEnqueue)(msg_t *pMsg); -#if 1 /* This is a debug aid */ -#define MsgLock(pMsg) funcLock(pMsg) -#define MsgUnlock(pMsg) funcUnlock(pMsg) -#else -#define MsgLock(pMsg) {dbgprintf("line %d\n - ", __LINE__); funcLock(pMsg);; } -#define MsgUnlock(pMsg) {dbgprintf("line %d - ", __LINE__); funcUnlock(pMsg); } -#endif - -/* the next function is a dummy to be used by the looking functions - * when the class is not yet running in an environment where locking - * is necessary. Please note that the need to lock can (and will) change - * during a single run. Typically, this is depending on the operation mode - * of the message queues (which is operator-configurable). -- rgerhards, 2008-01-05 - */ -static void MsgLockingDummy(msg_t __attribute__((unused)) *pMsg) -{ - /* empty be design */ -} - - -/* The following function prepares a message for enqueue into the queue. This is - * where a message may be accessed by multiple threads. This implementation here - * is the version for multiple concurrent acces. It initializes the locking - * structures. - */ -static void MsgPrepareEnqueueLockingCase(msg_t *pThis) -{ - assert(pThis != NULL); - pthread_mutexattr_settype(&pThis->mutAttr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&pThis->mut, &pThis->mutAttr); -} - -/* ... and now the locking and unlocking implementations: */ -static void MsgLockLockingCase(msg_t *pThis) -{ - /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */ - assert(pThis != NULL); - pthread_mutex_lock(&pThis->mut); -} - -static void MsgUnlockLockingCase(msg_t *pThis) -{ - /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */ - assert(pThis != NULL); - pthread_mutex_unlock(&pThis->mut); -} - -/* delete the mutex object on message destruction (locking case) - */ -static void MsgDeleteMutexLockingCase(msg_t *pThis) -{ - assert(pThis != NULL); - pthread_mutex_destroy(&pThis->mut); -} - -/* enable multiple concurrent access on the message object - * This works on a class-wide basis and can bot be undone. - * That is, if it is once enabled, it can not be disabled during - * the same run. When this function is called, no other thread - * must manipulate message objects. Then we would have race conditions, - * but guarding against this is counter-productive because it - * would cost additional time. Plus, it would be a programming error. - * rgerhards, 2008-01-05 - */ -rsRetVal MsgEnableThreadSafety(void) -{ - funcLock = MsgLockLockingCase; - funcUnlock = MsgUnlockLockingCase; - funcMsgPrepareEnqueue = MsgPrepareEnqueueLockingCase; - funcDeleteMutex = MsgDeleteMutexLockingCase; - return RS_RET_OK; -} - -/* end locking functions */ - - -/* "Constructor" for a msg "object". Returns a pointer to - * the new object or NULL if no such object could be allocated. - * An object constructed via this function should only be destroyed - * via "msgDestruct()". - */ -rsRetVal msgConstruct(msg_t **ppThis) -{ - DEFiRet; - msg_t *pM; - - assert(ppThis != NULL); - if((pM = calloc(1, sizeof(msg_t))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - /* initialize members that are non-zero */ - pM->iRefCount = 1; - pM->iSeverity = -1; - pM->iFacility = -1; - datetime.getCurrTime(&(pM->tRcvdAt)); - objConstructSetObjInfo(pM); - - /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/ - - *ppThis = pM; - -finalize_it: - RETiRet; -} - - -BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */ - int currRefCount; -CODESTARTobjDestruct(msg) - /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pM, pM->iRefCount - 1); */ -# ifdef DO_HAVE_ATOMICS - currRefCount = ATOMIC_DEC_AND_FETCH(pThis->iRefCount); -# else - currRefCount = --pThis->iRefCount; -# endif - if(currRefCount == 0) - { - /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */ - if(pThis->pszUxTradMsg != NULL) - free(pThis->pszUxTradMsg); - if(pThis->pszRawMsg != NULL) - free(pThis->pszRawMsg); - if(pThis->pszTAG != NULL) - free(pThis->pszTAG); - if(pThis->pszHOSTNAME != NULL) - free(pThis->pszHOSTNAME); - if(pThis->pszRcvFrom != NULL) - free(pThis->pszRcvFrom); - if(pThis->pszMSG != NULL) - free(pThis->pszMSG); - if(pThis->pszFacility != NULL) - free(pThis->pszFacility); - if(pThis->pszFacilityStr != NULL) - free(pThis->pszFacilityStr); - if(pThis->pszSeverity != NULL) - free(pThis->pszSeverity); - if(pThis->pszSeverityStr != NULL) - free(pThis->pszSeverityStr); - if(pThis->pszRcvdAt3164 != NULL) - free(pThis->pszRcvdAt3164); - if(pThis->pszRcvdAt3339 != NULL) - free(pThis->pszRcvdAt3339); - if(pThis->pszRcvdAt_MySQL != NULL) - free(pThis->pszRcvdAt_MySQL); - if(pThis->pszRcvdAt_PgSQL != NULL) - free(pThis->pszRcvdAt_PgSQL); - if(pThis->pszTIMESTAMP3164 != NULL) - free(pThis->pszTIMESTAMP3164); - if(pThis->pszTIMESTAMP3339 != NULL) - free(pThis->pszTIMESTAMP3339); - if(pThis->pszTIMESTAMP_MySQL != NULL) - free(pThis->pszTIMESTAMP_MySQL); - if(pThis->pszTIMESTAMP_PgSQL != NULL) - free(pThis->pszTIMESTAMP_PgSQL); - if(pThis->pszPRI != NULL) - free(pThis->pszPRI); - if(pThis->pCSProgName != NULL) - rsCStrDestruct(&pThis->pCSProgName); - if(pThis->pCSStrucData != NULL) - rsCStrDestruct(&pThis->pCSStrucData); - if(pThis->pCSAPPNAME != NULL) - rsCStrDestruct(&pThis->pCSAPPNAME); - if(pThis->pCSPROCID != NULL) - rsCStrDestruct(&pThis->pCSPROCID); - if(pThis->pCSMSGID != NULL) - rsCStrDestruct(&pThis->pCSMSGID); - funcDeleteMutex(pThis); - } else { - pThis = NULL; /* tell framework not to destructing the object! */ - } -ENDobjDestruct(msg) - - -/* The macros below are used in MsgDup(). I use macros - * to keep the fuction code somewhat more readyble. It is my - * replacement for inline functions in CPP - */ -#define tmpCOPYSZ(name) \ - if(pOld->psz##name != NULL) { \ - if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ - msgDestruct(&pNew);\ - return NULL;\ - }\ - pNew->iLen##name = pOld->iLen##name;\ - } - -/* copy the CStr objects. - * if the old value is NULL, we do not need to do anything because we - * initialized the new value to NULL via calloc(). - */ -#define tmpCOPYCSTR(name) \ - if(pOld->pCS##name != NULL) {\ - if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ - msgDestruct(&pNew);\ - return NULL;\ - }\ - } -/* Constructs a message object by duplicating another one. - * Returns NULL if duplication failed. We do not need to lock the - * message object here, because a fully-created msg object is never - * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup() - * can never run into a situation where the message object is being - * modified while its content is copied - it's forbidden by definition. - * rgerhards, 2007-07-10 - */ -msg_t* MsgDup(msg_t* pOld) -{ - msg_t* pNew; - - assert(pOld != NULL); - - BEGINfunc - if(msgConstruct(&pNew) != RS_RET_OK) { - return NULL; - } - - /* now copy the message properties */ - pNew->iRefCount = 1; - pNew->iSeverity = pOld->iSeverity; - pNew->iFacility = pOld->iFacility; - pNew->bParseHOSTNAME = pOld->bParseHOSTNAME; - pNew->msgFlags = pOld->msgFlags; - pNew->iProtocolVersion = pOld->iProtocolVersion; - memcpy(&pNew->tRcvdAt, &pOld->tRcvdAt, sizeof(struct syslogTime)); - memcpy(&pNew->tTIMESTAMP, &pOld->tTIMESTAMP, sizeof(struct syslogTime)); - tmpCOPYSZ(Severity); - tmpCOPYSZ(SeverityStr); - tmpCOPYSZ(Facility); - tmpCOPYSZ(FacilityStr); - tmpCOPYSZ(PRI); - tmpCOPYSZ(RawMsg); - tmpCOPYSZ(MSG); - tmpCOPYSZ(UxTradMsg); - tmpCOPYSZ(TAG); - tmpCOPYSZ(HOSTNAME); - tmpCOPYSZ(RcvFrom); - - tmpCOPYCSTR(ProgName); - tmpCOPYCSTR(StrucData); - tmpCOPYCSTR(APPNAME); - tmpCOPYCSTR(PROCID); - tmpCOPYCSTR(MSGID); - - /* we do not copy all other cache properties, as we do not even know - * if they are needed once again. So we let them re-create if needed. - */ - - ENDfunc - return pNew; -} -#undef tmpCOPYSZ -#undef tmpCOPYCSTR - - -/* This method serializes a message object. That means the whole - * object is modified into text form. That text form is suitable for - * later reconstruction of the object by calling MsgDeSerialize(). - * The most common use case for this method is the creation of an - * on-disk representation of the message object. - * We do not serialize the cache properties. We re-create them when needed. - * This saves us a lot of memory. Performance is no concern, as serializing - * is a so slow operation that recration of the caches does not count. Also, - * we do not serialize bParseHOSTNAME, as this is only a helper variable - * during msg construction - and never again used later. - * rgerhards, 2008-01-03 - */ -static rsRetVal MsgSerialize(msg_t *pThis, strm_t *pStrm) -{ - DEFiRet; - - assert(pThis != NULL); - assert(pStrm != NULL); - - CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); - objSerializeSCALAR(pStrm, iProtocolVersion, SHORT); - objSerializeSCALAR(pStrm, iSeverity, SHORT); - objSerializeSCALAR(pStrm, iFacility, SHORT); - objSerializeSCALAR(pStrm, msgFlags, INT); - objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME); - objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); - - objSerializePTR(pStrm, pszRawMsg, PSZ); - objSerializePTR(pStrm, pszMSG, PSZ); - objSerializePTR(pStrm, pszUxTradMsg, PSZ); - objSerializePTR(pStrm, pszTAG, PSZ); - objSerializePTR(pStrm, pszHOSTNAME, PSZ); - objSerializePTR(pStrm, pszRcvFrom, PSZ); - - objSerializePTR(pStrm, pCSStrucData, CSTR); - objSerializePTR(pStrm, pCSAPPNAME, CSTR); - objSerializePTR(pStrm, pCSPROCID, CSTR); - objSerializePTR(pStrm, pCSMSGID, CSTR); - - CHKiRet(obj.EndSerialize(pStrm)); - -finalize_it: - RETiRet; -} - - -/* Increment reference count - see description of the "msg" - * structure for details. As a convenience to developers, - * this method returns the msg pointer that is passed to it. - * It is recommended that it is called as follows: - * - * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer); - */ -msg_t *MsgAddRef(msg_t *pM) -{ - assert(pM != NULL); -# ifdef DO_HAVE_ATOMICS - ATOMIC_INC(pM->iRefCount); -# else - MsgLock(pM); - pM->iRefCount++; - MsgUnlock(pM); -# endif - /* DEV debugging only! dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);*/ - return(pM); -} - - -/* This functions tries to aquire the PROCID from TAG. Its primary use is - * when a legacy syslog message has been received and should be forwarded as - * syslog-protocol (or the PROCID is requested for any other reason). - * In legacy syslog, the PROCID is considered to be the character sequence - * between the first [ and the first ]. This usually are digits only, but we - * do not check that. However, if there is no closing ], we do not assume we - * can obtain a PROCID. Take in mind that not every legacy syslog message - * actually has a PROCID. - * rgerhards, 2005-11-24 - */ -static rsRetVal aquirePROCIDFromTAG(msg_t *pM) -{ - register int i; - DEFiRet; - - assert(pM != NULL); - if(pM->pCSPROCID != NULL) - return RS_RET_OK; /* we are already done ;) */ - - if(getProtocolVersion(pM) != 0) - return RS_RET_OK; /* we can only emulate if we have legacy format */ - - /* find first '['... */ - i = 0; - while((i < pM->iLenTAG) && (pM->pszTAG[i] != '[')) - ++i; - if(!(i < pM->iLenTAG)) - return RS_RET_OK; /* no [, so can not emulate... */ - - ++i; /* skip '[' */ - - /* now obtain the PROCID string... */ - CHKiRet(rsCStrConstruct(&pM->pCSPROCID)); - rsCStrSetAllocIncrement(pM->pCSPROCID, 16); - while((i < pM->iLenTAG) && (pM->pszTAG[i] != ']')) { - CHKiRet(rsCStrAppendChar(pM->pCSPROCID, pM->pszTAG[i])); - ++i; - } - - if(!(i < pM->iLenTAG)) { - /* oops... it looked like we had a PROCID, but now it has - * turned out this is not true. In this case, we need to free - * the buffer and simply return. Note that this is NOT an error - * case! - */ - rsCStrDestruct(&pM->pCSPROCID); - FINALIZE; - } - - /* OK, finaally we could obtain a PROCID. So let's use it ;) */ - CHKiRet(rsCStrFinish(pM->pCSPROCID)); - -finalize_it: - RETiRet; -} - - -/* Parse and set the "programname" for a given MSG object. Programname - * is a BSD concept, it is the tag without any instance-specific information. - * Precisely, the programname is terminated by either (whichever occurs first): - * - end of tag - * - nonprintable character - * - ':' - * - '[' - * - '/' - * The above definition has been taken from the FreeBSD syslogd sources. - * - * The program name is not parsed by default, because it is infrequently-used. - * If it is needed, this function should be called first. It checks if it is - * already set and extracts it, if not. - * A message object must be provided, else a crash will occur. - * rgerhards, 2005-10-19 - */ -static rsRetVal aquireProgramName(msg_t *pM) -{ - DEFiRet; - register int i; - - assert(pM != NULL); - if(pM->pCSProgName == NULL) { - /* ok, we do not yet have it. So let's parse the TAG - * to obtain it. - */ - CHKiRet(rsCStrConstruct(&pM->pCSProgName)); - rsCStrSetAllocIncrement(pM->pCSProgName, 33); - for( i = 0 - ; (i < pM->iLenTAG) && isprint((int) pM->pszTAG[i]) - && (pM->pszTAG[i] != '\0') && (pM->pszTAG[i] != ':') - && (pM->pszTAG[i] != '[') && (pM->pszTAG[i] != '/') - ; ++i) { - CHKiRet(rsCStrAppendChar(pM->pCSProgName, pM->pszTAG[i])); - } - CHKiRet(rsCStrFinish(pM->pCSProgName)); - } -finalize_it: - RETiRet; -} - - -/* This function moves the HOSTNAME inside the message object to the - * TAG. It is a specialised function used to handle the condition when - * a message without HOSTNAME is being processed. The missing HOSTNAME - * is only detected at a later stage, during TAG processing, so that - * we already had set the HOSTNAME property and now need to move it to - * the TAG. Of course, we could do this via a couple of get/set methods, - * but it is far more efficient to do it via this specialised method. - * This is especially important as this can be a very common case, e.g. - * when BSD syslog is acting as a sender. - * rgerhards, 2005-11-10. - */ -void moveHOSTNAMEtoTAG(msg_t *pM) -{ - assert(pM != NULL); - if(pM->pszTAG != NULL) - free(pM->pszTAG); - pM->pszTAG = pM->pszHOSTNAME; - pM->iLenTAG = pM->iLenHOSTNAME; - pM->pszHOSTNAME = NULL; - pM->iLenHOSTNAME = 0; -} - -/* Access methods - dumb & easy, not a comment for each ;) - */ -void setProtocolVersion(msg_t *pM, int iNewVersion) -{ - assert(pM != NULL); - if(iNewVersion != 0 && iNewVersion != 1) { - dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion); - iNewVersion = 0; - } - pM->iProtocolVersion = iNewVersion; -} - -int getProtocolVersion(msg_t *pM) -{ - assert(pM != NULL); - return(pM->iProtocolVersion); -} - -/* note: string is taken from constant pool, do NOT free */ -char *getProtocolVersionString(msg_t *pM) -{ - assert(pM != NULL); - return(pM->iProtocolVersion ? "1" : "0"); -} - -int getMSGLen(msg_t *pM) -{ - return((pM == NULL) ? 0 : pM->iLenMSG); -} - - -char *getRawMsg(msg_t *pM) -{ - if(pM == NULL) - return ""; - else - if(pM->pszRawMsg == NULL) - return ""; - else - return (char*)pM->pszRawMsg; -} - -char *getUxTradMsg(msg_t *pM) -{ - if(pM == NULL) - return ""; - else - if(pM->pszUxTradMsg == NULL) - return ""; - else - return (char*)pM->pszUxTradMsg; -} - -char *getMSG(msg_t *pM) -{ - if(pM == NULL) - return ""; - else - if(pM->pszMSG == NULL) - return ""; - else - return (char*)pM->pszMSG; -} - - -/* Get PRI value in text form */ -char *getPRI(msg_t *pM) -{ - if(pM == NULL) - return ""; - - MsgLock(pM); - if(pM->pszPRI == NULL) { - /* OK, we need to construct it... - * we use a 5 byte buffer - as of - * RFC 3164, it can't be longer. Should it - * still be, snprintf will truncate... - */ - if((pM->pszPRI = malloc(5)) == NULL) return ""; - pM->iLenPRI = snprintf((char*)pM->pszPRI, 5, "%d", - LOG_MAKEPRI(pM->iFacility, pM->iSeverity)); - } - MsgUnlock(pM); - - return (char*)pM->pszPRI; -} - - -/* Get PRI value as integer */ -int getPRIi(msg_t *pM) -{ - assert(pM != NULL); - return (pM->iFacility << 3) + (pM->iSeverity); -} - - -char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) -{ - if(pM == NULL) - return ""; - - switch(eFmt) { - case tplFmtDefault: - MsgLock(pM); - if(pM->pszTIMESTAMP3164 == NULL) { - if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); - } - MsgUnlock(pM); - return(pM->pszTIMESTAMP3164); - case tplFmtMySQLDate: - MsgLock(pM); - if(pM->pszTIMESTAMP_MySQL == NULL) { - if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL, 15); - } - MsgUnlock(pM); - return(pM->pszTIMESTAMP_MySQL); - case tplFmtPgSQLDate: - MsgLock(pM); - if(pM->pszTIMESTAMP_PgSQL == NULL) { - if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL, 21); - } - MsgUnlock(pM); - return(pM->pszTIMESTAMP_PgSQL); - case tplFmtRFC3164Date: - MsgLock(pM); - if(pM->pszTIMESTAMP3164 == NULL) { - if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); - } - MsgUnlock(pM); - return(pM->pszTIMESTAMP3164); - case tplFmtRFC3339Date: - MsgLock(pM); - if(pM->pszTIMESTAMP3339 == NULL) { - if((pM->pszTIMESTAMP3339 = malloc(33)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; /* TODO: check this: can it cause a free() of constant memory?) */ - } - datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339, 33); - } - MsgUnlock(pM); - return(pM->pszTIMESTAMP3339); - } - return "INVALID eFmt OPTION!"; -} - -char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) -{ - if(pM == NULL) - return ""; - - switch(eFmt) { - case tplFmtDefault: - MsgLock(pM); - if(pM->pszRcvdAt3164 == NULL) { - if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); - } - MsgUnlock(pM); - return(pM->pszRcvdAt3164); - case tplFmtMySQLDate: - MsgLock(pM); - if(pM->pszRcvdAt_MySQL == NULL) { - if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL, 15); - } - MsgUnlock(pM); - return(pM->pszRcvdAt_MySQL); - case tplFmtPgSQLDate: - MsgLock(pM); - if(pM->pszRcvdAt_PgSQL == NULL) { - if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL, 21); - } - MsgUnlock(pM); - return(pM->pszRcvdAt_PgSQL); - case tplFmtRFC3164Date: - MsgLock(pM); - if(pM->pszRcvdAt3164 == NULL) { - if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); - } - MsgUnlock(pM); - return(pM->pszRcvdAt3164); - case tplFmtRFC3339Date: - MsgLock(pM); - if(pM->pszRcvdAt3339 == NULL) { - if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { - glblHadMemShortage = 1; - MsgUnlock(pM); - return ""; - } - datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339, 33); - } - MsgUnlock(pM); - return(pM->pszRcvdAt3339); - } - return "INVALID eFmt OPTION!"; -} - - -char *getSeverity(msg_t *pM) -{ - if(pM == NULL) - return ""; - - MsgLock(pM); - if(pM->pszSeverity == NULL) { - /* we use a 2 byte buffer - can only be one digit */ - if((pM->pszSeverity = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenSeverity = - snprintf((char*)pM->pszSeverity, 2, "%d", pM->iSeverity); - } - MsgUnlock(pM); - return((char*)pM->pszSeverity); -} - - -char *getSeverityStr(msg_t *pM) -{ - syslogCODE *c; - int val; - char *name = NULL; - - if(pM == NULL) - return ""; - - MsgLock(pM); - if(pM->pszSeverityStr == NULL) { - for(c = rs_prioritynames, val = pM->iSeverity; c->c_name; c++) - if(c->c_val == val) { - name = c->c_name; - break; - } - if(name == NULL) { - /* we use a 2 byte buffer - can only be one digit */ - if((pM->pszSeverityStr = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenSeverityStr = - snprintf((char*)pM->pszSeverityStr, 2, "%d", pM->iSeverity); - } else { - if((pM->pszSeverityStr = (uchar*) strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenSeverityStr = strlen((char*)name); - } - } - MsgUnlock(pM); - return((char*)pM->pszSeverityStr); -} - -char *getFacility(msg_t *pM) -{ - if(pM == NULL) - return ""; - - MsgLock(pM); - if(pM->pszFacility == NULL) { - /* we use a 12 byte buffer - as of - * syslog-protocol, facility can go - * up to 2^32 -1 - */ - if((pM->pszFacility = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenFacility = - snprintf((char*)pM->pszFacility, 12, "%d", pM->iFacility); - } - MsgUnlock(pM); - return((char*)pM->pszFacility); -} - -char *getFacilityStr(msg_t *pM) -{ - syslogCODE *c; - int val; - char *name = NULL; - - if(pM == NULL) - return ""; - - MsgLock(pM); - if(pM->pszFacilityStr == NULL) { - for(c = rs_facilitynames, val = pM->iFacility << 3; c->c_name; c++) - if(c->c_val == val) { - name = c->c_name; - break; - } - if(name == NULL) { - /* we use a 12 byte buffer - as of - * syslog-protocol, facility can go - * up to 2^32 -1 - */ - if((pM->pszFacilityStr = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenFacilityStr = - snprintf((char*)pM->pszFacilityStr, 12, "%d", val >> 3); - } else { - if((pM->pszFacilityStr = (uchar*)strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } - pM->iLenFacilityStr = strlen((char*)name); - } - } - MsgUnlock(pM); - return((char*)pM->pszFacilityStr); -} - - -/* set flow control state (if not called, the default - NO_DELAY - is used) - * This needs no locking because it is only done while the object is - * not fully constructed (which also means you must not call this - * method after the msg has been handed over to a queue). - * rgerhards, 2008-03-14 - */ -rsRetVal -MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl) -{ - DEFiRet; - assert(pMsg != NULL); - assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY); - - pMsg->flowCtlType = eFlowCtl; - - RETiRet; -} - - -/* rgerhards 2004-11-24: set APP-NAME in msg object - * TODO: revisit msg locking code! - */ -rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME) -{ - DEFiRet; - assert(pMsg != NULL); - if(pMsg->pCSAPPNAME == NULL) { - /* we need to obtain the object first */ - CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); - rsCStrSetAllocIncrement(pMsg->pCSAPPNAME, 128); - } - /* if we reach this point, we have the object */ - iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); - -finalize_it: - RETiRet; -} - - -static void tryEmulateAPPNAME(msg_t *pM); /* forward reference */ -/* rgerhards, 2005-11-24 - */ -char *getAPPNAME(msg_t *pM) -{ - assert(pM != NULL); - MsgLock(pM); - if(pM->pCSAPPNAME == NULL) - tryEmulateAPPNAME(pM); - MsgUnlock(pM); - return (pM->pCSAPPNAME == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); -} - - -/* rgerhards 2004-11-24: set PROCID in msg object - */ -rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) -{ - DEFiRet; - ISOBJ_TYPE_assert(pMsg, msg); - if(pMsg->pCSPROCID == NULL) { - /* we need to obtain the object first */ - CHKiRet(rsCStrConstruct(&pMsg->pCSPROCID)); - rsCStrSetAllocIncrement(pMsg->pCSPROCID, 128); - } - /* if we reach this point, we have the object */ - iRet = rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID); - -finalize_it: - RETiRet; -} - -/* rgerhards, 2005-11-24 - */ -int getPROCIDLen(msg_t *pM) -{ - assert(pM != NULL); - MsgLock(pM); - if(pM->pCSPROCID == NULL) - aquirePROCIDFromTAG(pM); - MsgUnlock(pM); - return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); -} - - -/* rgerhards, 2005-11-24 - */ -char *getPROCID(msg_t *pM) -{ - char* pszRet; - - ISOBJ_TYPE_assert(pM, msg); - MsgLock(pM); - if(pM->pCSPROCID == NULL) - aquirePROCIDFromTAG(pM); - pszRet = (pM->pCSPROCID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSPROCID); - MsgUnlock(pM); - return pszRet; -} - - -/* rgerhards 2004-11-24: set MSGID in msg object - */ -rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID) -{ - DEFiRet; - ISOBJ_TYPE_assert(pMsg, msg); - if(pMsg->pCSMSGID == NULL) { - /* we need to obtain the object first */ - CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); - rsCStrSetAllocIncrement(pMsg->pCSMSGID, 128); - } - /* if we reach this point, we have the object */ - iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); - -finalize_it: - RETiRet; -} - -/* rgerhards, 2005-11-24 - */ -#if 0 /* This method is currently not called, be we like to preserve it */ -static int getMSGIDLen(msg_t *pM) -{ - return (pM->pCSMSGID == NULL) ? 1 : rsCStrLen(pM->pCSMSGID); -} -#endif - - -/* rgerhards, 2005-11-24 - */ -char *getMSGID(msg_t *pM) -{ - return (pM->pCSMSGID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID); -} - - -/* Set the TAG to a caller-provided string. This is thought - * to be a heap buffer that the caller will no longer use. This - * function is a performance optimization over MsgSetTAG(). - * rgerhards 2004-11-19 - */ -void MsgAssignTAG(msg_t *pMsg, uchar *pBuf) -{ - assert(pMsg != NULL); - pMsg->iLenTAG = (pBuf == NULL) ? 0 : strlen((char*)pBuf); - pMsg->pszTAG = (uchar*) pBuf; -} - - -/* rgerhards 2004-11-16: set TAG in msg object - */ -void MsgSetTAG(msg_t *pMsg, char* pszTAG) -{ - assert(pMsg != NULL); - if(pMsg->pszTAG != NULL) - free(pMsg->pszTAG); - pMsg->iLenTAG = strlen(pszTAG); - if((pMsg->pszTAG = malloc(pMsg->iLenTAG + 1)) != NULL) - memcpy(pMsg->pszTAG, pszTAG, pMsg->iLenTAG + 1); - else - dbgprintf("Could not allocate memory in MsgSetTAG()\n"); -} - - -/* This function tries to emulate the TAG if none is - * set. Its primary purpose is to provide an old-style TAG - * when a syslog-protocol message has been received. Then, - * the tag is APP-NAME "[" PROCID "]". The function first checks - * if there is a TAG and, if not, if it can emulate it. - * rgerhards, 2005-11-24 - */ -static void tryEmulateTAG(msg_t *pM) -{ - int iTAGLen; - uchar *pBuf; - assert(pM != NULL); - - if(pM->pszTAG != NULL) - return; /* done, no need to emulate */ - - if(getProtocolVersion(pM) == 1) { - if(!strcmp(getPROCID(pM), "-")) { - /* no process ID, use APP-NAME only */ - MsgSetTAG(pM, getAPPNAME(pM)); - } else { - /* now we can try to emulate */ - iTAGLen = getAPPNAMELen(pM) + getPROCIDLen(pM) + 3; - if((pBuf = malloc(iTAGLen * sizeof(char))) == NULL) - return; /* nothing we can do */ - snprintf((char*)pBuf, iTAGLen, "%s[%s]", getAPPNAME(pM), getPROCID(pM)); - MsgAssignTAG(pM, pBuf); - } - } -} - - -#if 0 /* This method is currently not called, be we like to preserve it */ -static int getTAGLen(msg_t *pM) -{ - if(pM == NULL) - return 0; - else { - tryEmulateTAG(pM); - if(pM->pszTAG == NULL) - return 0; - else - return pM->iLenTAG; - } -} -#endif - - -char *getTAG(msg_t *pM) -{ - char *ret; - - if(pM == NULL) - ret = ""; - else { - MsgLock(pM); - tryEmulateTAG(pM); - if(pM->pszTAG == NULL) - ret = ""; - else - ret = (char*) pM->pszTAG; - MsgUnlock(pM); - } - return(ret); -} - - -int getHOSTNAMELen(msg_t *pM) -{ - if(pM == NULL) - return 0; - else - if(pM->pszHOSTNAME == NULL) - return 0; - else - return pM->iLenHOSTNAME; -} - - -char *getHOSTNAME(msg_t *pM) -{ - if(pM == NULL) - return ""; - else - if(pM->pszHOSTNAME == NULL) - return ""; - else - return (char*) pM->pszHOSTNAME; -} - - -char *getRcvFrom(msg_t *pM) -{ - if(pM == NULL) - return ""; - else - if(pM->pszRcvFrom == NULL) - return ""; - else - return (char*) pM->pszRcvFrom; -} - -/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object - */ -rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData) -{ - DEFiRet; - ISOBJ_TYPE_assert(pMsg, msg); - if(pMsg->pCSStrucData == NULL) { - /* we need to obtain the object first */ - CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData)); - rsCStrSetAllocIncrement(pMsg->pCSStrucData, 128); - } - /* if we reach this point, we have the object */ - iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); - -finalize_it: - RETiRet; -} - -/* get the length of the "STRUCTURED-DATA" sz string - * rgerhards, 2005-11-24 - */ -#if 0 /* This method is currently not called, be we like to preserve it */ -static int getStructuredDataLen(msg_t *pM) -{ - return (pM->pCSStrucData == NULL) ? 1 : rsCStrLen(pM->pCSStrucData); -} -#endif - - -/* get the "STRUCTURED-DATA" as sz string - * rgerhards, 2005-11-24 - */ -char *getStructuredData(msg_t *pM) -{ - return (pM->pCSStrucData == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSStrucData); -} - - - -/* get the length of the "programname" sz string - * rgerhards, 2005-10-19 - */ -int getProgramNameLen(msg_t *pM) -{ - int iRet; - - assert(pM != NULL); - MsgLock(pM); - if((iRet = aquireProgramName(pM)) != RS_RET_OK) { - dbgprintf("error %d returned by aquireProgramName() in getProgramNameLen()\n", iRet); - MsgUnlock(pM); - return 0; /* best we can do (consistent wiht what getProgramName() returns) */ - } - MsgUnlock(pM); - - return (pM->pCSProgName == NULL) ? 0 : rsCStrLen(pM->pCSProgName); -} - - -/* get the "programname" as sz string - * rgerhards, 2005-10-19 - */ -char *getProgramName(msg_t *pM) /* this is the non-locking version for internal use */ -{ - int iRet; - char *pszRet; - - assert(pM != NULL); - MsgLock(pM); - if((iRet = aquireProgramName(pM)) != RS_RET_OK) { - dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); - pszRet = ""; /* best we can do */ - } else { - pszRet = (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); - } - - MsgUnlock(pM); - return pszRet; -} -/* The code below was an approach without PTHREAD_MUTEX_RECURSIVE - * However, it turned out to be quite complex. So far, we use recursive - * locking, which is OK from a performance point of view, especially as - * we do not anticipate that multithreading msg objects is used often. - * However, we may re-think about using non-recursive locking and I leave this - * code in here to conserve the idea. -- rgerhards, 2008-01-05 - */ -#if 0 -static char *getProgramNameNoLock(msg_t *pM) /* this is the non-locking version for internal use */ -{ - int iRet; - - assert(pM != NULL); - if((iRet = aquireProgramName(pM)) != RS_RET_OK) { - dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); - return ""; /* best we can do */ - } - - return (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); -} -char *getProgramName(msg_t *pM) /* this is the external callable version */ -{ - char *pszRet; - - MsgLock(pM); - pszRet = getProgramNameNoLock(pM); - MsgUnlock(pM); - return pszRet; -} -/* an alternative approach has been: */ -/* The macro below is used to generate external function definitions - * for such functions that may also be called internally (and thus have - * both a locking and non-locking implementation. Over time, we could - * reconsider how we handle that. -- rgerhards, 2008-01-05 - */ -#define EXT_LOCKED_FUNC(fName, ret) \ -ret fName(msg_t *pM) \ -{ \ - ret valRet; \ - MsgLock(pM); \ - valRet = fName##NoLock(pM); \ - MsgUnlock(pM); \ - return(valRet); \ -} -EXT_LOCKED_FUNC(getProgramName, char*) -/* in this approach, the external function is provided by the macro and - * needs not to be writen. - */ -#endif /* #if 0 -- saved code */ - - -/* This function tries to emulate APPNAME if it is not present. Its - * main use is when we have received a log record via legacy syslog and - * now would like to send out the same one via syslog-protocol. - */ -static void tryEmulateAPPNAME(msg_t *pM) -{ - assert(pM != NULL); - if(pM->pCSAPPNAME != NULL) - return; /* we are already done */ - - if(getProtocolVersion(pM) == 0) { - /* only then it makes sense to emulate */ - MsgSetAPPNAME(pM, getProgramName(pM)); - } -} - - -/* rgerhards, 2005-11-24 - */ -static int getAPPNAMELen(msg_t *pM) -{ - assert(pM != NULL); - if(pM->pCSAPPNAME == NULL) - tryEmulateAPPNAME(pM); - return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); -} - - -/* rgerhards 2004-11-16: set pszRcvFrom in msg object - */ -void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom) -{ - assert(pMsg != NULL); - if(pMsg->pszRcvFrom != NULL) - free(pMsg->pszRcvFrom); - - pMsg->iLenRcvFrom = strlen(pszRcvFrom); - if((pMsg->pszRcvFrom = malloc(pMsg->iLenRcvFrom + 1)) != NULL) { - memcpy(pMsg->pszRcvFrom, pszRcvFrom, pMsg->iLenRcvFrom + 1); - } -} - - -/* Set the HOSTNAME to a caller-provided string. This is thought - * to be a heap buffer that the caller will no longer use. This - * function is a performance optimization over MsgSetHOSTNAME(). - * rgerhards 2004-11-19 - */ -void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf) -{ - assert(pMsg != NULL); - assert(pBuf != NULL); - pMsg->iLenHOSTNAME = strlen(pBuf); - pMsg->pszHOSTNAME = (uchar*) pBuf; -} - - -/* rgerhards 2004-11-09: set HOSTNAME in msg object - * rgerhards, 2007-06-21: - * Does not return anything. If an error occurs, the hostname is - * simply not set. I have changed this behaviour. The only problem - * we can run into is memory shortage. If we have such, it is better - * to loose the hostname than the full message. So we silently ignore - * that problem and hope that memory will be available the next time - * we need it. The rest of the code already knows how to handle an - * unset HOSTNAME. - */ -void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME) -{ - assert(pMsg != NULL); - if(pMsg->pszHOSTNAME != NULL) - free(pMsg->pszHOSTNAME); - - pMsg->iLenHOSTNAME = strlen(pszHOSTNAME); - if((pMsg->pszHOSTNAME = malloc(pMsg->iLenHOSTNAME + 1)) != NULL) - memcpy(pMsg->pszHOSTNAME, pszHOSTNAME, pMsg->iLenHOSTNAME + 1); - else - dbgprintf("Could not allocate memory in MsgSetHOSTNAME()\n"); -} - - -/* Set the UxTradMsg to a caller-provided string. This is thought - * to be a heap buffer that the caller will no longer use. This - * function is a performance optimization over MsgSetUxTradMsg(). - * rgerhards 2004-11-19 - */ -#if 0 /* This method is currently not called, be we like to preserve it */ -static void MsgAssignUxTradMsg(msg_t *pMsg, char *pBuf) -{ - assert(pMsg != NULL); - assert(pBuf != NULL); - pMsg->iLenUxTradMsg = strlen(pBuf); - pMsg->pszUxTradMsg = pBuf; -} -#endif - - -/* rgerhards 2004-11-17: set the traditional Unix message in msg object - */ -int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg) -{ - assert(pMsg != NULL); - assert(pszUxTradMsg != NULL); - pMsg->iLenUxTradMsg = strlen(pszUxTradMsg); - if(pMsg->pszUxTradMsg != NULL) - free(pMsg->pszUxTradMsg); - if((pMsg->pszUxTradMsg = malloc(pMsg->iLenUxTradMsg + 1)) != NULL) - memcpy(pMsg->pszUxTradMsg, pszUxTradMsg, pMsg->iLenUxTradMsg + 1); - else - dbgprintf("Could not allocate memory for pszUxTradMsg buffer."); - - return(0); -} - - -/* rgerhards 2004-11-09: set MSG in msg object - */ -void MsgSetMSG(msg_t *pMsg, char* pszMSG) -{ - assert(pMsg != NULL); - assert(pszMSG != NULL); - - if(pMsg->pszMSG != NULL) - free(pMsg->pszMSG); - - pMsg->iLenMSG = strlen(pszMSG); - if((pMsg->pszMSG = (uchar*) malloc(pMsg->iLenMSG + 1)) != NULL) - memcpy(pMsg->pszMSG, pszMSG, pMsg->iLenMSG + 1); - else - dbgprintf("MsgSetMSG could not allocate memory for pszMSG buffer."); -} - -/* rgerhards 2004-11-11: set RawMsg in msg object - */ -void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg) -{ - assert(pMsg != NULL); - if(pMsg->pszRawMsg != NULL) - free(pMsg->pszRawMsg); - - pMsg->iLenRawMsg = strlen(pszRawMsg); - if((pMsg->pszRawMsg = (uchar*) malloc(pMsg->iLenRawMsg + 1)) != NULL) - memcpy(pMsg->pszRawMsg, pszRawMsg, pMsg->iLenRawMsg + 1); - else - dbgprintf("Could not allocate memory for pszRawMsg buffer."); -} - - -/* Decode a priority into textual information like auth.emerg. - * The variable pRes must point to a user-supplied buffer and - * pResLen must contain its size. The pointer to the buffer - * is also returned, what makes this functiona suitable for - * use in printf-like functions. - * Note: a buffer size of 20 characters is always sufficient. - * Interface to this function changed 2007-06-15 by RGerhards - */ -char *textpri(char *pRes, size_t pResLen, int pri) -{ - syslogCODE *c_pri, *c_fac; - - assert(pRes != NULL); - assert(pResLen > 0); - - for (c_fac = rs_facilitynames; c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri)<<3); c_fac++); - for (c_pri = rs_prioritynames; c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++); - - snprintf (pRes, pResLen, "%s.%s<%d>", c_fac->c_name, c_pri->c_name, pri); - - return pRes; -} - - -/* This function returns the current date in different - * variants. It is used to construct the $NOW series of - * system properties. The returned buffer must be freed - * by the caller when no longer needed. If the function - * can not allocate memory, it returns a NULL pointer. - * Added 2007-07-10 rgerhards - */ -typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_HHOUR, NOW_QHOUR, NOW_MINUTE } eNOWType; -#define tmpBUFSIZE 16 /* size of formatting buffer */ -static uchar *getNOW(eNOWType eNow) -{ - uchar *pBuf; - struct syslogTime t; - - if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { - glblHadMemShortage = 1; - return NULL; - } - - datetime.getCurrTime(&t); - switch(eNow) { - case NOW_NOW: - snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); - break; - case NOW_YEAR: - snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d", t.year); - break; - case NOW_MONTH: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.month); - break; - case NOW_DAY: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.day); - break; - case NOW_HOUR: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.hour); - break; - case NOW_HHOUR: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 30); - break; - case NOW_QHOUR: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 15); - break; - case NOW_MINUTE: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute); - break; - } - - return(pBuf); -} -#undef tmpBUFSIZE /* clean up */ - - -/* This function returns a string-representation of the - * requested message property. This is a generic function used - * to abstract properties so that these can be easier - * queried. Returns NULL if property could not be found. - * Actually, this function is a big if..elseif. What it does - * is simply to map property names (from MonitorWare) to the - * message object data fields. - * - * In case we need string forms of propertis we do not - * yet have in string form, we do a memory allocation that - * is sufficiently large (in all cases). Once the string - * form has been obtained, it is saved until the Msg object - * is finally destroyed. This is so that we save the processing - * time in the (likely) case that this property is requested - * again. It also saves us a lot of dynamic memory management - * issues in the upper layers, because we so can guarantee that - * the buffer will remain static AND available during the lifetime - * of the object. Please note that both the max size allocation as - * well as keeping things in memory might like look like a - * waste of memory (some might say it actually is...) - we - * deliberately accept this because performance is more important - * to us ;) - * rgerhards 2004-11-18 - * Parameter "bMustBeFreed" is set by this function. It tells the - * caller whether or not the string returned must be freed by the - * caller itself. It is is 0, the caller MUST NOT free it. If it is - * 1, the caller MUST free 1. Handling this wrongly leads to either - * a memory leak of a program abort (do to double-frees or frees on - * the constant memory pool). So be careful to do it right. - * rgerhards 2004-11-23 - * regular expression support contributed by Andres Riancho merged - * on 2005-09-13 - * changed so that it now an be called without a template entry (NULL). - * In this case, only the (unmodified) property is returned. This will - * be used in selector line processing. - * rgerhards 2005-09-15 - */ -char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, - cstr_t *pCSPropName, unsigned short *pbMustBeFreed) -{ - uchar *pName; - char *pRes; /* result pointer */ - char *pBufStart; - char *pBuf; - int iLen; - -#ifdef FEATURE_REGEXP - /* Variables necessary for regular expression matching */ - size_t nmatch = 1; - regmatch_t pmatch[1]; -#endif - - assert(pMsg != NULL); - assert(pbMustBeFreed != NULL); - - if(pCSPropName == NULL) { - assert(pTpe != NULL); - pName = pTpe->data.field.pPropRepl; - } else { - pName = rsCStrGetSzStrNoNULL(pCSPropName); - } - *pbMustBeFreed = 0; - - /* sometimes there are aliases to the original MonitoWare - * property names. These come after || in the ifs below. */ - if(!strcmp((char*) pName, "msg")) { - pRes = getMSG(pMsg); - } else if(!strcmp((char*) pName, "rawmsg")) { - pRes = getRawMsg(pMsg); - } else if(!strcmp((char*) pName, "uxtradmsg")) { - pRes = getUxTradMsg(pMsg); - } else if(!strcmp((char*) pName, "fromhost")) { - pRes = getRcvFrom(pMsg); - } else if(!strcmp((char*) pName, "source") || !strcmp((char*) pName, "hostname")) { - pRes = getHOSTNAME(pMsg); - } else if(!strcmp((char*) pName, "syslogtag")) { - pRes = getTAG(pMsg); - } else if(!strcmp((char*) pName, "pri")) { - pRes = getPRI(pMsg); - } else if(!strcmp((char*) pName, "pri-text")) { - pBuf = malloc(20 * sizeof(char)); - if(pBuf == NULL) { - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } else { - *pbMustBeFreed = 1; - pRes = textpri(pBuf, 20, getPRIi(pMsg)); - } - } else if(!strcmp((char*) pName, "iut")) { - pRes = "1"; /* always 1 for syslog messages (a MonitorWare thing;)) */ - } else if(!strcmp((char*) pName, "syslogfacility")) { - pRes = getFacility(pMsg); - } else if(!strcmp((char*) pName, "syslogfacility-text")) { - pRes = getFacilityStr(pMsg); - } else if(!strcmp((char*) pName, "syslogseverity") || !strcmp((char*) pName, "syslogpriority")) { - pRes = getSeverity(pMsg); - } else if(!strcmp((char*) pName, "syslogseverity-text") || !strcmp((char*) pName, "syslogpriority-text")) { - pRes = getSeverityStr(pMsg); - } else if(!strcmp((char*) pName, "timegenerated")) { - pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); - } else if(!strcmp((char*) pName, "timereported") - || !strcmp((char*) pName, "timestamp")) { - pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); - } else if(!strcmp((char*) pName, "programname")) { - pRes = getProgramName(pMsg); - } else if(!strcmp((char*) pName, "protocol-version")) { - pRes = getProtocolVersionString(pMsg); - } else if(!strcmp((char*) pName, "structured-data")) { - pRes = getStructuredData(pMsg); - } else if(!strcmp((char*) pName, "app-name")) { - pRes = getAPPNAME(pMsg); - } else if(!strcmp((char*) pName, "procid")) { - pRes = getPROCID(pMsg); - } else if(!strcmp((char*) pName, "msgid")) { - pRes = getMSGID(pMsg); - /* here start system properties (those, that do not relate to the message itself */ - } else if(!strcmp((char*) pName, "$now")) { - if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$year")) { - if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$month")) { - if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$day")) { - if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$hour")) { - if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$hhour")) { - if((pRes = (char*) getNOW(NOW_HHOUR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$qhour")) { - if((pRes = (char*) getNOW(NOW_QHOUR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp((char*) pName, "$minute")) { - if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else { - /* there is no point in continuing, we may even otherwise render the - * error message unreadable. rgerhards, 2007-07-10 - */ - dbgprintf("invalid property name: '%s'\n", pName); - return "**INVALID PROPERTY NAME**"; - } - - /* If we did not receive a template pointer, we are already done... */ - if(pTpe == NULL) { - return pRes; - } - - /* Now check if we need to make "temporary" transformations (these - * are transformations that do not go back into the message - - * memory must be allocated for them!). - */ - - /* substring extraction */ - /* first we check if we need to extract by field number - * rgerhards, 2005-12-22 - */ - if(pTpe->data.field.has_fields == 1) { - size_t iCurrFld; - char *pFld; - char *pFldEnd; - /* first, skip to the field in question. The field separator - * is always one character and is stored in the template entry. - */ - iCurrFld = 1; - pFld = pRes; - while(*pFld && iCurrFld < pTpe->data.field.iToPos) { - /* skip fields until the requested field or end of string is found */ - while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) - ++pFld; /* skip to field terminator */ - if(*pFld == pTpe->data.field.field_delim) { - ++pFld; /* eat it */ - ++iCurrFld; - } - } - dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, (int) iCurrFld); - - if(iCurrFld == pTpe->data.field.iToPos) { - /* field found, now extract it */ - /* first of all, we need to find the end */ - pFldEnd = pFld; - while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) - ++pFldEnd; - --pFldEnd; /* we are already at the delimiter - so we need to - * step back a little not to copy it as part of the field. */ - /* we got our end pointer, now do the copy */ - /* TODO: code copied from below, this is a candidate for a separate function */ - iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ - pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); - if(pBuf == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - /* now copy */ - memcpy(pBuf, pFld, iLen); - pBuf[iLen] = '\0'; /* terminate it */ - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBufStart; - *pbMustBeFreed = 1; - if(*(pFldEnd+1) != '\0') - ++pFldEnd; /* OK, skip again over delimiter char */ - } else { - /* field not found, return error */ - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**FIELD NOT FOUND**"; - } - } else if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { - /* we need to obtain a private copy */ - int iFrom, iTo; - char *pSb; - iFrom = pTpe->data.field.iFromPos; - iTo = pTpe->data.field.iToPos; - /* need to zero-base to and from (they are 1-based!) */ - if(iFrom > 0) - --iFrom; - if(iTo > 0) - --iTo; - iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ - pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); - if(pBuf == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - pSb = pRes; - if(iFrom) { - /* skip to the start of the substring (can't do pointer arithmetic - * because the whole string might be smaller!!) - */ - while(*pSb && iFrom) { - --iFrom; - ++pSb; - } - } - /* OK, we are at the begin - now let's copy... */ - while(*pSb && iLen) { - *pBuf++ = *pSb; - ++pSb; - --iLen; - } - *pBuf = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBufStart; - *pbMustBeFreed = 1; -#ifdef FEATURE_REGEXP - } else { - /* Check for regular expressions */ - if (pTpe->data.field.has_regex != 0) { - if (pTpe->data.field.has_regex == 2) - /* Could not compile regex before! */ - return "**NO MATCH** **BAD REGULAR EXPRESSION**"; - - dbgprintf("debug: String to match for regex is: %s\n", pRes); - - if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { - if (0 != regexp.regexec(&pTpe->data.field.re, pRes, nmatch, pmatch, 0)) { - /* we got no match! */ - if (*pbMustBeFreed == 1) { - free(pRes); - *pbMustBeFreed = 0; - } - return "**NO MATCH**"; - } else { - /* Match! */ - /* I need to malloc pB */ - int iLenBuf; - char *pB; - - iLenBuf = pmatch[0].rm_eo - pmatch[0].rm_so; - pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); - - if (pB == NULL) { - if (*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY ALLOCATING pBuf**"; - } - - /* Lets copy the matched substring to the buffer */ - memcpy(pB, pRes + pmatch[0].rm_so, iLenBuf); - pB[iLenBuf] = '\0';/* terminate string, did not happen before */ - - if (*pbMustBeFreed == 1) - free(pRes); - pRes = pB; - *pbMustBeFreed = 1; - } - } else { - /* we could not load regular expression support. This is quite unexpected at - * this stage of processing (after all, the config parser found it), but so - * it is. We return an error in that case. -- rgerhards, 2008-03-07 - */ - dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n"); - if (*pbMustBeFreed == 1) { - free(pRes); - *pbMustBeFreed = 0; - } - return "***REGEXP NOT AVAILABLE***"; - } - } -#endif /* #ifdef FEATURE_REGEXP */ - } - - if(*pRes) { - /* case conversations (should go after substring, because so we are able to - * work on the smallest possible buffer). - */ - if(pTpe->data.field.eCaseConv != tplCaseConvNo) { - /* we need to obtain a private copy */ - int iBufLen = strlen(pRes); - char *pBStart; - char *pB; - char *pSrc; - pBStart = pB = malloc((iBufLen + 1) * sizeof(char)); - if(pB == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - pSrc = pRes; - while(*pSrc) { - *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? - (char)toupper((int)*pSrc) : (char)tolower((int)*pSrc); - /* currently only these two exist */ - ++pSrc; - } - *pB = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBStart; - *pbMustBeFreed = 1; - } - - /* now do control character dropping/escaping/replacement - * Only one of these can be used. If multiple options are given, the - * result is random (though currently there obviously is an order of - * preferrence, see code below. But this is NOT guaranteed. - * RGerhards, 2006-11-17 - * We must copy the strings if we modify them, because they may either - * point to static memory or may point into the message object, in which - * case we would actually modify the original property (which of course - * is wrong). - * This was found and fixed by varmojefkoj on 2007-09-11 - */ - if(pTpe->data.field.options.bDropCC) { - int iLenBuf = 0; - char *pSrc = pRes; - char *pDstStart; - char *pDst; - char bDropped = 0; - - while(*pSrc) { - if(!iscntrl((int) *pSrc++)) - iLenBuf++; - else - bDropped = 1; - } - - if(bDropped) { - pDst = pDstStart = malloc(iLenBuf + 1); - if(pDst == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - for(pSrc = pRes; *pSrc; pSrc++) { - if(!iscntrl((int) *pSrc)) - *pDst++ = *pSrc; - } - *pDst = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pDstStart; - *pbMustBeFreed = 1; - } - } else if(pTpe->data.field.options.bSpaceCC) { - char *pSrc; - char *pDstStart; - char *pDst; - - if(*pbMustBeFreed == 1) { - /* in this case, we already work on dynamic - * memory, so there is no need to copy it - we can - * modify it in-place without any harm. This is a - * performance optiomization. - */ - for(pDst = pRes; *pDst; pDst++) { - if(iscntrl((int) *pDst)) - *pDst = ' '; - } - } else { - pDst = pDstStart = malloc(strlen(pRes) + 1); - if(pDst == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - for(pSrc = pRes; *pSrc; pSrc++) { - if(iscntrl((int) *pSrc)) - *pDst++ = ' '; - else - *pDst++ = *pSrc; - } - *pDst = '\0'; - pRes = pDstStart; - *pbMustBeFreed = 1; - } - } else if(pTpe->data.field.options.bEscapeCC) { - /* we must first count how many control charactes are - * present, because we need this to compute the new string - * buffer length. While doing so, we also compute the string - * length. - */ - int iNumCC = 0; - int iLenBuf = 0; - char *pB; - - for(pB = pRes ; *pB ; ++pB) { - ++iLenBuf; - if(iscntrl((int) *pB)) - ++iNumCC; - } - - if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ - /* OK, let's do the escaping... */ - char *pBStart; - char szCCEsc[8]; /* buffer for escape sequence */ - int i; - - iLenBuf += iNumCC * 4; - pBStart = pB = malloc((iLenBuf + 1) * sizeof(char)); - if(pB == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - while(*pRes) { - if(iscntrl((int) *pRes)) { - snprintf(szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); - for(i = 0 ; i < 4 ; ++i) - *pB++ = szCCEsc[i]; - } else { - *pB++ = *pRes; - } - ++pRes; - } - *pB = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBStart; - *pbMustBeFreed = 1; - } - } - } - - /* Take care of spurious characters to make the property safe - * for a path definition - */ - if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) { - if(pTpe->data.field.options.bSecPathDrop) { - int iLenBuf = 0; - char *pSrc = pRes; - char *pDstStart; - char *pDst; - char bDropped = 0; - - while(*pSrc) { - if(*pSrc++ != '/') - iLenBuf++; - else - bDropped = 1; - } - - if(bDropped) { - pDst = pDstStart = malloc(iLenBuf + 1); - if(pDst == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - for(pSrc = pRes; *pSrc; pSrc++) { - if(*pSrc != '/') - *pDst++ = *pSrc; - } - *pDst = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pDstStart; - *pbMustBeFreed = 1; - } - } else { - char *pSrc; - char *pDstStart; - char *pDst; - - if(*pbMustBeFreed == 1) { - /* here, again, we can modify the string as we already obtained - * a private buffer. As we do not change the size of that buffer, - * in-place modification is possible. This is a performance - * enhancement. - */ - for(pDst = pRes; *pDst; pDst++) { - if(*pDst == '/') - *pDst++ = '_'; - } - } else { - pDst = pDstStart = malloc(strlen(pRes) + 1); - if(pDst == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - for(pSrc = pRes; *pSrc; pSrc++) { - if(*pSrc == '/') - *pDst++ = '_'; - else - *pDst++ = *pSrc; - } - *pDst = '\0'; - /* we must NOT check if it needs to be freed, because we have done - * this in the if above. So if we come to hear, the pSrc string needs - * not to be freed (and we do not need to care about it). - */ - pRes = pDstStart; - *pbMustBeFreed = 1; - } - } - - /* check for "." and ".." (note the parenthesis in the if condition!) */ - if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) { - char *pTmp = pRes; - - if(*(pRes + 1) == '\0') - pRes = "_"; - else - pRes = "_.";; - if(*pbMustBeFreed == 1) - free(pTmp); - *pbMustBeFreed = 0; - } else if(*pRes == '\0') { - if(*pbMustBeFreed == 1) - free(pRes); - pRes = "_"; - *pbMustBeFreed = 0; - } - } - - /* Now drop last LF if present (pls note that this must not be done - * if bEscapeCC was set! - */ - if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { - int iLn = strlen(pRes); - char *pB; - if(iLn > 0 && *(pRes + iLn - 1) == '\n') { - /* we have a LF! */ - /* check if we need to obtain a private copy */ - if(*pbMustBeFreed == 0) { - /* ok, original copy, need a private one */ - pB = malloc((iLn + 1) * sizeof(char)); - if(pB == NULL) { - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - memcpy(pB, pRes, iLn - 1); - pRes = pB; - *pbMustBeFreed = 1; - } - *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ - } - } - - /*dbgprintf("MsgGetProp(\"%s\"): \"%s\"\n", pName, pRes); only for verbose debug logging */ - return(pRes); -} - - -/* The returns a message variable suitable for use with RainerScript. Most importantly, this means - * that the value is returned in a var_t object. The var_t is constructed inside this function and - * MUST be freed by the caller. - * rgerhards, 2008-02-25 - */ -rsRetVal -msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar) -{ - DEFiRet; - var_t *pVar; - uchar *pszProp = NULL; - cstr_t *pstrProp; - unsigned short bMustBeFreed = 0; - - ISOBJ_TYPE_assert(pThis, msg); - ASSERT(pstrPropName != NULL); - ASSERT(ppVar != NULL); - - /* make sure we have a var_t instance */ - CHKiRet(var.Construct(&pVar)); - CHKiRet(var.ConstructFinalize(pVar)); - - /* always call MsgGetProp() without a template specifier */ - pszProp = (uchar*) MsgGetProp(pThis, NULL, pstrPropName, &bMustBeFreed); - - /* now create a string object out of it and hand that over to the var */ - CHKiRet(rsCStrConstructFromszStr(&pstrProp, pszProp)); - CHKiRet(var.SetString(pVar, pstrProp)); - - /* finally store var */ - *ppVar = pVar; - -finalize_it: - if(bMustBeFreed) - free(pszProp); - - RETiRet; -} - - -/* This function can be used as a generic way to set properties. - * We have to handle a lot of legacy, so our return value is not always - * 100% correct (called functions do not always provide one, should - * change over time). - * rgerhards, 2008-01-07 - */ -#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) -rsRetVal MsgSetProperty(msg_t *pThis, var_t *pProp) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pThis, msg); - assert(pProp != NULL); - - if(isProp("iProtocolVersion")) { - setProtocolVersion(pThis, pProp->val.num); - } else if(isProp("iSeverity")) { - pThis->iSeverity = pProp->val.num; - } else if(isProp("iFacility")) { - pThis->iFacility = pProp->val.num; - } else if(isProp("msgFlags")) { - pThis->msgFlags = pProp->val.num; - } else if(isProp("pszRawMsg")) { - MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pszMSG")) { - MsgSetMSG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pszUxTradMsg")) { - MsgSetUxTradMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pszTAG")) { - MsgSetTAG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pszRcvFrom")) { - MsgSetHOSTNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pszHOSTNAME")) { - MsgSetRcvFrom(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pCSStrucData")) { - MsgSetStructuredData(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pCSAPPNAME")) { - MsgSetAPPNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pCSPROCID")) { - MsgSetPROCID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("pCSMSGID")) { - MsgSetMSGID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); - } else if(isProp("tRcvdAt")) { - memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); - } else if(isProp("tTIMESTAMP")) { - memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); - } - - RETiRet; -} -#undef isProp - - -/* This is a construction finalizer that must be called after all properties - * have been set. It does some final work on the message object. After this - * is done, the object is considered ready for full processing. - * rgerhards, 2008-07-08 - */ -static rsRetVal msgConstructFinalizer(msg_t *pThis) -{ - MsgPrepareEnqueue(pThis); - return RS_RET_OK; -} - - -/* get the severity - this is an entry point that - * satisfies the base object class getSeverity semantics. - * rgerhards, 2008-01-14 - */ -static rsRetVal -MsgGetSeverity(obj_t *pThis, int *piSeverity) -{ - ISOBJ_TYPE_assert(pThis, msg); - assert(piSeverity != NULL); - *piSeverity = ((msg_t*) pThis)->iSeverity; - return RS_RET_OK; -} - - -/* dummy */ -rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } - -/* Initialize the message class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-01-04 - */ -BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE) - /* request objects we use */ - CHKiRet(objUse(var, CORE_COMPONENT)); - CHKiRet(objUse(datetime, CORE_COMPONENT)); - - /* set our own handlers */ - OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); - OBJSetMethodHandler(objMethod_SETPROPERTY, MsgSetProperty); - OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, msgConstructFinalizer); - OBJSetMethodHandler(objMethod_GETSEVERITY, MsgGetSeverity); - /* initially, we have no need to lock message objects */ - funcLock = MsgLockingDummy; - funcUnlock = MsgLockingDummy; - funcDeleteMutex = MsgLockingDummy; - funcMsgPrepareEnqueue = MsgLockingDummy; -ENDObjClassInit(msg) - -/* - * vi:set ai: - */ diff --git a/msg.h b/msg.h deleted file mode 100644 index 61feaddb..00000000 --- a/msg.h +++ /dev/null @@ -1,177 +0,0 @@ -/* msg.h - * Header file for all msg-related functions. - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "template.h" /* this is a quirk, but these two are too interdependant... */ - -#ifndef MSG_H_INCLUDED -#define MSG_H_INCLUDED 1 - -#include -#include "obj.h" -#include "syslogd-types.h" -#include "template.h" - -/* rgerhards 2004-11-08: The following structure represents a - * syslog message. - * - * Important Note: - * The message object is used for multiple purposes (once it - * has been created). Once created, it actully is a read-only - * object (though we do not specifically express this). In order - * to avoid multiple copies of the same object, we use a - * reference counter. This counter is set to 1 by the constructer - * and increased by 1 with a call to MsgAddRef(). The destructor - * checks the reference count. If it is more than 1, only the counter - * will be decremented. If it is 1, however, the object is actually - * destroyed. To make this work, it is vital that MsgAddRef() is - * called each time a "copy" is stored somewhere. - */ -struct msg { - BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ - pthread_mutexattr_t mutAttr; - pthread_mutex_t mut; - int iRefCount; /* reference counter (0 = unused) */ - short bParseHOSTNAME; /* should the hostname be parsed from the message? */ - /* background: the hostname is not present on "regular" messages - * received via UNIX domain sockets from the same machine. However, - * it is available when we have a forwarder (e.g. rfc3195d) using local - * sockets. All in all, the parser would need parse templates, that would - * resolve all these issues... rgerhards, 2005-10-06 - */ - flowControl_t flowCtlType; /**< type of flow control we can apply, for enqueueing, needs not to be persisted because - once data has entered the queue, this property is no longer needed. */ - short iSeverity; /* the severity 0..7 */ - uchar *pszSeverity; /* severity as string... */ - int iLenSeverity; /* ... and its length. */ - uchar *pszSeverityStr; /* severity name... */ - int iLenSeverityStr; /* ... and its length. */ - short iFacility; /* Facility code 0 .. 23*/ - uchar *pszFacility; /* Facility as string... */ - int iLenFacility; /* ... and its length. */ - uchar *pszFacilityStr; /* facility name... */ - int iLenFacilityStr; /* ... and its length. */ - uchar *pszPRI; /* the PRI as a string */ - int iLenPRI; /* and its length */ - uchar *pszRawMsg; /* message as it was received on the - * wire. This is important in case we - * need to preserve cryptographic verifiers. - */ - int iLenRawMsg; /* length of raw message */ - uchar *pszMSG; /* the MSG part itself */ - int iLenMSG; /* Length of the MSG part */ - uchar *pszUxTradMsg; /* the traditional UNIX message */ - int iLenUxTradMsg;/* Length of the traditional UNIX message */ - uchar *pszTAG; /* pointer to tag value */ - int iLenTAG; /* Length of the TAG part */ - uchar *pszHOSTNAME; /* HOSTNAME from syslog message */ - int iLenHOSTNAME; /* Length of HOSTNAME */ - uchar *pszRcvFrom; /* System message was received from */ - int iLenRcvFrom; /* Length of pszRcvFrom */ - short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ - cstr_t *pCSProgName; /* the (BSD) program name */ - cstr_t *pCSStrucData;/* STRUCTURED-DATA */ - cstr_t *pCSAPPNAME; /* APP-NAME */ - cstr_t *pCSPROCID; /* PROCID */ - cstr_t *pCSMSGID; /* MSGID */ - struct syslogTime tRcvdAt;/* time the message entered this program */ - char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */ - char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */ - char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 charcters) */ - char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */ - struct syslogTime tTIMESTAMP;/* (parsed) value of the timestamp */ - char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 charcters) */ - char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 charcters at most) */ - char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 charcters) */ - char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */ - int msgFlags; /* flags associated with this message */ -}; -typedef struct msg msg_t; /* new name */ - -/* function prototypes - */ -PROTOTYPEObjClassInit(msg); -char* getProgramName(msg_t*); -rsRetVal msgConstruct(msg_t **ppThis); -rsRetVal msgDestruct(msg_t **ppM); -msg_t* MsgDup(msg_t* pOld); -msg_t *MsgAddRef(msg_t *pM); -void setProtocolVersion(msg_t *pM, int iNewVersion); -int getProtocolVersion(msg_t *pM); -char *getProtocolVersionString(msg_t *pM); -int getMSGLen(msg_t *pM); -char *getRawMsg(msg_t *pM); -char *getUxTradMsg(msg_t *pM); -char *getMSG(msg_t *pM); -char *getPRI(msg_t *pM); -int getPRIi(msg_t *pM); -char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt); -char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt); -char *getSeverity(msg_t *pM); -char *getSeverityStr(msg_t *pM); -char *getFacility(msg_t *pM); -char *getFacilityStr(msg_t *pM); -rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME); -char *getAPPNAME(msg_t *pM); -rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID); -int getPROCIDLen(msg_t *pM); -char *getPROCID(msg_t *pM); -rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID); -void MsgAssignTAG(msg_t *pMsg, uchar *pBuf); -void MsgSetTAG(msg_t *pMsg, char* pszTAG); -rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl); -char *getTAG(msg_t *pM); -int getHOSTNAMELen(msg_t *pM); -char *getHOSTNAME(msg_t *pM); -char *getRcvFrom(msg_t *pM); -rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData); -char *getStructuredData(msg_t *pM); -int getProgramNameLen(msg_t *pM); -char *getProgramName(msg_t *pM); -void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom); -void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf); -void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME); -int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg); -void MsgSetMSG(msg_t *pMsg, char* pszMSG); -void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg); -void moveHOSTNAMEtoTAG(msg_t *pM); -char *getMSGID(msg_t *pM); -char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, - cstr_t *pCSPropName, unsigned short *pbMustBeFreed); -char *textpri(char *pRes, size_t pResLen, int pri); -rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar); -rsRetVal MsgEnableThreadSafety(void); - -/* The MsgPrepareEnqueue() function is a macro for performance reasons. - * It needs one global variable to work. This is acceptable, as it gains - * us quite some performance and is fully abstracted using this header file. - * The important thing is that no other module is permitted to actually - * access that global variable! -- rgerhards, 2008-01-05 - */ -extern void (*funcMsgPrepareEnqueue)(msg_t *pMsg); -#define MsgPrepareEnqueue(pMsg) funcMsgPrepareEnqueue(pMsg) - -#endif /* #ifndef MSG_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/obj-types.h b/obj-types.h deleted file mode 100644 index 4cd45153..00000000 --- a/obj-types.h +++ /dev/null @@ -1,405 +0,0 @@ -/* Some type definitions and macros for the obj object. - * I needed to move them out of the main obj.h, because obj.h's - * prototypes use other data types. However, their .h's rely - * on some of the obj.h data types and macros. So I needed to break - * that loop somehow and I've done that by moving the typedefs - * into this file here. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef OBJ_TYPES_H_INCLUDED -#define OBJ_TYPES_H_INCLUDED - -#include "stringbuf.h" -#include "syslogd-types.h" - -/* property types for obj[De]Serialize() */ -typedef enum { - PROPTYPE_NONE = 0, /* currently no value set */ - PROPTYPE_PSZ = 1, - PROPTYPE_SHORT = 2, - PROPTYPE_INT = 3, - PROPTYPE_LONG = 4, - PROPTYPE_INT64 = 5, - PROPTYPE_CSTR = 6, - PROPTYPE_SYSLOGTIME = 7 -} propType_t; - -typedef unsigned objID_t; - -typedef enum { /* IDs of base methods supported by all objects - used for jump table, so - * they must start at zero and be incremented. -- rgerhards, 2008-01-04 - */ - objMethod_CONSTRUCT = 0, - objMethod_DESTRUCT = 1, - objMethod_SERIALIZE = 2, - objMethod_DESERIALIZE = 3, - objMethod_SETPROPERTY = 4, - objMethod_CONSTRUCTION_FINALIZER = 5, - objMethod_GETSEVERITY = 6, - objMethod_DEBUGPRINT = 7 -} objMethod_t; -#define OBJ_NUM_METHODS 8 /* must be updated to contain the max number of methods supported */ - - -/* the base data type for interfaces - * This MUST be in sync with the ifBEGIN macro - */ -typedef struct interface_s { - int ifVersion; /* must be set to version requested */ - int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes, 2-load failed; if not 1, functions can NOT be called! */ -} interface_t; - - -typedef struct objInfo_s { - uchar *pszID; /* the object ID as a string */ - size_t lenID; /* length of the ID string */ - int iObjVers; - uchar *pszName; - rsRetVal (*objMethods[OBJ_NUM_METHODS])(); - rsRetVal (*QueryIF)(interface_t*); - struct modInfo_s *pModInfo; -} objInfo_t; - - -typedef struct obj { /* the dummy struct that each derived class can be casted to */ - objInfo_t *pObjInfo; -#ifndef NDEBUG /* this means if debug... */ - unsigned int iObjCooCKiE; /* must always be 0xBADEFEE for a valid object */ -#endif - uchar *pszName; /* the name of *this* specific object instance */ -} obj_t; - - -/* macros which must be gloablly-visible (because they are used during definition of - * other objects. - */ -#ifndef NDEBUG /* this means if debug... */ -#include -# define BEGINobjInstance \ - obj_t objData -# define ISOBJ_assert(pObj) \ - do { \ - ASSERT((pObj) != NULL); \ - ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ - } while(0); -# define ISOBJ_TYPE_assert(pObj, objType) \ - do { \ - ASSERT(pObj != NULL); \ - ASSERT((unsigned) ((obj_t*) (pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ - ASSERT(!strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)); \ - } while(0); -#else /* non-debug mode, no checks but much faster */ -# define BEGINobjInstance obj_t objData -# define ISOBJ_TYPE_assert(pObj, objType) -# define ISOBJ_assert(pObj) -#endif - -#define DEFpropSetMethPTR(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType *pVal)\ - { \ - /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ - pThis->prop = pVal; \ - return RS_RET_OK; \ - } -#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType*) -#define DEFpropSetMeth(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ - { \ - /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ - pThis->prop = pVal; \ - return RS_RET_OK; \ - } -#define DEFpropSetMethFP(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\ - { \ - /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ - pThis->prop = pVal; \ - return RS_RET_OK; \ - } -#define PROTOTYPEpropSetMethFP(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType) -#define DEFpropSetMeth(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ - { \ - /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ - pThis->prop = pVal; \ - return RS_RET_OK; \ - } -#define PROTOTYPEpropSetMeth(obj, prop, dataType)\ - rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal) -#define INTERFACEpropSetMeth(obj, prop, dataType)\ - rsRetVal (*Set##prop)(obj##_t *pThis, dataType) -/* class initializer */ -#define PROTOTYPEObjClassInit(objName) rsRetVal objName##ClassInit(struct modInfo_s*) -/* below: objName must be the object name (e.g. vm, strm, ...) and ISCORE must be - * 1 if the module is a statically linked core module and 0 if it is a - * dynamically loaded one. -- rgerhards, 2008-02-29 - */ -#define OBJ_IS_CORE_MODULE 1 /* This should better be renamed to something like "OBJ_IS_NOT_LIBHEAD" or so... ;) */ -#define OBJ_IS_LOADABLE_MODULE 0 -#define BEGINObjClassInit(objName, objVers, objType) \ -rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ -{ \ - DEFiRet; \ - if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ - CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ - } \ - CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ - (rsRetVal (*)(void*))objName##Construct,\ - (rsRetVal (*)(void*))objName##Destruct,\ - (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); \ - -#define ENDObjClassInit(objName) \ - iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ -finalize_it: \ - RETiRet; \ -} - -/* ... and now the same for abstract classes. - * TODO: consolidate the two -- rgerhards, 2008-02-29 - */ -#define BEGINAbstractObjClassInit(objName, objVers, objType) \ -rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ -{ \ - DEFiRet; \ - if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ - CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ - } \ - CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ - NULL,\ - NULL,\ - (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); - -#define ENDObjClassInit(objName) \ - iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ -finalize_it: \ - RETiRet; \ -} - - -/* now come the class exit. This is to be called immediately before the class is - * unloaded (actual unload for plugins, program termination for core modules) - * gerhards, 2008-03-10 - */ -#define PROTOTYPEObjClassExit(objName) rsRetVal objName##ClassExit(void) -#define BEGINObjClassExit(objName, objType) \ -rsRetVal objName##ClassExit(void) \ -{ \ - DEFiRet; - -#define CODESTARTObjClassExit(objName) - -#define ENDObjClassExit(objName) \ - iRet = obj.UnregisterObj((uchar*)#objName); \ - RETiRet; \ -} - -/* this defines both the constructor and initializer - * rgerhards, 2008-01-10 - */ -#define BEGINobjConstruct(obj) \ - rsRetVal obj##Initialize(obj##_t __attribute__((unused)) *pThis) \ - { \ - DEFiRet; - -#define ENDobjConstruct(obj) \ - /* use finalize_it: before calling the macro (if you need it)! */ \ - RETiRet; \ - } \ - rsRetVal obj##Construct(obj##_t **ppThis) \ - { \ - DEFiRet; \ - obj##_t *pThis; \ - \ - ASSERT(ppThis != NULL); \ - \ - if((pThis = (obj##_t *)calloc(1, sizeof(obj##_t))) == NULL) { \ - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); \ - } \ - objConstructSetObjInfo(pThis); \ - \ - obj##Initialize(pThis); \ - \ - finalize_it: \ - OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ - RETiRet; \ - } - - -/* this defines the destructor. The important point is that the base object - * destructor is called. The upper-level class shall destruct all of its - * properties, but not the instance itself. This is freed here by the - * framework (we need an intact pointer because we need to free the - * obj_t structures inside it). A pointer to the object pointer must be - * parse, because it is re-set to NULL (this, for example, is important in - * cancellation handlers). The object pointer is always named pThis. - * The object is always freed, even if there is some error while - * Cancellation is blocked during destructors, as this could have fatal - * side-effects. However, this also means the upper-level object should - * not perform any lenghty processing. - * IMPORTANT: if the upper level object requires some situations where the - * object shall not be destructed (e.g. via reference counting), then - * it shall set pThis to NULL, which prevents destruction of the - * object. - * processing. - * rgerhards, 2008-01-30 - */ -#define BEGINobjDestruct(OBJ) \ - rsRetVal OBJ##Destruct(OBJ##_t **ppThis) \ - { \ - DEFiRet; \ - int iCancelStateSave; \ - OBJ##_t *pThis; - -#define CODESTARTobjDestruct(OBJ) \ - ASSERT(ppThis != NULL); \ - pThis = *ppThis; \ - ISOBJ_TYPE_assert(pThis, OBJ); \ - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); - -#define ENDobjDestruct(OBJ) \ - goto finalize_it; /* prevent compiler warning ;) */ \ - /* no more code here! */ \ - finalize_it: \ - if(pThis != NULL) { \ - obj.DestructObjSelf((obj_t*) pThis); \ - free(pThis); \ - *ppThis = NULL; \ - } \ - pthread_setcancelstate(iCancelStateSave, NULL); \ - RETiRet; \ - } - - -/* this defines the debug print entry point. DebugPrint is optional. If - * it is provided, the object should output some meaningful information - * via the debug system. - * rgerhards, 2008-02-20 - */ -#define PROTOTYPEObjDebugPrint(obj) rsRetVal obj##DebugPrint(obj##_t *pThis) -#define INTERFACEObjDebugPrint(obj) rsRetVal (*DebugPrint)(obj##_t *pThis) -#define BEGINobjDebugPrint(obj) \ - rsRetVal obj##DebugPrint(obj##_t *pThis) \ - { \ - DEFiRet; \ - -#define CODESTARTobjDebugPrint(obj) \ - ASSERT(pThis != NULL); \ - ISOBJ_TYPE_assert(pThis, obj); \ - -#define ENDobjDebugPrint(obj) \ - RETiRet; \ - } - -/* ------------------------------ object loader system ------------------------------ * - * The following code is the early beginning of a dynamic object loader system. The - * root idea is that all objects will become dynamically loadable libraries over time, - * which is necessary to get a clean plug-in interface where every plugin can access - * rsyslog's rich object model via simple and quite portable methods. - * - * To do so, each object defines one or more interfaces. They are essentially structures - * with function (method) pointers. Anyone interested in calling an object must first - * obtain the interface and can then call through it. - * - * The interface data type must always be called _if_t, as this is expected - * by the macros. Having consitent naming is also easier for the programmer. By default, - * macros create a static variable named like the object in each calling objects - * static data block. - * - * To facilitate moving to this system, I begin to implement some hooks, which - * allows to use interfaces today (when the rest of the infrastructure is not yet - * there). This is in the hope that it will ease migration to the full-fledged system - * once we are ready to work on that. - * rgerhards, 2008-02-21 - */ - -/* this defines the QueryInterface print entry point. Over time, it should be - * present in all objects. - */ -//#define PROTOTYPEObjQueryInterface(obj) rsRetVal obj##QueryInterface(obj##_if_t *pThis) -#define BEGINobjQueryInterface(obj) \ - rsRetVal obj##QueryInterface(obj##_if_t *pIf) \ - { \ - DEFiRet; \ - -#define CODESTARTobjQueryInterface(obj) \ - ASSERT(pIf != NULL); - -#define ENDobjQueryInterface(obj) \ - RETiRet; \ - } - - -/* the following macros should be used to define interfaces inside the - * header files. - */ -#define BEGINinterface(obj) \ - typedef struct obj##_if_s {\ - ifBEGIN; /* This MUST always be the first interface member */ -#define ENDinterface(obj) \ - } obj##_if_t; - -/* the following macro is used to get access to an object (not an instance, - * just the class itself!). It must be called before any of the object's - * methods can be accessed. The MYLIB part is the name of my library, or NULL if - * the caller is a core module. Using the right value here is important to get - * the reference counting correct (object accesses from the same library must - * not be counted because that would cause a library plugin to never unload, as - * its ClassExit() entry points are only called if no object is referenced, which - * would never happen as the library references itself. - * rgerhards, 2008-03-11 - */ -#define CORE_COMPONENT NULL /* use this to indicate this is a core component */ -#define DONT_LOAD_LIB NULL /* do not load a library to obtain object interface (currently same as CORE_COMPONENT) */ -/*#define objUse(objName, MYLIB, FILENAME) \ - obj.UseObj(__FILE__, (uchar*)#objName, MYLIB, (uchar*)FILENAME, (void*) &objName) -*/ -#define objUse(objName, FILENAME) \ - obj.UseObj(__FILE__, (uchar*)#objName, (uchar*)FILENAME, (void*) &objName) -#define objRelease(objName, FILENAME) \ - obj.ReleaseObj(__FILE__, (uchar*)#objName, (uchar*) FILENAME, (void*) &objName) - -/* defines data that must always be present at the very begin of the interface structure */ -#define ifBEGIN \ - int ifVersion; /* must be set to version requested */ \ - int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes; if no, functions can NOT be called! */ - - -/* use the following define some place in your static data (suggested right at - * the beginning - */ -#define DEFobjCurrIf(obj) \ - static obj##_if_t obj = { .ifVersion = obj##CURR_IF_VERSION, .ifIsLoaded = 0 }; - -/* define the prototypes for a class - when we use interfaces, we just have few - * functions that actually need to be non-static. - */ -#define PROTOTYPEObj(obj) \ - PROTOTYPEObjClassInit(obj); \ - PROTOTYPEObjClassExit(obj); - -/* ------------------------------ end object loader system ------------------------------ */ - - -#include "modules.h" -#endif /* #ifndef OBJ_TYPES_H_INCLUDED */ diff --git a/obj.c b/obj.c deleted file mode 100644 index 7a4435ea..00000000 --- a/obj.c +++ /dev/null @@ -1,1342 +0,0 @@ -/* obj.c - * - * This file implements a generic object "class". All other classes can - * use the service of this base class here to include auto-destruction and - * other capabilities in a generic manner. - * - * As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable - * objects. In essence, each object will soon be available via its interface, - * only. Before any object's code is accessed (including global static methods), - * the caller needs to obtain an object interface. To do so, it needs to provide - * the object name and the file where the object is expected to reside in. A - * file may not be given, in which case the object is expected to reside in - * the rsyslog core. The caller than receives an interface pointer which can - * be utilized to access all the object's methods. This method enables rsyslog - * to load library modules on demand. In order to keep overhead low, callers - * should request object interface only once in the object Init function and - * free them when they exit. The only exception is when a caller needs to - * access an object only conditional, in which case a pointer to its interface - * shall be aquired as need first arises but still be released only on exit - * or when there definitely is no further need. The whole idea is to limit - * the very performance-intense act of dynamically loading an objects library. - * Of course, it is possible to violate this suggestion, but than you should - * have very good reasoning to do so. - * - * Please note that there is one trick we need to do. Each object queries - * the object interfaces and it does so via objUse(). objUse, however, is - * part of the obj object's interface (implemented via the file you are - * just reading). So in order to obtain a pointer to objUse, we need to - * call it - obviously not possible. One solution would be that objUse is - * hardcoded into all callers. That, however, would bring us into slight - * trouble with actually dynamically loaded modules, as we should NOT - * rely on the OS loader to resolve symbols back to the caller (this - * is a feature not universally available and highly importable). Of course, - * we can solve this with a pHostQueryEtryPoint() call. It still sounds - * somewhat unnatural to call a regular interface function via a special - * method. So what we do instead is define a special function called - * objGetObjInterface() which delivers our own interface. That function - * than will be defined global and be queriable via pHostQueryEtryPoint(). - * I agree, technically this is much the same, but from an architecture - * point of view it looks cleaner (at least to me). - * - * Please note that there is another egg-hen problem: we use a linked list, - * which is provided by the linkedList object. However, we need to - * initialize the linked list before we can provide the UseObj() - * functionality. That, in turn, would probably be required by the - * linkedList object. So the solution is to use a backdoor just to - * init the linked list and from then on use the usual interfaces. - * - * File begun on 2008-01-04 by RGerhards - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include -#include -#include -#include -#include - -/* how many objects are supported by rsyslogd? */ -#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */ - -#include "rsyslog.h" -#include "syslogd-types.h" -#include "srUtils.h" -#include "obj.h" -#include "stream.h" -#include "modules.h" -#include "errmsg.h" -#include "cfsysline.h" - -/* static data */ -DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */ -DEFobjCurrIf(var) -DEFobjCurrIf(module) -DEFobjCurrIf(errmsg) -static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */ - - -/* cookies for serialized lines */ -#define COOKIE_OBJLINE '<' -#define COOKIE_PROPLINE '+' -#define COOKIE_ENDLINE '>' -#define COOKIE_BLANKLINE '.' - -/* forward definitions */ -static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo); - -/* methods */ - -/* This is a dummy method to be used when a standard method has not been - * implemented by an object. Having it allows us to simply call via the - * jump table without any NULL pointer checks - which gains quite - * some performance. -- rgerhards, 2008-01-04 - */ -static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis) -{ - return RS_RET_NOT_IMPLEMENTED; -} - -/* and now the macro to check if something is not implemented - * must be provided an objInfo_t pointer. - */ -#define objInfoIsImplemented(pThis, method) \ - (pThis->objMethods[method] != objInfoNotImplementedDummy) - -/* construct an object Info object. Each class shall do this on init. The - * resulting object shall be cached during the lifetime of the class and each - * object shall receive a reference. A constructor and destructor MUST be provided for all - * objects, thus they are in the parameter list. - * pszID is the identifying object name and must point to constant pool memory. It is never freed. - */ -static rsRetVal -InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers, - rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), - rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo) -{ - DEFiRet; - int i; - objInfo_t *pThis; - - assert(ppThis != NULL); - - if((pThis = calloc(1, sizeof(objInfo_t))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - pThis->pszID = pszID; - pThis->lenID = strlen((char*)pszID); - pThis->pszName = (uchar*)strdup((char*)pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */ - pThis->iObjVers = iObjVers; - pThis->QueryIF = pQueryIF; - pThis->pModInfo = pModInfo; - - pThis->objMethods[0] = pConstruct; - pThis->objMethods[1] = pDestruct; - for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) { - pThis->objMethods[i] = objInfoNotImplementedDummy; - } - - *ppThis = pThis; - -finalize_it: - RETiRet; -} - - -/* destruct the objInfo object - must be done only when no more instances exist. - * rgerhards, 2008-03-10 - */ -static rsRetVal -InfoDestruct(objInfo_t **ppThis) -{ - DEFiRet; - objInfo_t *pThis; - - assert(ppThis != NULL); - pThis = *ppThis; - assert(pThis != NULL); - - if(pThis->pszName != NULL) - free(pThis->pszName); - free(pThis); - *ppThis = NULL; - - RETiRet; -} - - -/* set a method handler */ -static rsRetVal -InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)) -{ - assert(pThis != NULL); - assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS); - pThis->objMethods[objMethod] = pHandler; - - return RS_RET_OK; -} - -/* destruct the base object properties. - * rgerhards, 2008-01-29 - */ -static rsRetVal -DestructObjSelf(obj_t *pThis) -{ - DEFiRet; - - ISOBJ_assert(pThis); - if(pThis->pszName != NULL) { - free(pThis->pszName); - } - - RETiRet; -} - - -/* --------------- object serializiation / deserialization support --------------- */ - - -/* serialize the header of an object - * pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag) - */ -static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pStrm, strm); - ISOBJ_assert(pObj); - assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); - - /* object cookie and serializer version (so far always 1) */ - CHKiRet(strmWriteChar(pStrm, COOKIE_OBJLINE)); - CHKiRet(strmWrite(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */ - CHKiRet(strmWriteChar(pStrm, ':')); - CHKiRet(strmWriteChar(pStrm, '1')); - - /* object type, version and string length */ - CHKiRet(strmWriteChar(pStrm, ':')); - CHKiRet(strmWrite(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)); - CHKiRet(strmWriteChar(pStrm, ':')); - CHKiRet(strmWriteLong(pStrm, objGetVersion(pObj))); - - /* record trailer */ - CHKiRet(strmWriteChar(pStrm, ':')); - CHKiRet(strmWriteChar(pStrm, '\n')); - -finalize_it: - RETiRet; -} - - -/* begin serialization of an object - * rgerhards, 2008-01-06 - */ -static rsRetVal -BeginSerialize(strm_t *pStrm, obj_t *pObj) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pStrm, strm); - ISOBJ_assert(pObj); - - CHKiRet(strmRecordBegin(pStrm)); - CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj")); - -finalize_it: - RETiRet; -} - - -/* begin serialization of an object's property bag - * Note: a property bag is used to serialize some of an objects - * properties, but not necessarily all. A good example is the queue - * object, which at some stage needs to serialize a number of its - * properties, but not the queue data itself. From the object point - * of view, a property bag can not be used to re-instantiate an object. - * Otherwise, the serialization is exactly the same. - * rgerhards, 2008-01-11 - */ -static rsRetVal -BeginSerializePropBag(strm_t *pStrm, obj_t *pObj) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pStrm, strm); - ISOBJ_assert(pObj); - - CHKiRet(strmRecordBegin(pStrm)); - CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB")); - -finalize_it: - RETiRet; -} - - -/* append a property - */ -static rsRetVal -SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr) -{ - DEFiRet; - uchar *pszBuf = NULL; - size_t lenBuf = 0; - uchar szBuf[64]; - varType_t vType = VARTYPE_NONE; - - ISOBJ_TYPE_assert(pStrm, strm); - assert(pszPropName != NULL); - - /*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/ - /* if we have no user pointer, there is no need to write this property. - * TODO: think if that's the righ point of view - * rgerhards, 2008-01-06 - */ - if(pUsr == NULL) { - ABORT_FINALIZE(RS_RET_OK); - } - - /* TODO: use the stream functions for data conversion here - should be quicker */ - - switch(propType) { - case PROPTYPE_PSZ: - pszBuf = (uchar*) pUsr; - lenBuf = strlen((char*) pszBuf); - vType = VARTYPE_STR; - break; - case PROPTYPE_SHORT: - CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr))); - pszBuf = szBuf; - lenBuf = strlen((char*) szBuf); - vType = VARTYPE_NUMBER; - break; - case PROPTYPE_INT: - CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr))); - pszBuf = szBuf; - lenBuf = strlen((char*) szBuf); - vType = VARTYPE_NUMBER; - break; - case PROPTYPE_LONG: - CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr))); - pszBuf = szBuf; - lenBuf = strlen((char*) szBuf); - vType = VARTYPE_NUMBER; - break; - case PROPTYPE_INT64: - CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr))); - pszBuf = szBuf; - lenBuf = strlen((char*) szBuf); - vType = VARTYPE_NUMBER; - break; - case PROPTYPE_CSTR: - pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr); - lenBuf = rsCStrLen((cstr_t*) pUsr); - vType = VARTYPE_STR; - break; - case PROPTYPE_SYSLOGTIME: - lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d", - ((syslogTime_t*)pUsr)->timeType, - ((syslogTime_t*)pUsr)->year, - ((syslogTime_t*)pUsr)->month, - ((syslogTime_t*)pUsr)->day, - ((syslogTime_t*)pUsr)->hour, - ((syslogTime_t*)pUsr)->minute, - ((syslogTime_t*)pUsr)->second, - ((syslogTime_t*)pUsr)->secfrac, - ((syslogTime_t*)pUsr)->secfracPrecision, - ((syslogTime_t*)pUsr)->OffsetMode, - ((syslogTime_t*)pUsr)->OffsetHour, - ((syslogTime_t*)pUsr)->OffsetMinute); - if(lenBuf > sizeof(szBuf) - 1) - ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL); - vType = VARTYPE_SYSLOGTIME; - pszBuf = szBuf; - break; - default: - dbgprintf("invalid PROPTYPE %d\n", propType); - break; - } - - /* cookie */ - CHKiRet(strmWriteChar(pStrm, COOKIE_PROPLINE)); - /* name */ - CHKiRet(strmWrite(pStrm, pszPropName, strlen((char*)pszPropName))); - CHKiRet(strmWriteChar(pStrm, ':')); - /* type */ - CHKiRet(strmWriteLong(pStrm, (int) vType)); - CHKiRet(strmWriteChar(pStrm, ':')); - /* length */ - CHKiRet(strmWriteLong(pStrm, lenBuf)); - CHKiRet(strmWriteChar(pStrm, ':')); - - /* data */ - CHKiRet(strmWrite(pStrm, (uchar*) pszBuf, lenBuf)); - - /* trailer */ - CHKiRet(strmWriteChar(pStrm, ':')); - CHKiRet(strmWriteChar(pStrm, '\n')); - -finalize_it: - RETiRet; -} - - -/* end serialization of an object. The caller receives a - * standard C string, which he must free when no longer needed. - */ -static rsRetVal -EndSerialize(strm_t *pStrm) -{ - DEFiRet; - - assert(pStrm != NULL); - - CHKiRet(strmWriteChar(pStrm, COOKIE_ENDLINE)); - CHKiRet(strmWrite(pStrm, (uchar*) "End\n", sizeof("END\n") - 1)); - CHKiRet(strmWriteChar(pStrm, COOKIE_BLANKLINE)); - CHKiRet(strmWriteChar(pStrm, '\n')); - - CHKiRet(strmRecordEnd(pStrm)); - -finalize_it: - RETiRet; -} - - -/* define a helper to make code below a bit cleaner (and quicker to write) */ -#define NEXTC CHKiRet(strmReadChar(pStrm, &c))//;dbgprintf("c: %c\n", c); - - -/* de-serialize an embedded, non-octect-counted string. This is useful - * for deserializing the object name inside the header. The string is - * terminated by the first occurence of the ':' character. - * rgerhards, 2008-02-29 - */ -static rsRetVal -objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm) -{ - DEFiRet; - uchar c; - cstr_t *pStr = NULL; - - assert(ppStr != NULL); - - CHKiRet(rsCStrConstruct(&pStr)); - - NEXTC; - while(c != ':') { - CHKiRet(rsCStrAppendChar(pStr, c)); - NEXTC; - } - CHKiRet(rsCStrFinish(pStr)); - - *ppStr = pStr; - -finalize_it: - if(iRet != RS_RET_OK && pStr != NULL) - rsCStrDestruct(&pStr); - - RETiRet; -} - - -/* de-serialize a number */ -static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm) -{ - DEFiRet; - number_t i; - int bIsNegative; - uchar c; - - assert(pNum != NULL); - - NEXTC; - if(c == '-') { - bIsNegative = 1; - NEXTC; - } else { - bIsNegative = 0; - } - - /* we check this so that we get more meaningful error codes */ - if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER); - - i = 0; - while(isdigit(c)) { - i = i * 10 + c - '0'; - NEXTC; - } - - if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); - - if(bIsNegative) - i *= -1; - - *pNum = i; -finalize_it: - RETiRet; -} - - -/* de-serialize a string, length must be provided but may be 0 */ -static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm) -{ - DEFiRet; - int i; - uchar c; - cstr_t *pCStr = NULL; - - assert(ppCStr != NULL); - assert(iLen >= 0); - - CHKiRet(rsCStrConstruct(&pCStr)); - - NEXTC; - for(i = 0 ; i < iLen ; ++i) { - CHKiRet(rsCStrAppendChar(pCStr, c)); - NEXTC; - } - CHKiRet(rsCStrFinish(pCStr)); - - /* check terminator */ - if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); - - *ppCStr = pCStr; - -finalize_it: - if(iRet != RS_RET_OK && pCStr != NULL) - rsCStrDestruct(&pCStr); - - RETiRet; -} - - -/* de-serialize a syslogTime -- rgerhards,2008-01-08 */ -#define GETVAL(var) \ - CHKiRet(objDeserializeNumber(&l, pStrm)); \ - pTime->var = l; -static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm) -{ - DEFiRet; - number_t l; - uchar c; - - assert(pTime != NULL); - - GETVAL(timeType); - GETVAL(year); - GETVAL(month); - GETVAL(day); - GETVAL(hour); - GETVAL(minute); - GETVAL(second); - GETVAL(secfrac); - GETVAL(secfracPrecision); - /* OffsetMode is a single character! */ - NEXTC; pTime->OffsetMode = c; - NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); - GETVAL(OffsetHour); - GETVAL(OffsetMinute); - -finalize_it: - RETiRet; -} -#undef GETVAL - -/* de-serialize an object header - * rgerhards, 2008-01-07 - */ -static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm) -{ - DEFiRet; - number_t oVers; - uchar c; - - assert(ppstrID != NULL); - assert(poVers != NULL); - assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); - - /* check header cookie */ - NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER); - NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); - NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); - NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); - NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER); - NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); - NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); - - /* object type and version */ - CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm)); - CHKiRet(objDeserializeNumber(&oVers, pStrm)); - - /* and now we skip over the rest until the delemiting \n */ - NEXTC; - while(c != '\n') { - NEXTC; - } - - *poVers = oVers; - -finalize_it: - RETiRet; -} - - -/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line - * up until the \n is read. - */ -static rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm) -{ - DEFiRet; - number_t i; - number_t iLen; - uchar c; - - assert(pProp != NULL); - - /* check cookie */ - NEXTC; - if(c != COOKIE_PROPLINE) { - /* oops, we've read one char that does not belong to use - unget it first */ - CHKiRet(strmUnreadChar(pStrm, c)); - ABORT_FINALIZE(RS_RET_NO_PROPLINE); - } - - /* get the property name first */ - CHKiRet(rsCStrConstruct(&pProp->pcsName)); - - NEXTC; - while(c != ':') { - CHKiRet(rsCStrAppendChar(pProp->pcsName, c)); - NEXTC; - } - CHKiRet(rsCStrFinish(pProp->pcsName)); - - /* property type */ - CHKiRet(objDeserializeNumber(&i, pStrm)); - pProp->varType = i; - - /* size (needed for strings) */ - CHKiRet(objDeserializeNumber(&iLen, pStrm)); - - /* we now need to deserialize the value */ - switch(pProp->varType) { - case VARTYPE_STR: - CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm)); - break; - case VARTYPE_NUMBER: - CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm)); - break; - case VARTYPE_SYSLOGTIME: - CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm)); - break; - default: - dbgprintf("invalid VARTYPE %d\n", pProp->varType); - break; - } - - /* we should now be at the end of the line. So the next char must be \n */ - NEXTC; - if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME); - -finalize_it: - RETiRet; -} - - -/* de-serialize an object trailer. This does not get any data but checks if the - * format is ok. - * rgerhards, 2008-01-07 - */ -static rsRetVal objDeserializeTrailer(strm_t *pStrm) -{ - DEFiRet; - uchar c; - - /* check header cookie */ - NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); - -finalize_it: - RETiRet; -} - - - -/* This method tries to recover a serial store if it got out of sync. - * To do so, it scans the line beginning cookies and waits for the object - * cookie. If that is found, control is returned. If the store is exhausted, - * we will receive an RS_RET_EOF error as part of NEXTC, which will also - * terminate this function. So we may either return with somehting that - * looks like a valid object or end of store. - * rgerhards, 2008-01-07 - */ -static rsRetVal objDeserializeTryRecover(strm_t *pStrm) -{ - DEFiRet; - uchar c; - int bWasNL; - int bRun; - - assert(pStrm != NULL); - bRun = 1; - bWasNL = 0; - - while(bRun) { - NEXTC; - if(c == '\n') - bWasNL = 1; - else { - if(bWasNL == 1 && c == COOKIE_OBJLINE) - bRun = 0; /* we found it! */ - else - bWasNL = 0; - } - } - - CHKiRet(strmUnreadChar(pStrm, c)); - -finalize_it: - dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet); - RETiRet; -} - - -/* De-serialize the properties of an object. This includes processing - * of the trailer. Header must already have been processed. - * rgerhards, 2008-01-11 - */ -static rsRetVal objDeserializeProperties(obj_t *pObj, objInfo_t *pObjInfo, strm_t *pStrm) -{ - DEFiRet; - var_t *pVar = NULL; - - ISOBJ_assert(pObj); - ISOBJ_TYPE_assert(pStrm, strm); - ASSERT(pObjInfo != NULL); - - CHKiRet(var.Construct(&pVar)); - CHKiRet(var.ConstructFinalize(pVar)); - - iRet = objDeserializeProperty(pVar, pStrm); - while(iRet == RS_RET_OK) { - CHKiRet(pObjInfo->objMethods[objMethod_SETPROPERTY](pObj, pVar)); - /* re-init var object - TODO: method of var! */ - rsCStrDestruct(&pVar->pcsName); /* no longer needed */ - if(pVar->varType == VARTYPE_STR) { - if(pVar->val.pStr != NULL) - rsCStrDestruct(&pVar->val.pStr); - } - iRet = objDeserializeProperty(pVar, pStrm); - } - - if(iRet != RS_RET_NO_PROPLINE) - FINALIZE; - - CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */ -finalize_it: - if(pVar != NULL) - var.Destruct(&pVar); - - RETiRet; -} - - -/* De-Serialize an object. - * Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning) - * expected object ID (to check against), a fixup function that can modify the object before it is finalized - * and a user pointer that is to be passed to that function in addition to the object. The fixup function - * pointer may be NULL, in which case none is called. - * The caller must destruct the created object. - * rgerhards, 2008-01-07 - */ -static rsRetVal -Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr) -{ - DEFiRet; - rsRetVal iRetLocal; - obj_t *pObj = NULL; - int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ - cstr_t *pstrID = NULL; - objInfo_t *pObjInfo; - - assert(ppObj != NULL); - assert(pszTypeExpected != NULL); - ISOBJ_TYPE_assert(pStrm, strm); - - /* we de-serialize the header. if all goes well, we are happy. However, if - * we experience a problem, we try to recover. We do this by skipping to - * the next object header. This is defined via the line-start cookies. In - * worst case, we exhaust the queue, but then we receive EOF return state, - * from objDeserializeTryRecover(), what will cause us to ultimately give up. - * rgerhards, 2008-07-08 - */ - do { - iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); - if(iRetLocal != RS_RET_OK) { - dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal); - CHKiRet(objDeserializeTryRecover(pStrm)); - } - } while(iRetLocal != RS_RET_OK); - - if(rsCStrSzStrCmp(pstrID, pszTypeExpected, strlen((char*)pszTypeExpected))) // TODO: optimize strlen() - caller shall provide - ABORT_FINALIZE(RS_RET_INVALID_OID); - - CHKiRet(FindObjInfo(pstrID, &pObjInfo)); - - CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj)); - - /* we got the object, now we need to fill the properties */ - CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); - - /* check if we need to call a fixup function that modifies the object - * before it is finalized. -- rgerhards, 2008-01-13 - */ - if(fFixup != NULL) - CHKiRet(fFixup(pObj, pUsr)); - - /* we have a valid object, let's finalize our work and return */ - if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER)) - CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj)); - - *((obj_t**) ppObj) = pObj; - -finalize_it: - if(iRet != RS_RET_OK && pObj != NULL) - free(pObj); // TODO: check if we can call destructor 2008-01-13 rger - - if(pstrID != NULL) - rsCStrDestruct(&pstrID); - - RETiRet; -} - - -/* De-Serialize an object, but treat it as property bag. - * rgerhards, 2008-01-11 - */ -rsRetVal -objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm) -{ - DEFiRet; - rsRetVal iRetLocal; - cstr_t *pstrID = NULL; - int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ - objInfo_t *pObjInfo; - - ISOBJ_assert(pObj); - ISOBJ_TYPE_assert(pStrm, strm); - - /* we de-serialize the header. if all goes well, we are happy. However, if - * we experience a problem, we try to recover. We do this by skipping to - * the next object header. This is defined via the line-start cookies. In - * worst case, we exhaust the queue, but then we receive EOF return state - * from objDeserializeTryRecover(), what will cause us to ultimately give up. - * rgerhards, 2008-07-08 - */ - do { - iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); - if(iRetLocal != RS_RET_OK) { - dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal); - CHKiRet(objDeserializeTryRecover(pStrm)); - } - } while(iRetLocal != RS_RET_OK); - - if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) - ABORT_FINALIZE(RS_RET_INVALID_OID); - - CHKiRet(FindObjInfo(pstrID, &pObjInfo)); - - /* we got the object, now we need to fill the properties */ - CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); - -finalize_it: - if(pstrID != NULL) - rsCStrDestruct(&pstrID); - - RETiRet; -} - - - -/* De-Serialize an object property bag. As a property bag contains only partial properties, - * it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated - * object of the correct type. - * Params: Pointer to object (pObj) - * Pointer to be passed to the function - * The caller must destruct the created object. - * rgerhards, 2008-01-07 - */ -static rsRetVal -DeserializePropBag(obj_t *pObj, strm_t *pStrm) -{ - DEFiRet; - rsRetVal iRetLocal; - cstr_t *pstrID = NULL; - int oVers; - objInfo_t *pObjInfo; - - ISOBJ_assert(pObj); - ISOBJ_TYPE_assert(pStrm, strm); - - /* we de-serialize the header. if all goes well, we are happy. However, if - * we experience a problem, we try to recover. We do this by skipping to - * the next object header. This is defined via the line-start cookies. In - * worst case, we exhaust the queue, but then we receive EOF return state - * from objDeserializeTryRecover(), what will cause us to ultimately give up. - * rgerhards, 2008-07-08 - */ - do { - iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm); - if(iRetLocal != RS_RET_OK) { - dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal); - CHKiRet(objDeserializeTryRecover(pStrm)); - } - } while(iRetLocal != RS_RET_OK); - - if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) - ABORT_FINALIZE(RS_RET_INVALID_OID); - - CHKiRet(FindObjInfo(pstrID, &pObjInfo)); - - /* we got the object, now we need to fill the properties */ - CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); - -finalize_it: - if(pstrID != NULL) - rsCStrDestruct(&pstrID); - - RETiRet; -} - -#undef NEXTC /* undef helper macro */ - - -/* --------------- end object serializiation / deserialization support --------------- */ - - -/* set the object (instance) name - * rgerhards, 2008-01-29 - * TODO: change the naming to a rsCStr obj! (faster) - */ -static rsRetVal -SetName(obj_t *pThis, uchar *pszName) -{ - DEFiRet; - - if(pThis->pszName != NULL) - free(pThis->pszName); - - pThis->pszName = (uchar*) strdup((char*) pszName); - - if(pThis->pszName == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - -finalize_it: - RETiRet; -} - - -/* get the object (instance) name - * Note that we use a non-standard calling convention. Thus function must never - * fail, else we run into real big problems. So it must make sure that at least someting - * is returned. - * rgerhards, 2008-01-30 - */ -static uchar * -GetName(obj_t *pThis) -{ - uchar *ret; - uchar szName[128]; - - BEGINfunc - ISOBJ_assert(pThis); - - if(pThis->pszName == NULL) { - snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis); - SetName(pThis, szName); - /* looks strange, but we NEED to re-check because if there was an - * error in objSetName(), the pointer may still be NULL - */ - if(pThis->pszName == NULL) { - ret = objGetClassName(pThis); - } else { - ret = pThis->pszName; - } - } else { - ret = pThis->pszName; - } - - ENDfunc - return ret; -} - - -/* Find the objInfo object for the current object - * rgerhards, 2008-02-29 - */ -static rsRetVal -FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo) -{ - DEFiRet; - int bFound; - int i; - - assert(pstrOID != NULL); - assert(ppInfo != NULL); - - bFound = 0; - i = 0; - while(!bFound && i < OBJ_NUM_IDS) { -#if 0 -RUNLOG_VAR("%d", i); -if(arrObjInfo[i] != NULL) { -RUNLOG_VAR("%p", arrObjInfo[i]->pszID); -RUNLOG_VAR("%s", arrObjInfo[i]->pszID); -} -#endif - if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) { - bFound = 1; - break; - } - ++i; - } - - if(!bFound) - ABORT_FINALIZE(RS_RET_NOT_FOUND); - - *ppInfo = arrObjInfo[i]; - -finalize_it: - if(iRet == RS_RET_OK) { - /* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/ - /*EMPTY BY INTENSION*/; - } else { - dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet); - } - - RETiRet; -} - - -/* register a classes' info pointer, so that we can reference it later, if needed to - * (e.g. for de-serialization support). - * rgerhards, 2008-01-07 - * In this function, we look for a free space in the object table. While we do so, we - * also detect if the same object has already been registered, which is not valid. - * rgerhards, 2008-02-29 - */ -static rsRetVal -RegisterObj(uchar *pszObjName, objInfo_t *pInfo) -{ - DEFiRet; - int bFound; - int i; - - assert(pszObjName != NULL); - assert(pInfo != NULL); - - bFound = 0; - i = 0; - while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) { - if( arrObjInfo[i] != NULL - && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { - bFound = 1; - break; - } - ++i; - } - - if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED); - if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE); - - arrObjInfo[i] = pInfo; - /* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */ - -finalize_it: - if(iRet != RS_RET_OK) { - errmsg.LogError(NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet); - } - - RETiRet; -} - - -/* deregister a classes' info pointer, usually called because the class is unloaded. - * After deregistration, the class can no longer be accessed, except if it is reloaded. - * rgerhards, 2008-03-10 - */ -static rsRetVal -UnregisterObj(uchar *pszObjName) -{ - DEFiRet; - int bFound; - int i; - - assert(pszObjName != NULL); - - bFound = 0; - i = 0; - while(!bFound && i < OBJ_NUM_IDS) { - if( arrObjInfo[i] != NULL - && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { - bFound = 1; - break; - } - ++i; - } - - if(!bFound) - ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED); - - InfoDestruct(&arrObjInfo[i]); - /* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */ - -finalize_it: - if(iRet != RS_RET_OK) { - dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet); - } - - RETiRet; -} - - -/* This function shall be called by anyone who would like to use an object. It will - * try to locate the object, load it into memory if not already present and return - * a pointer to the objects interface. - * rgerhards, 2008-02-29 - */ -static rsRetVal -UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) -{ - DEFiRet; - cstr_t *pStr = NULL; - objInfo_t *pObjInfo; - - - /* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ - - if(pIf->ifIsLoaded == 1) { - ABORT_FINALIZE(RS_RET_OK); /* we are already set */ - } - if(pIf->ifIsLoaded == 2) { - ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */ - } - - /* we must be careful that we do not enter in infinite loop if an error occurs during - * loading a module. ModLoad emits an error message in such cases and that potentially - * can trigger the same code here. So we initially set the module state to "load error" - * and set it to "fully initialized" when the load succeeded. It's a bit hackish, but - * looks like a good solution. -- rgerhards, 2008-03-07 - */ - pIf->ifIsLoaded = 2; - - CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); - iRet = FindObjInfo(pStr, &pObjInfo); - if(iRet == RS_RET_NOT_FOUND) { - /* in this case, we need to see if we can dynamically load the object */ - if(pObjFile == NULL) { - FINALIZE; /* no chance, we have lost... */ - } else { - CHKiRet(module.Load(pObjFile)); - /* NOW, we must find it or we have a problem... */ - CHKiRet(FindObjInfo(pStr, &pObjInfo)); - } - } else if(iRet != RS_RET_OK) { - FINALIZE; /* give up */ - } - - /* if we reach this point, we have a valid pObjInfo */ - if(pObjFile != NULL) { /* NULL means core module */ - module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */ - } - - CHKiRet(pObjInfo->QueryIF(pIf)); - pIf->ifIsLoaded = 1; /* we are happy */ - -finalize_it: - if(pStr != NULL) - rsCStrDestruct(&pStr); - - RETiRet; -} - - -/* This function shall be called when a caller is done with an object. Its primary - * purpose is to keep the reference count correct, which is highly important for - * modules residing in loadable modules. - * rgerhards, 2008-03-10 - */ -static rsRetVal -ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) -{ - DEFiRet; - cstr_t *pStr = NULL; - objInfo_t *pObjInfo; - - - dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); - - if(pObjFile == NULL) - FINALIZE; /* if it is not a lodable module, we do not need to do anything... */ - - if(pIf->ifIsLoaded == 0) { - ABORT_FINALIZE(RS_RET_OK); /* we are already set */ /* TODO: flag an error? */ - } - if(pIf->ifIsLoaded == 2) { - pIf->ifIsLoaded = 0; /* clean up */ - ABORT_FINALIZE(RS_RET_OK); /* we had a load error and can not continue */ - } - - CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); - CHKiRet(FindObjInfo(pStr, &pObjInfo)); - - /* if we reach this point, we have a valid pObjInfo */ - //if(pObjInfo->pModInfo != NULL) { /* NULL means core module */ - module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */ - - pIf->ifIsLoaded = 0; /* indicated "no longer valid" */ - -finalize_it: - if(pStr != NULL) - rsCStrDestruct(&pStr); - - RETiRet; -} - - -/* queryInterface function - * rgerhards, 2008-02-29 - */ -BEGINobjQueryInterface(obj) -CODESTARTobjQueryInterface(obj) - if(pIf->ifVersion != objCURR_IF_VERSION) { /* check for current version, increment on each change */ - ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); - } - - /* ok, we have the right interface, so let's fill it - * Please note that we may also do some backwards-compatibility - * work here (if we can support an older interface version - that, - * of course, also affects the "if" above). - */ - pIf->UseObj = UseObj; - pIf->ReleaseObj = ReleaseObj; - pIf->InfoConstruct = InfoConstruct; - pIf->DestructObjSelf = DestructObjSelf; - pIf->BeginSerializePropBag = BeginSerializePropBag; - pIf->InfoSetMethod = InfoSetMethod; - pIf->BeginSerialize = BeginSerialize; - pIf->SerializeProp = SerializeProp; - pIf->EndSerialize = EndSerialize; - pIf->RegisterObj = RegisterObj; - pIf->UnregisterObj = UnregisterObj; - pIf->Deserialize = Deserialize; - pIf->DeserializePropBag = DeserializePropBag; - pIf->SetName = SetName; - pIf->GetName = GetName; -finalize_it: -ENDobjQueryInterface(obj) - - -/* This function returns a pointer to our own interface. It is used as the - * hook that every object (including dynamically loaded ones) can use to - * obtain a pointer to our interface which than can be used to obtain - * pointers to any other interface in the system. This function must be - * externally visible because of its special nature. - * rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)] - */ -rsRetVal -objGetObjInterface(obj_if_t *pIf) -{ - DEFiRet; - assert(pIf != NULL); - objQueryInterface(pIf); - RETiRet; -} - - -/* exit our class - * rgerhards, 2008-03-11 - */ -rsRetVal -objClassExit(void) -{ - DEFiRet; - /* release objects we no longer need */ - objRelease(var, CORE_COMPONENT); - objRelease(module, CORE_COMPONENT); - objRelease(errmsg, CORE_COMPONENT); - - /* TODO: implement the class exits! */ -#if 0 - errmsgClassInit(pModInfo); - cfsyslineInit(pModInfo); - varClassInit(pModInfo); -#endif - moduleClassExit(); - RETiRet; -} - - -/* initialize our own class - * Please note that this also initializes those classes that we rely on. - * Though this is a bit dirty, we need to do it - otherwise we can't get - * around that bootstrap problem. We need to face the fact the the obj - * class is a little different from the rest of the system, as it provides - * the core class loader functionality. - * rgerhards, 2008-02-29 - */ -rsRetVal -objClassInit(modInfo_t *pModInfo) -{ - DEFiRet; - int i; - - /* first, initialize the object system itself. This must be done - * before any other object is created. - */ - for(i = 0 ; i < OBJ_NUM_IDS ; ++i) { - arrObjInfo[i] = NULL; - } - - /* request objects we use */ - CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ - - /* init classes we use (limit to as few as possible!) */ - CHKiRet(errmsgClassInit(pModInfo)); - CHKiRet(cfsyslineInit()); - CHKiRet(varClassInit(pModInfo)); - CHKiRet(moduleClassInit(pModInfo)); - CHKiRet(objUse(var, CORE_COMPONENT)); - CHKiRet(objUse(module, CORE_COMPONENT)); - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - -finalize_it: - RETiRet; -} - -/* vi:set ai: - */ diff --git a/obj.h b/obj.h deleted file mode 100644 index 29ad2ae4..00000000 --- a/obj.h +++ /dev/null @@ -1,124 +0,0 @@ -/* Definition of the generic obj class module. - * - * This module relies heavily on preprocessor macros in order to - * provide fast execution time AND ease of use. - * - * Each object that uses this base class MUST provide a constructor with - * the following interface: - * - * Destruct(pThis); - * - * A constructor is not necessary (except for some features, e.g. de-serialization). - * If it is provided, it is a three-part constructor (to handle all cases with a - * generic interface): - * - * Construct(&pThis); - * SetProperty(pThis, property_t *); - * ConstructFinalize(pThis); - * - * SetProperty() and ConstructFinalize() may also be called on an object - * instance which has been Construct()'ed outside of this module. - * - * pThis always references to a pointer of the object. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef OBJ_H_INCLUDED -#define OBJ_H_INCLUDED - -#include "obj-types.h" -#include "var.h" -#include "stream.h" - -/* macros */ -/* the following one is a helper that prevents us from writing the - * ever-same code at the end of Construct() - */ -#define OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ - if(iRet == RS_RET_OK) { \ - *ppThis = pThis; \ - } else { \ - if(pThis != NULL) \ - free(pThis); \ - } - -#define objSerializeSCALAR_VAR(strm, propName, propType, var) \ - CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &var)); -#define objSerializeSCALAR(strm, propName, propType) \ - CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &pThis->propName)); -#define objSerializePTR(strm, propName, propType) \ - CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) pThis->propName)); -#define DEFobjStaticHelpers \ - static objInfo_t *pObjInfoOBJ = NULL; \ - DEFobjCurrIf(obj) - - -#define objGetClassName(pThis) (((obj_t*) (pThis))->pObjInfo->pszID) -#define objGetVersion(pThis) (((obj_t*) (pThis))->pObjInfo->iObjVers) -/* the next macro MUST be called in Constructors: */ -#ifndef NDEBUG /* this means if debug... */ -# define objConstructSetObjInfo(pThis) \ - ASSERT(((obj_t*) (pThis))->pObjInfo == NULL); \ - ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ - ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE -#else -# define objConstructSetObjInfo(pThis) ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ -#endif -#define objDestruct(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DESTRUCT])(&pThis) -#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE]) -#define objGetSeverity(pThis, piSever) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_GETSEVERITY])(pThis, piSever) -#define objDebugPrint(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DEBUGPRINT])(pThis) - -#define OBJSetMethodHandler(methodID, pHdlr) \ - CHKiRet(obj.InfoSetMethod(pObjInfoOBJ, methodID, (rsRetVal (*)(void*)) pHdlr)) - -/* interfaces */ -BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */ - rsRetVal (*UseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); - rsRetVal (*ReleaseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); - rsRetVal (*InfoConstruct)(objInfo_t **ppThis, uchar *pszID, int iObjVers, - rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), - rsRetVal (*pQueryIF)(interface_t*), modInfo_t*); - rsRetVal (*DestructObjSelf)(obj_t *pThis); - rsRetVal (*BeginSerializePropBag)(strm_t *pStrm, obj_t *pObj); - rsRetVal (*InfoSetMethod)(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)); - rsRetVal (*BeginSerialize)(strm_t *pStrm, obj_t *pObj); - rsRetVal (*SerializeProp)(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr); - rsRetVal (*EndSerialize)(strm_t *pStrm); - rsRetVal (*RegisterObj)(uchar *pszObjName, objInfo_t *pInfo); - rsRetVal (*UnregisterObj)(uchar *pszObjName); - rsRetVal (*Deserialize)(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr); - rsRetVal (*DeserializePropBag)(obj_t *pObj, strm_t *pStrm); - rsRetVal (*SetName)(obj_t *pThis, uchar *pszName); - uchar * (*GetName)(obj_t *pThis); -ENDinterface(obj) -#define objCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ - - -/* prototypes */ -/* the following define *is* necessary, because it provides the root way of obtaining - * interfaces (at some place we need to start our query... - */ -rsRetVal objGetObjInterface(obj_if_t *pIf); -PROTOTYPEObjClassInit(obj); -PROTOTYPEObjClassExit(obj); - -#endif /* #ifndef OBJ_H_INCLUDED */ diff --git a/objomsr.c b/objomsr.c deleted file mode 100644 index 6a617ad1..00000000 --- a/objomsr.c +++ /dev/null @@ -1,145 +0,0 @@ -/* objomsr.c - * Implementation of the omsr (omodStringRequest) object. - * - * File begun on 2007-07-27 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include -#include -#include -#include - -#include "rsyslog.h" -#include "objomsr.h" - - -/* destructor - */ -rsRetVal OMSRdestruct(omodStringRequest_t *pThis) -{ - int i; - - assert(pThis != NULL); - /* free the strings */ - if(pThis->ppTplName != NULL) { - for(i = 0 ; i < pThis->iNumEntries ; ++i) { - if(pThis->ppTplName[i] != NULL) { - free(pThis->ppTplName[i]); - } - } - free(pThis->ppTplName); - } - if(pThis->piTplOpts != NULL) - free(pThis->piTplOpts); - free(pThis); - - return RS_RET_OK; -} - - -/* constructor - */ -rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries) -{ - omodStringRequest_t *pThis; - DEFiRet; - - assert(ppThis != NULL); - assert(iNumEntries >= 0); - if((pThis = calloc(1, sizeof(omodStringRequest_t))) == NULL) { - iRet = RS_RET_OUT_OF_MEMORY; - goto abort_it; - } - - /* got the structure, so fill it */ - pThis->iNumEntries = iNumEntries; - /* allocate string for template name array. The individual strings will be - * allocated as the code progresses (we do not yet know the string sizes) - */ - if((pThis->ppTplName = calloc(iNumEntries, sizeof(uchar*))) == NULL) { - OMSRdestruct(pThis); - pThis = NULL; - iRet = RS_RET_OUT_OF_MEMORY; - goto abort_it; - } - /* allocate the template options array. */ - if((pThis->piTplOpts = calloc(iNumEntries, sizeof(int))) == NULL) { - OMSRdestruct(pThis); - pThis = NULL; - iRet = RS_RET_OUT_OF_MEMORY; - goto abort_it; - } - -abort_it: - *ppThis = pThis; - RETiRet; -} - -/* set a template name and option to the object. Index must be given. The pTplName must be - * pointing to memory that can be freed. If in doubt, the caller must strdup() the value. - */ -rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts) -{ - assert(pThis != NULL); - assert(pTplName != NULL); - assert(iEntry < pThis->iNumEntries); - - if(pThis->ppTplName[iEntry] != NULL) - free(pThis->ppTplName[iEntry]); - pThis->ppTplName[iEntry] = pTplName; - pThis->piTplOpts[iEntry] = iTplOpts; - - return RS_RET_OK; -} - - -/* get number of entries for this object - */ -int OMSRgetEntryCount(omodStringRequest_t *pThis) -{ - assert(pThis != NULL); - return pThis->iNumEntries; -} - - -/* return data for a specific entry. All data returned is - * read-only and lasts only as long as the object lives. If the caller - * needs it for an extended period of time, the caller must copy the - * strings. Please note that the string pointer may be NULL, which is the - * case when it was never set. - */ -int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts) -{ - assert(pThis != NULL); - assert(ppTplName != NULL); - assert(piTplOpts != NULL); - assert(iEntry < pThis->iNumEntries); - - *ppTplName = pThis->ppTplName[iEntry]; - *piTplOpts = pThis->piTplOpts[iEntry]; - - return RS_RET_OK; -} -/* - * vi:set ai: - */ diff --git a/objomsr.h b/objomsr.h deleted file mode 100644 index 9fdddf69..00000000 --- a/objomsr.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Definition of the omsr (omodStringRequest) object. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef OBJOMSR_H_INCLUDED -#define OBJOMSR_H_INCLUDED - -/* define flags for required template options */ -#define OMSR_NO_RQD_TPL_OPTS 0 -#define OMSR_RQD_TPL_OPT_SQL 1 -/* next option is 2, 4, 8, ... */ - -struct omodStringRequest_s { /* strings requested by output module for doAction() */ - int iNumEntries; /* number of array entries for data elements below */ - uchar **ppTplName; /* pointer to array of template names */ - int *piTplOpts;/* pointer to array of check-options when pulling template */ -}; -typedef struct omodStringRequest_s omodStringRequest_t; - -/* prototypes */ -rsRetVal OMSRdestruct(omodStringRequest_t *pThis); -rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries); -rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts); -int OMSRgetEntryCount(omodStringRequest_t *pThis); -int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts); - -#endif /* #ifndef OBJOMSR_H_INCLUDED */ diff --git a/parse.c b/parse.c index 171e5355..58458d62 100644 --- a/parse.c +++ b/parse.c @@ -3,7 +3,7 @@ * * begun 2005-09-15 rgerhards * - * Copyright 2005 + * Copyright 2005-2008 * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. * * This file is part of rsyslog. diff --git a/plugins/ommysql/Makefile.am b/plugins/ommysql/Makefile.am index 329c2119..d5433a40 100644 --- a/plugins/ommysql/Makefile.am +++ b/plugins/ommysql/Makefile.am @@ -1,7 +1,7 @@ pkglib_LTLIBRARIES = ommysql.la ommysql_la_SOURCES = ommysql.c ommysql.h -ommysql_la_CPPFLAGS = -I$(top_srcdir) $(mysql_cflags) $(pthreads_cflags) $(rsrt_cflags) +ommysql_la_CPPFLAGS = $(rsrt_cflags) $(mysql_cflags) $(pthreads_cflags) ommysql_la_LDFLAGS = -module -avoid-version ommysql_la_LIBADD = $(mysql_libs) diff --git a/rsyslog.h b/rsyslog.h deleted file mode 100644 index c73c659c..00000000 --- a/rsyslog.h +++ /dev/null @@ -1,270 +0,0 @@ -/* Header file with global definitions for the whole - * rsyslog project (including all subprojects like - * rfc3195d). - * Begun 2005-09-15 RGerhards - * - * Copyright (C) 2005 by Rainer Gerhards and Adiscon GmbH - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. -*/ -#ifndef INCLUDED_RSYSLOG_H -#define INCLUDED_RSYSLOG_H - -/* ############################################################# * - * # Config Settings # * - * ############################################################# */ -#define RS_STRINGBUF_ALLOC_INCREMENT 128 - -/* ############################################################# * - * # End Config Settings # * - * ############################################################# */ - -#ifndef NOLARGEFILE -# undef _LARGEFILE_SOURCE -# undef _LARGEFILE64_SOURCE -# undef _FILE_OFFSET_BITS -# define _LARGEFILE_SOURCE -# define _LARGEFILE64_SOURCE -# define _FILE_OFFSET_BITS 64 -#endif - - -/* some universal 64 bit define... */ -typedef long long int64; -typedef long long unsigned uint64; -typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ - -#ifdef __hpux -typedef unsigned int u_int32_t; /* TODO: is this correct? */ -typedef int socklen_t; -#endif - -/* settings for flow control - * TODO: is there a better place for them? -- rgerhards, 2008-03-14 - */ -typedef enum { - eFLOWCTL_NO_DELAY = 0, /**< UDP and other non-delayable sources */ - eFLOWCTL_LIGHT_DELAY = 1, /**< some light delay possible, but no extended period of time */ - eFLOWCTL_FULL_DELAY = 2 /**< delay possible for extended period of time */ -} flowControl_t; - - -/* The error codes below are orginally "borrowed" from - * liblogging. As such, we reserve values up to -2999 - * just in case we need to borrow something more ;) -*/ -enum rsRetVal_ /** return value. All methods return this if not specified otherwise */ -{ - RS_RET_NOT_IMPLEMENTED = -7, /**< implementation is missing (probably internal error or lazyness ;)) */ - RS_RET_OUT_OF_MEMORY = -6, /**< memory allocation failed */ - RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,/**< the caller provided a buffer, but the called function sees the size of this buffer is too small - operation not carried out */ - RS_RET_TRUE = -1, /**< to indicate a true state (can be used as TRUE, legacy) */ - RS_RET_FALSE = -2, /**< to indicate a false state (can be used as FALSE, legacy) */ - RS_RET_NO_IRET = -8, /**< This is a trick for the debuging system - it means no iRet is provided */ - RS_RET_ERR = -3000, /**< generic failure */ - RS_TRUNCAT_TOO_LARGE = -3001, /**< truncation operation where too many chars should be truncated */ - RS_RET_FOUND_AT_STRING_END = -3002, /**< some value found, but at the last pos of string */ - RS_RET_NOT_FOUND = -3003, /**< some requested value not found */ - RS_RET_MISSING_TRAIL_QUOTE = -3004, /**< an expected trailing quote is missing */ - RS_RET_NO_DIGIT = -3005, /**< an digit was expected, but none found (mostly parsing) */ - RS_RET_NO_MORE_DATA = -3006, /**< insufficient data, e.g. end of string during parsing */ - RS_RET_INVALID_IP = -3007, /**< invalid ip found where valid was expected */ - RS_RET_OBJ_CREATION_FAILED = - 3008, /**< the creation of an object failed (no details available) */ - RS_RET_PARAM_ERROR = -1000, /**< invalid parameter in call to function */ - RS_RET_MISSING_INTERFACE = -1001,/**< interface version mismatch, required missing */ - RS_RET_INVALID_CORE_INTERFACE = -1002,/**< interface provided by host invalid, can not be used */ - RS_RET_ENTRY_POINT_NOT_FOUND = -1003,/**< a requested entry point was not found */ - RS_RET_MODULE_ENTRY_POINT_NOT_FOUND = -1004,/**< a entry point requested from a module was not present in it */ - RS_RET_OBJ_NOT_AVAILABLE = -1005,/**< something could not be completed because the required object is not available*/ - RS_RET_LOAD_ERROR = -1006,/**< we had an error loading the object/interface and can not continue */ - RS_RET_MODULE_STILL_REFERENCED = -1007,/**< module could not be unloaded because it still is referenced by someone */ - RS_RET_OBJ_UNKNOWN = -1008,/**< object is unknown where required */ - RS_RET_OBJ_NOT_REGISTERED = -1009,/**< tried to unregister an object that is not registered */ - /* return states for config file processing */ - RS_RET_NONE = -2000, /**< some value is not available - not necessarily an error */ - RS_RET_CONFLINE_UNPROCESSED = -2001,/**< config line was not processed, pass to other module */ - RS_RET_DISCARDMSG = -2002, /**< discard message (no error state, processing request!) */ - RS_RET_INCOMPATIBLE = -2003, /**< function not compatible with requested feature */ - RS_RET_NOENTRY = -2004, /**< do not create an entry for (whatever) - not necessary an error */ - RS_RET_NO_SQL_STRING = -2005, /**< string is not suitable for use as SQL */ - RS_RET_DISABLE_ACTION = -2006, /**< action requests that it be disabled */ - RS_RET_SUSPENDED = -2007, /**< something was suspended, not neccesarily an error */ - RS_RET_RQD_TPLOPT_MISSING = -2008,/**< a required template option is missing */ - RS_RET_INVALID_VALUE = -2009,/**< some value is invalid (e.g. user-supplied data) */ - RS_RET_INVALID_INT = -2010,/**< invalid integer */ - RS_RET_INVALID_CMD = -2011,/**< invalid command */ - RS_RET_VAL_OUT_OF_RANGE = -2012, /**< value out of range */ - RS_RET_FOPEN_FAILURE = -2013, /**< failure during fopen, for example file not found - see errno */ - RS_RET_END_OF_LINKEDLIST = -2014, /**< end of linked list, not an error, but a status */ - RS_RET_CHAIN_NOT_PERMITTED = -2015, /**< chaining (e.g. of config command handlers) not permitted */ - RS_RET_INVALID_PARAMS = -2016,/**< supplied parameters are invalid */ - RS_RET_EMPTY_LIST = -2017, /**< linked list is empty */ - RS_RET_FINISHED = -2018, /**< some opertion is finished, not an error state */ - RS_RET_INVALID_SOURCE = -2019, /**< source (address) invalid for some reason */ - RS_RET_ADDRESS_UNKNOWN = -2020, /**< an address is unknown - not necessarily an error */ - RS_RET_MALICIOUS_ENTITY = -2021, /**< there is an malicious entity involved */ - RS_RET_NO_KERNEL_LOGSRC = -2022, /**< no source for kernel logs can be obtained */ - RS_RET_TCP_SEND_ERROR = -2023, /**< error during TCP send process */ - RS_RET_GSS_SEND_ERROR = -2024, /**< error during GSS (via TCP) send process */ - RS_RET_TCP_SOCKCREATE_ERR = -2025, /**< error during creation of TCP socket */ - RS_RET_GSS_SENDINIT_ERROR = -2024, /**< error during GSS (via TCP) send initialization process */ - RS_RET_QUEUE_FULL = -2025, /**< queue is full, operation could not be completed */ - RS_RET_EOF = -2026, /**< end of file reached, not necessarily an error */ - RS_RET_IO_ERROR = -2027, /**< some kind of IO error happened */ - RS_RET_INVALID_OID = -2028, /**< invalid object ID */ - RS_RET_INVALID_HEADER = -2029, /**< invalid header */ - RS_RET_INVALID_HEADER_VERS = -2030, /**< invalid header version */ - RS_RET_INVALID_DELIMITER = -2031, /**< invalid delimiter, e.g. between params */ - RS_RET_INVALID_PROPFRAME = -2032, /**< invalid framing in serialized property */ - RS_RET_NO_PROPLINE = -2033, /**< line is not a property line */ - RS_RET_INVALID_TRAILER = -2034, /**< invalid trailer */ - RS_RET_VALUE_TOO_LOW = -2035, /**< a provided value is too low */ - RS_RET_FILE_PREFIX_MISSING = -2036, /**< a required file prefix (parameter?) is missing */ - RS_RET_INVALID_HEADER_RECTYPE = -2037, /**< invalid record type in header or invalid header */ - RS_RET_QTYPE_MISMATCH = -2038, /**< different qType when reading back a property type */ - RS_RET_NO_FILE_ACCESS = -2039, /**< covers EACCES error on file open() */ - RS_RET_FILE_NOT_FOUND = -2040, /**< file not found */ - RS_RET_TIMED_OUT = -2041, /**< timeout occured (not necessarily an error) */ - RS_RET_QSIZE_ZERO = -2042, /**< queue size is zero where this is not supported */ - RS_RET_ALREADY_STARTING = -2043, /**< something (a thread?) is already starting - not necessarily an error */ - RS_RET_NO_MORE_THREADS = -2044, /**< no more threads available, not necessarily an error */ - RS_RET_NO_FILEPREFIX = -2045, /**< file prefix is not specified where one is needed */ - RS_RET_CONFIG_ERROR = -2046, /**< there is a problem with the user-provided config settigs */ - RS_RET_OUT_OF_DESRIPTORS = -2047, /**< a descriptor table's space has been exhausted */ - RS_RET_NO_DRIVERS = -2048, /**< a required drivers missing */ - RS_RET_NO_DRIVERNAME = -2049, /**< driver name missing where one was required */ - RS_RET_EOS = -2050, /**< end of stream (of whatever) */ - RS_RET_SYNTAX_ERROR = -2051, /**< syntax error, eg. during parsing */ - RS_RET_INVALID_OCTAL_DIGIT = -2052, /**< invalid octal digit during parsing */ - RS_RET_INVALID_HEX_DIGIT = -2053, /**< invalid hex digit during parsing */ - RS_RET_INTERFACE_NOT_SUPPORTED = -2054, /**< interface not supported */ - RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */ - RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */ - RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */ - RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ - RS_RET_INVALID_NUMBER = -2059, /**< number invalid during parsing */ - RS_RET_NOT_A_NUMBER = -2060, /**< e.g. conversion impossible because the string is not a number */ - RS_RET_OBJ_ALREADY_REGISTERED = -2061, /**< object (name) is already registered */ - RS_RET_OBJ_REGISTRY_OUT_OF_SPACE = -2062, /**< the object registry has run out of space */ - RS_RET_HOST_NOT_PERMITTED = -2063, /**< a host is not permitted to perform an action it requested */ - RS_RET_MODULE_LOAD_ERR = -2064, /**< module could not be loaded */ - RS_RET_MODULE_LOAD_ERR_PATHLEN = -2065, /**< module could not be loaded - path to long */ - RS_RET_MODULE_LOAD_ERR_DLOPEN = -2066, /**< module could not be loaded - problem in dlopen() */ - RS_RET_MODULE_LOAD_ERR_NO_INIT = -2067, /**< module could not be loaded - init() missing */ - RS_RET_MODULE_LOAD_ERR_INIT_FAILED = -2068, /**< module could not be loaded - init() failed */ - RS_RET_NO_SOCKET = -2069, /**< socket could not be obtained or was not provided */ - RS_RET_SMTP_ERROR = -2070, /**< error during SMTP transation */ - RS_RET_MAIL_NO_TO = -2071, /**< recipient for mail destination is missing */ - RS_RET_MAIL_NO_FROM = -2072, /**< sender for mail destination is missing */ - RS_RET_INVALID_PRI = -2073, /**< PRI value is invalid */ - - /* RainerScript error messages (range 1000.. 1999) */ - RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ - - /* some generic error/status codes */ - RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */ - RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */ - RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */ - RS_RET_OK = 0 /**< operation successful */ -}; -typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ - -/* some helpful macros to work with srRetVals. - * Be sure to call the to-be-returned variable always "iRet" and - * the function finalizer always "finalize_it". - */ -#define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it -/* macro below is to be used if we need our own handling, eg for cleanup */ -#define CHKiRet_Hdlr(code) if((iRet = code) != RS_RET_OK) -/* macro below is to handle failing malloc/calloc/strdup... which we almost always handle in the same way... */ -#define CHKmalloc(operation) if((operation) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY) -/* macro below is used in conjunction with CHKiRet_Hdlr, else use ABORT_FINALIZE */ -#define FINALIZE goto finalize_it; -#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK -#define RETiRet do{ ENDfuncIRet return iRet; }while(0) - -#define ABORT_FINALIZE(errCode) \ - do { \ - iRet = errCode; \ - goto finalize_it; \ - } while (0) - -/** Object ID. These are for internal checking. Each - * object is assigned a specific ID. This is contained in - * all Object structs (just like C++ RTTI). We can use - * this field to see if we have been passed a correct ID. - * Other than that, there is currently no other use for - * the object id. - */ -enum rsObjectID -{ - OIDrsFreed = -1, /**< assigned, when an object is freed. If this - * is seen during a method call, this is an - * invalid object pointer! - */ - OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */ - /* The 0x3412 is a debug aid. It helps us find object IDs in memory - * dumps (on X86, this is 1234 in the dump ;) - * If you are on an embedded device and you would like to save space - * make them 1 byte only. - */ - OIDrsCStr = 0x34120001, - OIDrsPars = 0x34120002 -}; -typedef enum rsObjectID rsObjID; - -/* support to set object types */ -#ifdef NDEBUG -#define rsSETOBJTYPE(pObj, type) -#define rsCHECKVALIDOBJECT(x, type) -#else -#define rsSETOBJTYPE(pObj, type) pObj->OID = type; -#define rsCHECKVALIDOBJECT(x, type) {assert(x != NULL); assert(x->OID == type);} -#endif - -/** - * This macro should be used to free objects. - * It aids in interpreting dumps during debugging. - */ -#ifdef NDEBUG -#define RSFREEOBJ(x) free(x) -#else -#define RSFREEOBJ(x) {(x)->OID = OIDrsFreed; free(x);} -#endif - -/* get rid of the unhandy "unsigned char" - */ -typedef unsigned char uchar; - -/* for the time being, we do our own portability handling here. It - * looks like autotools either does not yet support checks for it, or - * I wasn't smart enough to find them ;) rgerhards, 2007-07-18 - */ -#ifndef __GNUC__ -# define __attribute__(x) /*NOTHING*/ -#endif - -/* The following prototype is convenient, even though it may not be the 100% correct place.. -- rgerhards 2008-01-07 */ -void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2))); - -#include "debug.h" - -#endif /* multi-include protection */ -/* - * vi:set ai: - */ diff --git a/runtime/Makefile.am b/runtime/Makefile.am index cd8a19c2..048ef411 100644 --- a/runtime/Makefile.am +++ b/runtime/Makefile.am @@ -1,9 +1,36 @@ sbin_PROGRAMS = man_MANS = noinst_LTLIBRARIES = librsyslog.la +pkglib_LTLIBRARIES = #pkglib_LTLIBRARIES = librsyslog.la librsyslog_la_SOURCES = \ + rsyslog.h \ + atomic.h \ + syslogd-types.h \ + module-template.h \ + obj-types.h \ + glbl.h \ + msg.c \ + msg.h \ + linkedlist.c \ + linkedlist.h \ + objomsr.c \ + objomsr.h \ + stringbuf.c \ + stringbuf.h \ + datetime.c \ + datetime.h \ + srutils.c \ + srUtils.h \ + errmsg.c \ + errmsg.h \ + debug.c \ + debug.h \ + obj.c \ + obj.h \ + modules.c \ + modules.h \ sync.c \ sync.h \ expr.c \ @@ -33,7 +60,7 @@ librsyslog_la_SOURCES = \ queue.c \ queue.h -librsyslog_la_CPPFLAGS = -I$(top_srcdir) $(pthreads_cflags) +librsyslog_la_CPPFLAGS = -D_PATH_MODDIR=\"$(pkglibdir)/\" -I$(top_srcdir) $(pthreads_cflags) #librsyslog_la_LDFLAGS = -module -avoid-version librsyslog_la_LIBADD = @@ -41,8 +68,7 @@ librsyslog_la_LIBADD = # regular expression support # if ENABLE_REGEXP -noinst_LTLIBRARIES += lmregexp.la -#pkglib_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) diff --git a/runtime/atomic.h b/runtime/atomic.h new file mode 100644 index 00000000..430ae7f0 --- /dev/null +++ b/runtime/atomic.h @@ -0,0 +1,51 @@ +/* This header supplies atomic operations. So far, we rely on GCC's + * atomic builtins. I have no idea if we can check them via autotools, + * but I am making the necessary provisioning to live without them if + * they are not available. Please note that you should only use the macros + * here if you think you can actually live WITHOUT an explicit atomic operation, + * because in the non-presence of them, we simply do it without atomicitiy. + * Which, for word-aligned data types, usually (but only usually!) should work. + * + * We are using the functions described in + * http:/gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html + * + * THESE MACROS MUST ONLY BE USED WITH WORD-SIZED DATA TYPES! + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" /* autotools! */ + +#ifndef INCLUDED_ATOMIC_H +#define INCLUDED_ATOMIC_H + +/* set the following to 1 if we have atomic operations (and #undef it otherwise) */ +/* #define DO_HAVE_ATOMICS 1 */ +/* for this release, we disable atomic calls because there seem to be some + * portability problems and we can not fix that without destabilizing the build. + * They simply came in too late. -- rgerhards, 2008-04-02 + */ +/* make sure they are not used! +#define ATOMIC_INC(data) ((void) __sync_fetch_and_add(&data, 1)) +#define ATOMIC_DEC_AND_FETCH(data) __sync_sub_and_fetch(&data, 1) +*/ +#define ATOMIC_INC(data) (++(data)) + +#endif /* #ifndef INCLUDED_ATOMIC_H */ diff --git a/runtime/datetime.c b/runtime/datetime.c new file mode 100644 index 00000000..d72cac3c --- /dev/null +++ b/runtime/datetime.c @@ -0,0 +1,630 @@ +/* The datetime object. It contains date and time related functions. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. The main intension was to move code out of syslogd.c + * in a useful manner. It is still undecided if all functions will continue + * to stay here or some will be moved into parser modules (once we have them). + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +# include +#endif + +#include "rsyslog.h" +#include "obj.h" +#include "modules.h" +#include "datetime.h" +#include "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + + +/* ------------------------------ methods ------------------------------ */ + + +/** + * Get the current date/time in the best resolution the operating + * system has to offer (well, actually at most down to the milli- + * second level. + * + * The date and time is returned in separate fields as this is + * most portable and removes the need for additional structures + * (but I have to admit it is somewhat "bulky";)). + * + * Obviously, all caller-provided pointers must not be NULL... + */ +static void getCurrTime(struct syslogTime *t) +{ + struct timeval tp; + struct tm *tm; + struct tm tmBuf; + long lBias; +# if defined(__hpux) + struct timezone tz; +# endif + + assert(t != NULL); +# if defined(__hpux) + /* TODO: check this: under HP UX, the tz information is actually valid + * data. So we need to obtain and process it there. + */ + gettimeofday(&tp, &tz); +# else + gettimeofday(&tp, NULL); +# endif + tm = localtime_r((time_t*) &(tp.tv_sec), &tmBuf); + + t->year = tm->tm_year + 1900; + t->month = tm->tm_mon + 1; + t->day = tm->tm_mday; + t->hour = tm->tm_hour; + t->minute = tm->tm_min; + t->second = tm->tm_sec; + t->secfrac = tp.tv_usec; + t->secfracPrecision = 6; + +# if __sun + /* Solaris uses a different method of exporting the time zone. + * It is UTC - localtime, which is the opposite sign of mins east of GMT. + */ + lBias = -(daylight ? altzone : timezone); +# elif defined(__hpux) + lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0; +# else + lBias = tm->tm_gmtoff; +# endif + if(lBias < 0) + { + t->OffsetMode = '-'; + lBias *= -1; + } + else + t->OffsetMode = '+'; + t->OffsetHour = lBias / 3600; + t->OffsetMinute = lBias % 3600; +} + + + + +/******************************************************************* + * BEGIN CODE-LIBLOGGING * + ******************************************************************* + * Code in this section is borrowed from liblogging. This is an + * interim solution. Once liblogging is fully integrated, this is + * to be removed (see http://www.monitorware.com/liblogging for + * more details. 2004-11-16 rgerhards + * + * Please note that the orginal liblogging code is modified so that + * it fits into the context of the current version of syslogd.c. + * + * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!! + */ + +/** + * Parse a 32 bit integer number from a string. + * + * \param ppsz Pointer to the Pointer to the string being parsed. It + * must be positioned at the first digit. Will be updated + * so that on return it points to the first character AFTER + * the integer parsed. + * \retval The number parsed. + */ + +static int srSLMGParseInt32(char** ppsz) +{ + int i; + + i = 0; + while(isdigit((int) **ppsz)) + { + i = i * 10 + **ppsz - '0'; + ++(*ppsz); + } + + return i; +} + + +/** + * Parse a TIMESTAMP-3339. + * updates the parse pointer position. + */ +static int +ParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) +{ + char *pszTS = *ppszTS; + + assert(pTime != NULL); + assert(ppszTS != NULL); + assert(pszTS != NULL); + + pTime->year = srSLMGParseInt32(&pszTS); + + /* We take the liberty to accept slightly malformed timestamps e.g. in + * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course, + * with the current state of affairs, we would never run into this code + * here because at postion 11, there is no "T" in such cases ;) + */ + if(*pszTS++ != '-') + return FALSE; + pTime->month = srSLMGParseInt32(&pszTS); + if(pTime->month < 1 || pTime->month > 12) + return FALSE; + + if(*pszTS++ != '-') + return FALSE; + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != 'T') + return FALSE; + + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + + /* Now let's see if we have secfrac */ + if(*pszTS == '.') + { + char *pszStart = ++pszTS; + pTime->secfrac = srSLMGParseInt32(&pszTS); + pTime->secfracPrecision = (int) (pszTS - pszStart); + } + else + { + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + } + + /* check the timezone */ + if(*pszTS == 'Z') + { + pszTS++; /* eat Z */ + pTime->OffsetMode = 'Z'; + pTime->OffsetHour = 0; + pTime->OffsetMinute = 0; + } + else if((*pszTS == '+') || (*pszTS == '-')) + { + pTime->OffsetMode = *pszTS; + pszTS++; + + pTime->OffsetHour = srSLMGParseInt32(&pszTS); + if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->OffsetMinute = srSLMGParseInt32(&pszTS); + if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) + return FALSE; + } + else + /* there MUST be TZ information */ + return FALSE; + + /* OK, we actually have a 3339 timestamp, so let's indicated this */ + if(*pszTS == ' ') + ++pszTS; + else + return FALSE; + + /* update parse pointer */ + *ppszTS = pszTS; + + return TRUE; +} + + +/** + * Parse a TIMESTAMP-3164. + * Returns TRUE on parse OK, FALSE on parse error. + */ +static int +ParseTIMESTAMP3164(struct syslogTime *pTime, char* pszTS) +{ + assert(pTime != NULL); + assert(pszTS != NULL); + + getCurrTime(pTime); /* obtain the current year and UTC offsets! */ + + /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), + * we may see the following character sequences occur: + * + * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec + * + * We will use this for parsing, as it probably is the + * fastest way to parse it. + * + * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C... + * Fixed a bug that lead to invalid detection of the data. The issue was that + * we had an if(++pszTS == 'x') inside of some of the consturcts below. However, + * there were also some elseifs (doing the same ++), which than obviously did not + * check the orginal character but the next one. Now removed the ++ and put it + * into the statements below. Was a really nasty bug... I didn't detect it before + * june, when it first manifested. This also lead to invalid parsing of the rest + * of the message, as the time stamp was not detected to be correct. - rgerhards + */ + switch(*pszTS++) + { + case 'J': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 1; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'n') { + ++pszTS; + pTime->month = 6; + } else if(*pszTS == 'l') { + ++pszTS; + pTime->month = 7; + } else + return FALSE; + } else + return FALSE; + break; + case 'F': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'b') { + ++pszTS; + pTime->month = 2; + } else + return FALSE; + } else + return FALSE; + break; + case 'M': + if(*pszTS == 'a') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 3; + } else if(*pszTS == 'y') { + ++pszTS; + pTime->month = 5; + } else + return FALSE; + } else + return FALSE; + break; + case 'A': + if(*pszTS == 'p') { + ++pszTS; + if(*pszTS == 'r') { + ++pszTS; + pTime->month = 4; + } else + return FALSE; + } else if(*pszTS == 'u') { + ++pszTS; + if(*pszTS == 'g') { + ++pszTS; + pTime->month = 8; + } else + return FALSE; + } else + return FALSE; + break; + case 'S': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'p') { + ++pszTS; + pTime->month = 9; + } else + return FALSE; + } else + return FALSE; + break; + case 'O': + if(*pszTS == 'c') { + ++pszTS; + if(*pszTS == 't') { + ++pszTS; + pTime->month = 10; + } else + return FALSE; + } else + return FALSE; + break; + case 'N': + if(*pszTS == 'o') { + ++pszTS; + if(*pszTS == 'v') { + ++pszTS; + pTime->month = 11; + } else + return FALSE; + } else + return FALSE; + break; + case 'D': + if(*pszTS == 'e') { + ++pszTS; + if(*pszTS == 'c') { + ++pszTS; + pTime->month = 12; + } else + return FALSE; + } else + return FALSE; + break; + default: + return FALSE; + } + + /* done month */ + + if(*pszTS++ != ' ') + return FALSE; + + /* we accept a slightly malformed timestamp when receiving. This is + * we accept one-digit days + */ + if(*pszTS == ' ') + ++pszTS; + + pTime->day = srSLMGParseInt32(&pszTS); + if(pTime->day < 1 || pTime->day > 31) + return FALSE; + + if(*pszTS++ != ' ') + return FALSE; + pTime->hour = srSLMGParseInt32(&pszTS); + if(pTime->hour < 0 || pTime->hour > 23) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->minute = srSLMGParseInt32(&pszTS); + if(pTime->minute < 0 || pTime->minute > 59) + return FALSE; + + if(*pszTS++ != ':') + return FALSE; + pTime->second = srSLMGParseInt32(&pszTS); + if(pTime->second < 0 || pTime->second > 60) + return FALSE; + if(*pszTS++ != ':') + + /* OK, we actually have a 3164 timestamp, so let's indicate this + * and fill the rest of the properties. */ + pTime->timeType = 1; + pTime->secfracPrecision = 0; + pTime->secfrac = 0; + return TRUE; +} + +/******************************************************************* + * END CODE-LIBLOGGING * + *******************************************************************/ + +/** + * Format a syslogTimestamp into format required by MySQL. + * We are using the 14 digits format. For example 20041111122600 + * is interpreted as '2004-11-11 12:26:00'. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst) +{ + /* currently we do not consider localtime/utc. This may later be + * added. If so, I recommend using a property replacer option + * and/or a global configuration option. However, we should wait + * on user requests for this feature before doing anything. + * rgerhards, 2007-06-26 + */ + assert(ts != NULL); + assert(pDst != NULL); + + if (iLenDst < 15) /* we need at least 14 bytes + 14 digits for timestamp + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); + +} + +int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) +{ + /* see note in formatTimestampToMySQL, applies here as well */ + assert(ts != NULL); + assert(pDst != NULL); + + if (iLenDst < 21) /* we need 20 bytes + '\n' */ + return(0); + + return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); +} + +/** + * Format a syslogTimestamp to a RFC3339 timestamp string (as + * specified in syslog-protocol). + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string terminator). If 0 is returend, an error occured. + */ +int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + int iRet; + char szTZ[7]; /* buffer for TZ information */ + + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 20) + return(0); /* we NEED at least 20 bytes */ + + /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ + if(ts->OffsetMode == 'Z') { + szTZ[0] = 'Z'; + szTZ[1] = '\0'; + } else { + snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", + ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); + } + + if(ts->secfracPrecision > 0) + { /* we now need to include fractional seconds. While doing so, we must look at + * the precision specified. For example, if we have millisec precision (3 digits), a + * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this + * is a huge difference ;). To avoid this, we first create a format string with + * the specific precision and *then* use that format string to do the actual + * formating (mmmmhhh... kind of self-modifying code... ;)). + */ + char szFmtStr[64]; + /* be careful: there is ONE actual %d in the format string below ;) */ + snprintf(szFmtStr, sizeof(szFmtStr), + "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", + ts->secfracPrecision); + iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); + } + else + iRet = snprintf(pBuf, iLenBuf, + "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", + ts->year, ts->month, ts->day, + ts->hour, ts->minute, ts->second, szTZ); + return(iRet); +} + +/** + * Format a syslogTimestamp to a RFC3164 timestamp sring. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string termnator). If 0 is returend, an error occured. + */ +int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", + "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec"}; + assert(ts != NULL); + assert(pBuf != NULL); + + if(iLenBuf < 16) + return(0); /* we NEED 16 bytes */ + return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", + monthNames[ts->month], ts->day, ts->hour, + ts->minute, ts->second + )); +} + +/** + * Format a syslogTimestamp to a text format. + * The caller must provide the timestamp as well as a character + * buffer that will receive the resulting string. The function + * returns the size of the timestamp written in bytes (without + * the string termnator). If 0 is returend, an error occured. + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) +{ + assert(ts != NULL); + assert(pBuf != NULL); + + if(ts->timeType == 1) { + return(formatTimestamp3164(ts, pBuf, iLenBuf)); + } + + if(ts->timeType == 2) { + return(formatTimestamp3339(ts, pBuf, iLenBuf)); + } + + return(0); +} +#endif +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(datetime) +CODESTARTobjQueryInterface(datetime) + if(pIf->ifVersion != datetimeCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->getCurrTime = getCurrTime; + pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339; + pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164; + pIf->formatTimestampToMySQL = formatTimestampToMySQL; + pIf->formatTimestampToPgSQL = formatTimestampToPgSQL; + pIf->formatTimestamp3339 = formatTimestamp3339; + pIf->formatTimestamp3164 = formatTimestamp3164; +finalize_it: +ENDobjQueryInterface(datetime) + + +/* Initialize the datetime class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +ENDObjClassInit(datetime) + +/* vi:set ai: + */ diff --git a/runtime/datetime.h b/runtime/datetime.h new file mode 100644 index 00000000..fcb78172 --- /dev/null +++ b/runtime/datetime.h @@ -0,0 +1,52 @@ +/* The datetime object. Contains time-related functions. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_DATETIME_H +#define INCLUDED_DATETIME_H + +#include "datetime.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the datetime object */ +typedef struct datetime_s { +} datetime_t; + + +/* interfaces */ +BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */ + void (*getCurrTime)(struct syslogTime *t); + //static int srSLMGParseInt32(char** ppsz); + int (*ParseTIMESTAMP3339)(struct syslogTime *pTime, char** ppszTS); + int (*ParseTIMESTAMP3164)(struct syslogTime *pTime, char* pszTS); + int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst, size_t iLenDst); + int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst, size_t iLenDst); + int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); + int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, size_t iLenBuf); +ENDinterface(datetime) +#define datetimeCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(datetime); + +#endif /* #ifndef INCLUDED_DATETIME_H */ diff --git a/runtime/debug.c b/runtime/debug.c new file mode 100644 index 00000000..53624e38 --- /dev/null +++ b/runtime/debug.c @@ -0,0 +1,1332 @@ +/* debug.c + * + * This file proides debug and run time error analysis support. Some of the + * settings are very performance intense and my be turned off during a release + * build. + * + * File begun on 2008-01-22 by RGerhards + * + * Some functions are controlled by environment variables: + * + * RSYSLOG_DEBUGLOG if set, a debug log file is written to that location + * RSYSLOG_DEBUG specific debug options + * + * For details, visit doc/debug.html + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" /* autotools! */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rsyslog.h" +#include "debug.h" +#include "atomic.h" +#include "obj.h" + + +/* static data (some time to be replaced) */ +DEFobjCurrIf(obj) +int Debug; /* debug flag - read-only after startup */ +int debugging_on = 0; /* read-only, except on sig USR1 */ +static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bLogAllocFree = 0; /* shall calls to (m/c)alloc and free be logged to the debug log? */ +static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logged to the debug log? */ +static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */ +static int bPrintTime = 1; /* print a timestamp together with debug message */ +static int bPrintAllDebugOnExit = 0; +static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */ +static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ +static FILE *altdbg = NULL; /* and the handle for alternate debug output */ +static FILE *stddbg; + +/* list of files/objects that should be printed */ +typedef struct dbgPrintName_s { + uchar *pName; + struct dbgPrintName_s *pNext; +} dbgPrintName_t; + + +/* forward definitions */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID); +static dbgThrdInfo_t *dbgGetThrdInfo(void); +static int dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot); + + +/* This lists are single-linked and members are added at the top */ +static dbgPrintName_t *printNameFileRoot = NULL; + + +/* list of all known FuncDBs. We use a special list, because it must only be single-linked. As + * functions never disappear, we only need to add elements when we see a new one and never need + * to remove anything. For this, we simply add at the top, which saves us a Last pointer. The goal + * is to use as few memory as possible. + */ +typedef struct dbgFuncDBListEntry_s { + dbgFuncDB_t *pFuncDB; + struct dbgFuncDBListEntry_s *pNext; +} dbgFuncDBListEntry_t; +dbgFuncDBListEntry_t *pFuncDBListRoot; + +static pthread_mutex_t mutFuncDBList; + +typedef struct dbgMutLog_s { + struct dbgMutLog_s *pNext; + struct dbgMutLog_s *pPrev; + pthread_mutex_t *mut; + pthread_t thrd; + dbgFuncDB_t *pFuncDB; + int lockLn; /* the actual line where the mutex was locked */ + short mutexOp; +} dbgMutLog_t; +static dbgMutLog_t *dbgMutLogListRoot = NULL; +static dbgMutLog_t *dbgMutLogListLast = NULL; +static pthread_mutex_t mutMutLog; + + +static dbgThrdInfo_t *dbgCallStackListRoot = NULL; +static dbgThrdInfo_t *dbgCallStackListLast = NULL; +static pthread_mutex_t mutCallStack; + +static pthread_mutex_t mutdbgprintf; +static pthread_mutex_t mutdbgoprint; + +static pthread_key_t keyCallStack; + + +/* we do not have templates, so we use some macros to create linked list handlers + * for the several types + * DLL means "doubly linked list" + * rgerhards, 2008-01-23 + */ +#define DLL_Del(type, pThis) \ + if(pThis->pPrev != NULL) \ + pThis->pPrev->pNext = pThis->pNext; \ + if(pThis->pNext != NULL) \ + pThis->pNext->pPrev = pThis->pPrev; \ + if(pThis == dbg##type##ListRoot) \ + dbg##type##ListRoot = pThis->pNext; \ + if(pThis == dbg##type##ListLast) \ + dbg##type##ListLast = pThis->pPrev; \ + free(pThis); + +#define DLL_Add(type, pThis) \ + if(dbg##type##ListRoot == NULL) { \ + dbg##type##ListRoot = pThis; \ + dbg##type##ListLast = pThis; \ + } else { \ + pThis->pPrev = dbg##type##ListLast; \ + dbg##type##ListLast->pNext = pThis; \ + dbg##type##ListLast = pThis; \ + } + +/* we need to do our own mutex cancel cleanup handler as it shall not + * be subject to the debugging instrumentation (that would probably run us + * into an infinite loop + */ +static void dbgMutexCancelCleanupHdlr(void *pmut) +{ + pthread_mutex_unlock((pthread_mutex_t*) pmut); +} + + +/* handler to update the last execution location seen + * rgerhards, 2008-01-28 + */ +static inline void +dbgRecordExecLocation(int iStackPtr, int line) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + pThrd->lastLine[iStackPtr] = line; +} + + +/* ------------------------- mutex tracking code ------------------------- */ + +/* ------------------------- FuncDB utility functions ------------------------- */ + +#define SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ((int) (sizeof(pFuncDB->mutInfo) / sizeof(dbgFuncDBmutInfoEntry_t))) + +/* print a FuncDB + */ +static void dbgFuncDBPrint(dbgFuncDB_t *pFuncDB) +{ + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + /* make output suitable for sorting on invocation count */ + dbgprintf("%10.10ld times called: %s:%d:%s\n", pFuncDB->nTimesCalled, pFuncDB->file, pFuncDB->line, pFuncDB->func); +} + + +/* print all funcdb entries + */ +static void dbgFuncDBPrintAll(void) +{ + dbgFuncDBListEntry_t *pFuncDBList; + int nFuncs = 0; + + for(pFuncDBList = pFuncDBListRoot ; pFuncDBList != NULL ; pFuncDBList = pFuncDBList->pNext) { + dbgFuncDBPrint(pFuncDBList->pFuncDB); + nFuncs++; + } + + dbgprintf("%d unique functions called\n", nFuncs); +} + + +/* find a mutex inside the FuncDB mutex table. Returns NULL if not found. Only mutexes from the same thread + * are found. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBGetMutexInfo(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + int i; + int iFound = -1; + pthread_t ourThrd = pthread_self(); + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].pmut == pmut && pFuncDB->mutInfo[i].lockLn != -1 && pFuncDB->mutInfo[i].thrd == ourThrd) { + iFound = i; + break; + } + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + + +/* print any mutex that can be found in the FuncDB. Custom header is provided. + * "thrd" is the thread that is searched. If it is 0, mutexes for all threads + * shall be printed. + */ +static inline void +dbgFuncDBPrintActiveMutexes(dbgFuncDB_t *pFuncDB, char *pszHdrText, pthread_t thrd) +{ + int i; + char pszThrdName[64]; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn != -1 && (thrd == 0 || thrd == pFuncDB->mutInfo[i].thrd)) { + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pFuncDB->mutInfo[i].thrd, 1); + dbgprintf("%s:%d:%s:invocation %ld: %s %p[%d/%s]\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, + pFuncDB->mutInfo[i].lInvocation, pszHdrText, (void*)pFuncDB->mutInfo[i].pmut, i, + pszThrdName); + } + } +} + +/* find a free mutex info spot in FuncDB. NULL is returned if table is full. + */ +static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBFindFreeMutexInfo(dbgFuncDB_t *pFuncDB) +{ + int i; + int iFound = -1; + + for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) { + if(pFuncDB->mutInfo[i].lockLn == -1) { + iFound = i; + break; + } + } + + if(iFound == -1) { + dbgprintf("%s:%d:%s: INFO: out of space in FuncDB for mutex info (max %d entries) - ignoring\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func, SIZE_FUNCDB_MUTEX_TABLE(pFuncDB)); + } + + return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i]; +} + +/* add a mutex lock to the FuncDB. If the size is exhausted, info is discarded. + */ +static inline void dbgFuncDBAddMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut, int lockLn) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBFindFreeMutexInfo(pFuncDB)) != NULL) { + pMutInfo->pmut = pmut; + pMutInfo->lockLn = lockLn; + pMutInfo->lInvocation = pFuncDB->nTimesCalled; + pMutInfo->thrd = pthread_self(); + } +} + +/* remove a locked mutex from the FuncDB (unlock case!). + */ +static inline void dbgFuncDBRemoveMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut) +{ + dbgFuncDBmutInfoEntry_t *pMutInfo; + + if((pMutInfo = dbgFuncDBGetMutexInfo(pFuncDB, pmut)) != NULL) { + pMutInfo->lockLn = -1; + } +} + + +/* ------------------------- END FuncDB utility functions ------------------------- */ + +/* ########################################################################### + * IMPORTANT NOTE + * Mutex instrumentation reduces the code's concurrency and thus affects its + * order of execution. It is vital to test the code also with mutex + * instrumentation turned off! Some bugs may not show up while it on... + * ########################################################################### + */ + +/* constructor & add new entry to list + */ +dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pLog = calloc(1, sizeof(dbgMutLog_t)); + assert(pLog != NULL); + + /* fill data members */ + pLog->mut = pmut; + pLog->thrd = pthread_self(); + pLog->mutexOp = mutexOp; + pLog->lockLn = lockLn; + pLog->pFuncDB = pFuncDB; + + DLL_Add(MutLog, pLog); + + return pLog; +} + + +/* destruct log entry + */ +void dbgMutLogDelEntry(dbgMutLog_t *pLog) +{ + assert(pLog != NULL); + DLL_Del(MutLog, pLog); +} + + +/* print a single mutex log entry */ +static void dbgMutLogPrintOne(dbgMutLog_t *pLog) +{ + char *strmutop; + char buf[64]; + char pszThrdName[64]; + + assert(pLog != NULL); + switch(pLog->mutexOp) { + case MUTOP_LOCKWAIT: + strmutop = "waited on"; + break; + case MUTOP_LOCK: + strmutop = "owned"; + break; + default: + snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp); + strmutop = buf; + break; + } + + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pLog->thrd, 1); + dbgprintf("mutex 0x%lx is being %s by code at %s:%d, thread %s\n", (unsigned long) pLog->mut, + strmutop, pLog->pFuncDB->file, + (pLog->mutexOp == MUTOP_LOCK) ? pLog->lockLn : pLog->pFuncDB->line, + pszThrdName); +} + +/* print the complete mutex log */ +static void dbgMutLogPrintAll(void) +{ + dbgMutLog_t *pLog; + + dbgprintf("Mutex log for all known mutex operations:\n"); + for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext) + dbgMutLogPrintOne(pLog); + +} + + +/* find the last log entry for that specific mutex object. Is used to delete + * a thread's own requests. Searches occur from the back. + * The pFuncDB is optional and may be NULL to indicate no specific funciont is + * reqested (aka "it is ignored" ;)). This is important for the unlock case. + */ +dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, dbgFuncDB_t *pFuncDB) +{ + dbgMutLog_t *pLog; + pthread_t mythrd = pthread_self(); + + pLog = dbgMutLogListLast; + while(pLog != NULL) { + if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop + && (pFuncDB == NULL || pLog->pFuncDB == pFuncDB)) + break; + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find mutex object from the back of the list */ +dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast) +{ + dbgMutLog_t *pLog; + + if(pLast == NULL) + pLog = dbgMutLogListLast; + else + pLog = pLast->pPrev; /* if we get the last processed one, we need to go one before it, else its an endless loop */ + + while(pLog != NULL) { + if(pLog->mut == pmut) { + break; + } + pLog = pLog->pPrev; + } + + return pLog; +} + + +/* find lock aquire for mutex from back of list */ +dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut) +{ + dbgMutLog_t *pLog; + + pLog = dbgMutLogFindFromBack(pmut, NULL); + while(pLog != NULL) { + if(pLog->mutexOp == MUTOP_LOCK) + break; + pLog = dbgMutLogFindFromBack(pmut, pLog); + } + + return pLog; +} + +/* report wait on a mutex and add it to the mutex log */ +static inline void dbgMutexPreLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln) +{ + dbgMutLog_t *pHolder; + dbgMutLog_t *pLog; + char pszBuf[128]; + char pszHolderThrdName[64]; + char *pszHolder; + + pthread_mutex_lock(&mutMutLog); + pHolder = dbgMutLogFindHolder(pmut); + pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, pFuncDB, ln); + + if(pHolder == NULL) + pszHolder = "[NONE]"; + else { + dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1); + snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName); + pszHolder = pszBuf; + } + + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder); + pthread_mutex_unlock(&mutMutLog); +} + + +/* report aquired mutex */ +static inline void dbgMutexLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + + /* find and delete "waiting" entry */ + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, pFuncDB); + assert(pLog != NULL); + dbgMutLogDelEntry(pLog); + + /* add "lock" entry */ + pLog = dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn); + dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn); + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut); +} + +/* if we unlock, we just remove the lock aquired entry from the log list */ +static inline void dbgMutexUnlockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int unlockLn) +{ + dbgMutLog_t *pLog; + + pthread_mutex_lock(&mutMutLog); + pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL); + assert(pLog != NULL); + + /* we found the last lock entry. We now need to see from which FuncDB we need to + * remove it. This is recorded inside the mutex log entry. + */ + dbgFuncDBRemoveMutexLock(pLog->pFuncDB, pmut); + + /* donw with the log entry, get rid of it... */ + dbgMutLogDelEntry(pLog); + + pthread_mutex_unlock(&mutMutLog); + if(bPrintMutexAction) + dbgprintf("%s:%d:%s: mutex %p UNlocked\n", pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut); +} + + +/* wrapper for pthread_mutex_lock() */ +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_lock(pmut); + if(ret == 0) { + dbgMutexLockLog(pmut, pFuncDB, ln); + } else { + dbgprintf("%s:%d:%s: ERROR: pthread_mutex_lock() for mutex %p failed with error %d\n", + pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret); + } + + return ret; +} + + +/* wrapper for pthread_mutex_unlock() */ +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + ret = pthread_mutex_unlock(pmut); + return ret; +} + + +/* wrapper for pthread_cond_wait() */ +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p\n", pFuncDB->file, pFuncDB->line, + pFuncDB->func, (void*)pmut, (void*)cond); + } + dbgMutexPreLockLog(pmut, pFuncDB, ln); + ret = pthread_cond_wait(cond, pmut); + return ret; +} + + +/* wrapper for pthread_cond_timedwait() */ +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + int ret; + dbgRecordExecLocation(iStackPtr, ln); + dbgMutexUnlockLog(pmut, pFuncDB, ln); + dbgMutexPreLockLog(pmut, pFuncDB, ln); + if(bPrintMutexAction) { + dbgprintf("%s:%d:%s: mutex %p waiting on condition %p (with timeout)\n", pFuncDB->file, + pFuncDB->line, pFuncDB->func, (void*)pmut, (void*)cond); + } + ret = pthread_cond_timedwait(cond, pmut, abstime); + dbgMutexLockLog(pmut, pFuncDB, ln); + return ret; +} + + +/* ------------------------- end mutex tracking code ------------------------- */ + + +/* ------------------------- malloc/free tracking code ------------------------- */ + +/* wrapper for free() */ +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr) +{ + dbgRecordExecLocation(iStackPtr, ln); + if(bLogAllocFree) { + dbgprintf("%s:%d:%s: free %p\n", pFuncDB->file, ln, pFuncDB->func, (void*) pMem); + } + free(pMem); +} + + +/* ------------------------- end malloc/free tracking code ------------------------- */ + +/* ------------------------- thread tracking code ------------------------- */ + +/* get ptr to call stack - if none exists, create a new stack + */ +static dbgThrdInfo_t *dbgGetThrdInfo(void) +{ + dbgThrdInfo_t *pThrd; + + pthread_mutex_lock(&mutCallStack); + if((pThrd = pthread_getspecific(keyCallStack)) == NULL) { + /* construct object */ + pThrd = calloc(1, sizeof(dbgThrdInfo_t)); + pThrd->thrd = pthread_self(); + (void) pthread_setspecific(keyCallStack, pThrd); + DLL_Add(CallStack, pThrd); + } + pthread_mutex_unlock(&mutCallStack); + return pThrd; +} + + + +/* find a specific thread ID. It must be present, else something is wrong + */ +static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd) +{ + dbgThrdInfo_t *pThrd; + + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + if(pThrd->thrd == thrd) + break; + } + return pThrd; +} + + +/* build a string with the thread name. If none is set, the thread ID is + * used instead. Caller must provide buffer space. If bIncludeNumID is set + * to 1, the numerical ID is always included. + * rgerhards 2008-01-23 + */ +static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID) +{ + dbgThrdInfo_t *pThrd; + + assert(pszBuf != NULL); + + pThrd = dbgFindThrd(thrd); + + if(pThrd == 0 || pThrd->pszThrdName == NULL) { + /* no thread name, use numeric value */ + snprintf(pszBuf, lenBuf, "%lx", (long) thrd); + } else { + if(bIncludeNumID) { + snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, (long) thrd); + } else { + snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName); + } + } + +} + + +/* set a name for the current thread. The caller provided string is duplicated. + */ +void dbgSetThrdName(uchar *pszName) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + if(pThrd->pszThrdName != NULL) + free(pThrd->pszThrdName); + pThrd->pszThrdName = strdup((char*)pszName); +} + + +/* destructor for a call stack object */ +static void dbgCallStackDestruct(void *arg) +{ + dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg; + + dbgprintf("destructor for debug call stack %p called\n", pThrd); + if(pThrd->pszThrdName != NULL) { + free(pThrd->pszThrdName); + } + + pthread_mutex_lock(&mutCallStack); + DLL_Del(CallStack, pThrd); + pthread_mutex_unlock(&mutCallStack); +} + + +/* print a thread's call stack + */ +static void dbgCallStackPrint(dbgThrdInfo_t *pThrd) +{ + int i; + char pszThrdName[64]; + + pthread_mutex_lock(&mutCallStack); + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1); + dbgprintf("\n"); + dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName); + for(i = 0 ; i < pThrd->stackPtr ; i++) { + dbgprintf("%d: %s:%d:%s:\n", i, pThrd->callStack[i]->file, pThrd->lastLine[i], pThrd->callStack[i]->func); + } + dbgprintf("maximum number of nested calls for this thread: %d.\n", pThrd->stackPtrMax); + dbgprintf("NOTE: not all calls may have been recorded, code does not currently guarantee that!\n"); + pthread_mutex_unlock(&mutCallStack); +} + +/* print all threads call stacks + */ +static void dbgCallStackPrintAll(void) +{ + dbgThrdInfo_t *pThrd; + /* stack info */ + for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) { + dbgCallStackPrint(pThrd); + } +} + + +/* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat + * more meaningful way. + * rgerhards, 2008-01-22 + */ +void +sigsegvHdlr(int signum) +{ + char *signame; + struct sigaction sigAct; + + /* first, restore the default abort handler */ + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + sigaction(SIGABRT, &sigAct, NULL); + + /* then do our actual processing */ + if(signum == SIGSEGV) { + signame = " (SIGSEGV)"; + } else if(signum == SIGABRT) { + signame = " (SIGABRT)"; + } else { + signame = ""; + } + + dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame); + + if(bAbortTrace) { + dbgPrintAllDebugInfo(); + dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + } + + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + + /* and finally abort... */ + /* TODO: think about restarting rsyslog in this case: may be a good idea, + * but may also be a very bad one (restart loops!) + */ + abort(); +} + + +/* print some debug output when an object is given + * This is mostly a copy of dbgprintf, but I do not know how to combine it + * into a single function as we have variable arguments and I don't know how to call + * from one vararg function into another. I don't dig in this, it is OK for the + * time being. -- rgerhards, 2008-01-29 + */ +void +dbgoprint(obj_t *pObj, char *fmt, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + if(!(Debug && debugging_on)) + return; + + /* a quick and very dirty hack to enable us to display just from those objects + * that we are interested in. So far, this must be changed at compile time (and + * chances are great it is commented out while you read it. Later, this shall + * be selectable via the environment. -- rgerhards, 2008-02-20 + */ +#if 0 + if(objGetObjID(pObj) != OBJexpr) + return; +#endif + + + pthread_mutex_lock(&mutdbgoprint); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgoprint); + + /* The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. + * rgerhards, 2005-10-25 + * I have decided that it is not worth fixing it - especially as it works + * pretty well. + * rgerhards, 2007-06-15 + */ + if(ptLastThrdID != pthread_self()) { + if(!bWasNL) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + bWasNL = 1; + } + ptLastThrdID = pthread_self(); + } + + /* do not cache the thread name, as the caller might have changed it + * TODO: optimized, invalidate cache when new name is set + */ + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); + + if(bWasNL) { + if(bPrintTime) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + /* print object name header if we have an object */ + if(pObj != NULL) { + if(stddbg != NULL) fprintf(stddbg, "%s: ", obj.GetName(pObj)); + if(altdbg != NULL) fprintf(altdbg, "%s: ", obj.GetName(pObj)); + } + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} + + +/* print some debug output when no object is given + * WARNING: duplicate code, see dbgoprin above! + */ +void +dbgprintf(char *fmt, ...) +{ + static pthread_t ptLastThrdID = 0; + static int bWasNL = 0; + va_list ap; + static char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */ + static char pszWriteBuf[1024]; + size_t lenWriteBuf; + struct timespec t; + + if(!(Debug && debugging_on)) + return; + + pthread_mutex_lock(&mutdbgprintf); + pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprintf); + + /* The bWasNL handler does not really work. It works if no thread + * switching occurs during non-NL messages. Else, things are messed + * up. Anyhow, it works well enough to provide useful help during + * getting this up and running. It is questionable if the extra effort + * is worth fixing it, giving the limited appliability. + * rgerhards, 2005-10-25 + * I have decided that it is not worth fixing it - especially as it works + * pretty well. + * rgerhards, 2007-06-15 + */ + if(ptLastThrdID != pthread_self()) { + if(!bWasNL) { + if(stddbg != NULL) fprintf(stddbg, "\n"); + if(altdbg != NULL) fprintf(altdbg, "\n"); + bWasNL = 1; + } + ptLastThrdID = pthread_self(); + } + + /* do not cache the thread name, as the caller might have changed it + * TODO: optimized, invalidate cache when new name is set + */ + dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0); + + if(bWasNL) { + if(bPrintTime) { + clock_gettime(CLOCK_REALTIME, &t); + if(stddbg != NULL) fprintf(stddbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + if(altdbg != NULL) fprintf(altdbg, "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec); + } + if(stddbg != NULL) fprintf(stddbg, "%s: ", pszThrdName); + if(altdbg != NULL) fprintf(altdbg, "%s: ", pszThrdName); + } + bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? 1 : 0; + va_start(ap, fmt); + lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap); + if(lenWriteBuf >= sizeof(pszWriteBuf)) { + /* if our buffer was too small, we simply truncate. TODO: maybe something better? */ + lenWriteBuf = sizeof(pszWriteBuf) - 1; + } + va_end(ap); + /* + if(stddbg != NULL) fprintf(stddbg, "%s", pszWriteBuf); + if(altdbg != NULL) fprintf(altdbg, "%s", pszWriteBuf); + */ + if(stddbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, stddbg); + if(altdbg != NULL) fwrite(pszWriteBuf, lenWriteBuf, 1, altdbg); + + if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); + pthread_cleanup_pop(1); +} + +void tester(void) +{ +BEGINfunc +ENDfunc +} + +/* handler called when a function is entered. This function creates a new + * funcDB on the heap if the passed-in pointer is NULL. + */ +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line) +{ + int iStackPtr = 0; /* TODO: find some better default, this one hurts the least, but it is not clean */ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + dbgFuncDBListEntry_t *pFuncDBListEntry; + unsigned int i; + dbgFuncDB_t *pFuncDB; + + assert(ppFuncDB != NULL); + assert(file != NULL); + assert(func != NULL); + pFuncDB = *ppFuncDB; + assert((pFuncDB == NULL) || (pFuncDB->magic == dbgFUNCDB_MAGIC)); + + if(pFuncDB == NULL) { + /* we do not yet have a funcDB and need to create a new one. We also add it + * to the linked list of funcDBs. Please note that when a module is unloaded and + * then reloaded again, we currently do not try to find its previous funcDB but + * instead create a duplicate. While finding the past one is straightforward, it + * opens up the question what to do with e.g. mutex data left in it. We do not + * yet see any need to handle these questions, so duplicaton seems to be the right + * thing to do. -- rgerhards, 2008-03-10 + */ + /* dbgprintf("%s:%d:%s: called first time, initializing FuncDB\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); */ + /* get a new funcDB and add it to the list (all of this is protected by the mutex) */ + pthread_mutex_lock(&mutFuncDBList); + if((pFuncDBListEntry = calloc(1, sizeof(dbgFuncDBListEntry_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB List entry, not adding\n", errno); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + if((pFuncDB = calloc(1, sizeof(dbgFuncDB_t))) == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } else { + pFuncDBListEntry->pFuncDB = pFuncDB; + pFuncDBListEntry->pNext = pFuncDBListRoot; + pFuncDBListRoot = pFuncDBListEntry; + } + } + /* now intialize the funcDB + * note that we duplicate the strings, because the address provided may go away + * if a loadable module is unloaded! + */ + pFuncDB->magic = dbgFUNCDB_MAGIC; + pFuncDB->file = strdup(file); + pFuncDB->func = strdup(func); + pFuncDB->line = line; + pFuncDB->nTimesCalled = 0; + for(i = 0 ; i < sizeof(pFuncDB->mutInfo)/sizeof(dbgFuncDBmutInfoEntry_t) ; ++i) { + pFuncDB->mutInfo[i].lockLn = -1; /* set to not Locked */ + } + + /* a round of safety checks... */ + if(pFuncDB->file == NULL || pFuncDB->func == NULL) { + dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno); + /* do a little bit of cleanup */ + if(pFuncDB->file != NULL) + free(pFuncDB->file); + if(pFuncDB->func != NULL) + free(pFuncDB->func); + free(pFuncDB); + free(pFuncDBListEntry); + pthread_mutex_unlock(&mutFuncDBList); + goto exit_it; + } + + /* done mutex-protected operations */ + pthread_mutex_unlock(&mutFuncDBList); + + *ppFuncDB = pFuncDB; /* all went well, so we can update the caller */ + } + + /* when we reach this point, we have a fully-initialized FuncDB! */ + ATOMIC_INC(pFuncDB->nTimesCalled); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) + dbgprintf("%s:%d: %s: enter\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(dbgFuncDB_t*))) { + dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n", + pFuncDB->file, pFuncDB->line, pFuncDB->func); + iStackPtr = pThrd->stackPtr; + } else { + iStackPtr = pThrd->stackPtr++; + if(pThrd->stackPtr > pThrd->stackPtrMax) + pThrd->stackPtrMax = pThrd->stackPtr; + pThrd->callStack[iStackPtr] = pFuncDB; + pThrd->lastLine[iStackPtr] = line; + } + +exit_it: + return iStackPtr; +} + + +/* handler called when a function is exited + */ +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet) +{ + dbgThrdInfo_t *pThrd = dbgGetThrdInfo(); + + assert(iStackPtrRestore >= 0); + assert(pFuncDB != NULL); + assert(pFuncDB->magic == dbgFUNCDB_MAGIC); + + dbgFuncDBPrintActiveMutexes(pFuncDB, "WARNING: mutex still owned by us as we exit function, mutex: ", pthread_self()); + if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) { + if(iRet == RS_RET_NO_IRET) + dbgprintf("%s:%d: %s: exit: (no iRet)\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); + else + dbgprintf("%s:%d: %s: exit: %d\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, iRet); + } + pThrd->stackPtr = iStackPtrRestore; + if(pThrd->stackPtr < 0) { + dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", (long) pthread_self()); + pThrd->stackPtr = 0; + } +} + + +/* externally-callable handler to record the last exec location. We use a different function + * so that the internal one can be inline. + */ +void +dbgSetExecLocation(int iStackPtr, int line) +{ + dbgRecordExecLocation(iStackPtr, line); +} + + +void dbgPrintAllDebugInfo(void) +{ + dbgCallStackPrintAll(); + dbgMutLogPrintAll(); + if(bPrintFuncDBOnExit) + dbgFuncDBPrintAll(); +} + + +/* Handler for SIGUSR2. Dumps all available debug output + */ +static void sigusr2Hdlr(int __attribute__((unused)) signum) +{ + dbgprintf("SIGUSR2 received, dumping debug information\n"); + dbgPrintAllDebugInfo(); +} + +/* support system to set debug options at runtime */ + + +/* parse a param/value pair from the current location of the + * option string. Returns 1 if an option was found, 0 + * otherwise. 0 means there are NO MORE options to be + * processed. -- rgerhards, 2008-02-28 + */ +static int +dbgGetRTOptNamVal(uchar **ppszOpt, uchar **ppOptName, uchar **ppOptVal) +{ + int bRet = 0; + uchar *p; + size_t i; + static uchar optname[128]; /* not thread- or reentrant-safe, but that */ + static uchar optval[1024]; /* doesn't matter (called only once at startup) */ + + assert(ppszOpt != NULL); + assert(*ppszOpt != NULL); + + /* make sure we have some initial values */ + optname[0] = '\0'; + optval[0] = '\0'; + + p = *ppszOpt; + /* skip whitespace */ + while(*p && isspace(*p)) + ++p; + + /* name - up until '=' or whitespace */ + i = 0; + while(i < (sizeof(optname)/sizeof(uchar) - 1) && *p && *p != '=' && !isspace(*p)) { + optname[i++] = *p++; + } + + if(i > 0) { + bRet = 1; + optname[i] = '\0'; + if(*p == '=') { + /* we have a value, get it */ + ++p; + i = 0; + while(i < (sizeof(optval)/sizeof(uchar) - 1) && *p && !isspace(*p)) { + optval[i++] = *p++; + } + optval[i] = '\0'; + } + } + + /* done */ + *ppszOpt = p; + *ppOptName = optname; + *ppOptVal = optval; + return bRet; +} + + +/* create new PrintName list entry and add it to list (they will never + * be removed. -- rgerhards, 2008-02-28 + */ +static void +dbgPrintNameAdd(uchar *pName, dbgPrintName_t **ppRoot) +{ + dbgPrintName_t *pEntry; + + if((pEntry = calloc(1, sizeof(dbgPrintName_t))) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if((pEntry->pName = (uchar*) strdup((char*) pName)) == NULL) { + fprintf(stderr, "ERROR: out of memory during debug setup\n"); + exit(1); + } + + if(*ppRoot != NULL) { + pEntry->pNext = *ppRoot; /* we enqueue at the front */ + } + *ppRoot = pEntry; + +printf("Name %s added to %p\n", pName, *ppRoot); +} + + +/* check if name is in a printName list - returns 1 if so, 0 otherwise. + * There is one special handling: if the root pointer is NULL, the function + * always returns 1. This is because when no name is set, output shall be + * unrestricted. + * rgerhards, 2008-02-28 + */ +static int +dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot) +{ + int bFound = 0; + dbgPrintName_t *pEntry = pRoot; + + if(pRoot == NULL) + bFound = 1; + + while(pEntry != NULL && !bFound) { + if(!strcasecmp((char*)pEntry->pName, (char*)pName)) { + bFound = 1; + } else { + pEntry = pEntry->pNext; + } + } + + return bFound; +} + + +/* read in the runtime options + * rgerhards, 2008-02-28 + */ +static void +dbgGetRuntimeOptions(void) +{ + uchar *pszOpts; + uchar *optval; + uchar *optname; + + /* set some defaults */ + stddbg = stdout; + + if((pszOpts = (uchar*) getenv("RSYSLOG_DEBUG")) != NULL) { + /* we have options set, so let's process them */ + while(dbgGetRTOptNamVal(&pszOpts, &optname, &optval)) { + if(!strcasecmp((char*)optname, "help")) { + fprintf(stderr, + "rsyslogd runtime debug support - help requested, rsyslog terminates\n\n" + "environment variables:\n" + "addional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n" + "to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n" + "Commands are (all case-insensitive):\n" + "help (this list, terminates rsyslogd\n" + "LogFuncFlow\n" + "LogAllocFree (very partly implemented)\n" + "PrintFuncDB\n" + "PrintMutexAction\n" + "PrintAllDebugInfoOnExit (not yet implemented)\n" + "NoLogTimestamp\n" + "Nostdoout\n" + "filetrace=file (may be provided multiple times)\n" + "\nSee debug.html in your doc set or http://www.rsyslog.com for details\n"); + exit(1); + } else if(!strcasecmp((char*)optname, "debug")) { + /* this is earlier in the process than the -d option, as such it + * allows us to spit out debug messages from the very beginning. + */ + Debug = 1; + debugging_on = 1; + } else if(!strcasecmp((char*)optname, "logfuncflow")) { + bLogFuncFlow = 1; + } else if(!strcasecmp((char*)optname, "logallocfree")) { + bLogAllocFree = 1; + } else if(!strcasecmp((char*)optname, "printfuncdb")) { + bPrintFuncDBOnExit = 1; + } else if(!strcasecmp((char*)optname, "printmutexaction")) { + bPrintMutexAction = 1; + } else if(!strcasecmp((char*)optname, "printalldebuginfoonexit")) { + bPrintAllDebugOnExit = 1; + } else if(!strcasecmp((char*)optname, "nologtimestamp")) { + bPrintTime = 0; + } else if(!strcasecmp((char*)optname, "nostdout")) { + stddbg = NULL; + } else if(!strcasecmp((char*)optname, "noaborttrace")) { + bAbortTrace = 0; + } else if(!strcasecmp((char*)optname, "filetrace")) { + if(*optval == '\0') { + fprintf(stderr, "Error: logfile debug option requires filename, " + "e.g. \"logfile=debug.c\"\n"); + exit(1); + } else { + /* create new entry and add it to list */ + dbgPrintNameAdd(optval, &printNameFileRoot); + } + } else { + fprintf(stderr, "Error: invalid debug option '%s', value '%s' - ignored\n", + optval, optname); + } + } + } +} + + +/* end support system to set debug options at runtime */ + +rsRetVal dbgClassInit(void) +{ + DEFiRet; + + struct sigaction sigAct; + sigset_t sigSet; + + (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */ + + /* we initialize all Mutexes with code, as some platforms seem to have + * bugs in the static initializer macros. So better be on the safe side... + * rgerhards, 2008-03-06 + */ + pthread_mutex_init(&mutFuncDBList, NULL); + pthread_mutex_init(&mutMutLog, NULL); + pthread_mutex_init(&mutCallStack, NULL); + pthread_mutex_init(&mutdbgprintf, NULL); + pthread_mutex_init(&mutdbgoprint, NULL); + + /* while we try not to use any of the real rsyslog code (to avoid infinite loops), we + * need to have the ability to query object names. Thus, we need to obtain a pointer to + * the object interface. -- rgerhards, 2008-02-29 + */ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sigusr2Hdlr; + sigaction(SIGUSR2, &sigAct, NULL); + + sigemptyset(&sigSet); + sigaddset(&sigSet, SIGUSR2); + pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL); + + dbgGetRuntimeOptions(); /* init debug system from environment */ + pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG"); + + if(pszAltDbgFileName != NULL) { + /* we have a secondary file, so let's open it) */ + if((altdbg = fopen(pszAltDbgFileName, "w")) == NULL) { + fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno)); + } + } + + dbgSetThrdName((uchar*)"main thread"); + +finalize_it: + RETiRet; +} + + +rsRetVal dbgClassExit(void) +{ + dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel; + pthread_key_delete(keyCallStack); + + if(bPrintAllDebugOnExit) + dbgPrintAllDebugInfo(); + + if(altdbg != NULL) + fclose(altdbg); + + /* now free all of our memory to make the memory debugger happy... */ + pFuncDBListEtry = pFuncDBListRoot; + while(pFuncDBListEtry != NULL) { + pToDel = pFuncDBListEtry; + pFuncDBListEtry = pFuncDBListEtry->pNext; + free(pToDel->pFuncDB->file); + free(pToDel->pFuncDB->func); + free(pToDel->pFuncDB); + free(pToDel); + } + + return RS_RET_OK; +} +/* vi:set ai: + */ diff --git a/runtime/debug.h b/runtime/debug.h new file mode 100644 index 00000000..214b7c05 --- /dev/null +++ b/runtime/debug.h @@ -0,0 +1,146 @@ +/* debug.h + * + * Definitions for the debug and run-time analysis support module. + * Contains a lot of macros. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef DEBUG_H_INCLUDED +#define DEBUG_H_INCLUDED + +#include +#include "obj-types.h" + +/* external static data elements (some time to be replaced) */ +extern int Debug; /* debug flag - read-only after startup */ +extern int debugging_on; /* read-only, except on sig USR1 */ + +/* data types */ + +/* the function database. It is used as a static var inside each function. That provides + * us the fast access to it that we need to make the instrumentation work. It's address + * also serves as a unique function identifier and can be used inside other structures + * to refer to the function (e.g. for pretty-printing names). + * rgerhards, 2008-01-24 + */ +typedef struct dbgFuncDBmutInfoEntry_s { + pthread_mutex_t *pmut; + int lockLn; /* line where it was locked (inside our func): -1 means mutex is not locked */ + pthread_t thrd; /* thrd where the mutex was locked */ + unsigned long lInvocation; /* invocation (unique during program run!) of this function that locked the mutex */ +} dbgFuncDBmutInfoEntry_t; +typedef struct dbgFuncDB_s { + unsigned magic; + unsigned long nTimesCalled; + char *func; + char *file; + int line; + dbgFuncDBmutInfoEntry_t mutInfo[5]; + /* remember to update the initializer if you add anything or change the order! */ +} dbgFuncDB_t; +#define dbgFUNCDB_MAGIC 0xA1B2C3D4 +#define dbgFuncDB_t_INITIALIZER \ + { \ + .magic = dbgFUNCDB_MAGIC,\ + .nTimesCalled = 0,\ + .func = __func__, \ + .file = __FILE__, \ + .line = __LINE__ \ + } + +/* the structure below was originally just the thread's call stack, but it has + * a bit evolved over time. So we have now ended up with the fact that it + * all debug info we know about the thread. + */ +typedef struct dbgCallStack_s { + pthread_t thrd; + dbgFuncDB_t *callStack[500]; + int lastLine[500]; /* last line where code execution was seen */ + int stackPtr; + int stackPtrMax; + char *pszThrdName; + struct dbgCallStack_s *pNext; + struct dbgCallStack_s *pPrev; +} dbgThrdInfo_t; + + +/* prototypes */ +rsRetVal dbgClassInit(void); +rsRetVal dbgClassExit(void); +void sigsegvHdlr(int signum); +void dbgoprint(obj_t *pObj, char *fmt, ...) __attribute__((format(printf, 2, 3))); +void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); +int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncD, int ln, int iStackPtr); +void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr); +int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line); +void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet); +void dbgSetExecLocation(int iStackPtr, int line); +void dbgSetThrdName(uchar *pszName); +void dbgPrintAllDebugInfo(void); + +/* macros */ +#ifdef RTINST +# define BEGINfunc static dbgFuncDB_t *pdbgFuncDB; int dbgCALLStaCK_POP_POINT = dbgEntrFunc(&pdbgFuncDB, __FILE__, __func__, __LINE__); +# define ENDfunc dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, RS_RET_NO_IRET); +# define ENDfuncIRet dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, iRet); +# define ASSERT(x) assert(x) +#else +# define BEGINfunc +# define ENDfunc +# define ENDfuncIRet +# define ASSERT(x) +#endif +#ifdef RTINST +# define RUNLOG dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__); dbgprintf("%s:%d: %s: log point\n", __FILE__, __LINE__, __func__) +# define RUNLOG_VAR(fmt, x) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: var '%s'[%s]: " fmt "\n", __FILE__, __LINE__, __func__, #x, fmt, x) +# define RUNLOG_STR(str) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\ + dbgprintf("%s:%d: %s: %s\n", __FILE__, __LINE__, __func__, str) +#else +# define RUNLOG +# define RUNLOG_VAR(fmt, x) +# define RUNLOG_STR(str) +#endif + +/* mutex operations */ +#define MUTOP_LOCKWAIT 1 +#define MUTOP_LOCK 2 +#define MUTOP_UNLOCK 3 + + +/* debug aides */ +#ifdef RTINST +#define d_pthread_mutex_lock(x) dbgMutexLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_mutex_unlock(x) dbgMutexUnlock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_wait(cond, mut) dbgCondWait(cond, mut, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_pthread_cond_timedwait(cond, mut, to) dbgCondTimedWait(cond, mut, to, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#define d_free(x) dbgFree(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT ) +#else +#define d_pthread_mutex_lock(x) pthread_mutex_lock(x) +#define d_pthread_mutex_unlock(x) pthread_mutex_unlock(x) +#define d_pthread_cond_wait(cond, mut) pthread_cond_wait(cond, mut) +#define d_pthread_cond_timedwait(cond, mut, to) pthread_cond_timedwait(cond, mut, to) +#define d_free(x) free(x) +#endif +#endif /* #ifndef DEBUG_H_INCLUDED */ diff --git a/runtime/errmsg.c b/runtime/errmsg.c new file mode 100644 index 00000000..42f84724 --- /dev/null +++ b/runtime/errmsg.c @@ -0,0 +1,122 @@ +/* The errmsg object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c. I converted this module to lgpl and have checked that + * all contributors agreed to that step. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include +#include + +#include "rsyslog.h" +#include "syslogd.h" +#include "obj.h" +#include "errmsg.h" +#include "sysvar.h" +#include "srUtils.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers + + +/* ------------------------------ methods ------------------------------ */ + + +/* TODO: restructure this code some time. Especially look if we need + * to check errno and, if so, how to do that in a clean way. + */ +static void __attribute__((format(printf, 2, 3))) +LogError(int __attribute__((unused)) iErrCode, char *fmt, ... ) +{ + va_list ap; + char buf[1024]; + char msg[1024]; + char errStr[1024]; + size_t lenBuf; + + BEGINfunc + assert(fmt != NULL); + /* Format parameters */ + va_start(ap, fmt); + lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap); + if(lenBuf >= sizeof(buf)) { + /* if our buffer was too small, we simply truncate. */ + lenBuf--; + } + va_end(ap); + + /* Log the error now */ + buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + + dbgprintf("Called LogError, msg: %s\n", buf); + + if (errno == 0) { + snprintf(msg, sizeof(msg), "%s", buf); + } else { + rs_strerror_r(errno, errStr, sizeof(errStr)); + snprintf(msg, sizeof(msg), "%s: %s", buf, errStr); + } + msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ + errno = 0; + logmsgInternal(LOG_SYSLOG|LOG_ERR, msg, ADDDATE); + + ENDfunc +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(errmsg) +CODESTARTobjQueryInterface(errmsg) + if(pIf->ifVersion != errmsgCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->LogError = LogError; +finalize_it: +ENDobjQueryInterface(errmsg) + + +/* Initialize the errmsg class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(errmsg, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ +ENDObjClassInit(errmsg) + +/* vi:set ai: + */ diff --git a/runtime/errmsg.h b/runtime/errmsg.h new file mode 100644 index 00000000..bde6bcff --- /dev/null +++ b/runtime/errmsg.h @@ -0,0 +1,46 @@ +/* The errmsg object. It is used to emit error message inside rsyslog. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_ERRMSG_H +#define INCLUDED_ERRMSG_H + +#include "errmsg.h" + +/* TODO: define error codes */ +#define NO_ERRCODE -1 + +/* the errmsg object */ +typedef struct errmsg_s { +} errmsg_t; + + +/* interfaces */ +BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */ + void __attribute__((format(printf, 2, 3))) (*LogError)(int iErrCode, char *pszErrFmt, ... ); +ENDinterface(errmsg) +#define errmsgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(errmsg); + +#endif /* #ifndef INCLUDED_ERRMSG_H */ diff --git a/runtime/linkedlist.c b/runtime/linkedlist.c new file mode 100644 index 00000000..ce20651e --- /dev/null +++ b/runtime/linkedlist.c @@ -0,0 +1,414 @@ +/* linkedlist.c + * This file set implements a generic linked list object. It can be used + * wherever a linke list is required. + * + * NOTE: we do not currently provide a constructor and destructor for the + * object itself as we assume it will always be part of another strucuture. + * Having a pointer to it, I think, does not really make sense but costs + * performance. Consequently, there is is llInit() and llDestroy() and they + * do what a constructor and destructur do, except for creating the + * linkedList_t structure itself. + * + * File begun on 2007-07-31 by RGerhards + * + * Copyright (C) 2007, 2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include + +#include "rsyslog.h" +#include "linkedlist.h" + + +/* Initialize an existing linkedList_t structure + * pKey destructor may be zero to take care of non-keyed lists. + */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()) +{ + assert(pThis != NULL); + assert(pEltDestructor != NULL); + + pThis->pEltDestruct = pEltDestructor; + pThis->pKeyDestruct = pKeyDestructor; + pThis->cmpOp = pCmpOp; + pThis->pKey = NULL; + pThis->iNumElts = 0; + pThis->pRoot = NULL; + pThis->pLast = NULL; + + return RS_RET_OK; +}; + + +/* llDestroyEltData - destroys a list element + * It is a separate function as the + * functionality is needed in multiple code-pathes. + */ +static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt) +{ + DEFiRet; + + assert(pList != NULL); + assert(pElt != NULL); + + /* we ignore errors during destruction, as we need to try + * free the element in any case. + */ + if(pElt->pData != NULL) + pList->pEltDestruct(pElt->pData); + if(pElt->pKey != NULL) + pList->pKeyDestruct(pElt->pKey); + free(pElt); + pList->iNumElts--; /* one less */ + + RETiRet; +} + + +/* llDestroy - destroys a COMPLETE linkedList + */ +rsRetVal llDestroy(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + assert(pThis != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL) { + pEltPrev = pElt; + pElt = pElt->pNext; + /* we ignore errors during destruction, as we need to try + * finish the linked list in any case. + */ + llDestroyElt(pThis, pEltPrev); + } + /* now clean up the pointers */ + pThis->pRoot = NULL; + pThis->pLast = NULL; + + RETiRet; +} + +/* llDestroyRootElt - destroy the root element but otherwise + * keeps this list intact. -- rgerhards, 2007-08-03 + */ +rsRetVal llDestroyRootElt(linkedList_t *pThis) +{ + DEFiRet; + llElt_t *pPrev; + + if(pThis->pRoot == NULL) { + ABORT_FINALIZE(RS_RET_EMPTY_LIST); + } + + pPrev = pThis->pRoot; + if(pPrev->pNext == NULL) { + /* it was the only list element */ + pThis->pLast = NULL; + pThis->pRoot = NULL; + } else { + /* there are other list elements */ + pThis->pRoot = pPrev->pNext; + } + + CHKiRet(llDestroyElt(pThis, pPrev)); + +finalize_it: + RETiRet; +} + + +/* get next user data element of a linked list. The caller must also + * provide a "cookie" to the function. On initial call, it must be + * NULL. Other than that, the caller is not allowed to to modify the + * cookie. In the current implementation, the cookie is an actual + * pointer to the current list element, but this is nothing that the + * caller should rely on. + */ +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr) +{ + llElt_t *pElt; + DEFiRet; + + assert(pThis != NULL); + assert(ppElt != NULL); + assert(ppUsr != NULL); + + pElt = *ppElt; + + pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext; + + if(pElt == NULL) { + iRet = RS_RET_END_OF_LINKEDLIST; + } else { + *ppUsr = pElt->pData; + } + + *ppElt = pElt; + + RETiRet; +} + + +/* return the key of an Elt + * rgerhards, 2007-09-11: note that ppDatea is actually a void**, + * but I need to make it a void* to avoid lots of compiler warnings. + * It will be converted later down in the code. + */ +rsRetVal llGetKey(llElt_t *pThis, void *ppData) +{ + assert(pThis != NULL); + assert(ppData != NULL); + + *(void**) ppData = pThis->pKey; + + return RS_RET_OK; +} + + +/* construct a new llElt_t + */ +static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData) +{ + DEFiRet; + llElt_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + pThis->pKey = pKey; + pThis->pData = pData; + +finalize_it: + *ppThis = pThis; + RETiRet; +} + + +/* append a user element to the end of the linked list. This includes setting a key. If no + * key is desired, simply pass in a NULL pointer for it. + */ +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData) +{ + llElt_t *pElt; + DEFiRet; + + CHKiRet(llEltConstruct(&pElt, pKey, pData)); + + pThis->iNumElts++; /* one more */ + if(pThis->pLast == NULL) { + pThis->pRoot = pElt; + } else { + pThis->pLast->pNext = pElt; + } + pThis->pLast = pElt; + +finalize_it: + RETiRet; +} + + +/* unlink a requested element. As we have singly-linked lists, the + * caller also needs to pass in the previous element (or NULL, if it is the + * root element). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + assert(pElt != NULL); + + if(pEltPrev == NULL) { /* root element? */ + pThis->pRoot = pElt->pNext; + } else { /* regular element */ + pEltPrev->pNext = pElt->pNext; + } + + if(pElt == pThis->pLast) + pThis->pLast = pEltPrev; + + return RS_RET_OK; +} + + +/* unlinks and immediately deletes an element. Previous element must + * be given (or zero if the root element is to be deleted). + * rgerhards, 2007-11-21 + */ +static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev) +{ + DEFiRet; + + assert(pElt != NULL); + + CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev)); + CHKiRet(llDestroyElt(pThis, pElt)); + +finalize_it: + RETiRet; +} + +/* find a user element based on the provided key - this is the + * internal variant, which also tracks the last element pointer + * before the found element. This is necessary to delete elements. + * NULL means there is no element in front of it, aka the found elt + * is the root elt. + * rgerhards, 2007-11-21 + */ +static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev = NULL; + int bFound = 0; + + assert(pThis != NULL); + assert(pKey != NULL); + assert(ppElt != NULL); + assert(ppEltPrev != NULL); + + pElt = pThis->pRoot; + while(pElt != NULL && bFound == 0) { + if(pThis->cmpOp(pKey, pElt->pKey) == 0) + bFound = 1; + else { + pEltPrev = pElt; + pElt = pElt->pNext; + } + } + + if(bFound == 1) { + *ppElt = pElt; + *ppEltPrev = pEltPrev; + } else + iRet = RS_RET_NOT_FOUND; + + RETiRet; +} + + +/* find a user element based on the provided key + */ +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found the element */ + *ppData = pElt->pData; + +finalize_it: + RETiRet; +} + + +/* find a delete an element based on user-provided key. The element is + * delete, the caller does not receive anything. If we need to receive + * the element before destruction, we may implement an llFindAndUnlink() + * at that time. + * rgerhards, 2007-11-21 + */ +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey) +{ + DEFiRet; + llElt_t *pElt; + llElt_t *pEltPrev; + + CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev)); + + /* if we reach this point, we have found an element */ + CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev)); + +finalize_it: + RETiRet; +} + + +/* provide the count of linked list elements + */ +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt) +{ + DEFiRet; + + assert(pThis != NULL); + assert(piCnt != NULL); + + *piCnt = pThis->iNumElts; + + RETiRet; +} + + +/* execute a function on all list members. The functions receives a + * user-supplied parameter, which may be either a simple value + * or a pointer to a structure with more data. If the user-supplied + * function does not return RS_RET_OK, this function here terminates. + * rgerhards, 2007-08-02 + * rgerhards, 2007-11-21: added functionality to delete a list element. + * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element + * is deleted. + */ +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + DEFiRet; + rsRetVal iRetLL; + void *pData; + linkedListCookie_t llCookie = NULL; + linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */ + + assert(pThis != NULL); + assert(pFunc != NULL); + + while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) { + iRet = pFunc(pData, pParam); + if(iRet == RS_RET_OK_DELETE_LISTENTRY) { + /* delete element */ + CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev)); + /* we need to revert back, as we have just deleted the current element. + * So the actual current element is the one before it, which happens to be + * stored in llCookiePrev. -- rgerhards, 2007-11-21 + */ + llCookie = llCookiePrev; + } else if (iRet != RS_RET_OK) { + goto finalize_it; + } + llCookiePrev = llCookie; + } + + if(iRetLL != RS_RET_END_OF_LINKEDLIST) + iRet = iRetLL; + +finalize_it: + RETiRet; +} + +/* vim:set ai: + */ diff --git a/runtime/linkedlist.h b/runtime/linkedlist.h new file mode 100644 index 00000000..aeacd6d7 --- /dev/null +++ b/runtime/linkedlist.h @@ -0,0 +1,73 @@ +/* Definition of the linkedlist object. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef LINKEDLIST_H_INCLUDED +#define LINKEDLIST_H_INCLUDED + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct llElt_s { /* config file sysline parse entry */ + struct llElt_s *pNext; + void *pKey; /* key for this element */ + void *pData; /* user-supplied data pointer */ +}; +typedef struct llElt_s llElt_t; + + +/* this is the list of known configuration commands with pointers to + * their handlers. + * The short name is cslc (Configfile SysLine Command) + */ +struct linkedList_s { /* config file sysline parse entry */ + int iNumElts; /* number of elements in list */ + rsRetVal (*pEltDestruct)(void*pData); /* destructor for user pointer in llElt_t's */ + rsRetVal (*pKeyDestruct)(void*pKey); /* destructor for key pointer in llElt_t's */ + int (*cmpOp)(void*, void*); /* pointer to key compare operation function, retval like strcmp */ + void *pKey; /* the list key (searchable, if set) */ + llElt_t *pRoot; /* list root */ + llElt_t *pLast; /* list tail */ +}; +typedef struct linkedList_s linkedList_t; + +typedef llElt_t* linkedListCookie_t; /* this type avoids exposing internals and keeps us flexible */ + +/* prototypes */ +rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)()); +rsRetVal llDestroy(linkedList_t *pThis); +rsRetVal llDestroyRootElt(linkedList_t *pThis); +rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr); +rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData); +rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData); +rsRetVal llGetKey(llElt_t *pThis, void *ppData); +rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt); +rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam); +rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey); +/* use the macro below to define a function that will be executed by + * llExecFunc() + */ +#define DEFFUNC_llExecFunc(funcName)\ + static rsRetVal funcName(void __attribute__((unused)) *pData, void __attribute__((unused)) *pParam) + +#endif /* #ifndef LINKEDLIST_H_INCLUDED */ diff --git a/runtime/module-template.h b/runtime/module-template.h new file mode 100644 index 00000000..5db73d33 --- /dev/null +++ b/runtime/module-template.h @@ -0,0 +1,482 @@ +/* module-template.h + * This header contains macros that can be used to implement the + * plumbing of modules. + * + * File begun on 2007-07-25 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef MODULE_TEMPLATE_H_INCLUDED +#define MODULE_TEMPLATE_H_INCLUDED 1 + +#include "modules.h" +#include "obj.h" +#include "objomsr.h" +#include "threads.h" + +/* macro to define standard output-module static data members + */ +#define DEF_MOD_STATIC_DATA \ + static __attribute__((unused)) rsRetVal (*omsdRegCFSLineHdlr)(); + +#define DEF_OMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_IMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA \ + DEFobjCurrIf(obj) +#define DEF_LMOD_STATIC_DATA \ + DEF_MOD_STATIC_DATA + + +/* Macro to define the module type. Each module can only have a single type. If + * a module provides multiple types, several separate modules must be created which + * then should share a single library containing the majority of code. This macro + * must be present in each module. -- rgerhards, 2007-12-14 + */ +#define MODULE_TYPE(x)\ +static rsRetVal modGetType(eModType_t *modType) \ + { \ + *modType = x; \ + return RS_RET_OK;\ + } + +#define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN) +#define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT) +#define MODULE_TYPE_LIB \ + DEF_LMOD_STATIC_DATA \ + MODULE_TYPE(eMOD_LIB) + +/* macro to define a unique module id. This must be able to fit in a void*. The + * module id must be unique inside a running rsyslogd application. It is used to + * track ownership of several objects. Most importantly, when the module is + * unloaded the module id value is used to find what needs to be destroyed. + * We currently use a pointer to modExit() as the module id. This sounds to be + * reasonable save, as each module must have this entry point AND there is no valid + * reason for twice this entry point being in memory. + * rgerhards, 2007-11-21 + */ +#define STD_LOADABLE_MODULE_ID ((void*) modExit) + + +/* macro to implement the "modGetID()" interface function + * rgerhards 2007-11-21 + */ +#define DEFmodGetID \ +static rsRetVal modGetID(void **pID) \ + { \ + *pID = STD_LOADABLE_MODULE_ID;\ + return RS_RET_OK;\ + } + +/* to following macros are used to generate function headers and standard + * functionality. It works as follows (described on the sample case of + * createInstance()): + * + * BEGINcreateInstance + * ... custom variable definitions (on stack) ... (if any) + * CODESTARTcreateInstance + * ... custom code ... (if any) + * ENDcreateInstance + */ + +/* createInstance() + */ +#define BEGINcreateInstance \ +static rsRetVal createInstance(instanceData **ppData)\ + {\ + DEFiRet; /* store error code here */\ + instanceData *pData; /* use this to point to data elements */ + +#define CODESTARTcreateInstance \ + if((pData = calloc(1, sizeof(instanceData))) == NULL) {\ + *ppData = NULL;\ + ENDfunc \ + return RS_RET_OUT_OF_MEMORY;\ + } + +#define ENDcreateInstance \ + *ppData = pData;\ + RETiRet;\ +} + +/* freeInstance() + * This is the cleanup function for the module instance. It is called immediately before + * the module instance is destroyed (unloaded). The module should do any cleanup + * here, e.g. close file, free instantance heap memory and the like. Control will + * not be passed back to the module once this function is finished. Keep in mind, + * however, that other instances may still be loaded and used. So do not destroy + * anything that may be used by another instance. If you have such a ressource, you + * currently need to do the instance counting yourself. + */ +#define BEGINfreeInstance \ +static rsRetVal freeInstance(void* pModData)\ +{\ + DEFiRet;\ + instanceData *pData; + +#define CODESTARTfreeInstance \ + pData = (instanceData*) pModData; + +#define ENDfreeInstance \ + if(pData != NULL)\ + free(pData); /* we need to free this in any case */\ + RETiRet;\ +} + +/* isCompatibleWithFeature() + */ +#define BEGINisCompatibleWithFeature \ +static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\ +{\ + rsRetVal iRet = RS_RET_INCOMPATIBLE; \ + BEGINfunc + +#define CODESTARTisCompatibleWithFeature + +#define ENDisCompatibleWithFeature \ + RETiRet;\ +} + +/* doAction() + */ +#define BEGINdoAction \ +static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __attribute__((unused)) iMsgOpts, instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTdoAction \ + /* ppString may be NULL if the output module requested no strings */ + +#define ENDdoAction \ + RETiRet;\ +} + + +/* dbgPrintInstInfo() + * Extra comments: + * Print debug information about this instance. + */ +#define BEGINdbgPrintInstInfo \ +static rsRetVal dbgPrintInstInfo(void *pModData)\ +{\ + DEFiRet;\ + instanceData *pData = NULL; + +#define CODESTARTdbgPrintInstInfo \ + pData = (instanceData*) pModData; + +#define ENDdbgPrintInstInfo \ + RETiRet;\ +} + + +/* parseSelectorAct() + * Extra comments: + * try to process a selector action line. Checks if the action + * applies to this module and, if so, processed it. If not, it + * is left untouched. The driver will then call another module. + * On exit, ppModData must point to instance data. Also, a string + * request object must be created and filled. A macro is defined + * for that. + * For the most usual case, we have defined a macro below. + * If more than one string is requested, the macro can be used together + * with own code that overwrites the entry count. In this case, the + * macro must come before the own code. It is recommended to be + * placed right after CODESTARTparseSelectorAct. + */ +#define BEGINparseSelectorAct \ +static rsRetVal parseSelectorAct(uchar **pp, void **ppModData, omodStringRequest_t **ppOMSR)\ +{\ + DEFiRet;\ + uchar *p;\ + instanceData *pData = NULL; + +#define CODESTARTparseSelectorAct \ + assert(pp != NULL);\ + assert(ppModData != NULL);\ + assert(ppOMSR != NULL);\ + p = *pp; + +#define CODE_STD_STRING_REQUESTparseSelectorAct(NumStrReqEntries) \ + CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries)); + +#define CODE_STD_FINALIZERparseSelectorAct \ +finalize_it:\ + if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {\ + *ppModData = pData;\ + *pp = p;\ + } else {\ + /* cleanup, we failed */\ + if(*ppOMSR != NULL) {\ + OMSRdestruct(*ppOMSR);\ + *ppOMSR = NULL;\ + }\ + if(pData != NULL) {\ + freeInstance(pData);\ + } \ + } + +#define ENDparseSelectorAct \ + RETiRet;\ +} + + +/* tryResume() + * This entry point is called to check if a module can resume operations. This + * happens when a module requested that it be suspended. In suspended state, + * the engine periodically tries to resume the module. If that succeeds, normal + * processing continues. If not, the module will not be called unless a + * tryResume() call succeeds. + * Returns RS_RET_OK, if resumption succeeded, RS_RET_SUSPENDED otherwise + * rgerhard, 2007-08-02 + */ +#define BEGINtryResume \ +static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\ +{\ + DEFiRet; + +#define CODESTARTtryResume \ + assert(pData != NULL); + +#define ENDtryResume \ + RETiRet;\ +} + + + +/* queryEtryPt() + */ +#define BEGINqueryEtryPt \ +DEFmodGetID \ +static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\ +{\ + DEFiRet; + +#define CODESTARTqueryEtryPt \ + if((name == NULL) || (pEtryPoint == NULL)) {\ + ENDfunc \ + return RS_RET_PARAM_ERROR;\ + } \ + *pEtryPoint = NULL; + +#define ENDqueryEtryPt \ + if(iRet == RS_RET_OK)\ + if(*pEtryPoint == NULL) { \ + dbgprintf("entry point '%s' not present in module\n", name); \ + iRet = RS_RET_MODULE_ENTRY_POINT_NOT_FOUND;\ + } \ + RETiRet;\ +} + +/* the following definition is the standard block for queryEtryPt for all types + * of modules. It should be included in any module, and typically is so by calling + * the module-type specific macros. + */ +#define CODEqueryEtryPt_STD_MOD_QUERIES \ + if(!strcmp((char*) name, "modExit")) {\ + *pEtryPoint = modExit;\ + } else if(!strcmp((char*) name, "modGetID")) {\ + *pEtryPoint = modGetID;\ + } else if(!strcmp((char*) name, "getType")) {\ + *pEtryPoint = modGetType;\ + } + +/* the following definition is the standard block for queryEtryPt for output + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_OMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "doAction")) {\ + *pEtryPoint = doAction;\ + } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\ + *pEtryPoint = dbgPrintInstInfo;\ + } else if(!strcmp((char*) name, "freeInstance")) {\ + *pEtryPoint = freeInstance;\ + } else if(!strcmp((char*) name, "parseSelectorAct")) {\ + *pEtryPoint = parseSelectorAct;\ + } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\ + *pEtryPoint = isCompatibleWithFeature;\ + } else if(!strcmp((char*) name, "tryResume")) {\ + *pEtryPoint = tryResume;\ + } + +/* the following definition is the standard block for queryEtryPt for INPUT + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_IMOD_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES \ + else if(!strcmp((char*) name, "runInput")) {\ + *pEtryPoint = runInput;\ + } else if(!strcmp((char*) name, "willRun")) {\ + *pEtryPoint = willRun;\ + } else if(!strcmp((char*) name, "afterRun")) {\ + *pEtryPoint = afterRun;\ + } + +/* the following definition is the standard block for queryEtryPt for LIBRARY + * modules. This can be used if no specific handling (e.g. to cover version + * differences) is needed. + */ +#define CODEqueryEtryPt_STD_LIB_QUERIES \ + CODEqueryEtryPt_STD_MOD_QUERIES + +/* modInit() + * This has an extra parameter, which is the specific name of the modInit + * function. That is needed for built-in modules, which must have unique + * names in order to link statically. Please note that this is alwaysy only + * the case with modInit() and NO other entry point. The reason is that only + * modInit() is visible form a linker/loader point of view. All other entry + * points are passed via rsyslog-internal query functions and are defined + * static inside the modules source. This is an important concept, as it allows + * us to support different interface versions within a single module. (Granted, + * we do not currently have different interface versions, so we can not put + * it to a test - but our firm believe is that we can do all abstraction needed...) + * + * Extra Comments: + * initialize the module + * + * Later, much more must be done. So far, we only return a pointer + * to the queryEtryPt() function + * TODO: do interface version checking & handshaking + * iIfVersRequetsed is the version of the interface specification that the + * caller would like to see being used. ipIFVersProvided is what we + * decide to provide. + * rgerhards, 2007-11-21: see modExit() comment below for important information + * on the need to initialize static data with code. modInit() may be called on a + * cached, left-in-memory copy of a previous incarnation. + */ +#define BEGINmodInit(uniqName) \ +rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\ +{\ + DEFiRet; \ + rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf); + +#define CODESTARTmodInit \ + assert(pHostQueryEtryPt != NULL);\ + iRet = pHostQueryEtryPt((uchar*)"objGetObjInterface", &pObjGetObjInterface); \ + if((iRet != RS_RET_OK) || (pQueryEtryPt == NULL) || (ipIFVersProvided == NULL) || (pObjGetObjInterface == NULL)) { \ + ENDfunc \ + return (iRet == RS_RET_OK) ? RS_RET_PARAM_ERROR : iRet; \ + } \ + /* now get the obj interface so that we can access other objects */ \ + CHKiRet(pObjGetObjInterface(&obj)); + +#define ENDmodInit \ +finalize_it:\ + *pQueryEtryPt = queryEtryPt;\ + RETiRet;\ +} + + +/* definitions for host API queries */ +#define CODEmodInit_QueryRegCFSLineHdlr \ + CHKiRet(pHostQueryEtryPt((uchar*)"regCfSysLineHdlr", &omsdRegCFSLineHdlr)); + +#endif /* #ifndef MODULE_TEMPLATE_H_INCLUDED */ + +/* modExit() + * This is the counterpart to modInit(). It destroys a module and makes it ready for + * unloading. It is similiar to freeInstance() for the instance data. Please note that + * this entry point needs to free any module-globale data structures and registrations. + * For example, the CfSysLineHandlers a module has registered need to be unregistered + * here. This entry point is only called immediately before unloading of the module. So + * it is likely to be destroyed. HOWEVER, the caller may decide to keep the module cached. + * So a module must never assume that it is actually destroyed. A call to modInit() may + * happen immediately after modExit(). So a module can NOT assume that static data elements + * are being re-initialized by the loader - this must always be done by module code itself. + * It is suggested to do this in modInit(). - rgerhards, 2007-11-21 + */ +#define BEGINmodExit \ +static rsRetVal modExit(void)\ +{\ + DEFiRet; + +#define CODESTARTmodExit + +#define ENDmodExit \ + RETiRet;\ +} + + +/* runInput() + * This is the main function for input modules. It is used to gather data from the + * input source and submit it to the message queue. Each runInput() instance has its own + * thread. This is handled by the rsyslog engine. It needs to spawn off new threads only + * if there is a module-internal need to do so. + */ +#define BEGINrunInput \ +static rsRetVal runInput(thrdInfo_t __attribute__((unused)) *pThrd)\ +{\ + DEFiRet; + +#define CODESTARTrunInput \ + dbgSetThrdName((uchar*)__FILE__); /* we need to provide something better later */ + +#define ENDrunInput \ + RETiRet;\ +} + + +/* willRun() + * This is a function that will be replaced in the longer term. It is used so + * that a module can tell the caller if it will run or not. This is to be replaced + * when we introduce input module instances. However, these require config syntax + * changes and I may (or may not... ;)) hold that until another config file + * format is available. -- rgerhards, 2007-12-17 + * returns RS_RET_NO_RUN if it will not run (RS_RET_OK or error otherwise) + */ +#define BEGINwillRun \ +static rsRetVal willRun(void)\ +{\ + DEFiRet; + +#define CODESTARTwillRun + +#define ENDwillRun \ + RETiRet;\ +} + + +/* afterRun() + * This function is called after an input module has been run and its thread has + * been terminated. It shall do any necessary cleanup. + * This is expected to evolve into a freeInstance type of call once the input module + * interface evolves to support multiple instances. + * rgerhards, 2007-12-17 + */ +#define BEGINafterRun \ +static rsRetVal afterRun(void)\ +{\ + DEFiRet; + +#define CODESTARTafterRun + +#define ENDafterRun \ + RETiRet;\ +} + + +/* + * vi:set ai: + */ diff --git a/runtime/modules.c b/runtime/modules.c new file mode 100644 index 00000000..f10390c7 --- /dev/null +++ b/runtime/modules.c @@ -0,0 +1,803 @@ +/* modules.c + * This is the implementation of syslogd modules object. + * This object handles plug-ins and build-in modules of all kind. + * + * Modules are reference-counted. Anyone who access a module must call + * Use() before any function is accessed and Release() when he is done. + * When the reference count reaches 0, rsyslog unloads the module (that + * may be changed in the future to cache modules). Rsyslog does NOT + * unload modules with a reference count > 0, even if the unload + * method is called! + * + * File begun on 2007-07-22 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#ifdef OS_BSD +# include "libgen.h" +#endif + +#include /* TODO: replace this with the libtools equivalent! */ + +#include +#include + +#include "syslogd.h" +#include "cfsysline.h" +#include "modules.h" +#include "errmsg.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + +static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */ +static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */ + +/* config settings */ +uchar *pModDir = NULL; /* read-only after startup */ + + +#ifdef DEBUG +/* we add some home-grown support to track our users (and detect who does not free us). In + * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 + */ + +/* add a user to the current list of users (always at the root) */ +static void +modUsrAdd(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + + BEGINfunc + if((pUsr = calloc(1, sizeof(modUsr_t))) == NULL) + goto finalize_it; + + if((pUsr->pszFile = strdup(pszUsr)) == NULL) { + free(pUsr); + goto finalize_it; + } + + if(pThis->pModUsrRoot != NULL) { + pUsr->pNext = pThis->pModUsrRoot; + } + pThis->pModUsrRoot = pUsr; + +finalize_it: + ENDfunc; +} + + +/* remove a user from the current user list + * rgerhards, 2008-03-11 + */ +static void +modUsrDel(modInfo_t *pThis, char *pszUsr) +{ + modUsr_t *pUsr; + modUsr_t *pPrev = NULL; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + if(!strcmp(pUsr->pszFile, pszUsr)) + break; + else + pPrev = pUsr; + } + + if(pUsr == NULL) { + dbgprintf("oops - tried to delete user %s from module %s and it wasn't registered as one...\n", + pszUsr, pThis->pszName); + } else { + if(pPrev == NULL) { + /* This was at the root! */ + pThis->pModUsrRoot = pUsr->pNext; + } else { + pPrev->pNext = pUsr->pNext; + } + /* free ressources */ + free(pUsr->pszFile); + free(pUsr); + pUsr = NULL; /* just to make sure... */ + } +} + + +/* print a short list all all source files using the module in question + * rgerhards, 2008-03-11 + */ +static void +modUsrPrint(modInfo_t *pThis) +{ + modUsr_t *pUsr; + + for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) { + dbgprintf("\tmodule %s is currently in use by file %s\n", + pThis->pszName, pUsr->pszFile); + } +} + + +/* print all loaded modules and who is accessing them. This is primarily intended + * to be called at end of run to detect "module leaks" and who is causing them. + * rgerhards, 2008-03-11 + */ +//static void +void +modUsrPrintAll(void) +{ + modInfo_t *pMod; + + BEGINfunc + for(pMod = pLoadedModules ; pMod != NULL ; pMod = pMod->pNext) { + dbgprintf("printing users of loadable module %s, refcount %u, ptr %p, type %d\n", pMod->pszName, pMod->uRefCnt, pMod, pMod->eType); + modUsrPrint(pMod); + } + ENDfunc +} + +#endif /* #ifdef DEBUG */ + + +/* Construct a new module object + */ +static rsRetVal moduleConstruct(modInfo_t **pThis) +{ + modInfo_t *pNew; + + if((pNew = calloc(1, sizeof(modInfo_t))) == NULL) + return RS_RET_OUT_OF_MEMORY; + + /* OK, we got the element, now initialize members that should + * not be zero-filled. + */ + + *pThis = pNew; + return RS_RET_OK; +} + + +/* Destructs a module object. The object must not be linked to the + * linked list of modules. Please note that all other dependencies on this + * modules must have been removed before (e.g. CfSysLineHandlers!) + */ +static void moduleDestruct(modInfo_t *pThis) +{ + assert(pThis != NULL); + if(pThis->pszName != NULL) + free(pThis->pszName); + if(pThis->pModHdlr != NULL) { +# ifdef VALGRIND +# warning "dlclose disabled for valgrind" +# else + dlclose(pThis->pModHdlr); +# endif + } + + free(pThis); +} + + +/* The following function is the queryEntryPoint for host-based entry points. + * Modules may call it to get access to core interface functions. Please note + * that utility functions can be accessed via shared libraries - at least this + * is my current shool of thinking. + * Please note that the implementation as a query interface allows to take + * care of plug-in interface version differences. -- rgerhards, 2007-07-31 + */ +static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)()) +{ + DEFiRet; + + if((name == NULL) || (pEtryPoint == NULL)) + return RS_RET_PARAM_ERROR; + + if(!strcmp((char*) name, "regCfSysLineHdlr")) { + *pEtryPoint = regCfSysLineHdlr; + } else if(!strcmp((char*) name, "objGetObjInterface")) { + *pEtryPoint = objGetObjInterface; + } else { + *pEtryPoint = NULL; /* to be on the safe side */ + ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND); + } + +finalize_it: + RETiRet; +} + + +/* get the name of a module + */ +static uchar *modGetName(modInfo_t *pThis) +{ + return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName); +} + + +/* get the state-name of a module. The state name is its name + * together with a short description of the module state (which + * is pulled from the module itself. + * rgerhards, 2007-07-24 + * TODO: the actual state name is not yet pulled + */ +static uchar *modGetStateName(modInfo_t *pThis) +{ + return(modGetName(pThis)); +} + + +/* Add a module to the loaded module linked list + */ +static inline void +addModToList(modInfo_t *pThis) +{ + assert(pThis != NULL); + + if(pLoadedModules == NULL) { + pLoadedModules = pLoadedModulesLast = pThis; + } else { + /* there already exist entries */ + pThis->pPrev = pLoadedModulesLast; + pLoadedModulesLast->pNext = pThis; + pLoadedModulesLast = pThis; + } +} + + +/* Get the next module pointer - this is used to traverse the list. + * The function returns the next pointer or NULL, if there is no next one. + * The last object must be provided to the function. If NULL is provided, + * it starts at the root of the list. Even in this case, NULL may be + * returned - then, the list is empty. + * rgerhards, 2007-07-23 + */ +static modInfo_t *GetNxt(modInfo_t *pThis) +{ + modInfo_t *pNew; + + if(pThis == NULL) + pNew = pLoadedModules; + else + pNew = pThis->pNext; + + return(pNew); +} + + +/* this function is like GetNxt(), but it returns pointers to + * modules of specific type only. As we currently deal just with output modules, + * it is a dummy, to be filled with real code later. + * rgerhards, 2007-07-24 + */ +static modInfo_t *GetNxtType(modInfo_t *pThis, eModType_t rqtdType) +{ + modInfo_t *pMod = pThis; + + do { + pMod = GetNxt(pMod); + } while(!(pMod == NULL || pMod->eType == rqtdType)); /* warning: do ... while() */ + + return pMod; +} + + +/* Prepare a module for unloading. + * This is currently a dummy, to be filled when we have a plug-in + * interface - rgerhards, 2007-08-09 + * rgerhards, 2007-11-21: + * When this function is called, all instance-data must already have + * been destroyed. In the case of output modules, this happens when the + * rule set is being destroyed. When we implement other module types, we + * need to think how we handle it there (and if we have any instance data). + * rgerhards, 2008-03-10: reject unload request if the module has a reference + * count > 0. + */ +static rsRetVal +modPrepareUnload(modInfo_t *pThis) +{ + DEFiRet; + void *pModCookie; + + assert(pThis != NULL); + + if(pThis->uRefCnt > 0) { + dbgprintf("rejecting unload of module '%s' because it has a refcount of %d\n", + pThis->pszName, pThis->uRefCnt); + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + + CHKiRet(pThis->modGetID(&pModCookie)); + pThis->modExit(); /* tell the module to get ready for unload */ + CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie)); + +finalize_it: + RETiRet; +} + + +/* Add an already-loaded module to the module linked list. This function does + * everything needed to fully initialize the module. + */ +static rsRetVal +doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*), uchar *name, void *pModHdlr) +{ + DEFiRet; + modInfo_t *pNew = NULL; + rsRetVal (*modGetType)(eModType_t *pType); + + assert(modInit != NULL); + + if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) { + pNew = NULL; + ABORT_FINALIZE(iRet); + } + + CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew)); + + if(pNew->iIFVers != CURR_MOD_IF_VERSION) { + ABORT_FINALIZE(RS_RET_MISSING_INTERFACE); + } + + /* We now poll the module to see what type it is. We do this only once as this + * can never change in the lifetime of an module. -- rgerhards, 2007-12-14 + */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getType", &modGetType)); + CHKiRet((iRet = (*modGetType)(&pNew->eType)) != RS_RET_OK); + dbgprintf("module of type %d being loaded.\n", pNew->eType); + + /* OK, we know we can successfully work with the module. So we now fill the + * rest of the data elements. First we load the interfaces common to all + * module types. + */ + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit)); + + /* ... and now the module-specific interfaces */ + switch(pNew->eType) { + case eMOD_IN: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"runInput", &pNew->mod.im.runInput)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"willRun", &pNew->mod.im.willRun)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"afterRun", &pNew->mod.im.afterRun)); + break; + case eMOD_OUT: + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", &pNew->dbgPrintInstInfo)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature)); + CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume)); + break; + case eMOD_LIB: + break; + } + + pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */ + pNew->pModHdlr = pModHdlr; + /* TODO: take this from module */ + if(pModHdlr == NULL) + pNew->eLinkType = eMOD_LINK_STATIC; + else + pNew->eLinkType = eMOD_LINK_DYNAMIC_LOADED; + + /* we initialized the structure, now let's add it to the linked list of modules */ + addModToList(pNew); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + moduleDestruct(pNew); + } + + RETiRet; +} + +/* Print loaded modules. This is more or less a + * debug or test aid, but anyhow I think it's worth it... + * This only works if the dbgprintf() subsystem is initialized. + * TODO: update for new input modules! + */ +static void modPrintList(void) +{ + modInfo_t *pMod; + + pMod = GetNxt(NULL); + while(pMod != NULL) { + dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ", + (char*) modGetName(pMod), pMod->iIFVers); + dbgprintf("type="); + switch(pMod->eType) { + case eMOD_OUT: + dbgprintf("output"); + break; + case eMOD_IN: + dbgprintf("input"); + break; + case eMOD_LIB: + dbgprintf("library"); + break; + } + dbgprintf(" module.\n"); + dbgprintf("Entry points:\n"); + dbgprintf("\tqueryEtryPt: 0x%lx\n", (unsigned long) pMod->modQueryEtryPt); + dbgprintf("\tdoAction: 0x%lx\n", (unsigned long) pMod->mod.om.doAction); + dbgprintf("\tparseSelectorAct: 0x%lx\n", (unsigned long) pMod->mod.om.parseSelectorAct); + dbgprintf("\tdbgPrintInstInfo: 0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo); + dbgprintf("\tfreeInstance: 0x%lx\n", (unsigned long) pMod->freeInstance); + dbgprintf("\n"); + pMod = GetNxt(pMod); /* done, go next */ + } +} + + +/* unlink and destroy a module. The caller must provide a pointer to the module + * itself as well as one to its immediate predecessor. + * rgerhards, 2008-02-26 + */ +static rsRetVal +modUnlinkAndDestroy(modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + /* first check if we are permitted to unload */ + if(pThis->eType == eMOD_LIB) { + if(pThis->uRefCnt > 0) { + dbgprintf("module %s NOT unloaded because it still has a refcount of %u\n", + pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + //modUsrPrintAll(); +# endif + ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED); + } + } + + /* we need to unlink the module before we can destruct it -- rgerhards, 2008-02-26 */ + if(pThis->pPrev == NULL) { + /* module is root, so we need to set a new root */ + pLoadedModules = pThis->pNext; + } else { + pThis->pPrev->pNext = pThis->pNext; + } + + if(pThis->pNext == NULL) { + pLoadedModulesLast = pThis->pPrev; + } else { + pThis->pNext->pPrev = pThis->pPrev; + } + + /* finally, we are ready for the module to go away... */ + dbgprintf("Unloading module %s\n", modGetName(pThis)); + CHKiRet(modPrepareUnload(pThis)); + *ppThis = pThis->pNext; + + moduleDestruct(pThis); + +finalize_it: + RETiRet; +} + + +/* unload all loaded modules of a specific type (use eMOD_ALL if you want to + * unload all module types). The unload happens only if the module is no longer + * referenced. So some modules may survive this call. + * rgerhards, 2008-03-11 + */ +static rsRetVal +modUnloadAndDestructAll(eModLinkType_t modLinkTypesToUnload) +{ + DEFiRet; + modInfo_t *pModCurr; /* module currently being processed */ + + pModCurr = GetNxt(NULL); + while(pModCurr != NULL) { + if(modLinkTypesToUnload == eMOD_LINK_ALL || pModCurr->eLinkType == modLinkTypesToUnload) { + if(modUnlinkAndDestroy(&pModCurr) == RS_RET_MODULE_STILL_REFERENCED) { + pModCurr = GetNxt(pModCurr); + } + /* Note: if the module was successfully unloaded, it has updated the + * pModCurr pointer to the next module. So we do NOT need to advance + * to the next module on successful unload. + */ + } else { + pModCurr = GetNxt(pModCurr); + } + } + +# ifdef DEBUG + if(pLoadedModules != NULL) { + dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n"); + modUsrPrintAll(); + } +# endif + + RETiRet; +} + + +/* load a module and initialize it, based on doModLoad() from conf.c + * rgerhards, 2008-03-05 + * varmojfekoj added support for dynamically loadable modules on 2007-08-13 + * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is + * called below. This is ok because modules are currently only loaded during + * configuration file processing, which is executed on a single thread. Should we + * change that design at any stage (what is unlikely), we need to find a + * replacement. + */ +static rsRetVal +Load(uchar *pModName) +{ + DEFiRet; + + size_t iPathLen, iModNameLen; + uchar szPath[PATH_MAX]; + uchar *pModNameCmp; + int bHasExtension; + void *pModHdlr, *pModInit; + modInfo_t *pModInfo; + + assert(pModName != NULL); + dbgprintf("Requested to load module '%s'\n", pModName); + + iModNameLen = strlen((char *) pModName); + if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) { + iModNameLen -= 3; + bHasExtension = TRUE; + } else + bHasExtension = FALSE; + + pModInfo = GetNxt(NULL); + while(pModInfo != NULL) { + if(!strncmp((char *) pModName, (char *) (pModNameCmp = modGetName(pModInfo)), iModNameLen) && + (!*(pModNameCmp + iModNameLen) || !strcmp((char *) pModNameCmp + iModNameLen, ".so"))) { + dbgprintf("Module '%s' already loaded\n", pModName); + ABORT_FINALIZE(RS_RET_OK); + } + pModInfo = GetNxt(pModInfo); + } + + /* now build our load module name */ + if(*pModName == '/') { + *szPath = '\0'; /* we do not need to append the path - its already in the module name */ + iPathLen = 0; + } else { + *szPath = '\0'; + strncat((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath) - 1); + iPathLen = strlen((char*) szPath); + if((szPath[iPathLen - 1] != '/')) { + if((iPathLen <= sizeof(szPath) - 2)) { + szPath[iPathLen++] = '/'; + szPath[iPathLen] = '\0'; + } else { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + } + } + + /* ... add actual name ... */ + strncat((char *) szPath, (char *) pModName, sizeof(szPath) - iPathLen - 1); + + /* now see if we have an extension and, if not, append ".so" */ + if(!bHasExtension) { + /* we do not have an extension and so need to add ".so" + * TODO: I guess this is highly importable, so we should change the + * algo over time... -- rgerhards, 2008-03-05 + */ + /* ... so now add the extension */ + strncat((char *) szPath, ".so", sizeof(szPath) - strlen((char*) szPath) - 1); + iPathLen += 3; + } + + if(iPathLen + strlen((char*) pModName) >= sizeof(szPath)) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', path too long\n", pModName); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_PATHLEN); + } + + /* complete load path constructed, so ... GO! */ + dbgprintf("loading module '%s'\n", szPath); + if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlopen: %s\n", szPath, dlerror()); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN); + } + if(!(pModInit = dlsym(pModHdlr, "modInit"))) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', dlsym: %s\n", szPath, dlerror()); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT); + } + if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "could not load module '%s', rsyslog error %d\n", szPath, iRet); + dlclose(pModHdlr); + ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED); + } + +finalize_it: + RETiRet; +} + + +/* set the default module load directory. A NULL value may be provided, in + * which case any previous value is deleted but no new one set. The caller-provided + * string is duplicated. If it needs to be freed, that's the caller's duty. + * rgerhards, 2008-03-07 + */ +static rsRetVal +SetModDir(uchar *pszModDir) +{ + DEFiRet; + + dbgprintf("setting default module load directory '%s'\n", pszModDir); + if(pModDir != NULL) { + free(pModDir); + } + + pModDir = (uchar*) strdup((char*)pszModDir); + + RETiRet; +} + + +/* Reference-Counting object access: add 1 to the current reference count. Must be + * called by anyone interested in using a module. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Use(char *srcFile, modInfo_t *pThis) +{ + DEFiRet; + + assert(pThis != NULL); + pThis->uRefCnt++; + dbgprintf("source file %s requested reference for module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); + +# ifdef DEBUG + modUsrAdd(pThis, srcFile); +# endif + + RETiRet; + +} + + +/* Reference-Counting object access: subract one from the current refcount. Must + * by called by anyone who no longer needs a module. If count reaches 0, the + * module is unloaded. -- rgerhards, 20080-03-10 + */ +static rsRetVal +Release(char *srcFile, modInfo_t **ppThis) +{ + DEFiRet; + modInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + if(pThis->uRefCnt == 0) { + /* oops, we are already at 0? */ + dbgprintf("internal error: module '%s' already has a refcount of 0 (released by %s)!\n", + pThis->pszName, srcFile); + } else { + --pThis->uRefCnt; + dbgprintf("file %s released module '%s', reference count now %u\n", + srcFile, pThis->pszName, pThis->uRefCnt); +# ifdef DEBUG + modUsrDel(pThis, srcFile); + modUsrPrint(pThis); +# endif + } + + if(pThis->uRefCnt == 0) { + /* we have a zero refcount, so we must unload the module */ + dbgprintf("module '%s' has zero reference count, unloading...\n", pThis->pszName); + modUnlinkAndDestroy(&pThis); + /* we must NOT do a *ppThis = NULL, because ppThis now points into freed memory! + * If in doubt, see obj.c::ReleaseObj() for how we are called. + */ + } + + RETiRet; + +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +BEGINObjClassExit(module, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(module) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + +# ifdef DEBUG + modUsrPrintAll(); /* debug aid - TODO: integrate with debug.c, at least the settings! */ +# endif +ENDObjClassExit(module) + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(module) +CODESTARTobjQueryInterface(module) + if(pIf->ifVersion != moduleCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->GetNxt = GetNxt; + pIf->GetNxtType = GetNxtType; + pIf->GetName = modGetName; + pIf->GetStateName = modGetStateName; + pIf->PrintList = modPrintList; + pIf->UnloadAndDestructAll = modUnloadAndDestructAll; + pIf->doModInit = doModInit; + pIf->SetModDir = SetModDir; + pIf->Load = Load; + pIf->Use = Use; + pIf->Release = Release; +finalize_it: +ENDobjQueryInterface(module) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-03-05 + */ +BEGINAbstractObjClassInit(module, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + uchar *pModPath; + + /* use any module load path specified in the environment */ + if((pModPath = (uchar*) getenv("RSYSLOG_MODDIR")) != NULL) { + SetModDir(pModPath); + } + + /* now check if another module path was set via the command line (-M) + * if so, that overrides the environment. Please note that we must use + * a global setting here because the command line parser can NOT call + * into the module object, because it is not initialized at that point. So + * instead a global setting is changed and we pick it up as soon as we + * initialize -- rgerhards, 2008-04-04 + */ + if(glblModPath != NULL) { + SetModDir(glblModPath); + } + + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDObjClassInit(module) + +/* vi:set ai: + */ diff --git a/runtime/modules.h b/runtime/modules.h new file mode 100644 index 00000000..7d34bcf7 --- /dev/null +++ b/runtime/modules.h @@ -0,0 +1,150 @@ +/* modules.h + * + * Definition for build-in and plug-ins module handler. This file is the base + * for all dynamically loadable module support. In theory, in v3 all modules + * are dynamically loaded, in practice we currently do have a few build-in + * once. This may become removed. + * + * The loader keeps track of what is loaded. For library modules, it is also + * used to find objects (libraries) and to obtain the queryInterface function + * for them. A reference count is maintened for libraries, so that they are + * unloaded only when nobody still accesses them. + * + * File begun on 2007-07-22 by RGerhards + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef MODULES_H_INCLUDED +#define MODULES_H_INCLUDED 1 + +#include "objomsr.h" + + +/* the following define defines the current version of the module interface. + * It can be used by any module which want's to simply prevent version conflicts + * and does not intend to do specific old-version emulations. + * rgerhards, 2008-03-04 + * version 3 adds modInfo_t ptr to call of modInit -- rgerhards, 2008-03-10 + * version 4 removes needUDPSocket OM callback -- rgerhards, 2008-03-22 + */ +#define CURR_MOD_IF_VERSION 4 + +typedef enum eModType_ { + eMOD_IN, /* input module */ + eMOD_OUT, /* output module */ + eMOD_LIB /* library module - this module provides one or many interfaces */ +} eModType_t; + + +#ifdef DEBUG +typedef struct modUsr_s { + struct modUsr_s *pNext; + char *pszFile; +} modUsr_t; +#endif + + +/* how is this module linked? */ +typedef enum eModLinkType_ { + eMOD_LINK_STATIC, + eMOD_LINK_DYNAMIC_UNLOADED, /* dynalink module, currently not loaded */ + eMOD_LINK_DYNAMIC_LOADED, /* dynalink module, currently loaded */ + eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */ +} eModLinkType_t; + +typedef struct modInfo_s { + struct modInfo_s *pPrev; /* support for creating a double linked module list */ + struct modInfo_s *pNext; /* support for creating a linked module list */ + int iIFVers; /* Interface version of module */ + eModType_t eType; /* type of this module */ + eModLinkType_t eLinkType; + uchar* pszName; /* printable module name, e.g. for dbgprintf */ + unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */ + /* functions supported by all types of modules */ + rsRetVal (*modInit)(int, int*, rsRetVal(**)()); /* initialize the module */ + /* be sure to support version handshake! */ + rsRetVal (*modQueryEtryPt)(uchar *name, rsRetVal (**EtryPoint)()); /* query entry point addresses */ + rsRetVal (*isCompatibleWithFeature)(syslogFeature); + rsRetVal (*freeInstance)(void*);/* called before termination or module unload */ + rsRetVal (*dbgPrintInstInfo)(void*);/* called before termination or module unload */ + rsRetVal (*tryResume)(void*);/* called to see if module actin can be resumed now */ + rsRetVal (*modExit)(void); /* called before termination or module unload */ + rsRetVal (*modGetID)(void **); /* get its unique ID from module */ + /* below: parse a configuration line - return if processed + * or not. If not, must be parsed to next module. + */ + rsRetVal (*parseConfigLine)(uchar **pConfLine); + /* below: create an instance of this module. Most importantly the module + * can allocate instance memory in this call. + */ + rsRetVal (*createInstance)(); + /* TODO: pass pointer to msg submit function to IM rger, 2007-12-14 */ + union { + struct {/* data for input modules */ + rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */ + rsRetVal (*willRun)(void); /* function to gather input and submit to queue */ + rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */ + } im; + struct {/* data for output modules */ + /* below: perform the configured action + */ + rsRetVal (*doAction)(uchar**, unsigned, void*); + rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**); + } om; + struct { /* data for library modules */ + } fm; + } mod; + void *pModHdlr; /* handler to the dynamic library holding the module */ +# ifdef DEBUG + /* we add some home-grown support to track our users (and detect who does not free us). In + * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11 + */ + modUsr_t *pModUsrRoot; +# endif +} modInfo_t; + +/* interfaces */ +BEGINinterface(module) /* name must also be changed in ENDinterface macro! */ + modInfo_t *(*GetNxt)(modInfo_t *pThis); + modInfo_t *(*GetNxtType)(modInfo_t *pThis, eModType_t rqtdType); + uchar *(*GetName)(modInfo_t *pThis); + uchar *(*GetStateName)(modInfo_t *pThis); + rsRetVal (*Use)(char *srcFile, modInfo_t *pThis); /**< must be called before a module is used (ref counting) */ + rsRetVal (*Release)(char *srcFile, modInfo_t **ppThis); /**< release a module (ref counting) */ + void (*PrintList)(void); + rsRetVal (*UnloadAndDestructAll)(eModLinkType_t modLinkTypesToUnload); + rsRetVal (*doModInit)(rsRetVal (*modInit)(), uchar *name, void *pModHdlr); + rsRetVal (*Load)(uchar *name); + rsRetVal (*SetModDir)(uchar *name); +ENDinterface(module) +#define moduleCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(module); + +/* TODO: remove them below (means move the config init code) -- rgerhards, 2008-02-19 */ +extern uchar *pModDir; /* read-only after startup */ + + +#endif /* #ifndef MODULES_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/runtime/msg.c b/runtime/msg.c new file mode 100644 index 00000000..ed9cdbbb --- /dev/null +++ b/runtime/msg.c @@ -0,0 +1,2294 @@ +/* msg.c + * The msg object. Implementation of all msg-related functions + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#define SYSLOG_NAMES +#include +#include +#include +#include "rsyslog.h" +#include "syslogd.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "template.h" +#include "msg.h" +#include "var.h" +#include "datetime.h" +#include "regexp.h" +#include "atomic.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(var) +DEFobjCurrIf(datetime) +DEFobjCurrIf(regexp) + +static syslogCODE rs_prioritynames[] = + { + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "err", LOG_ERR }, + { "error", LOG_ERR }, /* DEPRECATED */ + { "info", LOG_INFO }, + { "none", INTERNAL_NOPRI }, /* INTERNAL */ + { "notice", LOG_NOTICE }, + { "panic", LOG_EMERG }, /* DEPRECATED */ + { "warn", LOG_WARNING }, /* DEPRECATED */ + { "warning", LOG_WARNING }, + { NULL, -1 } + }; + +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif +static syslogCODE rs_facilitynames[] = + { + { "auth", LOG_AUTH }, + { "authpriv", LOG_AUTHPRIV }, + { "cron", LOG_CRON }, + { "daemon", LOG_DAEMON }, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif + { "kern", LOG_KERN }, + { "lpr", LOG_LPR }, + { "mail", LOG_MAIL }, + //{ "mark", INTERNAL_MARK }, /* INTERNAL */ + { "news", LOG_NEWS }, + { "security", LOG_AUTH }, /* DEPRECATED */ + { "syslog", LOG_SYSLOG }, + { "user", LOG_USER }, + { "uucp", LOG_UUCP }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL, -1 } + }; + +/* some forward declarations */ +static int getAPPNAMELen(msg_t *pM); + +/* The following functions will support advanced output module + * multithreading, once this is implemented. Currently, we + * include them as hooks only. The idea is that we need to guard + * some msg objects data fields against concurrent access if + * we run on multiple threads. Please note that in any case this + * is not necessary for calls from INPUT modules, because they + * construct the message object and do this serially. Only when + * the message is in the processing queue, multiple threads may + * access a single object. Consequently, there are no guard functions + * for "set" methods, as these are called during input. Only "get" + * functions that modify important structures have them. + * rgerhards, 2007-07-20 + * We now support locked and non-locked operations, depending on + * the configuration of rsyslog. To support this, we use function + * pointers. Initially, we start in non-locked mode. There, all + * locking operations call into dummy functions. When locking is + * enabled, the function pointers are changed to functions doing + * actual work. We also introduced another MsgPrepareEnqueue() function + * which initializes the locking structures, if needed. This is + * necessary because internal messages during config file startup + * processing are always created in non-locking mode. So we can + * not initialize locking structures during constructions. We now + * postpone this until when the message is fully constructed and + * enqueued. Then we know the status of locking. This has a nice + * side effect, and that is that during the initial creation of + * the Msg object no locking needs to be done, which results in better + * performance. -- rgerhards, 2008-01-05 + */ +static void (*funcLock)(msg_t *pMsg); +static void (*funcUnlock)(msg_t *pMsg); +static void (*funcDeleteMutex)(msg_t *pMsg); +void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#if 1 /* This is a debug aid */ +#define MsgLock(pMsg) funcLock(pMsg) +#define MsgUnlock(pMsg) funcUnlock(pMsg) +#else +#define MsgLock(pMsg) {dbgprintf("line %d\n - ", __LINE__); funcLock(pMsg);; } +#define MsgUnlock(pMsg) {dbgprintf("line %d - ", __LINE__); funcUnlock(pMsg); } +#endif + +/* the next function is a dummy to be used by the looking functions + * when the class is not yet running in an environment where locking + * is necessary. Please note that the need to lock can (and will) change + * during a single run. Typically, this is depending on the operation mode + * of the message queues (which is operator-configurable). -- rgerhards, 2008-01-05 + */ +static void MsgLockingDummy(msg_t __attribute__((unused)) *pMsg) +{ + /* empty be design */ +} + + +/* The following function prepares a message for enqueue into the queue. This is + * where a message may be accessed by multiple threads. This implementation here + * is the version for multiple concurrent acces. It initializes the locking + * structures. + */ +static void MsgPrepareEnqueueLockingCase(msg_t *pThis) +{ + assert(pThis != NULL); + pthread_mutexattr_settype(&pThis->mutAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&pThis->mut, &pThis->mutAttr); +} + +/* ... and now the locking and unlocking implementations: */ +static void MsgLockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_lock(&pThis->mut); +} + +static void MsgUnlockLockingCase(msg_t *pThis) +{ + /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */ + assert(pThis != NULL); + pthread_mutex_unlock(&pThis->mut); +} + +/* delete the mutex object on message destruction (locking case) + */ +static void MsgDeleteMutexLockingCase(msg_t *pThis) +{ + assert(pThis != NULL); + pthread_mutex_destroy(&pThis->mut); +} + +/* enable multiple concurrent access on the message object + * This works on a class-wide basis and can bot be undone. + * That is, if it is once enabled, it can not be disabled during + * the same run. When this function is called, no other thread + * must manipulate message objects. Then we would have race conditions, + * but guarding against this is counter-productive because it + * would cost additional time. Plus, it would be a programming error. + * rgerhards, 2008-01-05 + */ +rsRetVal MsgEnableThreadSafety(void) +{ + funcLock = MsgLockLockingCase; + funcUnlock = MsgUnlockLockingCase; + funcMsgPrepareEnqueue = MsgPrepareEnqueueLockingCase; + funcDeleteMutex = MsgDeleteMutexLockingCase; + return RS_RET_OK; +} + +/* end locking functions */ + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". + */ +rsRetVal msgConstruct(msg_t **ppThis) +{ + DEFiRet; + msg_t *pM; + + assert(ppThis != NULL); + if((pM = calloc(1, sizeof(msg_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* initialize members that are non-zero */ + pM->iRefCount = 1; + pM->iSeverity = -1; + pM->iFacility = -1; + datetime.getCurrTime(&(pM->tRcvdAt)); + objConstructSetObjInfo(pM); + + /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/ + + *ppThis = pM; + +finalize_it: + RETiRet; +} + + +BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */ + int currRefCount; +CODESTARTobjDestruct(msg) + /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pM, pM->iRefCount - 1); */ +# ifdef DO_HAVE_ATOMICS + currRefCount = ATOMIC_DEC_AND_FETCH(pThis->iRefCount); +# else + currRefCount = --pThis->iRefCount; +# endif + if(currRefCount == 0) + { + /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */ + if(pThis->pszUxTradMsg != NULL) + free(pThis->pszUxTradMsg); + if(pThis->pszRawMsg != NULL) + free(pThis->pszRawMsg); + if(pThis->pszTAG != NULL) + free(pThis->pszTAG); + if(pThis->pszHOSTNAME != NULL) + free(pThis->pszHOSTNAME); + if(pThis->pszRcvFrom != NULL) + free(pThis->pszRcvFrom); + if(pThis->pszMSG != NULL) + free(pThis->pszMSG); + if(pThis->pszFacility != NULL) + free(pThis->pszFacility); + if(pThis->pszFacilityStr != NULL) + free(pThis->pszFacilityStr); + if(pThis->pszSeverity != NULL) + free(pThis->pszSeverity); + if(pThis->pszSeverityStr != NULL) + free(pThis->pszSeverityStr); + if(pThis->pszRcvdAt3164 != NULL) + free(pThis->pszRcvdAt3164); + if(pThis->pszRcvdAt3339 != NULL) + free(pThis->pszRcvdAt3339); + if(pThis->pszRcvdAt_MySQL != NULL) + free(pThis->pszRcvdAt_MySQL); + if(pThis->pszRcvdAt_PgSQL != NULL) + free(pThis->pszRcvdAt_PgSQL); + if(pThis->pszTIMESTAMP3164 != NULL) + free(pThis->pszTIMESTAMP3164); + if(pThis->pszTIMESTAMP3339 != NULL) + free(pThis->pszTIMESTAMP3339); + if(pThis->pszTIMESTAMP_MySQL != NULL) + free(pThis->pszTIMESTAMP_MySQL); + if(pThis->pszTIMESTAMP_PgSQL != NULL) + free(pThis->pszTIMESTAMP_PgSQL); + if(pThis->pszPRI != NULL) + free(pThis->pszPRI); + if(pThis->pCSProgName != NULL) + rsCStrDestruct(&pThis->pCSProgName); + if(pThis->pCSStrucData != NULL) + rsCStrDestruct(&pThis->pCSStrucData); + if(pThis->pCSAPPNAME != NULL) + rsCStrDestruct(&pThis->pCSAPPNAME); + if(pThis->pCSPROCID != NULL) + rsCStrDestruct(&pThis->pCSPROCID); + if(pThis->pCSMSGID != NULL) + rsCStrDestruct(&pThis->pCSMSGID); + funcDeleteMutex(pThis); + } else { + pThis = NULL; /* tell framework not to destructing the object! */ + } +ENDobjDestruct(msg) + + +/* The macros below are used in MsgDup(). I use macros + * to keep the fuction code somewhat more readyble. It is my + * replacement for inline functions in CPP + */ +#define tmpCOPYSZ(name) \ + if(pOld->psz##name != NULL) { \ + if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + pNew->iLen##name = pOld->iLen##name;\ + } + +/* copy the CStr objects. + * if the old value is NULL, we do not need to do anything because we + * initialized the new value to NULL via calloc(). + */ +#define tmpCOPYCSTR(name) \ + if(pOld->pCS##name != NULL) {\ + if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + } +/* Constructs a message object by duplicating another one. + * Returns NULL if duplication failed. We do not need to lock the + * message object here, because a fully-created msg object is never + * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup() + * can never run into a situation where the message object is being + * modified while its content is copied - it's forbidden by definition. + * rgerhards, 2007-07-10 + */ +msg_t* MsgDup(msg_t* pOld) +{ + msg_t* pNew; + + assert(pOld != NULL); + + BEGINfunc + if(msgConstruct(&pNew) != RS_RET_OK) { + return NULL; + } + + /* now copy the message properties */ + pNew->iRefCount = 1; + pNew->iSeverity = pOld->iSeverity; + pNew->iFacility = pOld->iFacility; + pNew->bParseHOSTNAME = pOld->bParseHOSTNAME; + pNew->msgFlags = pOld->msgFlags; + pNew->iProtocolVersion = pOld->iProtocolVersion; + memcpy(&pNew->tRcvdAt, &pOld->tRcvdAt, sizeof(struct syslogTime)); + memcpy(&pNew->tTIMESTAMP, &pOld->tTIMESTAMP, sizeof(struct syslogTime)); + tmpCOPYSZ(Severity); + tmpCOPYSZ(SeverityStr); + tmpCOPYSZ(Facility); + tmpCOPYSZ(FacilityStr); + tmpCOPYSZ(PRI); + tmpCOPYSZ(RawMsg); + tmpCOPYSZ(MSG); + tmpCOPYSZ(UxTradMsg); + tmpCOPYSZ(TAG); + tmpCOPYSZ(HOSTNAME); + tmpCOPYSZ(RcvFrom); + + tmpCOPYCSTR(ProgName); + tmpCOPYCSTR(StrucData); + tmpCOPYCSTR(APPNAME); + tmpCOPYCSTR(PROCID); + tmpCOPYCSTR(MSGID); + + /* we do not copy all other cache properties, as we do not even know + * if they are needed once again. So we let them re-create if needed. + */ + + ENDfunc + return pNew; +} +#undef tmpCOPYSZ +#undef tmpCOPYCSTR + + +/* This method serializes a message object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object by calling MsgDeSerialize(). + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the cache properties. We re-create them when needed. + * This saves us a lot of memory. Performance is no concern, as serializing + * is a so slow operation that recration of the caches does not count. Also, + * we do not serialize bParseHOSTNAME, as this is only a helper variable + * during msg construction - and never again used later. + * rgerhards, 2008-01-03 + */ +static rsRetVal MsgSerialize(msg_t *pThis, strm_t *pStrm) +{ + DEFiRet; + + assert(pThis != NULL); + assert(pStrm != NULL); + + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + objSerializeSCALAR(pStrm, iProtocolVersion, SHORT); + objSerializeSCALAR(pStrm, iSeverity, SHORT); + objSerializeSCALAR(pStrm, iFacility, SHORT); + objSerializeSCALAR(pStrm, msgFlags, INT); + objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME); + objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); + + objSerializePTR(pStrm, pszRawMsg, PSZ); + objSerializePTR(pStrm, pszMSG, PSZ); + objSerializePTR(pStrm, pszUxTradMsg, PSZ); + objSerializePTR(pStrm, pszTAG, PSZ); + objSerializePTR(pStrm, pszHOSTNAME, PSZ); + objSerializePTR(pStrm, pszRcvFrom, PSZ); + + objSerializePTR(pStrm, pCSStrucData, CSTR); + objSerializePTR(pStrm, pCSAPPNAME, CSTR); + objSerializePTR(pStrm, pCSPROCID, CSTR); + objSerializePTR(pStrm, pCSMSGID, CSTR); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + +/* Increment reference count - see description of the "msg" + * structure for details. As a convenience to developers, + * this method returns the msg pointer that is passed to it. + * It is recommended that it is called as follows: + * + * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer); + */ +msg_t *MsgAddRef(msg_t *pM) +{ + assert(pM != NULL); +# ifdef DO_HAVE_ATOMICS + ATOMIC_INC(pM->iRefCount); +# else + MsgLock(pM); + pM->iRefCount++; + MsgUnlock(pM); +# endif + /* DEV debugging only! dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);*/ + return(pM); +} + + +/* This functions tries to aquire the PROCID from TAG. Its primary use is + * when a legacy syslog message has been received and should be forwarded as + * syslog-protocol (or the PROCID is requested for any other reason). + * In legacy syslog, the PROCID is considered to be the character sequence + * between the first [ and the first ]. This usually are digits only, but we + * do not check that. However, if there is no closing ], we do not assume we + * can obtain a PROCID. Take in mind that not every legacy syslog message + * actually has a PROCID. + * rgerhards, 2005-11-24 + */ +static rsRetVal aquirePROCIDFromTAG(msg_t *pM) +{ + register int i; + DEFiRet; + + assert(pM != NULL); + if(pM->pCSPROCID != NULL) + return RS_RET_OK; /* we are already done ;) */ + + if(getProtocolVersion(pM) != 0) + return RS_RET_OK; /* we can only emulate if we have legacy format */ + + /* find first '['... */ + i = 0; + while((i < pM->iLenTAG) && (pM->pszTAG[i] != '[')) + ++i; + if(!(i < pM->iLenTAG)) + return RS_RET_OK; /* no [, so can not emulate... */ + + ++i; /* skip '[' */ + + /* now obtain the PROCID string... */ + CHKiRet(rsCStrConstruct(&pM->pCSPROCID)); + rsCStrSetAllocIncrement(pM->pCSPROCID, 16); + while((i < pM->iLenTAG) && (pM->pszTAG[i] != ']')) { + CHKiRet(rsCStrAppendChar(pM->pCSPROCID, pM->pszTAG[i])); + ++i; + } + + if(!(i < pM->iLenTAG)) { + /* oops... it looked like we had a PROCID, but now it has + * turned out this is not true. In this case, we need to free + * the buffer and simply return. Note that this is NOT an error + * case! + */ + rsCStrDestruct(&pM->pCSPROCID); + FINALIZE; + } + + /* OK, finaally we could obtain a PROCID. So let's use it ;) */ + CHKiRet(rsCStrFinish(pM->pCSPROCID)); + +finalize_it: + RETiRet; +} + + +/* Parse and set the "programname" for a given MSG object. Programname + * is a BSD concept, it is the tag without any instance-specific information. + * Precisely, the programname is terminated by either (whichever occurs first): + * - end of tag + * - nonprintable character + * - ':' + * - '[' + * - '/' + * The above definition has been taken from the FreeBSD syslogd sources. + * + * The program name is not parsed by default, because it is infrequently-used. + * If it is needed, this function should be called first. It checks if it is + * already set and extracts it, if not. + * A message object must be provided, else a crash will occur. + * rgerhards, 2005-10-19 + */ +static rsRetVal aquireProgramName(msg_t *pM) +{ + DEFiRet; + register int i; + + assert(pM != NULL); + if(pM->pCSProgName == NULL) { + /* ok, we do not yet have it. So let's parse the TAG + * to obtain it. + */ + CHKiRet(rsCStrConstruct(&pM->pCSProgName)); + rsCStrSetAllocIncrement(pM->pCSProgName, 33); + for( i = 0 + ; (i < pM->iLenTAG) && isprint((int) pM->pszTAG[i]) + && (pM->pszTAG[i] != '\0') && (pM->pszTAG[i] != ':') + && (pM->pszTAG[i] != '[') && (pM->pszTAG[i] != '/') + ; ++i) { + CHKiRet(rsCStrAppendChar(pM->pCSProgName, pM->pszTAG[i])); + } + CHKiRet(rsCStrFinish(pM->pCSProgName)); + } +finalize_it: + RETiRet; +} + + +/* This function moves the HOSTNAME inside the message object to the + * TAG. It is a specialised function used to handle the condition when + * a message without HOSTNAME is being processed. The missing HOSTNAME + * is only detected at a later stage, during TAG processing, so that + * we already had set the HOSTNAME property and now need to move it to + * the TAG. Of course, we could do this via a couple of get/set methods, + * but it is far more efficient to do it via this specialised method. + * This is especially important as this can be a very common case, e.g. + * when BSD syslog is acting as a sender. + * rgerhards, 2005-11-10. + */ +void moveHOSTNAMEtoTAG(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pszTAG != NULL) + free(pM->pszTAG); + pM->pszTAG = pM->pszHOSTNAME; + pM->iLenTAG = pM->iLenHOSTNAME; + pM->pszHOSTNAME = NULL; + pM->iLenHOSTNAME = 0; +} + +/* Access methods - dumb & easy, not a comment for each ;) + */ +void setProtocolVersion(msg_t *pM, int iNewVersion) +{ + assert(pM != NULL); + if(iNewVersion != 0 && iNewVersion != 1) { + dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion); + iNewVersion = 0; + } + pM->iProtocolVersion = iNewVersion; +} + +int getProtocolVersion(msg_t *pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion); +} + +/* note: string is taken from constant pool, do NOT free */ +char *getProtocolVersionString(msg_t *pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion ? "1" : "0"); +} + +int getMSGLen(msg_t *pM) +{ + return((pM == NULL) ? 0 : pM->iLenMSG); +} + + +char *getRawMsg(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszRawMsg == NULL) + return ""; + else + return (char*)pM->pszRawMsg; +} + +char *getUxTradMsg(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszUxTradMsg == NULL) + return ""; + else + return (char*)pM->pszUxTradMsg; +} + +char *getMSG(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszMSG == NULL) + return ""; + else + return (char*)pM->pszMSG; +} + + +/* Get PRI value in text form */ +char *getPRI(msg_t *pM) +{ + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszPRI == NULL) { + /* OK, we need to construct it... + * we use a 5 byte buffer - as of + * RFC 3164, it can't be longer. Should it + * still be, snprintf will truncate... + */ + if((pM->pszPRI = malloc(5)) == NULL) return ""; + pM->iLenPRI = snprintf((char*)pM->pszPRI, 5, "%d", + LOG_MAKEPRI(pM->iFacility, pM->iSeverity)); + } + MsgUnlock(pM); + + return (char*)pM->pszPRI; +} + + +/* Get PRI value as integer */ +int getPRIi(msg_t *pM) +{ + assert(pM != NULL); + return (pM->iFacility << 3) + (pM->iSeverity); +} + + +char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) +{ + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_MySQL == NULL) { + if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL, 15); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_PgSQL == NULL) { + if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL, 21); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_PgSQL); + case tplFmtRFC3164Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, 16); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3339 == NULL) { + if((pM->pszTIMESTAMP3339 = malloc(33)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; /* TODO: check this: can it cause a free() of constant memory?) */ + } + datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339, 33); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3339); + } + return "INVALID eFmt OPTION!"; +} + +char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) +{ + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_MySQL == NULL) { + if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL, 15); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_PgSQL == NULL) { + if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL, 21); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_PgSQL); + case tplFmtRFC3164Date: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 16); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszRcvdAt3339 == NULL) { + if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { + glblHadMemShortage = 1; + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339, 33); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3339); + } + return "INVALID eFmt OPTION!"; +} + + +char *getSeverity(msg_t *pM) +{ + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszSeverity == NULL) { + /* we use a 2 byte buffer - can only be one digit */ + if((pM->pszSeverity = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverity = + snprintf((char*)pM->pszSeverity, 2, "%d", pM->iSeverity); + } + MsgUnlock(pM); + return((char*)pM->pszSeverity); +} + + +char *getSeverityStr(msg_t *pM) +{ + syslogCODE *c; + int val; + char *name = NULL; + + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszSeverityStr == NULL) { + for(c = rs_prioritynames, val = pM->iSeverity; c->c_name; c++) + if(c->c_val == val) { + name = c->c_name; + break; + } + if(name == NULL) { + /* we use a 2 byte buffer - can only be one digit */ + if((pM->pszSeverityStr = malloc(2)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverityStr = + snprintf((char*)pM->pszSeverityStr, 2, "%d", pM->iSeverity); + } else { + if((pM->pszSeverityStr = (uchar*) strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenSeverityStr = strlen((char*)name); + } + } + MsgUnlock(pM); + return((char*)pM->pszSeverityStr); +} + +char *getFacility(msg_t *pM) +{ + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszFacility == NULL) { + /* we use a 12 byte buffer - as of + * syslog-protocol, facility can go + * up to 2^32 -1 + */ + if((pM->pszFacility = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacility = + snprintf((char*)pM->pszFacility, 12, "%d", pM->iFacility); + } + MsgUnlock(pM); + return((char*)pM->pszFacility); +} + +char *getFacilityStr(msg_t *pM) +{ + syslogCODE *c; + int val; + char *name = NULL; + + if(pM == NULL) + return ""; + + MsgLock(pM); + if(pM->pszFacilityStr == NULL) { + for(c = rs_facilitynames, val = pM->iFacility << 3; c->c_name; c++) + if(c->c_val == val) { + name = c->c_name; + break; + } + if(name == NULL) { + /* we use a 12 byte buffer - as of + * syslog-protocol, facility can go + * up to 2^32 -1 + */ + if((pM->pszFacilityStr = malloc(12)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacilityStr = + snprintf((char*)pM->pszFacilityStr, 12, "%d", val >> 3); + } else { + if((pM->pszFacilityStr = (uchar*)strdup(name)) == NULL) { MsgUnlock(pM) ; return ""; } + pM->iLenFacilityStr = strlen((char*)name); + } + } + MsgUnlock(pM); + return((char*)pM->pszFacilityStr); +} + + +/* set flow control state (if not called, the default - NO_DELAY - is used) + * This needs no locking because it is only done while the object is + * not fully constructed (which also means you must not call this + * method after the msg has been handed over to a queue). + * rgerhards, 2008-03-14 + */ +rsRetVal +MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl) +{ + DEFiRet; + assert(pMsg != NULL); + assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY); + + pMsg->flowCtlType = eFlowCtl; + + RETiRet; +} + + +/* rgerhards 2004-11-24: set APP-NAME in msg object + * TODO: revisit msg locking code! + */ +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME) +{ + DEFiRet; + assert(pMsg != NULL); + if(pMsg->pCSAPPNAME == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); + rsCStrSetAllocIncrement(pMsg->pCSAPPNAME, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME); + +finalize_it: + RETiRet; +} + + +static void tryEmulateAPPNAME(msg_t *pM); /* forward reference */ +/* rgerhards, 2005-11-24 + */ +char *getAPPNAME(msg_t *pM) +{ + assert(pM != NULL); + MsgLock(pM); + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + MsgUnlock(pM); + return (pM->pCSAPPNAME == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); +} + + +/* rgerhards 2004-11-24: set PROCID in msg object + */ +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSPROCID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSPROCID)); + rsCStrSetAllocIncrement(pMsg->pCSPROCID, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID); + +finalize_it: + RETiRet; +} + +/* rgerhards, 2005-11-24 + */ +int getPROCIDLen(msg_t *pM) +{ + assert(pM != NULL); + MsgLock(pM); + if(pM->pCSPROCID == NULL) + aquirePROCIDFromTAG(pM); + MsgUnlock(pM); + return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); +} + + +/* rgerhards, 2005-11-24 + */ +char *getPROCID(msg_t *pM) +{ + char* pszRet; + + ISOBJ_TYPE_assert(pM, msg); + MsgLock(pM); + if(pM->pCSPROCID == NULL) + aquirePROCIDFromTAG(pM); + pszRet = (pM->pCSPROCID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSPROCID); + MsgUnlock(pM); + return pszRet; +} + + +/* rgerhards 2004-11-24: set MSGID in msg object + */ +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSMSGID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); + rsCStrSetAllocIncrement(pMsg->pCSMSGID, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID); + +finalize_it: + RETiRet; +} + +/* rgerhards, 2005-11-24 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getMSGIDLen(msg_t *pM) +{ + return (pM->pCSMSGID == NULL) ? 1 : rsCStrLen(pM->pCSMSGID); +} +#endif + + +/* rgerhards, 2005-11-24 + */ +char *getMSGID(msg_t *pM) +{ + return (pM->pCSMSGID == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID); +} + + +/* Set the TAG to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetTAG(). + * rgerhards 2004-11-19 + */ +void MsgAssignTAG(msg_t *pMsg, uchar *pBuf) +{ + assert(pMsg != NULL); + pMsg->iLenTAG = (pBuf == NULL) ? 0 : strlen((char*)pBuf); + pMsg->pszTAG = (uchar*) pBuf; +} + + +/* rgerhards 2004-11-16: set TAG in msg object + */ +void MsgSetTAG(msg_t *pMsg, char* pszTAG) +{ + assert(pMsg != NULL); + if(pMsg->pszTAG != NULL) + free(pMsg->pszTAG); + pMsg->iLenTAG = strlen(pszTAG); + if((pMsg->pszTAG = malloc(pMsg->iLenTAG + 1)) != NULL) + memcpy(pMsg->pszTAG, pszTAG, pMsg->iLenTAG + 1); + else + dbgprintf("Could not allocate memory in MsgSetTAG()\n"); +} + + +/* This function tries to emulate the TAG if none is + * set. Its primary purpose is to provide an old-style TAG + * when a syslog-protocol message has been received. Then, + * the tag is APP-NAME "[" PROCID "]". The function first checks + * if there is a TAG and, if not, if it can emulate it. + * rgerhards, 2005-11-24 + */ +static void tryEmulateTAG(msg_t *pM) +{ + int iTAGLen; + uchar *pBuf; + assert(pM != NULL); + + if(pM->pszTAG != NULL) + return; /* done, no need to emulate */ + + if(getProtocolVersion(pM) == 1) { + if(!strcmp(getPROCID(pM), "-")) { + /* no process ID, use APP-NAME only */ + MsgSetTAG(pM, getAPPNAME(pM)); + } else { + /* now we can try to emulate */ + iTAGLen = getAPPNAMELen(pM) + getPROCIDLen(pM) + 3; + if((pBuf = malloc(iTAGLen * sizeof(char))) == NULL) + return; /* nothing we can do */ + snprintf((char*)pBuf, iTAGLen, "%s[%s]", getAPPNAME(pM), getPROCID(pM)); + MsgAssignTAG(pM, pBuf); + } + } +} + + +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getTAGLen(msg_t *pM) +{ + if(pM == NULL) + return 0; + else { + tryEmulateTAG(pM); + if(pM->pszTAG == NULL) + return 0; + else + return pM->iLenTAG; + } +} +#endif + + +char *getTAG(msg_t *pM) +{ + char *ret; + + if(pM == NULL) + ret = ""; + else { + MsgLock(pM); + tryEmulateTAG(pM); + if(pM->pszTAG == NULL) + ret = ""; + else + ret = (char*) pM->pszTAG; + MsgUnlock(pM); + } + return(ret); +} + + +int getHOSTNAMELen(msg_t *pM) +{ + if(pM == NULL) + return 0; + else + if(pM->pszHOSTNAME == NULL) + return 0; + else + return pM->iLenHOSTNAME; +} + + +char *getHOSTNAME(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszHOSTNAME == NULL) + return ""; + else + return (char*) pM->pszHOSTNAME; +} + + +char *getRcvFrom(msg_t *pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszRcvFrom == NULL) + return ""; + else + return (char*) pM->pszRcvFrom; +} + +/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object + */ +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSStrucData == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData)); + rsCStrSetAllocIncrement(pMsg->pCSStrucData, 128); + } + /* if we reach this point, we have the object */ + iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData); + +finalize_it: + RETiRet; +} + +/* get the length of the "STRUCTURED-DATA" sz string + * rgerhards, 2005-11-24 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static int getStructuredDataLen(msg_t *pM) +{ + return (pM->pCSStrucData == NULL) ? 1 : rsCStrLen(pM->pCSStrucData); +} +#endif + + +/* get the "STRUCTURED-DATA" as sz string + * rgerhards, 2005-11-24 + */ +char *getStructuredData(msg_t *pM) +{ + return (pM->pCSStrucData == NULL) ? "-" : (char*) rsCStrGetSzStrNoNULL(pM->pCSStrucData); +} + + + +/* get the length of the "programname" sz string + * rgerhards, 2005-10-19 + */ +int getProgramNameLen(msg_t *pM) +{ + int iRet; + + assert(pM != NULL); + MsgLock(pM); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramNameLen()\n", iRet); + MsgUnlock(pM); + return 0; /* best we can do (consistent wiht what getProgramName() returns) */ + } + MsgUnlock(pM); + + return (pM->pCSProgName == NULL) ? 0 : rsCStrLen(pM->pCSProgName); +} + + +/* get the "programname" as sz string + * rgerhards, 2005-10-19 + */ +char *getProgramName(msg_t *pM) /* this is the non-locking version for internal use */ +{ + int iRet; + char *pszRet; + + assert(pM != NULL); + MsgLock(pM); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); + pszRet = ""; /* best we can do */ + } else { + pszRet = (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); + } + + MsgUnlock(pM); + return pszRet; +} +/* The code below was an approach without PTHREAD_MUTEX_RECURSIVE + * However, it turned out to be quite complex. So far, we use recursive + * locking, which is OK from a performance point of view, especially as + * we do not anticipate that multithreading msg objects is used often. + * However, we may re-think about using non-recursive locking and I leave this + * code in here to conserve the idea. -- rgerhards, 2008-01-05 + */ +#if 0 +static char *getProgramNameNoLock(msg_t *pM) /* this is the non-locking version for internal use */ +{ + int iRet; + + assert(pM != NULL); + if((iRet = aquireProgramName(pM)) != RS_RET_OK) { + dbgprintf("error %d returned by aquireProgramName() in getProgramName()\n", iRet); + return ""; /* best we can do */ + } + + return (pM->pCSProgName == NULL) ? "" : (char*) rsCStrGetSzStrNoNULL(pM->pCSProgName); +} +char *getProgramName(msg_t *pM) /* this is the external callable version */ +{ + char *pszRet; + + MsgLock(pM); + pszRet = getProgramNameNoLock(pM); + MsgUnlock(pM); + return pszRet; +} +/* an alternative approach has been: */ +/* The macro below is used to generate external function definitions + * for such functions that may also be called internally (and thus have + * both a locking and non-locking implementation. Over time, we could + * reconsider how we handle that. -- rgerhards, 2008-01-05 + */ +#define EXT_LOCKED_FUNC(fName, ret) \ +ret fName(msg_t *pM) \ +{ \ + ret valRet; \ + MsgLock(pM); \ + valRet = fName##NoLock(pM); \ + MsgUnlock(pM); \ + return(valRet); \ +} +EXT_LOCKED_FUNC(getProgramName, char*) +/* in this approach, the external function is provided by the macro and + * needs not to be writen. + */ +#endif /* #if 0 -- saved code */ + + +/* This function tries to emulate APPNAME if it is not present. Its + * main use is when we have received a log record via legacy syslog and + * now would like to send out the same one via syslog-protocol. + */ +static void tryEmulateAPPNAME(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME != NULL) + return; /* we are already done */ + + if(getProtocolVersion(pM) == 0) { + /* only then it makes sense to emulate */ + MsgSetAPPNAME(pM, getProgramName(pM)); + } +} + + +/* rgerhards, 2005-11-24 + */ +static int getAPPNAMELen(msg_t *pM) +{ + assert(pM != NULL); + if(pM->pCSAPPNAME == NULL) + tryEmulateAPPNAME(pM); + return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +} + + +/* rgerhards 2004-11-16: set pszRcvFrom in msg object + */ +void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom) +{ + assert(pMsg != NULL); + if(pMsg->pszRcvFrom != NULL) + free(pMsg->pszRcvFrom); + + pMsg->iLenRcvFrom = strlen(pszRcvFrom); + if((pMsg->pszRcvFrom = malloc(pMsg->iLenRcvFrom + 1)) != NULL) { + memcpy(pMsg->pszRcvFrom, pszRcvFrom, pMsg->iLenRcvFrom + 1); + } +} + + +/* Set the HOSTNAME to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetHOSTNAME(). + * rgerhards 2004-11-19 + */ +void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf) +{ + assert(pMsg != NULL); + assert(pBuf != NULL); + pMsg->iLenHOSTNAME = strlen(pBuf); + pMsg->pszHOSTNAME = (uchar*) pBuf; +} + + +/* rgerhards 2004-11-09: set HOSTNAME in msg object + * rgerhards, 2007-06-21: + * Does not return anything. If an error occurs, the hostname is + * simply not set. I have changed this behaviour. The only problem + * we can run into is memory shortage. If we have such, it is better + * to loose the hostname than the full message. So we silently ignore + * that problem and hope that memory will be available the next time + * we need it. The rest of the code already knows how to handle an + * unset HOSTNAME. + */ +void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME) +{ + assert(pMsg != NULL); + if(pMsg->pszHOSTNAME != NULL) + free(pMsg->pszHOSTNAME); + + pMsg->iLenHOSTNAME = strlen(pszHOSTNAME); + if((pMsg->pszHOSTNAME = malloc(pMsg->iLenHOSTNAME + 1)) != NULL) + memcpy(pMsg->pszHOSTNAME, pszHOSTNAME, pMsg->iLenHOSTNAME + 1); + else + dbgprintf("Could not allocate memory in MsgSetHOSTNAME()\n"); +} + + +/* Set the UxTradMsg to a caller-provided string. This is thought + * to be a heap buffer that the caller will no longer use. This + * function is a performance optimization over MsgSetUxTradMsg(). + * rgerhards 2004-11-19 + */ +#if 0 /* This method is currently not called, be we like to preserve it */ +static void MsgAssignUxTradMsg(msg_t *pMsg, char *pBuf) +{ + assert(pMsg != NULL); + assert(pBuf != NULL); + pMsg->iLenUxTradMsg = strlen(pBuf); + pMsg->pszUxTradMsg = pBuf; +} +#endif + + +/* rgerhards 2004-11-17: set the traditional Unix message in msg object + */ +int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg) +{ + assert(pMsg != NULL); + assert(pszUxTradMsg != NULL); + pMsg->iLenUxTradMsg = strlen(pszUxTradMsg); + if(pMsg->pszUxTradMsg != NULL) + free(pMsg->pszUxTradMsg); + if((pMsg->pszUxTradMsg = malloc(pMsg->iLenUxTradMsg + 1)) != NULL) + memcpy(pMsg->pszUxTradMsg, pszUxTradMsg, pMsg->iLenUxTradMsg + 1); + else + dbgprintf("Could not allocate memory for pszUxTradMsg buffer."); + + return(0); +} + + +/* rgerhards 2004-11-09: set MSG in msg object + */ +void MsgSetMSG(msg_t *pMsg, char* pszMSG) +{ + assert(pMsg != NULL); + assert(pszMSG != NULL); + + if(pMsg->pszMSG != NULL) + free(pMsg->pszMSG); + + pMsg->iLenMSG = strlen(pszMSG); + if((pMsg->pszMSG = (uchar*) malloc(pMsg->iLenMSG + 1)) != NULL) + memcpy(pMsg->pszMSG, pszMSG, pMsg->iLenMSG + 1); + else + dbgprintf("MsgSetMSG could not allocate memory for pszMSG buffer."); +} + +/* rgerhards 2004-11-11: set RawMsg in msg object + */ +void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg) +{ + assert(pMsg != NULL); + if(pMsg->pszRawMsg != NULL) + free(pMsg->pszRawMsg); + + pMsg->iLenRawMsg = strlen(pszRawMsg); + if((pMsg->pszRawMsg = (uchar*) malloc(pMsg->iLenRawMsg + 1)) != NULL) + memcpy(pMsg->pszRawMsg, pszRawMsg, pMsg->iLenRawMsg + 1); + else + dbgprintf("Could not allocate memory for pszRawMsg buffer."); +} + + +/* Decode a priority into textual information like auth.emerg. + * The variable pRes must point to a user-supplied buffer and + * pResLen must contain its size. The pointer to the buffer + * is also returned, what makes this functiona suitable for + * use in printf-like functions. + * Note: a buffer size of 20 characters is always sufficient. + * Interface to this function changed 2007-06-15 by RGerhards + */ +char *textpri(char *pRes, size_t pResLen, int pri) +{ + syslogCODE *c_pri, *c_fac; + + assert(pRes != NULL); + assert(pResLen > 0); + + for (c_fac = rs_facilitynames; c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri)<<3); c_fac++); + for (c_pri = rs_prioritynames; c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++); + + snprintf (pRes, pResLen, "%s.%s<%d>", c_fac->c_name, c_pri->c_name, pri); + + return pRes; +} + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_HHOUR, NOW_QHOUR, NOW_MINUTE } eNOWType; +#define tmpBUFSIZE 16 /* size of formatting buffer */ +static uchar *getNOW(eNOWType eNow) +{ + uchar *pBuf; + struct syslogTime t; + + if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { + glblHadMemShortage = 1; + return NULL; + } + + datetime.getCurrTime(&t); + switch(eNow) { + case NOW_NOW: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d", t.year); + break; + case NOW_MONTH: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.month); + break; + case NOW_DAY: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.day); + break; + case NOW_HOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.hour); + break; + case NOW_HHOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 30); + break; + case NOW_QHOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute / 15); + break; + case NOW_MINUTE: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute); + break; + } + + return(pBuf); +} +#undef tmpBUFSIZE /* clean up */ + + +/* This function returns a string-representation of the + * requested message property. This is a generic function used + * to abstract properties so that these can be easier + * queried. Returns NULL if property could not be found. + * Actually, this function is a big if..elseif. What it does + * is simply to map property names (from MonitorWare) to the + * message object data fields. + * + * In case we need string forms of propertis we do not + * yet have in string form, we do a memory allocation that + * is sufficiently large (in all cases). Once the string + * form has been obtained, it is saved until the Msg object + * is finally destroyed. This is so that we save the processing + * time in the (likely) case that this property is requested + * again. It also saves us a lot of dynamic memory management + * issues in the upper layers, because we so can guarantee that + * the buffer will remain static AND available during the lifetime + * of the object. Please note that both the max size allocation as + * well as keeping things in memory might like look like a + * waste of memory (some might say it actually is...) - we + * deliberately accept this because performance is more important + * to us ;) + * rgerhards 2004-11-18 + * Parameter "bMustBeFreed" is set by this function. It tells the + * caller whether or not the string returned must be freed by the + * caller itself. It is is 0, the caller MUST NOT free it. If it is + * 1, the caller MUST free 1. Handling this wrongly leads to either + * a memory leak of a program abort (do to double-frees or frees on + * the constant memory pool). So be careful to do it right. + * rgerhards 2004-11-23 + * regular expression support contributed by Andres Riancho merged + * on 2005-09-13 + * changed so that it now an be called without a template entry (NULL). + * In this case, only the (unmodified) property is returned. This will + * be used in selector line processing. + * rgerhards 2005-09-15 + */ +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + cstr_t *pCSPropName, unsigned short *pbMustBeFreed) +{ + uchar *pName; + char *pRes; /* result pointer */ + char *pBufStart; + char *pBuf; + int iLen; + +#ifdef FEATURE_REGEXP + /* Variables necessary for regular expression matching */ + size_t nmatch = 1; + regmatch_t pmatch[1]; +#endif + + assert(pMsg != NULL); + assert(pbMustBeFreed != NULL); + + if(pCSPropName == NULL) { + assert(pTpe != NULL); + pName = pTpe->data.field.pPropRepl; + } else { + pName = rsCStrGetSzStrNoNULL(pCSPropName); + } + *pbMustBeFreed = 0; + + /* sometimes there are aliases to the original MonitoWare + * property names. These come after || in the ifs below. */ + if(!strcmp((char*) pName, "msg")) { + pRes = getMSG(pMsg); + } else if(!strcmp((char*) pName, "rawmsg")) { + pRes = getRawMsg(pMsg); + } else if(!strcmp((char*) pName, "uxtradmsg")) { + pRes = getUxTradMsg(pMsg); + } else if(!strcmp((char*) pName, "fromhost")) { + pRes = getRcvFrom(pMsg); + } else if(!strcmp((char*) pName, "source") || !strcmp((char*) pName, "hostname")) { + pRes = getHOSTNAME(pMsg); + } else if(!strcmp((char*) pName, "syslogtag")) { + pRes = getTAG(pMsg); + } else if(!strcmp((char*) pName, "pri")) { + pRes = getPRI(pMsg); + } else if(!strcmp((char*) pName, "pri-text")) { + pBuf = malloc(20 * sizeof(char)); + if(pBuf == NULL) { + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } else { + *pbMustBeFreed = 1; + pRes = textpri(pBuf, 20, getPRIi(pMsg)); + } + } else if(!strcmp((char*) pName, "iut")) { + pRes = "1"; /* always 1 for syslog messages (a MonitorWare thing;)) */ + } else if(!strcmp((char*) pName, "syslogfacility")) { + pRes = getFacility(pMsg); + } else if(!strcmp((char*) pName, "syslogfacility-text")) { + pRes = getFacilityStr(pMsg); + } else if(!strcmp((char*) pName, "syslogseverity") || !strcmp((char*) pName, "syslogpriority")) { + pRes = getSeverity(pMsg); + } else if(!strcmp((char*) pName, "syslogseverity-text") || !strcmp((char*) pName, "syslogpriority-text")) { + pRes = getSeverityStr(pMsg); + } else if(!strcmp((char*) pName, "timegenerated")) { + pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp((char*) pName, "timereported") + || !strcmp((char*) pName, "timestamp")) { + pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp((char*) pName, "programname")) { + pRes = getProgramName(pMsg); + } else if(!strcmp((char*) pName, "protocol-version")) { + pRes = getProtocolVersionString(pMsg); + } else if(!strcmp((char*) pName, "structured-data")) { + pRes = getStructuredData(pMsg); + } else if(!strcmp((char*) pName, "app-name")) { + pRes = getAPPNAME(pMsg); + } else if(!strcmp((char*) pName, "procid")) { + pRes = getPROCID(pMsg); + } else if(!strcmp((char*) pName, "msgid")) { + pRes = getMSGID(pMsg); + /* here start system properties (those, that do not relate to the message itself */ + } else if(!strcmp((char*) pName, "$now")) { + if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$year")) { + if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$month")) { + if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$day")) { + if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$hour")) { + if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$hhour")) { + if((pRes = (char*) getNOW(NOW_HHOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$qhour")) { + if((pRes = (char*) getNOW(NOW_QHOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp((char*) pName, "$minute")) { + if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else { + /* there is no point in continuing, we may even otherwise render the + * error message unreadable. rgerhards, 2007-07-10 + */ + dbgprintf("invalid property name: '%s'\n", pName); + return "**INVALID PROPERTY NAME**"; + } + + /* If we did not receive a template pointer, we are already done... */ + if(pTpe == NULL) { + return pRes; + } + + /* Now check if we need to make "temporary" transformations (these + * are transformations that do not go back into the message - + * memory must be allocated for them!). + */ + + /* substring extraction */ + /* first we check if we need to extract by field number + * rgerhards, 2005-12-22 + */ + if(pTpe->data.field.has_fields == 1) { + size_t iCurrFld; + char *pFld; + char *pFldEnd; + /* first, skip to the field in question. The field separator + * is always one character and is stored in the template entry. + */ + iCurrFld = 1; + pFld = pRes; + while(*pFld && iCurrFld < pTpe->data.field.iToPos) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) + ++pFld; /* skip to field terminator */ + if(*pFld == pTpe->data.field.field_delim) { + ++pFld; /* eat it */ + ++iCurrFld; + } + } + dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, (int) iCurrFld); + + if(iCurrFld == pTpe->data.field.iToPos) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + /* TODO: code copied from below, this is a candidate for a separate function */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + /* now copy */ + memcpy(pBuf, pFld, iLen); + pBuf[iLen] = '\0'; /* terminate it */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + if(*(pFldEnd+1) != '\0') + ++pFldEnd; /* OK, skip again over delimiter char */ + } else { + /* field not found, return error */ + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**FIELD NOT FOUND**"; + } + } else if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + /* we need to obtain a private copy */ + int iFrom, iTo; + char *pSb; + iFrom = pTpe->data.field.iFromPos; + iTo = pTpe->data.field.iToPos; + /* need to zero-base to and from (they are 1-based!) */ + if(iFrom > 0) + --iFrom; + if(iTo > 0) + --iTo; + iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pSb = pRes; + if(iFrom) { + /* skip to the start of the substring (can't do pointer arithmetic + * because the whole string might be smaller!!) + */ + while(*pSb && iFrom) { + --iFrom; + ++pSb; + } + } + /* OK, we are at the begin - now let's copy... */ + while(*pSb && iLen) { + *pBuf++ = *pSb; + ++pSb; + --iLen; + } + *pBuf = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; +#ifdef FEATURE_REGEXP + } else { + /* Check for regular expressions */ + if (pTpe->data.field.has_regex != 0) { + if (pTpe->data.field.has_regex == 2) + /* Could not compile regex before! */ + return "**NO MATCH** **BAD REGULAR EXPRESSION**"; + + dbgprintf("debug: String to match for regex is: %s\n", pRes); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + if (0 != regexp.regexec(&pTpe->data.field.re, pRes, nmatch, pmatch, 0)) { + /* we got no match! */ + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + return "**NO MATCH**"; + } else { + /* Match! */ + /* I need to malloc pB */ + int iLenBuf; + char *pB; + + iLenBuf = pmatch[0].rm_eo - pmatch[0].rm_so; + pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY ALLOCATING pBuf**"; + } + + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + pmatch[0].rm_so, iLenBuf); + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ + + if (*pbMustBeFreed == 1) + free(pRes); + pRes = pB; + *pbMustBeFreed = 1; + } + } else { + /* we could not load regular expression support. This is quite unexpected at + * this stage of processing (after all, the config parser found it), but so + * it is. We return an error in that case. -- rgerhards, 2008-03-07 + */ + dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n"); + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + return "***REGEXP NOT AVAILABLE***"; + } + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + if(*pRes) { + /* case conversations (should go after substring, because so we are able to + * work on the smallest possible buffer). + */ + if(pTpe->data.field.eCaseConv != tplCaseConvNo) { + /* we need to obtain a private copy */ + int iBufLen = strlen(pRes); + char *pBStart; + char *pB; + char *pSrc; + pBStart = pB = malloc((iBufLen + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + pSrc = pRes; + while(*pSrc) { + *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? + (char)toupper((int)*pSrc) : (char)tolower((int)*pSrc); + /* currently only these two exist */ + ++pSrc; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + + /* now do control character dropping/escaping/replacement + * Only one of these can be used. If multiple options are given, the + * result is random (though currently there obviously is an order of + * preferrence, see code below. But this is NOT guaranteed. + * RGerhards, 2006-11-17 + * We must copy the strings if we modify them, because they may either + * point to static memory or may point into the message object, in which + * case we would actually modify the original property (which of course + * is wrong). + * This was found and fixed by varmojefkoj on 2007-09-11 + */ + if(pTpe->data.field.options.bDropCC) { + int iLenBuf = 0; + char *pSrc = pRes; + char *pDstStart; + char *pDst; + char bDropped = 0; + + while(*pSrc) { + if(!iscntrl((int) *pSrc++)) + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(!iscntrl((int) *pSrc)) + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bSpaceCC) { + char *pSrc; + char *pDstStart; + char *pDst; + + if(*pbMustBeFreed == 1) { + /* in this case, we already work on dynamic + * memory, so there is no need to copy it - we can + * modify it in-place without any harm. This is a + * performance optiomization. + */ + for(pDst = pRes; *pDst; pDst++) { + if(iscntrl((int) *pDst)) + *pDst = ' '; + } + } else { + pDst = pDstStart = malloc(strlen(pRes) + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(iscntrl((int) *pSrc)) + *pDst++ = ' '; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bEscapeCC) { + /* we must first count how many control charactes are + * present, because we need this to compute the new string + * buffer length. While doing so, we also compute the string + * length. + */ + int iNumCC = 0; + int iLenBuf = 0; + char *pB; + + for(pB = pRes ; *pB ; ++pB) { + ++iLenBuf; + if(iscntrl((int) *pB)) + ++iNumCC; + } + + if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ + /* OK, let's do the escaping... */ + char *pBStart; + char szCCEsc[8]; /* buffer for escape sequence */ + int i; + + iLenBuf += iNumCC * 4; + pBStart = pB = malloc((iLenBuf + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + while(*pRes) { + if(iscntrl((int) *pRes)) { + snprintf(szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); + for(i = 0 ; i < 4 ; ++i) + *pB++ = szCCEsc[i]; + } else { + *pB++ = *pRes; + } + ++pRes; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + } + } + + /* Take care of spurious characters to make the property safe + * for a path definition + */ + if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) { + if(pTpe->data.field.options.bSecPathDrop) { + int iLenBuf = 0; + char *pSrc = pRes; + char *pDstStart; + char *pDst; + char bDropped = 0; + + while(*pSrc) { + if(*pSrc++ != '/') + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc != '/') + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else { + char *pSrc; + char *pDstStart; + char *pDst; + + if(*pbMustBeFreed == 1) { + /* here, again, we can modify the string as we already obtained + * a private buffer. As we do not change the size of that buffer, + * in-place modification is possible. This is a performance + * enhancement. + */ + for(pDst = pRes; *pDst; pDst++) { + if(*pDst == '/') + *pDst++ = '_'; + } + } else { + pDst = pDstStart = malloc(strlen(pRes) + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc == '/') + *pDst++ = '_'; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + /* we must NOT check if it needs to be freed, because we have done + * this in the if above. So if we come to hear, the pSrc string needs + * not to be freed (and we do not need to care about it). + */ + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } + + /* check for "." and ".." (note the parenthesis in the if condition!) */ + if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) { + char *pTmp = pRes; + + if(*(pRes + 1) == '\0') + pRes = "_"; + else + pRes = "_.";; + if(*pbMustBeFreed == 1) + free(pTmp); + *pbMustBeFreed = 0; + } else if(*pRes == '\0') { + if(*pbMustBeFreed == 1) + free(pRes); + pRes = "_"; + *pbMustBeFreed = 0; + } + } + + /* Now drop last LF if present (pls note that this must not be done + * if bEscapeCC was set! + */ + if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { + int iLn = strlen(pRes); + char *pB; + if(iLn > 0 && *(pRes + iLn - 1) == '\n') { + /* we have a LF! */ + /* check if we need to obtain a private copy */ + if(*pbMustBeFreed == 0) { + /* ok, original copy, need a private one */ + pB = malloc((iLn + 1) * sizeof(char)); + if(pB == NULL) { + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + memcpy(pB, pRes, iLn - 1); + pRes = pB; + *pbMustBeFreed = 1; + } + *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ + } + } + + /*dbgprintf("MsgGetProp(\"%s\"): \"%s\"\n", pName, pRes); only for verbose debug logging */ + return(pRes); +} + + +/* The returns a message variable suitable for use with RainerScript. Most importantly, this means + * that the value is returned in a var_t object. The var_t is constructed inside this function and + * MUST be freed by the caller. + * rgerhards, 2008-02-25 + */ +rsRetVal +msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar) +{ + DEFiRet; + var_t *pVar; + uchar *pszProp = NULL; + cstr_t *pstrProp; + unsigned short bMustBeFreed = 0; + + ISOBJ_TYPE_assert(pThis, msg); + ASSERT(pstrPropName != NULL); + ASSERT(ppVar != NULL); + + /* make sure we have a var_t instance */ + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + /* always call MsgGetProp() without a template specifier */ + pszProp = (uchar*) MsgGetProp(pThis, NULL, pstrPropName, &bMustBeFreed); + + /* now create a string object out of it and hand that over to the var */ + CHKiRet(rsCStrConstructFromszStr(&pstrProp, pszProp)); + CHKiRet(var.SetString(pVar, pstrProp)); + + /* finally store var */ + *ppVar = pVar; + +finalize_it: + if(bMustBeFreed) + free(pszProp); + + RETiRet; +} + + +/* This function can be used as a generic way to set properties. + * We have to handle a lot of legacy, so our return value is not always + * 100% correct (called functions do not always provide one, should + * change over time). + * rgerhards, 2008-01-07 + */ +#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal MsgSetProperty(msg_t *pThis, var_t *pProp) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, msg); + assert(pProp != NULL); + + if(isProp("iProtocolVersion")) { + setProtocolVersion(pThis, pProp->val.num); + } else if(isProp("iSeverity")) { + pThis->iSeverity = pProp->val.num; + } else if(isProp("iFacility")) { + pThis->iFacility = pProp->val.num; + } else if(isProp("msgFlags")) { + pThis->msgFlags = pProp->val.num; + } else if(isProp("pszRawMsg")) { + MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszMSG")) { + MsgSetMSG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszUxTradMsg")) { + MsgSetUxTradMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszTAG")) { + MsgSetTAG(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszRcvFrom")) { + MsgSetHOSTNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pszHOSTNAME")) { + MsgSetRcvFrom(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSStrucData")) { + MsgSetStructuredData(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSAPPNAME")) { + MsgSetAPPNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSPROCID")) { + MsgSetPROCID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("pCSMSGID")) { + MsgSetMSGID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr)); + } else if(isProp("tRcvdAt")) { + memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } else if(isProp("tTIMESTAMP")) { + memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime)); + } + + RETiRet; +} +#undef isProp + + +/* This is a construction finalizer that must be called after all properties + * have been set. It does some final work on the message object. After this + * is done, the object is considered ready for full processing. + * rgerhards, 2008-07-08 + */ +static rsRetVal msgConstructFinalizer(msg_t *pThis) +{ + MsgPrepareEnqueue(pThis); + return RS_RET_OK; +} + + +/* get the severity - this is an entry point that + * satisfies the base object class getSeverity semantics. + * rgerhards, 2008-01-14 + */ +static rsRetVal +MsgGetSeverity(obj_t *pThis, int *piSeverity) +{ + ISOBJ_TYPE_assert(pThis, msg); + assert(piSeverity != NULL); + *piSeverity = ((msg_t*) pThis)->iSeverity; + return RS_RET_OK; +} + + +/* dummy */ +rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the message class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-04 + */ +BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE) + /* request objects we use */ + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); + OBJSetMethodHandler(objMethod_SETPROPERTY, MsgSetProperty); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, msgConstructFinalizer); + OBJSetMethodHandler(objMethod_GETSEVERITY, MsgGetSeverity); + /* initially, we have no need to lock message objects */ + funcLock = MsgLockingDummy; + funcUnlock = MsgLockingDummy; + funcDeleteMutex = MsgLockingDummy; + funcMsgPrepareEnqueue = MsgLockingDummy; +ENDObjClassInit(msg) + +/* + * vi:set ai: + */ diff --git a/runtime/msg.h b/runtime/msg.h new file mode 100644 index 00000000..56ce56bb --- /dev/null +++ b/runtime/msg.h @@ -0,0 +1,178 @@ +/* msg.h + * Header file for all msg-related functions. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "template.h" /* this is a quirk, but these two are too interdependant... */ + +#ifndef MSG_H_INCLUDED +#define MSG_H_INCLUDED 1 + +#include +#include "obj.h" +#include "syslogd-types.h" +#include "template.h" + +/* rgerhards 2004-11-08: The following structure represents a + * syslog message. + * + * Important Note: + * The message object is used for multiple purposes (once it + * has been created). Once created, it actully is a read-only + * object (though we do not specifically express this). In order + * to avoid multiple copies of the same object, we use a + * reference counter. This counter is set to 1 by the constructer + * and increased by 1 with a call to MsgAddRef(). The destructor + * checks the reference count. If it is more than 1, only the counter + * will be decremented. If it is 1, however, the object is actually + * destroyed. To make this work, it is vital that MsgAddRef() is + * called each time a "copy" is stored somewhere. + */ +struct msg { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + pthread_mutexattr_t mutAttr; + pthread_mutex_t mut; + int iRefCount; /* reference counter (0 = unused) */ + short bParseHOSTNAME; /* should the hostname be parsed from the message? */ + /* background: the hostname is not present on "regular" messages + * received via UNIX domain sockets from the same machine. However, + * it is available when we have a forwarder (e.g. rfc3195d) using local + * sockets. All in all, the parser would need parse templates, that would + * resolve all these issues... rgerhards, 2005-10-06 + */ + flowControl_t flowCtlType; /**< type of flow control we can apply, for enqueueing, needs not to be persisted because + once data has entered the queue, this property is no longer needed. */ + short iSeverity; /* the severity 0..7 */ + uchar *pszSeverity; /* severity as string... */ + int iLenSeverity; /* ... and its length. */ + uchar *pszSeverityStr; /* severity name... */ + int iLenSeverityStr; /* ... and its length. */ + short iFacility; /* Facility code 0 .. 23*/ + uchar *pszFacility; /* Facility as string... */ + int iLenFacility; /* ... and its length. */ + uchar *pszFacilityStr; /* facility name... */ + int iLenFacilityStr; /* ... and its length. */ + uchar *pszPRI; /* the PRI as a string */ + int iLenPRI; /* and its length */ + uchar *pszRawMsg; /* message as it was received on the + * wire. This is important in case we + * need to preserve cryptographic verifiers. + */ + int iLenRawMsg; /* length of raw message */ + uchar *pszMSG; /* the MSG part itself */ + int iLenMSG; /* Length of the MSG part */ + uchar *pszUxTradMsg; /* the traditional UNIX message */ + int iLenUxTradMsg;/* Length of the traditional UNIX message */ + uchar *pszTAG; /* pointer to tag value */ + int iLenTAG; /* Length of the TAG part */ + uchar *pszHOSTNAME; /* HOSTNAME from syslog message */ + int iLenHOSTNAME; /* Length of HOSTNAME */ + uchar *pszRcvFrom; /* System message was received from */ + int iLenRcvFrom; /* Length of pszRcvFrom */ + short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */ + cstr_t *pCSProgName; /* the (BSD) program name */ + cstr_t *pCSStrucData;/* STRUCTURED-DATA */ + cstr_t *pCSAPPNAME; /* APP-NAME */ + cstr_t *pCSPROCID; /* PROCID */ + cstr_t *pCSMSGID; /* MSGID */ + struct syslogTime tRcvdAt;/* time the message entered this program */ + char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */ + char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */ + char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 charcters) */ + char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */ + struct syslogTime tTIMESTAMP;/* (parsed) value of the timestamp */ + char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 charcters) */ + char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 charcters at most) */ + char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 charcters) */ + char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */ + int msgFlags; /* flags associated with this message */ +}; +typedef struct msg msg_t; /* new name */ + +/* function prototypes + */ +PROTOTYPEObjClassInit(msg); +char* getProgramName(msg_t*); +rsRetVal msgConstruct(msg_t **ppThis); +rsRetVal msgDestruct(msg_t **ppM); +msg_t* MsgDup(msg_t* pOld); +msg_t *MsgAddRef(msg_t *pM); +void setProtocolVersion(msg_t *pM, int iNewVersion); +int getProtocolVersion(msg_t *pM); +char *getProtocolVersionString(msg_t *pM); +int getMSGLen(msg_t *pM); +char *getRawMsg(msg_t *pM); +char *getUxTradMsg(msg_t *pM); +char *getMSG(msg_t *pM); +char *getPRI(msg_t *pM); +int getPRIi(msg_t *pM); +char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt); +char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt); +char *getSeverity(msg_t *pM); +char *getSeverityStr(msg_t *pM); +char *getFacility(msg_t *pM); +char *getFacilityStr(msg_t *pM); +rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME); +char *getAPPNAME(msg_t *pM); +rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID); +int getPROCIDLen(msg_t *pM); +char *getPROCID(msg_t *pM); +rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID); +void MsgAssignTAG(msg_t *pMsg, uchar *pBuf); +void MsgSetTAG(msg_t *pMsg, char* pszTAG); +rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl); +char *getTAG(msg_t *pM); +int getHOSTNAMELen(msg_t *pM); +char *getHOSTNAME(msg_t *pM); +char *getRcvFrom(msg_t *pM); +rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData); +char *getStructuredData(msg_t *pM); +int getProgramNameLen(msg_t *pM); +char *getProgramName(msg_t *pM); +void MsgSetRcvFrom(msg_t *pMsg, char* pszRcvFrom); +void MsgAssignHOSTNAME(msg_t *pMsg, char *pBuf); +void MsgSetHOSTNAME(msg_t *pMsg, char* pszHOSTNAME); +int MsgSetUxTradMsg(msg_t *pMsg, char* pszUxTradMsg); +void MsgSetMSG(msg_t *pMsg, char* pszMSG); +void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg); +void moveHOSTNAMEtoTAG(msg_t *pM); +char *getMSGID(msg_t *pM); +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + cstr_t *pCSPropName, unsigned short *pbMustBeFreed); +char *textpri(char *pRes, size_t pResLen, int pri); +rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar); +rsRetVal MsgEnableThreadSafety(void); + +/* The MsgPrepareEnqueue() function is a macro for performance reasons. + * It needs one global variable to work. This is acceptable, as it gains + * us quite some performance and is fully abstracted using this header file. + * The important thing is that no other module is permitted to actually + * access that global variable! -- rgerhards, 2008-01-05 + */ +extern void (*funcMsgPrepareEnqueue)(msg_t *pMsg); +#define MsgPrepareEnqueue(pMsg) funcMsgPrepareEnqueue(pMsg) + +#endif /* #ifndef MSG_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/runtime/obj-types.h b/runtime/obj-types.h new file mode 100644 index 00000000..901733c5 --- /dev/null +++ b/runtime/obj-types.h @@ -0,0 +1,406 @@ +/* Some type definitions and macros for the obj object. + * I needed to move them out of the main obj.h, because obj.h's + * prototypes use other data types. However, their .h's rely + * on some of the obj.h data types and macros. So I needed to break + * that loop somehow and I've done that by moving the typedefs + * into this file here. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJ_TYPES_H_INCLUDED +#define OBJ_TYPES_H_INCLUDED + +#include "stringbuf.h" +#include "syslogd-types.h" + +/* property types for obj[De]Serialize() */ +typedef enum { + PROPTYPE_NONE = 0, /* currently no value set */ + PROPTYPE_PSZ = 1, + PROPTYPE_SHORT = 2, + PROPTYPE_INT = 3, + PROPTYPE_LONG = 4, + PROPTYPE_INT64 = 5, + PROPTYPE_CSTR = 6, + PROPTYPE_SYSLOGTIME = 7 +} propType_t; + +typedef unsigned objID_t; + +typedef enum { /* IDs of base methods supported by all objects - used for jump table, so + * they must start at zero and be incremented. -- rgerhards, 2008-01-04 + */ + objMethod_CONSTRUCT = 0, + objMethod_DESTRUCT = 1, + objMethod_SERIALIZE = 2, + objMethod_DESERIALIZE = 3, + objMethod_SETPROPERTY = 4, + objMethod_CONSTRUCTION_FINALIZER = 5, + objMethod_GETSEVERITY = 6, + objMethod_DEBUGPRINT = 7 +} objMethod_t; +#define OBJ_NUM_METHODS 8 /* must be updated to contain the max number of methods supported */ + + +/* the base data type for interfaces + * This MUST be in sync with the ifBEGIN macro + */ +typedef struct interface_s { + int ifVersion; /* must be set to version requested */ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes, 2-load failed; if not 1, functions can NOT be called! */ +} interface_t; + + +typedef struct objInfo_s { + uchar *pszID; /* the object ID as a string */ + size_t lenID; /* length of the ID string */ + int iObjVers; + uchar *pszName; + rsRetVal (*objMethods[OBJ_NUM_METHODS])(); + rsRetVal (*QueryIF)(interface_t*); + struct modInfo_s *pModInfo; +} objInfo_t; + + +typedef struct obj { /* the dummy struct that each derived class can be casted to */ + objInfo_t *pObjInfo; +#ifndef NDEBUG /* this means if debug... */ + unsigned int iObjCooCKiE; /* must always be 0xBADEFEE for a valid object */ +#endif + uchar *pszName; /* the name of *this* specific object instance */ +} obj_t; + + +/* macros which must be gloablly-visible (because they are used during definition of + * other objects. + */ +#ifndef NDEBUG /* this means if debug... */ +#include +# define BEGINobjInstance \ + obj_t objData +# define ISOBJ_assert(pObj) \ + do { \ + ASSERT((pObj) != NULL); \ + ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + } while(0); +# define ISOBJ_TYPE_assert(pObj, objType) \ + do { \ + ASSERT(pObj != NULL); \ + ASSERT((unsigned) ((obj_t*) (pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \ + ASSERT(!strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)); \ + } while(0); +#else /* non-debug mode, no checks but much faster */ +# define BEGINobjInstance obj_t objData +# define ISOBJ_TYPE_assert(pObj, objType) +# define ISOBJ_assert(pObj) +#endif + +#define DEFpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType *pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType*) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define DEFpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMethFP(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType) +#define DEFpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\ + { \ + /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\ + pThis->prop = pVal; \ + return RS_RET_OK; \ + } +#define PROTOTYPEpropSetMeth(obj, prop, dataType)\ + rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal) +#define INTERFACEpropSetMeth(obj, prop, dataType)\ + rsRetVal (*Set##prop)(obj##_t *pThis, dataType) +/* class initializer */ +#define PROTOTYPEObjClassInit(objName) rsRetVal objName##ClassInit(struct modInfo_s*) +/* below: objName must be the object name (e.g. vm, strm, ...) and ISCORE must be + * 1 if the module is a statically linked core module and 0 if it is a + * dynamically loaded one. -- rgerhards, 2008-02-29 + */ +#define OBJ_IS_CORE_MODULE 1 /* This should better be renamed to something like "OBJ_IS_NOT_LIBHEAD" or so... ;) */ +#define OBJ_IS_LOADABLE_MODULE 0 +#define BEGINObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + (rsRetVal (*)(void*))objName##Construct,\ + (rsRetVal (*)(void*))objName##Destruct,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); \ + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + +/* ... and now the same for abstract classes. + * TODO: consolidate the two -- rgerhards, 2008-02-29 + */ +#define BEGINAbstractObjClassInit(objName, objVers, objType) \ +rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \ +{ \ + DEFiRet; \ + if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \ + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \ + } \ + CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \ + NULL,\ + NULL,\ + (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); + +#define ENDObjClassInit(objName) \ + iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \ +finalize_it: \ + RETiRet; \ +} + + +/* now come the class exit. This is to be called immediately before the class is + * unloaded (actual unload for plugins, program termination for core modules) + * gerhards, 2008-03-10 + */ +#define PROTOTYPEObjClassExit(objName) rsRetVal objName##ClassExit(void) +#define BEGINObjClassExit(objName, objType) \ +rsRetVal objName##ClassExit(void) \ +{ \ + DEFiRet; + +#define CODESTARTObjClassExit(objName) + +#define ENDObjClassExit(objName) \ + iRet = obj.UnregisterObj((uchar*)#objName); \ + RETiRet; \ +} + +/* this defines both the constructor and initializer + * rgerhards, 2008-01-10 + */ +#define BEGINobjConstruct(obj) \ + rsRetVal obj##Initialize(obj##_t __attribute__((unused)) *pThis) \ + { \ + DEFiRet; + +#define ENDobjConstruct(obj) \ + /* use finalize_it: before calling the macro (if you need it)! */ \ + RETiRet; \ + } \ + rsRetVal obj##Construct(obj##_t **ppThis) \ + { \ + DEFiRet; \ + obj##_t *pThis; \ + \ + ASSERT(ppThis != NULL); \ + \ + if((pThis = (obj##_t *)calloc(1, sizeof(obj##_t))) == NULL) { \ + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); \ + } \ + objConstructSetObjInfo(pThis); \ + \ + obj##Initialize(pThis); \ + \ + finalize_it: \ + OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + RETiRet; \ + } + + +/* this defines the destructor. The important point is that the base object + * destructor is called. The upper-level class shall destruct all of its + * properties, but not the instance itself. This is freed here by the + * framework (we need an intact pointer because we need to free the + * obj_t structures inside it). A pointer to the object pointer must be + * parse, because it is re-set to NULL (this, for example, is important in + * cancellation handlers). The object pointer is always named pThis. + * The object is always freed, even if there is some error while + * Cancellation is blocked during destructors, as this could have fatal + * side-effects. However, this also means the upper-level object should + * not perform any lenghty processing. + * IMPORTANT: if the upper level object requires some situations where the + * object shall not be destructed (e.g. via reference counting), then + * it shall set pThis to NULL, which prevents destruction of the + * object. + * processing. + * rgerhards, 2008-01-30 + */ +#define BEGINobjDestruct(OBJ) \ + rsRetVal OBJ##Destruct(OBJ##_t **ppThis) \ + { \ + DEFiRet; \ + int iCancelStateSave; \ + OBJ##_t *pThis; + +#define CODESTARTobjDestruct(OBJ) \ + ASSERT(ppThis != NULL); \ + pThis = *ppThis; \ + ISOBJ_TYPE_assert(pThis, OBJ); \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); + +#define ENDobjDestruct(OBJ) \ + goto finalize_it; /* prevent compiler warning ;) */ \ + /* no more code here! */ \ + finalize_it: \ + if(pThis != NULL) { \ + obj.DestructObjSelf((obj_t*) pThis); \ + free(pThis); \ + *ppThis = NULL; \ + } \ + pthread_setcancelstate(iCancelStateSave, NULL); \ + RETiRet; \ + } + + +/* this defines the debug print entry point. DebugPrint is optional. If + * it is provided, the object should output some meaningful information + * via the debug system. + * rgerhards, 2008-02-20 + */ +#define PROTOTYPEObjDebugPrint(obj) rsRetVal obj##DebugPrint(obj##_t *pThis) +#define INTERFACEObjDebugPrint(obj) rsRetVal (*DebugPrint)(obj##_t *pThis) +#define BEGINobjDebugPrint(obj) \ + rsRetVal obj##DebugPrint(obj##_t *pThis) \ + { \ + DEFiRet; \ + +#define CODESTARTobjDebugPrint(obj) \ + ASSERT(pThis != NULL); \ + ISOBJ_TYPE_assert(pThis, obj); \ + +#define ENDobjDebugPrint(obj) \ + RETiRet; \ + } + +/* ------------------------------ object loader system ------------------------------ * + * The following code is the early beginning of a dynamic object loader system. The + * root idea is that all objects will become dynamically loadable libraries over time, + * which is necessary to get a clean plug-in interface where every plugin can access + * rsyslog's rich object model via simple and quite portable methods. + * + * To do so, each object defines one or more interfaces. They are essentially structures + * with function (method) pointers. Anyone interested in calling an object must first + * obtain the interface and can then call through it. + * + * The interface data type must always be called _if_t, as this is expected + * by the macros. Having consitent naming is also easier for the programmer. By default, + * macros create a static variable named like the object in each calling objects + * static data block. + * + * To facilitate moving to this system, I begin to implement some hooks, which + * allows to use interfaces today (when the rest of the infrastructure is not yet + * there). This is in the hope that it will ease migration to the full-fledged system + * once we are ready to work on that. + * rgerhards, 2008-02-21 + */ + +/* this defines the QueryInterface print entry point. Over time, it should be + * present in all objects. + */ +//#define PROTOTYPEObjQueryInterface(obj) rsRetVal obj##QueryInterface(obj##_if_t *pThis) +#define BEGINobjQueryInterface(obj) \ + rsRetVal obj##QueryInterface(obj##_if_t *pIf) \ + { \ + DEFiRet; \ + +#define CODESTARTobjQueryInterface(obj) \ + ASSERT(pIf != NULL); + +#define ENDobjQueryInterface(obj) \ + RETiRet; \ + } + + +/* the following macros should be used to define interfaces inside the + * header files. + */ +#define BEGINinterface(obj) \ + typedef struct obj##_if_s {\ + ifBEGIN; /* This MUST always be the first interface member */ +#define ENDinterface(obj) \ + } obj##_if_t; + +/* the following macro is used to get access to an object (not an instance, + * just the class itself!). It must be called before any of the object's + * methods can be accessed. The MYLIB part is the name of my library, or NULL if + * the caller is a core module. Using the right value here is important to get + * the reference counting correct (object accesses from the same library must + * not be counted because that would cause a library plugin to never unload, as + * its ClassExit() entry points are only called if no object is referenced, which + * would never happen as the library references itself. + * rgerhards, 2008-03-11 + */ +#define CORE_COMPONENT NULL /* use this to indicate this is a core component */ +#define DONT_LOAD_LIB NULL /* do not load a library to obtain object interface (currently same as CORE_COMPONENT) */ +/*#define objUse(objName, MYLIB, FILENAME) \ + obj.UseObj(__FILE__, (uchar*)#objName, MYLIB, (uchar*)FILENAME, (void*) &objName) +*/ +#define objUse(objName, FILENAME) \ + obj.UseObj(__FILE__, (uchar*)#objName, (uchar*)FILENAME, (void*) &objName) +#define objRelease(objName, FILENAME) \ + obj.ReleaseObj(__FILE__, (uchar*)#objName, (uchar*) FILENAME, (void*) &objName) + +/* defines data that must always be present at the very begin of the interface structure */ +#define ifBEGIN \ + int ifVersion; /* must be set to version requested */ \ + int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes; if no, functions can NOT be called! */ + + +/* use the following define some place in your static data (suggested right at + * the beginning + */ +#define DEFobjCurrIf(obj) \ + static obj##_if_t obj = { .ifVersion = obj##CURR_IF_VERSION, .ifIsLoaded = 0 }; + +/* define the prototypes for a class - when we use interfaces, we just have few + * functions that actually need to be non-static. + */ +#define PROTOTYPEObj(obj) \ + PROTOTYPEObjClassInit(obj); \ + PROTOTYPEObjClassExit(obj); + +/* ------------------------------ end object loader system ------------------------------ */ + + +#include "modules.h" +#endif /* #ifndef OBJ_TYPES_H_INCLUDED */ diff --git a/runtime/obj.c b/runtime/obj.c new file mode 100644 index 00000000..8f2f99e3 --- /dev/null +++ b/runtime/obj.c @@ -0,0 +1,1336 @@ +/* obj.c + * + * This file implements a generic object "class". All other classes can + * use the service of this base class here to include auto-destruction and + * other capabilities in a generic manner. + * + * As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable + * objects. In essence, each object will soon be available via its interface, + * only. Before any object's code is accessed (including global static methods), + * the caller needs to obtain an object interface. To do so, it needs to provide + * the object name and the file where the object is expected to reside in. A + * file may not be given, in which case the object is expected to reside in + * the rsyslog core. The caller than receives an interface pointer which can + * be utilized to access all the object's methods. This method enables rsyslog + * to load library modules on demand. In order to keep overhead low, callers + * should request object interface only once in the object Init function and + * free them when they exit. The only exception is when a caller needs to + * access an object only conditional, in which case a pointer to its interface + * shall be aquired as need first arises but still be released only on exit + * or when there definitely is no further need. The whole idea is to limit + * the very performance-intense act of dynamically loading an objects library. + * Of course, it is possible to violate this suggestion, but than you should + * have very good reasoning to do so. + * + * Please note that there is one trick we need to do. Each object queries + * the object interfaces and it does so via objUse(). objUse, however, is + * part of the obj object's interface (implemented via the file you are + * just reading). So in order to obtain a pointer to objUse, we need to + * call it - obviously not possible. One solution would be that objUse is + * hardcoded into all callers. That, however, would bring us into slight + * trouble with actually dynamically loaded modules, as we should NOT + * rely on the OS loader to resolve symbols back to the caller (this + * is a feature not universally available and highly importable). Of course, + * we can solve this with a pHostQueryEtryPoint() call. It still sounds + * somewhat unnatural to call a regular interface function via a special + * method. So what we do instead is define a special function called + * objGetObjInterface() which delivers our own interface. That function + * than will be defined global and be queriable via pHostQueryEtryPoint(). + * I agree, technically this is much the same, but from an architecture + * point of view it looks cleaner (at least to me). + * + * Please note that there is another egg-hen problem: we use a linked list, + * which is provided by the linkedList object. However, we need to + * initialize the linked list before we can provide the UseObj() + * functionality. That, in turn, would probably be required by the + * linkedList object. So the solution is to use a backdoor just to + * init the linked list and from then on use the usual interfaces. + * + * File begun on 2008-01-04 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include +#include + +/* how many objects are supported by rsyslogd? */ +#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */ + +#include "rsyslog.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "obj.h" +#include "stream.h" +#include "modules.h" +#include "errmsg.h" +#include "cfsysline.h" + +/* static data */ +DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */ +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */ + + +/* cookies for serialized lines */ +#define COOKIE_OBJLINE '<' +#define COOKIE_PROPLINE '+' +#define COOKIE_ENDLINE '>' +#define COOKIE_BLANKLINE '.' + +/* forward definitions */ +static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo); + +/* methods */ + +/* This is a dummy method to be used when a standard method has not been + * implemented by an object. Having it allows us to simply call via the + * jump table without any NULL pointer checks - which gains quite + * some performance. -- rgerhards, 2008-01-04 + */ +static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis) +{ + return RS_RET_NOT_IMPLEMENTED; +} + +/* and now the macro to check if something is not implemented + * must be provided an objInfo_t pointer. + */ +#define objInfoIsImplemented(pThis, method) \ + (pThis->objMethods[method] != objInfoNotImplementedDummy) + +/* construct an object Info object. Each class shall do this on init. The + * resulting object shall be cached during the lifetime of the class and each + * object shall receive a reference. A constructor and destructor MUST be provided for all + * objects, thus they are in the parameter list. + * pszID is the identifying object name and must point to constant pool memory. It is never freed. + */ +static rsRetVal +InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo) +{ + DEFiRet; + int i; + objInfo_t *pThis; + + assert(ppThis != NULL); + + if((pThis = calloc(1, sizeof(objInfo_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pThis->pszID = pszID; + pThis->lenID = strlen((char*)pszID); + pThis->pszName = (uchar*)strdup((char*)pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */ + pThis->iObjVers = iObjVers; + pThis->QueryIF = pQueryIF; + pThis->pModInfo = pModInfo; + + pThis->objMethods[0] = pConstruct; + pThis->objMethods[1] = pDestruct; + for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) { + pThis->objMethods[i] = objInfoNotImplementedDummy; + } + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* destruct the objInfo object - must be done only when no more instances exist. + * rgerhards, 2008-03-10 + */ +static rsRetVal +InfoDestruct(objInfo_t **ppThis) +{ + DEFiRet; + objInfo_t *pThis; + + assert(ppThis != NULL); + pThis = *ppThis; + assert(pThis != NULL); + + if(pThis->pszName != NULL) + free(pThis->pszName); + free(pThis); + *ppThis = NULL; + + RETiRet; +} + + +/* set a method handler */ +static rsRetVal +InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)) +{ + assert(pThis != NULL); + assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS); + pThis->objMethods[objMethod] = pHandler; + + return RS_RET_OK; +} + +/* destruct the base object properties. + * rgerhards, 2008-01-29 + */ +static rsRetVal +DestructObjSelf(obj_t *pThis) +{ + DEFiRet; + + ISOBJ_assert(pThis); + if(pThis->pszName != NULL) { + free(pThis->pszName); + } + + RETiRet; +} + + +/* --------------- object serializiation / deserialization support --------------- */ + + +/* serialize the header of an object + * pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag) + */ +static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* object cookie and serializer version (so far always 1) */ + CHKiRet(strmWriteChar(pStrm, COOKIE_OBJLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '1')); + + /* object type, version and string length */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWrite(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)); + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteLong(pStrm, objGetVersion(pObj))); + + /* record trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object + * rgerhards, 2008-01-06 + */ +static rsRetVal +BeginSerialize(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strmRecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj")); + +finalize_it: + RETiRet; +} + + +/* begin serialization of an object's property bag + * Note: a property bag is used to serialize some of an objects + * properties, but not necessarily all. A good example is the queue + * object, which at some stage needs to serialize a number of its + * properties, but not the queue data itself. From the object point + * of view, a property bag can not be used to re-instantiate an object. + * Otherwise, the serialization is exactly the same. + * rgerhards, 2008-01-11 + */ +static rsRetVal +BeginSerializePropBag(strm_t *pStrm, obj_t *pObj) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + ISOBJ_assert(pObj); + + CHKiRet(strmRecordBegin(pStrm)); + CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB")); + +finalize_it: + RETiRet; +} + + +/* append a property + */ +static rsRetVal +SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr) +{ + DEFiRet; + uchar *pszBuf = NULL; + size_t lenBuf = 0; + uchar szBuf[64]; + varType_t vType = VARTYPE_NONE; + + ISOBJ_TYPE_assert(pStrm, strm); + assert(pszPropName != NULL); + + /*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/ + /* if we have no user pointer, there is no need to write this property. + * TODO: think if that's the righ point of view + * rgerhards, 2008-01-06 + */ + if(pUsr == NULL) { + ABORT_FINALIZE(RS_RET_OK); + } + + /* TODO: use the stream functions for data conversion here - should be quicker */ + + switch(propType) { + case PROPTYPE_PSZ: + pszBuf = (uchar*) pUsr; + lenBuf = strlen((char*) pszBuf); + vType = VARTYPE_STR; + break; + case PROPTYPE_SHORT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_LONG: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_INT64: + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr))); + pszBuf = szBuf; + lenBuf = strlen((char*) szBuf); + vType = VARTYPE_NUMBER; + break; + case PROPTYPE_CSTR: + pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr); + lenBuf = rsCStrLen((cstr_t*) pUsr); + vType = VARTYPE_STR; + break; + case PROPTYPE_SYSLOGTIME: + lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d", + ((syslogTime_t*)pUsr)->timeType, + ((syslogTime_t*)pUsr)->year, + ((syslogTime_t*)pUsr)->month, + ((syslogTime_t*)pUsr)->day, + ((syslogTime_t*)pUsr)->hour, + ((syslogTime_t*)pUsr)->minute, + ((syslogTime_t*)pUsr)->second, + ((syslogTime_t*)pUsr)->secfrac, + ((syslogTime_t*)pUsr)->secfracPrecision, + ((syslogTime_t*)pUsr)->OffsetMode, + ((syslogTime_t*)pUsr)->OffsetHour, + ((syslogTime_t*)pUsr)->OffsetMinute); + if(lenBuf > sizeof(szBuf) - 1) + ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL); + vType = VARTYPE_SYSLOGTIME; + pszBuf = szBuf; + break; + default: + dbgprintf("invalid PROPTYPE %d\n", propType); + break; + } + + /* cookie */ + CHKiRet(strmWriteChar(pStrm, COOKIE_PROPLINE)); + /* name */ + CHKiRet(strmWrite(pStrm, pszPropName, strlen((char*)pszPropName))); + CHKiRet(strmWriteChar(pStrm, ':')); + /* type */ + CHKiRet(strmWriteLong(pStrm, (int) vType)); + CHKiRet(strmWriteChar(pStrm, ':')); + /* length */ + CHKiRet(strmWriteLong(pStrm, lenBuf)); + CHKiRet(strmWriteChar(pStrm, ':')); + + /* data */ + CHKiRet(strmWrite(pStrm, (uchar*) pszBuf, lenBuf)); + + /* trailer */ + CHKiRet(strmWriteChar(pStrm, ':')); + CHKiRet(strmWriteChar(pStrm, '\n')); + +finalize_it: + RETiRet; +} + + +/* end serialization of an object. The caller receives a + * standard C string, which he must free when no longer needed. + */ +static rsRetVal +EndSerialize(strm_t *pStrm) +{ + DEFiRet; + + assert(pStrm != NULL); + + CHKiRet(strmWriteChar(pStrm, COOKIE_ENDLINE)); + CHKiRet(strmWrite(pStrm, (uchar*) "End\n", sizeof("END\n") - 1)); + CHKiRet(strmWriteChar(pStrm, COOKIE_BLANKLINE)); + CHKiRet(strmWriteChar(pStrm, '\n')); + + CHKiRet(strmRecordEnd(pStrm)); + +finalize_it: + RETiRet; +} + + +/* define a helper to make code below a bit cleaner (and quicker to write) */ +#define NEXTC CHKiRet(strmReadChar(pStrm, &c))//;dbgprintf("c: %c\n", c); + + +/* de-serialize an embedded, non-octect-counted string. This is useful + * for deserializing the object name inside the header. The string is + * terminated by the first occurence of the ':' character. + * rgerhards, 2008-02-29 + */ +static rsRetVal +objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm) +{ + DEFiRet; + uchar c; + cstr_t *pStr = NULL; + + assert(ppStr != NULL); + + CHKiRet(rsCStrConstruct(&pStr)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pStr)); + + *ppStr = pStr; + +finalize_it: + if(iRet != RS_RET_OK && pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* de-serialize a number */ +static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm) +{ + DEFiRet; + number_t i; + int bIsNegative; + uchar c; + + assert(pNum != NULL); + + NEXTC; + if(c == '-') { + bIsNegative = 1; + NEXTC; + } else { + bIsNegative = 0; + } + + /* we check this so that we get more meaningful error codes */ + if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER); + + i = 0; + while(isdigit(c)) { + i = i * 10 + c - '0'; + NEXTC; + } + + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + if(bIsNegative) + i *= -1; + + *pNum = i; +finalize_it: + RETiRet; +} + + +/* de-serialize a string, length must be provided but may be 0 */ +static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm) +{ + DEFiRet; + int i; + uchar c; + cstr_t *pCStr = NULL; + + assert(ppCStr != NULL); + assert(iLen >= 0); + + CHKiRet(rsCStrConstruct(&pCStr)); + + NEXTC; + for(i = 0 ; i < iLen ; ++i) { + CHKiRet(rsCStrAppendChar(pCStr, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pCStr)); + + /* check terminator */ + if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + + *ppCStr = pCStr; + +finalize_it: + if(iRet != RS_RET_OK && pCStr != NULL) + rsCStrDestruct(&pCStr); + + RETiRet; +} + + +/* de-serialize a syslogTime -- rgerhards,2008-01-08 */ +#define GETVAL(var) \ + CHKiRet(objDeserializeNumber(&l, pStrm)); \ + pTime->var = l; +static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm) +{ + DEFiRet; + number_t l; + uchar c; + + assert(pTime != NULL); + + GETVAL(timeType); + GETVAL(year); + GETVAL(month); + GETVAL(day); + GETVAL(hour); + GETVAL(minute); + GETVAL(second); + GETVAL(secfrac); + GETVAL(secfracPrecision); + /* OffsetMode is a single character! */ + NEXTC; pTime->OffsetMode = c; + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER); + GETVAL(OffsetHour); + GETVAL(OffsetMinute); + +finalize_it: + RETiRet; +} +#undef GETVAL + +/* de-serialize an object header + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm) +{ + DEFiRet; + number_t oVers; + uchar c; + + assert(ppstrID != NULL); + assert(poVers != NULL); + assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB")); + + /* check header cookie */ + NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER); + NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS); + + /* object type and version */ + CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm)); + CHKiRet(objDeserializeNumber(&oVers, pStrm)); + + /* and now we skip over the rest until the delemiting \n */ + NEXTC; + while(c != '\n') { + NEXTC; + } + + *poVers = oVers; + +finalize_it: + RETiRet; +} + + +/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line + * up until the \n is read. + */ +static rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm) +{ + DEFiRet; + number_t i; + number_t iLen; + uchar c; + + assert(pProp != NULL); + + /* check cookie */ + NEXTC; + if(c != COOKIE_PROPLINE) { + /* oops, we've read one char that does not belong to use - unget it first */ + CHKiRet(strmUnreadChar(pStrm, c)); + ABORT_FINALIZE(RS_RET_NO_PROPLINE); + } + + /* get the property name first */ + CHKiRet(rsCStrConstruct(&pProp->pcsName)); + + NEXTC; + while(c != ':') { + CHKiRet(rsCStrAppendChar(pProp->pcsName, c)); + NEXTC; + } + CHKiRet(rsCStrFinish(pProp->pcsName)); + + /* property type */ + CHKiRet(objDeserializeNumber(&i, pStrm)); + pProp->varType = i; + + /* size (needed for strings) */ + CHKiRet(objDeserializeNumber(&iLen, pStrm)); + + /* we now need to deserialize the value */ + switch(pProp->varType) { + case VARTYPE_STR: + CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm)); + break; + case VARTYPE_NUMBER: + CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm)); + break; + case VARTYPE_SYSLOGTIME: + CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm)); + break; + default: + dbgprintf("invalid VARTYPE %d\n", pProp->varType); + break; + } + + /* we should now be at the end of the line. So the next char must be \n */ + NEXTC; + if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME); + +finalize_it: + RETiRet; +} + + +/* de-serialize an object trailer. This does not get any data but checks if the + * format is ok. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTrailer(strm_t *pStrm) +{ + DEFiRet; + uchar c; + + /* check header cookie */ + NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER); + +finalize_it: + RETiRet; +} + + + +/* This method tries to recover a serial store if it got out of sync. + * To do so, it scans the line beginning cookies and waits for the object + * cookie. If that is found, control is returned. If the store is exhausted, + * we will receive an RS_RET_EOF error as part of NEXTC, which will also + * terminate this function. So we may either return with somehting that + * looks like a valid object or end of store. + * rgerhards, 2008-01-07 + */ +static rsRetVal objDeserializeTryRecover(strm_t *pStrm) +{ + DEFiRet; + uchar c; + int bWasNL; + int bRun; + + assert(pStrm != NULL); + bRun = 1; + bWasNL = 0; + + while(bRun) { + NEXTC; + if(c == '\n') + bWasNL = 1; + else { + if(bWasNL == 1 && c == COOKIE_OBJLINE) + bRun = 0; /* we found it! */ + else + bWasNL = 0; + } + } + + CHKiRet(strmUnreadChar(pStrm, c)); + +finalize_it: + dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet); + RETiRet; +} + + +/* De-serialize the properties of an object. This includes processing + * of the trailer. Header must already have been processed. + * rgerhards, 2008-01-11 + */ +static rsRetVal objDeserializeProperties(obj_t *pObj, objInfo_t *pObjInfo, strm_t *pStrm) +{ + DEFiRet; + var_t *pVar = NULL; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + ASSERT(pObjInfo != NULL); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + iRet = objDeserializeProperty(pVar, pStrm); + while(iRet == RS_RET_OK) { + CHKiRet(pObjInfo->objMethods[objMethod_SETPROPERTY](pObj, pVar)); + /* re-init var object - TODO: method of var! */ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } + iRet = objDeserializeProperty(pVar, pStrm); + } + + if(iRet != RS_RET_NO_PROPLINE) + FINALIZE; + + CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */ +finalize_it: + if(pVar != NULL) + var.Destruct(&pVar); + + RETiRet; +} + + +/* De-Serialize an object. + * Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning) + * expected object ID (to check against), a fixup function that can modify the object before it is finalized + * and a user pointer that is to be passed to that function in addition to the object. The fixup function + * pointer may be NULL, in which case none is called. + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr) +{ + DEFiRet; + rsRetVal iRetLocal; + obj_t *pObj = NULL; + int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ + cstr_t *pstrID = NULL; + objInfo_t *pObjInfo; + + assert(ppObj != NULL); + assert(pszTypeExpected != NULL); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state, + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pszTypeExpected, strlen((char*)pszTypeExpected))) // TODO: optimize strlen() - caller shall provide + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + + /* check if we need to call a fixup function that modifies the object + * before it is finalized. -- rgerhards, 2008-01-13 + */ + if(fFixup != NULL) + CHKiRet(fFixup(pObj, pUsr)); + + /* we have a valid object, let's finalize our work and return */ + if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER)) + CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj)); + + *((obj_t**) ppObj) = pObj; + +finalize_it: + if(iRet != RS_RET_OK && pObj != NULL) + free(pObj); // TODO: check if we can call destructor 2008-01-13 rger + + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + +/* De-Serialize an object, but treat it as property bag. + * rgerhards, 2008-01-11 + */ +rsRetVal +objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers = 0; /* after all, it is totally useless but takes up some execution time... */ + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + + + +/* De-Serialize an object property bag. As a property bag contains only partial properties, + * it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated + * object of the correct type. + * Params: Pointer to object (pObj) + * Pointer to be passed to the function + * The caller must destruct the created object. + * rgerhards, 2008-01-07 + */ +static rsRetVal +DeserializePropBag(obj_t *pObj, strm_t *pStrm) +{ + DEFiRet; + rsRetVal iRetLocal; + cstr_t *pstrID = NULL; + int oVers; + objInfo_t *pObjInfo; + + ISOBJ_assert(pObj); + ISOBJ_TYPE_assert(pStrm, strm); + + /* we de-serialize the header. if all goes well, we are happy. However, if + * we experience a problem, we try to recover. We do this by skipping to + * the next object header. This is defined via the line-start cookies. In + * worst case, we exhaust the queue, but then we receive EOF return state + * from objDeserializeTryRecover(), what will cause us to ultimately give up. + * rgerhards, 2008-07-08 + */ + do { + iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm); + if(iRetLocal != RS_RET_OK) { + dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal); + CHKiRet(objDeserializeTryRecover(pStrm)); + } + } while(iRetLocal != RS_RET_OK); + + if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID)) + ABORT_FINALIZE(RS_RET_INVALID_OID); + + CHKiRet(FindObjInfo(pstrID, &pObjInfo)); + + /* we got the object, now we need to fill the properties */ + CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm)); + +finalize_it: + if(pstrID != NULL) + rsCStrDestruct(&pstrID); + + RETiRet; +} + +#undef NEXTC /* undef helper macro */ + + +/* --------------- end object serializiation / deserialization support --------------- */ + + +/* set the object (instance) name + * rgerhards, 2008-01-29 + * TODO: change the naming to a rsCStr obj! (faster) + */ +static rsRetVal +SetName(obj_t *pThis, uchar *pszName) +{ + DEFiRet; + + if(pThis->pszName != NULL) + free(pThis->pszName); + + pThis->pszName = (uchar*) strdup((char*) pszName); + + if(pThis->pszName == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + +finalize_it: + RETiRet; +} + + +/* get the object (instance) name + * Note that we use a non-standard calling convention. Thus function must never + * fail, else we run into real big problems. So it must make sure that at least someting + * is returned. + * rgerhards, 2008-01-30 + */ +static uchar * +GetName(obj_t *pThis) +{ + uchar *ret; + uchar szName[128]; + + BEGINfunc + ISOBJ_assert(pThis); + + if(pThis->pszName == NULL) { + snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis); + SetName(pThis, szName); + /* looks strange, but we NEED to re-check because if there was an + * error in objSetName(), the pointer may still be NULL + */ + if(pThis->pszName == NULL) { + ret = objGetClassName(pThis); + } else { + ret = pThis->pszName; + } + } else { + ret = pThis->pszName; + } + + ENDfunc + return ret; +} + + +/* Find the objInfo object for the current object + * rgerhards, 2008-02-29 + */ +static rsRetVal +FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pstrOID != NULL); + assert(ppInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_NOT_FOUND); + + *ppInfo = arrObjInfo[i]; + +finalize_it: + if(iRet == RS_RET_OK) { + /* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/ + /*EMPTY BY INTENSION*/; + } else { + dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet); + } + + RETiRet; +} + + +/* register a classes' info pointer, so that we can reference it later, if needed to + * (e.g. for de-serialization support). + * rgerhards, 2008-01-07 + * In this function, we look for a free space in the object table. While we do so, we + * also detect if the same object has already been registered, which is not valid. + * rgerhards, 2008-02-29 + */ +static rsRetVal +RegisterObj(uchar *pszObjName, objInfo_t *pInfo) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + assert(pInfo != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) { + if( arrObjInfo[i] != NULL + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED); + if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE); + + arrObjInfo[i] = pInfo; + /* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */ + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet); + } + + RETiRet; +} + + +/* deregister a classes' info pointer, usually called because the class is unloaded. + * After deregistration, the class can no longer be accessed, except if it is reloaded. + * rgerhards, 2008-03-10 + */ +static rsRetVal +UnregisterObj(uchar *pszObjName) +{ + DEFiRet; + int bFound; + int i; + + assert(pszObjName != NULL); + + bFound = 0; + i = 0; + while(!bFound && i < OBJ_NUM_IDS) { + if( arrObjInfo[i] != NULL + && !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) { + bFound = 1; + break; + } + ++i; + } + + if(!bFound) + ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED); + + InfoDestruct(&arrObjInfo[i]); + /* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */ + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet); + } + + RETiRet; +} + + +/* This function shall be called by anyone who would like to use an object. It will + * try to locate the object, load it into memory if not already present and return + * a pointer to the objects interface. + * rgerhards, 2008-02-29 + */ +static rsRetVal +UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + /* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */ + + if(pIf->ifIsLoaded == 1) { + ABORT_FINALIZE(RS_RET_OK); /* we are already set */ + } + if(pIf->ifIsLoaded == 2) { + ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */ + } + + /* we must be careful that we do not enter in infinite loop if an error occurs during + * loading a module. ModLoad emits an error message in such cases and that potentially + * can trigger the same code here. So we initially set the module state to "load error" + * and set it to "fully initialized" when the load succeeded. It's a bit hackish, but + * looks like a good solution. -- rgerhards, 2008-03-07 + */ + pIf->ifIsLoaded = 2; + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + iRet = FindObjInfo(pStr, &pObjInfo); + if(iRet == RS_RET_NOT_FOUND) { + /* in this case, we need to see if we can dynamically load the object */ + if(pObjFile == NULL) { + FINALIZE; /* no chance, we have lost... */ + } else { + CHKiRet(module.Load(pObjFile)); + /* NOW, we must find it or we have a problem... */ + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + } + } else if(iRet != RS_RET_OK) { + FINALIZE; /* give up */ + } + + /* if we reach this point, we have a valid pObjInfo */ + if(pObjFile != NULL) { /* NULL means core module */ + module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */ + } + + CHKiRet(pObjInfo->QueryIF(pIf)); + pIf->ifIsLoaded = 1; /* we are happy */ + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* This function shall be called when a caller is done with an object. Its primary + * purpose is to keep the reference count correct, which is highly important for + * modules residing in loadable modules. + * rgerhards, 2008-03-10 + */ +static rsRetVal +ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf) +{ + DEFiRet; + cstr_t *pStr = NULL; + objInfo_t *pObjInfo; + + + dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); + + if(pObjFile == NULL) + FINALIZE; /* if it is not a lodable module, we do not need to do anything... */ + + if(pIf->ifIsLoaded == 0) { + ABORT_FINALIZE(RS_RET_OK); /* we are already set */ /* TODO: flag an error? */ + } + if(pIf->ifIsLoaded == 2) { + pIf->ifIsLoaded = 0; /* clean up */ + ABORT_FINALIZE(RS_RET_OK); /* we had a load error and can not continue */ + } + + CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName)); + CHKiRet(FindObjInfo(pStr, &pObjInfo)); + + /* if we reach this point, we have a valid pObjInfo */ + //if(pObjInfo->pModInfo != NULL) { /* NULL means core module */ + module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */ + + pIf->ifIsLoaded = 0; /* indicated "no longer valid" */ + +finalize_it: + if(pStr != NULL) + rsCStrDestruct(&pStr); + + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(obj) +CODESTARTobjQueryInterface(obj) + if(pIf->ifVersion != objCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->UseObj = UseObj; + pIf->ReleaseObj = ReleaseObj; + pIf->InfoConstruct = InfoConstruct; + pIf->DestructObjSelf = DestructObjSelf; + pIf->BeginSerializePropBag = BeginSerializePropBag; + pIf->InfoSetMethod = InfoSetMethod; + pIf->BeginSerialize = BeginSerialize; + pIf->SerializeProp = SerializeProp; + pIf->EndSerialize = EndSerialize; + pIf->RegisterObj = RegisterObj; + pIf->UnregisterObj = UnregisterObj; + pIf->Deserialize = Deserialize; + pIf->DeserializePropBag = DeserializePropBag; + pIf->SetName = SetName; + pIf->GetName = GetName; +finalize_it: +ENDobjQueryInterface(obj) + + +/* This function returns a pointer to our own interface. It is used as the + * hook that every object (including dynamically loaded ones) can use to + * obtain a pointer to our interface which than can be used to obtain + * pointers to any other interface in the system. This function must be + * externally visible because of its special nature. + * rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)] + */ +rsRetVal +objGetObjInterface(obj_if_t *pIf) +{ + DEFiRet; + assert(pIf != NULL); + objQueryInterface(pIf); + RETiRet; +} + + +/* exit our class + * rgerhards, 2008-03-11 + */ +rsRetVal +objClassExit(void) +{ + DEFiRet; + /* release objects we no longer need */ + objRelease(var, CORE_COMPONENT); + objRelease(module, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + + /* TODO: implement the class exits! */ +#if 0 + errmsgClassInit(pModInfo); + cfsyslineInit(pModInfo); + varClassInit(pModInfo); +#endif + moduleClassExit(); + RETiRet; +} + + +/* initialize our own class + * Please note that this also initializes those classes that we rely on. + * Though this is a bit dirty, we need to do it - otherwise we can't get + * around that bootstrap problem. We need to face the fact the the obj + * class is a little different from the rest of the system, as it provides + * the core class loader functionality. + * rgerhards, 2008-02-29 + */ +rsRetVal +objClassInit(modInfo_t *pModInfo) +{ + DEFiRet; + int i; + + /* first, initialize the object system itself. This must be done + * before any other object is created. + */ + for(i = 0 ; i < OBJ_NUM_IDS ; ++i) { + arrObjInfo[i] = NULL; + } + + /* request objects we use */ + CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ + + /* init classes we use (limit to as few as possible!) */ + CHKiRet(errmsgClassInit(pModInfo)); + CHKiRet(cfsyslineInit()); + CHKiRet(varClassInit(pModInfo)); + CHKiRet(moduleClassInit(pModInfo)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +finalize_it: + RETiRet; +} + +/* vi:set ai: + */ diff --git a/runtime/obj.h b/runtime/obj.h new file mode 100644 index 00000000..dc04203b --- /dev/null +++ b/runtime/obj.h @@ -0,0 +1,125 @@ +/* Definition of the generic obj class module. + * + * This module relies heavily on preprocessor macros in order to + * provide fast execution time AND ease of use. + * + * Each object that uses this base class MUST provide a constructor with + * the following interface: + * + * Destruct(pThis); + * + * A constructor is not necessary (except for some features, e.g. de-serialization). + * If it is provided, it is a three-part constructor (to handle all cases with a + * generic interface): + * + * Construct(&pThis); + * SetProperty(pThis, property_t *); + * ConstructFinalize(pThis); + * + * SetProperty() and ConstructFinalize() may also be called on an object + * instance which has been Construct()'ed outside of this module. + * + * pThis always references to a pointer of the object. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJ_H_INCLUDED +#define OBJ_H_INCLUDED + +#include "obj-types.h" +#include "var.h" +#include "stream.h" + +/* macros */ +/* the following one is a helper that prevents us from writing the + * ever-same code at the end of Construct() + */ +#define OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \ + if(iRet == RS_RET_OK) { \ + *ppThis = pThis; \ + } else { \ + if(pThis != NULL) \ + free(pThis); \ + } + +#define objSerializeSCALAR_VAR(strm, propName, propType, var) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &var)); +#define objSerializeSCALAR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &pThis->propName)); +#define objSerializePTR(strm, propName, propType) \ + CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) pThis->propName)); +#define DEFobjStaticHelpers \ + static objInfo_t *pObjInfoOBJ = NULL; \ + DEFobjCurrIf(obj) + + +#define objGetClassName(pThis) (((obj_t*) (pThis))->pObjInfo->pszID) +#define objGetVersion(pThis) (((obj_t*) (pThis))->pObjInfo->iObjVers) +/* the next macro MUST be called in Constructors: */ +#ifndef NDEBUG /* this means if debug... */ +# define objConstructSetObjInfo(pThis) \ + ASSERT(((obj_t*) (pThis))->pObjInfo == NULL); \ + ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \ + ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE +#else +# define objConstructSetObjInfo(pThis) ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ +#endif +#define objDestruct(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DESTRUCT])(&pThis) +#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE]) +#define objGetSeverity(pThis, piSever) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_GETSEVERITY])(pThis, piSever) +#define objDebugPrint(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_DEBUGPRINT])(pThis) + +#define OBJSetMethodHandler(methodID, pHdlr) \ + CHKiRet(obj.InfoSetMethod(pObjInfoOBJ, methodID, (rsRetVal (*)(void*)) pHdlr)) + +/* interfaces */ +BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*UseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*ReleaseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf); + rsRetVal (*InfoConstruct)(objInfo_t **ppThis, uchar *pszID, int iObjVers, + rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *), + rsRetVal (*pQueryIF)(interface_t*), modInfo_t*); + rsRetVal (*DestructObjSelf)(obj_t *pThis); + rsRetVal (*BeginSerializePropBag)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*InfoSetMethod)(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*)); + rsRetVal (*BeginSerialize)(strm_t *pStrm, obj_t *pObj); + rsRetVal (*SerializeProp)(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr); + rsRetVal (*EndSerialize)(strm_t *pStrm); + rsRetVal (*RegisterObj)(uchar *pszObjName, objInfo_t *pInfo); + rsRetVal (*UnregisterObj)(uchar *pszObjName); + rsRetVal (*Deserialize)(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr); + rsRetVal (*DeserializePropBag)(obj_t *pObj, strm_t *pStrm); + rsRetVal (*SetName)(obj_t *pThis, uchar *pszName); + uchar * (*GetName)(obj_t *pThis); +ENDinterface(obj) +#define objCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +/* the following define *is* necessary, because it provides the root way of obtaining + * interfaces (at some place we need to start our query... + */ +rsRetVal objGetObjInterface(obj_if_t *pIf); +PROTOTYPEObjClassInit(obj); +PROTOTYPEObjClassExit(obj); + +#endif /* #ifndef OBJ_H_INCLUDED */ diff --git a/runtime/objomsr.c b/runtime/objomsr.c new file mode 100644 index 00000000..21d284f3 --- /dev/null +++ b/runtime/objomsr.c @@ -0,0 +1,145 @@ +/* objomsr.c + * Implementation of the omsr (omodStringRequest) object. + * + * File begun on 2007-07-27 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include + +#include "rsyslog.h" +#include "objomsr.h" + + +/* destructor + */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis) +{ + int i; + + assert(pThis != NULL); + /* free the strings */ + if(pThis->ppTplName != NULL) { + for(i = 0 ; i < pThis->iNumEntries ; ++i) { + if(pThis->ppTplName[i] != NULL) { + free(pThis->ppTplName[i]); + } + } + free(pThis->ppTplName); + } + if(pThis->piTplOpts != NULL) + free(pThis->piTplOpts); + free(pThis); + + return RS_RET_OK; +} + + +/* constructor + */ +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries) +{ + omodStringRequest_t *pThis; + DEFiRet; + + assert(ppThis != NULL); + assert(iNumEntries >= 0); + if((pThis = calloc(1, sizeof(omodStringRequest_t))) == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + + /* got the structure, so fill it */ + pThis->iNumEntries = iNumEntries; + /* allocate string for template name array. The individual strings will be + * allocated as the code progresses (we do not yet know the string sizes) + */ + if((pThis->ppTplName = calloc(iNumEntries, sizeof(uchar*))) == NULL) { + OMSRdestruct(pThis); + pThis = NULL; + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + /* allocate the template options array. */ + if((pThis->piTplOpts = calloc(iNumEntries, sizeof(int))) == NULL) { + OMSRdestruct(pThis); + pThis = NULL; + iRet = RS_RET_OUT_OF_MEMORY; + goto abort_it; + } + +abort_it: + *ppThis = pThis; + RETiRet; +} + +/* set a template name and option to the object. Index must be given. The pTplName must be + * pointing to memory that can be freed. If in doubt, the caller must strdup() the value. + */ +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts) +{ + assert(pThis != NULL); + assert(pTplName != NULL); + assert(iEntry < pThis->iNumEntries); + + if(pThis->ppTplName[iEntry] != NULL) + free(pThis->ppTplName[iEntry]); + pThis->ppTplName[iEntry] = pTplName; + pThis->piTplOpts[iEntry] = iTplOpts; + + return RS_RET_OK; +} + + +/* get number of entries for this object + */ +int OMSRgetEntryCount(omodStringRequest_t *pThis) +{ + assert(pThis != NULL); + return pThis->iNumEntries; +} + + +/* return data for a specific entry. All data returned is + * read-only and lasts only as long as the object lives. If the caller + * needs it for an extended period of time, the caller must copy the + * strings. Please note that the string pointer may be NULL, which is the + * case when it was never set. + */ +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts) +{ + assert(pThis != NULL); + assert(ppTplName != NULL); + assert(piTplOpts != NULL); + assert(iEntry < pThis->iNumEntries); + + *ppTplName = pThis->ppTplName[iEntry]; + *piTplOpts = pThis->piTplOpts[iEntry]; + + return RS_RET_OK; +} +/* vim:set ai: + */ diff --git a/runtime/objomsr.h b/runtime/objomsr.h new file mode 100644 index 00000000..2255e4f3 --- /dev/null +++ b/runtime/objomsr.h @@ -0,0 +1,46 @@ +/* Definition of the omsr (omodStringRequest) object. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef OBJOMSR_H_INCLUDED +#define OBJOMSR_H_INCLUDED + +/* define flags for required template options */ +#define OMSR_NO_RQD_TPL_OPTS 0 +#define OMSR_RQD_TPL_OPT_SQL 1 +/* next option is 2, 4, 8, ... */ + +struct omodStringRequest_s { /* strings requested by output module for doAction() */ + int iNumEntries; /* number of array entries for data elements below */ + uchar **ppTplName; /* pointer to array of template names */ + int *piTplOpts;/* pointer to array of check-options when pulling template */ +}; +typedef struct omodStringRequest_s omodStringRequest_t; + +/* prototypes */ +rsRetVal OMSRdestruct(omodStringRequest_t *pThis); +rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries); +rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts); +int OMSRgetEntryCount(omodStringRequest_t *pThis); +int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts); + +#endif /* #ifndef OBJOMSR_H_INCLUDED */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h new file mode 100644 index 00000000..2bc7f904 --- /dev/null +++ b/runtime/rsyslog.h @@ -0,0 +1,272 @@ +/* This is the header file for the rsyslog runtime. It must be included + * if someone intends to use the runtime. + * + * Begun 2005-09-15 RGerhards + * + * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef INCLUDED_RSYSLOG_H +#define INCLUDED_RSYSLOG_H + +/* ############################################################# * + * # Config Settings # * + * ############################################################# */ +#define RS_STRINGBUF_ALLOC_INCREMENT 128 + +/* ############################################################# * + * # End Config Settings # * + * ############################################################# */ + +#ifndef NOLARGEFILE +# undef _LARGEFILE_SOURCE +# undef _LARGEFILE64_SOURCE +# undef _FILE_OFFSET_BITS +# define _LARGEFILE_SOURCE +# define _LARGEFILE64_SOURCE +# define _FILE_OFFSET_BITS 64 +#endif + +/* define some base data types */ +typedef struct thrdInfo thrdInfo_t; + +/* some universal 64 bit define... */ +typedef long long int64; +typedef long long unsigned uint64; +typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ + +#ifdef __hpux +typedef unsigned int u_int32_t; /* TODO: is this correct? */ +typedef int socklen_t; +#endif + +/* settings for flow control + * TODO: is there a better place for them? -- rgerhards, 2008-03-14 + */ +typedef enum { + eFLOWCTL_NO_DELAY = 0, /**< UDP and other non-delayable sources */ + eFLOWCTL_LIGHT_DELAY = 1, /**< some light delay possible, but no extended period of time */ + eFLOWCTL_FULL_DELAY = 2 /**< delay possible for extended period of time */ +} flowControl_t; + + +/* The error codes below are orginally "borrowed" from + * liblogging. As such, we reserve values up to -2999 + * just in case we need to borrow something more ;) +*/ +enum rsRetVal_ /** return value. All methods return this if not specified otherwise */ +{ + RS_RET_NOT_IMPLEMENTED = -7, /**< implementation is missing (probably internal error or lazyness ;)) */ + RS_RET_OUT_OF_MEMORY = -6, /**< memory allocation failed */ + RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,/**< the caller provided a buffer, but the called function sees the size of this buffer is too small - operation not carried out */ + RS_RET_TRUE = -1, /**< to indicate a true state (can be used as TRUE, legacy) */ + RS_RET_FALSE = -2, /**< to indicate a false state (can be used as FALSE, legacy) */ + RS_RET_NO_IRET = -8, /**< This is a trick for the debuging system - it means no iRet is provided */ + RS_RET_ERR = -3000, /**< generic failure */ + RS_TRUNCAT_TOO_LARGE = -3001, /**< truncation operation where too many chars should be truncated */ + RS_RET_FOUND_AT_STRING_END = -3002, /**< some value found, but at the last pos of string */ + RS_RET_NOT_FOUND = -3003, /**< some requested value not found */ + RS_RET_MISSING_TRAIL_QUOTE = -3004, /**< an expected trailing quote is missing */ + RS_RET_NO_DIGIT = -3005, /**< an digit was expected, but none found (mostly parsing) */ + RS_RET_NO_MORE_DATA = -3006, /**< insufficient data, e.g. end of string during parsing */ + RS_RET_INVALID_IP = -3007, /**< invalid ip found where valid was expected */ + RS_RET_OBJ_CREATION_FAILED = - 3008, /**< the creation of an object failed (no details available) */ + RS_RET_PARAM_ERROR = -1000, /**< invalid parameter in call to function */ + RS_RET_MISSING_INTERFACE = -1001,/**< interface version mismatch, required missing */ + RS_RET_INVALID_CORE_INTERFACE = -1002,/**< interface provided by host invalid, can not be used */ + RS_RET_ENTRY_POINT_NOT_FOUND = -1003,/**< a requested entry point was not found */ + RS_RET_MODULE_ENTRY_POINT_NOT_FOUND = -1004,/**< a entry point requested from a module was not present in it */ + RS_RET_OBJ_NOT_AVAILABLE = -1005,/**< something could not be completed because the required object is not available*/ + RS_RET_LOAD_ERROR = -1006,/**< we had an error loading the object/interface and can not continue */ + RS_RET_MODULE_STILL_REFERENCED = -1007,/**< module could not be unloaded because it still is referenced by someone */ + RS_RET_OBJ_UNKNOWN = -1008,/**< object is unknown where required */ + RS_RET_OBJ_NOT_REGISTERED = -1009,/**< tried to unregister an object that is not registered */ + /* return states for config file processing */ + RS_RET_NONE = -2000, /**< some value is not available - not necessarily an error */ + RS_RET_CONFLINE_UNPROCESSED = -2001,/**< config line was not processed, pass to other module */ + RS_RET_DISCARDMSG = -2002, /**< discard message (no error state, processing request!) */ + RS_RET_INCOMPATIBLE = -2003, /**< function not compatible with requested feature */ + RS_RET_NOENTRY = -2004, /**< do not create an entry for (whatever) - not necessary an error */ + RS_RET_NO_SQL_STRING = -2005, /**< string is not suitable for use as SQL */ + RS_RET_DISABLE_ACTION = -2006, /**< action requests that it be disabled */ + RS_RET_SUSPENDED = -2007, /**< something was suspended, not neccesarily an error */ + RS_RET_RQD_TPLOPT_MISSING = -2008,/**< a required template option is missing */ + RS_RET_INVALID_VALUE = -2009,/**< some value is invalid (e.g. user-supplied data) */ + RS_RET_INVALID_INT = -2010,/**< invalid integer */ + RS_RET_INVALID_CMD = -2011,/**< invalid command */ + RS_RET_VAL_OUT_OF_RANGE = -2012, /**< value out of range */ + RS_RET_FOPEN_FAILURE = -2013, /**< failure during fopen, for example file not found - see errno */ + RS_RET_END_OF_LINKEDLIST = -2014, /**< end of linked list, not an error, but a status */ + RS_RET_CHAIN_NOT_PERMITTED = -2015, /**< chaining (e.g. of config command handlers) not permitted */ + RS_RET_INVALID_PARAMS = -2016,/**< supplied parameters are invalid */ + RS_RET_EMPTY_LIST = -2017, /**< linked list is empty */ + RS_RET_FINISHED = -2018, /**< some opertion is finished, not an error state */ + RS_RET_INVALID_SOURCE = -2019, /**< source (address) invalid for some reason */ + RS_RET_ADDRESS_UNKNOWN = -2020, /**< an address is unknown - not necessarily an error */ + RS_RET_MALICIOUS_ENTITY = -2021, /**< there is an malicious entity involved */ + RS_RET_NO_KERNEL_LOGSRC = -2022, /**< no source for kernel logs can be obtained */ + RS_RET_TCP_SEND_ERROR = -2023, /**< error during TCP send process */ + RS_RET_GSS_SEND_ERROR = -2024, /**< error during GSS (via TCP) send process */ + RS_RET_TCP_SOCKCREATE_ERR = -2025, /**< error during creation of TCP socket */ + RS_RET_GSS_SENDINIT_ERROR = -2024, /**< error during GSS (via TCP) send initialization process */ + RS_RET_QUEUE_FULL = -2025, /**< queue is full, operation could not be completed */ + RS_RET_EOF = -2026, /**< end of file reached, not necessarily an error */ + RS_RET_IO_ERROR = -2027, /**< some kind of IO error happened */ + RS_RET_INVALID_OID = -2028, /**< invalid object ID */ + RS_RET_INVALID_HEADER = -2029, /**< invalid header */ + RS_RET_INVALID_HEADER_VERS = -2030, /**< invalid header version */ + RS_RET_INVALID_DELIMITER = -2031, /**< invalid delimiter, e.g. between params */ + RS_RET_INVALID_PROPFRAME = -2032, /**< invalid framing in serialized property */ + RS_RET_NO_PROPLINE = -2033, /**< line is not a property line */ + RS_RET_INVALID_TRAILER = -2034, /**< invalid trailer */ + RS_RET_VALUE_TOO_LOW = -2035, /**< a provided value is too low */ + RS_RET_FILE_PREFIX_MISSING = -2036, /**< a required file prefix (parameter?) is missing */ + RS_RET_INVALID_HEADER_RECTYPE = -2037, /**< invalid record type in header or invalid header */ + RS_RET_QTYPE_MISMATCH = -2038, /**< different qType when reading back a property type */ + RS_RET_NO_FILE_ACCESS = -2039, /**< covers EACCES error on file open() */ + RS_RET_FILE_NOT_FOUND = -2040, /**< file not found */ + RS_RET_TIMED_OUT = -2041, /**< timeout occured (not necessarily an error) */ + RS_RET_QSIZE_ZERO = -2042, /**< queue size is zero where this is not supported */ + RS_RET_ALREADY_STARTING = -2043, /**< something (a thread?) is already starting - not necessarily an error */ + RS_RET_NO_MORE_THREADS = -2044, /**< no more threads available, not necessarily an error */ + RS_RET_NO_FILEPREFIX = -2045, /**< file prefix is not specified where one is needed */ + RS_RET_CONFIG_ERROR = -2046, /**< there is a problem with the user-provided config settigs */ + RS_RET_OUT_OF_DESRIPTORS = -2047, /**< a descriptor table's space has been exhausted */ + RS_RET_NO_DRIVERS = -2048, /**< a required drivers missing */ + RS_RET_NO_DRIVERNAME = -2049, /**< driver name missing where one was required */ + RS_RET_EOS = -2050, /**< end of stream (of whatever) */ + RS_RET_SYNTAX_ERROR = -2051, /**< syntax error, eg. during parsing */ + RS_RET_INVALID_OCTAL_DIGIT = -2052, /**< invalid octal digit during parsing */ + RS_RET_INVALID_HEX_DIGIT = -2053, /**< invalid hex digit during parsing */ + RS_RET_INTERFACE_NOT_SUPPORTED = -2054, /**< interface not supported */ + RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */ + RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */ + RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */ + RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ + RS_RET_INVALID_NUMBER = -2059, /**< number invalid during parsing */ + RS_RET_NOT_A_NUMBER = -2060, /**< e.g. conversion impossible because the string is not a number */ + RS_RET_OBJ_ALREADY_REGISTERED = -2061, /**< object (name) is already registered */ + RS_RET_OBJ_REGISTRY_OUT_OF_SPACE = -2062, /**< the object registry has run out of space */ + RS_RET_HOST_NOT_PERMITTED = -2063, /**< a host is not permitted to perform an action it requested */ + RS_RET_MODULE_LOAD_ERR = -2064, /**< module could not be loaded */ + RS_RET_MODULE_LOAD_ERR_PATHLEN = -2065, /**< module could not be loaded - path to long */ + RS_RET_MODULE_LOAD_ERR_DLOPEN = -2066, /**< module could not be loaded - problem in dlopen() */ + RS_RET_MODULE_LOAD_ERR_NO_INIT = -2067, /**< module could not be loaded - init() missing */ + RS_RET_MODULE_LOAD_ERR_INIT_FAILED = -2068, /**< module could not be loaded - init() failed */ + RS_RET_NO_SOCKET = -2069, /**< socket could not be obtained or was not provided */ + RS_RET_SMTP_ERROR = -2070, /**< error during SMTP transation */ + RS_RET_MAIL_NO_TO = -2071, /**< recipient for mail destination is missing */ + RS_RET_MAIL_NO_FROM = -2072, /**< sender for mail destination is missing */ + RS_RET_INVALID_PRI = -2073, /**< PRI value is invalid */ + + /* RainerScript error messages (range 1000.. 1999) */ + RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ + + /* some generic error/status codes */ + RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */ + RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */ + RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */ + RS_RET_OK = 0 /**< operation successful */ +}; +typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ + +/* some helpful macros to work with srRetVals. + * Be sure to call the to-be-returned variable always "iRet" and + * the function finalizer always "finalize_it". + */ +#define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it +/* macro below is to be used if we need our own handling, eg for cleanup */ +#define CHKiRet_Hdlr(code) if((iRet = code) != RS_RET_OK) +/* macro below is to handle failing malloc/calloc/strdup... which we almost always handle in the same way... */ +#define CHKmalloc(operation) if((operation) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY) +/* macro below is used in conjunction with CHKiRet_Hdlr, else use ABORT_FINALIZE */ +#define FINALIZE goto finalize_it; +#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK +#define RETiRet do{ ENDfuncIRet return iRet; }while(0) + +#define ABORT_FINALIZE(errCode) \ + do { \ + iRet = errCode; \ + goto finalize_it; \ + } while (0) + +/** Object ID. These are for internal checking. Each + * object is assigned a specific ID. This is contained in + * all Object structs (just like C++ RTTI). We can use + * this field to see if we have been passed a correct ID. + * Other than that, there is currently no other use for + * the object id. + */ +enum rsObjectID +{ + OIDrsFreed = -1, /**< assigned, when an object is freed. If this + * is seen during a method call, this is an + * invalid object pointer! + */ + OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */ + /* The 0x3412 is a debug aid. It helps us find object IDs in memory + * dumps (on X86, this is 1234 in the dump ;) + * If you are on an embedded device and you would like to save space + * make them 1 byte only. + */ + OIDrsCStr = 0x34120001, + OIDrsPars = 0x34120002 +}; +typedef enum rsObjectID rsObjID; + +/* support to set object types */ +#ifdef NDEBUG +#define rsSETOBJTYPE(pObj, type) +#define rsCHECKVALIDOBJECT(x, type) +#else +#define rsSETOBJTYPE(pObj, type) pObj->OID = type; +#define rsCHECKVALIDOBJECT(x, type) {assert(x != NULL); assert(x->OID == type);} +#endif + +/** + * This macro should be used to free objects. + * It aids in interpreting dumps during debugging. + */ +#ifdef NDEBUG +#define RSFREEOBJ(x) free(x) +#else +#define RSFREEOBJ(x) {(x)->OID = OIDrsFreed; free(x);} +#endif + +/* get rid of the unhandy "unsigned char" + */ +typedef unsigned char uchar; + +/* for the time being, we do our own portability handling here. It + * looks like autotools either does not yet support checks for it, or + * I wasn't smart enough to find them ;) rgerhards, 2007-07-18 + */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +/* The following prototype is convenient, even though it may not be the 100% correct place.. -- rgerhards 2008-01-07 */ +void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2))); + +#include "debug.h" + +#endif /* multi-include protection */ +/* vim:set ai: + */ diff --git a/runtime/srUtils.h b/runtime/srUtils.h new file mode 100644 index 00000000..81d20357 --- /dev/null +++ b/runtime/srUtils.h @@ -0,0 +1,126 @@ +/*! \file srUtils.h + * \brief General, small utilities that fit nowhere else. + * + * \author Rainer Gerhards + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef __SRUTILS_H_INCLUDED__ +#define __SRUTILS_H_INCLUDED__ 1 + + +/* syslog names */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ +#define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */ +#define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ +#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ + +typedef struct syslogName_s { + char *c_name; + int c_val; +} syslogName_t; + +extern syslogName_t syslogPriNames[]; +extern syslogName_t syslogFacNames[]; + +/** + * A reimplementation of itoa(), as this is not available + * on all platforms. We used the chance to make an interface + * that fits us well, so it is no longer plain itoa(). + * + * This method works with the US-ASCII alphabet. If you port this + * to e.g. EBCDIC, you need to make a small adjustment. Keep in mind, + * that on the wire it MUST be US-ASCII, so basically all you need + * to do is replace the constant '0' with 0x30 ;). + * + * \param pBuf Caller-provided buffer that will receive the + * generated ASCII string. + * + * \param iLenBuf Length of the caller-provided buffer. + * + * \param iToConv The integer to be converted. + */ +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv); + +/** + * A method to duplicate a string for which the length is known. + * Len must be the length in characters WITHOUT the trailing + * '\0' byte. + * rgerhards, 2007-07-10 + */ +unsigned char *srUtilStrDup(unsigned char *pOld, size_t len); +/** + * A method to create a directory and all its missing parents for + * a given file name. Please not that the rightmost element is + * considered to be a file name and thus NO directory is being created + * for it. + * added 2007-07-17 by rgerhards + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, uid_t uid, gid_t gid, int bFailOnChown); +int execProg(uchar *program, int bWait, uchar *arg); +void skipWhiteSpace(uchar **pp); +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits); +int getNumberDigits(long lNum); +rsRetVal timeoutComp(struct timespec *pt, long iTimeout); +long timeoutVal(struct timespec *pt); +void mutexCancelCleanup(void *arg); +void srSleep(int iSeconds, int iuSeconds); +char *rs_strerror_r(int errnum, char *buf, size_t buflen); +int decodeSyslogName(uchar *name, syslogName_t *codetab); + +/* mutex operations */ +/* some macros to cancel-safe lock a mutex (it will automatically be released + * when the thread is cancelled. This needs to be done as macros because + * pthread_cleanup_push sometimes is a macro that can not be used inside a function. + * It's a bit ugly, but works well... rgerhards, 2008-01-20 + */ +#define DEFVARS_mutex_cancelsafeLock int iCancelStateSave +#define mutex_cancelsafe_lock(mut) \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + pthread_cleanup_push(mutexCancelCleanup, mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); +#define mutex_cancelsafe_unlock(mut) pthread_cleanup_pop(1) + +/* some useful constants */ +#define MUTEX_ALREADY_LOCKED 0 +#define LOCK_MUTEX 1 +#define DEFVARS_mutexProtection\ + int iCancelStateSave; \ + int bLockedOpIsLocked=0 +#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \ + if(bMustLock == LOCK_MUTEX) { \ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ + d_pthread_mutex_lock(mut); \ + bLockedOpIsLocked = 1; \ + } +#define END_MTX_PROTECTED_OPERATIONS(mut) \ + if(bLockedOpIsLocked) { \ + d_pthread_mutex_unlock(mut); \ + pthread_setcancelstate(iCancelStateSave, NULL); \ + } +#endif diff --git a/runtime/srutils.c b/runtime/srutils.c new file mode 100644 index 00000000..93908767 --- /dev/null +++ b/runtime/srutils.c @@ -0,0 +1,509 @@ +/**\file srUtils.c + * \brief General utilties that fit nowhere else. + * + * The namespace for this file is "srUtil". + * + * \author Rainer Gerhards + * \date 2003-09-09 + * Coding begun. + * + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define TRUE 1 +#define FALSE 0 +#include "srUtils.h" +#include "syslogd.h" +#include "obj.h" + + +/* here we host some syslog specific names. There currently is no better place + * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14 + * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in + * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL. + */ +syslogName_t syslogPriNames[] = { + {"alert", LOG_ALERT}, + {"crit", LOG_CRIT}, + {"debug", LOG_DEBUG}, + {"emerg", LOG_EMERG}, + {"err", LOG_ERR}, + {"error", LOG_ERR}, /* DEPRECATED */ + {"info", LOG_INFO}, + {"none", INTERNAL_NOPRI}, /* INTERNAL */ + {"notice", LOG_NOTICE}, + {"panic", LOG_EMERG}, /* DEPRECATED */ + {"warn", LOG_WARNING}, /* DEPRECATED */ + {"warning", LOG_WARNING}, + {"*", TABLE_ALLPRI}, + {NULL, -1} +}; + +#ifndef LOG_AUTHPRIV +# define LOG_AUTHPRIV LOG_AUTH +#endif +syslogName_t syslogFacNames[] = { + {"auth", LOG_AUTH}, + {"authpriv", LOG_AUTHPRIV}, + {"cron", LOG_CRON}, + {"daemon", LOG_DAEMON}, + {"kern", LOG_KERN}, + {"lpr", LOG_LPR}, + {"mail", LOG_MAIL}, + {"mark", LOG_MARK}, /* INTERNAL */ + {"news", LOG_NEWS}, + {"security", LOG_AUTH}, /* DEPRECATED */ + {"syslog", LOG_SYSLOG}, + {"user", LOG_USER}, + {"uucp", LOG_UUCP}, +#if defined(LOG_FTP) + {"ftp", LOG_FTP}, +#endif + {"local0", LOG_LOCAL0}, + {"local1", LOG_LOCAL1}, + {"local2", LOG_LOCAL2}, + {"local3", LOG_LOCAL3}, + {"local4", LOG_LOCAL4}, + {"local5", LOG_LOCAL5}, + {"local6", LOG_LOCAL6}, + {"local7", LOG_LOCAL7}, + {NULL, -1}, +}; + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* As this is not a "real" object, there won't be any private + * members in this file. + */ + +/* ################################################################# * + * public members * + * ################################################################# */ + +rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) +{ + int i; + int bIsNegative; + char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */ + + assert(pBuf != NULL); + assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */ + + if(iToConv < 0) + { + bIsNegative = TRUE; + iToConv *= -1; + } + else + bIsNegative = FALSE; + + /* first generate a string with the digits in the reverse direction */ + i = 0; + do + { + szBuf[i++] = iToConv % 10 + '0'; + iToConv /= 10; + } while(iToConv > 0); /* warning: do...while()! */ + --i; /* undo last increment - we were pointing at NEXT location */ + + /* make sure we are within bounds... */ + if(i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */ + return RS_RET_PROVIDED_BUFFER_TOO_SMALL; + + /* then move it to the right direction... */ + if(bIsNegative == TRUE) + *pBuf++ = '-'; + while(i >= 0) + *pBuf++ = szBuf[i--]; + *pBuf = '\0'; /* terminate it!!! */ + + return RS_RET_OK; +} + +uchar *srUtilStrDup(uchar *pOld, size_t len) +{ + uchar *pNew; + + assert(pOld != NULL); + + if((pNew = malloc(len + 1)) != NULL) + memcpy(pNew, pOld, len + 1); + + return pNew; +} + + +/* creates a path recursively + * Return 0 on success, -1 otherwise. On failure, errno + * hold the last OS error. + * Param "mode" holds the mode that all non-existing directories + * are to be created with. + */ +int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, + uid_t uid, gid_t gid, int bFailOnChownFail) +{ + uchar *p; + uchar *pszWork; + size_t len; + int bErr = 0; + + assert(szFile != NULL); + assert(lenFile > 0); + + len = lenFile + 1; /* add one for '\0'-byte */ + if((pszWork = malloc(sizeof(uchar) * len)) == NULL) + return -1; + memcpy(pszWork, szFile, len); + for(p = pszWork+1 ; *p ; p++) + if(*p == '/') { + /* temporarily terminate string, create dir and go on */ + *p = '\0'; + if(access((char*)pszWork, F_OK)) { + if(mkdir((char*)pszWork, mode) == 0) { + if(uid != (uid_t) -1 || gid != (gid_t) -1) { + /* we need to set owner/group */ + if(chown((char*)pszWork, uid, gid) != 0) + if(bFailOnChownFail) + bErr = 1; + /* silently ignore if configured + * to do so. + */ + } + } else + bErr = 1; + if(bErr) { + int eSave = errno; + free(pszWork); + errno = eSave; + return -1; + } + } + *p = '/'; + } + free(pszWork); + return 0; +} + + +/* execute a program with a single argument + * returns child pid if everything ok, 0 on failure. if + * it fails, errno is set. if it fails after the fork(), the caller + * can not be notfied for obvious reasons. if bwait is set to 1, + * the code waits until the child terminates - that potentially takes + * a lot of time. + * implemented 2007-07-20 rgerhards + */ +int execProg(uchar *program, int bWait, uchar *arg) +{ + int pid; + int sig; + struct sigaction sigAct; + + dbgprintf("exec program '%s' with param '%s'\n", program, arg); + pid = fork(); + if (pid < 0) { + return 0; + } + + if(pid) { /* Parent */ + if(bWait) + if(waitpid(pid, NULL, 0) == -1) + if(errno != ECHILD) { + /* we do not use logerror(), because + * that might bring us into an endless + * loop. At some time, we may + * reconsider this behaviour. + */ + dbgprintf("could not wait on child after executing '%s'", + (char*)program); + } + return pid; + } + /* Child */ + alarm(0); /* create a clean environment before we exec the real child */ + + memset(&sigAct, 0, sizeof(sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + + for(sig = 1 ; sig < NSIG ; ++sig) + sigaction(sig, &sigAct, NULL); + + execlp((char*)program, (char*) program, (char*)arg, NULL); + /* In the long term, it's a good idea to implement some enhanced error + * checking here. However, it can not easily be done. For starters, we + * may run into endless loops if we log to syslog. The next problem is + * that output is typically not seen by the user. For the time being, + * we use no error reporting, which is quite consitent with the old + * system() way of doing things. rgerhards, 2007-07-20 + */ + perror("exec"); + exit(1); /* not much we can do in this case */ +} + + +/* skip over whitespace in a standard C string. The + * provided pointer is advanced to the first non-whitespace + * charater or the \0 byte, if there is none. It is never + * moved past the \0. + */ +void skipWhiteSpace(uchar **pp) +{ + register uchar *p; + + assert(pp != NULL); + assert(*pp != NULL); + + p = *pp; + while(*p && isspace((int) *p)) + ++p; + *pp = p; +} + + +/* generate a file name from four parts: + * /. + * If number is negative, it is not used. If any of the strings is + * NULL, an empty string is used instead. Length must be provided. + * lNumDigits is the minimum number of digits that lNum should have. This + * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will + * result in "0003" being used inside the file name. Set lNumDigits to 0 + * to use as few space as possible. + * rgerhards, 2008-01-03 + */ +rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, + size_t lenFName, long lNum, int lNumDigits) +{ + DEFiRet; + uchar *pName; + uchar *pNameWork; + size_t lenName; + uchar szBuf[128]; /* buffer for number */ + char szFmtBuf[32]; /* buffer for snprintf format */ + size_t lenBuf; + + if(lNum < 0) { + szBuf[0] = '\0'; + lenBuf = 0; + } else { + if(lNumDigits > 0) { + snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits); + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum); + } else + lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum); + } + + lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */ + if((pName = malloc(sizeof(uchar) * lenName)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + /* got memory, now construct string */ + memcpy(pName, pDirName, lenDirName); + pNameWork = pName + lenDirName; + *pNameWork++ = '/'; + memcpy(pNameWork, pFName, lenFName); + pNameWork += lenFName; + if(lenBuf > 0) { + memcpy(pNameWork, szBuf, lenBuf); + pNameWork += lenBuf; + } + *pNameWork = '\0'; + + *ppName = pName; + +finalize_it: + RETiRet; +} + +/* get the number of digits required to represent a given number. We use an + * iterative approach as we do not like to draw in the floating point + * library just for log(). -- rgerhards, 2008-01-10 + */ +int getNumberDigits(long lNum) +{ + int iDig; + + if(lNum == 0) + iDig = 1; + else + for(iDig = 0 ; lNum != 0 ; ++iDig) + lNum /= 10; + + return iDig; +} + + +/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait() + * rgerhards, 2008-01-14 + */ +rsRetVal +timeoutComp(struct timespec *pt, long iTimeout) +{ + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, pt); + pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ + if(pt->tv_nsec > 999999999) { /* overrun? */ + pt->tv_nsec -= 1000000000; + } + pt->tv_sec += iTimeout / 1000; + return RS_RET_OK; /* so far, this is static... */ +} + + +/* This function is kind of the reverse of timeoutComp() - it takes an absolute + * timeout value and computes how far this is in the future. If the value is already + * in the past, 0 is returned. The return value is in ms. + * rgerhards, 2008-01-25 + */ +long +timeoutVal(struct timespec *pt) +{ + struct timespec t; + long iTimeout; + + assert(pt != NULL); + /* compute timeout */ + clock_gettime(CLOCK_REALTIME, &t); + iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; + iTimeout += (pt->tv_sec - t.tv_sec) * 1000; + + if(iTimeout < 0) + iTimeout = 0; + + return iTimeout; +} + + +/* cancellation cleanup handler - frees provided mutex + * rgerhards, 2008-01-14 + */ +void +mutexCancelCleanup(void *arg) +{ + BEGINfunc + assert(arg != NULL); + d_pthread_mutex_unlock((pthread_mutex_t*) arg); + ENDfunc +} + + +/* rsSleep() - a fairly portable way to to sleep. It + * will wake up when + * a) the wake-time is over + * rgerhards, 2008-01-28 + */ +void +srSleep(int iSeconds, int iuSeconds) +{ + struct timeval tvSelectTimeout; + + BEGINfunc + tvSelectTimeout.tv_sec = iSeconds; + tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ + select(0, NULL, NULL, NULL, &tvSelectTimeout); + ENDfunc +} + + +/* From varmojfekoj's mail on why he provided rs_strerror_r(): + * There are two problems with strerror_r(): + * I see you've rewritten some of the code which calls it to use only + * the supplied buffer; unfortunately the GNU implementation sometimes + * doesn't use the buffer at all and returns a pointer to some + * immutable string instead, as noted in the man page. + * + * The other problem is that on some systems strerror_r() has a return + * type of int. + * + * So I've written a wrapper function rs_strerror_r(), which should + * take care of all this and be used instead. + * + * Added 2008-01-30 + */ +char *rs_strerror_r(int errnum, char *buf, size_t buflen) { +#ifdef __hpux + char *pszErr; + pszErr = strerror(errnum); + snprintf(buf, buflen, "%s", pszErr); +#else +# ifdef STRERROR_R_CHAR_P + char *p = strerror_r(errnum, buf, buflen); + if (p != buf) { + strncpy(buf, p, buflen); + buf[buflen - 1] = '\0'; + } +# else + strerror_r(errnum, buf, buflen); +# endif +#endif /* #ifdef __hpux */ + return buf; +} + + +/* Decode a symbolic name to a numeric value + */ +int decodeSyslogName(uchar *name, syslogName_t *codetab) +{ + register syslogName_t *c; + register uchar *p; + uchar buf[80]; + + ASSERT(name != NULL); + ASSERT(codetab != NULL); + + dbgprintf("symbolic name: %s", name); + if (isdigit((int) *name)) + { + dbgprintf("\n"); + return (atoi((char*) name)); + } + strncpy((char*) buf, (char*) name, 79); + for (p = buf; *p; p++) + if (isupper((int) *p)) + *p = tolower((int) *p); + for (c = codetab; c->c_name; c++) + if (!strcmp((char*) buf, (char*) c->c_name)) + { + dbgprintf(" ==> %d\n", c->c_val); + return (c->c_val); + } + return (-1); +} + + +/* vim:set ai: + */ diff --git a/runtime/stringbuf.c b/runtime/stringbuf.c new file mode 100644 index 00000000..93d1e1ef --- /dev/null +++ b/runtime/stringbuf.c @@ -0,0 +1,1080 @@ +/* This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * Please see syslogd.c for license information. + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * begun 2005-09-07 rgerhards + * + * Copyright (C) 2007-2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include +#include +#include +#include +#include "rsyslog.h" +#include "stringbuf.h" +#include "srUtils.h" +#include "regexp.h" +#include "obj.h" + + +/* ################################################################# * + * private members * + * ################################################################# */ + +/* static data */ +DEFobjCurrIf(obj) +DEFobjCurrIf(regexp) + +/* ################################################################# * + * public members * + * ################################################################# */ + + +rsRetVal rsCStrConstruct(cstr_t **ppThis) +{ + DEFiRet; + cstr_t *pThis; + + ASSERT(ppThis != NULL); + + if((pThis = (cstr_t*) calloc(1, sizeof(cstr_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + rsSETOBJTYPE(pThis, OIDrsCStr); + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + pThis->iBufSize = 0; + pThis->iStrLen = 0; + pThis->iAllocIncrement = RS_STRINGBUF_ALLOC_INCREMENT; + *ppThis = pThis; + +finalize_it: + RETiRet; +} + + +/* construct from sz string + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = strlen((char*)(char *) sz); + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, sz, pThis->iStrLen); + + *ppThis = pThis; + +finalize_it: + RETiRet; +} + +/* construct from CStr object. only the counted string is + * copied, not the szString. + * rgerhards 2005-10-18 + */ +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom) +{ + DEFiRet; + cstr_t *pThis; + + assert(ppThis != NULL); + rsCHECKVALIDOBJECT(pFrom, OIDrsCStr); + + CHKiRet(rsCStrConstruct(&pThis)); + + pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen; + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + + /* copy properties */ + memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen); + + *ppThis = pThis; +finalize_it: + RETiRet; +} + + +void rsCStrDestruct(cstr_t **ppThis) +{ + cstr_t *pThis = *ppThis; + + /* rgerhards 2005-10-19: The free of pBuf was contained in conditional compilation. + * The code was only compiled if STRINGBUF_TRIM_ALLOCSIZE was set to 1. I honestly + * do not know why it was so, I think it was an artifact. Anyhow, I have changed this + * now. Should there any issue occur, this comment hopefully will shed some light + * on what happened. I re-verified, and this function has never before been called + * by anyone. So changing it can have no impact for obvious reasons... + * + * rgerhards, 2008-02-20: I changed the interface to the new calling conventions, where + * the destructor receives a pointer to the object, so that it can set it to NULL. + */ + if(pThis->pBuf != NULL) { + free(pThis->pBuf); + } + + if(pThis->pszBuf != NULL) { + free(pThis->pszBuf); + } + + RSFREEOBJ(pThis); + *ppThis = NULL; +} + + +/* extend the string buffer if its size is insufficient. + * Param iMinNeeded is the minumum free space needed. If it is larger + * than the default alloc increment, space for at least this amount is + * allocated. In practice, a bit more is allocated because we envision that + * some more characters may be added after these. + * rgerhards, 2008-01-07 + */ +static rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded) +{ + DEFiRet; + uchar *pNewBuf; + size_t iNewSize; + + /* first compute the new size needed */ + if(iMinNeeded > pThis->iAllocIncrement) { + /* we allocate "n" iAllocIncrements. Usually, that should + * leave some room after the absolutely needed one. It also + * reduces memory fragmentation. Note that all of this are + * integer operations (very important to understand what is + * going on)! Parenthesis are for better readibility. + */ + iNewSize = ((iMinNeeded / pThis->iAllocIncrement) + 1) * pThis->iAllocIncrement; + } else { + iNewSize = pThis->iBufSize + pThis->iAllocIncrement; + } + iNewSize += pThis->iBufSize; /* add current size */ + + /* and then allocate and copy over */ + /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */ + if((pNewBuf = (uchar*) malloc(iNewSize * sizeof(uchar))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + memcpy(pNewBuf, pThis->pBuf, pThis->iBufSize); + pThis->iBufSize = iNewSize; + if(pThis->pBuf != NULL) { + free(pThis->pBuf); + } + pThis->pBuf = pNewBuf; + +finalize_it: + RETiRet; +} + + +/* append a string of known length. In this case, we make sure we do at most + * one additional memory allocation. + * I optimized this function to use memcpy(), among others. Consider it a + * rewrite (which may be good to know in case of bugs) -- rgerhards, 2008-01-07 + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen) +{ + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(psz != NULL); + + /* does the string fit? */ + if(pThis->iStrLen + iStrLen > pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */ + } + + /* ok, now we always have sufficient continues memory to do a memcpy() */ + memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen); + pThis->iStrLen += iStrLen; + +finalize_it: + RETiRet; +} + + +/* changed to be a wrapper to rsCStrAppendStrWithLen() so that + * we can save some time when we have the length but do not + * need to change existing code. + * rgerhards, 2007-07-03 + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz) +{ + return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz)); +} + + +/* append the contents of one cstr_t object to another + * rgerhards, 2008-02-25 + */ +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) +{ + return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen); +} + + +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) +{ + DEFiRet; + uchar szBuf[32]; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i)); + + iRet = rsCStrAppendStr(pThis, szBuf); +finalize_it: + RETiRet; +} + + +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c) +{ + DEFiRet; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen >= pThis->iBufSize) { + CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */ + } + + /* ok, when we reach this, we have sufficient memory */ + *(pThis->pBuf + pThis->iStrLen++) = c; + + /* check if we need to invalidate an sz representation! */ + if(pThis->pszBuf != NULL) { + free(pThis->pszBuf); + pThis->pszBuf = NULL; + } + +finalize_it: + RETiRet; +} + + +/* Sets the string object to the classigal sz-string provided. + * Any previously stored vlaue is discarded. If a NULL pointer + * the the new value (pszNew) is provided, an empty string is + * created (this is NOT an error!). Property iAllocIncrement is + * not modified by this function. + * rgerhards, 2005-10-18 + */ +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->pBuf != NULL) + free(pThis->pBuf); + if(pThis->pszBuf != NULL) + free(pThis->pszBuf); + if(pszNew == NULL) { + pThis->iStrLen = 0; + pThis->iBufSize = 0; + pThis->pBuf = NULL; + pThis->pszBuf = NULL; + } else { + pThis->iStrLen = strlen((char*)pszNew); + pThis->iBufSize = pThis->iStrLen; + pThis->pszBuf = NULL; + /* iAllocIncrement is NOT modified! */ + + /* now save the new value */ + if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { + RSFREEOBJ(pThis); + return RS_RET_OUT_OF_MEMORY; + } + + /* we do NOT need to copy the \0! */ + memcpy(pThis->pBuf, pszNew, pThis->iStrLen); + } + + return RS_RET_OK; +} + +/* Converts the CStr object to a classical sz string and returns that. + * Same restrictions as in rsCStrGetSzStr() applies (see there!). This + * function here guarantees that a valid string is returned, even if + * the CStr object currently holds a NULL pointer string buffer. If so, + * "" is returned. + * rgerhards 2005-10-19 + * WARNING: The returned pointer MUST NOT be freed, as it may be + * obtained from that constant memory pool (in case of NULL!) + */ +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + if(pThis->pBuf == NULL) + return (uchar*) ""; + else + return rsCStrGetSzStr(pThis); +} + + +/* Converts the CStr object to a classical zero-terminated C string + * and returns that string. The caller must not free it and must not + * destroy the CStr object as long as the ascii string is used. + * This function may return NULL, if the string is currently NULL. This + * is a feature, not a bug. If you need non-NULL in any case, use + * rsCStrGetSzStrNoNULL() instead. + * rgerhards, 2005-09-15 + */ +uchar* rsCStrGetSzStr(cstr_t *pThis) +{ + size_t i; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->pBuf != NULL) + if(pThis->pszBuf == NULL) { + /* we do not yet have a usable sz version - so create it... */ + if((pThis->pszBuf = malloc((pThis->iStrLen + 1) * sizeof(uchar))) == NULL) { + /* TODO: think about what to do - so far, I have no bright + * idea... rgerhards 2005-09-07 + */ + } + else { /* we can create the sz String */ + /* now copy it while doing a sanity check. The string might contain a + * \0 byte. There is no way how a sz string can handle this. For + * the time being, we simply replace it with space - something that + * could definitely be improved (TODO). + * 2005-09-15 rgerhards + */ + for(i = 0 ; i < pThis->iStrLen ; ++i) { + if(pThis->pBuf[i] == '\0') + pThis->pszBuf[i] = ' '; + else + pThis->pszBuf[i] = pThis->pBuf[i]; + } + /* write terminator... */ + pThis->pszBuf[i] = '\0'; + } + } + + return(pThis->pszBuf); +} + + +/* Converts the CStr object to a classical zero-terminated C string, + * returns that string and destroys the CStr object. The returned string + * MUST be freed by the caller. The function might return NULL if + * no memory can be allocated. + * + * TODO: + * This function should at some time become special. The base idea is to + * add one extra byte to the end of the regular buffer, so that we can + * convert it to an szString without the need to copy. The extra memory + * footprint is not hefty, but the performance gain is potentially large. + * To get it done now, I am not doing the optimiziation right now. + * rgerhards, 2005-09-07 + * + * rgerhards, 2007-09-04: I have changed the interface of this function. It now + * returns an rsRetVal, so that we can communicate back if we have an error. + * Using the standard method is much better than returning NULL. Secondly, NULL + * was not actually an error - it was in indication if the string was empty. + * This was needed in some parts of the code, in others not. I have now added + * a second parameter to specify what the caller needs. I hope these changes + * will make it less likely that the function is called incorrectly, what + * previously happend quite often and was the cause of a number of program + * aborts. So the parameters are now: + * pointer to the object, pointer to string-pointer to receive string and + * bRetNULL: 0 - must not return NULL on empty string, return "" in that + * case, 1 - return NULL instead of an empty string. + * PLEASE NOTE: the caller must free the memory returned in ppSz in any case + * (except, of course, if it is NULL). + */ +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL) +{ + DEFiRet; + uchar* pRetBuf; + + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(ppSz != NULL); + assert(bRetNULL == 0 || bRetNULL == 1); + + if(pThis->pBuf == NULL) { + if(bRetNULL == 0) { + if((pRetBuf = malloc(sizeof(uchar))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + *pRetBuf = '\0'; + } else { + pRetBuf = NULL; + } + } else + pRetBuf = rsCStrGetSzStr(pThis); + + *ppSz = pRetBuf; + +finalize_it: + /* We got it, now free the object ourselfs. Please note + * that we can NOT use the rsCStrDestruct function as it would + * also free the sz String buffer, which we pass on to the user. + */ + if(pThis->pBuf != NULL) + free(pThis->pBuf); + RSFREEOBJ(pThis); + + RETiRet; +} + + +#if STRINGBUF_TRIM_ALLOCSIZE == 1 + /* Only in this mode, we need to trim the string. To do + * so, we must allocate a new buffer of the exact + * string size, and then copy the old one over. + */ + /* WARNING + * STRINGBUF_TRIM_ALLOCSIZE can, in theory, be used to trim + * memory buffers. This part of the code was inherited from + * liblogging (where it is used in a different context) but + * never put to use in rsyslog. The reason is that it is hardly + * imaginable where the extra performance cost is worth the save + * in memory alloc. Then Anders Blomdel rightfully pointed out that + * the code does not work at all - and nobody even know that it + * probably shouldn't. Rather than removing, I deciced to somewhat + * fix the code, so that this feature may be enabled if somebody + * really has a need for it. Be warned, however, that I NEVER + * tested the fix. So if you intend to use this feature, you must + * do full testing before you rely on it. -- rgerhards, 2008-02-12 + */ +rsRetVal rsCStrFinish(cstr_t __attribute__((unused)) *pThis) +{ + DEFiRet; + uchar* pBuf; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if((pBuf = malloc((pThis->iStrLen) * sizeof(uchar))) == NULL) + { /* OK, in this case we use the previous buffer. At least + * we have it ;) + */ + } + else + { /* got the new buffer, so let's use it */ + memcpy(pBuf, pThis->pBuf, pThis->iStrLen); + pThis->pBuf = pBuf; + } + + RETiRet; +} +#endif /* #if STRINGBUF_TRIM_ALLOCSIZE == 1 */ + + +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(iNewIncrement > 0); + + pThis->iAllocIncrement = iNewIncrement; +} + + +/* return the length of the current string + * 2005-09-09 rgerhards + * Please note: this is only a function in a debug build. + * For release builds, it is a macro defined in stringbuf.h. + * This is due to performance reasons. + */ +#ifndef NDEBUG +int rsCStrLen(cstr_t *pThis) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + return(pThis->iStrLen); +} +#endif + +/* Truncate characters from the end of the string. + * rgerhards 2005-09-15 + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) +{ + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(pThis->iStrLen < nTrunc) + return RS_TRUNCAT_TOO_LARGE; + + pThis->iStrLen -= nTrunc; + + if(pThis->pszBuf != NULL) { + /* in this case, we adjust the psz representation + * by writing a new \0 terminator - this is by far + * the fastest way and outweights the additional memory + * required. 2005-9-19 rgerhards. + */ + pThis->pszBuf[pThis->iStrLen] = '\0'; + } + + return RS_RET_OK; +} + +/* Trim trailing whitespace from a given string + */ +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis) +{ + register int i; + register uchar *pC; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + i = pThis->iStrLen; + pC = pThis->pBuf + i - 1; + while(i > 0 && isspace((int)*pC)) { + --pC; + --i; + } + /* i now is the new string length! */ + pThis->iStrLen = i; + + return RS_RET_OK; +} + +/* compare two string objects - works like strcmp(), but operates + * on CStr objects. Please note that this version here is + * faster in the majority of cases, simply because it can + * rely on StrLen. + * rgerhards 2005-09-19 + * fixed bug, in which only the last byte was actually compared + * in equal-size strings. + * rgerhards, 2005-09-26 + */ +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + rsCHECKVALIDOBJECT(pCS2, OIDrsCStr); + if(pCS1->iStrLen == pCS2->iStrLen) + if(pCS1->iStrLen == 0) + return 0; /* zero-sized string are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < pCS1->iStrLen ; ++i) { + if(pCS1->pBuf[i] != pCS2->pBuf[i]) + return pCS1->pBuf[i] - pCS2->pBuf[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + } + else + return pCS1->iStrLen - pCS2->iStrLen; +} + + +/* check if a sz-type string starts with a CStr object. This function + * is initially written to support the "startswith" property-filter + * comparison operation. Maybe it also has other needs. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-10-19 + */ +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register int i; + int iMax; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(iLenSz >= pCS1->iStrLen) { + /* we need to checkusing pCS1->iStrLen charactes at maximum, thus + * we move it to iMax. + */ + iMax = pCS1->iStrLen; + if(iMax == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iMax ; ++i) { + if(psz[i] != pCS1->pBuf[i]) + return psz[i] - pCS1->pBuf[i]; + } + /* if we arrive here, the string actually starts with pCS1 */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* check if a CStr object starts with a sz-type string. + * This functions is modelled after the strcmp() series, thus a + * return value of 0 indicates that the string starts with the + * sequence while -1 indicates it does not! + * rgerhards 2005-09-26 + */ +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i] != psz[i]) + return pCS1->pBuf[i] - psz[i]; + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + + +/* The same as rsCStrStartsWithSzStr(), but does a case-insensitive + * comparison. TODO: consolidate the two. + * rgerhards 2008-02-28 + */ +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + register size_t i; + + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen >= iLenSz) { + /* we are using iLenSz below, because we need to check + * iLenSz characters at maximum (start with!) + */ + if(iLenSz == 0) + return 0; /* yes, it starts with a zero-sized string ;) */ + else { /* we now have something to compare, so let's do it... */ + for(i = 0 ; i < iLenSz ; ++i) { + if(tolower(pCS1->pBuf[i]) != tolower(psz[i])) + return tolower(pCS1->pBuf[i]) - tolower(psz[i]); + } + /* if we arrive here, the string actually starts with psz */ + return 0; + } + } + else + return -1; /* pCS1 is less then psz */ +} + +/* check if a CStr object matches a regex. + * msamia@redhat.com 2007-07-12 + * @return returns 0 if matched + * bug: doesn't work for CStr containing \0 + * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there + * never is a \0 *inside* a property string. + * Note that the function returns -1 if regexp functionality is not available. + * TODO: change calling interface! -- rgerhards, 2008-03-07 + */ +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz) +{ + regex_t preq; + int ret; + + BEGINfunc + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + regexp.regcomp(&preq, (char*) rsCStrGetSzStr(pCS1), 0); + ret = regexp.regexec(&preq, (char*) psz, 0, NULL, 0); + regexp.regfree(&preq); + } else { + ret = 1; /* simulate "not found" */ + } + + ENDfunc + return ret; +} + + +/* compare a rsCStr object with a classical sz string. This function + * is almost identical to rsCStrZsStrCmp(), but it also takes an offset + * to the CStr object from where the comparison is to start. + * I have thought quite a while if it really makes sense to more or + * less duplicate the code. After all, if you call it with an offset of + * zero, the functionality is exactly the same. So it looks natural to + * just have a single function. However, supporting the offset requires + * some (few) additional integer operations. While they are few, they + * happen at places in the code that is run very frequently. All in all, + * I have opted for performance and thus duplicated the code. I hope + * this is a good, or at least acceptable, compromise. + * rgerhards, 2005-09-26 + * This function also has an offset-pointer which allows to + * specify *where* the compare operation should begin in + * the CStr. If everything is to be compared, it must be set + * to 0. If some leading bytes are to be skipped, it must be set + * to the first index that is to be compared. It must not be + * set higher than the string length (this is considered a + * program bug and will lead to unpredictable results and program aborts). + * rgerhards 2005-09-26 + */ +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) +{ + BEGINfunc + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(iOffset < pCS1->iStrLen); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if((pCS1->iStrLen - iOffset) == iLenSz) { + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) { + return 0; /* zero-sized strings are equal ;) */ + ENDfunc + } else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i+iOffset] != psz[i]) + return pCS1->pBuf[i+iOffset] - psz[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + ENDfunc + } + } + else { + return pCS1->iStrLen - iOffset - iLenSz; + ENDfunc + } +} + + +/* Converts a string to a number. If the string dos not contain a number, + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber) +{ + DEFiRet; + number_t n; + int bIsNegative; + size_t i; + + ASSERT(pStr != NULL); + ASSERT(pNumber != NULL); + + if(pStr->iStrLen == 0) { + /* can be converted to 0! (by convention) */ + pNumber = 0; + FINALIZE; + } + + /* first skip whitespace (if present) */ + for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) { + /*DO NOTHING*/ + } + + /* we have a string, so let's check its syntax */ + if(pStr->pBuf[i] == '+') { + ++i; /* skip that char */ + bIsNegative = 0; + } else if(pStr->pBuf[0] == '-') { + ++i; /* skip that char */ + bIsNegative = 1; + } else { + bIsNegative = 0; + } + + /* TODO: octal? hex? */ + n = 0; + while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) { + n = n * 10 + pStr->pBuf[i] * 10; + ++i; + } + + if(i < pStr->iStrLen) /* non-digits before end of string? */ + ABORT_FINALIZE(RS_RET_NOT_A_NUMBER); + + if(bIsNegative) + n *= -1; + + /* we got it, so return the number */ + *pNumber = n; + +finalize_it: + RETiRet; +} + + +/* Converts a string to a boolen. First tries to convert to a number. If + * that succeeds, we are done (number is then used as boolean value). If + * that fails, we look if the string is "yes" or "true". If so, a value + * of 1 is returned. In all other cases, a value of 0 is returned. Please + * note that we do not have a specific boolean type, so we return a number. + * so, these are + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToBool(cstr_t *pStr, number_t *pBool) +{ + DEFiRet; + + ASSERT(pStr != NULL); + ASSERT(pBool != NULL); + + iRet = rsCStrConvertToNumber(pStr, pBool); + + if(iRet != RS_RET_NOT_A_NUMBER) { + FINALIZE; /* in any case, we have nothing left to do */ + } + + /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */ + if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) { + *pBool = 1; + } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) { + *pBool = 1; + } else { + *pBool = 0; + } + +finalize_it: + RETiRet; +} + + +/* compare a rsCStr object with a classical sz string. + * Just like rsCStrCStrCmp, just for a different data type. + * There must not only the sz string but also its length be + * provided. If the caller does not know the length he can + * call with + * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz)); + * we are not doing the strlen((char*)) ourselfs as the caller might + * already know the length and in such cases we can save the + * overhead of doing it one more time (strelen() is costly!). + * The bottom line is that the provided length MUST be correct! + * The to sz string pointer must not be NULL! + * rgerhards 2005-09-26 + */ +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) +{ + rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); + assert(psz != NULL); + assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ + if(pCS1->iStrLen == iLenSz) + /* we are using iLenSz below, because the lengths + * are equal and iLenSz is faster to access + */ + if(iLenSz == 0) + return 0; /* zero-sized strings are equal ;) */ + else { /* we now have two non-empty strings of equal + * length, so we need to actually check if they + * are equal. + */ + register size_t i; + for(i = 0 ; i < iLenSz ; ++i) { + if(pCS1->pBuf[i] != psz[i]) + return pCS1->pBuf[i] - psz[i]; + } + /* if we arrive here, the strings are equal */ + return 0; + } + else + return pCS1->iStrLen - iLenSz; +} + + +/* Locate the first occurence of this rsCStr object inside a standard sz string. + * Returns the offset (0-bound) of this first occurrence. If not found, -1 is + * returned. Both parameters MUST be given (NULL is not allowed). + * rgerhards 2005-09-19 + */ +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(*(pComp + iCheck) != *(pThis->pBuf + iCheck)) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +/* This is the same as rsCStrLocateInSzStr(), but does a case-insensitve + * comparison. + * TODO: over time, consolidate the two. + * rgerhards, 2008-02-28 + */ +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz) +{ + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + assert(sz != NULL); + + if(pThis->iStrLen == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = strlen((char*)sz) - pThis->iStrLen; + + bFound = 0; + i = 0; + while(i <= iMax && !bFound) { + size_t iCheck; + uchar *pComp = sz + i; + for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) + if(tolower(*(pComp + iCheck)) != tolower(*(pThis->pBuf + iCheck))) + break; + if(iCheck == pThis->iStrLen) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} + + +#if 0 /* read comment below why this is commented out. In short: for future use! */ +/* locate the first occurence of a standard sz string inside a rsCStr object. + * Returns the offset (0-bound) of this first occurrence. If not found, -1 is + * returned. + * rgerhards 2005-09-19 + * WARNING: I accidently created this function (I later noticed I didn't relly + * need it... I will not remove the function, as it probably is useful + * some time later. However, it is not fully tested, so start with testing + * it before you put it to first use). + */ +int rsCStrLocateSzStr(cstr_t *pThis, uchar *sz) +{ + int iLenSz; + int i; + int iMax; + int bFound; + rsCHECKVALIDOBJECT(pThis, OIDrsCStr); + + if(sz == NULL) + return 0; + + iLenSz = strlen((char*)sz); + if(iLenSz == 0) + return 0; + + /* compute the largest index where a match could occur - after all, + * the to-be-located string must be able to be present in the + * searched string (it needs its size ;)). + */ + iMax = pThis->iStrLen - iLenSz; + + bFound = 0; + i = 0; + while(i < iMax && !bFound) { + int iCheck; + uchar *pComp = pThis->pBuf + i; + for(iCheck = 0 ; iCheck < iLenSz ; ++iCheck) + if(*(pComp + iCheck) != *(sz + iCheck)) + break; + if(iCheck == iLenSz) + bFound = 1; /* found! - else it wouldn't be equal */ + else + ++i; /* on to the next try */ + } + + return(bFound ? i : -1); +} +#endif /* end comment out */ + + +/* our exit function. TODO: remove once converted to a class + * rgerhards, 2008-03-11 + */ +rsRetVal strExit() +{ + DEFiRet; + objRelease(regexp, LM_REGEXP_FILENAME); + RETiRet; +} + + +/* our init function. TODO: remove once converted to a class + */ +rsRetVal strInit() +{ + DEFiRet; + CHKiRet(objGetObjInterface(&obj)); + +finalize_it: + RETiRet; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + * vi:set ai: + */ diff --git a/runtime/stringbuf.h b/runtime/stringbuf.h new file mode 100644 index 00000000..c1966449 --- /dev/null +++ b/runtime/stringbuf.h @@ -0,0 +1,169 @@ +/*! \file stringbuf.h + * \brief The counted string object + * + * This is the byte-counted string class for rsyslog. It is a replacement + * for classical \0 terminated string functions. We introduce it in + * the hope it will make the program more secure, obtain some performance + * and, most importantly, lay they foundation for syslog-protocol, which + * requires strings to be able to handle embedded \0 characters. + * + * \author Rainer Gerhards + * \date 2005-09-07 + * Initial version begun. + * + * All functions in this "class" start with rsCStr (rsyslog Counted String). + * Copyright 2005 + * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef _STRINGBUF_H_INCLUDED__ +#define _STRINGBUF_H_INCLUDED__ 1 + +/** + * The dynamic string buffer object. + */ +typedef struct cstr_s +{ +#ifndef NDEBUG + rsObjID OID; /**< object ID */ +#endif + uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */ + uchar *pszBuf; /**< pointer to the sz version of the string (after it has been created )*/ + size_t iBufSize; /**< current maximum size of the string buffer */ + size_t iStrLen; /**< length of the string in characters. */ + size_t iAllocIncrement; /**< the amount of bytes the string should be expanded if it needs to */ +} cstr_t; + + +/** + * Construct a rsCStr object. + */ +rsRetVal rsCStrConstruct(cstr_t **ppThis); +rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz); +rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom); + +/** + * Destruct the string buffer object. + */ +void rsCStrDestruct(cstr_t **ppThis); + +/** + * Append a character to an existing string. If necessary, the + * method expands the string buffer. + * + * \param c Character to append to string. + */ +rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c); + +/** + * Truncate "n" number of characters from the end of the + * string. The buffer remains unchanged, just the + * string length is manipulated. This is for performance + * reasons. + */ +rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc); + +rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis); + +/** + * Append a string to the buffer. For performance reasons, + * use rsCStrAppenStrWithLen() if you know the length. + * + * \param psz pointer to string to be appended. Must not be NULL. + */ +rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz); + +/** + * Append a string to the buffer. + * + * \param psz pointer to string to be appended. Must not be NULL. + * \param iStrLen the length of the string pointed to by psz + */ +rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen); + +/** + * Set a new allocation incremet. This will influence + * the allocation the next time the string will be expanded. + * It can be set and changed at any time. If done immediately + * after custructing the StrB object, this will also be + * the inital allocation. + * + * \param iNewIncrement The new increment size + * + * \note It is possible to use a very low increment, e.g. 1 byte. + * This can generate a considerable overhead. We highly + * advise not to use an increment below 32 bytes, except + * if you are very well aware why you are doing it ;) + */ +void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement); +#define rsCStrGetAllocIncrement(pThis) ((pThis)->iAllocIncrement) + +/** + * Append an integer to the string. No special formatting is + * done. + */ +rsRetVal rsCStrAppendInt(cstr_t *pThis, long i); + + +rsRetVal strExit(void); /* TODO: remove once we have a real object interface! */ +uchar* rsCStrGetSzStr(cstr_t *pThis); +uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis); +rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew); +rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL); +int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2); +int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz); +int rsCStrLocateSzStr(cstr_t *pCStr, uchar *sz); +int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz); +int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); +int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz); +rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); +rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); +rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend); + +/* now come inline-like functions */ +#ifdef NDEBUG +# define rsCStrLen(x) ((int)((x)->iStrLen)) +#else + int rsCStrLen(cstr_t *pThis); +#endif + +#if STRINGBUF_TRIM_ALLOCSIZE != 1 +/* This is the normal case (see comment in rsCStrFinish!). In those cases, the function + * simply needs to do nothing, so that we can save us the function call. + * rgerhards, 2008-02-12 + */ +# define rsCStrFinish(pThis) RS_RET_OK +#else + /** + * Finish the string buffer dynamic allocation. + */ + rsRetVal rsCStrFinish(cstr_t *pThis); +#endif + +#define rsCStrGetBufBeg(x) ((x)->pBuf) + +rsRetVal strInit(); +rsRetVal strExit(); + +#endif /* single include */ diff --git a/runtime/syslogd-types.h b/runtime/syslogd-types.h new file mode 100644 index 00000000..be0dfdd8 --- /dev/null +++ b/runtime/syslogd-types.h @@ -0,0 +1,103 @@ +/* syslogd-type.h + * This file contains type defintions used by syslogd and its modules. + * It is a required input for any module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#ifndef SYSLOGD_TYPES_INCLUDED +#define SYSLOGD_TYPES_INCLUDED 1 + +#include "stringbuf.h" +#include +#if HAVE_SYSLOG_H +#include +#endif + +#define FALSE 0 +#define TRUE 1 + +#ifdef UT_NAMESIZE +# define UNAMESZ UT_NAMESIZE /* length of a login name */ +#else +# define UNAMESZ 8 /* length of a login name */ +#endif +#define MAXUNAMES 20 /* maximum number of user names */ +#define MAXFNAME 200 /* max file pathname length */ + +#define _DB_MAXDBLEN 128 /* maximum number of db */ +#define _DB_MAXUNAMELEN 128 /* maximum number of user name */ +#define _DB_MAXPWDLEN 128 /* maximum number of user's pass */ +#define _DB_DELAYTIMEONERROR 20 /* If an error occur we stop logging until + a delayed time is over */ + + +/* we define features of the syslog code. This features can be used + * to check if modules are compatible with them - and possible other + * applications I do not yet envision. -- rgerhards, 2007-07-24 + */ +typedef enum _syslogFeature { + sFEATURERepeatedMsgReduction = 1 +} syslogFeature; + +/* we define our own facility and severities */ +/* facility and severity codes */ +typedef struct _syslogCode { + char *c_name; + int c_val; +} syslogCODE; + +/* values for host comparisons specified with host selector blocks + * (+host, -host). rgerhards 2005-10-18. + */ +enum _EHostnameCmpMode { + HN_NO_COMP = 0, /* do not compare hostname */ + HN_COMP_MATCH = 1, /* hostname must match */ + HN_COMP_NOMATCH = 2 /* hostname must NOT match */ +}; +typedef enum _EHostnameCmpMode EHostnameCmpMode; + +/* rgerhards 2004-11-11: the following structure represents + * a time as it is used in syslog. + */ +struct syslogTime { + int timeType; /* 0 - unitinialized , 1 - RFC 3164, 2 - syslog-protocol */ + int year; + int month; + int day; + int hour; /* 24 hour clock */ + int minute; + int second; + int secfrac; /* fractional seconds (must be 32 bit!) */ + int secfracPrecision; + char OffsetMode; /* UTC offset + or - */ + char OffsetHour; /* UTC offset in hours */ + int OffsetMinute; /* UTC offset in minutes */ + /* full UTC offset minutes = OffsetHours*60 + OffsetMinute. Then use + * OffsetMode to know the direction. + */ +}; +typedef struct syslogTime syslogTime_t; + +#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */ +/* vi:set ai: + */ diff --git a/srUtils.c b/srUtils.c deleted file mode 100644 index fa451b7e..00000000 --- a/srUtils.c +++ /dev/null @@ -1,506 +0,0 @@ -/**\file srUtils.c - * \brief General utilties that fit nowhere else. - * - * The namespace for this file is "srUtil". - * - * \author Rainer Gerhards - * \date 2003-09-09 - * Coding begun. - * - * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "liblogging-stub.h" -#define TRUE 1 -#define FALSE 0 -#include "srUtils.h" -#include "syslogd.h" -#include "obj.h" - - -/* here we host some syslog specific names. There currently is no better place - * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14 - */ -syslogName_t syslogPriNames[] = { - {"alert", LOG_ALERT}, - {"crit", LOG_CRIT}, - {"debug", LOG_DEBUG}, - {"emerg", LOG_EMERG}, - {"err", LOG_ERR}, - {"error", LOG_ERR}, /* DEPRECATED */ - {"info", LOG_INFO}, - {"none", INTERNAL_NOPRI}, /* INTERNAL */ - {"notice", LOG_NOTICE}, - {"panic", LOG_EMERG}, /* DEPRECATED */ - {"warn", LOG_WARNING}, /* DEPRECATED */ - {"warning", LOG_WARNING}, - {"*", TABLE_ALLPRI}, - {NULL, -1} -}; - -#ifndef LOG_AUTHPRIV -# define LOG_AUTHPRIV LOG_AUTH -#endif -syslogName_t syslogFacNames[] = { - {"auth", LOG_AUTH}, - {"authpriv", LOG_AUTHPRIV}, - {"cron", LOG_CRON}, - {"daemon", LOG_DAEMON}, - {"kern", LOG_KERN}, - {"lpr", LOG_LPR}, - {"mail", LOG_MAIL}, - {"mark", LOG_MARK}, /* INTERNAL */ - {"news", LOG_NEWS}, - {"security", LOG_AUTH}, /* DEPRECATED */ - {"syslog", LOG_SYSLOG}, - {"user", LOG_USER}, - {"uucp", LOG_UUCP}, -#if defined(LOG_FTP) - {"ftp", LOG_FTP}, -#endif - {"local0", LOG_LOCAL0}, - {"local1", LOG_LOCAL1}, - {"local2", LOG_LOCAL2}, - {"local3", LOG_LOCAL3}, - {"local4", LOG_LOCAL4}, - {"local5", LOG_LOCAL5}, - {"local6", LOG_LOCAL6}, - {"local7", LOG_LOCAL7}, - {NULL, -1}, -}; - -/* ################################################################# * - * private members * - * ################################################################# */ - -/* As this is not a "real" object, there won't be any private - * members in this file. - */ - -/* ################################################################# * - * public members * - * ################################################################# */ - -rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv) -{ - int i; - int bIsNegative; - char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */ - - assert(pBuf != NULL); - assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */ - - if(iToConv < 0) - { - bIsNegative = TRUE; - iToConv *= -1; - } - else - bIsNegative = FALSE; - - /* first generate a string with the digits in the reverse direction */ - i = 0; - do - { - szBuf[i++] = iToConv % 10 + '0'; - iToConv /= 10; - } while(iToConv > 0); /* warning: do...while()! */ - --i; /* undo last increment - we were pointing at NEXT location */ - - /* make sure we are within bounds... */ - if(i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */ - return RS_RET_PROVIDED_BUFFER_TOO_SMALL; - - /* then move it to the right direction... */ - if(bIsNegative == TRUE) - *pBuf++ = '-'; - while(i >= 0) - *pBuf++ = szBuf[i--]; - *pBuf = '\0'; /* terminate it!!! */ - - return RS_RET_OK; -} - -uchar *srUtilStrDup(uchar *pOld, size_t len) -{ - uchar *pNew; - - assert(pOld != NULL); - - if((pNew = malloc(len + 1)) != NULL) - memcpy(pNew, pOld, len + 1); - - return pNew; -} - - -/* creates a path recursively - * Return 0 on success, -1 otherwise. On failure, errno - * hold the last OS error. - * Param "mode" holds the mode that all non-existing directories - * are to be created with. - */ -int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, - uid_t uid, gid_t gid, int bFailOnChownFail) -{ - uchar *p; - uchar *pszWork; - size_t len; - int bErr = 0; - - assert(szFile != NULL); - assert(lenFile > 0); - - len = lenFile + 1; /* add one for '\0'-byte */ - if((pszWork = malloc(sizeof(uchar) * len)) == NULL) - return -1; - memcpy(pszWork, szFile, len); - for(p = pszWork+1 ; *p ; p++) - if(*p == '/') { - /* temporarily terminate string, create dir and go on */ - *p = '\0'; - if(access((char*)pszWork, F_OK)) { - if(mkdir((char*)pszWork, mode) == 0) { - if(uid != (uid_t) -1 || gid != (gid_t) -1) { - /* we need to set owner/group */ - if(chown((char*)pszWork, uid, gid) != 0) - if(bFailOnChownFail) - bErr = 1; - /* silently ignore if configured - * to do so. - */ - } - } else - bErr = 1; - if(bErr) { - int eSave = errno; - free(pszWork); - errno = eSave; - return -1; - } - } - *p = '/'; - } - free(pszWork); - return 0; -} - - -/* execute a program with a single argument - * returns child pid if everything ok, 0 on failure. if - * it fails, errno is set. if it fails after the fork(), the caller - * can not be notfied for obvious reasons. if bwait is set to 1, - * the code waits until the child terminates - that potentially takes - * a lot of time. - * implemented 2007-07-20 rgerhards - */ -int execProg(uchar *program, int bWait, uchar *arg) -{ - int pid; - int sig; - struct sigaction sigAct; - - dbgprintf("exec program '%s' with param '%s'\n", program, arg); - pid = fork(); - if (pid < 0) { - return 0; - } - - if(pid) { /* Parent */ - if(bWait) - if(waitpid(pid, NULL, 0) == -1) - if(errno != ECHILD) { - /* we do not use logerror(), because - * that might bring us into an endless - * loop. At some time, we may - * reconsider this behaviour. - */ - dbgprintf("could not wait on child after executing '%s'", - (char*)program); - } - return pid; - } - /* Child */ - alarm(0); /* create a clean environment before we exec the real child */ - - memset(&sigAct, 0, sizeof(sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = SIG_DFL; - - for(sig = 1 ; sig < NSIG ; ++sig) - sigaction(sig, &sigAct, NULL); - - execlp((char*)program, (char*) program, (char*)arg, NULL); - /* In the long term, it's a good idea to implement some enhanced error - * checking here. However, it can not easily be done. For starters, we - * may run into endless loops if we log to syslog. The next problem is - * that output is typically not seen by the user. For the time being, - * we use no error reporting, which is quite consitent with the old - * system() way of doing things. rgerhards, 2007-07-20 - */ - perror("exec"); - exit(1); /* not much we can do in this case */ -} - - -/* skip over whitespace in a standard C string. The - * provided pointer is advanced to the first non-whitespace - * charater or the \0 byte, if there is none. It is never - * moved past the \0. - */ -void skipWhiteSpace(uchar **pp) -{ - register uchar *p; - - assert(pp != NULL); - assert(*pp != NULL); - - p = *pp; - while(*p && isspace((int) *p)) - ++p; - *pp = p; -} - - -/* generate a file name from four parts: - * /. - * If number is negative, it is not used. If any of the strings is - * NULL, an empty string is used instead. Length must be provided. - * lNumDigits is the minimum number of digits that lNum should have. This - * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will - * result in "0003" being used inside the file name. Set lNumDigits to 0 - * to use as few space as possible. - * rgerhards, 2008-01-03 - */ -rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, - size_t lenFName, long lNum, int lNumDigits) -{ - DEFiRet; - uchar *pName; - uchar *pNameWork; - size_t lenName; - uchar szBuf[128]; /* buffer for number */ - char szFmtBuf[32]; /* buffer for snprintf format */ - size_t lenBuf; - - if(lNum < 0) { - szBuf[0] = '\0'; - lenBuf = 0; - } else { - if(lNumDigits > 0) { - snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits); - lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum); - } else - lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum); - } - - lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */ - if((pName = malloc(sizeof(uchar) * lenName)) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - /* got memory, now construct string */ - memcpy(pName, pDirName, lenDirName); - pNameWork = pName + lenDirName; - *pNameWork++ = '/'; - memcpy(pNameWork, pFName, lenFName); - pNameWork += lenFName; - if(lenBuf > 0) { - memcpy(pNameWork, szBuf, lenBuf); - pNameWork += lenBuf; - } - *pNameWork = '\0'; - - *ppName = pName; - -finalize_it: - RETiRet; -} - -/* get the number of digits required to represent a given number. We use an - * iterative approach as we do not like to draw in the floating point - * library just for log(). -- rgerhards, 2008-01-10 - */ -int getNumberDigits(long lNum) -{ - int iDig; - - if(lNum == 0) - iDig = 1; - else - for(iDig = 0 ; lNum != 0 ; ++iDig) - lNum /= 10; - - return iDig; -} - - -/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait() - * rgerhards, 2008-01-14 - */ -rsRetVal -timeoutComp(struct timespec *pt, long iTimeout) -{ - assert(pt != NULL); - /* compute timeout */ - clock_gettime(CLOCK_REALTIME, pt); - pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */ - if(pt->tv_nsec > 999999999) { /* overrun? */ - pt->tv_nsec -= 1000000000; - } - pt->tv_sec += iTimeout / 1000; - return RS_RET_OK; /* so far, this is static... */ -} - - -/* This function is kind of the reverse of timeoutComp() - it takes an absolute - * timeout value and computes how far this is in the future. If the value is already - * in the past, 0 is returned. The return value is in ms. - * rgerhards, 2008-01-25 - */ -long -timeoutVal(struct timespec *pt) -{ - struct timespec t; - long iTimeout; - - assert(pt != NULL); - /* compute timeout */ - clock_gettime(CLOCK_REALTIME, &t); - iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000; - iTimeout += (pt->tv_sec - t.tv_sec) * 1000; - - if(iTimeout < 0) - iTimeout = 0; - - return iTimeout; -} - - -/* cancellation cleanup handler - frees provided mutex - * rgerhards, 2008-01-14 - */ -void -mutexCancelCleanup(void *arg) -{ - BEGINfunc - assert(arg != NULL); - d_pthread_mutex_unlock((pthread_mutex_t*) arg); - ENDfunc -} - - -/* rsSleep() - a fairly portable way to to sleep. It - * will wake up when - * a) the wake-time is over - * rgerhards, 2008-01-28 - */ -void -srSleep(int iSeconds, int iuSeconds) -{ - struct timeval tvSelectTimeout; - - BEGINfunc - tvSelectTimeout.tv_sec = iSeconds; - tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */ - select(0, NULL, NULL, NULL, &tvSelectTimeout); - ENDfunc -} - - -/* From varmojfekoj's mail on why he provided rs_strerror_r(): - * There are two problems with strerror_r(): - * I see you've rewritten some of the code which calls it to use only - * the supplied buffer; unfortunately the GNU implementation sometimes - * doesn't use the buffer at all and returns a pointer to some - * immutable string instead, as noted in the man page. - * - * The other problem is that on some systems strerror_r() has a return - * type of int. - * - * So I've written a wrapper function rs_strerror_r(), which should - * take care of all this and be used instead. - * - * Added 2008-01-30 - */ -char *rs_strerror_r(int errnum, char *buf, size_t buflen) { -#ifdef __hpux - char *pszErr; - pszErr = strerror(errnum); - snprintf(buf, buflen, "%s", pszErr); -#else -# ifdef STRERROR_R_CHAR_P - char *p = strerror_r(errnum, buf, buflen); - if (p != buf) { - strncpy(buf, p, buflen); - buf[buflen - 1] = '\0'; - } -# else - strerror_r(errnum, buf, buflen); -# endif -#endif /* #ifdef __hpux */ - return buf; -} - - -/* Decode a symbolic name to a numeric value - */ -int decodeSyslogName(uchar *name, syslogName_t *codetab) -{ - register syslogName_t *c; - register uchar *p; - uchar buf[80]; - - ASSERT(name != NULL); - ASSERT(codetab != NULL); - - dbgprintf("symbolic name: %s", name); - if (isdigit((int) *name)) - { - dbgprintf("\n"); - return (atoi((char*) name)); - } - strncpy((char*) buf, (char*) name, 79); - for (p = buf; *p; p++) - if (isupper((int) *p)) - *p = tolower((int) *p); - for (c = codetab; c->c_name; c++) - if (!strcmp((char*) buf, (char*) c->c_name)) - { - dbgprintf(" ==> %d\n", c->c_val); - return (c->c_val); - } - return (-1); -} - - -/* vim:set ai: - */ diff --git a/srUtils.h b/srUtils.h deleted file mode 100644 index ebd6518f..00000000 --- a/srUtils.h +++ /dev/null @@ -1,125 +0,0 @@ -/*! \file srUtils.h - * \brief General, small utilities that fit nowhere else. - * - * \author Rainer Gerhards - * \date 2003-09-09 - * Coding begun. - * - * Copyright 2003-2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef __SRUTILS_H_INCLUDED__ -#define __SRUTILS_H_INCLUDED__ 1 - - -/* syslog names */ -#ifndef LOG_MAKEPRI -# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) -#endif -#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ -#define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */ -#define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ -#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ - -typedef struct syslogName_s { - char *c_name; - int c_val; -} syslogName_t; - -extern syslogName_t syslogPriNames[]; -extern syslogName_t syslogFacNames[]; - -/** - * A reimplementation of itoa(), as this is not available - * on all platforms. We used the chance to make an interface - * that fits us well, so it is no longer plain itoa(). - * - * This method works with the US-ASCII alphabet. If you port this - * to e.g. EBCDIC, you need to make a small adjustment. Keep in mind, - * that on the wire it MUST be US-ASCII, so basically all you need - * to do is replace the constant '0' with 0x30 ;). - * - * \param pBuf Caller-provided buffer that will receive the - * generated ASCII string. - * - * \param iLenBuf Length of the caller-provided buffer. - * - * \param iToConv The integer to be converted. - */ -rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv); - -/** - * A method to duplicate a string for which the length is known. - * Len must be the length in characters WITHOUT the trailing - * '\0' byte. - * rgerhards, 2007-07-10 - */ -unsigned char *srUtilStrDup(unsigned char *pOld, size_t len); -/** - * A method to create a directory and all its missing parents for - * a given file name. Please not that the rightmost element is - * considered to be a file name and thus NO directory is being created - * for it. - * added 2007-07-17 by rgerhards - */ -int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, uid_t uid, gid_t gid, int bFailOnChown); -int execProg(uchar *program, int bWait, uchar *arg); -void skipWhiteSpace(uchar **pp); -rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName, - size_t lenFName, long lNum, int lNumDigits); -int getNumberDigits(long lNum); -rsRetVal timeoutComp(struct timespec *pt, long iTimeout); -long timeoutVal(struct timespec *pt); -void mutexCancelCleanup(void *arg); -void srSleep(int iSeconds, int iuSeconds); -char *rs_strerror_r(int errnum, char *buf, size_t buflen); -int decodeSyslogName(uchar *name, syslogName_t *codetab); - -/* mutex operations */ -/* some macros to cancel-safe lock a mutex (it will automatically be released - * when the thread is cancelled. This needs to be done as macros because - * pthread_cleanup_push sometimes is a macro that can not be used inside a function. - * It's a bit ugly, but works well... rgerhards, 2008-01-20 - */ -#define DEFVARS_mutex_cancelsafeLock int iCancelStateSave -#define mutex_cancelsafe_lock(mut) \ - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ - d_pthread_mutex_lock(mut); \ - pthread_cleanup_push(mutexCancelCleanup, mut); \ - pthread_setcancelstate(iCancelStateSave, NULL); -#define mutex_cancelsafe_unlock(mut) pthread_cleanup_pop(1) - -/* some useful constants */ -#define MUTEX_ALREADY_LOCKED 0 -#define LOCK_MUTEX 1 -#define DEFVARS_mutexProtection\ - int iCancelStateSave; \ - int bLockedOpIsLocked=0 -#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \ - if(bMustLock == LOCK_MUTEX) { \ - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave); \ - d_pthread_mutex_lock(mut); \ - bLockedOpIsLocked = 1; \ - } -#define END_MTX_PROTECTED_OPERATIONS(mut) \ - if(bLockedOpIsLocked) { \ - d_pthread_mutex_unlock(mut); \ - pthread_setcancelstate(iCancelStateSave, NULL); \ - } -#endif diff --git a/stringbuf.c b/stringbuf.c deleted file mode 100644 index 4254d5bd..00000000 --- a/stringbuf.c +++ /dev/null @@ -1,1079 +0,0 @@ -/* This is the byte-counted string class for rsyslog. It is a replacement - * for classical \0 terminated string functions. We introduce it in - * the hope it will make the program more secure, obtain some performance - * and, most importantly, lay they foundation for syslog-protocol, which - * requires strings to be able to handle embedded \0 characters. - * Please see syslogd.c for license information. - * All functions in this "class" start with rsCStr (rsyslog Counted String). - * begun 2005-09-07 rgerhards - * - * Copyright (C) 2007 by Rainer Gerhards and Adiscon GmbH - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include -#include -#include -#include -#include -#include "rsyslog.h" -#include "stringbuf.h" -#include "srUtils.h" -#include "regexp.h" -#include "obj.h" - - -/* ################################################################# * - * private members * - * ################################################################# */ - -/* static data */ -DEFobjCurrIf(obj) -DEFobjCurrIf(regexp) - -/* ################################################################# * - * public members * - * ################################################################# */ - - -rsRetVal rsCStrConstruct(cstr_t **ppThis) -{ - DEFiRet; - cstr_t *pThis; - - ASSERT(ppThis != NULL); - - if((pThis = (cstr_t*) calloc(1, sizeof(cstr_t))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - rsSETOBJTYPE(pThis, OIDrsCStr); - pThis->pBuf = NULL; - pThis->pszBuf = NULL; - pThis->iBufSize = 0; - pThis->iStrLen = 0; - pThis->iAllocIncrement = RS_STRINGBUF_ALLOC_INCREMENT; - *ppThis = pThis; - -finalize_it: - RETiRet; -} - - -/* construct from sz string - * rgerhards 2005-09-15 - */ -rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz) -{ - DEFiRet; - cstr_t *pThis; - - assert(ppThis != NULL); - - CHKiRet(rsCStrConstruct(&pThis)); - - pThis->iBufSize = pThis->iStrLen = strlen((char*)(char *) sz); - if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { - RSFREEOBJ(pThis); - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - /* we do NOT need to copy the \0! */ - memcpy(pThis->pBuf, sz, pThis->iStrLen); - - *ppThis = pThis; - -finalize_it: - RETiRet; -} - -/* construct from CStr object. only the counted string is - * copied, not the szString. - * rgerhards 2005-10-18 - */ -rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom) -{ - DEFiRet; - cstr_t *pThis; - - assert(ppThis != NULL); - rsCHECKVALIDOBJECT(pFrom, OIDrsCStr); - - CHKiRet(rsCStrConstruct(&pThis)); - - pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen; - if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { - RSFREEOBJ(pThis); - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - - /* copy properties */ - memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen); - - *ppThis = pThis; -finalize_it: - RETiRet; -} - - -void rsCStrDestruct(cstr_t **ppThis) -{ - cstr_t *pThis = *ppThis; - - /* rgerhards 2005-10-19: The free of pBuf was contained in conditional compilation. - * The code was only compiled if STRINGBUF_TRIM_ALLOCSIZE was set to 1. I honestly - * do not know why it was so, I think it was an artifact. Anyhow, I have changed this - * now. Should there any issue occur, this comment hopefully will shed some light - * on what happened. I re-verified, and this function has never before been called - * by anyone. So changing it can have no impact for obvious reasons... - * - * rgerhards, 2008-02-20: I changed the interface to the new calling conventions, where - * the destructor receives a pointer to the object, so that it can set it to NULL. - */ - if(pThis->pBuf != NULL) { - free(pThis->pBuf); - } - - if(pThis->pszBuf != NULL) { - free(pThis->pszBuf); - } - - RSFREEOBJ(pThis); - *ppThis = NULL; -} - - -/* extend the string buffer if its size is insufficient. - * Param iMinNeeded is the minumum free space needed. If it is larger - * than the default alloc increment, space for at least this amount is - * allocated. In practice, a bit more is allocated because we envision that - * some more characters may be added after these. - * rgerhards, 2008-01-07 - */ -static rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded) -{ - DEFiRet; - uchar *pNewBuf; - size_t iNewSize; - - /* first compute the new size needed */ - if(iMinNeeded > pThis->iAllocIncrement) { - /* we allocate "n" iAllocIncrements. Usually, that should - * leave some room after the absolutely needed one. It also - * reduces memory fragmentation. Note that all of this are - * integer operations (very important to understand what is - * going on)! Parenthesis are for better readibility. - */ - iNewSize = ((iMinNeeded / pThis->iAllocIncrement) + 1) * pThis->iAllocIncrement; - } else { - iNewSize = pThis->iBufSize + pThis->iAllocIncrement; - } - iNewSize += pThis->iBufSize; /* add current size */ - - /* and then allocate and copy over */ - /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */ - if((pNewBuf = (uchar*) malloc(iNewSize * sizeof(uchar))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - memcpy(pNewBuf, pThis->pBuf, pThis->iBufSize); - pThis->iBufSize = iNewSize; - if(pThis->pBuf != NULL) { - free(pThis->pBuf); - } - pThis->pBuf = pNewBuf; - -finalize_it: - RETiRet; -} - - -/* append a string of known length. In this case, we make sure we do at most - * one additional memory allocation. - * I optimized this function to use memcpy(), among others. Consider it a - * rewrite (which may be good to know in case of bugs) -- rgerhards, 2008-01-07 - */ -rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen) -{ - DEFiRet; - - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - assert(psz != NULL); - - /* does the string fit? */ - if(pThis->iStrLen + iStrLen > pThis->iBufSize) { - CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */ - } - - /* ok, now we always have sufficient continues memory to do a memcpy() */ - memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen); - pThis->iStrLen += iStrLen; - -finalize_it: - RETiRet; -} - - -/* changed to be a wrapper to rsCStrAppendStrWithLen() so that - * we can save some time when we have the length but do not - * need to change existing code. - * rgerhards, 2007-07-03 - */ -rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz) -{ - return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz)); -} - - -/* append the contents of one cstr_t object to another - * rgerhards, 2008-02-25 - */ -rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) -{ - return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen); -} - - -rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) -{ - DEFiRet; - uchar szBuf[32]; - - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i)); - - iRet = rsCStrAppendStr(pThis, szBuf); -finalize_it: - RETiRet; -} - - -rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c) -{ - DEFiRet; - - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if(pThis->iStrLen >= pThis->iBufSize) { - CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */ - } - - /* ok, when we reach this, we have sufficient memory */ - *(pThis->pBuf + pThis->iStrLen++) = c; - - /* check if we need to invalidate an sz representation! */ - if(pThis->pszBuf != NULL) { - free(pThis->pszBuf); - pThis->pszBuf = NULL; - } - -finalize_it: - RETiRet; -} - - -/* Sets the string object to the classigal sz-string provided. - * Any previously stored vlaue is discarded. If a NULL pointer - * the the new value (pszNew) is provided, an empty string is - * created (this is NOT an error!). Property iAllocIncrement is - * not modified by this function. - * rgerhards, 2005-10-18 - */ -rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew) -{ - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if(pThis->pBuf != NULL) - free(pThis->pBuf); - if(pThis->pszBuf != NULL) - free(pThis->pszBuf); - if(pszNew == NULL) { - pThis->iStrLen = 0; - pThis->iBufSize = 0; - pThis->pBuf = NULL; - pThis->pszBuf = NULL; - } else { - pThis->iStrLen = strlen((char*)pszNew); - pThis->iBufSize = pThis->iStrLen; - pThis->pszBuf = NULL; - /* iAllocIncrement is NOT modified! */ - - /* now save the new value */ - if((pThis->pBuf = (uchar*) malloc(sizeof(uchar) * pThis->iStrLen)) == NULL) { - RSFREEOBJ(pThis); - return RS_RET_OUT_OF_MEMORY; - } - - /* we do NOT need to copy the \0! */ - memcpy(pThis->pBuf, pszNew, pThis->iStrLen); - } - - return RS_RET_OK; -} - -/* Converts the CStr object to a classical sz string and returns that. - * Same restrictions as in rsCStrGetSzStr() applies (see there!). This - * function here guarantees that a valid string is returned, even if - * the CStr object currently holds a NULL pointer string buffer. If so, - * "" is returned. - * rgerhards 2005-10-19 - * WARNING: The returned pointer MUST NOT be freed, as it may be - * obtained from that constant memory pool (in case of NULL!) - */ -uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis) -{ - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - if(pThis->pBuf == NULL) - return (uchar*) ""; - else - return rsCStrGetSzStr(pThis); -} - - -/* Converts the CStr object to a classical zero-terminated C string - * and returns that string. The caller must not free it and must not - * destroy the CStr object as long as the ascii string is used. - * This function may return NULL, if the string is currently NULL. This - * is a feature, not a bug. If you need non-NULL in any case, use - * rsCStrGetSzStrNoNULL() instead. - * rgerhards, 2005-09-15 - */ -uchar* rsCStrGetSzStr(cstr_t *pThis) -{ - size_t i; - - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if(pThis->pBuf != NULL) - if(pThis->pszBuf == NULL) { - /* we do not yet have a usable sz version - so create it... */ - if((pThis->pszBuf = malloc((pThis->iStrLen + 1) * sizeof(uchar))) == NULL) { - /* TODO: think about what to do - so far, I have no bright - * idea... rgerhards 2005-09-07 - */ - } - else { /* we can create the sz String */ - /* now copy it while doing a sanity check. The string might contain a - * \0 byte. There is no way how a sz string can handle this. For - * the time being, we simply replace it with space - something that - * could definitely be improved (TODO). - * 2005-09-15 rgerhards - */ - for(i = 0 ; i < pThis->iStrLen ; ++i) { - if(pThis->pBuf[i] == '\0') - pThis->pszBuf[i] = ' '; - else - pThis->pszBuf[i] = pThis->pBuf[i]; - } - /* write terminator... */ - pThis->pszBuf[i] = '\0'; - } - } - - return(pThis->pszBuf); -} - - -/* Converts the CStr object to a classical zero-terminated C string, - * returns that string and destroys the CStr object. The returned string - * MUST be freed by the caller. The function might return NULL if - * no memory can be allocated. - * - * TODO: - * This function should at some time become special. The base idea is to - * add one extra byte to the end of the regular buffer, so that we can - * convert it to an szString without the need to copy. The extra memory - * footprint is not hefty, but the performance gain is potentially large. - * To get it done now, I am not doing the optimiziation right now. - * rgerhards, 2005-09-07 - * - * rgerhards, 2007-09-04: I have changed the interface of this function. It now - * returns an rsRetVal, so that we can communicate back if we have an error. - * Using the standard method is much better than returning NULL. Secondly, NULL - * was not actually an error - it was in indication if the string was empty. - * This was needed in some parts of the code, in others not. I have now added - * a second parameter to specify what the caller needs. I hope these changes - * will make it less likely that the function is called incorrectly, what - * previously happend quite often and was the cause of a number of program - * aborts. So the parameters are now: - * pointer to the object, pointer to string-pointer to receive string and - * bRetNULL: 0 - must not return NULL on empty string, return "" in that - * case, 1 - return NULL instead of an empty string. - * PLEASE NOTE: the caller must free the memory returned in ppSz in any case - * (except, of course, if it is NULL). - */ -rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL) -{ - DEFiRet; - uchar* pRetBuf; - - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - assert(ppSz != NULL); - assert(bRetNULL == 0 || bRetNULL == 1); - - if(pThis->pBuf == NULL) { - if(bRetNULL == 0) { - if((pRetBuf = malloc(sizeof(uchar))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - *pRetBuf = '\0'; - } else { - pRetBuf = NULL; - } - } else - pRetBuf = rsCStrGetSzStr(pThis); - - *ppSz = pRetBuf; - -finalize_it: - /* We got it, now free the object ourselfs. Please note - * that we can NOT use the rsCStrDestruct function as it would - * also free the sz String buffer, which we pass on to the user. - */ - if(pThis->pBuf != NULL) - free(pThis->pBuf); - RSFREEOBJ(pThis); - - RETiRet; -} - - -#if STRINGBUF_TRIM_ALLOCSIZE == 1 - /* Only in this mode, we need to trim the string. To do - * so, we must allocate a new buffer of the exact - * string size, and then copy the old one over. - */ - /* WARNING - * STRINGBUF_TRIM_ALLOCSIZE can, in theory, be used to trim - * memory buffers. This part of the code was inherited from - * liblogging (where it is used in a different context) but - * never put to use in rsyslog. The reason is that it is hardly - * imaginable where the extra performance cost is worth the save - * in memory alloc. Then Anders Blomdel rightfully pointed out that - * the code does not work at all - and nobody even know that it - * probably shouldn't. Rather than removing, I deciced to somewhat - * fix the code, so that this feature may be enabled if somebody - * really has a need for it. Be warned, however, that I NEVER - * tested the fix. So if you intend to use this feature, you must - * do full testing before you rely on it. -- rgerhards, 2008-02-12 - */ -rsRetVal rsCStrFinish(cstr_t __attribute__((unused)) *pThis) -{ - DEFiRet; - uchar* pBuf; - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if((pBuf = malloc((pThis->iStrLen) * sizeof(uchar))) == NULL) - { /* OK, in this case we use the previous buffer. At least - * we have it ;) - */ - } - else - { /* got the new buffer, so let's use it */ - memcpy(pBuf, pThis->pBuf, pThis->iStrLen); - pThis->pBuf = pBuf; - } - - RETiRet; -} -#endif /* #if STRINGBUF_TRIM_ALLOCSIZE == 1 */ - - -void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement) -{ - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - assert(iNewIncrement > 0); - - pThis->iAllocIncrement = iNewIncrement; -} - - -/* return the length of the current string - * 2005-09-09 rgerhards - * Please note: this is only a function in a debug build. - * For release builds, it is a macro defined in stringbuf.h. - * This is due to performance reasons. - */ -#ifndef NDEBUG -int rsCStrLen(cstr_t *pThis) -{ - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - return(pThis->iStrLen); -} -#endif - -/* Truncate characters from the end of the string. - * rgerhards 2005-09-15 - */ -rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) -{ - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if(pThis->iStrLen < nTrunc) - return RS_TRUNCAT_TOO_LARGE; - - pThis->iStrLen -= nTrunc; - - if(pThis->pszBuf != NULL) { - /* in this case, we adjust the psz representation - * by writing a new \0 terminator - this is by far - * the fastest way and outweights the additional memory - * required. 2005-9-19 rgerhards. - */ - pThis->pszBuf[pThis->iStrLen] = '\0'; - } - - return RS_RET_OK; -} - -/* Trim trailing whitespace from a given string - */ -rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis) -{ - register int i; - register uchar *pC; - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - i = pThis->iStrLen; - pC = pThis->pBuf + i - 1; - while(i > 0 && isspace((int)*pC)) { - --pC; - --i; - } - /* i now is the new string length! */ - pThis->iStrLen = i; - - return RS_RET_OK; -} - -/* compare two string objects - works like strcmp(), but operates - * on CStr objects. Please note that this version here is - * faster in the majority of cases, simply because it can - * rely on StrLen. - * rgerhards 2005-09-19 - * fixed bug, in which only the last byte was actually compared - * in equal-size strings. - * rgerhards, 2005-09-26 - */ -int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2) -{ - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - rsCHECKVALIDOBJECT(pCS2, OIDrsCStr); - if(pCS1->iStrLen == pCS2->iStrLen) - if(pCS1->iStrLen == 0) - return 0; /* zero-sized string are equal ;) */ - else { /* we now have two non-empty strings of equal - * length, so we need to actually check if they - * are equal. - */ - register size_t i; - for(i = 0 ; i < pCS1->iStrLen ; ++i) { - if(pCS1->pBuf[i] != pCS2->pBuf[i]) - return pCS1->pBuf[i] - pCS2->pBuf[i]; - } - /* if we arrive here, the strings are equal */ - return 0; - } - else - return pCS1->iStrLen - pCS2->iStrLen; -} - - -/* check if a sz-type string starts with a CStr object. This function - * is initially written to support the "startswith" property-filter - * comparison operation. Maybe it also has other needs. - * This functions is modelled after the strcmp() series, thus a - * return value of 0 indicates that the string starts with the - * sequence while -1 indicates it does not! - * rgerhards 2005-10-19 - */ -int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) -{ - register int i; - int iMax; - - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - assert(psz != NULL); - assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ - if(iLenSz >= pCS1->iStrLen) { - /* we need to checkusing pCS1->iStrLen charactes at maximum, thus - * we move it to iMax. - */ - iMax = pCS1->iStrLen; - if(iMax == 0) - return 0; /* yes, it starts with a zero-sized string ;) */ - else { /* we now have something to compare, so let's do it... */ - for(i = 0 ; i < iMax ; ++i) { - if(psz[i] != pCS1->pBuf[i]) - return psz[i] - pCS1->pBuf[i]; - } - /* if we arrive here, the string actually starts with pCS1 */ - return 0; - } - } - else - return -1; /* pCS1 is less then psz */ -} - - -/* check if a CStr object starts with a sz-type string. - * This functions is modelled after the strcmp() series, thus a - * return value of 0 indicates that the string starts with the - * sequence while -1 indicates it does not! - * rgerhards 2005-09-26 - */ -int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) -{ - register size_t i; - - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - assert(psz != NULL); - assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ - if(pCS1->iStrLen >= iLenSz) { - /* we are using iLenSz below, because we need to check - * iLenSz characters at maximum (start with!) - */ - if(iLenSz == 0) - return 0; /* yes, it starts with a zero-sized string ;) */ - else { /* we now have something to compare, so let's do it... */ - for(i = 0 ; i < iLenSz ; ++i) { - if(pCS1->pBuf[i] != psz[i]) - return pCS1->pBuf[i] - psz[i]; - } - /* if we arrive here, the string actually starts with psz */ - return 0; - } - } - else - return -1; /* pCS1 is less then psz */ -} - - -/* The same as rsCStrStartsWithSzStr(), but does a case-insensitive - * comparison. TODO: consolidate the two. - * rgerhards 2008-02-28 - */ -int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz) -{ - register size_t i; - - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - assert(psz != NULL); - assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ - if(pCS1->iStrLen >= iLenSz) { - /* we are using iLenSz below, because we need to check - * iLenSz characters at maximum (start with!) - */ - if(iLenSz == 0) - return 0; /* yes, it starts with a zero-sized string ;) */ - else { /* we now have something to compare, so let's do it... */ - for(i = 0 ; i < iLenSz ; ++i) { - if(tolower(pCS1->pBuf[i]) != tolower(psz[i])) - return tolower(pCS1->pBuf[i]) - tolower(psz[i]); - } - /* if we arrive here, the string actually starts with psz */ - return 0; - } - } - else - return -1; /* pCS1 is less then psz */ -} - -/* check if a CStr object matches a regex. - * msamia@redhat.com 2007-07-12 - * @return returns 0 if matched - * bug: doesn't work for CStr containing \0 - * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there - * never is a \0 *inside* a property string. - * Note that the function returns -1 if regexp functionality is not available. - * TODO: change calling interface! -- rgerhards, 2008-03-07 - */ -int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz) -{ - regex_t preq; - int ret; - - BEGINfunc - - if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { - regexp.regcomp(&preq, (char*) rsCStrGetSzStr(pCS1), 0); - ret = regexp.regexec(&preq, (char*) psz, 0, NULL, 0); - regexp.regfree(&preq); - } else { - ret = 1; /* simulate "not found" */ - } - - ENDfunc - return ret; -} - - -/* compare a rsCStr object with a classical sz string. This function - * is almost identical to rsCStrZsStrCmp(), but it also takes an offset - * to the CStr object from where the comparison is to start. - * I have thought quite a while if it really makes sense to more or - * less duplicate the code. After all, if you call it with an offset of - * zero, the functionality is exactly the same. So it looks natural to - * just have a single function. However, supporting the offset requires - * some (few) additional integer operations. While they are few, they - * happen at places in the code that is run very frequently. All in all, - * I have opted for performance and thus duplicated the code. I hope - * this is a good, or at least acceptable, compromise. - * rgerhards, 2005-09-26 - * This function also has an offset-pointer which allows to - * specify *where* the compare operation should begin in - * the CStr. If everything is to be compared, it must be set - * to 0. If some leading bytes are to be skipped, it must be set - * to the first index that is to be compared. It must not be - * set higher than the string length (this is considered a - * program bug and will lead to unpredictable results and program aborts). - * rgerhards 2005-09-26 - */ -int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) -{ - BEGINfunc - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - assert(iOffset < pCS1->iStrLen); - assert(psz != NULL); - assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ - if((pCS1->iStrLen - iOffset) == iLenSz) { - /* we are using iLenSz below, because the lengths - * are equal and iLenSz is faster to access - */ - if(iLenSz == 0) { - return 0; /* zero-sized strings are equal ;) */ - ENDfunc - } else { /* we now have two non-empty strings of equal - * length, so we need to actually check if they - * are equal. - */ - register size_t i; - for(i = 0 ; i < iLenSz ; ++i) { - if(pCS1->pBuf[i+iOffset] != psz[i]) - return pCS1->pBuf[i+iOffset] - psz[i]; - } - /* if we arrive here, the strings are equal */ - return 0; - ENDfunc - } - } - else { - return pCS1->iStrLen - iOffset - iLenSz; - ENDfunc - } -} - - -/* Converts a string to a number. If the string dos not contain a number, - * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. - * If all goes well, pNumber contains the number that the string was converted - * to. - */ -rsRetVal -rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber) -{ - DEFiRet; - number_t n; - int bIsNegative; - size_t i; - - ASSERT(pStr != NULL); - ASSERT(pNumber != NULL); - - if(pStr->iStrLen == 0) { - /* can be converted to 0! (by convention) */ - pNumber = 0; - FINALIZE; - } - - /* first skip whitespace (if present) */ - for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) { - /*DO NOTHING*/ - } - - /* we have a string, so let's check its syntax */ - if(pStr->pBuf[i] == '+') { - ++i; /* skip that char */ - bIsNegative = 0; - } else if(pStr->pBuf[0] == '-') { - ++i; /* skip that char */ - bIsNegative = 1; - } else { - bIsNegative = 0; - } - - /* TODO: octal? hex? */ - n = 0; - while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) { - n = n * 10 + pStr->pBuf[i] * 10; - ++i; - } - - if(i < pStr->iStrLen) /* non-digits before end of string? */ - ABORT_FINALIZE(RS_RET_NOT_A_NUMBER); - - if(bIsNegative) - n *= -1; - - /* we got it, so return the number */ - *pNumber = n; - -finalize_it: - RETiRet; -} - - -/* Converts a string to a boolen. First tries to convert to a number. If - * that succeeds, we are done (number is then used as boolean value). If - * that fails, we look if the string is "yes" or "true". If so, a value - * of 1 is returned. In all other cases, a value of 0 is returned. Please - * note that we do not have a specific boolean type, so we return a number. - * so, these are - * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. - * If all goes well, pNumber contains the number that the string was converted - * to. - */ -rsRetVal -rsCStrConvertToBool(cstr_t *pStr, number_t *pBool) -{ - DEFiRet; - - ASSERT(pStr != NULL); - ASSERT(pBool != NULL); - - iRet = rsCStrConvertToNumber(pStr, pBool); - - if(iRet != RS_RET_NOT_A_NUMBER) { - FINALIZE; /* in any case, we have nothing left to do */ - } - - /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */ - if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) { - *pBool = 1; - } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) { - *pBool = 1; - } else { - *pBool = 0; - } - -finalize_it: - RETiRet; -} - - -/* compare a rsCStr object with a classical sz string. - * Just like rsCStrCStrCmp, just for a different data type. - * There must not only the sz string but also its length be - * provided. If the caller does not know the length he can - * call with - * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz)); - * we are not doing the strlen((char*)) ourselfs as the caller might - * already know the length and in such cases we can save the - * overhead of doing it one more time (strelen() is costly!). - * The bottom line is that the provided length MUST be correct! - * The to sz string pointer must not be NULL! - * rgerhards 2005-09-26 - */ -int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) -{ - rsCHECKVALIDOBJECT(pCS1, OIDrsCStr); - assert(psz != NULL); - assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */ - if(pCS1->iStrLen == iLenSz) - /* we are using iLenSz below, because the lengths - * are equal and iLenSz is faster to access - */ - if(iLenSz == 0) - return 0; /* zero-sized strings are equal ;) */ - else { /* we now have two non-empty strings of equal - * length, so we need to actually check if they - * are equal. - */ - register size_t i; - for(i = 0 ; i < iLenSz ; ++i) { - if(pCS1->pBuf[i] != psz[i]) - return pCS1->pBuf[i] - psz[i]; - } - /* if we arrive here, the strings are equal */ - return 0; - } - else - return pCS1->iStrLen - iLenSz; -} - - -/* Locate the first occurence of this rsCStr object inside a standard sz string. - * Returns the offset (0-bound) of this first occurrence. If not found, -1 is - * returned. Both parameters MUST be given (NULL is not allowed). - * rgerhards 2005-09-19 - */ -int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz) -{ - int i; - int iMax; - int bFound; - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - assert(sz != NULL); - - if(pThis->iStrLen == 0) - return 0; - - /* compute the largest index where a match could occur - after all, - * the to-be-located string must be able to be present in the - * searched string (it needs its size ;)). - */ - iMax = strlen((char*)sz) - pThis->iStrLen; - - bFound = 0; - i = 0; - while(i <= iMax && !bFound) { - size_t iCheck; - uchar *pComp = sz + i; - for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) - if(*(pComp + iCheck) != *(pThis->pBuf + iCheck)) - break; - if(iCheck == pThis->iStrLen) - bFound = 1; /* found! - else it wouldn't be equal */ - else - ++i; /* on to the next try */ - } - - return(bFound ? i : -1); -} - - -/* This is the same as rsCStrLocateInSzStr(), but does a case-insensitve - * comparison. - * TODO: over time, consolidate the two. - * rgerhards, 2008-02-28 - */ -int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz) -{ - int i; - int iMax; - int bFound; - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - assert(sz != NULL); - - if(pThis->iStrLen == 0) - return 0; - - /* compute the largest index where a match could occur - after all, - * the to-be-located string must be able to be present in the - * searched string (it needs its size ;)). - */ - iMax = strlen((char*)sz) - pThis->iStrLen; - - bFound = 0; - i = 0; - while(i <= iMax && !bFound) { - size_t iCheck; - uchar *pComp = sz + i; - for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck) - if(tolower(*(pComp + iCheck)) != tolower(*(pThis->pBuf + iCheck))) - break; - if(iCheck == pThis->iStrLen) - bFound = 1; /* found! - else it wouldn't be equal */ - else - ++i; /* on to the next try */ - } - - return(bFound ? i : -1); -} - - -#if 0 /* read comment below why this is commented out. In short: for future use! */ -/* locate the first occurence of a standard sz string inside a rsCStr object. - * Returns the offset (0-bound) of this first occurrence. If not found, -1 is - * returned. - * rgerhards 2005-09-19 - * WARNING: I accidently created this function (I later noticed I didn't relly - * need it... I will not remove the function, as it probably is useful - * some time later. However, it is not fully tested, so start with testing - * it before you put it to first use). - */ -int rsCStrLocateSzStr(cstr_t *pThis, uchar *sz) -{ - int iLenSz; - int i; - int iMax; - int bFound; - rsCHECKVALIDOBJECT(pThis, OIDrsCStr); - - if(sz == NULL) - return 0; - - iLenSz = strlen((char*)sz); - if(iLenSz == 0) - return 0; - - /* compute the largest index where a match could occur - after all, - * the to-be-located string must be able to be present in the - * searched string (it needs its size ;)). - */ - iMax = pThis->iStrLen - iLenSz; - - bFound = 0; - i = 0; - while(i < iMax && !bFound) { - int iCheck; - uchar *pComp = pThis->pBuf + i; - for(iCheck = 0 ; iCheck < iLenSz ; ++iCheck) - if(*(pComp + iCheck) != *(sz + iCheck)) - break; - if(iCheck == iLenSz) - bFound = 1; /* found! - else it wouldn't be equal */ - else - ++i; /* on to the next try */ - } - - return(bFound ? i : -1); -} -#endif /* end comment out */ - - -/* our exit function. TODO: remove once converted to a class - * rgerhards, 2008-03-11 - */ -rsRetVal strExit() -{ - DEFiRet; - objRelease(regexp, LM_REGEXP_FILENAME); - RETiRet; -} - - -/* our init function. TODO: remove once converted to a class - */ -rsRetVal strInit() -{ - DEFiRet; - CHKiRet(objGetObjInterface(&obj)); - -finalize_it: - RETiRet; -} - - -/* - * Local variables: - * c-indent-level: 8 - * c-basic-offset: 8 - * tab-width: 8 - * End: - * vi:set ai: - */ diff --git a/stringbuf.h b/stringbuf.h deleted file mode 100644 index e44e86e1..00000000 --- a/stringbuf.h +++ /dev/null @@ -1,168 +0,0 @@ -/*! \file stringbuf.h - * \brief The counted string object - * - * This is the byte-counted string class for rsyslog. It is a replacement - * for classical \0 terminated string functions. We introduce it in - * the hope it will make the program more secure, obtain some performance - * and, most importantly, lay they foundation for syslog-protocol, which - * requires strings to be able to handle embedded \0 characters. - * - * \author Rainer Gerhards - * \date 2005-09-07 - * Initial version begun. - * - * All functions in this "class" start with rsCStr (rsyslog Counted String). - * Copyright 2005 - * Rainer Gerhards and Adiscon GmbH. All Rights Reserved. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef _STRINGBUF_H_INCLUDED__ -#define _STRINGBUF_H_INCLUDED__ 1 - -/** - * The dynamic string buffer object. - */ -typedef struct cstr_s -{ -#ifndef NDEBUG - rsObjID OID; /**< object ID */ -#endif - uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */ - uchar *pszBuf; /**< pointer to the sz version of the string (after it has been created )*/ - size_t iBufSize; /**< current maximum size of the string buffer */ - size_t iStrLen; /**< length of the string in characters. */ - size_t iAllocIncrement; /**< the amount of bytes the string should be expanded if it needs to */ -} cstr_t; - - -/** - * Construct a rsCStr object. - */ -rsRetVal rsCStrConstruct(cstr_t **ppThis); -rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz); -rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom); - -/** - * Destruct the string buffer object. - */ -void rsCStrDestruct(cstr_t **ppThis); - -/** - * Append a character to an existing string. If necessary, the - * method expands the string buffer. - * - * \param c Character to append to string. - */ -rsRetVal rsCStrAppendChar(cstr_t *pThis, uchar c); - -/** - * Truncate "n" number of characters from the end of the - * string. The buffer remains unchanged, just the - * string length is manipulated. This is for performance - * reasons. - */ -rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc); - -rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis); - -/** - * Append a string to the buffer. For performance reasons, - * use rsCStrAppenStrWithLen() if you know the length. - * - * \param psz pointer to string to be appended. Must not be NULL. - */ -rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz); - -/** - * Append a string to the buffer. - * - * \param psz pointer to string to be appended. Must not be NULL. - * \param iStrLen the length of the string pointed to by psz - */ -rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen); - -/** - * Set a new allocation incremet. This will influence - * the allocation the next time the string will be expanded. - * It can be set and changed at any time. If done immediately - * after custructing the StrB object, this will also be - * the inital allocation. - * - * \param iNewIncrement The new increment size - * - * \note It is possible to use a very low increment, e.g. 1 byte. - * This can generate a considerable overhead. We highly - * advise not to use an increment below 32 bytes, except - * if you are very well aware why you are doing it ;) - */ -void rsCStrSetAllocIncrement(cstr_t *pThis, int iNewIncrement); -#define rsCStrGetAllocIncrement(pThis) ((pThis)->iAllocIncrement) - -/** - * Append an integer to the string. No special formatting is - * done. - */ -rsRetVal rsCStrAppendInt(cstr_t *pThis, long i); - - -rsRetVal strExit(void); /* TODO: remove once we have a real object interface! */ -uchar* rsCStrGetSzStr(cstr_t *pThis); -uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis); -rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew); -rsRetVal rsCStrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL); -int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2); -int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz); -int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz); -int rsCStrLocateSzStr(cstr_t *pCStr, uchar *sz); -int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz); -int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz); -int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); -int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); -int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); -int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz); -rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); -rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); -rsRetVal rsCStrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend); - -/* now come inline-like functions */ -#ifdef NDEBUG -# define rsCStrLen(x) ((int)((x)->iStrLen)) -#else - int rsCStrLen(cstr_t *pThis); -#endif - -#if STRINGBUF_TRIM_ALLOCSIZE != 1 -/* This is the normal case (see comment in rsCStrFinish!). In those cases, the function - * simply needs to do nothing, so that we can save us the function call. - * rgerhards, 2008-02-12 - */ -# define rsCStrFinish(pThis) RS_RET_OK -#else - /** - * Finish the string buffer dynamic allocation. - */ - rsRetVal rsCStrFinish(cstr_t *pThis); -#endif - -#define rsCStrGetBufBeg(x) ((x)->pBuf) - -rsRetVal strInit(); -rsRetVal strExit(); - -#endif /* single include */ diff --git a/syslogd-types.h b/syslogd-types.h deleted file mode 100644 index 9aea3778..00000000 --- a/syslogd-types.h +++ /dev/null @@ -1,104 +0,0 @@ -/* syslogd-type.h - * This file contains type defintions used by syslogd and its modules. - * It is a required input for any module. - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef SYSLOGD_TYPES_INCLUDED -#define SYSLOGD_TYPES_INCLUDED 1 - -#include "stringbuf.h" -//#include "net.h" -#include -#if HAVE_SYSLOG_H -#include -#endif - -#define FALSE 0 -#define TRUE 1 - -#ifdef UT_NAMESIZE -# define UNAMESZ UT_NAMESIZE /* length of a login name */ -#else -# define UNAMESZ 8 /* length of a login name */ -#endif -#define MAXUNAMES 20 /* maximum number of user names */ -#define MAXFNAME 200 /* max file pathname length */ - -#define _DB_MAXDBLEN 128 /* maximum number of db */ -#define _DB_MAXUNAMELEN 128 /* maximum number of user name */ -#define _DB_MAXPWDLEN 128 /* maximum number of user's pass */ -#define _DB_DELAYTIMEONERROR 20 /* If an error occur we stop logging until - a delayed time is over */ - - -/* we define features of the syslog code. This features can be used - * to check if modules are compatible with them - and possible other - * applications I do not yet envision. -- rgerhards, 2007-07-24 - */ -typedef enum _syslogFeature { - sFEATURERepeatedMsgReduction = 1 -} syslogFeature; - -/* we define our own facility and severities */ -/* facility and severity codes */ -typedef struct _syslogCode { - char *c_name; - int c_val; -} syslogCODE; - -/* values for host comparisons specified with host selector blocks - * (+host, -host). rgerhards 2005-10-18. - */ -enum _EHostnameCmpMode { - HN_NO_COMP = 0, /* do not compare hostname */ - HN_COMP_MATCH = 1, /* hostname must match */ - HN_COMP_NOMATCH = 2 /* hostname must NOT match */ -}; -typedef enum _EHostnameCmpMode EHostnameCmpMode; - -/* rgerhards 2004-11-11: the following structure represents - * a time as it is used in syslog. - */ -struct syslogTime { - int timeType; /* 0 - unitinialized , 1 - RFC 3164, 2 - syslog-protocol */ - int year; - int month; - int day; - int hour; /* 24 hour clock */ - int minute; - int second; - int secfrac; /* fractional seconds (must be 32 bit!) */ - int secfracPrecision; - char OffsetMode; /* UTC offset + or - */ - char OffsetHour; /* UTC offset in hours */ - int OffsetMinute; /* UTC offset in minutes */ - /* full UTC offset minutes = OffsetHours*60 + OffsetMinute. Then use - * OffsetMode to know the direction. - */ -}; -typedef struct syslogTime syslogTime_t; - -#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/threads.h b/threads.h index aa6a5c28..78924d95 100644 --- a/threads.h +++ b/threads.h @@ -23,16 +23,15 @@ #ifndef THREADS_H_INCLUDED #define THREADS_H_INCLUDED - /* the thread object */ -typedef struct thrdInfo { +struct thrdInfo { pthread_mutex_t *mutTermOK; /* Is it ok to terminate that thread now? */ int bIsActive; /* Is thread running? */ int bShallStop; /* set to 1 if the thread should be stopped ? */ rsRetVal (*pUsrThrdMain)(struct thrdInfo*); /* user thread main to be called in new thread */ rsRetVal (*pAfterRun)(struct thrdInfo*); /* cleanup function */ pthread_t thrdID; -} thrdInfo_t; +}; /* prototypes */ rsRetVal thrdExit(void); -- cgit v1.2.3 From 75f3cb34e6ce1c8c782e5c612765dc39b76cc3df Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 11:36:22 +0200 Subject: moved net module to runtime after careful analysis, I concluded that it is OK to place the current net.c/.h code under LGPL. Individual contributor agreement is given and no sysklogd code remains (see net.c for details). --- Makefile.am | 9 +- net.c | 1138 -------------------------------------------------- net.h | 118 ------ runtime/Makefile.am | 11 + runtime/net.c | 1149 +++++++++++++++++++++++++++++++++++++++++++++++++++ runtime/net.h | 117 ++++++ tcpsrv.c | 1 - 7 files changed, 1278 insertions(+), 1265 deletions(-) delete mode 100644 net.c delete mode 100644 net.h create mode 100644 runtime/net.c create mode 100644 runtime/net.h diff --git a/Makefile.am b/Makefile.am index 5829ff86..cc3b41bb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -60,14 +60,7 @@ endif if ENABLE_INET -pkglib_LTLIBRARIES += lmnet.la lmtcpsrv.la lmtcpclt.la -# -# network support -# -lmnet_la_SOURCES = net.c net.h -lmnet_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) -lmnet_la_LDFLAGS = -module -avoid-version $(rsrt_libs) -lmnet_la_LIBADD = +pkglib_LTLIBRARIES += lmtcpsrv.la lmtcpclt.la # # # TCP (stream) server support diff --git a/net.c b/net.c deleted file mode 100644 index bbd6bec7..00000000 --- a/net.c +++ /dev/null @@ -1,1138 +0,0 @@ -/* net.c - * Implementation of network-related stuff. - * - * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Starting 2007-12-24, I have begun to shuffle more network-related code - * from syslogd.c to over here. I am not sure if it will stay here in the - * long term, but it is good to have it out of syslogd.c. Maybe this here is - * an interim location ;) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" - -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "syslogd.h" -#include "syslogd-types.h" -#include "module-template.h" -#include "parse.h" -#include "srUtils.h" -#include "obj.h" -#include "errmsg.h" -#include "net.h" - -MODULE_TYPE_LIB - -/* static data */ -DEFobjStaticHelpers -DEFobjCurrIf(errmsg) - -/* support for defining allowed TCP and UDP senders. We use the same - * structure to implement this (a linked list), but we define two different - * list roots, one for UDP and one for TCP. - * rgerhards, 2005-09-26 - */ -/* All of the five below are read-only after startup */ -struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */ -struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */ -static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */ -static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */ -#ifdef USE_GSSAPI -struct AllowedSenders *pAllowedSenders_GSS = NULL; -static struct AllowedSenders *pLastAllowedSenders_GSS = NULL; -#endif - -int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */ -int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */ - -/* Code for handling allowed/disallowed senders - */ -static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) { - register uint8_t i; - - assert (addr != NULL); - assert (bits <= 128); - - i = bits/32; - if (bits%32) - addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); - for (; i < (sizeof addr->s6_addr32)/4; i++) - addr->s6_addr32[i] = 0; -} - -static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) { - - assert (addr != NULL); - assert (bits <=32 ); - - addr->s_addr &= htonl(0xffffffff << (32 - bits)); -} - -#define SIN(sa) ((struct sockaddr_in *)(sa)) -#define SIN6(sa) ((struct sockaddr_in6 *)(sa)) - -/* This function adds an allowed sender entry to the ACL linked list. - * In any case, a single entry is added. If an error occurs, the - * function does its error reporting itself. All validity checks - * must already have been done by the caller. - * This is a helper to AddAllowedSender(). - * rgerhards, 2007-07-17 - */ -static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, - struct NetAddr *iAllow, uint8_t iSignificantBits) -{ - struct AllowedSenders *pEntry = NULL; - - assert(ppRoot != NULL); - assert(ppLast != NULL); - assert(iAllow != NULL); - - if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { - glblHadMemShortage = 1; - return RS_RET_OUT_OF_MEMORY; /* no options left :( */ - } - - memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr)); - pEntry->pNext = NULL; - pEntry->SignificantBits = iSignificantBits; - - /* enqueue */ - if(*ppRoot == NULL) { - *ppRoot = pEntry; - } else { - (*ppLast)->pNext = pEntry; - } - *ppLast = pEntry; - - return RS_RET_OK; -} - -/* function to clear the allowed sender structure in cases where - * it must be freed (occurs most often when HUPed. - * TODO: reconsider recursive implementation - * I think there is also a memory leak, because only the last entry - * is acutally deleted... -- rgerhards, 2007-12-25 - */ -void clearAllowedSenders (struct AllowedSenders *pAllow) -{ - if (pAllow != NULL) { - if (pAllow->pNext != NULL) - clearAllowedSenders (pAllow->pNext); - else { - if (F_ISSET(pAllow->allowedSender.flags, ADDR_NAME)) - free (pAllow->allowedSender.addr.HostWildcard); - else - free (pAllow->allowedSender.addr.NetAddr); - - free (pAllow); - } - } -} - -/* function to add an allowed sender to the allowed sender list. The - * root of the list is caller-provided, so it can be used for all - * supported lists. The caller must provide a pointer to the root, - * as it eventually needs to be updated. Also, a pointer to the - * pointer to the last element must be provided (to speed up adding - * list elements). - * rgerhards, 2005-09-26 - * If a hostname is given there are possible multiple entries - * added (all addresses from that host). - */ -static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, - struct NetAddr *iAllow, uint8_t iSignificantBits) -{ - DEFiRet; - - assert(ppRoot != NULL); - assert(ppLast != NULL); - assert(iAllow != NULL); - - if (!F_ISSET(iAllow->flags, ADDR_NAME)) { - if(iSignificantBits == 0) - /* we handle this seperatly just to provide a better - * error message. - */ - errmsg.LogError(NO_ERRCODE, "You can not specify 0 bits of the netmask, this would " - "match ALL systems. If you really intend to do that, " - "remove all $AllowedSender directives."); - - switch (iAllow->addr.NetAddr->sa_family) { - case AF_INET: - if((iSignificantBits < 1) || (iSignificantBits > 32)) { - errmsg.LogError(NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32", - (int)iSignificantBits); - iSignificantBits = 32; - } - - MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits); - break; - case AF_INET6: - if((iSignificantBits < 1) || (iSignificantBits > 128)) { - errmsg.LogError(NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128", - iSignificantBits); - iSignificantBits = 128; - } - - MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits); - break; - default: - /* rgerhards, 2007-07-16: We have an internal program error in this - * case. However, there is not much we can do against it right now. Of - * course, we could abort, but that would probably cause more harm - * than good. So we continue to run. We simply do not add this line - the - * worst thing that happens is that one host will not be allowed to - * log. - */ - errmsg.LogError(NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d", - iAllow->addr.NetAddr->sa_family); - ABORT_FINALIZE(RS_RET_ERR); - } - /* OK, entry constructed, now lets add it to the ACL list */ - iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); - } else { - /* we need to process a hostname ACL */ - if (DisableDNS) { - errmsg.LogError(NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled."); - ABORT_FINALIZE(RS_RET_OK); - } - - if (!strchr (iAllow->addr.HostWildcard, '*') && - !strchr (iAllow->addr.HostWildcard, '?') && - ACLDontResolve == 0) { - /* single host - in this case, we pull its IP addresses from DNS - * and add IP-based ACLs. - */ - struct addrinfo hints, *res, *restmp; - struct NetAddr allowIP; - - memset (&hints, 0, sizeof (struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; -# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */ - hints.ai_flags = AI_ADDRCONFIG; -# endif - - if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) { - errmsg.LogError(NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); - - if (ACLAddHostnameOnFail) { - errmsg.LogError(NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); - iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); - FINALIZE; - } else { - errmsg.LogError(NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); - ABORT_FINALIZE(RS_RET_NOENTRY); - } - } - - for (restmp = res ; res != NULL ; res = res->ai_next) { - switch (res->ai_family) { - case AF_INET: /* add IPv4 */ - iSignificantBits = 32; - allowIP.flags = 0; - if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); - - if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) - != RS_RET_OK) - FINALIZE; - break; - case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */ - if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) { - /* extract & add IPv4 */ - - iSignificantBits = 32; - allowIP.flags = 0; - if((allowIP.addr.NetAddr = malloc(sizeof(struct sockaddr_in))) - == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN - SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in); -#endif - SIN(allowIP.addr.NetAddr)->sin_port = 0; - memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr), - &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]), - sizeof (struct sockaddr_in)); - - if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, - iSignificantBits)) - != RS_RET_OK) - FINALIZE; - } else { - /* finally add IPv6 */ - - iSignificantBits = 128; - allowIP.flags = 0; - if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); - - if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, - iSignificantBits)) - != RS_RET_OK) - FINALIZE; - } - break; - } - } - freeaddrinfo (restmp); - } else { - /* wildcards in hostname - we need to add a text-based ACL. - * For this, we already have everything ready and just need - * to pass it along... - */ - iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); - } - } - -finalize_it: - RETiRet; -} - - -/* Print an allowed sender list. The caller must tell us which one. - * iListToPrint = 1 means UDP, 2 means TCP - * rgerhards, 2005-09-27 - */ -void PrintAllowedSenders(int iListToPrint) -{ - struct AllowedSenders *pSender; - uchar szIP[64]; - - assert((iListToPrint == 1) || (iListToPrint == 2) -#ifdef USE_GSSAPI - || (iListToPrint == 3) -#endif - ); - - dbgprintf("Allowed %s Senders:\n", - (iListToPrint == 1) ? "UDP" : -#ifdef USE_GSSAPI - (iListToPrint == 3) ? "GSS" : -#endif - "TCP"); - - pSender = (iListToPrint == 1) ? pAllowedSenders_UDP : -#ifdef USE_GSSAPI - (iListToPrint == 3) ? pAllowedSenders_GSS : -#endif - pAllowedSenders_TCP; - if(pSender == NULL) { - dbgprintf("\tNo restrictions set.\n"); - } else { - while(pSender != NULL) { - if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) - dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); - else { - if(getnameinfo (pSender->allowedSender.addr.NetAddr, - SALEN(pSender->allowedSender.addr.NetAddr), - (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { - dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits); - } else { - /* getnameinfo() failed - but as this is only a - * debug function, we simply spit out an error and do - * not care much about it. - */ - dbgprintf("\tERROR in getnameinfo() - something may be wrong " - "- ignored for now\n"); - } - } - pSender = pSender->pNext; - } - } -} - - -/* parse an allowed sender config line and add the allowed senders - * (if the line is correct). - * rgerhards, 2005-09-27 - */ -rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) -{ - struct AllowedSenders **ppRoot; - struct AllowedSenders **ppLast; - rsParsObj *pPars; - rsRetVal iRet; - struct NetAddr *uIP = NULL; - int iBits; - - assert(pName != NULL); - assert(ppRestOfConfLine != NULL); - assert(*ppRestOfConfLine != NULL); - - if(!strcasecmp(pName, "udp")) { - ppRoot = &pAllowedSenders_UDP; - ppLast = &pLastAllowedSenders_UDP; - } else if(!strcasecmp(pName, "tcp")) { - ppRoot = &pAllowedSenders_TCP; - ppLast = &pLastAllowedSenders_TCP; -#ifdef USE_GSSAPI - } else if(!strcasecmp(pName, "gss")) { - ppRoot = &pAllowedSenders_GSS; - ppLast = &pLastAllowedSenders_GSS; -#endif - } else { - errmsg.LogError(NO_ERRCODE, "Invalid protocol '%s' in allowed sender " - "list, line ignored", pName); - return RS_RET_ERR; - } - - /* OK, we now know the protocol and have valid list pointers. - * So let's process the entries. We are using the parse class - * for this. - */ - /* create parser object starting with line string without leading colon */ - if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) { - errmsg.LogError(NO_ERRCODE, "Error %d constructing parser object - ignoring allowed sender list", iRet); - return(iRet); - } - - while(!parsIsAtEndOfParseString(pPars)) { - if(parsPeekAtCharAtParsPtr(pPars) == '#') - break; /* a comment-sign stops processing of line */ - /* now parse a single IP address */ - if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) { - errmsg.LogError(NO_ERRCODE, "Error %d parsing address in allowed sender" - "list - ignoring.", iRet); - rsParsDestruct(pPars); - return(iRet); - } - if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) - != RS_RET_OK) { - if (iRet == RS_RET_NOENTRY) { - errmsg.LogError(NO_ERRCODE, "Error %d adding allowed sender entry " - "- ignoring.", iRet); - } else { - errmsg.LogError(NO_ERRCODE, "Error %d adding allowed sender entry " - "- terminating, nothing more will be added.", iRet); - rsParsDestruct(pPars); - return(iRet); - } - } - free (uIP); /* copy stored in AllowedSenders list */ - } - - /* cleanup */ - *ppRestOfConfLine += parsGetCurrentPosition(pPars); - return rsParsDestruct(pPars); -} - - - -/* compares a host to an allowed sender list entry. Handles all subleties - * including IPv4/v6 as well as domain name wildcards. - * This is a helper to isAllowedSender. As it is only called once, it is - * declared inline. - * Returns 0 if they do not match, something else otherwise. - * contributed 1007-07-16 by mildew@gmail.com - */ -static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) -{ - assert(pAllow != NULL); - assert(pFrom != NULL); - - if(F_ISSET(pAllow->flags, ADDR_NAME)) { - dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); - -# if !defined(FNM_CASEFOLD) - /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */ - return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0); -# else - return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); -# endif - } else {/* We need to compare an IP address */ - switch (pFrom->sa_family) { - case AF_INET: - if (AF_INET == pAllow->addr.NetAddr->sa_family) - return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) ) - == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr); - else - return 0; - break; - case AF_INET6: - switch (pAllow->addr.NetAddr->sa_family) { - case AF_INET6: { - struct in6_addr ip, net; - register uint8_t i; - - memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr)); - memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr)); - - i = bits/32; - if (bits % 32) - ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); - for (; i < (sizeof ip.s6_addr32)/4; i++) - ip.s6_addr32[i] = 0; - - return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 && - (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ? - SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1)); - } - case AF_INET: { - struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr; - struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr; - - if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr && -#if BYTE_ORDER == LITTLE_ENDIAN - (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) && -#else - (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) && -#endif - (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0)) - return 1; - else - return 0; - } - default: - /* Unsupported AF */ - return 0; - } - default: - /* Unsupported AF */ - return 0; - } - } -} - - -/* check if a sender is allowed. The root of the the allowed sender. - * list must be proveded by the caller. As such, this function can be - * used to check both UDP and TCP allowed sender lists. - * returns 1, if the sender is allowed, 0 otherwise. - * rgerhards, 2005-09-26 - */ -static int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost) -{ - struct AllowedSenders *pAllow; - - assert(pFrom != NULL); - - if(pAllowRoot == NULL) - return 1; /* checking disabled, everything is valid! */ - - /* now we loop through the list of allowed senders. As soon as - * we find a match, we return back (indicating allowed). We loop - * until we are out of allowed senders. If so, we fall through the - * loop and the function's terminal return statement will indicate - * that the sender is disallowed. - */ - for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) { - if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) - return 1; - } - return 0; -} - - -/* The following #ifdef sequence is a small compatibility - * layer. It tries to work around the different availality - * levels of SO_BSDCOMPAT on linuxes... - * I borrowed this code from - * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html - * It still needs to be a bit better adapted to rsyslog. - * rgerhards 2005-09-19 - */ -#include -static int -should_use_so_bsdcompat(void) -{ -#ifndef OS_BSD - static int init_done; - static int so_bsdcompat_is_obsolete; - - if (!init_done) { - struct utsname myutsname; - unsigned int version, patchlevel; - - init_done = 1; - if (uname(&myutsname) < 0) { - char errStr[1024]; - dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr))); - return 1; - } - /* Format is .. - where the first three are unsigned integers and the last - is an arbitrary string. We only care about the first two. */ - if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) { - dbgprintf("uname: unexpected release '%s'\r\n", - myutsname.release); - return 1; - } - /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5 - kernels. It is a no-op in 2.4 but not in 2.2 kernels. */ - if (version > 2 || (version == 2 && patchlevel >= 5)) - so_bsdcompat_is_obsolete = 1; - } - return !so_bsdcompat_is_obsolete; -#else /* #ifndef OS_BSD */ - return 1; -#endif /* #ifndef OS_BSD */ -} -#ifndef SO_BSDCOMPAT -/* this shall prevent compiler errors due to undfined name */ -#define SO_BSDCOMPAT 0 -#endif - - -/* get the hostname of the message source. This was originally in cvthname() - * but has been moved out of it because of clarity and fuctional separation. - * It must be provided by the socket we received the message on as well as - * a NI_MAXHOST size large character buffer for the FQDN. - * - * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) - * for some explanation of the code found below. We do by default not - * discard message where we detected malicouos DNS PTR records. However, - * there is a user-configurabel option that will tell us if - * we should abort. For this, the return value tells the caller if the - * message should be processed (1) or discarded (0). - */ -static rsRetVal -gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) -{ - DEFiRet; - int error; - sigset_t omask, nmask; - char ip[NI_MAXHOST]; - struct addrinfo hints, *res; - - assert(f != NULL); - assert(pszHostFQDN != NULL); - - error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f), - ip, sizeof ip, NULL, 0, NI_NUMERICHOST); - - if (error) { - dbgprintf("Malformed from address %s\n", gai_strerror(error)); - strcpy((char*) pszHostFQDN, "???"); - ABORT_FINALIZE(RS_RET_INVALID_SOURCE); - } - - if (!DisableDNS) { - sigemptyset(&nmask); - sigaddset(&nmask, SIGHUP); - pthread_sigmask(SIG_BLOCK, &nmask, &omask); - - error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *) f), - (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD); - - if (error == 0) { - memset (&hints, 0, sizeof (struct addrinfo)); - hints.ai_flags = AI_NUMERICHOST; - hints.ai_socktype = SOCK_DGRAM; - - /* we now do a lookup once again. This one should fail, - * because we should not have obtained a non-numeric address. If - * we got a numeric one, someone messed with DNS! - */ - if (getaddrinfo ((char*)pszHostFQDN, NULL, &hints, &res) == 0) { - uchar szErrMsg[1024]; - freeaddrinfo (res); - /* OK, we know we have evil. The question now is what to do about - * it. One the one hand, the message might probably be intended - * to harm us. On the other hand, losing the message may also harm us. - * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords - * option. If it tells us we should discard, we do so, else we proceed, - * but log an error message together with it. - * time being, we simply drop the name we obtained and use the IP - that one - * is OK in any way. We do also log the error message. rgerhards, 2007-07-16 - */ - if(bDropMalPTRMsgs == 1) { - snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), - "Malicious PTR record, message dropped " - "IP = \"%s\" HOST = \"%s\"", - ip, pszHostFQDN); - errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); - pthread_sigmask(SIG_SETMASK, &omask, NULL); - ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); - } - - /* Please note: we deal with a malicous entry. Thus, we have crafted - * the snprintf() below so that all text is in front of the entry - maybe - * it contains characters that make the message unreadable - * (OK, I admit this is more or less impossible, but I am paranoid...) - * rgerhards, 2007-07-16 - */ - snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), - "Malicious PTR record (message accepted, but used IP " - "instead of PTR name: IP = \"%s\" HOST = \"%s\"", - ip, pszHostFQDN); - errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); - - error = 1; /* that will trigger using IP address below. */ - } - } - pthread_sigmask(SIG_SETMASK, &omask, NULL); - } - - if (error || DisableDNS) { - dbgprintf("Host name for your address (%s) unknown\n", ip); - strcpy((char*) pszHostFQDN, ip); - ABORT_FINALIZE(RS_RET_ADDRESS_UNKNOWN); - } - -finalize_it: - RETiRet; -} - - - -/* print out which socket we are listening on. This is only - * a debug aid. rgerhards, 2007-07-02 - */ -void debugListenInfo(int fd, char *type) -{ - char *szFamily; - int port; - struct sockaddr sa; - struct sockaddr_in *ipv4; - struct sockaddr_in6 *ipv6; - socklen_t saLen = sizeof(sa); - - if(getsockname(fd, &sa, &saLen) == 0) { - switch(sa.sa_family) { - case PF_INET: - szFamily = "IPv4"; - ipv4 = (struct sockaddr_in*) &sa; - port = ntohs(ipv4->sin_port); - break; - case PF_INET6: - szFamily = "IPv6"; - ipv6 = (struct sockaddr_in6*) &sa; - port = ntohs(ipv6->sin6_port); - break; - default: - szFamily = "other"; - port = -1; - break; - } - dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n", - type, fd, szFamily, port); - return; - } - - /* we can not obtain peer info. We are just providing - * debug info, so this is no reason to break the program - * or do any serious error reporting. - */ - dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd); -} - - -/* Return a printable representation of a host address. - * Now (2007-07-16) also returns the full host name (if it could be obtained) - * in the second param [thanks to mildew@gmail.com for the patch]. - * The caller must provide buffer space for pszHost and pszHostFQDN. These - * buffers must be of size NI_MAXHOST. This is not checked here, because - * there is no way to check it. We use this way of doing things because it - * frees us from using dynamic memory allocation where it really does not - * pay. - */ -rsRetVal cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN) -{ - DEFiRet; - register uchar *p; - int count; - - assert(f != NULL); - assert(pszHost != NULL); - assert(pszHostFQDN != NULL); - - iRet = gethname(f, pszHostFQDN); - - if(iRet == RS_RET_INVALID_SOURCE || iRet == RS_RET_ADDRESS_UNKNOWN) { - strcpy((char*) pszHost, (char*) pszHostFQDN); /* we use whatever was provided as replacement */ - ABORT_FINALIZE(RS_RET_OK); /* this is handled, we are happy with it */ - } else if(iRet != RS_RET_OK) { - FINALIZE; /* we return whatever error state we have - can not handle it */ - } - - /* if we reach this point, we obtained a non-numeric hostname and can now process it */ - - /* Convert to lower case, just like LocalDomain above - */ - for (p = pszHostFQDN ; *p ; p++) - if (isupper((int) *p)) - *p = tolower(*p); - - /* OK, the fqdn is now known. Now it is time to extract only the hostname - * part if we were instructed to do so. - */ - /* TODO: quick and dirty right now: we need to optimize that. We simply - * copy over the buffer and then use the old code. In the long term, that should - * be placed in its own function and probably outside of the net module (at least - * if should no longer reley on syslogd.c's global config-setting variables). - * Note that the old code always removes the local domain. We may want to - * make this in option in the long term. (rgerhards, 2007-09-11) - */ - strcpy((char*)pszHost, (char*)pszHostFQDN); - if ((p = (uchar*) strchr((char*)pszHost, '.'))) { /* find start of domain name "machine.example.com" */ - if(strcmp((char*) (p + 1), LocalDomain) == 0) { - *p = '\0'; /* simply terminate the string */ - } else { - /* now check if we belong to any of the domain names that were specified - * in the -s command line option. If so, remove and we are done. - */ - if (StripDomains) { - count=0; - while (StripDomains[count]) { - if (strcmp((char*)(p + 1), StripDomains[count]) == 0) { - *p = '\0'; - FINALIZE; /* we are done */ - } - count++; - } - } - /* if we reach this point, we have not found any domain we should strip. Now - * we try and see if the host itself is listed in the -l command line option - * and so should be stripped also. If so, we do it and return. Please note that - * -l list FQDNs, not just the hostname part. If it did just list the hostname, the - * door would be wide-open for all kinds of mixing up of hosts. Because of this, - * you'll see comparison against the full string (pszHost) below. The termination - * still occurs at *p, which points at the first dot after the hostname. - */ - if (LocalHosts) { - count=0; - while (LocalHosts[count]) { - if (!strcmp((char*)pszHost, LocalHosts[count])) { - *p = '\0'; - break; /* we are done */ - } - count++; - } - } - } - } - -finalize_it: - RETiRet; -} - - -/* get the name of the local host. A pointer to a character pointer is passed - * in, which on exit points to the local hostname. This buffer is dynamically - * allocated and must be free()ed by the caller. If the functions returns an - * error, the pointer is NULL. This function is based on GNU/Hurd's localhostname - * function. - * rgerhards, 20080-04-10 - */ -static rsRetVal -getLocalHostname(uchar **ppName) -{ - DEFiRet; - uchar *buf = NULL; - size_t buf_len = 0; - - assert(ppName != NULL); - - do { - if(buf == NULL) { - buf_len = 128; /* Initial guess */ - CHKmalloc(buf = malloc(buf_len)); - } else { - buf_len += buf_len; - CHKmalloc(buf = realloc (buf, buf_len)); - } - } while((gethostname((char*)buf, buf_len) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG); - - *ppName = buf; - buf = NULL; - -finalize_it: - if(iRet != RS_RET_OK) { - if(buf != NULL) - free(buf); - } - RETiRet; -} - - -/* closes the UDP listen sockets (if they exist) and frees - * all dynamically assigned memory. - */ -void closeUDPListenSockets(int *pSockArr) -{ - register int i; - - assert(pSockArr != NULL); - if(pSockArr != NULL) { - for (i = 0; i < *pSockArr; i++) - close(pSockArr[i+1]); - free(pSockArr); - } -} - - -/* creates the UDP listen sockets - * hostname and/or pszPort may be NULL, but not both! - * bIsServer indicates if a server socket should be created - * 1 - server, 0 - client - */ -int *create_udp_socket(uchar *hostname, uchar *pszPort, int bIsServer) -{ - struct addrinfo hints, *res, *r; - int error, maxs, *s, *socks, on = 1; - int sockflags; - - assert(!((pszPort == NULL) && (hostname == NULL))); - memset(&hints, 0, sizeof(hints)); - if(bIsServer) - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; - else - hints.ai_flags = AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = SOCK_DGRAM; - error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res); - if(error) { - errmsg.LogError(NO_ERRCODE, "%s", gai_strerror(error)); - errmsg.LogError(NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n"); - return NULL; - } - - /* Count max number of sockets we may open */ - for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) - /* EMPTY */; - socks = malloc((maxs+1) * sizeof(int)); - if (socks == NULL) { - errmsg.LogError(NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP message reception"); - freeaddrinfo(res); - return NULL; - } - - *socks = 0; /* num of sockets counter at start of array */ - s = socks + 1; - for (r = res; r != NULL ; r = r->ai_next) { - *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); - if (*s < 0) { - if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) - errmsg.LogError(NO_ERRCODE, "create_udp_socket(), socket"); - /* it is debateble if PF_INET with EAFNOSUPPORT should - * also be ignored... - */ - continue; - } - -# ifdef IPV6_V6ONLY - if (r->ai_family == AF_INET6) { - int ion = 1; - if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, - (char *)&ion, sizeof (ion)) < 0) { - errmsg.LogError(NO_ERRCODE, "setsockopt"); - close(*s); - *s = -1; - continue; - } - } -# endif - - /* if we have an error, we "just" suspend that socket. Eventually - * other sockets will work. At the end of this function, we check - * if we managed to open at least one socket. If not, we'll write - * a "inet suspended" message and declare failure. Else we use - * what we could obtain. - * rgerhards, 2007-06-22 - */ - if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, - (char *) &on, sizeof(on)) < 0 ) { - errmsg.LogError(NO_ERRCODE, "setsockopt(REUSEADDR)"); - close(*s); - *s = -1; - continue; - } - - /* We need to enable BSD compatibility. Otherwise an attacker - * could flood our log files by sending us tons of ICMP errors. - */ -#if !defined(OS_BSD) && !defined(__hpux) - if (should_use_so_bsdcompat()) { - if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, - (char *) &on, sizeof(on)) < 0) { - errmsg.LogError(NO_ERRCODE, "setsockopt(BSDCOMPAT)"); - close(*s); - *s = -1; - continue; - } - } -#endif - /* We must not block on the network socket, in case a packet - * gets lost between select and recv, otherwise the process - * will stall until the timeout, and other processes trying to - * log will also stall. - * Patch vom Colin Phipps to the original - * sysklogd source. Applied to rsyslogd on 2005-10-19. - */ - if ((sockflags = fcntl(*s, F_GETFL)) != -1) { - sockflags |= O_NONBLOCK; - /* SETFL could fail too, so get it caught by the subsequent - * error check. - */ - sockflags = fcntl(*s, F_SETFL, sockflags); - } - if (sockflags == -1) { - errmsg.LogError(NO_ERRCODE, "fcntl(O_NONBLOCK)"); - close(*s); - *s = -1; - continue; - } - - if(bIsServer) { - /* rgerhards, 2007-06-22: if we run on a kernel that does not support - * the IPV6_V6ONLY socket option, we need to use a work-around. On such - * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4 - * socket can not listen on the same port as an IPv6 socket. The only - * workaround is to ignore the "socket in use" error. This is what we - * do if we have to. - */ - if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) - # ifndef IPV6_V6ONLY - && (errno != EADDRINUSE) - # endif - ) { - errmsg.LogError(NO_ERRCODE, "bind"); - close(*s); - *s = -1; - continue; - } - } - - (*socks)++; - s++; - } - - if(res != NULL) - freeaddrinfo(res); - - if(Debug && *socks != maxs) - dbgprintf("We could initialize %d UDP listen sockets out of %d we received " - "- this may or may not be an error indication.\n", *socks, maxs); - - if(*socks == 0) { - errmsg.LogError(NO_ERRCODE, "No UDP listen socket could successfully be initialized, " - "message reception via UDP disabled.\n"); - /* we do NOT need to free any sockets, because there were none... */ - free(socks); - return(NULL); - } - - return(socks); -} - - -/* queryInterface function - * rgerhards, 2008-03-05 - */ -BEGINobjQueryInterface(net) -CODESTARTobjQueryInterface(net) - if(pIf->ifVersion != netCURR_IF_VERSION) { /* check for current version, increment on each change */ - ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); - } - - /* ok, we have the right interface, so let's fill it - * Please note that we may also do some backwards-compatibility - * work here (if we can support an older interface version - that, - * of course, also affects the "if" above). - */ - pIf->cvthname = cvthname; - /* things to go away after proper modularization */ - pIf->addAllowedSenderLine = addAllowedSenderLine; - pIf->PrintAllowedSenders = PrintAllowedSenders; - pIf->clearAllowedSenders = clearAllowedSenders; - pIf->debugListenInfo = debugListenInfo; - pIf->create_udp_socket = create_udp_socket; - pIf->closeUDPListenSockets = closeUDPListenSockets; - pIf->isAllowedSender = isAllowedSender; - pIf->should_use_so_bsdcompat = should_use_so_bsdcompat; - pIf->getLocalHostname = getLocalHostname; -finalize_it: -ENDobjQueryInterface(net) - - -/* exit our class - * rgerhards, 2008-03-10 - */ -BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ -CODESTARTObjClassExit(net) - /* release objects we no longer need */ - objRelease(errmsg, CORE_COMPONENT); -ENDObjClassExit(net) - - -/* Initialize the net class. Must be called as the very first method - * before anything else is called inside this class. - * rgerhards, 2008-02-19 - */ -BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */ - /* request objects we use */ - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - - /* set our own handlers */ -ENDObjClassInit(net) - - -/* --------------- here now comes the plumbing that makes as a library module --------------- */ - - -BEGINmodExit -CODESTARTmodExit - netClassExit(); -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_LIB_QUERIES -ENDqueryEtryPt - - -BEGINmodInit() -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ - - /* Initialize all classes that are in our module - this includes ourselfs */ - CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ -ENDmodInit -/* vi:set ai: - */ diff --git a/net.h b/net.h deleted file mode 100644 index 0f5b0bc1..00000000 --- a/net.h +++ /dev/null @@ -1,118 +0,0 @@ -/* Definitions for network-related stuff. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef INCLUDED_NET_H -#define INCLUDED_NET_H - -#ifdef SYSLOG_INET -#include -#include /* this is needed on HP UX -- rgerhards, 2008-03-04 */ - -#define F_SET(where, flag) (where)|=(flag) -#define F_ISSET(where, flag) ((where)&(flag))==(flag) -#define F_UNSET(where, flag) (where)&=~(flag) - -#define ADDR_NAME 0x01 /* address is hostname wildcard) */ -#define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */ - -#ifdef OS_BSD -# ifndef _KERNEL -# define s6_addr32 __u6_addr.__u6_addr32 -# endif -#endif - -struct NetAddr { - uint8_t flags; - union { - struct sockaddr *NetAddr; - char *HostWildcard; - } addr; -}; - -#ifndef SO_BSDCOMPAT - /* this shall prevent compiler errors due to undfined name */ -# define SO_BSDCOMPAT 0 -#endif - - -/* IPv6 compatibility layer for older platforms - * We need to handle a few things different if we are running - * on an older platform which does not support all the glory - * of IPv6. We try to limit toll on features and reliability, - * but obviously it is better to run rsyslog on a platform that - * supports everything... - * rgerhards, 2007-06-22 - */ -#ifndef AI_NUMERICSERV -# define AI_NUMERICSERV 0 -#endif - - -#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN -#define SALEN(sa) ((sa)->sa_len) -#else -static inline size_t SALEN(struct sockaddr *sa) { - switch (sa->sa_family) { - case AF_INET: return (sizeof (struct sockaddr_in)); - case AF_INET6: return (sizeof (struct sockaddr_in6)); - default: return 0; - } -} -#endif - -struct AllowedSenders { - struct NetAddr allowedSender; /* ip address allowed */ - uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ - struct AllowedSenders *pNext; -}; - - -/* interfaces */ -BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ - rsRetVal (*cvthname)(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN); - /* things to go away after proper modularization */ - rsRetVal (*addAllowedSenderLine)(char* pName, uchar** ppRestOfConfLine); - void (*PrintAllowedSenders)(int iListToPrint); - void (*clearAllowedSenders) (); - void (*debugListenInfo)(int fd, char *type); - int *(*create_udp_socket)(uchar *hostname, uchar *LogPort, int bIsServer); - void (*closeUDPListenSockets)(int *finet); - int (*isAllowedSender)(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost); - rsRetVal (*getLocalHostname)(uchar**); - int (*should_use_so_bsdcompat)(void); - /* data memebers - these should go away over time... TODO */ - int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ - int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ - struct AllowedSenders *pAllowedSenders_UDP; - struct AllowedSenders *pAllowedSenders_TCP; - struct AllowedSenders *pAllowedSenders_GSS; -ENDinterface(net) -#define netCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ - -/* prototypes */ -PROTOTYPEObj(net); - -/* the name of our library binary */ -#define LM_NET_FILENAME "lmnet" - -#endif /* #ifdef SYSLOG_INET */ -#endif /* #ifndef INCLUDED_NET_H */ diff --git a/runtime/Makefile.am b/runtime/Makefile.am index 048ef411..8cb3a638 100644 --- a/runtime/Makefile.am +++ b/runtime/Makefile.am @@ -75,3 +75,14 @@ lmregexp_la_LDFLAGS = -module -avoid-version $(rsrt_libs) lmregexp_la_LIBADD = endif +if ENABLE_INET +pkglib_LTLIBRARIES += lmnet.la +# +# network support +# +lmnet_la_SOURCES = net.c net.h +lmnet_la_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) +lmnet_la_LDFLAGS = -module -avoid-version $(rsrt_libs) +lmnet_la_LIBADD = + +endif # if ENABLE_INET diff --git a/runtime/net.c b/runtime/net.c new file mode 100644 index 00000000..84c286d2 --- /dev/null +++ b/runtime/net.c @@ -0,0 +1,1149 @@ +/* net.c + * Implementation of network-related stuff. + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Starting 2007-12-24, I have begun to shuffle more network-related code + * from syslogd.c to over here. I am not sure if it will stay here in the + * long term, but it is good to have it out of syslogd.c. Maybe this here is + * an interim location ;) + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * rgerhards, 2008-04-16: I changed this code to LGPL today. I carefully analyzed + * that it does not borrow code from the original sysklogd and that I have + * permission to do so from all other contributors. My analysis found that all + * code from sysklogd has been superseeded by our own functionality, so it + * is OK to move this file to LGPL. Some variable sysklogd variable names + * remain, but even this will change as the net object evolves. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" + +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" +#include "syslogd-types.h" +#include "module-template.h" +#include "parse.h" +#include "srUtils.h" +#include "obj.h" +#include "errmsg.h" +#include "net.h" + +MODULE_TYPE_LIB + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(errmsg) + +/* support for defining allowed TCP and UDP senders. We use the same + * structure to implement this (a linked list), but we define two different + * list roots, one for UDP and one for TCP. + * rgerhards, 2005-09-26 + */ +/* All of the five below are read-only after startup */ +struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */ +struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */ +static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */ +static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */ +#ifdef USE_GSSAPI +struct AllowedSenders *pAllowedSenders_GSS = NULL; +static struct AllowedSenders *pLastAllowedSenders_GSS = NULL; +#endif + +int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */ +int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */ + +/* Code for handling allowed/disallowed senders + */ +static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) { + register uint8_t i; + + assert (addr != NULL); + assert (bits <= 128); + + i = bits/32; + if (bits%32) + addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof addr->s6_addr32)/4; i++) + addr->s6_addr32[i] = 0; +} + +static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) { + + assert (addr != NULL); + assert (bits <=32 ); + + addr->s_addr &= htonl(0xffffffff << (32 - bits)); +} + +#define SIN(sa) ((struct sockaddr_in *)(sa)) +#define SIN6(sa) ((struct sockaddr_in6 *)(sa)) + +/* This function adds an allowed sender entry to the ACL linked list. + * In any case, a single entry is added. If an error occurs, the + * function does its error reporting itself. All validity checks + * must already have been done by the caller. + * This is a helper to AddAllowedSender(). + * rgerhards, 2007-07-17 + */ +static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + struct AllowedSenders *pEntry = NULL; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { + glblHadMemShortage = 1; + return RS_RET_OUT_OF_MEMORY; /* no options left :( */ + } + + memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr)); + pEntry->pNext = NULL; + pEntry->SignificantBits = iSignificantBits; + + /* enqueue */ + if(*ppRoot == NULL) { + *ppRoot = pEntry; + } else { + (*ppLast)->pNext = pEntry; + } + *ppLast = pEntry; + + return RS_RET_OK; +} + +/* function to clear the allowed sender structure in cases where + * it must be freed (occurs most often when HUPed. + * TODO: reconsider recursive implementation + * I think there is also a memory leak, because only the last entry + * is acutally deleted... -- rgerhards, 2007-12-25 + */ +void clearAllowedSenders (struct AllowedSenders *pAllow) +{ + if (pAllow != NULL) { + if (pAllow->pNext != NULL) + clearAllowedSenders (pAllow->pNext); + else { + if (F_ISSET(pAllow->allowedSender.flags, ADDR_NAME)) + free (pAllow->allowedSender.addr.HostWildcard); + else + free (pAllow->allowedSender.addr.NetAddr); + + free (pAllow); + } + } +} + +/* function to add an allowed sender to the allowed sender list. The + * root of the list is caller-provided, so it can be used for all + * supported lists. The caller must provide a pointer to the root, + * as it eventually needs to be updated. Also, a pointer to the + * pointer to the last element must be provided (to speed up adding + * list elements). + * rgerhards, 2005-09-26 + * If a hostname is given there are possible multiple entries + * added (all addresses from that host). + */ +static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, + struct NetAddr *iAllow, uint8_t iSignificantBits) +{ + DEFiRet; + + assert(ppRoot != NULL); + assert(ppLast != NULL); + assert(iAllow != NULL); + + if (!F_ISSET(iAllow->flags, ADDR_NAME)) { + if(iSignificantBits == 0) + /* we handle this seperatly just to provide a better + * error message. + */ + errmsg.LogError(NO_ERRCODE, "You can not specify 0 bits of the netmask, this would " + "match ALL systems. If you really intend to do that, " + "remove all $AllowedSender directives."); + + switch (iAllow->addr.NetAddr->sa_family) { + case AF_INET: + if((iSignificantBits < 1) || (iSignificantBits > 32)) { + errmsg.LogError(NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32", + (int)iSignificantBits); + iSignificantBits = 32; + } + + MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits); + break; + case AF_INET6: + if((iSignificantBits < 1) || (iSignificantBits > 128)) { + errmsg.LogError(NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128", + iSignificantBits); + iSignificantBits = 128; + } + + MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits); + break; + default: + /* rgerhards, 2007-07-16: We have an internal program error in this + * case. However, there is not much we can do against it right now. Of + * course, we could abort, but that would probably cause more harm + * than good. So we continue to run. We simply do not add this line - the + * worst thing that happens is that one host will not be allowed to + * log. + */ + errmsg.LogError(NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d", + iAllow->addr.NetAddr->sa_family); + ABORT_FINALIZE(RS_RET_ERR); + } + /* OK, entry constructed, now lets add it to the ACL list */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } else { + /* we need to process a hostname ACL */ + if (DisableDNS) { + errmsg.LogError(NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled."); + ABORT_FINALIZE(RS_RET_OK); + } + + if (!strchr (iAllow->addr.HostWildcard, '*') && + !strchr (iAllow->addr.HostWildcard, '?') && + ACLDontResolve == 0) { + /* single host - in this case, we pull its IP addresses from DNS + * and add IP-based ACLs. + */ + struct addrinfo hints, *res, *restmp; + struct NetAddr allowIP; + + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; +# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */ + hints.ai_flags = AI_ADDRCONFIG; +# endif + + if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) { + errmsg.LogError(NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); + + if (ACLAddHostnameOnFail) { + errmsg.LogError(NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + FINALIZE; + } else { + errmsg.LogError(NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); + ABORT_FINALIZE(RS_RET_NOENTRY); + } + } + + for (restmp = res ; res != NULL ; res = res->ai_next) { + switch (res->ai_family) { + case AF_INET: /* add IPv4 */ + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { + glblHadMemShortage = 1; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) + != RS_RET_OK) + FINALIZE; + break; + case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */ + if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) { + /* extract & add IPv4 */ + + iSignificantBits = 32; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(sizeof(struct sockaddr_in))) + == NULL) { + glblHadMemShortage = 1; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in); +#endif + SIN(allowIP.addr.NetAddr)->sin_port = 0; + memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr), + &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]), + sizeof (struct sockaddr_in)); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } else { + /* finally add IPv6 */ + + iSignificantBits = 128; + allowIP.flags = 0; + if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { + glblHadMemShortage = 1; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); + + if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, + iSignificantBits)) + != RS_RET_OK) + FINALIZE; + } + break; + } + } + freeaddrinfo (restmp); + } else { + /* wildcards in hostname - we need to add a text-based ACL. + * For this, we already have everything ready and just need + * to pass it along... + */ + iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); + } + } + +finalize_it: + RETiRet; +} + + +/* Print an allowed sender list. The caller must tell us which one. + * iListToPrint = 1 means UDP, 2 means TCP + * rgerhards, 2005-09-27 + */ +void PrintAllowedSenders(int iListToPrint) +{ + struct AllowedSenders *pSender; + uchar szIP[64]; + + assert((iListToPrint == 1) || (iListToPrint == 2) +#ifdef USE_GSSAPI + || (iListToPrint == 3) +#endif + ); + + dbgprintf("Allowed %s Senders:\n", + (iListToPrint == 1) ? "UDP" : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? "GSS" : +#endif + "TCP"); + + pSender = (iListToPrint == 1) ? pAllowedSenders_UDP : +#ifdef USE_GSSAPI + (iListToPrint == 3) ? pAllowedSenders_GSS : +#endif + pAllowedSenders_TCP; + if(pSender == NULL) { + dbgprintf("\tNo restrictions set.\n"); + } else { + while(pSender != NULL) { + if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) + dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); + else { + if(getnameinfo (pSender->allowedSender.addr.NetAddr, + SALEN(pSender->allowedSender.addr.NetAddr), + (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { + dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits); + } else { + /* getnameinfo() failed - but as this is only a + * debug function, we simply spit out an error and do + * not care much about it. + */ + dbgprintf("\tERROR in getnameinfo() - something may be wrong " + "- ignored for now\n"); + } + } + pSender = pSender->pNext; + } + } +} + + +/* parse an allowed sender config line and add the allowed senders + * (if the line is correct). + * rgerhards, 2005-09-27 + */ +rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) +{ + struct AllowedSenders **ppRoot; + struct AllowedSenders **ppLast; + rsParsObj *pPars; + rsRetVal iRet; + struct NetAddr *uIP = NULL; + int iBits; + + assert(pName != NULL); + assert(ppRestOfConfLine != NULL); + assert(*ppRestOfConfLine != NULL); + + if(!strcasecmp(pName, "udp")) { + ppRoot = &pAllowedSenders_UDP; + ppLast = &pLastAllowedSenders_UDP; + } else if(!strcasecmp(pName, "tcp")) { + ppRoot = &pAllowedSenders_TCP; + ppLast = &pLastAllowedSenders_TCP; +#ifdef USE_GSSAPI + } else if(!strcasecmp(pName, "gss")) { + ppRoot = &pAllowedSenders_GSS; + ppLast = &pLastAllowedSenders_GSS; +#endif + } else { + errmsg.LogError(NO_ERRCODE, "Invalid protocol '%s' in allowed sender " + "list, line ignored", pName); + return RS_RET_ERR; + } + + /* OK, we now know the protocol and have valid list pointers. + * So let's process the entries. We are using the parse class + * for this. + */ + /* create parser object starting with line string without leading colon */ + if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) { + errmsg.LogError(NO_ERRCODE, "Error %d constructing parser object - ignoring allowed sender list", iRet); + return(iRet); + } + + while(!parsIsAtEndOfParseString(pPars)) { + if(parsPeekAtCharAtParsPtr(pPars) == '#') + break; /* a comment-sign stops processing of line */ + /* now parse a single IP address */ + if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "Error %d parsing address in allowed sender" + "list - ignoring.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) + != RS_RET_OK) { + if (iRet == RS_RET_NOENTRY) { + errmsg.LogError(NO_ERRCODE, "Error %d adding allowed sender entry " + "- ignoring.", iRet); + } else { + errmsg.LogError(NO_ERRCODE, "Error %d adding allowed sender entry " + "- terminating, nothing more will be added.", iRet); + rsParsDestruct(pPars); + return(iRet); + } + } + free (uIP); /* copy stored in AllowedSenders list */ + } + + /* cleanup */ + *ppRestOfConfLine += parsGetCurrentPosition(pPars); + return rsParsDestruct(pPars); +} + + + +/* compares a host to an allowed sender list entry. Handles all subleties + * including IPv4/v6 as well as domain name wildcards. + * This is a helper to isAllowedSender. As it is only called once, it is + * declared inline. + * Returns 0 if they do not match, something else otherwise. + * contributed 1007-07-16 by mildew@gmail.com + */ +static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) +{ + assert(pAllow != NULL); + assert(pFrom != NULL); + + if(F_ISSET(pAllow->flags, ADDR_NAME)) { + dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); + +# if !defined(FNM_CASEFOLD) + /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */ + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0); +# else + return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); +# endif + } else {/* We need to compare an IP address */ + switch (pFrom->sa_family) { + case AF_INET: + if (AF_INET == pAllow->addr.NetAddr->sa_family) + return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) ) + == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr); + else + return 0; + break; + case AF_INET6: + switch (pAllow->addr.NetAddr->sa_family) { + case AF_INET6: { + struct in6_addr ip, net; + register uint8_t i; + + memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr)); + memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr)); + + i = bits/32; + if (bits % 32) + ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); + for (; i < (sizeof ip.s6_addr32)/4; i++) + ip.s6_addr32[i] = 0; + + return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 && + (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ? + SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1)); + } + case AF_INET: { + struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr; + struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr; + + if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr && +#if BYTE_ORDER == LITTLE_ENDIAN + (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) && +#else + (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) && +#endif + (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0)) + return 1; + else + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + default: + /* Unsupported AF */ + return 0; + } + } +} + + +/* check if a sender is allowed. The root of the the allowed sender. + * list must be proveded by the caller. As such, this function can be + * used to check both UDP and TCP allowed sender lists. + * returns 1, if the sender is allowed, 0 otherwise. + * rgerhards, 2005-09-26 + */ +static int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost) +{ + struct AllowedSenders *pAllow; + + assert(pFrom != NULL); + + if(pAllowRoot == NULL) + return 1; /* checking disabled, everything is valid! */ + + /* now we loop through the list of allowed senders. As soon as + * we find a match, we return back (indicating allowed). We loop + * until we are out of allowed senders. If so, we fall through the + * loop and the function's terminal return statement will indicate + * that the sender is disallowed. + */ + for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) { + if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) + return 1; + } + return 0; +} + + +/* The following #ifdef sequence is a small compatibility + * layer. It tries to work around the different availality + * levels of SO_BSDCOMPAT on linuxes... + * I borrowed this code from + * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html + * It still needs to be a bit better adapted to rsyslog. + * rgerhards 2005-09-19 + */ +#include +static int +should_use_so_bsdcompat(void) +{ +#ifndef OS_BSD + static int init_done; + static int so_bsdcompat_is_obsolete; + + if (!init_done) { + struct utsname myutsname; + unsigned int version, patchlevel; + + init_done = 1; + if (uname(&myutsname) < 0) { + char errStr[1024]; + dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr))); + return 1; + } + /* Format is .. + where the first three are unsigned integers and the last + is an arbitrary string. We only care about the first two. */ + if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) { + dbgprintf("uname: unexpected release '%s'\r\n", + myutsname.release); + return 1; + } + /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5 + kernels. It is a no-op in 2.4 but not in 2.2 kernels. */ + if (version > 2 || (version == 2 && patchlevel >= 5)) + so_bsdcompat_is_obsolete = 1; + } + return !so_bsdcompat_is_obsolete; +#else /* #ifndef OS_BSD */ + return 1; +#endif /* #ifndef OS_BSD */ +} +#ifndef SO_BSDCOMPAT +/* this shall prevent compiler errors due to undfined name */ +#define SO_BSDCOMPAT 0 +#endif + + +/* get the hostname of the message source. This was originally in cvthname() + * but has been moved out of it because of clarity and fuctional separation. + * It must be provided by the socket we received the message on as well as + * a NI_MAXHOST size large character buffer for the FQDN. + * + * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats) + * for some explanation of the code found below. We do by default not + * discard message where we detected malicouos DNS PTR records. However, + * there is a user-configurabel option that will tell us if + * we should abort. For this, the return value tells the caller if the + * message should be processed (1) or discarded (0). + */ +static rsRetVal +gethname(struct sockaddr_storage *f, uchar *pszHostFQDN) +{ + DEFiRet; + int error; + sigset_t omask, nmask; + char ip[NI_MAXHOST]; + struct addrinfo hints, *res; + + assert(f != NULL); + assert(pszHostFQDN != NULL); + + error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f), + ip, sizeof ip, NULL, 0, NI_NUMERICHOST); + + if (error) { + dbgprintf("Malformed from address %s\n", gai_strerror(error)); + strcpy((char*) pszHostFQDN, "???"); + ABORT_FINALIZE(RS_RET_INVALID_SOURCE); + } + + if (!DisableDNS) { + sigemptyset(&nmask); + sigaddset(&nmask, SIGHUP); + pthread_sigmask(SIG_BLOCK, &nmask, &omask); + + error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *) f), + (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + + if (error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_DGRAM; + + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if (getaddrinfo ((char*)pszHostFQDN, NULL, &hints, &res) == 0) { + uchar szErrMsg[1024]; + freeaddrinfo (res); + /* OK, we know we have evil. The question now is what to do about + * it. One the one hand, the message might probably be intended + * to harm us. On the other hand, losing the message may also harm us. + * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords + * option. If it tells us we should discard, we do so, else we proceed, + * but log an error message together with it. + * time being, we simply drop the name we obtained and use the IP - that one + * is OK in any way. We do also log the error message. rgerhards, 2007-07-16 + */ + if(bDropMalPTRMsgs == 1) { + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record, message dropped " + "IP = \"%s\" HOST = \"%s\"", + ip, pszHostFQDN); + errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); + pthread_sigmask(SIG_SETMASK, &omask, NULL); + ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); + } + + /* Please note: we deal with a malicous entry. Thus, we have crafted + * the snprintf() below so that all text is in front of the entry - maybe + * it contains characters that make the message unreadable + * (OK, I admit this is more or less impossible, but I am paranoid...) + * rgerhards, 2007-07-16 + */ + snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar), + "Malicious PTR record (message accepted, but used IP " + "instead of PTR name: IP = \"%s\" HOST = \"%s\"", + ip, pszHostFQDN); + errmsg.LogError(NO_ERRCODE, "%s", szErrMsg); + + error = 1; /* that will trigger using IP address below. */ + } + } + pthread_sigmask(SIG_SETMASK, &omask, NULL); + } + + if (error || DisableDNS) { + dbgprintf("Host name for your address (%s) unknown\n", ip); + strcpy((char*) pszHostFQDN, ip); + ABORT_FINALIZE(RS_RET_ADDRESS_UNKNOWN); + } + +finalize_it: + RETiRet; +} + + + +/* print out which socket we are listening on. This is only + * a debug aid. rgerhards, 2007-07-02 + */ +void debugListenInfo(int fd, char *type) +{ + char *szFamily; + int port; + struct sockaddr sa; + struct sockaddr_in *ipv4; + struct sockaddr_in6 *ipv6; + socklen_t saLen = sizeof(sa); + + if(getsockname(fd, &sa, &saLen) == 0) { + switch(sa.sa_family) { + case PF_INET: + szFamily = "IPv4"; + ipv4 = (struct sockaddr_in*) &sa; + port = ntohs(ipv4->sin_port); + break; + case PF_INET6: + szFamily = "IPv6"; + ipv6 = (struct sockaddr_in6*) &sa; + port = ntohs(ipv6->sin6_port); + break; + default: + szFamily = "other"; + port = -1; + break; + } + dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n", + type, fd, szFamily, port); + return; + } + + /* we can not obtain peer info. We are just providing + * debug info, so this is no reason to break the program + * or do any serious error reporting. + */ + dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd); +} + + +/* Return a printable representation of a host address. + * Now (2007-07-16) also returns the full host name (if it could be obtained) + * in the second param [thanks to mildew@gmail.com for the patch]. + * The caller must provide buffer space for pszHost and pszHostFQDN. These + * buffers must be of size NI_MAXHOST. This is not checked here, because + * there is no way to check it. We use this way of doing things because it + * frees us from using dynamic memory allocation where it really does not + * pay. + */ +rsRetVal cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN) +{ + DEFiRet; + register uchar *p; + int count; + + assert(f != NULL); + assert(pszHost != NULL); + assert(pszHostFQDN != NULL); + + iRet = gethname(f, pszHostFQDN); + + if(iRet == RS_RET_INVALID_SOURCE || iRet == RS_RET_ADDRESS_UNKNOWN) { + strcpy((char*) pszHost, (char*) pszHostFQDN); /* we use whatever was provided as replacement */ + ABORT_FINALIZE(RS_RET_OK); /* this is handled, we are happy with it */ + } else if(iRet != RS_RET_OK) { + FINALIZE; /* we return whatever error state we have - can not handle it */ + } + + /* if we reach this point, we obtained a non-numeric hostname and can now process it */ + + /* Convert to lower case */ + for(p = pszHostFQDN ; *p ; p++) + if (isupper((int) *p)) + *p = tolower(*p); + + /* OK, the fqdn is now known. Now it is time to extract only the hostname + * part if we were instructed to do so. + */ + /* TODO: quick and dirty right now: we need to optimize that. We simply + * copy over the buffer and then use the old code. In the long term, that should + * be placed in its own function and probably outside of the net module (at least + * if should no longer reley on syslogd.c's global config-setting variables). + * Note that the old code always removes the local domain. We may want to + * make this in option in the long term. (rgerhards, 2007-09-11) + */ + strcpy((char*)pszHost, (char*)pszHostFQDN); + if ((p = (uchar*) strchr((char*)pszHost, '.'))) { /* find start of domain name "machine.example.com" */ + if(strcmp((char*) (p + 1), LocalDomain) == 0) { + *p = '\0'; /* simply terminate the string */ + } else { + /* now check if we belong to any of the domain names that were specified + * in the -s command line option. If so, remove and we are done. + * TODO: this must go away! -- rgerhards, 2008-04-16 + * For proper modularization, this must be done different, e.g. via a + * "to be stripped" property of *this* object itself. + */ + if (StripDomains) { + count=0; + while (StripDomains[count]) { + if (strcmp((char*)(p + 1), StripDomains[count]) == 0) { + *p = '\0'; + FINALIZE; /* we are done */ + } + count++; + } + } + /* if we reach this point, we have not found any domain we should strip. Now + * we try and see if the host itself is listed in the -l command line option + * and so should be stripped also. If so, we do it and return. Please note that + * -l list FQDNs, not just the hostname part. If it did just list the hostname, the + * door would be wide-open for all kinds of mixing up of hosts. Because of this, + * you'll see comparison against the full string (pszHost) below. The termination + * still occurs at *p, which points at the first dot after the hostname. + * TODO: this must also go away - see comment above -- rgerhards, 2008-04-16 + */ + if (LocalHosts) { + count=0; + while (LocalHosts[count]) { + if (!strcmp((char*)pszHost, LocalHosts[count])) { + *p = '\0'; + break; /* we are done */ + } + count++; + } + } + } + } + +finalize_it: + RETiRet; +} + + +/* get the name of the local host. A pointer to a character pointer is passed + * in, which on exit points to the local hostname. This buffer is dynamically + * allocated and must be free()ed by the caller. If the functions returns an + * error, the pointer is NULL. This function is based on GNU/Hurd's localhostname + * function. + * rgerhards, 20080-04-10 + */ +static rsRetVal +getLocalHostname(uchar **ppName) +{ + DEFiRet; + uchar *buf = NULL; + size_t buf_len = 0; + + assert(ppName != NULL); + + do { + if(buf == NULL) { + buf_len = 128; /* Initial guess */ + CHKmalloc(buf = malloc(buf_len)); + } else { + buf_len += buf_len; + CHKmalloc(buf = realloc (buf, buf_len)); + } + } while((gethostname((char*)buf, buf_len) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG); + + *ppName = buf; + buf = NULL; + +finalize_it: + if(iRet != RS_RET_OK) { + if(buf != NULL) + free(buf); + } + RETiRet; +} + + +/* closes the UDP listen sockets (if they exist) and frees + * all dynamically assigned memory. + */ +void closeUDPListenSockets(int *pSockArr) +{ + register int i; + + assert(pSockArr != NULL); + if(pSockArr != NULL) { + for (i = 0; i < *pSockArr; i++) + close(pSockArr[i+1]); + free(pSockArr); + } +} + + +/* creates the UDP listen sockets + * hostname and/or pszPort may be NULL, but not both! + * bIsServer indicates if a server socket should be created + * 1 - server, 0 - client + */ +int *create_udp_socket(uchar *hostname, uchar *pszPort, int bIsServer) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks, on = 1; + int sockflags; + + assert(!((pszPort == NULL) && (hostname == NULL))); + memset(&hints, 0, sizeof(hints)); + if(bIsServer) + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + else + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res); + if(error) { + errmsg.LogError(NO_ERRCODE, "%s", gai_strerror(error)); + errmsg.LogError(NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n"); + return NULL; + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + socks = malloc((maxs+1) * sizeof(int)); + if (socks == NULL) { + errmsg.LogError(NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP message reception"); + freeaddrinfo(res); + return NULL; + } + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r != NULL ; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + errmsg.LogError(NO_ERRCODE, "create_udp_socket(), socket"); + /* it is debateble if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +# ifdef IPV6_V6ONLY + if (r->ai_family == AF_INET6) { + int ion = 1; + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&ion, sizeof (ion)) < 0) { + errmsg.LogError(NO_ERRCODE, "setsockopt"); + close(*s); + *s = -1; + continue; + } + } +# endif + + /* if we have an error, we "just" suspend that socket. Eventually + * other sockets will work. At the end of this function, we check + * if we managed to open at least one socket. If not, we'll write + * a "inet suspended" message and declare failure. Else we use + * what we could obtain. + * rgerhards, 2007-06-22 + */ + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0 ) { + errmsg.LogError(NO_ERRCODE, "setsockopt(REUSEADDR)"); + close(*s); + *s = -1; + continue; + } + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +#if !defined(OS_BSD) && !defined(__hpux) + if (should_use_so_bsdcompat()) { + if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + errmsg.LogError(NO_ERRCODE, "setsockopt(BSDCOMPAT)"); + close(*s); + *s = -1; + continue; + } + } +#endif + /* We must not block on the network socket, in case a packet + * gets lost between select and recv, otherwise the process + * will stall until the timeout, and other processes trying to + * log will also stall. + * Patch vom Colin Phipps to the original + * sysklogd source. Applied to rsyslogd on 2005-10-19. + */ + if ((sockflags = fcntl(*s, F_GETFL)) != -1) { + sockflags |= O_NONBLOCK; + /* SETFL could fail too, so get it caught by the subsequent + * error check. + */ + sockflags = fcntl(*s, F_SETFL, sockflags); + } + if (sockflags == -1) { + errmsg.LogError(NO_ERRCODE, "fcntl(O_NONBLOCK)"); + close(*s); + *s = -1; + continue; + } + + if(bIsServer) { + /* rgerhards, 2007-06-22: if we run on a kernel that does not support + * the IPV6_V6ONLY socket option, we need to use a work-around. On such + * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4 + * socket can not listen on the same port as an IPv6 socket. The only + * workaround is to ignore the "socket in use" error. This is what we + * do if we have to. + */ + if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) + # ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) + # endif + ) { + errmsg.LogError(NO_ERRCODE, "bind"); + close(*s); + *s = -1; + continue; + } + } + + (*socks)++; + s++; + } + + if(res != NULL) + freeaddrinfo(res); + + if(Debug && *socks != maxs) + dbgprintf("We could initialize %d UDP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", *socks, maxs); + + if(*socks == 0) { + errmsg.LogError(NO_ERRCODE, "No UDP listen socket could successfully be initialized, " + "message reception via UDP disabled.\n"); + /* we do NOT need to free any sockets, because there were none... */ + free(socks); + return(NULL); + } + + return(socks); +} + + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(net) +CODESTARTobjQueryInterface(net) + if(pIf->ifVersion != netCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->cvthname = cvthname; + /* things to go away after proper modularization */ + pIf->addAllowedSenderLine = addAllowedSenderLine; + pIf->PrintAllowedSenders = PrintAllowedSenders; + pIf->clearAllowedSenders = clearAllowedSenders; + pIf->debugListenInfo = debugListenInfo; + pIf->create_udp_socket = create_udp_socket; + pIf->closeUDPListenSockets = closeUDPListenSockets; + pIf->isAllowedSender = isAllowedSender; + pIf->should_use_so_bsdcompat = should_use_so_bsdcompat; + pIf->getLocalHostname = getLocalHostname; +finalize_it: +ENDobjQueryInterface(net) + + +/* exit our class + * rgerhards, 2008-03-10 + */ +BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */ +CODESTARTObjClassExit(net) + /* release objects we no longer need */ + objRelease(errmsg, CORE_COMPONENT); +ENDObjClassExit(net) + + +/* Initialize the net class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* set our own handlers */ +ENDObjClassInit(net) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit + netClassExit(); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + /* Initialize all classes that are in our module - this includes ourselfs */ + CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ +ENDmodInit +/* vi:set ai: + */ diff --git a/runtime/net.h b/runtime/net.h new file mode 100644 index 00000000..a0791a9d --- /dev/null +++ b/runtime/net.h @@ -0,0 +1,117 @@ +/* Definitions for network-related stuff. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#ifndef INCLUDED_NET_H +#define INCLUDED_NET_H + +#include +#include /* this is needed on HP UX -- rgerhards, 2008-03-04 */ + +#define F_SET(where, flag) (where)|=(flag) +#define F_ISSET(where, flag) ((where)&(flag))==(flag) +#define F_UNSET(where, flag) (where)&=~(flag) + +#define ADDR_NAME 0x01 /* address is hostname wildcard) */ +#define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */ + +#ifdef OS_BSD +# ifndef _KERNEL +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif + +struct NetAddr { + uint8_t flags; + union { + struct sockaddr *NetAddr; + char *HostWildcard; + } addr; +}; + +#ifndef SO_BSDCOMPAT + /* this shall prevent compiler errors due to undefined name */ +# define SO_BSDCOMPAT 0 +#endif + + +/* IPv6 compatibility layer for older platforms + * We need to handle a few things different if we are running + * on an older platform which does not support all the glory + * of IPv6. We try to limit toll on features and reliability, + * but obviously it is better to run rsyslog on a platform that + * supports everything... + * rgerhards, 2007-06-22 + */ +#ifndef AI_NUMERICSERV +# define AI_NUMERICSERV 0 +#endif + + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +#define SALEN(sa) ((sa)->sa_len) +#else +static inline size_t SALEN(struct sockaddr *sa) { + switch (sa->sa_family) { + case AF_INET: return (sizeof (struct sockaddr_in)); + case AF_INET6: return (sizeof (struct sockaddr_in6)); + default: return 0; + } +} +#endif + +struct AllowedSenders { + struct NetAddr allowedSender; /* ip address allowed */ + uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */ + struct AllowedSenders *pNext; +}; + + +/* interfaces */ +BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ + rsRetVal (*cvthname)(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN); + /* things to go away after proper modularization */ + rsRetVal (*addAllowedSenderLine)(char* pName, uchar** ppRestOfConfLine); + void (*PrintAllowedSenders)(int iListToPrint); + void (*clearAllowedSenders) (); + void (*debugListenInfo)(int fd, char *type); + int *(*create_udp_socket)(uchar *hostname, uchar *LogPort, int bIsServer); + void (*closeUDPListenSockets)(int *finet); + int (*isAllowedSender)(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost); + rsRetVal (*getLocalHostname)(uchar**); + int (*should_use_so_bsdcompat)(void); + /* data memebers - these should go away over time... TODO */ + int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ + int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ + struct AllowedSenders *pAllowedSenders_UDP; + struct AllowedSenders *pAllowedSenders_TCP; + struct AllowedSenders *pAllowedSenders_GSS; +ENDinterface(net) +#define netCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */ + +/* prototypes */ +PROTOTYPEObj(net); + +/* the name of our library binary */ +#define LM_NET_FILENAME "lmnet" + +#endif /* #ifndef INCLUDED_NET_H */ diff --git a/tcpsrv.c b/tcpsrv.c index 955fb9b5..daa373c1 100644 --- a/tcpsrv.c +++ b/tcpsrv.c @@ -572,7 +572,6 @@ Run(tcpsrv_t *pThis) for (i = 0; i < *pThis->pSocksLstn; i++) { if (FD_ISSET(pThis->pSocksLstn[i+1], &readfds)) { dbgprintf("New connect on TCP inetd socket: #%d\n", pThis->pSocksLstn[i+1]); -RUNLOG_VAR("%p", &pNewSess); SessAccept(pThis, &pNewSess, pThis->pSocksLstn[i+1]); --nfds; /* indicate we have processed one */ } -- cgit v1.2.3 From 71dea8c86fd80c911ee112439e0aab0dd222f650 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 11:45:34 +0200 Subject: cleanup: removed no longer needed files --- Makefile.am | 2 -- gss-misc.c | 1 - omfwd.c | 1 - plugins/omgssapi/omgssapi.c | 1 - runtime/net.h | 5 +++++ syslogd.c | 3 ++- tcpclt.c | 1 - tcpclt.h | 2 +- tcps_sess.h | 6 ----- tcpsyslog.c | 55 --------------------------------------------- tcpsyslog.h | 38 ------------------------------- 11 files changed, 8 insertions(+), 107 deletions(-) delete mode 100644 tcpsyslog.c delete mode 100644 tcpsyslog.h diff --git a/Makefile.am b/Makefile.am index cc3b41bb..123030ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,8 +35,6 @@ rsyslogd_SOURCES = \ template.h \ conf.c \ conf.h \ - tcpsyslog.c \ - tcpsyslog.h \ cfsysline.c \ cfsysline.h diff --git a/gss-misc.c b/gss-misc.c index a80f2e6b..d24dcf82 100644 --- a/gss-misc.c +++ b/gss-misc.c @@ -48,7 +48,6 @@ #include "omfwd.h" #include "template.h" #include "msg.h" -#include "tcpsyslog.h" #include "module-template.h" #include "obj.h" #include "errmsg.h" diff --git a/omfwd.c b/omfwd.c index 67ef4b64..ddaf496d 100644 --- a/omfwd.c +++ b/omfwd.c @@ -55,7 +55,6 @@ #include "omfwd.h" #include "template.h" #include "msg.h" -#include "tcpsyslog.h" #include "tcpclt.h" #include "cfsysline.h" #include "module-template.h" diff --git a/plugins/omgssapi/omgssapi.c b/plugins/omgssapi/omgssapi.c index 8c6a2006..078343d5 100644 --- a/plugins/omgssapi/omgssapi.c +++ b/plugins/omgssapi/omgssapi.c @@ -50,7 +50,6 @@ #include "omfwd.h" #include "template.h" #include "msg.h" -#include "tcpsyslog.h" #include "cfsysline.h" #include "module-template.h" #include "gss-misc.h" diff --git a/runtime/net.h b/runtime/net.h index a0791a9d..59199451 100644 --- a/runtime/net.h +++ b/runtime/net.h @@ -27,6 +27,11 @@ #include #include /* this is needed on HP UX -- rgerhards, 2008-03-04 */ +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; + #define F_SET(where, flag) (where)|=(flag) #define F_ISSET(where, flag) ((where)&(flag))==(flag) #define F_UNSET(where, flag) (where)&=~(flag) diff --git a/syslogd.c b/syslogd.c index bf4f5e67..ef12424b 100644 --- a/syslogd.c +++ b/syslogd.c @@ -129,6 +129,8 @@ #include #endif +#include + #include "pidfile.h" #include "srUtils.h" #include "stringbuf.h" @@ -140,7 +142,6 @@ #include "msg.h" #include "modules.h" #include "action.h" -#include "tcpsyslog.h" #include "iminternal.h" #include "cfsysline.h" #include "omshell.h" diff --git a/tcpclt.c b/tcpclt.c index 3a76e47d..824e8294 100644 --- a/tcpclt.c +++ b/tcpclt.c @@ -41,7 +41,6 @@ #include "syslogd.h" #include "syslogd-types.h" #include "net.h" -#include "tcpsyslog.h" #include "tcpclt.h" #include "module-template.h" #include "srUtils.h" diff --git a/tcpclt.h b/tcpclt.h index d2f1fe02..15344266 100644 --- a/tcpclt.h +++ b/tcpclt.h @@ -26,7 +26,7 @@ #ifndef TCPCLT_H_INCLUDED #define TCPCLT_H_INCLUDED 1 -#include "tcpsyslog.h" +//#include "tcpsyslog.h" #include "obj.h" /* the tcpclt object */ diff --git a/tcps_sess.h b/tcps_sess.h index 0433fdfb..1d45c482 100644 --- a/tcps_sess.h +++ b/tcps_sess.h @@ -28,12 +28,6 @@ /* a forward-definition, we are somewhat cyclic */ struct tcpsrv_s; -/* framing modes for TCP */ -typedef enum _TCPFRAMINGMODE { - TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ - TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ - } TCPFRAMINGMODE; - /* the tcps_sess object */ typedef struct tcps_sess_s { BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ diff --git a/tcpsyslog.c b/tcpsyslog.c deleted file mode 100644 index d00731d3..00000000 --- a/tcpsyslog.c +++ /dev/null @@ -1,55 +0,0 @@ -/* tcpsyslog.c - * This is the implementation of TCP-based syslog. It includes those - * (few) things that both clients and servers need. - * - * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_FCNTL_H -#include -#endif -#include "syslogd.h" -#include "syslogd-types.h" -#include "net.h" -#include "tcpsyslog.h" -#include "srUtils.h" - - -/* vi:set ai: - */ diff --git a/tcpsyslog.h b/tcpsyslog.h deleted file mode 100644 index 13c40a92..00000000 --- a/tcpsyslog.h +++ /dev/null @@ -1,38 +0,0 @@ -/* tcpsyslog.h - * These are the definitions for TCP-based syslog. - * - * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef TCPSYSLOG_H_INCLUDED -#define TCPSYSLOG_H_INCLUDED 1 - -#include - -typedef enum _TCPFRAMINGMODE { - TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ - TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ - } TCPFRAMINGMODE; - -#endif /* #ifndef TCPSYSLOG_H_INCLUDED */ -/* - * vi:set ai: - */ -- cgit v1.2.3 From 91661455ebf63275a849dc5c7f49c21d7837b442 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 12:48:20 +0200 Subject: provided ability to initialize the runtime --- ChangeLog | 4 ++ runtime/Makefile.am | 1 + runtime/rsyslog.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++ runtime/rsyslog.h | 6 ++ syslogd.c | 61 +++---------------- syslogd.h | 1 - 6 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 runtime/rsyslog.c diff --git a/ChangeLog b/ChangeLog index 29c12e9c..d88a124c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +- split of a "runtime library" for rsyslog - this is not yet a clean + model, because some modularization is still outstanding. In theory, + this shall enable other utilities but rsyslogd to use the same + runtime --------------------------------------------------------------------------- Version 3.17.1 (rgerhards), 2008-04-15 - removed dependency on MAXHOSTNAMELEN as much as it made sense. diff --git a/runtime/Makefile.am b/runtime/Makefile.am index 8cb3a638..6cd54f91 100644 --- a/runtime/Makefile.am +++ b/runtime/Makefile.am @@ -5,6 +5,7 @@ pkglib_LTLIBRARIES = #pkglib_LTLIBRARIES = librsyslog.la librsyslog_la_SOURCES = \ + rsyslog.c \ rsyslog.h \ atomic.h \ syslogd-types.h \ diff --git a/runtime/rsyslog.c b/runtime/rsyslog.c new file mode 100644 index 00000000..0d983bb1 --- /dev/null +++ b/runtime/rsyslog.c @@ -0,0 +1,166 @@ +/* rsyslog.c - the main entry point into rsyslog's runtime library (RTL) + * + * This module contains all function which work on a RTL global level. It's + * name is abbreviated to "rsrt" (rsyslog runtime). + * + * Please note that the runtime library is plugin-safe. That is, it must be + * initialized by calling a global initialization function. However, that + * function checks if the library is already initialized and, if so, does + * nothing except incrementing a refeence count. Similarly, the deinit + * function does nothing as long as there are still other users (which + * is tracked via the refcount). As such, it is safe to call init and + * exit multiple times, as long as this are always matching calls. This + * capability is needed for a plugin system, where one plugin never + * knows what the other did. + * + * The rsyslog runtime library is in general reentrant and thread-safe. There + * are some intentional exceptions (e.g. inside the msg object). These are + * documented. Any other threading and reentrency issue can be considered a bug. + * + * Module begun 2008-04-16 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 +#include + +#include "rsyslog.h" +#include "obj.h" +#include "vm.h" +#include "sysvar.h" +#include "stringbuf.h" +#include "wti.h" +#include "wtp.h" +#include "expr.h" +#include "ctok.h" +#include "vmop.h" +#include "vmstk.h" +#include "vmprg.h" +#include "datetime.h" +#include "queue.h" +#include "conf.h" + +/* static data */ +static int iRefCount = 0; /* our refcount - it MUST exist only once inside a process (not thread) + thus it is perfectly OK to use a static. MUST be initialized to 0! */ + +/* globally initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * If ppErrObj is provided, it receives a char pointer to the name of the object that + * caused the problem (if one occured). The caller must never free this pointer. If + * ppErrObj is NULL, no such information will be provided. pObjIF is the pointer to + * the "obj" object interface, which may be used to query any other rsyslog objects. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtInit(char **ppErrObj, obj_if_t *pObjIF) +{ + DEFiRet; + + if(iRefCount == 0) { + /* init runtime only if not yet done */ + if(ppErrObj != NULL) *ppErrObj = "obj"; + CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */ + CHKiRet(objGetObjInterface(pObjIF)); /* this provides the root pointer for all other queries */ + + /* initialize core classes. We must be very careful with the order of events. Some + * classes use others and if we do not initialize them in the right order, we may end + * up with an invalid call. The most important thing that can happen is that an error + * is detected and needs to be logged, wich in turn requires a broader number of classes + * to be available. The solution is that we take care in the order of calls AND use a + * class immediately after it is initialized. And, of course, we load those classes + * first that we use ourselfs... -- rgerhards, 2008-03-07 + */ + if(ppErrObj != NULL) *ppErrObj = "datetime"; + CHKiRet(datetimeClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "msg"; + CHKiRet(msgClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "str,"; + CHKiRet(strmClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wti"; + CHKiRet(wtiClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "wtp"; + CHKiRet(wtpClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "queue"; + CHKiRet(queueClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmstk"; + CHKiRet(vmstkClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "sysvar"; + CHKiRet(sysvarClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vm"; + CHKiRet(vmClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmop"; + CHKiRet(vmopClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "vmprg"; + CHKiRet(vmprgClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "ctok_token"; + CHKiRet(ctok_tokenClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "ctok"; + CHKiRet(ctokClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "expr"; + CHKiRet(exprClassInit(NULL)); + if(ppErrObj != NULL) *ppErrObj = "conf"; + CHKiRet(confClassInit(NULL)); + + /* dummy "classes" */ + if(ppErrObj != NULL) *ppErrObj = "str"; + CHKiRet(strInit()); + } + + ++iRefCount; + dbgprintf("rsyslog runtime initialized, version %s, current users %d\n", VERSION, iRefCount); + +finalize_it: + RETiRet; +} + + +/* globally de-initialze the runtime system + * NOTE: this is NOT thread safe and must not be called concurrently. If that + * ever poses a problem, we may use proper mutex calls - not considered needed yet. + * This function must be provided with the caller's obj object pointer. This is + * automatically deinitialized by the runtime system. + * rgerhards, 2008-04-16 + */ +rsRetVal +rsrtExit(obj_if_t *pObjIF) +{ + DEFiRet; + + if(iRefCount == 1) { + /* do actual de-init only if we are the last runtime user */ + confClassExit(); + objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + } + + --iRefCount; + /* TODO we must deinit this pointer! pObjIF = NULL; / * no longer exists for this caller */ + + dbgprintf("rsyslog runtime de-initialized, current users %d\n", iRefCount); + + RETiRet; +} + + +/* vim:set ai: + */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 2bc7f904..2dfc266b 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -46,6 +46,7 @@ /* define some base data types */ typedef struct thrdInfo thrdInfo_t; +typedef struct filed selector_t; /* TODO: this so far resides in syslogd.c, think about modularization */ /* some universal 64 bit define... */ typedef long long int64; @@ -266,6 +267,11 @@ typedef unsigned char uchar; void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2))); #include "debug.h" +#include "obj.h" + +/* some runtime prototypes */ +rsRetVal rsrtInit(char **ppErrObj, obj_if_t *pObjIF); +rsRetVal rsrtExit(obj_if_t *pObjIF); #endif /* multi-include protection */ /* vim:set ai: diff --git a/syslogd.c b/syslogd.c index ef12424b..68ffe5ce 100644 --- a/syslogd.c +++ b/syslogd.c @@ -152,15 +152,8 @@ #include "threads.h" #include "queue.h" #include "stream.h" -#include "wti.h" -#include "wtp.h" -#include "expr.h" -#include "ctok.h" #include "conf.h" -#include "vmop.h" -#include "vmstk.h" #include "vm.h" -#include "vmprg.h" #include "errmsg.h" #include "datetime.h" #include "sysvar.h" @@ -2830,8 +2823,9 @@ static void mainThread() } -/* Method to initialize all global classes. +/* Method to initialize all global classes and use the objects that we need. * rgerhards, 2008-01-04 + * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime */ static rsRetVal InitGlobalClasses(void) @@ -2839,67 +2833,31 @@ InitGlobalClasses(void) DEFiRet; char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */ - pErrObj = "obj"; - CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */ - CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ - /* the following classes were intialized by objClassInit() */ + /* Intialize the runtime system */ + pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */ + CHKiRet(rsrtInit(&pErrObj, &obj)); + + /* Now tell the system which classes we need ourselfs */ pErrObj = "errmsg"; CHKiRet(objUse(errmsg, CORE_COMPONENT)); pErrObj = "module"; CHKiRet(objUse(module, CORE_COMPONENT)); pErrObj = "var"; CHKiRet(objUse(var, CORE_COMPONENT)); - - /* initialize and use classes. We must be very careful with the order of events. Some - * classes use others and if we do not initialize them in the right order, we may end - * up with an invalid call. The most important thing that can happen is that an error - * is detected and needs to be logged, wich in turn requires a broader number of classes - * to be available. The solution is that we take care in the order of calls AND use a - * class immediately after it is initialized. And, of course, we load those classes - * first that we use ourselfs... -- rgerhards, 2008-03-07 - */ pErrObj = "datetime"; - CHKiRet(datetimeClassInit(NULL)); CHKiRet(objUse(datetime, CORE_COMPONENT)); - pErrObj = "msg"; - CHKiRet(msgClassInit(NULL)); - pErrObj = "str,"; - CHKiRet(strmClassInit(NULL)); - pErrObj = "wti"; - CHKiRet(wtiClassInit(NULL)); - pErrObj = "wtp"; - CHKiRet(wtpClassInit(NULL)); - pErrObj = "queue"; - CHKiRet(queueClassInit(NULL)); - pErrObj = "vmstk"; - CHKiRet(vmstkClassInit(NULL)); - pErrObj = "sysvar"; - CHKiRet(sysvarClassInit(NULL)); pErrObj = "vm"; - CHKiRet(vmClassInit(NULL)); CHKiRet(objUse(vm, CORE_COMPONENT)); - pErrObj = "vmop"; - CHKiRet(vmopClassInit(NULL)); - pErrObj = "vmprg"; - CHKiRet(vmprgClassInit(NULL)); - pErrObj = "ctok_token"; - CHKiRet(ctok_tokenClassInit(NULL)); - pErrObj = "ctok"; - CHKiRet(ctokClassInit(NULL)); pErrObj = "expr"; - CHKiRet(exprClassInit(NULL)); CHKiRet(objUse(expr, CORE_COMPONENT)); pErrObj = "conf"; - CHKiRet(confClassInit(NULL)); CHKiRet(objUse(conf, CORE_COMPONENT)); - /* dummy "classes" */ + /* intialize some dummy classes that are not part of the runtime */ pErrObj = "action"; CHKiRet(actionClassInit()); pErrObj = "template"; CHKiRet(templateInit()); - pErrObj = "str"; - CHKiRet(strInit()); /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ pErrObj = "net"; @@ -2939,7 +2897,6 @@ GlobalClassExit(void) objRelease(datetime, CORE_COMPONENT); /* TODO: implement the rest of the deinit */ - confClassExit(); #if 0 CHKiRet(datetimeClassInit(NULL)); CHKiRet(msgClassInit(NULL)); @@ -2969,7 +2926,7 @@ GlobalClassExit(void) CHKiRet(objUse(errmsg, CORE_COMPONENT)); CHKiRet(objUse(module, CORE_COMPONENT)); #endif - objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + rsrtExit(&obj); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ RETiRet; } diff --git a/syslogd.h b/syslogd.h index 46de8d28..a26af519 100644 --- a/syslogd.h +++ b/syslogd.h @@ -113,7 +113,6 @@ struct filed { linkedList_t llActList; /* list of configured actions */ }; -typedef struct filed selector_t; /* new type name */ #define MSG_PARSE_HOSTNAME 1 -- cgit v1.2.3 From d9b0c77d3e719d4c08361e62f3b067228c30f6a9 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 15:27:53 +0200 Subject: some more cleanup reduced dependencies, moved non-runtime files to its own directory except for some whom's status is unclear --- ChangeLog | 1 + Makefile.am | 51 +- action.c | 2 +- cfsysline.c | 2 +- conf.c | 7 +- configure.ac | 3 +- dirty.h | 92 ++ gss-misc.c | 3 +- iminternal.c | 190 --- iminternal.h | 49 - omdiscard.c | 121 -- omdiscard.h | 34 - omfile.c | 846 ---------- omfile.h | 34 - omfwd.c | 643 -------- omfwd.h | 34 - omshell.c | 148 -- omshell.h | 34 - omusrmsg.c | 352 ----- omusrmsg.h | 34 - outchannel.c | 2 +- parse.h | 19 +- pidfile.c | 156 -- pidfile.h | 51 - plugins/imfile/imfile.c | 2 +- plugins/imgssapi/imgssapi.c | 2 +- plugins/imklog/imklog.c | 2 +- plugins/imklog/imklog.h | 2 +- plugins/imklog/linux.c | 1 - plugins/immark/immark.c | 2 +- plugins/imrelp/imrelp.c | 2 +- plugins/imtcp/imtcp.c | 2 +- plugins/imudp/imudp.c | 2 +- plugins/imuxsock/imuxsock.c | 3 +- plugins/omgssapi/omgssapi.c | 3 +- plugins/omlibdbi/omlibdbi.c | 2 +- plugins/ommail/ommail.c | 2 +- plugins/ommysql/ommysql.c | 2 +- plugins/ompgsql/ompgsql.c | 2 +- plugins/omrelp/omrelp.c | 2 +- plugins/omsnmp/omsnmp.c | 2 +- plugins/omtesting/omtesting.c | 2 +- rsyslog.conf.5 | 728 --------- rsyslogd.8 | 375 ----- runtime/errmsg.c | 2 +- runtime/modules.c | 2 +- runtime/msg.c | 2 +- runtime/msg.h | 1 - runtime/net.c | 2 +- runtime/queue.c | 2 +- runtime/rsyslog.h | 16 + runtime/srutils.c | 2 +- runtime/stream.c | 2 +- runtime/wti.c | 2 +- runtime/wtp.c | 2 +- syslogd.c | 3446 ----------------------------------------- syslogd.h | 165 -- tcpclt.c | 2 +- tcps_sess.c | 2 +- tcpsrv.c | 2 +- template.c | 2 +- threads.c | 2 +- tools/iminternal.c | 190 +++ tools/iminternal.h | 49 + tools/omdiscard.c | 121 ++ tools/omdiscard.h | 34 + tools/omfile.c | 847 ++++++++++ tools/omfile.h | 34 + tools/omfwd.c | 643 ++++++++ tools/omfwd.h | 34 + tools/omshell.c | 148 ++ tools/omshell.h | 34 + tools/omusrmsg.c | 352 +++++ tools/omusrmsg.h | 34 + tools/pidfile.c | 156 ++ tools/pidfile.h | 51 + tools/rsyslog.conf.5 | 728 +++++++++ tools/rsyslogd.8 | 375 +++++ tools/syslogd.c | 3441 ++++++++++++++++++++++++++++++++++++++++ tools/syslogd.h | 100 ++ 80 files changed, 7530 insertions(+), 7545 deletions(-) create mode 100644 dirty.h delete mode 100644 iminternal.c delete mode 100644 iminternal.h delete mode 100644 omdiscard.c delete mode 100644 omdiscard.h delete mode 100644 omfile.c delete mode 100644 omfile.h delete mode 100644 omfwd.c delete mode 100644 omfwd.h delete mode 100644 omshell.c delete mode 100644 omshell.h delete mode 100644 omusrmsg.c delete mode 100644 omusrmsg.h delete mode 100644 pidfile.c delete mode 100644 pidfile.h delete mode 100644 rsyslog.conf.5 delete mode 100644 rsyslogd.8 delete mode 100644 syslogd.c delete mode 100644 syslogd.h create mode 100644 tools/iminternal.c create mode 100644 tools/iminternal.h create mode 100644 tools/omdiscard.c create mode 100644 tools/omdiscard.h create mode 100644 tools/omfile.c create mode 100644 tools/omfile.h create mode 100644 tools/omfwd.c create mode 100644 tools/omfwd.h create mode 100644 tools/omshell.c create mode 100644 tools/omshell.h create mode 100644 tools/omusrmsg.c create mode 100644 tools/omusrmsg.h create mode 100644 tools/pidfile.c create mode 100644 tools/pidfile.h create mode 100644 tools/rsyslog.conf.5 create mode 100644 tools/rsyslogd.8 create mode 100644 tools/syslogd.c create mode 100644 tools/syslogd.h diff --git a/ChangeLog b/ChangeLog index d88a124c..9e9cdc07 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ model, because some modularization is still outstanding. In theory, this shall enable other utilities but rsyslogd to use the same runtime +- changed directory structure, files are now better organized --------------------------------------------------------------------------- Version 3.17.1 (rgerhards), 2008-04-15 - removed dependency on MAXHOSTNAMELEN as much as it made sense. diff --git a/Makefile.am b/Makefile.am index 123030ce..ef2a0baa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,52 +1,5 @@ sbin_PROGRAMS = man_MANS = - -if ENABLE_RSYSLOGD -sbin_PROGRAMS += rsyslogd -rsyslogd_SOURCES = \ - syslogd.c \ - syslogd.h \ - omshell.c \ - omshell.h \ - omusrmsg.c \ - omusrmsg.h \ - omfwd.c \ - omfwd.h \ - omfile.c \ - omfile.h \ - omdiscard.c \ - omdiscard.h \ - iminternal.c \ - iminternal.h \ - pidfile.c \ - pidfile.h \ - \ - action.c \ - action.h \ - threads.c \ - threads.h \ - \ - parse.c \ - parse.h \ - \ - outchannel.c \ - outchannel.h \ - template.c \ - template.h \ - conf.c \ - conf.h \ - cfsysline.c \ - cfsysline.h - -rsyslogd_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags) -rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(dl_libs) $(rt_libs) $(rsrt_libs) -rsyslogd_LDFLAGS = -export-dynamic - -man_MANS += rsyslogd.8 rsyslog.conf.5 - -endif # if ENABLE_RSYSLOGD - -# now come the library plugins pkglib_LTLIBRARIES = if ENABLE_RFC3195 @@ -111,6 +64,10 @@ SUBDIRS = doc runtime . SUBDIRS += plugins/immark plugins/imuxsock plugins/imtcp plugins/imudp plugins/omtesting +if ENABLE_RSYSLOGD +SUBDIRS += tools +endif + if ENABLE_IMKLOG SUBDIRS += plugins/imklog endif diff --git a/action.c b/action.c index 39c37b5b..89ec3f74 100644 --- a/action.c +++ b/action.c @@ -34,7 +34,7 @@ #include #include -#include "syslogd.h" +#include "dirty.h" #include "template.h" #include "action.h" #include "modules.h" diff --git a/cfsysline.c b/cfsysline.c index 1fd03a46..9f2372af 100644 --- a/cfsysline.c +++ b/cfsysline.c @@ -32,7 +32,7 @@ #include #include -#include "syslogd.h" /* TODO: when the module interface & library design is done, this should be able to go away */ +#include "dirty.h" /* TODO: when the module interface & library design is done, this should be able to go away */ #include "cfsysline.h" #include "obj.h" #include "errmsg.h" diff --git a/conf.c b/conf.c index 098448c1..721ea4a7 100644 --- a/conf.c +++ b/conf.c @@ -45,7 +45,8 @@ #endif #include "rsyslog.h" -#include "syslogd.h" +#include "syslogd.h" /* this actually *is* part of the syslogd! */ +#include "dirty.h" #include "parse.h" #include "action.h" #include "template.h" @@ -57,6 +58,10 @@ #include "stringbuf.h" #include "srUtils.h" #include "errmsg.h" +#include "net.h" +#include "expr.h" +#include "ctok.h" +#include "ctok_token.h" /* forward definitions */ diff --git a/configure.ac b/configure.ac index 8ea017ee..f352457b 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ AC_PREREQ(2.61) AC_INIT([rsyslog],[3.17.1],[rsyslog@lists.adiscon.com]) AM_INIT_AUTOMAKE -AC_CONFIG_SRCDIR([syslogd.c]) +AC_CONFIG_SRCDIR([ChangeLog]) AC_CONFIG_HEADERS([config.h]) AC_GNU_SOURCE @@ -635,6 +635,7 @@ AM_CONDITIONAL(ENABLE_IMTEMPLATE, test x$enable_imtemplate = xyes) AC_CONFIG_FILES([Makefile \ runtime/Makefile \ + tools/Makefile \ doc/Makefile \ plugins/imudp/Makefile \ plugins/imtcp/Makefile \ diff --git a/dirty.h b/dirty.h new file mode 100644 index 00000000..5783daf8 --- /dev/null +++ b/dirty.h @@ -0,0 +1,92 @@ +/* This file is an aid to support non-modular object accesses + * while we do not have fully modularized everything. Once this is + * done, this file can (and should) be deleted. Presence of it + * also somewhat indicates that the runtime library is not really + * yet a runtime library, because it depends on some functionality + * residing somewhere else. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef DIRTY_H_INCLUDED +#define DIRTY_H_INCLUDED 1 + +#define MAXLINE 2048 /* maximum line length */ + +#define MSG_PARSE_HOSTNAME 1 +#define MSG_DONT_PARSE_HOSTNAME 0 + +/* Flags to logmsg(). + */ +#define NOFLAG 0x000 /* no flag is set (to be used when a flag must be specified and none is required) */ +#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ +#define SYNC_FILE 0x002 /* do fsync on file after printing */ +#define ADDDATE 0x004 /* add a date to the message */ +#define MARK 0x008 /* this message is a mark */ + +#ifdef USE_NETZIP +/* config param: minimum message size to try compression. The smaller + * the message, the less likely is any compression gain. We check for + * gain before we submit the message. But to do so we still need to + * do the (costly) compress() call. The following setting sets a size + * for which no call to compress() is done at all. This may result in + * a few more bytes being transmited but better overall performance. + * Note: I have not yet checked the minimum UDP packet size. It might be + * that we do not save anything by compressing very small messages, because + * UDP might need to pad ;) + * rgerhards, 2006-11-30 + */ +#define MIN_SIZE_FOR_COMPRESS 60 +#endif + +extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */ +extern int DisableDNS; +extern char **StripDomains; +extern char *LocalDomain; +extern char**LocalHosts; +extern uchar *LocalHostName; +extern int family; +extern int bDropMalPTRMsgs; +extern int option_DisallowWarning; + +rsRetVal submitMsg(msg_t *pMsg); +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); +rsRetVal logmsgInternal(int pri, char *msg, int flags); +rsRetVal parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType); + +/* TODO: the following 2 need to go in conf obj interface... */ +rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName); +rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl); + +/* Intervals at which we flush out "message repeated" messages, + * in seconds after previous message is logged. After each flush, + * we move to the next interval until we reach the largest. + * TODO: move this to action object! Only action.c and syslogd.c use it. + */ +extern int bActExecWhenPrevSusp; +extern int iActExecOnceInterval; +extern int MarkInterval; +extern int repeatinterval[2]; +extern int bReduceRepeatMsgs; +#define MAXREPEAT ((int)((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)) +#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) +#define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ + (f)->f_repeatcount = MAXREPEAT; \ + } +#endif /* #ifndef DIRTY_H_INCLUDED */ diff --git a/gss-misc.c b/gss-misc.c index d24dcf82..4f0df748 100644 --- a/gss-misc.c +++ b/gss-misc.c @@ -41,11 +41,10 @@ #include #endif #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "net.h" -#include "omfwd.h" #include "template.h" #include "msg.h" #include "module-template.h" diff --git a/iminternal.c b/iminternal.c deleted file mode 100644 index 60460a99..00000000 --- a/iminternal.c +++ /dev/null @@ -1,190 +0,0 @@ -/* iminternal.c - * This file set implements the internal messages input module for rsyslog. - * Note: we currently do not have an input module spec, but - * we will have one in the future. This module needs then to be - * adapted. - * - * File begun on 2007-08-03 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" - -#include -#include -#include -#include - -#include "syslogd.h" -#include "linkedlist.h" -#include "iminternal.h" - -static linkedList_t llMsgs; - - -/* destructs an iminternal object - */ -static rsRetVal iminternalDestruct(iminternal_t *pThis) -{ - DEFiRet; - - assert(pThis != NULL); - - if(pThis->pMsg != NULL) - msgDestruct(&pThis->pMsg); - - free(pThis); - - RETiRet; -} - - -/* Construct an iminternal object - */ -static rsRetVal iminternalConstruct(iminternal_t **ppThis) -{ - DEFiRet; - iminternal_t *pThis; - - assert(ppThis != NULL); - - if((pThis = (iminternal_t*) calloc(1, sizeof(iminternal_t))) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - -finalize_it: - if(iRet != RS_RET_OK) { - if(pThis != NULL) - iminternalDestruct(pThis); - } - - *ppThis = pThis; - - RETiRet; -} - - -/* add a message to the linked list - * Note: the pMsg reference counter is not incremented. Consequently, - * the caller must NOT decrement it. The caller actually hands over - * full ownership of the pMsg object. - * The interface of this function is modelled after syslogd/logmsg(), - * for which it is an "replacement". - */ -rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags) -{ - DEFiRet; - iminternal_t *pThis; - - assert(pMsg != NULL); - - CHKiRet(iminternalConstruct(&pThis)); - - pThis->pri = pri; - pThis->pMsg = pMsg; - pThis->flags = flags; - - CHKiRet(llAppend(&llMsgs, NULL, (void*) pThis)); - -finalize_it: - if(iRet != RS_RET_OK) { - dbgprintf("iminternalAddMsg() error %d - can not otherwise report this error, message lost\n", iRet); - if(pThis != NULL) - iminternalDestruct(pThis); - } - - RETiRet; -} - - -/* pull the first error message from the linked list, remove it - * from the list and return it to the caller. The caller is - * responsible for freeing the message! - */ -rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags) -{ - DEFiRet; - iminternal_t *pThis; - linkedListCookie_t llCookie = NULL; - - assert(pPri != NULL); - assert(ppMsg != NULL); - assert(pFlags != NULL); - - CHKiRet(llGetNextElt(&llMsgs, &llCookie, (void*)&pThis)); - *pPri = pThis->pri; - *pFlags = pThis->flags; - *ppMsg = pThis->pMsg; - pThis->pMsg = NULL; /* we do no longer own it - important for destructor */ - - if(llDestroyRootElt(&llMsgs) != RS_RET_OK) { - dbgprintf("Root element of iminternal linked list could not be destroyed - there is " - "nothing we can do against it, we ignore it for now. Things may go wild " - "from here on. This is most probably a program logic error.\n"); - } - -finalize_it: - RETiRet; -} - -/* tell the caller if we have any messages ready for processing. - * 0 means we have none, everything else means there is at least - * one message ready. - */ -rsRetVal iminternalHaveMsgReady(int* pbHaveOne) -{ - assert(pbHaveOne != NULL); - - return llGetNumElts(&llMsgs, pbHaveOne); -} - - -/* initialize the iminternal subsystem - * must be called once at the start of the program - */ -rsRetVal modInitIminternal(void) -{ - DEFiRet; - - iRet = llInit(&llMsgs, iminternalDestruct, NULL, NULL); - - RETiRet; -} - - -/* de-initialize the iminternal subsystem - * must be called once at the end of the program - * Note: the error list must have been pulled first. We do - * NOT care if there are any errors left - we simply destroy - * them. - */ -rsRetVal modExitIminternal(void) -{ - DEFiRet; - - iRet = llDestroy(&llMsgs); - - RETiRet; -} - -/* - * vi:set ai: - */ diff --git a/iminternal.h b/iminternal.h deleted file mode 100644 index 8dc0f171..00000000 --- a/iminternal.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Definition of the internal messages input module. - * - * Note: we currently do not have an input module spec, but - * we will have one in the future. This module needs then to be - * adapted. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ - -#ifndef IMINTERNAL_H_INCLUDED -#define IMINTERNAL_H_INCLUDED -#include "template.h" - -/* this is a single entry for a parse routine. It describes exactly - * one entry point/handler. - * The short name is cslch (Configfile SysLine CommandHandler) - */ -struct iminternal_s { /* config file sysline parse entry */ - int pri; - msg_t *pMsg; /* the message (in all its glory) */ - int flags; -}; -typedef struct iminternal_s iminternal_t; - -/* prototypes */ -rsRetVal modInitIminternal(void); -rsRetVal modExitIminternal(void); -rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags); -rsRetVal iminternalHaveMsgReady(int* pbHaveOne); -rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags); - -#endif /* #ifndef IMINTERNAL_H_INCLUDED */ diff --git a/omdiscard.c b/omdiscard.c deleted file mode 100644 index f13144e8..00000000 --- a/omdiscard.c +++ /dev/null @@ -1,121 +0,0 @@ -/* omdiscard.c - * This is the implementation of the built-in discard output module. - * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * File begun on 2007-07-24 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include "syslogd.h" -#include "syslogd-types.h" -#include "omdiscard.h" -#include "module-template.h" - -MODULE_TYPE_OUTPUT - -/* internal structures - */ -DEF_OMOD_STATIC_DATA - -typedef struct _instanceData { -} instanceData; - -/* we do not need a createInstance()! -BEGINcreateInstance -CODESTARTcreateInstance -ENDcreateInstance -*/ - - -BEGINdbgPrintInstInfo -CODESTARTdbgPrintInstInfo - /* do nothing */ -ENDdbgPrintInstInfo - - -BEGINisCompatibleWithFeature -CODESTARTisCompatibleWithFeature - /* we are not compatible with repeated msg reduction feature, so do not allow it */ -ENDisCompatibleWithFeature - - -BEGINtryResume -CODESTARTtryResume -ENDtryResume - -BEGINdoAction -CODESTARTdoAction - dbgprintf("\n"); - iRet = RS_RET_DISCARDMSG; -ENDdoAction - - -BEGINfreeInstance -CODESTARTfreeInstance - /* we do not have instance data, so we do not need to - * do anything here. -- rgerhards, 2007-07-25 - */ -ENDfreeInstance - - -BEGINparseSelectorAct -CODESTARTparseSelectorAct -CODE_STD_STRING_REQUESTparseSelectorAct(0) - pData = NULL; /* this action does not have any instance data */ - p = *pp; - - if(*p == '~') { - /* TODO: check the rest of the selector line - error reporting */ - dbgprintf("discard\n"); - } else { - iRet = RS_RET_CONFLINE_UNPROCESSED; - } -CODE_STD_FINALIZERparseSelectorAct -ENDparseSelectorAct - - -BEGINmodExit -CODESTARTmodExit -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_OMOD_QUERIES -ENDqueryEtryPt - - -BEGINmodInit(Discard) -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ -CODEmodInit_QueryRegCFSLineHdlr -ENDmodInit -/* - * vi:set ai: - */ diff --git a/omdiscard.h b/omdiscard.h deleted file mode 100644 index 116308a4..00000000 --- a/omdiscard.h +++ /dev/null @@ -1,34 +0,0 @@ -/* omdiscard.h - * These are the definitions for the built-in discard output module. - * - * File begun on 2007-07-24 by RGerhards - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef OMDISCARD_H_INCLUDED -#define OMDISCARD_H_INCLUDED 1 - -/* prototypes */ -rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); - -#endif /* #ifndef OMDISCARD_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/omfile.c b/omfile.c deleted file mode 100644 index 6a53a723..00000000 --- a/omfile.c +++ /dev/null @@ -1,846 +0,0 @@ -/* omfile.c - * This is the implementation of the build-in file output module. - * - * Handles: eTypeCONSOLE, eTypeTTY, eTypeFILE, eTypePIPE - * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "syslogd.h" -#include "syslogd-types.h" -#include "srUtils.h" -#include "template.h" -#include "outchannel.h" -#include "omfile.h" -#include "cfsysline.h" -#include "module-template.h" -#include "errmsg.h" - -MODULE_TYPE_OUTPUT - -/* internal structures - */ -DEF_OMOD_STATIC_DATA -DEFobjCurrIf(errmsg) - -/* The following structure is a dynafile name cache entry. - */ -struct s_dynaFileCacheEntry { - uchar *pName; /* name currently open, if dynamic name */ - short fd; /* name associated with file name in cache */ - time_t lastUsed; /* for LRU - last access */ -}; -typedef struct s_dynaFileCacheEntry dynaFileCacheEntry; - - -/* globals for default values */ -static int iDynaFileCacheSize = 10; /* max cache for dynamic files */ -static int fCreateMode = 0644; /* mode to use when creating files */ -static int fDirCreateMode = 0644; /* mode to use when creating files */ -static int bFailOnChown; /* fail if chown fails? */ -static uid_t fileUID; /* UID to be used for newly created files */ -static uid_t fileGID; /* GID to be used for newly created files */ -static uid_t dirUID; /* UID to be used for newly created directories */ -static uid_t dirGID; /* GID to be used for newly created directories */ -static int bCreateDirs; /* auto-create directories for dynaFiles: 0 - no, 1 - yes */ -static int bEnableSync = 0;/* enable syncing of files (no dash in front of pathname in conf): 0 - no, 1 - yes */ -static uchar *pszTplName = NULL; /* name of the default template to use */ -/* end globals for default values */ - -typedef struct _instanceData { - uchar f_fname[MAXFNAME];/* file or template name (display only) */ - short fd; /* file descriptor for (current) file */ - enum { - eTypeFILE, - eTypeTTY, - eTypeCONSOLE, - eTypePIPE - } fileType; - char bDynamicName; /* 0 - static name, 1 - dynamic name (with properties) */ - int fCreateMode; /* file creation mode for open() */ - int fDirCreateMode; /* creation mode for mkdir() */ - int bCreateDirs; /* auto-create directories? */ - int bSyncFile; /* should the file by sync()'ed? 1- yes, 0- no */ - uid_t fileUID; /* IDs for creation */ - uid_t dirUID; - gid_t fileGID; - gid_t dirGID; - int bFailOnChown; /* fail creation if chown fails? */ - int iCurrElt; /* currently active cache element (-1 = none) */ - int iCurrCacheSize; /* currently cache size (1-based) */ - int iDynaFileCacheSize; /* size of file handle cache */ - /* The cache is implemented as an array. An empty element is indicated - * by a NULL pointer. Memory is allocated as needed. The following - * pointer points to the overall structure. - */ - dynaFileCacheEntry **dynCache; - off_t f_sizeLimit; /* file size limit, 0 = no limit */ - char *f_sizeLimitCmd; /* command to carry out when size limit is reached */ -} instanceData; - - -BEGINisCompatibleWithFeature -CODESTARTisCompatibleWithFeature - if(eFeat == sFEATURERepeatedMsgReduction) - iRet = RS_RET_OK; -ENDisCompatibleWithFeature - - -BEGINdbgPrintInstInfo -CODESTARTdbgPrintInstInfo - if(pData->bDynamicName) { - printf("[dynamic]\n\ttemplate='%s'" - "\tfile cache size=%d\n" - "\tcreate directories: %s\n" - "\tfile owner %d, group %d\n" - "\tdirectory owner %d, group %d\n" - "\tfail if owner/group can not be set: %s\n", - pData->f_fname, - pData->iDynaFileCacheSize, - pData->bCreateDirs ? "yes" : "no", - pData->fileUID, pData->fileGID, - pData->dirUID, pData->dirGID, - pData->bFailOnChown ? "yes" : "no" - ); - } else { /* regular file */ - printf("%s", pData->f_fname); - if (pData->fd == -1) - printf(" (unused)"); - } -ENDdbgPrintInstInfo - - -/* set the dynaFile cache size. Does some limit checking. - * rgerhards, 2007-07-31 - */ -rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal) -{ - DEFiRet; - uchar errMsg[128]; /* for dynamic error messages */ - - if(iNewVal < 1) { - snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), - "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal); - errno = 0; - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - iRet = RS_RET_VAL_OUT_OF_RANGE; - iNewVal = 1; - } else if(iNewVal > 10000) { - snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), - "DynaFileCacheSize maximum is 10,000 (%d given), changed to 10,000.", iNewVal); - errno = 0; - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - iRet = RS_RET_VAL_OUT_OF_RANGE; - iNewVal = 10000; - } - - iDynaFileCacheSize = iNewVal; - dbgprintf("DynaFileCacheSize changed to %d.\n", iNewVal); - - RETiRet; -} - - -/* Helper to cfline(). Parses a output channel name up until the first - * comma and then looks for the template specifier. Tries - * to find that template. Maps the output channel to the - * proper filed structure settings. Everything is stored in the - * filed struct. Over time, the dependency on filed might be - * removed. - * rgerhards 2005-06-21 - */ -static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts) -{ - DEFiRet; - size_t i; - struct outchannel *pOch; - char szBuf[128]; /* should be more than sufficient */ - - /* this must always be a file, because we can not set a size limit - * on a pipe... - * rgerhards 2005-06-21: later, this will be a separate type, but let's - * emulate things for the time being. When everything runs, we can - * extend it... - */ - pData->fileType = eTypeFILE; - - ++p; /* skip '$' */ - i = 0; - /* get outchannel name */ - while(*p && *p != ';' && *p != ' ' && - i < sizeof(szBuf) / sizeof(char)) { - szBuf[i++] = *p++; - } - szBuf[i] = '\0'; - - /* got the name, now look up the channel... */ - pOch = ochFind(szBuf, i); - - if(pOch == NULL) { - char errMsg[128]; - errno = 0; - snprintf(errMsg, sizeof(errMsg)/sizeof(char), - "outchannel '%s' not found - ignoring action line", - szBuf); - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - ABORT_FINALIZE(RS_RET_NOT_FOUND); - } - - /* check if there is a file name in the outchannel... */ - if(pOch->pszFileTemplate == NULL) { - char errMsg[128]; - errno = 0; - snprintf(errMsg, sizeof(errMsg)/sizeof(char), - "outchannel '%s' has no file name template - ignoring action line", - szBuf); - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - ABORT_FINALIZE(RS_RET_ERR); - } - - /* OK, we finally got a correct template. So let's use it... */ - strncpy((char*) pData->f_fname, (char*) pOch->pszFileTemplate, MAXFNAME); - pData->f_sizeLimit = pOch->uSizeLimit; - /* WARNING: It is dangerous "just" to pass the pointer. As we - * never rebuild the output channel description, this is acceptable here. - */ - pData->f_sizeLimitCmd = (char*) pOch->cmdOnSizeLimit; - -RUNLOG_VAR("%p", pszTplName); - iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, - (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName); - -finalize_it: - RETiRet; -} - - -/* rgerhards 2005-06-21: Try to resolve a size limit - * situation. This first runs the command, and then - * checks if we are still above the treshold. - * returns 0 if ok, 1 otherwise - * TODO: consider moving the initial check in here, too - */ -int resolveFileSizeLimit(instanceData *pData) -{ - uchar *pParams; - uchar *pCmd; - uchar *p; - off_t actualFileSize; - ASSERT(pData != NULL); - - if(pData->f_sizeLimitCmd == NULL) - return 1; /* nothing we can do in this case... */ - - /* the execProg() below is probably not great, but at least is is - * fairly secure now. Once we change the way file size limits are - * handled, we should also revisit how this command is run (and - * with which parameters). rgerhards, 2007-07-20 - */ - /* we first check if we have command line parameters. We assume this, - * when we have a space in the program name. If we find it, everything after - * the space is treated as a single argument. - */ - if((pCmd = (uchar*)strdup((char*)pData->f_sizeLimitCmd)) == NULL) { - /* there is not much we can do - we make syslogd close the file in this case */ - glblHadMemShortage = 1; - return 1; - } - - for(p = pCmd ; *p && *p != ' ' ; ++p) { - /* JUST SKIP */ - } - - if(*p == ' ') { - *p = '\0'; /* pretend string-end */ - pParams = p+1; - } else - pParams = NULL; - - execProg(pCmd, 1, pParams); - - pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - pData->fCreateMode); - - actualFileSize = lseek(pData->fd, 0, SEEK_END); - if(actualFileSize >= pData->f_sizeLimit) { - /* OK, it didn't work out... */ - return 1; - } - - return 0; -} - - -/* This function deletes an entry from the dynamic file name - * cache. A pointer to the cache must be passed in as well - * as the index of the to-be-deleted entry. This index may - * point to an unallocated entry, in whcih case the - * function immediately returns. Parameter bFreeEntry is 1 - * if the entry should be d_free()ed and 0 if not. - */ -static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) -{ - ASSERT(pCache != NULL); - - BEGINfunc; - - if(pCache[iEntry] == NULL) - FINALIZE; - - dbgprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry, - pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName); - /* if the name is NULL, this is an improperly initilized entry which - * needs to be discarded. In this case, neither the file is to be closed - * not the name to be freed. - */ - if(pCache[iEntry]->pName != NULL) { - close(pCache[iEntry]->fd); - d_free(pCache[iEntry]->pName); - pCache[iEntry]->pName = NULL; - } - - if(bFreeEntry) { - d_free(pCache[iEntry]); - pCache[iEntry] = NULL; - } - -finalize_it: - ENDfunc; -} - - -/* This function frees the dynamic file name cache. - */ -static void dynaFileFreeCache(instanceData *pData) -{ - register int i; - ASSERT(pData != NULL); - - BEGINfunc; - for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { - dynaFileDelCacheEntry(pData->dynCache, i, 1); - } - - if(pData->dynCache != NULL) - d_free(pData->dynCache); - ENDfunc; -} - - -/* This is a shared code for both static and dynamic files. - */ -static void prepareFile(instanceData *pData, uchar *newFileName) -{ - if(access((char*)newFileName, F_OK) == 0) { - /* file already exists */ - pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - pData->fCreateMode); - } else { - pData->fd = -1; - /* file does not exist, create it (and eventually parent directories */ - if(pData->bCreateDirs) { - /* we fist need to create parent dirs if they are missing - * We do not report any errors here ourselfs but let the code - * fall through to error handler below. - */ - if(makeFileParentDirs(newFileName, strlen((char*)newFileName), - pData->fDirCreateMode, pData->dirUID, - pData->dirGID, pData->bFailOnChown) == 0) { - pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - pData->fCreateMode); - if(pData->fd != -1) { - /* check and set uid/gid */ - if(pData->fileUID != (uid_t)-1 || pData->fileGID != (gid_t) -1) { - /* we need to set owner/group */ - if(fchown(pData->fd, pData->fileUID, - pData->fileGID) != 0) { - if(pData->bFailOnChown) { - int eSave = errno; - close(pData->fd); - pData->fd = -1; - errno = eSave; - } - /* we will silently ignore the chown() failure - * if configured to do so. - */ - } - } - } - } - } - } -} - - -/* This function handles dynamic file names. It checks if the - * requested file name is already open and, if not, does everything - * needed to switch to the it. - * Function returns 0 if all went well and non-zero otherwise. - * On successful return pData->fd must point to the correct file to - * be written. - * This is a helper to writeFile(). rgerhards, 2007-07-03 - */ -static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) -{ - time_t ttOldest; /* timestamp of oldest element */ - int iOldest; - int i; - int iFirstFree; - dynaFileCacheEntry **pCache; - - ASSERT(pData != NULL); - ASSERT(newFileName != NULL); - - pCache = pData->dynCache; - - /* first check, if we still have the current file - * I *hope* this will be a performance enhancement. - */ - if( (pData->iCurrElt != -1) - && !strcmp((char*) newFileName, (char*) pCache[pData->iCurrElt]->pName)) { - /* great, we are all set */ - pCache[pData->iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */ - return 0; - } - - /* ok, no luck. Now let's search the table if we find a matching spot. - * While doing so, we also prepare for creation of a new one. - */ - iFirstFree = -1; /* not yet found */ - iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */ - ttOldest = time(NULL) + 1; /* there must always be an older one */ - for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { - if(pCache[i] == NULL) { - if(iFirstFree == -1) - iFirstFree = i; - } else { /* got an element, let's see if it matches */ - if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) { - /* we found our element! */ - pData->fd = pCache[i]->fd; - pData->iCurrElt = i; - pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */ - return 0; - } - /* did not find it - so lets keep track of the counters for LRU */ - if(pCache[i]->lastUsed < ttOldest) { - ttOldest = pCache[i]->lastUsed; - iOldest = i; - } - } - } - - /* we have not found an entry */ - if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) { - /* there is space left, so set it to that index */ - iFirstFree = pData->iCurrCacheSize++; - } - - if(iFirstFree == -1) { - dynaFileDelCacheEntry(pCache, iOldest, 0); - iFirstFree = iOldest; /* this one *is* now free ;) */ - } else { - /* we need to allocate memory for the cache structure */ - pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)); - if(pCache[iFirstFree] == NULL) { - glblHadMemShortage = TRUE; - dbgprintf("prepareDynfile(): could not alloc mem, discarding this request\n"); - return -1; - } - } - - /* Ok, we finally can open the file */ - prepareFile(pData, newFileName); - - /* file is either open now or an error state set */ - if(pData->fd == -1) { - /* do not report anything if the message is an internally-generated - * message. Otherwise, we could run into a never-ending loop. The bad - * news is that we also lose errors on startup messages, but so it is. - */ - if(iMsgOpts & INTERNAL_MSG) - dbgprintf("Could not open dynaFile, discarding message\n"); - else - errmsg.LogError(NO_ERRCODE, "Could not open dynamic file '%s' - discarding message", (char*)newFileName); - dynaFileDelCacheEntry(pCache, iFirstFree, 1); - pData->iCurrElt = -1; - return -1; - } - - pCache[iFirstFree]->fd = pData->fd; - pCache[iFirstFree]->pName = (uchar*)strdup((char*)newFileName); /* TODO: check for NULL (very unlikely) */ - pCache[iFirstFree]->lastUsed = time(NULL); - pData->iCurrElt = iFirstFree; - dbgprintf("Added new entry %d for file cache, file '%s'.\n", - iFirstFree, newFileName); - - return 0; -} - - -/* rgerhards 2004-11-11: write to a file output. This - * will be called for all outputs using file semantics, - * for example also for pipes. - */ -static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData) -{ - off_t actualFileSize; - DEFiRet; - - ASSERT(pData != NULL); - - /* first check if we have a dynamic file name and, if so, - * check if it still is ok or a new file needs to be created - */ - if(pData->bDynamicName) { - if(prepareDynFile(pData, ppString[1], iMsgOpts) != 0) - ABORT_FINALIZE(RS_RET_ERR); - } - - /* create the message based on format specified */ -again: - /* check if we have a file size limit and, if so, - * obey to it. - */ - if(pData->f_sizeLimit != 0) { - actualFileSize = lseek(pData->fd, 0, SEEK_END); - if(actualFileSize >= pData->f_sizeLimit) { - char errMsg[256]; - /* for now, we simply disable a file once it is - * beyond the maximum size. This is better than having - * us aborted by the OS... rgerhards 2005-06-21 - */ - (void) close(pData->fd); - /* try to resolve the situation */ - if(resolveFileSizeLimit(pData) != 0) { - /* didn't work out, so disable... */ - snprintf(errMsg, sizeof(errMsg), - "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation", - pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); - errno = 0; - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - ABORT_FINALIZE(RS_RET_DISABLE_ACTION); - } else { - snprintf(errMsg, sizeof(errMsg), - "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation", - pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); - errno = 0; - errmsg.LogError(NO_ERRCODE, "%s", errMsg); - } - } - } - - if (write(pData->fd, ppString[0], strlen((char*)ppString[0])) < 0) { - int e = errno; - - /* If a named pipe is full, just ignore it for now - - mrn 24 May 96 */ - if (pData->fileType == eTypePIPE && e == EAGAIN) - ABORT_FINALIZE(RS_RET_OK); - - /* If the filesystem is filled up, just ignore - * it for now and continue writing when possible - * based on patch for sysklogd by Martin Schulze on 2007-05-24 - */ - if (pData->fileType == eTypeFILE && e == ENOSPC) - ABORT_FINALIZE(RS_RET_OK); - - (void) close(pData->fd); - /* - * Check for EBADF on TTY's due to vhangup() - * Linux uses EIO instead (mrn 12 May 96) - */ - if ((pData->fileType == eTypeTTY || pData->fileType == eTypeCONSOLE) -#ifdef linux - && e == EIO) { -#else - && e == EBADF) { -#endif - pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_NOCTTY); - if (pData->fd < 0) { - iRet = RS_RET_DISABLE_ACTION; - errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); - } else { - untty(); - goto again; - } - } else { - iRet = RS_RET_DISABLE_ACTION; - errno = e; - errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); - } - } else if (pData->bSyncFile) { - fsync(pData->fd); - } - -finalize_it: - RETiRet; -} - - -BEGINcreateInstance -CODESTARTcreateInstance - pData->fd = -1; -ENDcreateInstance - - -BEGINfreeInstance -CODESTARTfreeInstance - if(pData->bDynamicName) { - dynaFileFreeCache(pData); - } else if(pData->fd != -1) - close(pData->fd); -ENDfreeInstance - - -BEGINtryResume -CODESTARTtryResume -ENDtryResume - -BEGINdoAction -CODESTARTdoAction - dbgprintf(" (%s)\n", pData->f_fname); - /* pData->fd == -1 is an indicator that the we couldn't - * open the file at startup. For dynaFiles, this is ok, - * all others are doomed. - */ - if(pData->bDynamicName || (pData->fd != -1)) - iRet = writeFile(ppString, iMsgOpts, pData); -ENDdoAction - - -BEGINparseSelectorAct -CODESTARTparseSelectorAct - /* yes, the if below is redundant, but I need it now. Will go away as - * the code further changes. -- rgerhards, 2007-07-25 - */ - if(*p == '$' || *p == '?' || *p == '|' || *p == '/' || *p == '-') { - if((iRet = createInstance(&pData)) != RS_RET_OK) { - ENDfunc - return iRet; /* this can not use RET_iRet! */ - } - } else { - /* this is not clean, but we need it for the time being - * TODO: remove when cleaning up modularization - */ - ENDfunc - return RS_RET_CONFLINE_UNPROCESSED; - } - - if(*p == '-') { - pData->bSyncFile = 0; - p++; - } else { - pData->bSyncFile = bEnableSync ? 1 : 0; - } - - pData->f_sizeLimit = 0; /* default value, use outchannels to configure! */ - - switch (*p) - { - case '$': - CODE_STD_STRING_REQUESTparseSelectorAct(1) - /* rgerhards 2005-06-21: this is a special setting for output-channel - * definitions. In the long term, this setting will probably replace - * anything else, but for the time being we must co-exist with the - * traditional mode lines. - * rgerhards, 2007-07-24: output-channels will go away. We keep them - * for compatibility reasons, but seems to have been a bad idea. - */ - if((iRet = cflineParseOutchannel(pData, p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) == RS_RET_OK) { - pData->bDynamicName = 0; - pData->fCreateMode = fCreateMode; /* preserve current setting */ - pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */ - pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - pData->fCreateMode); - } - break; - - case '?': /* This is much like a regular file handle, but we need to obtain - * a template name. rgerhards, 2007-07-03 - */ - CODE_STD_STRING_REQUESTparseSelectorAct(2) - ++p; /* eat '?' */ - if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, - (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) - != RS_RET_OK) - break; - /* "filename" is actually a template name, we need this as string 1. So let's add it - * to the pOMSR. -- rgerhards, 2007-07-27 - */ - if((iRet = OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) pData->f_fname), OMSR_NO_RQD_TPL_OPTS)) != RS_RET_OK) - break; - - pData->bDynamicName = 1; - pData->iCurrElt = -1; /* no current element */ - pData->fCreateMode = fCreateMode; /* freeze current setting */ - pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */ - pData->bCreateDirs = bCreateDirs; - pData->bFailOnChown = bFailOnChown; - pData->fileUID = fileUID; - pData->fileGID = fileGID; - pData->dirUID = dirUID; - pData->dirGID = dirGID; - pData->iDynaFileCacheSize = iDynaFileCacheSize; /* freeze current setting */ - /* we now allocate the cache table. We use calloc() intentionally, as we - * need all pointers to be initialized to NULL pointers. - */ - if((pData->dynCache = (dynaFileCacheEntry**) - calloc(iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))) == NULL) { - iRet = RS_RET_OUT_OF_MEMORY; - dbgprintf("Could not allocate memory for dynaFileCache - selector disabled.\n"); - } - break; - - case '|': - case '/': - CODE_STD_STRING_REQUESTparseSelectorAct(1) - /* rgerhards, 2007-0726: first check if file or pipe */ - if(*p == '|') { - pData->fileType = eTypePIPE; - ++p; - } else { - pData->fileType = eTypeFILE; - } - /* rgerhards 2004-11-17: from now, we need to have different - * processing, because after the first comma, the template name - * to use is specified. So we need to scan for the first coma first - * and then look at the rest of the line. - */ - if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, - (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) - != RS_RET_OK) - break; - - pData->bDynamicName = 0; - pData->fCreateMode = fCreateMode; /* preserve current setting */ - pData->fDirCreateMode = fDirCreateMode; - pData->bCreateDirs = bCreateDirs; - pData->bFailOnChown = bFailOnChown; - pData->fileUID = fileUID; - pData->fileGID = fileGID; - pData->dirUID = dirUID; - pData->dirGID = dirGID; - - if(pData->fileType == eTypePIPE) { - pData->fd = open((char*) pData->f_fname, O_RDWR|O_NONBLOCK); - } else { - prepareFile(pData, pData->f_fname); - } - - if ( pData->fd < 0 ){ - pData->fd = -1; - dbgprintf("Error opening log file: %s\n", pData->f_fname); - errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); - break; - } - if (isatty(pData->fd)) { - pData->fileType = eTypeTTY; - untty(); - } - if (strcmp((char*) p, ctty) == 0) - pData->fileType = eTypeCONSOLE; - break; - default: - iRet = RS_RET_CONFLINE_UNPROCESSED; - break; - } -CODE_STD_FINALIZERparseSelectorAct -ENDparseSelectorAct - - -/* Reset config variables for this module to default values. - * rgerhards, 2007-07-17 - */ -static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) -{ - fileUID = -1; - fileGID = -1; - dirUID = -1; - dirGID = -1; - bFailOnChown = 1; - iDynaFileCacheSize = 10; - fCreateMode = 0644; - fDirCreateMode = 0644; - bCreateDirs = 1; - bEnableSync = 0; - if(pszTplName != NULL) { - free(pszTplName); - pszTplName = NULL; - } - - return RS_RET_OK; -} - - -BEGINmodExit -CODESTARTmodExit - if(pszTplName != NULL) - free(pszTplName); -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_OMOD_QUERIES -ENDqueryEtryPt - - -BEGINmodInit(File) -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ -CODEmodInit_QueryRegCFSLineHdlr - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &dirUID, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &dirGID, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"fileowner", 0, eCmdHdlrUID, NULL, &fileUID, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"filegroup", 0, eCmdHdlrGID, NULL, &fileGID, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"dircreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fDirCreateMode, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fCreateMode, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &bCreateDirs, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &bFailOnChown, STD_LOADABLE_MODULE_ID)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfileenablesync", 0, eCmdHdlrBinary, NULL, &bEnableSync, STD_LOADABLE_MODULE_ID)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionfiledefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); -ENDmodInit -/* - * vi:set ai: - */ diff --git a/omfile.h b/omfile.h deleted file mode 100644 index 03e081f3..00000000 --- a/omfile.h +++ /dev/null @@ -1,34 +0,0 @@ -/* omfile.h - * These are the definitions for the build-in file output module. - * - * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef OMFILE_H_INCLUDED -#define OMFILE_H_INCLUDED 1 - -/* prototypes */ -rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); - -#endif /* #ifndef OMFILE_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/omfwd.c b/omfwd.c deleted file mode 100644 index ddaf496d..00000000 --- a/omfwd.c +++ /dev/null @@ -1,643 +0,0 @@ -/* omfwd.c - * This is the implementation of the build-in forwarding output module. - * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#ifdef SYSLOG_INET -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef USE_NETZIP -#include -#endif -#include -#include "syslogd.h" -#include "syslogd-types.h" -#include "srUtils.h" -#include "net.h" -#include "omfwd.h" -#include "template.h" -#include "msg.h" -#include "tcpclt.h" -#include "cfsysline.h" -#include "module-template.h" -#include "errmsg.h" - -MODULE_TYPE_OUTPUT - -/* internal structures - */ -DEF_OMOD_STATIC_DATA -DEFobjCurrIf(errmsg) -DEFobjCurrIf(net) -DEFobjCurrIf(tcpclt) - -typedef struct _instanceData { - char *f_hname; - short sock; /* file descriptor */ - int *pSockArray; /* sockets to use for UDP */ - enum { /* TODO: we shoud revisit these definitions */ - eDestFORW, - eDestFORW_SUSP, - eDestFORW_UNKN - } eDestState; - struct addrinfo *f_addr; - int compressionLevel; /* 0 - no compression, else level for zlib */ - char *port; - int protocol; -# define FORW_UDP 0 -# define FORW_TCP 1 - /* following fields for TCP-based delivery */ - time_t ttSuspend; /* time selector was suspended */ - tcpclt_t *pTCPClt; /* our tcpclt object */ -} instanceData; - -/* config data */ -static uchar *pszTplName = NULL; /* name of the default template to use */ - - -/* get the syslog forward port from selector_t. The passed in - * struct must be one that is setup for forwarding. - * rgerhards, 2007-06-28 - * We may change the implementation to try to lookup the port - * if it is unspecified. So far, we use the IANA default auf 514. - */ -static char *getFwdSyslogPt(instanceData *pData) -{ - assert(pData != NULL); - if(pData->port == NULL) - return("514"); - else - return(pData->port); -} - -BEGINcreateInstance -CODESTARTcreateInstance - pData->sock = -1; -ENDcreateInstance - - -BEGINisCompatibleWithFeature -CODESTARTisCompatibleWithFeature - if(eFeat == sFEATURERepeatedMsgReduction) - iRet = RS_RET_OK; -ENDisCompatibleWithFeature - - -BEGINfreeInstance -CODESTARTfreeInstance - switch (pData->eDestState) { - case eDestFORW: - case eDestFORW_SUSP: - freeaddrinfo(pData->f_addr); - /* fall through */ - case eDestFORW_UNKN: - if(pData->port != NULL) - free(pData->port); - break; - } - - /* final cleanup */ - if(pData->sock >= 0) - close(pData->sock); - if(pData->pSockArray != NULL) - net.closeUDPListenSockets(pData->pSockArray); - - if(pData->protocol == FORW_TCP) { - tcpclt.Destruct(&pData->pTCPClt); - } - - if(pData->f_hname != NULL) - free(pData->f_hname); - -ENDfreeInstance - - -BEGINdbgPrintInstInfo -CODESTARTdbgPrintInstInfo - printf("%s", pData->f_hname); -ENDdbgPrintInstInfo - - -/* Send a message via UDP - * rgehards, 2007-12-20 - */ -static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len) -{ - DEFiRet; - struct addrinfo *r; - int i; - unsigned lsent = 0; - int bSendSuccess; - - if(pData->pSockArray != NULL) { - /* we need to track if we have success sending to the remote - * peer. Success is indicated by at least one sendto() call - * succeeding. We track this be bSendSuccess. We can not simply - * rely on lsent, as a call might initially work, but a later - * call fails. Then, lsent has the error status, even though - * the sendto() succeeded. - * rgerhards, 2007-06-22 - */ - bSendSuccess = FALSE; - for (r = pData->f_addr; r; r = r->ai_next) { - for (i = 0; i < *pData->pSockArray; i++) { - lsent = sendto(pData->pSockArray[i+1], msg, len, 0, r->ai_addr, r->ai_addrlen); - if (lsent == len) { - bSendSuccess = TRUE; - break; - } else { - int eno = errno; - char errStr[1024]; - dbgprintf("sendto() error: %d = %s.\n", - eno, rs_strerror_r(eno, errStr, sizeof(errStr))); - } - } - if (lsent == len && !send_to_all) - break; - } - /* finished looping */ - if (bSendSuccess == FALSE) { - dbgprintf("error forwarding via udp, suspending\n"); - iRet = RS_RET_SUSPENDED; - } - } - - RETiRet; -} - -/* CODE FOR SENDING TCP MESSAGES */ - - -/* Send a frame via plain TCP protocol - * rgerhards, 2007-12-28 - */ -static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len) -{ - DEFiRet; - ssize_t lenSend; - instanceData *pData = (instanceData *) pvData; - - lenSend = send(pData->sock, msg, len, 0); - dbgprintf("TCP sent %ld bytes, requested %ld\n", (long) lenSend, (long) len); - - if(lenSend == -1) { - /* we have an error case - check what we can live with */ - switch(errno) { - case EMSGSIZE: - dbgprintf("message not (tcp)send, too large\n"); - /* This is not a real error, so it is not flagged as one */ - break; - default: - dbgprintf("message not (tcp)send"); - iRet = RS_RET_TCP_SEND_ERROR; - break; - } - } else if(lenSend != (ssize_t) len) { - /* no real error, could "just" not send everything... - * For the time being, we ignore this... - * rgerhards, 2005-10-25 - */ - dbgprintf("message not completely (tcp)send, ignoring %ld\n", (long) lenSend); - usleep(1000); /* experimental - might be benefitial in this situation */ - /* TODO: we need to revisit this code -- rgerhards, 2007-12-28 */ - } - - RETiRet; -} - - -/* This function is called immediately before a send retry is attempted. - * It shall clean up whatever makes sense. - * rgerhards, 2007-12-28 - */ -static rsRetVal TCPSendPrepRetry(void *pvData) -{ - DEFiRet; - instanceData *pData = (instanceData *) pvData; - - assert(pData != NULL); - close(pData->sock); - pData->sock = -1; - RETiRet; -} - - -/* initialies everything so that TCPSend can work. - * rgerhards, 2007-12-28 - */ -static rsRetVal TCPSendInit(void *pvData) -{ - DEFiRet; - instanceData *pData = (instanceData *) pvData; - - assert(pData != NULL); - if(pData->sock < 0) { - if((pData->sock = tcpclt.CreateSocket(pData->f_addr)) < 0) - iRet = RS_RET_TCP_SOCKCREATE_ERR; - } - - RETiRet; -} - - -/* try to resume connection if it is not ready - * rgerhards, 2007-08-02 - */ -static rsRetVal doTryResume(instanceData *pData) -{ - DEFiRet; - struct addrinfo *res; - struct addrinfo hints; - unsigned e; - - switch (pData->eDestState) { - case eDestFORW_SUSP: - iRet = RS_RET_OK; /* the actual check happens during doAction() only */ - pData->eDestState = eDestFORW; - break; - - case eDestFORW_UNKN: - /* The remote address is not yet known and needs to be obtained */ - dbgprintf(" %s\n", pData->f_hname); - memset(&hints, 0, sizeof(hints)); - /* port must be numeric, because config file syntax requests this */ - /* TODO: this code is a duplicate from cfline() - we should later create - * a common function. - */ - hints.ai_flags = AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = pData->protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM; - if((e = getaddrinfo(pData->f_hname, - getFwdSyslogPt(pData), &hints, &res)) == 0) { - dbgprintf("%s found, resuming.\n", pData->f_hname); - pData->f_addr = res; - pData->eDestState = eDestFORW; - } else { - iRet = RS_RET_SUSPENDED; - } - break; - case eDestFORW: - /* rgerhards, 2007-09-11: this can not happen, but I've included it to - * a) make the compiler happy, b) detect any logic errors */ - assert(0); - break; - } - - RETiRet; -} - - -BEGINtryResume -CODESTARTtryResume - iRet = doTryResume(pData); -ENDtryResume - -BEGINdoAction - char *psz; /* temporary buffering */ - register unsigned l; -CODESTARTdoAction - switch (pData->eDestState) { - case eDestFORW_SUSP: - dbgprintf("internal error in omfwd.c, eDestFORW_SUSP in doAction()!\n"); - iRet = RS_RET_SUSPENDED; - break; - - case eDestFORW_UNKN: - dbgprintf("doAction eDestFORW_UNKN\n"); - iRet = doTryResume(pData); - break; - - case eDestFORW: - dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdSyslogPt(pData), - pData->protocol == FORW_UDP ? "udp" : "tcp"); - /* with UDP, check if the socket is there and, if not, alloc - * it. TODO: there should be a better place for that code. - * rgerhards, 2007-12-26 - */ - if(pData->protocol == FORW_UDP) { - if(pData->pSockArray == NULL) { - pData->pSockArray = net.create_udp_socket((uchar*)pData->f_hname, NULL, 0); - } - } - pData->ttSuspend = time(NULL); - psz = (char*) ppString[0]; - l = strlen((char*) psz); - if (l > MAXLINE) - l = MAXLINE; - -# ifdef USE_NETZIP - /* Check if we should compress and, if so, do it. We also - * check if the message is large enough to justify compression. - * The smaller the message, the less likely is a gain in compression. - * To save CPU cycles, we do not try to compress very small messages. - * What "very small" means needs to be configured. Currently, it is - * hard-coded but this may be changed to a config parameter. - * rgerhards, 2006-11-30 - */ - if(pData->compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) { - Bytef out[MAXLINE+MAXLINE/100+12] = "z"; - uLongf destLen = sizeof(out) / sizeof(Bytef); - uLong srcLen = l; - int ret; - ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz, - srcLen, pData->compressionLevel); - dbgprintf("Compressing message, length was %d now %d, return state %d.\n", - l, (int) destLen, ret); - if(ret != Z_OK) { - /* if we fail, we complain, but only in debug mode - * Otherwise, we are silent. In any case, we ignore the - * failed compression and just sent the uncompressed - * data, which is still valid. So this is probably the - * best course of action. - * rgerhards, 2006-11-30 - */ - dbgprintf("Compression failed, sending uncompressed message\n"); - } else if(destLen+1 < l) { - /* only use compression if there is a gain in using it! */ - dbgprintf("there is gain in compression, so we do it\n"); - psz = (char*) out; - l = destLen + 1; /* take care for the "z" at message start! */ - } - ++destLen; - } -# endif - - if(pData->protocol == FORW_UDP) { - /* forward via UDP */ - CHKiRet(UDPSend(pData, psz, l)); - } else { - /* forward via TCP */ - rsRetVal ret; - ret = tcpclt.Send(pData->pTCPClt, pData, psz, l); - if(ret != RS_RET_OK) { - /* error! */ - dbgprintf("error forwarding via tcp, suspending\n"); - pData->eDestState = eDestFORW_SUSP; - iRet = RS_RET_SUSPENDED; - } - } - break; - } -finalize_it: -ENDdoAction - - -BEGINparseSelectorAct - uchar *q; - int i; - int error; - int bErr; - struct addrinfo hints, *res; - TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; -CODESTARTparseSelectorAct -CODE_STD_STRING_REQUESTparseSelectorAct(1) - if(*p == '@') { - if((iRet = createInstance(&pData)) != RS_RET_OK) - goto finalize_it; - ++p; /* eat '@' */ - if(*p == '@') { /* indicator for TCP! */ - pData->protocol = FORW_TCP; - ++p; /* eat this '@', too */ - } else { - pData->protocol = FORW_UDP; - } - /* we are now after the protocol indicator. Now check if we should - * use compression. We begin to use a new option format for this: - * @(option,option)host:port - * The first option defined is "z[0..9]" where the digit indicates - * the compression level. If it is not given, 9 (best compression) is - * assumed. An example action statement might be: - * @@(z5,o)127.0.0.1:1400 - * Which means send via TCP with medium (5) compresion (z) to the local - * host on port 1400. The '0' option means that octet-couting (as in - * IETF I-D syslog-transport-tls) is to be used for framing (this option - * applies to TCP-based syslog only and is ignored when specified with UDP). - * That is not yet implemented. - * rgerhards, 2006-12-07 - */ - if(*p == '(') { - /* at this position, it *must* be an option indicator */ - do { - ++p; /* eat '(' or ',' (depending on when called) */ - /* check options */ - if(*p == 'z') { /* compression */ -# ifdef USE_NETZIP - ++p; /* eat */ - if(isdigit((int) *p)) { - int iLevel; - iLevel = *p - '0'; - ++p; /* eat */ - pData->compressionLevel = iLevel; - } else { - errmsg.LogError(NO_ERRCODE, "Invalid compression level '%c' specified in " - "forwardig action - NOT turning on compression.", - *p); - } -# else - errmsg.LogError(NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " - "with compression support - request ignored."); -# endif /* #ifdef USE_NETZIP */ - } else if(*p == 'o') { /* octet-couting based TCP framing? */ - ++p; /* eat */ - /* no further options settable */ - tcp_framing = TCP_FRAMING_OCTET_COUNTING; - } else { /* invalid option! Just skip it... */ - errmsg.LogError(NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p); - ++p; /* eat invalid option */ - } - /* the option processing is done. We now do a generic skip - * to either the next option or the end of the option - * block. - */ - while(*p && *p != ')' && *p != ',') - ++p; /* just skip it */ - } while(*p && *p == ','); /* Attention: do.. while() */ - if(*p == ')') - ++p; /* eat terminator, on to next */ - else - /* we probably have end of string - leave it for the rest - * of the code to handle it (but warn the user) - */ - errmsg.LogError(NO_ERRCODE, "Option block not terminated in forwarding action."); - } - /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') - * now skip to port and then template name. rgerhards 2005-07-06 - */ - for(q = p ; *p && *p != ';' && *p != ':' ; ++p) - /* JUST SKIP */; - - pData->port = NULL; - if(*p == ':') { /* process port */ - uchar * tmp; - - *p = '\0'; /* trick to obtain hostname (later)! */ - tmp = ++p; - for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) - /* SKIP AND COUNT */; - pData->port = malloc(i + 1); - if(pData->port == NULL) { - errmsg.LogError(NO_ERRCODE, "Could not get memory to store syslog forwarding port, " - "using default port, results may not be what you intend\n"); - /* we leave f_forw.port set to NULL, this is then handled by - * getFwdSyslogPt(). - */ - } else { - memcpy(pData->port, tmp, i); - *(pData->port + i) = '\0'; - } - } - - /* now skip to template */ - bErr = 0; - while(*p && *p != ';') { - if(*p && *p != ';' && !isspace((int) *p)) { - if(bErr == 0) { /* only 1 error msg! */ - bErr = 1; - errno = 0; - errmsg.LogError(NO_ERRCODE, "invalid selector line (port), probably not doing " - "what was intended"); - } - } - ++p; - } - - /* TODO: make this if go away! */ - if(*p == ';') { - *p = '\0'; /* trick to obtain hostname (later)! */ - CHKmalloc(pData->f_hname = strdup((char*) q)); - *p = ';'; - } else { - CHKmalloc(pData->f_hname = strdup((char*) q)); - } - - /* process template */ - CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, - (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); - - /* first set the pData->eDestState */ - memset(&hints, 0, sizeof(hints)); - /* port must be numeric, because config file syntax requests this */ - hints.ai_flags = AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = pData->protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM; - if( (error = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) != 0) { - pData->eDestState = eDestFORW_UNKN; - pData->ttSuspend = time(NULL); - } else { - pData->eDestState = eDestFORW; - pData->f_addr = res; - } - /* - * Otherwise the host might be unknown due to an - * inaccessible nameserver (perhaps on the same - * host). We try to get the ip number later, like - * FORW_SUSP. - */ - if(pData->protocol == FORW_TCP) { - /* create our tcpclt */ - CHKiRet(tcpclt.Construct(&pData->pTCPClt)); - /* and set callbacks */ - CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendInit)); - CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendFrame)); - CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendPrepRetry)); - CHKiRet(tcpclt.SetFraming(pData->pTCPClt, tcp_framing)); - } - - } else { - iRet = RS_RET_CONFLINE_UNPROCESSED; - } - - /* TODO: do we need to call freeInstance if we failed - this is a general question for - * all output modules. I'll address it lates as the interface evolves. rgerhards, 2007-07-25 - */ -CODE_STD_FINALIZERparseSelectorAct -ENDparseSelectorAct - - -BEGINmodExit -CODESTARTmodExit - /* release what we no longer need */ - objRelease(errmsg, CORE_COMPONENT); - objRelease(net, LM_NET_FILENAME); - objRelease(tcpclt, LM_TCPCLT_FILENAME); - - if(pszTplName != NULL) { - free(pszTplName); - pszTplName = NULL; - } -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_OMOD_QUERIES -ENDqueryEtryPt - - -/* Reset config variables for this module to default values. - * rgerhards, 2008-03-28 - */ -static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) -{ - if(pszTplName != NULL) { - free(pszTplName); - pszTplName = NULL; - } - - return RS_RET_OK; -} - - -BEGINmodInit(Fwd) -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ -CODEmodInit_QueryRegCFSLineHdlr - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - CHKiRet(objUse(net, LM_NET_FILENAME)); - CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); - - CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); - CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); -ENDmodInit - -#endif /* #ifdef SYSLOG_INET */ -/* vim:set ai: - */ diff --git a/omfwd.h b/omfwd.h deleted file mode 100644 index dea432e5..00000000 --- a/omfwd.h +++ /dev/null @@ -1,34 +0,0 @@ -/* omfwd.h - * These are the definitions for the build-in forwarding output module. - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef OMFWD_H_INCLUDED -#define OMFWD_H_INCLUDED 1 - -/* prototypes */ -rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); - -#endif /* #ifndef OMFWD_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/omshell.c b/omshell.c deleted file mode 100644 index 2176c101..00000000 --- a/omshell.c +++ /dev/null @@ -1,148 +0,0 @@ -/* omshell.c - * This is the implementation of the build-in shell output module. - * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * shell support was initially written by bkalkbrenner 2005-09-20 - * - * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include "syslogd.h" -#include "syslogd-types.h" -#include "srUtils.h" -#include "omshell.h" -#include "module-template.h" -#include "errmsg.h" - -MODULE_TYPE_OUTPUT - -/* internal structures - */ -DEF_OMOD_STATIC_DATA -DEFobjCurrIf(errmsg) - -typedef struct _instanceData { - uchar progName[MAXFNAME]; /* program to execute */ -} instanceData; - - -BEGINcreateInstance -CODESTARTcreateInstance -ENDcreateInstance - - -BEGINisCompatibleWithFeature -CODESTARTisCompatibleWithFeature - if(eFeat == sFEATURERepeatedMsgReduction) - iRet = RS_RET_OK; -ENDisCompatibleWithFeature - - -BEGINfreeInstance -CODESTARTfreeInstance -ENDfreeInstance - - -BEGINdbgPrintInstInfo -CODESTARTdbgPrintInstInfo - printf("%s", pData->progName); -ENDdbgPrintInstInfo - - -BEGINtryResume -CODESTARTtryResume -ENDtryResume - -BEGINdoAction -CODESTARTdoAction - /* TODO: using pData->progName is not clean from the point of - * modularization. We'll change that as we go ahead with modularization. - * rgerhards, 2007-07-20 - */ - dbgprintf("\n"); - if(execProg((uchar*) pData->progName, 1, ppString[0]) == 0) - errmsg.LogError(NO_ERRCODE, "Executing program '%s' failed", (char*)pData->progName); -ENDdoAction - - -BEGINparseSelectorAct -CODESTARTparseSelectorAct -CODE_STD_STRING_REQUESTparseSelectorAct(1) - /* yes, the if below is redundant, but I need it now. Will go away as - * the code further changes. -- rgerhards, 2007-07-25 - */ - if(*p == '^') { - if((iRet = createInstance(&pData)) != RS_RET_OK) - goto finalize_it; - } - - - switch (*p) - { - case '^': /* bkalkbrenner 2005-09-20: execute shell command */ - dbgprintf("exec\n"); - ++p; - iRet = cflineParseFileName(p, (uchar*) pData->progName, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, - (uchar*)"RSYSLOG_TraditionalFileFormat"); - break; - default: - iRet = RS_RET_CONFLINE_UNPROCESSED; - break; - } - -CODE_STD_FINALIZERparseSelectorAct -ENDparseSelectorAct - - -BEGINmodExit -CODESTARTmodExit -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_OMOD_QUERIES -ENDqueryEtryPt - - -BEGINmodInit(Shell) -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ -CODEmodInit_QueryRegCFSLineHdlr - CHKiRet(objUse(errmsg, CORE_COMPONENT)); -ENDmodInit - -/* - * vi:set ai: - */ diff --git a/omshell.h b/omshell.h deleted file mode 100644 index 3061ad07..00000000 --- a/omshell.h +++ /dev/null @@ -1,34 +0,0 @@ -/* omshell.c - * These are the definitions for the build-in shell output module. - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef ACTSHELL_H_INCLUDED -#define ACTSHELL_H_INCLUDED 1 - -/* prototypes */ -rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); - -#endif /* #ifndef ACTSHELL_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/omusrmsg.c b/omusrmsg.c deleted file mode 100644 index 42d3291d..00000000 --- a/omusrmsg.c +++ /dev/null @@ -1,352 +0,0 @@ -/* omusrmsg.c - * This is the implementation of the build-in output module for sending - * user messages. - * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) - * This file is under development and has not yet arrived at being fully - * self-contained and a real object. So far, it is mostly an excerpt - * of the "old" message code without any modifications. However, it - * helps to have things at the right place one we go to the meat of it. - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_FCNTL_H -#include -#else -#include -#endif -#if HAVE_PATHS_H -#include -#endif -#include "srUtils.h" -#include "stringbuf.h" -#include "syslogd-types.h" -#include "syslogd.h" -#include "omusrmsg.h" -#include "module-template.h" -#include "errmsg.h" - - -/* portability: */ -#ifndef _PATH_DEV -# define _PATH_DEV "/dev/" -#endif - - -MODULE_TYPE_OUTPUT - -/* internal structures - */ -DEF_OMOD_STATIC_DATA -DEFobjCurrIf(errmsg) - -typedef struct _instanceData { - int bIsWall; /* 1- is wall, 0 - individual users */ - char uname[MAXUNAMES][UNAMESZ+1]; -} instanceData; - - -BEGINcreateInstance -CODESTARTcreateInstance -ENDcreateInstance - - -BEGINisCompatibleWithFeature -CODESTARTisCompatibleWithFeature - if(eFeat == sFEATURERepeatedMsgReduction) - iRet = RS_RET_OK; -ENDisCompatibleWithFeature - - -BEGINfreeInstance -CODESTARTfreeInstance - /* TODO: free the instance pointer (currently a leak, will go away) */ -ENDfreeInstance - - -BEGINdbgPrintInstInfo - register int i; -CODESTARTdbgPrintInstInfo - for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++) - dbgprintf("%s, ", pData->uname[i]); -ENDdbgPrintInstInfo - - -static jmp_buf ttybuf; - -static void endtty() -{ - longjmp(ttybuf, 1); -} - -/** - * BSD setutent/getutent() replacement routines - * The following routines emulate setutent() and getutent() under - * BSD because they are not available there. We only emulate what we actually - * need! rgerhards 2005-03-18 - */ -#ifdef OS_BSD -static FILE *BSD_uf = NULL; -void setutent(void) -{ - assert(BSD_uf == NULL); - if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) { - errmsg.LogError(NO_ERRCODE, "%s", _PATH_UTMP); - return; - } -} - -struct utmp* getutent(void) -{ - static struct utmp st_utmp; - - if(fread((char *)&st_utmp, sizeof(st_utmp), 1, BSD_uf) != 1) - return NULL; - - return(&st_utmp); -} - -void endutent(void) -{ - fclose(BSD_uf); - BSD_uf = NULL; -} -#endif /* #ifdef OS_BSD */ - - -/* - * WALLMSG -- Write a message to the world at large - * - * Write the specified message to either the entire - * world, or a list of approved users. - * - * rgerhards, 2005-10-19: applying the following sysklogd patch: - * Tue May 4 16:52:01 CEST 2004: Solar Designer - * Adjust the size of a variable to prevent a buffer overflow - * should _PATH_DEV ever contain something different than "/dev/". - */ -static rsRetVal wallmsg(uchar* pMsg, instanceData *pData) -{ - - char p[sizeof(_PATH_DEV) + UNAMESZ]; - register int i; - int ttyf; - static int reenter = 0; - struct utmp ut; - struct utmp *uptr; - struct sigaction sigAct; - - assert(pMsg != NULL); - - if (reenter++) - return RS_RET_OK; - - /* open the user login file */ - setutent(); - - /* - * Might as well fork instead of using nonblocking I/O - * and doing notty(). - */ - if (fork() == 0) { - memset(&sigAct, 0, sizeof(sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = SIG_DFL; - sigaction(SIGTERM, &sigAct, NULL); - alarm(0); - -# ifdef SIGTTOU - sigAct.sa_handler = SIG_DFL; - sigaction(SIGTERM, &sigAct, NULL); -# endif - /* It is save to call sigprocmask here, as we are now executing the child (no threads) */ - sigprocmask(SIG_SETMASK, &sigAct.sa_mask, NULL); - /* TODO: find a way to limit the max size of the message. hint: this - * should go into the template! - */ - - /* rgerhards 2005-10-24: HINT: this code might be run in a seperate thread - * instead of a seperate process once we have multithreading... - */ - - /* scan the user login file */ - while ((uptr = getutent())) { - memcpy(&ut, uptr, sizeof(ut)); - /* is this slot used? */ - if (ut.ut_name[0] == '\0') - continue; -#ifndef OS_BSD - if (ut.ut_type != USER_PROCESS) - continue; -#endif - if (!(strncmp (ut.ut_name,"LOGIN", 6))) /* paranoia */ - continue; - - /* should we send the message to this user? */ - if (pData->bIsWall == 0) { - for (i = 0; i < MAXUNAMES; i++) { - if (!pData->uname[i][0]) { - i = MAXUNAMES; - break; - } - if (strncmp(pData->uname[i], - ut.ut_name, UNAMESZ) == 0) - break; - } - if (i >= MAXUNAMES) - continue; - } - - /* compute the device name */ - strcpy(p, _PATH_DEV); - strncat(p, ut.ut_line, UNAMESZ); - - if (setjmp(ttybuf) == 0) { - sigAct.sa_handler = endtty; - sigaction(SIGALRM, &sigAct, NULL); - (void) alarm(15); - /* open the terminal */ - ttyf = open(p, O_WRONLY|O_NOCTTY); - if (ttyf >= 0) { - struct stat statb; - - if (fstat(ttyf, &statb) == 0 && - (statb.st_mode & S_IWRITE)) { - (void) write(ttyf, pMsg, strlen((char*)pMsg)); - } - close(ttyf); - ttyf = -1; - } - } - (void) alarm(0); - } - exit(0); /* "good" exit - this terminates the child forked just for message delivery */ - } - /* close the user login file */ - endutent(); - reenter = 0; - return RS_RET_OK; -} - - -BEGINtryResume -CODESTARTtryResume -ENDtryResume - -BEGINdoAction -CODESTARTdoAction - dbgprintf("\n"); - iRet = wallmsg(ppString[0], pData); -ENDdoAction - - -BEGINparseSelectorAct - uchar *q; - int i; -CODESTARTparseSelectorAct -CODE_STD_STRING_REQUESTparseSelectorAct(1) - - /* User names must begin with a gnu e-regex: - * [a-zA-Z0-9_.] - * plus '*' for wall - */ - if (!*p || !((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') - || (*p >= '0' && *p <= '9') || *p == '_' || *p == '.' || *p == '*')) - ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); - - if((iRet = createInstance(&pData)) != RS_RET_OK) - goto finalize_it; - - - if(*p == '*') { /* wall */ - dbgprintf("write-all"); - ++p; /* eat '*' */ - pData->bIsWall = 1; /* write to all users */ - if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " WallFmt")) - != RS_RET_OK) - goto finalize_it; - } else { - /* everything else beginning with the regex above - * is currently treated as a user name - * TODO: is this portable? - */ - dbgprintf("users: %s\n", p); /* ASP */ - pData->bIsWall = 0; /* write to individual users */ - for (i = 0; i < MAXUNAMES && *p && *p != ';'; i++) { - for (q = p; *q && *q != ',' && *q != ';'; ) - q++; - (void) strncpy((char*) pData->uname[i], (char*) p, UNAMESZ); - if ((q - p) > UNAMESZ) - pData->uname[i][UNAMESZ] = '\0'; - else - pData->uname[i][q - p] = '\0'; - while (*q == ',' || *q == ' ') - q++; - p = q; - } - /* done, on to the template - * TODO: we need to handle the case where i >= MAXUNAME! - */ - if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*)" StdUsrMsgFmt")) - != RS_RET_OK) - goto finalize_it; - } -CODE_STD_FINALIZERparseSelectorAct -ENDparseSelectorAct - - -BEGINmodExit -CODESTARTmodExit -ENDmodExit - - -BEGINqueryEtryPt -CODESTARTqueryEtryPt -CODEqueryEtryPt_STD_OMOD_QUERIES -ENDqueryEtryPt - - -BEGINmodInit(UsrMsg) -CODESTARTmodInit - *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ -CODEmodInit_QueryRegCFSLineHdlr - CHKiRet(objUse(errmsg, CORE_COMPONENT)); -ENDmodInit - -/* - * vi:set ai: - */ diff --git a/omusrmsg.h b/omusrmsg.h deleted file mode 100644 index 52e780f7..00000000 --- a/omusrmsg.h +++ /dev/null @@ -1,34 +0,0 @@ -/* omusrmsg.c - * These are the definitions for the build-in user message output module. - * - * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) - * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef OMUSRMSG_H_INCLUDED -#define OMUSRMSG_H_INCLUDED 1 - -/* prototypes */ -rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); - -#endif /* #ifndef OMUSRMSG_H_INCLUDED */ -/* - * vi:set ai: - */ diff --git a/outchannel.c b/outchannel.c index d013ea08..5c348b63 100644 --- a/outchannel.c +++ b/outchannel.c @@ -37,7 +37,7 @@ #include #include "stringbuf.h" #include "outchannel.h" -#include "syslogd.h" +#include "dirty.h" static struct outchannel *ochRoot = NULL; /* the root of the outchannel list */ static struct outchannel *ochLast = NULL; /* points to the last element of the outchannel list */ diff --git a/parse.h b/parse.h index b7ac950d..0fe2bb74 100644 --- a/parse.h +++ b/parse.h @@ -101,24 +101,9 @@ int parsIsAtEndOfParseString(rsParsObj *pThis); int parsGetCurrentPosition(rsParsObj *pThis); char parsPeekAtCharAtParsPtr(rsParsObj *pThis); #ifdef SYSLOG_INET -rsRetVal parsAddrWithBits(rsParsObj *pThis, struct NetAddr **pIP, int *pBits); -#endif - -#if 0 /* later! - but leave it in in case we need it some day... */ -/* Parse a property - * This is a complex parsing routine. It parses an property - * entry suitable for use in the property replacer. It is currently - * just an idea if this should be a parser function. - */ -parsRet parsProp(parseObj *pThis, ?? **pPropEtry); +rsRetVal parsAddrWithBits(rsParsObj *pThis, netAddr_t **pIP, int *pBits); #endif #endif -/* - * Local variables: - * c-indent-level: 8 - * c-basic-offset: 8 - * tab-width: 8 - * End: - * vi:set ai: +/* vim:set ai: */ diff --git a/pidfile.c b/pidfile.c deleted file mode 100644 index 2be13da6..00000000 --- a/pidfile.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - pidfile.c - interact with pidfiles - Copyright (c) 1995 Martin Schulze - - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. -*/ -#include "config.h" - - -#include "rsyslog.h" - -/* - * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze - * First version (v0.2) released - */ - -#include -#include -#include -#include -#include -#include -#include -#ifdef __sun -#include -#endif - -#include "srUtils.h" - -/* read_pid - * - * Reads the specified pidfile and returns the read pid. - * 0 is returned if either there's no pidfile, it's empty - * or no pid can be read. - */ -int read_pid (char *pidfile) -{ - FILE *f; - int pid; - - if (!(f=fopen(pidfile,"r"))) - return 0; - fscanf(f,"%d", &pid); - fclose(f); - return pid; -} - -/* check_pid - * - * Reads the pid using read_pid and looks up the pid in the process - * table (using /proc) to determine if the process already exists. If - * so 1 is returned, otherwise 0. - */ -int check_pid (char *pidfile) -{ - int pid = read_pid(pidfile); - - /* Amazing ! _I_ am already holding the pid file... */ - if ((!pid) || (pid == getpid ())) - return 0; - - /* - * The 'standard' method of doing this is to try and do a 'fake' kill - * of the process. If an ESRCH error is returned the process cannot - * be found -- GW - */ - /* But... errno is usually changed only on error.. */ - if (kill(pid, 0) && errno == ESRCH) - return(0); - - return pid; -} - -/* write_pid - * - * Writes the pid to the specified file. If that fails 0 is - * returned, otherwise the pid. - */ -int write_pid (char *pidfile) -{ - FILE *f; - int fd; - int pid; - - if ( ((fd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1) - || ((f = fdopen(fd, "r+")) == NULL) ) { - fprintf(stderr, "Can't open or create %s.\n", pidfile); - return 0; - } - - /* It seems to be acceptable that we do not lock the pid file - * if we run under Solaris. In any case, it is highly unlikely - * that two instances try to access this file. And flock is really - * causing me grief on my initial steps on Solaris. Some time later, - * we might re-enable it (or use some alternate method). - * 2006-02-16 rgerhards - */ - -#if HAVE_FLOCK - if (flock(fd, LOCK_EX|LOCK_NB) == -1) { - fscanf(f, "%d", &pid); - fclose(f); - printf("Can't lock, lock is held by pid %d.\n", pid); - return 0; - } -#endif - - pid = getpid(); - if (!fprintf(f,"%d\n", pid)) { - char errStr[1024]; - rs_strerror_r(errno, errStr, sizeof(errStr)); - printf("Can't write pid , %s.\n", errStr); - close(fd); - return 0; - } - fflush(f); - -#if HAVE_FLOCK - if (flock(fd, LOCK_UN) == -1) { - char errStr[1024]; - rs_strerror_r(errno, errStr, sizeof(errStr)); - printf("Can't unlock pidfile %s, %s.\n", pidfile, errStr); - close(fd); - return 0; - } -#endif - close(fd); - - return pid; -} - -/* remove_pid - * - * Remove the the specified file. The result from unlink(2) - * is returned - */ -int remove_pid (char *pidfile) -{ - return unlink (pidfile); -} - diff --git a/pidfile.h b/pidfile.h deleted file mode 100644 index 40be9069..00000000 --- a/pidfile.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - pidfile.h - interact with pidfiles - Copyright (c) 1995 Martin Schulze - - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. -*/ - -/* read_pid - * - * Reads the specified pidfile and returns the read pid. - * 0 is returned if either there's no pidfile, it's empty - * or no pid can be read. - */ -int read_pid (char *pidfile); - -/* check_pid - * - * Reads the pid using read_pid and looks up the pid in the process - * table (using /proc) to determine if the process already exists. If - * so 1 is returned, otherwise 0. - */ -int check_pid (char *pidfile); - -/* write_pid - * - * Writes the pid to the specified file. If that fails 0 is - * returned, otherwise the pid. - */ -int write_pid (char *pidfile); - -/* remove_pid - * - * Remove the the specified file. The result from unlink(2) - * is returned - */ -int remove_pid (char *pidfile); diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c index 75e54f04..925d0175 100644 --- a/plugins/imfile/imfile.c +++ b/plugins/imfile/imfile.c @@ -36,7 +36,7 @@ # include #endif #include "rsyslog.h" /* error codes etc... */ -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" /* access to config file objects */ #include "module-template.h" /* generic module interface code - very important, read it! */ #include "srUtils.h" /* some utility functions */ diff --git a/plugins/imgssapi/imgssapi.c b/plugins/imgssapi/imgssapi.c index 74d5d5c5..c9ac45d1 100644 --- a/plugins/imgssapi/imgssapi.c +++ b/plugins/imgssapi/imgssapi.c @@ -45,7 +45,7 @@ #endif #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" #include "net.h" diff --git a/plugins/imklog/imklog.c b/plugins/imklog/imklog.c index c6fb1592..1420e1af 100644 --- a/plugins/imklog/imklog.c +++ b/plugins/imklog/imklog.c @@ -45,7 +45,7 @@ #include #include -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "obj.h" #include "msg.h" diff --git a/plugins/imklog/imklog.h b/plugins/imklog/imklog.h index a37ecc9e..0847140b 100644 --- a/plugins/imklog/imklog.h +++ b/plugins/imklog/imklog.h @@ -28,7 +28,7 @@ #define IMKLOG_H_INCLUDED 1 #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" /* interface to "drivers" * the platform specific drivers must implement these entry points. Only one diff --git a/plugins/imklog/linux.c b/plugins/imklog/linux.c index a742a456..d00723dd 100644 --- a/plugins/imklog/linux.c +++ b/plugins/imklog/linux.c @@ -32,7 +32,6 @@ #include #include #include -#include "syslogd.h" #include "cfsysline.h" #include "template.h" #include "msg.h" diff --git a/plugins/immark/immark.c b/plugins/immark/immark.c index 30118de0..1907bb25 100644 --- a/plugins/immark/immark.c +++ b/plugins/immark/immark.c @@ -37,7 +37,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" diff --git a/plugins/imrelp/imrelp.c b/plugins/imrelp/imrelp.c index b7308016..6c969261 100644 --- a/plugins/imrelp/imrelp.c +++ b/plugins/imrelp/imrelp.c @@ -38,7 +38,7 @@ #include #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" #include "net.h" diff --git a/plugins/imtcp/imtcp.c b/plugins/imtcp/imtcp.c index 7baa95f2..b7f8f0b5 100644 --- a/plugins/imtcp/imtcp.c +++ b/plugins/imtcp/imtcp.c @@ -39,7 +39,7 @@ #include #endif #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" #include "net.h" diff --git a/plugins/imudp/imudp.c b/plugins/imudp/imudp.c index cda794c3..3103c4f8 100644 --- a/plugins/imudp/imudp.c +++ b/plugins/imudp/imudp.c @@ -33,7 +33,7 @@ #include #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "net.h" #include "cfsysline.h" #include "module-template.h" diff --git a/plugins/imuxsock/imuxsock.c b/plugins/imuxsock/imuxsock.c index f8798039..3ef2c3d1 100644 --- a/plugins/imuxsock/imuxsock.c +++ b/plugins/imuxsock/imuxsock.c @@ -35,11 +35,12 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" #include "srUtils.h" #include "errmsg.h" +#include "net.h" MODULE_TYPE_INPUT diff --git a/plugins/omgssapi/omgssapi.c b/plugins/omgssapi/omgssapi.c index 078343d5..b8b0b240 100644 --- a/plugins/omgssapi/omgssapi.c +++ b/plugins/omgssapi/omgssapi.c @@ -43,11 +43,10 @@ #endif #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "net.h" -#include "omfwd.h" #include "template.h" #include "msg.h" #include "cfsysline.h" diff --git a/plugins/omlibdbi/omlibdbi.c b/plugins/omlibdbi/omlibdbi.c index a942a453..661aee6f 100644 --- a/plugins/omlibdbi/omlibdbi.c +++ b/plugins/omlibdbi/omlibdbi.c @@ -40,7 +40,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "cfsysline.h" #include "srUtils.h" diff --git a/plugins/ommail/ommail.c b/plugins/ommail/ommail.c index 218c73c9..0dda78e9 100644 --- a/plugins/ommail/ommail.c +++ b/plugins/ommail/ommail.c @@ -44,7 +44,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "cfsysline.h" diff --git a/plugins/ommysql/ommysql.c b/plugins/ommysql/ommysql.c index 0522e31d..472cb10d 100644 --- a/plugins/ommysql/ommysql.c +++ b/plugins/ommysql/ommysql.c @@ -37,7 +37,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "template.h" diff --git a/plugins/ompgsql/ompgsql.c b/plugins/ompgsql/ompgsql.c index 1d7b2eb7..77fd6a07 100644 --- a/plugins/ompgsql/ompgsql.c +++ b/plugins/ompgsql/ompgsql.c @@ -36,7 +36,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "template.h" diff --git a/plugins/omrelp/omrelp.c b/plugins/omrelp/omrelp.c index 04571682..182307f6 100644 --- a/plugins/omrelp/omrelp.c +++ b/plugins/omrelp/omrelp.c @@ -36,7 +36,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "cfsysline.h" diff --git a/plugins/omsnmp/omsnmp.c b/plugins/omsnmp/omsnmp.c index 161ec073..21165f9b 100644 --- a/plugins/omsnmp/omsnmp.c +++ b/plugins/omsnmp/omsnmp.c @@ -36,7 +36,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "cfsysline.h" #include "module-template.h" diff --git a/plugins/omtesting/omtesting.c b/plugins/omtesting/omtesting.c index 15d3cb80..411bcf88 100644 --- a/plugins/omtesting/omtesting.c +++ b/plugins/omtesting/omtesting.c @@ -49,7 +49,7 @@ #include #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "module-template.h" diff --git a/rsyslog.conf.5 b/rsyslog.conf.5 deleted file mode 100644 index 1c47f535..00000000 --- a/rsyslog.conf.5 +++ /dev/null @@ -1,728 +0,0 @@ -.\" rsyslog.conf - rsyslogd(8) configuration file -.\" Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. -.\" -.\" This file is part of the rsyslog package, an enhanced system log daemon. -.\" -.\" This program is free software; you can redistribute it and/or modify -.\" it under the terms of the GNU General Public License as published by -.\" the Free Software Foundation; either version 2 of the License, or -.\" (at your option) any later version. -.\" -.\" This program is distributed in the hope that it will be useful, -.\" but WITHOUT ANY WARRANTY; without even the implied warranty of -.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -.\" GNU General Public License for more details. -.\" -.\" You should have received a copy of the GNU General Public License -.\" along with this program; if not, write to the Free Software -.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. -.\" -.TH RSYSLOG.CONF 5 "07 April 2008" "Version 3.17.0" "Linux System Administration" -.SH NAME -rsyslog.conf \- rsyslogd(8) configuration file -.SH DESCRIPTION -The -.I rsyslog.conf -file is the main configuration file for the -.BR rsyslogd (8) -which logs system messages on *nix systems. This file specifies rules -for logging. For special features see the -.BR rsyslogd (8) -manpage. Ryslog.conf is backward-compatible with sysklogd's syslog.conf file. So if you migrate -from syklogd you can rename it and it should work. - -.B Note that this version of rsyslog ships with extensive documentation in html format. -This is provided in the ./doc subdirectory and probably -in a separate package if you installed rsyslog via a packaging system. -To use rsyslog's advanced features, you -.B need -to look at the html documentation, because the man pages only cover -basic aspects of operation. - - -.SH MODULES - -Rsyslog has a modular design. Consequently, there is a growing number -of modules. See the html documentation for their full description. - -.TP -.I omsnmp -SNMP trap output module -.TP -.I omgssapi -Output module for GSS-enabled syslog -.TP -.I ommysql -Output module for MySQL -.TP -.I omprelp -Output module for the reliable RELP protocol (prevents message loss). -For details, see below at imrelp and the html documentation. -It can be used like this: -.IP -*.* :omrelp:server:port -.IP -*.* :omrelp:192.168.0.1:2514 # actual sample -.TP -.I ompgsql -Output module for PostgreSQL -.TP -.I omlibdbi -Generic database output module (Firebird/Interbase, MS SQL, Sybase, -SQLLite, Ingres, Oracle, mSQL) -.TP -.I imfile -Input module for text files -.TP -.I imudp -Input plugin for UDP syslog. Replaces the deprecated -r option. Can be -used like this: -.IP -$ModLoad imudp -.IP -$InputUDPServerRun 514 -.TP -.I imtcp -Input plugin for plain TCP syslog. Replaces the deprecated -t -option. Can be used like this: -.IP -$ModLoad imtcp -.IP -$InputTCPServerRun 514 -.TP -.TP -.I imtcp -Input plugin for the RELP protocol. RELP can be used instead -of UDP or plain TCP syslog to provide reliable delivery of -syslog messages. Please note that plain TCP syslog does NOT -provide truly reliable delivery, with it messages may be lost -when there is a connection problem or the server shuts down. -RELP prevents message loss in those cases. -It can be used like this: -.IP -$ModLoad imrelp -.IP -$InputRELPServerRun 2514 -.TP -.I imgssapi -Input plugin for plain TCP and GSS-enable syslog -.TP -.I immark -Support for mark messages -.TP -.I imklog -Kernel logging. To include kernel log messages, you need to do -.IP -$ModLoad imklog - -Please note that the klogd daemon is no longer necessary and consequently -no longer provided by the rsyslog package. -.TP -.I imuxsock -Unix sockets, including the system log socket. You need to specify -.IP -$ModLoad imudp - -in order to receive log messages from local system processes. This -config directive should only left out if you know exactly what you -are doing. - - -.SH BASIC STRUCTURE - -Lines starting with a hash mark ('#') and empty lines are ignored. -Rsyslog.conf should contain following sections (sorted by recommended order in file): - -.TP -Global directives -Global directives set some global properties of whole rsyslog daemon, for example size of main -message queue ($MainMessageQueueSize), loading external modules ($ModLoad) and so on. -All global directives need to be specified on a line by their own and must start with -a dollar-sign. The complete list of global directives can be found in html documentation in doc -directory or online on web pages. - -.TP -Templates -Templates allow you to specify format of the logged message. They are also used for dynamic -file name generation. They have to be defined before they are used in rules. For more info -about templates see TEMPLATES section of this manpage. - -.TP -Output channels -Output channels provide an umbrella for any type of output that the user might want. -They have to be defined before they are used in rules. For more info about output channels -see OUTPUT CHANNELS section of this manpage. - -.TP -Rules (selector + action) -Every rule line consists of two fields, a selector field and an action field. These -two fields are separated by one or more spaces or tabs. The selector field specifies -a pattern of facilities and priorities belonging to the specified action. - -.SH ACTIONS -The action field of a rule describes what to do with the message. In general, message content -is written to a kind of "logfile". But also other actions might be done, like writing to a -database table or forwarding to another host. - -.SS Regular file -Typically messages are logged to real files. The file has to be specified with full pathname, -beginning with a slash ('/'). - -.B Example: -.RS -*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFormat # log to a file in the traditional format -.RE - -Note: if you would like to use high-precision timestamps in your log files, -just remove the ";RSYSLOG_TraditionalFormat". That will select the default -template, which, if not changed, uses RFC 3339 timestamps. - -.B Example: -.RS -*.* /var/log/file.log # log to a file with RFC3339 timestamps -.RE - -.SS Named pipes -This version of rsyslogd(8) has support for logging output to named pipes (fifos). A fifo or -named pipe can be used as a destination for log messages by prepending a pipe symbol ('|') -to the name of the file. This is handy for debugging. Note that the fifo must be created with -the mkfifo(1) command before rsyslogd(8) is started. - -.SS Terminal and console -If the file you specified is a tty, special tty-handling is done, same with /dev/console. - -.SS Remote machine -There are three ways to forward message: the traditional UDP transport, which is extremely -lossy but standard, the plain TCP based transport which loses messages only during certain -situations but is widely available and the RELP transport which does not lose messages -but is currently available only as part of rsyslogd 3.15.0 and above. - -To forward messages to another host via UDP, prepend the hostname with the at sign ("@"). -To forward it via plain tcp, prepend two at signs ("@@"). To forward via RELP, prepend the -string ":omrelp:" in front of the hostname. - -.B Example: -.RS -*.* @192.168.0.1 -.RE -.sp -In the example above, messages are forwarded via UDP to the machine 192.168.0.1, the destination -port defaults to 514. Due to the nature of UDP, you will probably lose some messages in transit. -If you expect high traffic volume, you can expect to lose a quite noticable number of messages -(the higher the traffic, the more likely and severe is message loss). - -.B If you would like to prevent message loss, use RELP: -.RS -*.* :omrelp:192.168.0.1:2514 -.RE -.sp -Note that a port number was given as there is no standard port for relp. - -Keep in mind that you need to load the correct input and output plugins (see "Modules" above). - -Please note that rsyslogd offers a variety of options in regarding to remote -forwarding. For full details, please see the html documentation. - -.SS List of users -Usually critical messages are also directed to ``root'' on that machine. You can specify a list -of users that shall get the message by simply writing the login. You may specify more than one -user by separating them with commas (','). If they're logged in they get the message. Don't -think a mail would be sent, that might be too late. - -.SS Everyone logged on -Emergency messages often go to all users currently online to notify them that something strange -is happening with the system. To specify this wall(1)-feature use an asterisk ('*'). - -.SS Database table -This allows logging of the message to a database table. -By default, a MonitorWare-compatible schema is required for this to work. You can -create that schema with the createDB.SQL file that came with the rsyslog package. You can also -use any other schema of your liking - you just need to define a proper template and assign this -template to the action. - -See the html documentation for further details on database logging. - -.SS Discard -If the discard action is carried out, the received message is immediately discarded. Discard -can be highly effective if you want to filter out some annoying messages that otherwise would -fill your log files. To do that, place the discard actions early in your log files. -This often plays well with property-based filters, giving you great freedom in specifying -what you do not want. - -Discard is just the single tilde character with no further parameters. -.sp -.B Example: -.RS -*.* ~ # discards everything. -.RE - - -.SS Output channel -Binds an output channel definition (see there for details) to this action. Output channel actions -must start with a $-sign, e.g. if you would like to bind your output channel definition "mychannel" -to the action, use "$mychannel". Output channels support template definitions like all all other -actions. - -.SS Shell execute -This executes a program in a subshell. The program is passed the template-generated message as the -only command line parameter. Rsyslog waits until the program terminates and only then continues to run. - -.B Example: -.RS -^program-to-execute;template -.RE - -The program-to-execute can be any valid executable. It receives the template string as a single parameter -(argv[1]). - -.SH FILTER CONDITIONS -Rsyslog offers three different types "filter conditions": -.sp 0 - * "traditional" severity and facility based selectors -.sp 0 - * property-based filters -.sp 0 - * expression-based filters -.RE - -.SS Blocks -Rsyslogd supports BSD-style blocks inside rsyslog.conf. Each block of lines is separated from -the previous block by a program or hostname specification. A block will only log messages -corresponding to the most recent program and hostname specifications given. Thus, a block which -selects "ppp" as the program, directly followed by a block that selects messages from the -hostname "dialhost", then the second block will only log messages from the ppp program on dialhost. - -.SS Selectors -.B Selectors are the traditional way of filtering syslog messages. -They have been kept in rsyslog with their original syntax, because it is well-known, highly -effective and also needed for compatibility with stock syslogd configuration files. If you just -need to filter based on priority and facility, you should do this with selector lines. They are -not second-class citizens in rsyslog and offer the best performance for this job. - -.SS Property-Based Filters -Property-based filters are unique to rsyslogd. They allow to filter on any property, like HOSTNAME, -syslogtag and msg. - -A property-based filter must start with a colon in column 0. This tells rsyslogd that it is the new -filter type. The colon must be followed by the property name, a comma, the name of the compare -operation to carry out, another comma and then the value to compare against. This value must be quoted. -There can be spaces and tabs between the commas. Property names and compare operations are -case-sensitive, so "msg" works, while "MSG" is an invalid property name. In brief, the syntax is as follows: -.sp -.RS -:property, [!]compare-operation, "value" -.RE - -The following compare-operations are currently supported: -.sp -.RS -.B contains -.RS -Checks if the string provided in value is contained in the property -.RE -.sp -.B isequal -.RS -Compares the "value" string provided and the property contents. These two values must be exactly equal to match. -.RE -.sp -.B startswith -.RS -Checks if the value is found exactly at the beginning of the property value -.RE -.sp -.B regex -.RS -Compares the property against the provided regular expression. -.RE - -.SS Expression-Based Filters -See the html documentation for this feature. - - -.SH TEMPLATES - -Every output in rsyslog uses templates - this holds true for files, user -messages and so on. Templates compatible with the stock syslogd -formats are hardcoded into rsyslogd. If no template is specified, we use -one of these hardcoded templates. Search for "template_" in syslogd.c and -you will find the hardcoded ones. - -A template consists of a template directive, a name, the actual template text -and optional options. A sample is: - -.RS -.B $template MyTemplateName,"\\\\7Text %property% some more text\\\\n", -.RE - -The "$template" is the template directive. It tells rsyslog that this line -contains a template. The backslash is an escape character. For example, \\7 rings the -bell (this is an ASCII value), \\n is a new line. The set in rsyslog is a bit restricted -currently. - -All text in the template is used literally, except for things within percent -signs. These are properties and allow you access to the contents of the syslog -message. Properties are accessed via the property replacer and it can for example -pick a substring or do date-specific formatting. More on this is the PROPERTY REPLACER -section of this manpage. - -To escape: -.sp 0 - % = \\% -.sp 0 - \\ = \\\\ --> '\\' is used to escape (as in C) -.sp 0 -$template TraditionalFormat,%timegenerated% %HOSTNAME% %syslogtag%%msg%\n" - -Properties can be accessed by the property replacer (see there for details). - -.B Please note that templates can also by used to generate selector lines with dynamic file names. -For example, if you would like to split syslog messages from different hosts -to different files (one per host), you can define the following template: - -.RS -.B $template DynFile,"/var/log/system-%HOSTNAME%.log" -.RE - -This template can then be used when defining an output selector line. It will -result in something like "/var/log/system-localhost.log" - -.SS Template options -The part is optional. It carries options influencing the template as whole. -See details below. Be sure NOT to mistake template options with property options - the -later ones are processed by the property replacer and apply to a SINGLE property, only -(and not the whole template). - -Template options are case-insensitive. Currently defined are: - -.RS -.TP -sql -format the string suitable for a SQL statement in MySQL format. This will replace single -quotes ("'") and the backslash character by their backslash-escaped counterpart -("\'" and "\\") inside each field. Please note that in MySQL configuration, the NO_BACKSLASH_ESCAPES -mode must be turned off for this format to work (this is the default). - -.TP -stdsql -format the string suitable for a SQL statement that is to be sent to a standards-compliant -sql server. This will replace single quotes ("'") by two single quotes ("''") inside each field. -You must use stdsql together with MySQL if in MySQL configuration the NO_BACKSLASH_ESCAPES -is turned on. -.RE - -Either the -.B sql -or -.B stdsql -option -.B MUST -be specified when a template is used for writing to a database, -otherwise injection might occur. Please note that due to the unfortunate fact -that several vendors have violated the sql standard and introduced their own -escape methods, it is impossible to have a single option doing all the work. -So you yourself must make sure you are using the right format. -.B If you choose the wrong one, you are still vulnerable to sql injection. - -Please note that the database writer *checks* that the sql option is present -in the template. If it is not present, the write database action is disabled. -This is to guard you against accidental forgetting it and then becoming -vulnerable to SQL injection. The sql option can also be useful with files - -especially if you want to import them into a database on another machine for -performance reasons. However, do NOT use it if you do not have a real need for -it - among others, it takes some toll on the processing time. Not much, but on -a really busy system you might notice it ;) - -The default template for the write to database action has the sql option set. - -.SS Template examples -Please note that the samples are split across multiple lines. A template MUST -NOT actually be split across multiple lines. - -A template that resembles traditional syslogd file output: -.sp -.RS -$template TraditionalFormat,"%timegenerated% %HOSTNAME% -.sp 0 -%syslogtag%%msg:::drop-last-lf%\n" -.RE - -A template that tells you a little more about the message: -.sp -.RS -$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%, -.sp 0 -%syslogtag%,%msg%\n" -.RE - -A template for RFC 3164 format: -.sp -.RS -$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" -.RE - -A template for the format traditionally used for user messages: -.sp -.RS -$template usermsg," XXXX%syslogtag%%msg%\n\r" -.RE - -And a template with the traditional wall-message format: -.sp -.RS -$template wallmsg,"\\r\\n\\7Message from syslogd@%HOSTNAME% at %timegenerated%" -.RE - -.B A template that can be used for writing to a database (please note the SQL template option) -.sp -.RS -.ad l -$template MySQLInsert,"insert iut, message, receivedat values -('%iut%', '%msg:::UPPERCASE%', '%timegenerated:::date-mysql%') -into systemevents\\r\\n", SQL - -NOTE 1: This template is embedded into core application under name -.B StdDBFmt -, so you don't need to define it. -.sp -NOTE 2: You have to have MySQL module installed to use this template. -.ad -.RE - -.SH OUTPUT CHANNELS - -Output Channels are a new concept first introduced in rsyslog 0.9.0. As of this writing, -it is most likely that they will be replaced by something different in the future. -So if you use them, be prepared to change you configuration file syntax when you upgrade -to a later release. - -Output channels are defined via an $outchannel directive. It's syntax is as follows: -.sp -.RS -.B $outchannel name,file-name,max-size,action-on-max-size -.RE - -name is the name of the output channel (not the file), file-name is the file name to be -written to, max-size the maximum allowed size and action-on-max-size a command to be issued -when the max size is reached. This command always has exactly one parameter. The binary is -that part of action-on-max-size before the first space, its parameter is everything behind -that space. - -Keep in mind that $outchannel just defines a channel with "name". It does not activate it. -To do so, you must use a selector line (see below). That selector line includes the channel -name plus an $ sign in front of it. A sample might be: -.sp -.RS -*.* $mychannel -.RE - -.SH PROPERTY REPLACER -The property replacer is a core component in rsyslogd's output system. A syslog message has -a number of well-defined properties (see below). Each of this properties can be accessed and -manipulated by the property replacer. With it, it is easy to use only part of a property value -or manipulate the value, e.g. by converting all characters to lower case. - -.SS Accessing Properties -Syslog message properties are used inside templates. They are accessed by putting them between -percent signs. Properties can be modified by the property replacer. The full syntax is as follows: -.sp -.RS -.B %propname:fromChar:toChar:options% -.RE - -propname is the name of the property to access. -.B It is case-sensitive. - -.SS Available Properties -.TP -.B msg -the MSG part of the message (aka "the message" ;)) -.TP -.B rawmsg -the message exactly as it was received from the socket. Should be useful for debugging. -.TP -.B HOSTNAME -hostname from the message -.TP -.B FROMHOST -hostname of the system the message was received from (in a relay chain, this is the system immediately -in front of us and not necessarily the original sender) -.TP -.B syslogtag -TAG from the message -.TP -.B programname -the "static" part of the tag, as defined by BSD syslogd. For example, when TAG is "named[12345]", -programname is "named". -.TP -.B PRI -PRI part of the message - undecoded (single value) -.TP -.B PRI-text -the PRI part of the message in a textual form (e.g. "syslog.info") -.TP -.B IUT -the monitorware InfoUnitType - used when talking to a MonitorWare backend (also for phpLogCon) -.TP -.B syslogfacility -the facility from the message - in numerical form -.TP -.B syslogfacility-text -the facility from the message - in text form -.TP -.B syslogseverity -severity from the message - in numerical form -.TP -.B syslogseverity-text -severity from the message - in text form -.TP -.B timegenerated -timestamp when the message was RECEIVED. Always in high resolution -.TP -.B timereported -timestamp from the message. Resolution depends on what was provided in the message (in most cases, only seconds) -.TP -.B TIMESTAMP -alias for timereported -.TP -.B PROTOCOL-VERSION -The contents of the PROTOCOL-VERSION field from IETF draft draft-ietf-syslog-protocol -.TP -.B STRUCTURED-DATA -The contents of the STRUCTURED-DATA field from IETF draft draft-ietf-syslog-protocol -.TP -.B APP-NAME -The contents of the APP-NAME field from IETF draft draft-ietf-syslog-protocol -.TP -.B PROCID -The contents of the PROCID field from IETF draft draft-ietf-syslog-protocol -.TP -.B MSGID -The contents of the MSGID field from IETF draft draft-ietf-syslog-protocol -.TP -.B $NOW -The current date stamp in the format YYYY-MM-DD -.TP -.B $YEAR -The current year (4-digit) -.TP -.B $MONTH -The current month (2-digit) -.TP -.B $DAY -The current day of the month (2-digit) -.TP -.B $HOUR -The current hour in military (24 hour) time (2-digit) -.TP -.B $MINUTE -The current minute (2-digit) - -.P -Properties starting with a $-sign are so-called system properties. These do NOT stem from the -message but are rather internally-generated. - -.SS Character Positions -FromChar and toChar are used to build substrings. They specify the offset within the string that -should be copied. Offset counting starts at 1, so if you need to obtain the first 2 characters of -the message text, you can use this syntax: "%msg:1:2%". If you do not wish to specify from and to, -but you want to specify options, you still need to include the colons. For example, if you would -like to convert the full message text to lower case, use "%msg:::lowercase%". If you would like to -extract from a position until the end of the string, you can place a dollar-sign ("$") in toChar -(e.g. %msg:10:$%, which will extract from position 10 to the end of the string). - -There is also support for -.B regular expressions. -To use them, you need to place a "R" into FromChar. -This tells rsyslog that a regular expression instead of position-based extraction is desired. The -actual regular expression -.B must -then be provided in toChar. The regular expression must be followed -by the string "--end". It denotes the end of the regular expression and will not become part of it. -If you are using regular expressions, the property replacer will return the part of the property text -that matches the regular expression. An example for a property replacer sequence with a regular -expression is: "%msg:R:.*Sev:. \\(.*\\) \\[.*--end%" - -Also, extraction can be done based on so-called "fields". To do so, place a "F" into FromChar. A field -in its current definition is anything that is delimited by a delimiter character. The delimiter by -default is TAB (US-ASCII value 9). However, if can be changed to any other US-ASCII character by -specifying a comma and the decimal US-ASCII value of the delimiter immediately after the "F". For example, -to use comma (",") as a delimiter, use this field specifier: "F,44". If your syslog data is delimited, -this is a quicker way to extract than via regular expressions (actually, a *much* quicker way). Field -counting starts at 1. Field zero is accepted, but will always lead to a "field not found" error. The same -happens if a field number higher than the number of fields in the property is requested. The field number -must be placed in the "ToChar" parameter. An example where the 3rd field (delimited by TAB) from the msg -property is extracted is as follows: "%msg:F:3%". The same example with semicolon as delimiter is -"%msg:F,59:3%". - -Please note that the special characters "F" and "R" are case-sensitive. Only upper case works, lower case -will return an error. There are no white spaces permitted inside the sequence (that will lead to error -messages and will NOT provide the intended result). - -.SS Property Options -Property options are case-insensitive. Currently, the following options are defined: -.TP -uppercase -convert property to lowercase only -.TP -lowercase -convert property text to uppercase only -.TP -drop-last-lf -The last LF in the message (if any), is dropped. Especially useful for PIX. -.TP -date-mysql -format as mysql date -.TP -date-rfc3164 -format as RFC 3164 date -.TP -date-rfc3339 -format as RFC 3339 date -.TP -escape-cc -replace control characters (ASCII value 127 and values less then 32) with an escape sequence. The sequence is "#" where charval is the 3-digit decimal value of the control character. For example, a tabulator would be replaced by "#009". -.TP -space-cc -replace control characters by spaces -.TP -drop-cc -drop control characters - the resulting string will neither contain control characters, escape sequences nor any other replacement character like space. - -.SH QUEUED OPERATIONS -Rsyslogd supports queued operations to handle offline outputs -(like remote syslogd's or database servers being down). When running in -queued mode, rsyslogd buffers messages to memory and optionally to disk -(on an as-needed basis). Queues survive rsyslogd restarts. - -It is highly suggested to use remote forwarding and database writing -in queued mode, only. - -To learn more about queued operations, see the html documentation. - -.SH FILES -.PD 0 -.TP -.I /etc/rsyslog.conf -Configuration file for -.B rsyslogd - -.SH SEE ALSO -.BR rsyslogd (8), -.BR logger (1), -.BR syslog (3) - -The complete documentation can be found in the doc folder of the rsyslog distribution or online at - -.RS -.B http://www.rsyslog.com/doc - -.RE -Please note that the man page reflects only a subset of the configuration options. Be sure to read -the html documentation for all features and details. This is especially vital if you plan to set -up a more-then-extremely-simple system. - -.SH AUTHORS -.B rsyslogd -is taken from sysklogd sources, which have been heavily modified -by Rainer Gerhards (rgerhards@adiscon.com) and others. diff --git a/rsyslogd.8 b/rsyslogd.8 deleted file mode 100644 index 2aa911d9..00000000 --- a/rsyslogd.8 +++ /dev/null @@ -1,375 +0,0 @@ -.\" Copyright 2004-2008 Rainer Gerhards and Adiscon for the rsyslog modifications -.\" May be distributed under the GNU General Public License -.\" -.TH RSYSLOGD 8 "07 April 2008" "Version 3.17.0" "Linux System Administration" -.SH NAME -rsyslogd \- reliable and extended syslogd -.SH SYNOPSIS -.B rsyslogd -.RB [ " \-4 " ] -.RB [ " \-6 " ] -.RB [ " \-A " ] -.RB [ " \-d " ] -.RB [ " \-f " -.I config file -] -.br -.RB [ " \-i " -.I pid file -] -.RB [ " \-l " -.I hostlist -] -.RB [ " \-n " ] -.br -.RB [ " \-q " ] -.RB [ " \-Q " ] -.RB [ " \-s " -.I domainlist -] -.RB [ " \-v " ] -.RB [ " \-w " ] -.RB [ " \-x " ] -.LP -.SH DESCRIPTION -.B Rsyslogd -is a system utility providing support for message logging. -Support of both internet and -unix domain sockets enables this utility to support both local -and remote logging. - -.B Note that this version of rsyslog ships with extensive documentation in html format. -This is provided in the ./doc subdirectory and probably -in a separate package if you installed rsyslog via a packaging system. -To use rsyslog's advanced features, you -.B need -to look at the html documentation, because the man pages only cover -basic aspects of operation. -.B For details and configuration examples, see the rsyslog.conf (5) -.B man page and the online documentation at http://www.rsyslog.com/doc - -.BR Rsyslogd (8) -is derived from the sysklogd package which in turn is derived from the -stock BSD sources. - -.B Rsyslogd -provides a kind of logging that many modern programs use. Every logged -message contains at least a time and a hostname field, normally a -program name field, too, but that depends on how trusty the logging -program is. The rsyslog package supports free definition of output formats -via templates. It also supports precise timestamps and writing directly -to databases. If the database option is used, tools like phpLogCon can -be used to view the log data. - -While the -.B rsyslogd -sources have been heavily modified a couple of notes -are in order. First of all there has been a systematic attempt to -ensure that rsyslogd follows its default, standard BSD behavior. Of course, -some configuration file changes are necessary in order to support the -template system. However, rsyslogd should be able to use a standard -syslog.conf and act like the orginal syslogd. However, an original syslogd -will not work correctly with a rsyslog-enhanced configuration file. At -best, it will generate funny looking file names. -The second important concept to note is that this version of rsyslogd -interacts transparently with the version of syslog found in the -standard libraries. If a binary linked to the standard shared -libraries fails to function correctly we would like an example of the -anomalous behavior. - -The main configuration file -.I /etc/rsyslog.conf -or an alternative file, given with the -.B "\-f" -option, is read at startup. Any lines that begin with the hash mark -(``#'') and empty lines are ignored. If an error occurs during parsing -the error element is ignored. It is tried to parse the rest of the line. - -.LP -.SH OPTIONS -.B Note that in version 3 of rsyslog a number of command line options -.B have been deprecated and replaced with config file directives. The -.B -c option controls the backward compatibility mode in use. -.TP -.BI "\-A" -When sending UDP messages, there are potentially multiple pathes to -the target destination. By default, -.B rsyslogd -only sends to the first target it can successfully send to. If -A -is given, messages are sent to all targets. This may improve -reliability, but may also cause message duplicaton. This option -should enabled only if it is fully understood. -.TP -.BI "\-4" -Causes -.B rsyslogd -to listen to IPv4 addresses only. -If neither -4 nor -6 is given, -.B rsyslogd -listens to all configured addresses of the system. -.TP -.BI "\-6" -Causes -.B rsyslogd -to listen to IPv6 addresses only. -If neither -4 nor -6 is given, -.B rsyslogd -listens to all configured addresses of the system. -.TP -.BI "\-c " "version" -Selects the desired backward compatibility mode. It must always be the -first option on the command line, as it influences processing of the -other options. To use the rsyslog v3 native interface, specify -c3. To -use compatibility mode , either do not use -c at all or use --c where -.IR version -is the rsyslog version that it shall be -compatible with. Using -c0 tells rsyslog to be command-line compatible -to sysklogd, which is the default if -c is not given. -.B Please note that rsyslogd issues warning messages if the -c3 -.B command line option is not given. -This is to alert you that your are running in compatibility -mode. Compatibility mode interfers with you rsyslog.conf commands and -may cause some undesired side-effects. It is meant to be used with a -plain old rsyslog.conf - if you use new features, things become -messy. So the best advice is to work through this document, convert -your options and config file and then use rsyslog in native mode. In -order to aid you in this process, rsyslog logs every -compatibility-mode config file directive it has generated. So you can -simply copy them from your logfile and paste them to the config. -.TP -.B "\-d" -Turns on debug mode. Using this the daemon will not proceed a -.BR fork (2) -to set itself in the background, but opposite to that stay in the -foreground and write much debug information on the current tty. See the -DEBUGGING section for more information. -.TP -.BI "\-f " "config file" -Specify an alternative configuration file instead of -.IR /etc/rsyslog.conf "," -which is the default. -.TP -.BI "\-i " "pid file" -Specify an alternative pid file instead of the default one. -This option must be used if multiple instances of rsyslogd should -run on a single machine. -.TP -.BI "\-l " "hostlist" -Specify a hostname that should be logged only with its simple hostname -and not the fqdn. Multiple hosts may be specified using the colon -(``:'') separator. -.TP -.B "\-n" -Avoid auto-backgrounding. This is needed especially if the -.B rsyslogd -is started and controlled by -.BR init (8). -.TP -.BI "\-q " "add hostname if DNS fails during ACL processing" -During ACL processing, hostnames are resolved to IP addreses for -performance reasons. If DNS fails during that process, the hostname -is added as wildcard text, which results in proper, but somewhat -slower operation once DNS is up again. -.TP -.BI "\-Q " "do not resolve hostnames during ACL processing" -Do not resolve hostnames to IP addresses during ACL processing. -.TP -.BI "\-s " "domainlist" -Specify a domainname that should be stripped off before -logging. Multiple domains may be specified using the colon (``:'') -separator. -Please be advised that no sub-domains may be specified but only entire -domains. For example if -.B "\-s north.de" -is specified and the host logging resolves to satu.infodrom.north.de -no domain would be cut, you will have to specify two domains like: -.BR "\-s north.de:infodrom.north.de" . -.TP -.B "\-v" -Print version and exit. -.TP -.B "\-w" -Supress warnings issued when messages are received from non-authorized -machines (those, that are in no AllowedSender list). -.TP -.B "\-x" -Disable DNS for remote messages. -.LP -.SH SIGNALS -.B Rsyslogd -reacts to a set of signals. You may easily send a signal to -.B rsyslogd -using the following: -.IP -.nf -kill -SIGNAL $(cat /var/run/syslogd.pid) -.fi -.PP -Note that -SIGNAL must be replaced with the actual signal -you are trying to send, e.g. with HUP. So it then becomes: -.IP -.nf -kill -HUP $(cat /var/run/syslogd.pid) -.fi -.PP -.TP -.B HUP -This lets -.B rsyslogd -perform a re-initialization. All open files are closed, the -configuration file (default is -.IR /etc/rsyslog.conf ")" -will be reread and the -.BR rsyslog (3) -facility is started again. -.TP -.B TERM ", " INT ", " QUIT -.B Rsyslogd -will die. -.TP -.B USR1 -Switch debugging on/off. This option can only be used if -.B rsyslogd -is started with the -.B "\-d" -debug option. -.TP -.B CHLD -Wait for childs if some were born, because of wall'ing messages. -.LP -.SH SECURITY THREATS -There is the potential for the rsyslogd daemon to be -used as a conduit for a denial of service attack. -A rogue program(mer) could very easily flood the rsyslogd daemon with -syslog messages resulting in the log files consuming all the remaining -space on the filesystem. Activating logging over the inet domain -sockets will of course expose a system to risks outside of programs or -individuals on the local machine. - -There are a number of methods of protecting a machine: -.IP 1. -Implement kernel firewalling to limit which hosts or networks have -access to the 514/UDP socket. -.IP 2. -Logging can be directed to an isolated or non-root filesystem which, -if filled, will not impair the machine. -.IP 3. -The ext2 filesystem can be used which can be configured to limit a -certain percentage of a filesystem to usage by root only. \fBNOTE\fP -that this will require rsyslogd to be run as a non-root process. -\fBALSO NOTE\fP that this will prevent usage of remote logging on the default port since -rsyslogd will be unable to bind to the 514/UDP socket. -.IP 4. -Disabling inet domain sockets will limit risk to the local machine. -.SS Message replay and spoofing -If remote logging is enabled, messages can easily be spoofed and replayed. -As the messages are transmitted in clear-text, an attacker might use -the information obtained from the packets for malicious things. Also, an -attacker might reply recorded messages or spoof a sender's IP address, -which could lead to a wrong perception of system activity. These can -be prevented by using GSS-API authentication and encryption. Be sure -to think about syslog network security before enabling it. -.LP -.SH DEBUGGING -When debugging is turned on using -.B "\-d" -option then -.B rsyslogd -will be very verbose by writing much of what it does on stdout. -.SH FILES -.PD 0 -.TP -.I /etc/rsyslog.conf -Configuration file for -.BR rsyslogd . -See -.BR rsyslog.conf (5) -for exact information. -.TP -.I /dev/log -The Unix domain socket to from where local syslog messages are read. -.TP -.I /var/run/rsyslogd.pid -The file containing the process id of -.BR rsyslogd . -.TP -.I prefix/lib/rsyslog -Default directory for -.B rsyslogd -modules. The -.I prefix -is specified during compilation (e.g. /usr/local). -.SH ENVIRONMENT -.TP -.B RSYSLOG_DEBUG -Controls runtime debug support.It contains an option string with the -following options possible (all are case insensitive): - -.RS -.IP LogFuncFlow -Print out the logical flow of functions (entering and exiting them) -.IP FileTrace -Ppecifies which files to trace LogFuncFlow. If not set (the -default), a LogFuncFlow trace is provided for all files. Set to -limit it to the files specified.FileTrace may be specified multiple -times, one file each (e.g. export RSYSLOG_DEBUG="LogFuncFlow -FileTrace=vm.c FileTrace=expr.c" -.IP PrintFuncDB -Print the content of the debug function database whenever debug -information is printed (e.g. abort case)! -.IP PrintAllDebugInfoOnExit -Print all debug information immediately before rsyslogd exits -(currently not implemented!) -.IP PrintMutexAction -Print mutex action as it happens. Useful for finding deadlocks and -such. -.IP NoLogTimeStamp -Do not prefix log lines with a timestamp (default is to do that). -.IP NoStdOut -Do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not -set, this means no messages will be displayed at all. -.IP Help -Display a very short list of commands - hopefully a life saver if -you can't access the documentation... -.RE - -.TP -.B RSYSLOG_DEBUGLOG -If set, writes (allmost) all debug message to the specified log file -in addition to stdout. -.TP -.B RSYSLOG_MODDIR -Provides the default directory in which loadable modules reside. -.PD -.SH BUGS -Please review the file BUGS for up-to-date information on known -bugs and annouyances. -.SH Further Information -Please visit -.BR http://www.rsyslog.com/doc -for additional information, tutorials and a support forum. -.SH SEE ALSO -.BR rsyslog.conf (5), -.BR logger (1), -.BR syslog (2), -.BR syslog (3), -.BR services (5), -.BR savelog (8) -.LP -.SH COLLABORATORS -.B rsyslogd -is derived from sysklogd sources, which in turn was taken from -the BSD sources. Special thanks to Greg Wettstein (greg@wind.enjellic.com) -and Martin Schulze (joey@linux.de) for the fine sysklogd package. - -.PD 0 -.TP -Rainer Gerhards -.TP -Adiscon GmbH -.TP -Grossrinderfeld, Germany -.TP -rgerhards@adiscon.com -.PD diff --git a/runtime/errmsg.c b/runtime/errmsg.c index 42f84724..01d392b7 100644 --- a/runtime/errmsg.c +++ b/runtime/errmsg.c @@ -33,7 +33,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "obj.h" #include "errmsg.h" #include "sysvar.h" diff --git a/runtime/modules.c b/runtime/modules.c index f10390c7..8ae9f038 100644 --- a/runtime/modules.c +++ b/runtime/modules.c @@ -49,7 +49,7 @@ #include #include -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "modules.h" #include "errmsg.h" diff --git a/runtime/msg.c b/runtime/msg.c index ed9cdbbb..e5ed19c6 100644 --- a/runtime/msg.c +++ b/runtime/msg.c @@ -36,7 +36,7 @@ #include #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "srUtils.h" #include "stringbuf.h" #include "template.h" diff --git a/runtime/msg.h b/runtime/msg.h index 56ce56bb..9ec038dd 100644 --- a/runtime/msg.h +++ b/runtime/msg.h @@ -107,7 +107,6 @@ struct msg { char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */ int msgFlags; /* flags associated with this message */ }; -typedef struct msg msg_t; /* new name */ /* function prototypes */ diff --git a/runtime/net.c b/runtime/net.c index 84c286d2..70e7d6f6 100644 --- a/runtime/net.c +++ b/runtime/net.c @@ -55,7 +55,7 @@ #include #include -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "module-template.h" #include "parse.h" diff --git a/runtime/queue.c b/runtime/queue.c index 0f58c545..11c073a0 100644 --- a/runtime/queue.c +++ b/runtime/queue.c @@ -43,7 +43,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "queue.h" #include "stringbuf.h" #include "srUtils.h" diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 2dfc266b..5ec3a369 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -44,9 +44,25 @@ # define _FILE_OFFSET_BITS 64 #endif +/* portability: not all platforms have these defines, so we + * define them here if they are missing. -- rgerhards, 2008-03-04 + */ +#ifndef LOG_MAKEPRI +# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) +#endif +#ifndef LOG_PRI +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +#endif +#ifndef LOG_FAC +# define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) +#endif + + /* define some base data types */ typedef struct thrdInfo thrdInfo_t; typedef struct filed selector_t; /* TODO: this so far resides in syslogd.c, think about modularization */ +typedef struct NetAddr netAddr_t; +typedef struct msg msg_t; /* some universal 64 bit define... */ typedef long long int64; diff --git a/runtime/srutils.c b/runtime/srutils.c index 93908767..f1208c26 100644 --- a/runtime/srutils.c +++ b/runtime/srutils.c @@ -44,7 +44,7 @@ #define TRUE 1 #define FALSE 0 #include "srUtils.h" -#include "syslogd.h" +#include "dirty.h" #include "obj.h" diff --git a/runtime/stream.c b/runtime/stream.c index 1be4571a..7274b807 100644 --- a/runtime/stream.c +++ b/runtime/stream.c @@ -41,7 +41,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "stringbuf.h" #include "srUtils.h" #include "obj.h" diff --git a/runtime/wti.c b/runtime/wti.c index 82cd2165..88439049 100644 --- a/runtime/wti.c +++ b/runtime/wti.c @@ -40,7 +40,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "stringbuf.h" #include "srUtils.h" #include "wtp.h" diff --git a/runtime/wtp.c b/runtime/wtp.c index fcc7589c..98f1bdbe 100644 --- a/runtime/wtp.c +++ b/runtime/wtp.c @@ -41,7 +41,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "stringbuf.h" #include "srUtils.h" #include "wtp.h" diff --git a/syslogd.c b/syslogd.c deleted file mode 100644 index 68ffe5ce..00000000 --- a/syslogd.c +++ /dev/null @@ -1,3446 +0,0 @@ -/** - * \brief This is the main file of the rsyslogd daemon. - * - * Please visit the rsyslog project at - * - * http://www.rsyslog.com - * - * to learn more about it and discuss any questions you may have. - * - * rsyslog had initially been forked from the sysklogd project. - * I would like to express my thanks to the developers of the sysklogd - * package - without it, I would have had a much harder start... - * - * Please note that while rsyslog started from the sysklogd code base, - * it nowadays has almost nothing left in common with it. Allmost all - * parts of the code have been rewritten. - * - * This Project was intiated and is maintained by - * Rainer Gerhards . See - * AUTHORS to learn who helped make it become a reality. - * - * If you have questions about rsyslogd in general, please email - * info@adiscon.com. To learn more about rsyslogd, please visit - * http://www.rsyslog.com. - * - * \author Rainer Gerhards - * \date 2003-10-17 - * Some initial modifications on the sysklogd package to support - * liblogging. These have actually not yet been merged to the - * source you see currently (but they hopefully will) - * - * \date 2004-10-28 - * Restarted the modifications of sysklogd. This time, we - * focus on a simpler approach first. The initial goal is to - * provide MySQL database support (so that syslogd can log - * to the database). - * - * rsyslog - An Enhanced syslogd Replacement. - * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#include "config.h" -#include "rsyslog.h" - -/* change the following setting to e.g. 32768 if you would like to - * support large message sizes for IHE (32k is the current maximum - * needed for IHE). I was initially tempted to increase it to 32k, - * but there is a large memory footprint with the current - * implementation in rsyslog. This will change as the processing - * changes, but I have re-set it to 1k, because the vast majority - * of messages is below that and the memory savings is huge, at - * least compared to the overall memory footprint. - * - * If you intend to receive Windows Event Log data (e.g. via - * EventReporter - www.eventreporter.com), you might want to - * increase this number to an even higher value, as event - * log messages can be very lengthy. - * rgerhards, 2005-07-05 - * - * during my recent testing, it showed that 4k seems to be - * the typical maximum for UDP based syslog. This is a IP stack - * restriction. Not always ... but very often. If you go beyond - * that value, be sure to test that rsyslogd actually does what - * you think it should do ;) Also, it is a good idea to check the - * doc set for anything on IHE - it most probably has information on - * message sizes. - * rgerhards, 2005-08-05 - * - * I have increased the default message size to 2048 to be in sync - * with recent IETF syslog standardization efforts. - * rgerhards, 2006-11-30 - */ -#define DEFUPRI (LOG_USER|LOG_NOTICE) -#define TIMERINTVL 30 /* interval for checking flush, mark */ - -#include -#include -#include -#include -#include -#include -#define GNU_SOURCE -#include -#include -#include -#include -#include - -#ifdef __sun -# include -#else -# include -#endif -#include -#include -#include - -#if HAVE_SYS_TIMESPEC_H -# include -#endif - -#if HAVE_SYS_STAT_H -# include -#endif - -#include - -#if HAVE_PATHS_H -#include -#endif - -#ifdef USE_NETZIP -#include -#endif - -#include - -#include "pidfile.h" -#include "srUtils.h" -#include "stringbuf.h" -#include "syslogd-types.h" -#include "template.h" -#include "outchannel.h" -#include "syslogd.h" - -#include "msg.h" -#include "modules.h" -#include "action.h" -#include "iminternal.h" -#include "cfsysline.h" -#include "omshell.h" -#include "omusrmsg.h" -#include "omfwd.h" -#include "omfile.h" -#include "omdiscard.h" -#include "threads.h" -#include "queue.h" -#include "stream.h" -#include "conf.h" -#include "vm.h" -#include "errmsg.h" -#include "datetime.h" -#include "sysvar.h" - -/* definitions for objects we access */ -DEFobjCurrIf(obj) -DEFobjCurrIf(datetime) -DEFobjCurrIf(conf) -DEFobjCurrIf(expr) -DEFobjCurrIf(vm) -DEFobjCurrIf(var) -DEFobjCurrIf(module) -DEFobjCurrIf(errmsg) -DEFobjCurrIf(net) /* TODO: make go away! */ - - -/* forward definitions */ -static rsRetVal GlobalClassExit(void); - -/* We define our own set of syslog defintions so that we - * do not need to rely on (possibly different) implementations. - * 2007-07-19 rgerhards - */ -/* missing definitions for solaris - * 2006-02-16 Rger - */ -#ifdef __sun -# define LOG_AUTHPRIV LOG_AUTH -#endif -#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ -#define LOG_FTP (11<<3) /* ftp daemon */ - - -#ifndef UTMP_FILE -#ifdef UTMP_FILENAME -#define UTMP_FILE UTMP_FILENAME -#else -#ifdef _PATH_UTMP -#define UTMP_FILE _PATH_UTMP -#else -#define UTMP_FILE "/etc/utmp" -#endif -#endif -#endif - -#ifndef _PATH_LOGCONF -#define _PATH_LOGCONF "/etc/rsyslog.conf" -#endif - -#ifndef _PATH_MODDIR -#define _PATH_MODDIR "/lib/rsyslog/" -#endif - -#if defined(SYSLOGD_PIDNAME) -# undef _PATH_LOGPID -# if defined(FSSTND) -# ifdef OS_BSD -# define _PATH_VARRUN "/var/run/" -# endif -# if defined(__sun) || defined(__hpux) -# define _PATH_VARRUN "/var/run/" -# endif -# define _PATH_LOGPID _PATH_VARRUN SYSLOGD_PIDNAME -# else -# define _PATH_LOGPID "/etc/" SYSLOGD_PIDNAME -# endif -#else -# ifndef _PATH_LOGPID -# if defined(__sun) || defined(__hpux) -# define _PATH_VARRUN "/var/run/" -# endif -# if defined(FSSTND) -# define _PATH_LOGPID _PATH_VARRUN "rsyslogd.pid" -# else -# define _PATH_LOGPID "/etc/rsyslogd.pid" -# endif -# endif -#endif - -#ifndef _PATH_DEV -# define _PATH_DEV "/dev/" -#endif - -#ifndef _PATH_CONSOLE -#define _PATH_CONSOLE "/dev/console" -#endif - -#ifndef _PATH_TTY -#define _PATH_TTY "/dev/tty" -#endif - -static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */ -static char *PidFile = _PATH_LOGPID; /* read-only after startup */ -char ctty[] = _PATH_CONSOLE; /* this is read-only; used by omfile -- TODO: remove that dependency */ - -static pid_t myPid; /* our pid for use in self-generated messages, e.g. on startup */ -/* mypid is read-only after the initial fork() */ -static int restart = 0; /* do restart (config read) - multithread safe */ - -int glblHadMemShortage = 0; /* indicates if we had memory shortage some time during the run */ - - -static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be - * parsed inside message - rgerhards, 2006-03-13 */ -static int bFinished = 0; /* used by termination signal handler, read-only except there - * is either 0 or the number of the signal that requested the - * termination. - */ - -/* Intervals at which we flush out "message repeated" messages, - * in seconds after previous message is logged. After each flush, - * we move to the next interval until we reach the largest. - * TODO: this shall go into action object! -- rgerhards, 2008-01-29 - */ -int repeatinterval[2] = { 30, 60 }; /* # of secs before flush */ - -#define LIST_DELIMITER ':' /* delimiter between two hosts */ - -struct filed *Files = NULL; /* read-only after init() (but beware of sigusr1!) */ - -static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */ - -typedef struct legacyOptsLL_s { - uchar *line; - struct legacyOptsLL_s *next; -} legacyOptsLL_t; -legacyOptsLL_t *pLegacyOptsLL = NULL; - -/* global variables for config file state */ -static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */ -int iCompatibilityMode = 0; /* version we should be compatible with; 0 means sysklogd. It is - the default, so if no -c option is given, we make ourselvs - as compatible to sysklogd as possible. */ -static int bDebugPrintTemplateList = 1;/* output template list in debug mode? */ -static int bDebugPrintCfSysLineHandlerList = 1;/* output cfsyslinehandler list in debug mode? */ -static int bDebugPrintModuleList = 1;/* output module list in debug mode? */ -int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ -static uchar cCCEscapeChar = '\\';/* character to be used to start an escape sequence for control chars */ -static int bEscapeCCOnRcv = 1; /* escape control characters on reception: 0 - no, 1 - yes */ -int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ -int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ -int iActExecOnceInterval = 0; /* execute action once every nn seconds */ -uchar *pszWorkDir = NULL;/* name of rsyslog's spool directory (without trailing slash) */ -uchar *glblModPath = NULL; /* module load path - only used during initial init, only settable via -M command line option */ -/* end global config file state variables */ - -uchar *LocalHostName;/* our hostname - read-only after startup */ -char *LocalDomain; /* our local domain name - read-only after startup */ -int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ -int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */ -int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ -static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */ -int DisableDNS = 0; /* don't look up IP addresses of remote messages */ -char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ -char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ -static int bHaveMainQueue = 0;/* set to 1 if the main queue - in queueing mode - is available - * If the main queue is either not yet ready or not running in - * queueing mode (mode DIRECT!), then this is set to 0. - */ - -extern int errno; - -/* main message queue and its configuration parameters */ -static queue_t *pMsgQueue = NULL; /* the main message queue */ -static int iMainMsgQueueSize = 10000; /* size of the main message queue above */ -static int iMainMsgQHighWtrMark = 8000; /* high water mark for disk-assisted queues */ -static int iMainMsgQLowWtrMark = 2000; /* low water mark for disk-assisted queues */ -static int iMainMsgQDiscardMark = 9800; /* begin to discard messages */ -static int iMainMsgQDiscardSeverity = 8; /* by default, discard nothing to prevent unintentional loss */ -static int iMainMsgQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ -static queueType_t MainMsgQueType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */ -static uchar *pszMainMsgQFName = NULL; /* prefix for the main message queue file */ -static int64 iMainMsgQueMaxFileSize = 1024*1024; -static int iMainMsgQPersistUpdCnt = 0; /* persist queue info every n updates */ -static int iMainMsgQtoQShutdown = 0; /* queue shutdown */ -static int iMainMsgQtoActShutdown = 1000; /* action shutdown (in phase 2) */ -static int iMainMsgQtoEnq = 2000; /* timeout for queue enque */ -static int iMainMsgQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ -static int iMainMsgQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ -static int iMainMsgQDeqSlowdown = 0; /* dequeue slowdown (simple rate limiting) */ -static int64 iMainMsgQueMaxDiskSpace = 0; /* max disk space allocated 0 ==> unlimited */ -static int bMainMsgQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ -static int iMainMsgQueueDeqtWinFromHr = 0; /* hour begin of time frame when queue is to be dequeued */ -static int iMainMsgQueueDeqtWinToHr = 25; /* hour begin of time frame when queue is to be dequeued */ - - -/* support for simple textual representation of FIOP names - * rgerhards, 2005-09-27 - */ -static char* getFIOPName(unsigned iFIOP) -{ - char *pRet; - switch(iFIOP) { - case FIOP_CONTAINS: - pRet = "contains"; - break; - case FIOP_ISEQUAL: - pRet = "isequal"; - break; - case FIOP_STARTSWITH: - pRet = "startswith"; - break; - case FIOP_REGEX: - pRet = "regex"; - break; - default: - pRet = "NOP"; - break; - } - return pRet; -} - - -/* Reset config variables to default values. - * rgerhards, 2007-07-17 - */ -static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) -{ - cCCEscapeChar = '#'; - bActExecWhenPrevSusp = 0; - iActExecOnceInterval = 0; - bDebugPrintTemplateList = 1; - bDebugPrintCfSysLineHandlerList = 1; - bDebugPrintModuleList = 1; - bEscapeCCOnRcv = 1; /* default is to escape control characters */ - bReduceRepeatMsgs = 0; - bDropMalPTRMsgs = 0; - if(pszWorkDir != NULL) { - free(pszWorkDir); - pszWorkDir = NULL; - } - if(pszMainMsgQFName != NULL) { - free(pszMainMsgQFName); - pszMainMsgQFName = NULL; - } - iMainMsgQueueSize = 10000; - iMainMsgQHighWtrMark = 8000; - iMainMsgQLowWtrMark = 2000; - iMainMsgQDiscardMark = 9800; - iMainMsgQDiscardSeverity = 4; - iMainMsgQueMaxFileSize = 1024 * 1024; - iMainMsgQueueNumWorkers = 1; - iMainMsgQPersistUpdCnt = 0; - iMainMsgQtoQShutdown = 0; - iMainMsgQtoActShutdown = 1000; - iMainMsgQtoEnq = 2000; - iMainMsgQtoWrkShutdown = 60000; - iMainMsgQWrkMinMsgs = 100; - iMainMsgQDeqSlowdown = 0; - bMainMsgQSaveOnShutdown = 1; - MainMsgQueType = QUEUETYPE_FIXED_ARRAY; - iMainMsgQueMaxDiskSpace = 0; - glbliActionResumeRetryCount = 0; - - return RS_RET_OK; -} - - - -int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ - - -/* hardcoded standard templates (used for defaults) */ -static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\""; -static uchar template_TraditionalFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n\""; -static uchar template_FileFormat[] = "\"%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n\""; -static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\""; -static uchar template_ForwardFormat[] = "\"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg%\""; -static uchar template_TraditionalForwardFormat[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg%\""; -static uchar template_StdUsrMsgFmt[] = "\" %syslogtag%%msg%\n\r\""; -static uchar template_StdDBFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')\",SQL"; -static uchar template_StdPgSQLFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-pgsql%', '%timegenerated:::date-pgsql%', %iut%, '%syslogtag%')\",STDSQL"; -/* end template */ - - -/* up to the next comment, prototypes that should be removed by reordering */ -/* Function prototypes. */ -static char **crunch_list(char *list); -static void reapchild(); -static void debug_switch(); -static void sighup_handler(); -static void freeSelectors(void); -static void processImInternal(void); - - -static int usage(void) -{ - fprintf(stderr, "usage: rsyslogd [-cversion] [-46AdnqQvwx] [-lhostlist] [-sdomainlist]\n" - " [-fconffile] [-ipidfile]\n" - "To run rsyslogd in native mode, use \"rsyslogd -c3 \"\n\n" - "For further information see http://www.rsyslog.com/doc\n"); - exit(1); /* "good" exit - done to terminate usage() */ -} - - -/* function to destruct a selector_t object - * rgerhards, 2007-08-01 - */ -rsRetVal -selectorDestruct(void *pVal) -{ - selector_t *pThis = (selector_t *) pVal; - - assert(pThis != NULL); - - if(pThis->pCSHostnameComp != NULL) - rsCStrDestruct(&pThis->pCSHostnameComp); - if(pThis->pCSProgNameComp != NULL) - rsCStrDestruct(&pThis->pCSProgNameComp); - - if(pThis->f_filter_type == FILTER_PROP) { - if(pThis->f_filterData.prop.pCSPropName != NULL) - rsCStrDestruct(&pThis->f_filterData.prop.pCSPropName); - if(pThis->f_filterData.prop.pCSCompValue != NULL) - rsCStrDestruct(&pThis->f_filterData.prop.pCSCompValue); - } else if(pThis->f_filter_type == FILTER_EXPR) { - if(pThis->f_filterData.f_expr != NULL) - expr.Destruct(&pThis->f_filterData.f_expr); - } - - llDestroy(&pThis->llActList); - free(pThis); - - return RS_RET_OK; -} - - -/* function to construct a selector_t object - * rgerhards, 2007-08-01 - */ -rsRetVal -selectorConstruct(selector_t **ppThis) -{ - DEFiRet; - selector_t *pThis; - - assert(ppThis != NULL); - - if((pThis = (selector_t*) calloc(1, sizeof(selector_t))) == NULL) { - glblHadMemShortage = 1; - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } - CHKiRet(llInit(&pThis->llActList, actionDestruct, NULL, NULL)); - -finalize_it: - if(iRet != RS_RET_OK) { - if(pThis != NULL) { - selectorDestruct(pThis); - } - } - *ppThis = pThis; - RETiRet; -} - - -/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So - * it is never called once rsyslogd is running (not even when HUPed). This code - * contains some exits, but they are considered safe because they only happen - * during startup. Anyhow, when we review the code here, we might want to - * reconsider the exit()s. - */ -static char **crunch_list(char *list) -{ - int count, i; - char *p, *q; - char **result = NULL; - - p = list; - - /* strip off trailing delimiters */ - while (p[strlen(p)-1] == LIST_DELIMITER) { - count--; - p[strlen(p)-1] = '\0'; - } - /* cut off leading delimiters */ - while (p[0] == LIST_DELIMITER) { - count--; - p++; - } - - /* count delimiters to calculate elements */ - for (count=i=0; p[i]; i++) - if (p[i] == LIST_DELIMITER) count++; - - if ((result = (char **)malloc(sizeof(char *) * (count+2))) == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - - /* - * We now can assume that the first and last - * characters are different from any delimiters, - * so we don't have to care about this. - */ - count = 0; - while ((q=strchr(p, LIST_DELIMITER))) { - result[count] = (char *) malloc((q - p + 1) * sizeof(char)); - if (result[count] == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - strncpy(result[count], p, q - p); - result[count][q - p] = '\0'; - p = q; p++; - count++; - } - if ((result[count] = \ - (char *)malloc(sizeof(char) * strlen(p) + 1)) == NULL) { - printf ("Sorry, can't get enough memory, exiting.\n"); - exit(0); /* safe exit, because only called during startup */ - } - strcpy(result[count],p); - result[++count] = NULL; - -#if 0 - count=0; - while (result[count]) - dbgprintf("#%d: %s\n", count, StripDomains[count++]); -#endif - return result; -} - - -void untty(void) -#ifdef HAVE_SETSID -{ - if ( !Debug ) { - setsid(); - } - return; -} -#else -{ - int i; - - if ( !Debug ) { - i = open(_PATH_TTY, O_RDWR); - if (i >= 0) { -# if !defined(__hpux) - (void) ioctl(i, (int) TIOCNOTTY, (char *)0); -# else - /* TODO: we need to implement something for HP UX! -- rgerhards, 2008-03-04 */ - /* actually, HP UX should have setsid, so the code directly above should - * trigger. So the actual question is why it doesn't do that... - */ -# endif - (void) close(i); - } - } -} -#endif - - -/* Take a raw input line, decode the message, and print the message - * on the appropriate log files. - * rgerhards 2004-11-08: Please note - * that this function does only a partial decoding. At best, it splits - * the PRI part. No further decode happens. The rest is done in - * logmsg(). - * Added the iSource parameter so that we know if we have to parse - * HOSTNAME or not. rgerhards 2004-11-16. - * changed parameter iSource to bParseHost. For details, see comment in - * printchopped(). rgerhards 2005-10-06 - * rgerhards: 2008-03-06: added "flags" to allow an input module to specify - * flags, most importantly to request ignoring the messages' timestamp. - * - * rgerhards, 2008-03-19: - * I added an additional calling parameter to permit specifying the flow - * control capability of the source. - */ -rsRetVal printline(char *hname, char *msg, int bParseHost, int flags, flowControl_t flowCtlType) -{ - DEFiRet; - register char *p; - int pri; - msg_t *pMsg; - - /* Now it is time to create the message object (rgerhards) - */ - CHKiRet(msgConstruct(&pMsg)); - MsgSetFlowControlType(pMsg, flowCtlType); - MsgSetRawMsg(pMsg, msg); - - pMsg->bParseHOSTNAME = bParseHost; - /* test for special codes */ - pri = DEFUPRI; - p = msg; - if (*p == '<') { - pri = 0; - while (isdigit((int) *++p)) - { - pri = 10 * pri + (*p - '0'); - } - if (*p == '>') - ++p; - } - if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) - pri = DEFUPRI; - pMsg->iFacility = LOG_FAC(pri); - pMsg->iSeverity = LOG_PRI(pri); - - /* Now we look at the HOSTNAME. That is a bit complicated... - * If we have a locally received message, it does NOT - * contain any hostname information in the message itself. - * As such, the HOSTNAME is the same as the system that - * the message was received from (that, for obvious reasons, - * being the local host). rgerhards 2004-11-16 - */ - if(bParseHost == 0) - MsgSetHOSTNAME(pMsg, hname); - MsgSetRcvFrom(pMsg, hname); - - /* rgerhards 2004-11-19: well, well... we've now seen that we - * have the "hostname problem" also with the traditional Unix - * message. As we like to emulate it, we need to add the hostname - * to it. - */ - if(MsgSetUxTradMsg(pMsg, p) != 0) - ABORT_FINALIZE(RS_RET_ERR); - - logmsg(pMsg, flags | SYNC_FILE); - -finalize_it: - RETiRet; -} - - -/* This takes a received message that must be decoded and submits it to - * the main message queue. The function calls the necessary parser. - * - * rgerhards, 2006-11-30: I have greatly changed this function. Formerly, - * it tried to reassemble multi-part messages, which is a legacy stock - * sysklogd concept. In essence, that was that messages not ending with - * \0 were glued together. As far as I can see, this is a sysklogd - * specific feature and, from looking at the code, seems to be used - * pretty seldom (if at all). I remove this now, not the least because it is totally - * incompatible with upcoming IETF syslog standards. If you experience - * strange behaviour with messages beeing split across multiple lines, - * this function here might be the place to look at. - * - * Some previous history worth noting: - * I added the "iSource" parameter. This is needed to distinguish between - * messages that have a hostname in them (received from the internet) and - * those that do not have (most prominently /dev/log). rgerhards 2004-11-16 - * And now I removed the "iSource" parameter and changed it to be "bParseHost", - * because all that it actually controls is whether the host is parsed or not. - * For rfc3195 support, we needed to modify the algo for host parsing, so we can - * no longer rely just on the source (rfc3195d forwarded messages arrive via - * unix domain sockets but contain the hostname). rgerhards, 2005-10-06 - * - * rgerhards, 2008-02-18: - * This function was previously called "printchopped"() and has been renamed - * as part of the effort to create a clean internal message submission interface. - * It also has been adopted to our usual calling interface, but currently does - * not provide any useful return states. But we now have the hook and things can - * improve in the future. <-- TODO! - * - * rgerhards, 2008-03-19: - * I added an additional calling parameter to permit specifying the flow - * control capability of the source. - */ -rsRetVal -parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType) -{ - DEFiRet; - register int iMsg; - char *pMsg; - char *pData; - char *pEnd; - char tmpline[MAXLINE + 1]; -# ifdef USE_NETZIP - char deflateBuf[MAXLINE + 1]; - uLongf iLenDefBuf; -# endif - - assert(hname != NULL); - assert(msg != NULL); - assert(len >= 0); - - /* we first check if we have a NUL character at the very end of the - * message. This seems to be a frequent problem with a number of senders. - * So I have now decided to drop these NULs. However, if they are intentional, - * that may cause us some problems, e.g. with syslog-sign. On the other hand, - * current code always has problems with intentional NULs (as it needs to escape - * them to prevent problems with the C string libraries), so that does not - * really matter. Just to be on the save side, we'll log destruction of such - * NULs in the debug log. - * rgerhards, 2007-09-14 - */ - if(*(msg + len - 1) == '\0') { - dbgprintf("dropped NUL at very end of message\n"); - len--; - } - - /* then we check if we need to drop trailing LFs, which often make - * their way into syslog messages unintentionally. In order to remain - * compatible to recent IETF developments, we allow the user to - * turn on/off this handling. rgerhards, 2007-07-23 - */ - if(bDropTrailingLF && *(msg + len - 1) == '\n') { - dbgprintf("dropped LF at very end of message (DropTrailingLF is set)\n"); - len--; - } - - iMsg = 0; /* initialize receiving buffer index */ - pMsg = tmpline; /* set receiving buffer pointer */ - pData = msg; /* set source buffer pointer */ - pEnd = msg + len; /* this is one off, which is intensional */ - -# ifdef USE_NETZIP - /* we first need to check if we have a compressed record. If so, - * we must decompress it. - */ - if(len > 0 && *msg == 'z') { /* compressed data present? (do NOT change order if conditions!) */ - /* we have compressed data, so let's deflate it. We support a maximum - * message size of MAXLINE. If it is larger, an error message is logged - * and the message is dropped. We do NOT try to decompress larger messages - * as such might be used for denial of service. It might happen to later - * builds that such functionality be added as an optional, operator-configurable - * feature. - */ - int ret; - iLenDefBuf = MAXLINE; - ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) msg+1, len-1); - dbgprintf("Compressed message uncompressed with status %d, length: new %ld, old %d.\n", - ret, (long) iLenDefBuf, len-1); - /* Now check if the uncompression worked. If not, there is not much we can do. In - * that case, we log an error message but ignore the message itself. Storing the - * compressed text is dangerous, as it contains control characters. So we do - * not do this. If someone would like to have a copy, this code here could be - * modified to do a hex-dump of the buffer in question. We do not include - * this functionality right now. - * rgerhards, 2006-12-07 - */ - if(ret != Z_OK) { - errmsg.LogError(NO_ERRCODE, "Uncompression of a message failed with return code %d " - "- enable debug logging if you need further information. " - "Message ignored.", ret); - FINALIZE; /* unconditional exit, nothing left to do... */ - } - pData = deflateBuf; - pEnd = deflateBuf + iLenDefBuf; - } -# else /* ifdef USE_NETZIP */ - /* in this case, we still need to check if the message is compressed. If so, we must - * tell the user we can not accept it. - */ - if(len > 0 && *msg == 'z') { - errmsg.LogError(NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression " - "support enabled. The message will be ignored."); - FINALIZE; - } -# endif /* ifdef USE_NETZIP */ - - while(pData < pEnd) { - if(iMsg >= MAXLINE) { - /* emergency, we now need to flush, no matter if - * we are at end of message or not... - */ - if(iMsg == MAXLINE) { - *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ - printline(hname, tmpline, bParseHost, flags, flowCtlType); - } else { - /* This case in theory never can happen. If it happens, we have - * a logic error. I am checking for it, because if I would not, - * we would address memory invalidly with the code above. I - * do not care much about this case, just a debug log entry - * (I couldn't do any more smart things anyway...). - * rgerhards, 2007-9-20 - */ - dbgprintf("internal error: iMsg > MAXLINE in printchopped()\n"); - } - FINALIZE; /* in this case, we are done... nothing left we can do */ - } - if(*pData == '\0') { /* guard against \0 characters... */ - /* changed to the sequence (somewhat) proposed in - * draft-ietf-syslog-protocol-19. rgerhards, 2006-11-30 - */ - if(iMsg + 3 < MAXLINE) { /* do we have space? */ - *(pMsg + iMsg++) = cCCEscapeChar; - *(pMsg + iMsg++) = '0'; - *(pMsg + iMsg++) = '0'; - *(pMsg + iMsg++) = '0'; - } /* if we do not have space, we simply ignore the '\0'... */ - /* log an error? Very questionable... rgerhards, 2006-11-30 */ - /* decided: we do not log an error, it won't help... rger, 2007-06-21 */ - ++pData; - } else if(bEscapeCCOnRcv && iscntrl((int) *pData)) { - /* we are configured to escape control characters. Please note - * that this most probably break non-western character sets like - * Japanese, Korean or Chinese. rgerhards, 2007-07-17 - * Note: sysklogd logs octal values only for DEL and CCs above 127. - * For others, it logs ^n where n is the control char converted to an - * alphabet character. We like consistency and thus escape it to octal - * in all cases. If someone complains, we may change the mode. At least - * we known now what's going on. - * rgerhards, 2007-07-17 - */ - if(iMsg + 3 < MAXLINE) { /* do we have space? */ - *(pMsg + iMsg++) = cCCEscapeChar; - *(pMsg + iMsg++) = '0' + ((*pData & 0300) >> 6); - *(pMsg + iMsg++) = '0' + ((*pData & 0070) >> 3); - *(pMsg + iMsg++) = '0' + ((*pData & 0007)); - } /* again, if we do not have space, we ignore the char - see comment at '\0' */ - ++pData; - } else { - *(pMsg + iMsg++) = *pData++; - } - } - - *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ - - /* typically, we should end up here! */ - printline(hname, tmpline, bParseHost, flags, flowCtlType); - -finalize_it: - RETiRet; -} - -/* rgerhards 2004-11-09: the following is a function that can be used - * to log a message orginating from the syslogd itself. In sysklogd code, - * this is done by simply calling logmsg(). However, logmsg() is changed in - * rsyslog so that it takes a msg "object". So it can no longer be called - * directly. This method here solves the need. It provides an interface that - * allows to construct a locally-generated message. Please note that this - * function here probably is only an interim solution and that we need to - * think on the best way to do this. - */ -rsRetVal -logmsgInternal(int pri, char *msg, int flags) -{ - DEFiRet; - msg_t *pMsg; - - CHKiRet(msgConstruct(&pMsg)); - MsgSetUxTradMsg(pMsg, msg); - MsgSetRawMsg(pMsg, msg); - MsgSetHOSTNAME(pMsg, (char*)LocalHostName); - MsgSetRcvFrom(pMsg, (char*)LocalHostName); - MsgSetTAG(pMsg, "rsyslogd:"); - pMsg->iFacility = LOG_FAC(pri); - pMsg->iSeverity = LOG_PRI(pri); - pMsg->bParseHOSTNAME = 0; - datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ - flags |= INTERNAL_MSG; - - if(bHaveMainQueue == 0) { /* not yet in queued mode */ - iminternalAddMsg(pri, pMsg, flags); - } else { - /* we have the queue, so we can simply provide the - * message to the queue engine. - */ - logmsg(pMsg, flags); - } -finalize_it: - RETiRet; -} - -/* This functions looks at the given message and checks if it matches the - * provided filter condition. If so, it returns true, else it returns - * false. This is a helper to logmsg() and meant to drive the decision - * process if a message is to be processed or not. As I expect this - * decision code to grow more complex over time AND logmsg() is already - * a very lengthy function, I thought a separate function is more appropriate. - * 2005-09-19 rgerhards - * 2008-02-25 rgerhards: changed interface, now utilizes iRet, bProcessMsg - * returns is message should be procesed. - */ -static rsRetVal shouldProcessThisMessage(selector_t *f, msg_t *pMsg, int *bProcessMsg) -{ - DEFiRet; - unsigned short pbMustBeFreed; - char *pszPropVal; - int bRet = 0; - vm_t *pVM = NULL; - var_t *pResult = NULL; - - assert(f != NULL); - assert(pMsg != NULL); - - /* we first have a look at the global, BSD-style block filters (for tag - * and host). Only if they match, we evaluate the actual filter. - * rgerhards, 2005-10-18 - */ - if(f->eHostnameCmpMode == HN_NO_COMP) { - /* EMPTY BY INTENSION - we check this value first, because - * it is the one most often used, so this saves us time! - */ - } else if(f->eHostnameCmpMode == HN_COMP_MATCH) { - if(rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { - /* not equal, so we are already done... */ - dbgprintf("hostname filter '+%s' does not match '%s'\n", - rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); - FINALIZE; - } - } else { /* must be -hostname */ - if(!rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { - /* not equal, so we are already done... */ - dbgprintf("hostname filter '-%s' does not match '%s'\n", - rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); - FINALIZE; - } - } - - if(f->pCSProgNameComp != NULL) { - int bInv = 0, bEqv = 0, offset = 0; - if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp)) == '-') { - if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp) + 1) == '-') - offset = 1; - else { - bInv = 1; - offset = 1; - } - } - if(!rsCStrOffsetSzStrCmp(f->pCSProgNameComp, offset, (uchar*) getProgramName(pMsg), getProgramNameLen(pMsg))) - bEqv = 1; - - if((!bEqv && !bInv) || (bEqv && bInv)) { - /* not equal or inverted selection, so we are already done... */ - dbgprintf("programname filter '%s' does not match '%s'\n", - rsCStrGetSzStrNoNULL(f->pCSProgNameComp), getProgramName(pMsg)); - FINALIZE; - } - } - - /* done with the BSD-style block filters */ - - if(f->f_filter_type == FILTER_PRI) { - /* skip messages that are incorrect priority */ - if ( (f->f_filterData.f_pmask[pMsg->iFacility] == TABLE_NOPRI) || \ - ((f->f_filterData.f_pmask[pMsg->iFacility] & (1<iSeverity)) == 0) ) - bRet = 0; - else - bRet = 1; - } else if(f->f_filter_type == FILTER_EXPR) { - CHKiRet(vm.Construct(&pVM)); - CHKiRet(vm.ConstructFinalize(pVM)); - CHKiRet(vm.SetMsg(pVM, pMsg)); - CHKiRet(vm.ExecProg(pVM, f->f_filterData.f_expr->pVmprg)); - CHKiRet(vm.PopBoolFromStack(pVM, &pResult)); - dbgprintf("result of expression evaluation: %lld\n", pResult->val.num); - /* VM is destructed on function exit */ - bRet = (pResult->val.num) ? 1 : 0; - } else { - assert(f->f_filter_type == FILTER_PROP); /* assert() just in case... */ - pszPropVal = MsgGetProp(pMsg, NULL, f->f_filterData.prop.pCSPropName, &pbMustBeFreed); - - /* Now do the compares (short list currently ;)) */ - switch(f->f_filterData.prop.operation ) { - case FIOP_CONTAINS: - if(rsCStrLocateInSzStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal) != -1) - bRet = 1; - break; - case FIOP_ISEQUAL: - if(rsCStrSzStrCmp(f->f_filterData.prop.pCSCompValue, - (uchar*) pszPropVal, strlen(pszPropVal)) == 0) - bRet = 1; /* process message! */ - break; - case FIOP_STARTSWITH: - if(rsCStrSzStrStartsWithCStr(f->f_filterData.prop.pCSCompValue, - (uchar*) pszPropVal, strlen(pszPropVal)) == 0) - bRet = 1; /* process message! */ - break; - case FIOP_REGEX: - if(rsCStrSzStrMatchRegex(f->f_filterData.prop.pCSCompValue, - (unsigned char*) pszPropVal) == 0) - bRet = 1; - break; - default: - /* here, it handles NOP (for performance reasons) */ - assert(f->f_filterData.prop.operation == FIOP_NOP); - bRet = 1; /* as good as any other default ;) */ - break; - } - - /* now check if the value must be negated */ - if(f->f_filterData.prop.isNegated) - bRet = (bRet == 1) ? 0 : 1; - - if(Debug) { - dbgprintf("Filter: check for property '%s' (value '%s') ", - rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName), - pszPropVal); - if(f->f_filterData.prop.isNegated) - dbgprintf("NOT "); - dbgprintf("%s '%s': %s\n", - getFIOPName(f->f_filterData.prop.operation), - rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue), - bRet ? "TRUE" : "FALSE"); - } - - /* cleanup */ - if(pbMustBeFreed) - free(pszPropVal); - } - -finalize_it: - /* destruct in any case, not just on error, but it makes error handling much easier */ - if(pVM != NULL) - vm.Destruct(&pVM); - - if(pResult != NULL) - var.Destruct(&pResult); - - *bProcessMsg = bRet; - RETiRet; -} - - -/* helper to processMsg(), used to call the configured actions. It is - * executed from within llExecFunc() of the action list. - * rgerhards, 2007-08-02 - */ -typedef struct processMsgDoActions_s { - int bPrevWasSuspended; /* was the previous action suspended? */ - msg_t *pMsg; -} processMsgDoActions_t; -DEFFUNC_llExecFunc(processMsgDoActions) -{ - DEFiRet; - rsRetVal iRetMod; /* return value of module - we do not always pass that back */ - action_t *pAction = (action_t*) pData; - processMsgDoActions_t *pDoActData = (processMsgDoActions_t*) pParam; - - assert(pAction != NULL); - - if((pAction->bExecWhenPrevSusp == 1) && (pDoActData->bPrevWasSuspended == 0)) { - dbgprintf("not calling action because the previous one is not suspended\n"); - ABORT_FINALIZE(RS_RET_OK); - } - - iRetMod = actionCallAction(pAction, pDoActData->pMsg); - if(iRetMod == RS_RET_DISCARDMSG) { - ABORT_FINALIZE(RS_RET_DISCARDMSG); - } else if(iRetMod == RS_RET_SUSPENDED) { - /* indicate suspension for next module to be called */ - pDoActData->bPrevWasSuspended = 1; - } else { - pDoActData->bPrevWasSuspended = 0; - } - -finalize_it: - RETiRet; -} - - -/* Process (consume) a received message. Calls the actions configured. - * rgerhards, 2005-10-13 - */ -static void -processMsg(msg_t *pMsg) -{ - selector_t *f; - int bContinue; - int bProcessMsg; - processMsgDoActions_t DoActData; - rsRetVal iRet; - - BEGINfunc - assert(pMsg != NULL); - - /* log the message to the particular outputs */ - - bContinue = 1; - for (f = Files; f != NULL && bContinue ; f = f->f_next) { - /* first check the filters... */ - iRet = shouldProcessThisMessage(f, pMsg, &bProcessMsg); - if(!bProcessMsg) { - continue; - } - - /* ok -- from here, we have action-specific code, nothing really selector-specific -- rger 2007-08-01 */ - DoActData.pMsg = pMsg; - DoActData.bPrevWasSuspended = 0; - if(llExecFunc(&f->llActList, processMsgDoActions, (void*)&DoActData) == RS_RET_DISCARDMSG) - bContinue = 0; - } - ENDfunc -} - - -/* The consumer of dequeued messages. This function is called by the - * queue engine on dequeueing of a message. It runs on a SEPARATE - * THREAD. - * NOTE: Having more than one worker requires guarding of some - * message object structures and potentially others - need to be checked - * before we support multiple worker threads on the message queue. - * Please note: the message object is destructed by the queue itself! - */ -static rsRetVal -msgConsumer(void __attribute__((unused)) *notNeeded, void *pUsr) -{ - DEFiRet; - msg_t *pMsg = (msg_t*) pUsr; - - assert(pMsg != NULL); - - processMsg(pMsg); - msgDestruct(&pMsg); - - RETiRet; -} - - -/* Helper to parseRFCSyslogMsg. This function parses a field up to - * (and including) the SP character after it. The field contents is - * returned in a caller-provided buffer. The parsepointer is advanced - * to after the terminating SP. The caller must ensure that the - * provided buffer is large enough to hold the to be extracted value. - * Returns 0 if everything is fine or 1 if either the field is not - * SP-terminated or any other error occurs. - * rger, 2005-11-24 - */ -static int parseRFCField(char **pp2parse, char *pResult) -{ - char *p2parse; - int iRet = 0; - - assert(pp2parse != NULL); - assert(*pp2parse != NULL); - assert(pResult != NULL); - - p2parse = *pp2parse; - - /* this is the actual parsing loop */ - while(*p2parse && *p2parse != ' ') { - *pResult++ = *p2parse++; - } - - if(*p2parse == ' ') - ++p2parse; /* eat SP, but only if not at end of string */ - else - iRet = 1; /* there MUST be an SP! */ - *pResult = '\0'; - - /* set the new parse pointer */ - *pp2parse = p2parse; - return 0; -} - - -/* Helper to parseRFCSyslogMsg. This function parses the structured - * data field of a message. It does NOT parse inside structured data, - * just gets the field as whole. Parsing the single entities is left - * to other functions. The parsepointer is advanced - * to after the terminating SP. The caller must ensure that the - * provided buffer is large enough to hold the to be extracted value. - * Returns 0 if everything is fine or 1 if either the field is not - * SP-terminated or any other error occurs. - * rger, 2005-11-24 - */ -static int parseRFCStructuredData(char **pp2parse, char *pResult) -{ - char *p2parse; - int bCont = 1; - int iRet = 0; - - assert(pp2parse != NULL); - assert(*pp2parse != NULL); - assert(pResult != NULL); - - p2parse = *pp2parse; - - /* this is the actual parsing loop - * Remeber: structured data starts with [ and includes any characters - * until the first ] followed by a SP. There may be spaces inside - * structured data. There may also be \] inside the structured data, which - * do NOT terminate an element. - */ - if(*p2parse != '[') - return 1; /* this is NOT structured data! */ - - while(bCont) { - if(*p2parse == '\0') { - iRet = 1; /* this is not valid! */ - bCont = 0; - } else if(*p2parse == '\\' && *(p2parse+1) == ']') { - /* this is escaped, need to copy both */ - *pResult++ = *p2parse++; - *pResult++ = *p2parse++; - } else if(*p2parse == ']' && *(p2parse+1) == ' ') { - /* found end, just need to copy the ] and eat the SP */ - *pResult++ = *p2parse; - p2parse += 2; - bCont = 0; - } else { - *pResult++ = *p2parse++; - } - } - - if(*p2parse == ' ') - ++p2parse; /* eat SP, but only if not at end of string */ - else - iRet = 1; /* there MUST be an SP! */ - *pResult = '\0'; - - /* set the new parse pointer */ - *pp2parse = p2parse; - return 0; -} - -/* parse a RFC-formatted syslog message. This function returns - * 0 if processing of the message shall continue and 1 if something - * went wrong and this messe should be ignored. This function has been - * implemented in the effort to support syslog-protocol. Please note that - * the name (parse *RFC*) stems from the hope that syslog-protocol will - * some time become an RFC. Do not confuse this with informational - * RFC 3164 (which is legacy syslog). - * - * currently supported format: - * - * VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG - * - * is already stripped when this function is entered. VERSION already - * has been confirmed to be "1", but has NOT been stripped from the message. - * - * rger, 2005-11-24 - */ -static int parseRFCSyslogMsg(msg_t *pMsg, int flags) -{ - char *p2parse; - char *pBuf; - int bContParse = 1; - - assert(pMsg != NULL); - assert(pMsg->pszUxTradMsg != NULL); - p2parse = (char*) pMsg->pszUxTradMsg; - - /* do a sanity check on the version and eat it */ - assert(p2parse[0] == '1' && p2parse[1] == ' '); - p2parse += 2; - - /* Now get us some memory we can use as a work buffer while parsing. - * We simply allocated a buffer sufficiently large to hold all of the - * message, so we can not run into any troubles. I think this is - * more wise then to use individual buffers. - */ - if((pBuf = malloc(sizeof(char)* strlen(p2parse) + 1)) == NULL) - return 1; - - /* IMPORTANT NOTE: - * Validation is not actually done below nor are any errors handled. I have - * NOT included this for the current proof of concept. However, it is strongly - * advisable to add it when this code actually goes into production. - * rgerhards, 2005-11-24 - */ - - /* TIMESTAMP */ - if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) { - dbgprintf("no TIMESTAMP detected!\n"); - bContParse = 0; - flags |= ADDDATE; - } - - if (flags & ADDDATE) { - datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ - } - - /* HOSTNAME */ - if(bContParse) { - parseRFCField(&p2parse, pBuf); - MsgSetHOSTNAME(pMsg, pBuf); - } else { - /* we can not parse, so we get the system we - * received the data from. - */ - MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); - } - - /* APP-NAME */ - if(bContParse) { - parseRFCField(&p2parse, pBuf); - MsgSetAPPNAME(pMsg, pBuf); - } - - /* PROCID */ - if(bContParse) { - parseRFCField(&p2parse, pBuf); - MsgSetPROCID(pMsg, pBuf); - } - - /* MSGID */ - if(bContParse) { - parseRFCField(&p2parse, pBuf); - MsgSetMSGID(pMsg, pBuf); - } - - /* STRUCTURED-DATA */ - if(bContParse) { - parseRFCStructuredData(&p2parse, pBuf); - MsgSetStructuredData(pMsg, pBuf); - } - - /* MSG */ - MsgSetMSG(pMsg, p2parse); - - free(pBuf); - return 0; /* all ok */ -} - - -/* parse a legay-formatted syslog message. This function returns - * 0 if processing of the message shall continue and 1 if something - * went wrong and this messe should be ignored. This function has been - * implemented in the effort to support syslog-protocol. - * rger, 2005-11-24 - * As of 2006-01-10, I am removing the logic to continue parsing only - * when a valid TIMESTAMP is detected. Validity of other fields already - * is ignored. This is due to the fact that the parser has grown smarter - * and is now more able to understand different dialects of the syslog - * message format. I do not expect any bad side effects of this change, - * but I thought I log it in this comment. - * rgerhards, 2006-01-10 - */ -static int parseLegacySyslogMsg(msg_t *pMsg, int flags) -{ - char *p2parse; - char *pBuf; - char *pWork; - cstr_t *pStrB; - int iCnt; - int bTAGCharDetected; - - assert(pMsg != NULL); - assert(pMsg->pszUxTradMsg != NULL); - p2parse = (char*) pMsg->pszUxTradMsg; - - /* Check to see if msg contains a timestamp. We stary trying with a - * high-precision one... - */ - if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) { - /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */; - } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse) == TRUE) { - p2parse += 16; - } else if(*p2parse == ' ') { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */ - if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse+1) == TRUE) { - /* indeed, we got it! */ - p2parse += 17; - } else { - flags |= ADDDATE; - } - } else { - flags |= ADDDATE; - } - - /* here we need to check if the timestamp is valid. If it is not, - * we can not continue to parse but must treat the rest as the - * MSG part of the message (as of RFC 3164). - * rgerhards 2004-12-03 - */ - if(flags & ADDDATE) { - datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ - } - - /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we - * do this only when the user has not forbidden this. I now introduce some - * code that allows a user to configure rsyslogd to treat the rest of the - * message as MSG part completely. In this case, the hostname will be the - * machine that we received the message from and the tag will be empty. This - * is meant to be an interim solution, but for now it is in the code. - */ - if(bParseHOSTNAMEandTAG && !(flags & INTERNAL_MSG)) { - /* parse HOSTNAME - but only if this is network-received! - * rger, 2005-11-14: we still have a problem with BSD messages. These messages - * do NOT include a host name. In most cases, this leads to the TAG to be treated - * as hostname and the first word of the message as the TAG. Clearly, this is not - * of advantage ;) I think I have now found a way to handle this situation: there - * are certain characters which are frequently used in TAG (e.g. ':'), which are - * *invalid* in host names. So while parsing the hostname, I check for these characters. - * If I find them, I set a simple flag but continue. After parsing, I check the flag. - * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change - * the fields. I think this logic shall work with any type of syslog message. - */ - bTAGCharDetected = 0; - if(pMsg->bParseHOSTNAME) { - /* TODO: quick and dirty memory allocation */ - /* the memory allocated is far too much in most cases. But on the plus side, - * it is quite fast... - rgerhards, 2007-09-20 - */ - if((pBuf = malloc(sizeof(char)* (strlen(p2parse) +1))) == NULL) - return 1; - pWork = pBuf; - /* this is the actual parsing loop */ - while(*p2parse && *p2parse != ' ' && *p2parse != ':') { - if(*p2parse == '[' || *p2parse == ']' || *p2parse == '/') - bTAGCharDetected = 1; - *pWork++ = *p2parse++; - } - /* we need to handle ':' seperately, because it terminates the - * TAG - so we also need to terminate the parser here! - * rgerhards, 2007-09-10 *p2parse points to a valid address here in - * any case. We can reach this point only if we are at end of string, - * or we have a ':' or ' '. What the if below does is check if we are - * not at end of string and, if so, advance the parse pointer. If we - * are already at end of string, *p2parse is equal to '\0', neither if - * will be true and the parse pointer remain as is. This is perfectly - * well. - */ - if(*p2parse == ':') { - bTAGCharDetected = 1; - /* We will move hostname to tag, so preserve ':' (otherwise we - * will needlessly change the message format) */ - *pWork++ = *p2parse++; - } else if(*p2parse == ' ') - ++p2parse; - *pWork = '\0'; - MsgAssignHOSTNAME(pMsg, pBuf); - } - /* check if we seem to have a TAG */ - if(bTAGCharDetected) { - /* indeed, this smells like a TAG, so lets use it for this. We take - * the HOSTNAME from the sender system instead. - */ - dbgprintf("HOSTNAME contains invalid characters, assuming it to be a TAG.\n"); - moveHOSTNAMEtoTAG(pMsg); - MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); - } - - /* now parse TAG - that should be present in message from all sources. - * This code is somewhat not compliant with RFC 3164. As of 3164, - * the TAG field is ended by any non-alphanumeric character. In - * practice, however, the TAG often contains dashes and other things, - * which would end the TAG. So it is not desirable. As such, we only - * accept colon and SP to be terminators. Even there is a slight difference: - * a colon is PART of the TAG, while a SP is NOT part of the tag - * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character - * size limit (from RFC3164) on the tag. This had bad effects on existing - * envrionments, as sysklogd didn't obey it either (probably another bug - * in RFC3164...). We now receive the full size, but will modify the - * outputs so that only 32 characters max are used by default. - */ - /* The following code in general is quick & dirty - I need to get - * it going for a test, rgerhards 2004-11-16 */ - /* lol.. we tried to solve it, just to remind ourselfs that 32 octets - * is the max size ;) we need to shuffle the code again... Just for - * the records: the code is currently clean, but we could optimize it! */ - if(!bTAGCharDetected) { - uchar *pszTAG; - if(rsCStrConstruct(&pStrB) != RS_RET_OK) - return 1; - rsCStrSetAllocIncrement(pStrB, 33); - pWork = pBuf; - iCnt = 0; - while(*p2parse && *p2parse != ':' && *p2parse != ' ') { - rsCStrAppendChar(pStrB, *p2parse++); - ++iCnt; - } - if(*p2parse == ':') { - ++p2parse; - rsCStrAppendChar(pStrB, ':'); - } - rsCStrFinish(pStrB); - - rsCStrConvSzStrAndDestruct(pStrB, &pszTAG, 1); - if(pszTAG == NULL) - { /* rger, 2005-11-10: no TAG found - this implies that what - * we have considered to be the HOSTNAME is most probably the - * TAG. We consider it so probable, that we now adjust it - * that way. So we pick up the previously set hostname, assign - * it to tag and use the sender system (from IP stack) as - * the hostname. This situation is the standard case with - * stock BSD syslogd. - */ - dbgprintf("No TAG in message, assuming that HOSTNAME is missing.\n"); - moveHOSTNAMEtoTAG(pMsg); - MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); - } else { /* we have a TAG, so we can happily set it ;) */ - MsgAssignTAG(pMsg, pszTAG); - } - } else { - /* we have no TAG, so we ... */ - /*DO NOTHING*/; - } - } else { - /* we enter this code area when the user has instructed rsyslog NOT - * to parse HOSTNAME and TAG - rgerhards, 2006-03-13 - */ - if(!(flags & INTERNAL_MSG)) - { - dbgprintf("HOSTNAME and TAG not parsed by user configuraton.\n"); - MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); - } - } - - /* The rest is the actual MSG */ - MsgSetMSG(pMsg, p2parse); - - return 0; /* all ok */ -} - - -/* submit a fully created message to the main message queue. The message is - * fully processed and parsed, so no parsing at all happens. This is primarily - * a hook to prevent the need for callers to know about the main message queue - * (which may change in the future as we will probably have multiple rule - * sets and thus queues...). - * rgerhards, 2008-02-13 - */ -rsRetVal -submitMsg(msg_t *pMsg) -{ - DEFiRet; - - ISOBJ_TYPE_assert(pMsg, msg); - - MsgPrepareEnqueue(pMsg); - queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); - - RETiRet; -} - - -/* - * Log a message to the appropriate log files, users, etc. based on - * the priority. - * rgerhards 2004-11-08: actually, this also decodes all but the PRI part. - * rgerhards 2004-11-09: ... but only, if syslogd could properly be initialized - * if not, we use emergency logging to the console and in - * this case, no further decoding happens. - * changed to no longer receive a plain message but a msg object instead. - * rgerhards-2004-11-16: OK, we are now up to another change... This method - * actually needs to PARSE the message. How exactly this needs to happen depends on - * a number of things. Most importantly, it depends on the source. For example, - * locally received messages (SOURCE_UNIXAF) do NOT have a hostname in them. So - * we need to treat them differntly form network-received messages which have. - * Well, actually not all network-received message really have a hostname. We - * can just hope they do, but we can not be sure. So this method tries to find - * whatever can be found in the message and uses that... Obviously, there is some - * potential for misinterpretation, which we simply can not solve under the - * circumstances given. - */ -void -logmsg(msg_t *pMsg, int flags) -{ - char *msg; - - BEGINfunc - assert(pMsg != NULL); - assert(pMsg->pszUxTradMsg != NULL); - msg = (char*) pMsg->pszUxTradMsg; - dbgprintf("logmsg: flags %x, from '%s', msg %s\n", flags, getRcvFrom(pMsg), msg); - - /* rger 2005-11-24 (happy thanksgiving!): we now need to check if we have - * a traditional syslog message or one formatted according to syslog-protocol. - * We need to apply different parsers depending on that. We use the - * -protocol VERSION field for the detection. - */ - if(msg[0] == '1' && msg[1] == ' ') { - dbgprintf("Message has syslog-protocol format.\n"); - setProtocolVersion(pMsg, 1); - if(parseRFCSyslogMsg(pMsg, flags) == 1) { - msgDestruct(&pMsg); - return; - } - } else { /* we have legacy syslog */ - dbgprintf("Message has legacy syslog format.\n"); - setProtocolVersion(pMsg, 0); - if(parseLegacySyslogMsg(pMsg, flags) == 1) { - msgDestruct(&pMsg); - return; - } - } - - /* ---------------------- END PARSING ---------------- */ - - /* now submit the message to the main queue - then we are done */ - pMsg->msgFlags = flags; - MsgPrepareEnqueue(pMsg); - queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); - ENDfunc -} - - -static void -reapchild() -{ - int saved_errno = errno; - struct sigaction sigAct; - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = reapchild; - sigaction(SIGCHLD, &sigAct, NULL); /* reset signal handler -ASP */ - - while(waitpid(-1, NULL, WNOHANG) > 0); - errno = saved_errno; -} - - -/* helper to doFlushRptdMsgs() to flush the individual action links via llExecFunc - * rgerhards, 2007-08-02 - */ -DEFFUNC_llExecFunc(flushRptdMsgsActions) -{ - action_t *pAction = (action_t*) pData; - - assert(pAction != NULL); - - BEGINfunc - LockObj(pAction); - if (pAction->f_prevcount && time(NULL) >= REPEATTIME(pAction)) { - dbgprintf("flush %s: repeated %d times, %d sec.\n", - module.GetStateName(pAction->pMod), pAction->f_prevcount, - repeatinterval[pAction->f_repeatcount]); - actionWriteToAction(pAction); - BACKOFF(pAction); - } - UnlockObj(pAction); - - ENDfunc - return RS_RET_OK; /* we ignore errors, we can not do anything either way */ -} - - -/* This method flushes reapeat messages. - */ -static void -doFlushRptdMsgs(void) -{ - register selector_t *f; - - /* see if we need to flush any "message repeated n times"... - * Note that this interferes with objects running on other threads. - * We are using appropriate locking inside the function to handle that. - */ - for (f = Files; f != NULL ; f = f->f_next) { - llExecFunc(&f->llActList, flushRptdMsgsActions, NULL); - } -} - - -static void debug_switch() -{ - struct sigaction sigAct; - - if(debugging_on == 0) { - debugging_on = 1; - dbgprintf("Switching debugging_on to true\n"); - } else { - dbgprintf("Switching debugging_on to false\n"); - debugging_on = 0; - } - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = debug_switch; - sigaction(SIGUSR1, &sigAct, NULL); -} - - -void legacyOptsEnq(uchar *line) -{ - legacyOptsLL_t *pNew; - - pNew = malloc(sizeof(legacyOptsLL_t)); - if(line == NULL) - pNew->line = NULL; - else - pNew->line = (uchar *) strdup((char *) line); - pNew->next = NULL; - - if(pLegacyOptsLL == NULL) - pLegacyOptsLL = pNew; - else { - legacyOptsLL_t *pThis = pLegacyOptsLL; - - while(pThis->next != NULL) - pThis = pThis->next; - pThis->next = pNew; - } -} - - -void legacyOptsFree(void) -{ - legacyOptsLL_t *pThis = pLegacyOptsLL, *pNext; - - while(pThis != NULL) { - if(pThis->line != NULL) - free(pThis->line); - pNext = pThis->next; - free(pThis); - pThis = pNext; - } -} - - -void legacyOptsHook(void) -{ - legacyOptsLL_t *pThis = pLegacyOptsLL; - - while(pThis != NULL) { - if(pThis->line != NULL) { - errno = 0; - errmsg.LogError(NO_ERRCODE, "Warning: backward compatibility layer added to following " - "directive to rsyslog.conf: %s", pThis->line); - conf.cfsysline(pThis->line); - } - pThis = pThis->next; - } -} - - -void legacyOptsParseTCP(char ch, char *arg) -{ - register int i; - register char *pArg = arg; - static char conflict = '\0'; - - if((conflict == 'g' && ch == 't') || (conflict == 't' && ch == 'g')) { - fprintf(stderr, "rsyslog: If you want to use both -g and -t, use directives instead, -%c ignored.\n", ch); - return; - } else - conflict = ch; - - /* extract port */ - i = 0; - while(isdigit((int) *pArg)) - i = i * 10 + *pArg++ - '0'; - - /* number of sessions */ - if(*pArg == '\0' || *pArg == ',') { - if(ch == 't') - legacyOptsEnq((uchar *) "ModLoad imtcp"); - else if(ch == 'g') - legacyOptsEnq((uchar *) "ModLoad imgssapi"); - - if(i >= 0 && i <= 65535) { - uchar line[30]; - - if(ch == 't') { - snprintf((char *) line, sizeof(line), "InputTCPServerRun %d", i); - } else if(ch == 'g') { - snprintf((char *) line, sizeof(line), "InputGSSServerRun %d", i); - } - legacyOptsEnq(line); - } else { - if(ch == 't') { - fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i); - legacyOptsEnq((uchar *) "InputTCPServerRun 514"); - } else if(ch == 'g') { - fprintf(stderr, "rsyslogd: Invalid GSS listen port %d - changed to 514.\n", i); - legacyOptsEnq((uchar *) "InputGSSServerRun 514"); - } - } - - if(*pArg == ',') { - ++pArg; - while(isspace((int) *pArg)) - ++pArg; - i = 0; - while(isdigit((int) *pArg)) { - i = i * 10 + *pArg++ - '0'; - } - if(i > 0) { - uchar line[30]; - - snprintf((char *) line, sizeof(line), "InputTCPMaxSessions %d", i); - legacyOptsEnq(line); - } else { - if(ch == 't') { - fprintf(stderr, "rsyslogd: TCP session max configured " - "to %d [-t %s] - changing to 1.\n", i, arg); - legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); - } else if (ch == 'g') { - fprintf(stderr, "rsyslogd: GSS session max configured " - "to %d [-g %s] - changing to 1.\n", i, arg); - legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); - } - } - } - } else - fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", arg); -} - - -/* doDie() is a signal handler. If called, it sets the bFinished variable - * to indicate the program should terminate. However, it does not terminate - * it itself, because that causes issues with multi-threading. The actual - * termination is then done on the main thread. This solution might introduce - * a minimal delay, but it is much cleaner than the approach of doing everything - * inside the signal handler. - * rgerhards, 2005-10-26 - */ -static void doDie(int sig) -{ - static int iRetries = 0; /* debug aid */ - printf("DoDie called.\n"); - if(iRetries++ == 4) { - printf("DoDie called 5 times - unconditional exit\n"); - abort(); - } - bFinished = sig; -} - - -/* This function frees all dynamically allocated memory for program termination. - * It must be called only immediately before exit(). It is primarily an aid - * for memory debuggers, which prevents cluttered outupt. - * rgerhards, 2008-03-20 - */ -static void -freeAllDynMemForTermination(void) -{ - if(pszWorkDir != NULL) - free(pszWorkDir); - if(pszMainMsgQFName != NULL) - free(pszMainMsgQFName); - if(pModDir != NULL) - free(pModDir); - if(LocalHostName != NULL) - free(LocalHostName); -} - - -/* die() is called when the program shall end. This typically only occurs - * during sigterm or during the initialization. - * As die() is intended to shutdown rsyslogd, it is - * safe to call exit() here. Just make sure that die() itself is not called - * at inapropriate places. As a general rule of thumb, it is a bad idea to add - * any calls to die() in new code! - * rgerhards, 2005-10-24 - */ -static void -die(int sig) -{ - char buf[256]; - - dbgprintf("exiting on signal %d\n", sig); - - /* IMPORTANT: we should close the inputs first, and THEN send our termination - * message. If we do it the other way around, logmsgInternal() may block on - * a full queue and the inputs still fill up that queue. Depending on the - * scheduling order, we may end up with logmsgInternal being held for a quite - * long time. When the inputs are terminated first, that should not happen - * because the queue is drained in parallel. The situation could only become - * an issue with extremely long running actions in a queue full environment. - * However, such actions are at least considered poorly written, if not - * outright wrong. So we do not care about this very remote problem. - * rgerhards, 2008-01-11 - */ - - /* close the inputs */ - dbgprintf("Terminating input threads...\n"); - thrdTerminateAll(); /* TODO: inputs only, please */ - - /* and THEN send the termination log message (see long comment above) */ - if (sig) { - (void) snprintf(buf, sizeof(buf) / sizeof(char), - " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ - "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"]" " exiting on signal %d.", - (int) myPid, sig); - errno = 0; - logmsgInternal(LOG_SYSLOG|LOG_INFO, buf, ADDDATE); - } - - /* drain queue (if configured so) and stop main queue worker thread pool */ - dbgprintf("Terminating main queue...\n"); - queueDestruct(&pMsgQueue); - pMsgQueue = NULL; - - /* Free ressources and close connections. This includes flushing any remaining - * repeated msgs. - */ - dbgprintf("Terminating outputs...\n"); - freeSelectors(); - - dbgprintf("all primary multi-thread sources have been terminated - now doing aux cleanup...\n"); - /* rger 2005-02-22 - * now clean up the in-memory structures. OK, the OS - * would also take care of that, but if we do it - * ourselfs, this makes finding memory leaks a lot - * easier. - */ - tplDeleteAll(); - - remove_pid(PidFile); - if(glblHadMemShortage) - dbgprintf("Had memory shortage at least once during the run.\n"); - - /* de-init some modules */ - modExitIminternal(); - - /*dbgPrintAllDebugInfo(); / * this is the last spot where this can be done - below output modules are unloaded! */ - - /* the following line cleans up CfSysLineHandlers that were not based on loadable - * modules. As such, they are not yet cleared. - */ - unregCfSysLineHdlrs(); - - legacyOptsFree(); - - /* terminate the remaining classes */ - GlobalClassExit(); - - /* TODO: this would also be the right place to de-init the builtin output modules. We - * do not currently do that, because the module interface does not allow for - * it. This will come some time later (it's essential with loadable modules). - * For the time being, this is a memory leak on exit, but as the process is - * terminated, we do not really bother about it. - * rgerhards, 2007-08-03 - * I have added some code now, but all that mod init/de-init should be moved to - * init, so that modules are unloaded and reloaded on HUP to. Eventually it should go - * into freeSelectors() - but that needs to be seen. -- rgerhards, 2007-08-09 - */ - module.UnloadAndDestructAll(eMOD_LINK_ALL); - - dbgprintf("Clean shutdown completed, bye\n"); - /* dbgClassExit MUST be the last one, because it de-inits the debug system */ - dbgClassExit(); - - /* free all remaining memory blocks - this is not absolutely necessary, but helps - * us keep memory debugger logs clean and this is in aid in developing. It doesn't - * cost much time, so we do it always. -- rgerhards, 2008-03-20 - */ - freeAllDynMemForTermination(); - /* NO CODE HERE - feeelAllDynMemForTermination() must be the last thing before exit()! */ - exit(0); /* "good" exit, this is the terminator function for rsyslog [die()] */ -} - -/* - * Signal handler to terminate the parent process. - * rgerhards, 2005-10-24: this is only called during forking of the - * detached syslogd. I consider this method to be safe. - */ -static void doexit() -{ - exit(0); /* "good" exit, only during child-creation */ -} - - -/* set the action resume interval - */ -static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal) -{ - return actionSetGlobalResumeInterval(iNewVal); -} - - -/* set the processes umask (upon configuration request) - */ -static rsRetVal setUmask(void __attribute__((unused)) *pVal, int iUmask) -{ - umask(iUmask); - dbgprintf("umask set to 0%3.3o.\n", iUmask); - - return RS_RET_OK; -} - - -/* helper to freeSelectors(), used with llExecFunc() to flush - * pending output. -- rgerhards, 2007-08-02 - * We do not need to lock the action object here as the processing - * queue is already empty and no other threads are running when - * we call this function. -- rgerhards, 2007-12-12 - */ -DEFFUNC_llExecFunc(freeSelectorsActions) -{ - action_t *pAction = (action_t*) pData; - - assert(pAction != NULL); - - /* flush any pending output */ - if(pAction->f_prevcount) { - actionWriteToAction(pAction); - } - - return RS_RET_OK; /* never fails ;) */ -} - - -/* Close all open log files and free selector descriptor array. - */ -static void freeSelectors(void) -{ - selector_t *f; - selector_t *fPrev; - - if(Files != NULL) { - dbgprintf("Freeing log structures.\n"); - - for(f = Files ; f != NULL ; f = f->f_next) { - llExecFunc(&f->llActList, freeSelectorsActions, NULL); - } - - /* actions flushed and ready for destruction - so do that... */ - f = Files; - while (f != NULL) { - fPrev = f; - f = f->f_next; - selectorDestruct(fPrev); - } - - /* Reflect the deletion of the selectors linked list. */ - Files = NULL; - bHaveMainQueue = 0; - } -} - - -/* helper to dbPrintInitInfo, to print out all actions via - * the llExecFunc() facility. - * rgerhards, 2007-08-02 - */ -DEFFUNC_llExecFunc(dbgPrintInitInfoAction) -{ - DEFiRet; - iRet = actionDbgPrint((action_t*) pData); - dbgprintf("\n"); - - RETiRet; -} - -/* print debug information as part of init(). This pretty much - * outputs the whole config of rsyslogd. I've moved this code - * out of init() to clean it somewhat up. - * rgerhards, 2007-07-31 - */ -static void dbgPrintInitInfo(void) -{ - register selector_t *f; - int iSelNbr = 1; - int i; - - dbgprintf("\nActive selectors:\n"); - for (f = Files; f != NULL ; f = f->f_next) { - dbgprintf("Selector %d:\n", iSelNbr++); - if(f->pCSProgNameComp != NULL) - dbgprintf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp)); - if(f->eHostnameCmpMode != HN_NO_COMP) - dbgprintf("hostname: %s '%s'\n", - f->eHostnameCmpMode == HN_COMP_MATCH ? - "only" : "allbut", - rsCStrGetSzStrNoNULL(f->pCSHostnameComp)); - if(f->f_filter_type == FILTER_PRI) { - for (i = 0; i <= LOG_NFACILITIES; i++) - if (f->f_filterData.f_pmask[i] == TABLE_NOPRI) - dbgprintf(" X "); - else - dbgprintf("%2X ", f->f_filterData.f_pmask[i]); - } else if(f->f_filter_type == FILTER_EXPR) { - dbgprintf("EXPRESSION-BASED Filter: can currently not be displayed"); - } else { - dbgprintf("PROPERTY-BASED Filter:\n"); - dbgprintf("\tProperty.: '%s'\n", - rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName)); - dbgprintf("\tOperation: "); - if(f->f_filterData.prop.isNegated) - dbgprintf("NOT "); - dbgprintf("'%s'\n", getFIOPName(f->f_filterData.prop.operation)); - dbgprintf("\tValue....: '%s'\n", - rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue)); - dbgprintf("\tAction...: "); - } - - dbgprintf("\nActions:\n"); - llExecFunc(&f->llActList, dbgPrintInitInfoAction, NULL); /* actions */ - - dbgprintf("\n"); - } - dbgprintf("\n"); - if(bDebugPrintTemplateList) - tplPrintList(); - if(bDebugPrintModuleList) - module.PrintList(); - ochPrintList(); - - if(bDebugPrintCfSysLineHandlerList) - dbgPrintCfSysLineHandlers(); - - dbgprintf("Messages with malicious PTR DNS Records are %sdropped.\n", - bDropMalPTRMsgs ? "" : "not "); - - dbgprintf("Control characters are %sreplaced upon reception.\n", - bEscapeCCOnRcv? "" : "not "); - - if(bEscapeCCOnRcv) - dbgprintf("Control character escape sequence prefix is '%c'.\n", - cCCEscapeChar); - - dbgprintf("Main queue size %d messages.\n", iMainMsgQueueSize); - dbgprintf("Main queue worker threads: %d, Perists every %d updates.\n", - iMainMsgQueueNumWorkers, iMainMsgQPersistUpdCnt); - dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n", - iMainMsgQtoQShutdown, iMainMsgQtoActShutdown, iMainMsgQtoEnq); - dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n", - iMainMsgQHighWtrMark, iMainMsgQLowWtrMark, iMainMsgQDiscardMark, iMainMsgQDiscardSeverity); - dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n", - bMainMsgQSaveOnShutdown, iMainMsgQueMaxDiskSpace); - /* TODO: add - iActionRetryCount = 0; - iActionRetryInterval = 30000; - static int iMainMsgQtoWrkShutdown = 60000; - static int iMainMsgQtoWrkMinMsgs = 100; - static int iMainMsgQbSaveOnShutdown = 1; - iMainMsgQueMaxDiskSpace = 0; - setQPROP(queueSettoWrkShutdown, "$MainMsgQueueTimeoutWorkerThreadShutdown", 5000); - setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100); - setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1); - */ - dbgprintf("Work Directory: '%s'.\n", pszWorkDir); -} - - -/* Start the input modules. This function will probably undergo big changes - * while we implement the input module interface. For now, it does the most - * important thing to get at least my poor initial input modules up and - * running. Almost no config option is taken. - * rgerhards, 2007-12-14 - */ -static rsRetVal -startInputModules(void) -{ - DEFiRet; - modInfo_t *pMod; - - /* loop through all modules and activate them (brr...) */ - pMod = module.GetNxtType(NULL, eMOD_IN); - while(pMod != NULL) { - if((iRet = pMod->mod.im.willRun()) == RS_RET_OK) { - /* activate here */ - thrdCreate(pMod->mod.im.runInput, pMod->mod.im.afterRun); - } else { - dbgprintf("module %lx will not run, iRet %d\n", (unsigned long) pMod, iRet); - } - pMod = module.GetNxtType(pMod, eMOD_IN); - } - - ENDfunc - return RS_RET_OK; /* intentional: we do not care about module errors */ -} - - -/* INIT -- Initialize syslogd from configuration table - * init() is called at initial startup AND each time syslogd is HUPed - */ -static void -init(void) -{ - DEFiRet; - char cbuf[BUFSIZ]; - char bufStartUpMsg[512]; - struct sigaction sigAct; - - thrdTerminateAll(); /* stop all running input threads - TODO: reconsider location! */ - - /* initialize some static variables */ - pDfltHostnameCmp = NULL; - pDfltProgNameCmp = NULL; - eDfltHostnameCmpMode = HN_NO_COMP; - - dbgprintf("rsyslog %s - called init()\n", VERSION); - - /* delete the message queue, which also flushes all messages left over */ - if(pMsgQueue != NULL) { - dbgprintf("deleting main message queue\n"); - queueDestruct(&pMsgQueue); /* delete pThis here! */ - pMsgQueue = NULL; - } - - /* Close all open log files and free log descriptor array. This also frees - * all output-modules instance data. - */ - freeSelectors(); - - /* Unload all non-static modules */ - dbgprintf("Unloading non-static modules.\n"); - module.UnloadAndDestructAll(eMOD_LINK_DYNAMIC_LOADED); - - dbgprintf("Clearing templates.\n"); - tplDeleteNew(); - - /* re-setting values to defaults (where applicable) */ - /* TODO: once we have loadable modules, we must re-visit this code. The reason is - * that config variables are not re-set, because the module is not yet loaded. On - * the other hand, that doesn't matter, because the module got unloaded and is then - * re-loaded, so the variables should be re-set via that way. In any case, we should - * think about the whole situation when we implement loadable plugins. - * rgerhards, 2007-07-31 - */ - conf.cfsysline((uchar*)"ResetConfigVariables"); - - /* open the configuration file */ - if((iRet = conf.processConfFile(ConfFile)) != RS_RET_OK) { - /* rgerhards: this code is executed to set defaults when the - * config file could not be opened. We might think about - * abandoning the run in this case - but this, too, is not - * very clever... So we stick with what we have. - * We ignore any errors while doing this - we would be lost anyhow... - */ - selector_t *f = NULL; - char szTTYNameBuf[_POSIX_TTY_NAME_MAX+1]; /* +1 for NULL character */ - dbgprintf("primary config file could not be opened - using emergency definitions.\n"); - conf.cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f); - conf.cfline((uchar*)"*.PANIC\t*", &f); - if(ttyname_r(0, szTTYNameBuf, sizeof(szTTYNameBuf)) == 0) { - snprintf(cbuf,sizeof(cbuf), "*.*\t%s", szTTYNameBuf); - conf.cfline((uchar*)cbuf, &f); - } - selectorAddList(f); - } - - legacyOptsHook(); - - /* we are now done with reading the configuration. This is the right time to - * free some objects that were just needed for loading it. rgerhards 2005-10-19 - */ - if(pDfltHostnameCmp != NULL) { - rsCStrDestruct(&pDfltHostnameCmp); - } - - if(pDfltProgNameCmp != NULL) { - rsCStrDestruct(&pDfltProgNameCmp); - } - - /* some checks */ - if(iMainMsgQueueNumWorkers < 1) { - errmsg.LogError(NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n"); - iMainMsgQueueNumWorkers = 1; - } - - if(MainMsgQueType == QUEUETYPE_DISK) { - errno = 0; /* for logerror! */ - if(pszWorkDir == NULL) { - errmsg.LogError(NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. " - "Using 'FixedArray' instead.\n"); - MainMsgQueType = QUEUETYPE_FIXED_ARRAY; - } - if(pszMainMsgQFName == NULL) { - errmsg.LogError(NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in " - "'disk' mode. Using 'FixedArray' instead.\n"); - MainMsgQueType = QUEUETYPE_FIXED_ARRAY; - } - } - - /* switch the message object to threaded operation, if necessary */ - if(MainMsgQueType == QUEUETYPE_DIRECT || iMainMsgQueueNumWorkers > 1) { - MsgEnableThreadSafety(); - } - - /* create message queue */ - CHKiRet_Hdlr(queueConstruct(&pMsgQueue, MainMsgQueType, iMainMsgQueueNumWorkers, iMainMsgQueueSize, msgConsumer)) { - /* no queue is fatal, we need to give up in that case... */ - fprintf(stderr, "fatal error %d: could not create message queue - rsyslogd can not run!\n", iRet); - exit(1); - } - /* name our main queue object (it's not fatal if it fails...) */ - obj.SetName((obj_t*) pMsgQueue, (uchar*) "main queue"); - - /* ... set some properties ... */ -# define setQPROP(func, directive, data) \ - CHKiRet_Hdlr(func(pMsgQueue, data)) { \ - errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ - } -# define setQPROPstr(func, directive, data) \ - CHKiRet_Hdlr(func(pMsgQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \ - errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ - } - - setQPROP(queueSetMaxFileSize, "$MainMsgQueueFileSize", iMainMsgQueMaxFileSize); - setQPROP(queueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", iMainMsgQueMaxDiskSpace); - setQPROPstr(queueSetFilePrefix, "$MainMsgQueueFileName", pszMainMsgQFName); - setQPROP(queueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval", iMainMsgQPersistUpdCnt); - setQPROP(queueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", iMainMsgQtoQShutdown ); - setQPROP(queueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion", iMainMsgQtoActShutdown); - setQPROP(queueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown", iMainMsgQtoWrkShutdown); - setQPROP(queueSettoEnq, "$MainMsgQueueTimeoutEnqueue", iMainMsgQtoEnq); - setQPROP(queueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", iMainMsgQHighWtrMark); - setQPROP(queueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", iMainMsgQLowWtrMark); - setQPROP(queueSetiDiscardMrk, "$MainMsgQueueDiscardMark", iMainMsgQDiscardMark); - setQPROP(queueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity", iMainMsgQDiscardSeverity); - setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", iMainMsgQWrkMinMsgs); - setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", bMainMsgQSaveOnShutdown); - setQPROP(queueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", iMainMsgQDeqSlowdown); - setQPROP(queueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin", iMainMsgQueueDeqtWinFromHr); - setQPROP(queueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", iMainMsgQueueDeqtWinToHr); - -# undef setQPROP -# undef setQPROPstr - - /* ... and finally start the queue! */ - CHKiRet_Hdlr(queueStart(pMsgQueue)) { - /* no queue is fatal, we need to give up in that case... */ - fprintf(stderr, "fatal error %d: could not start message queue - rsyslogd can not run!\n", iRet); - exit(1); - } - - bHaveMainQueue = (MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1; - dbgprintf("Main processing queue is initialized and running\n"); - - /* the output part and the queue is now ready to run. So it is a good time - * to start the inputs. Please note that the net code above should be - * shuffled to down here once we have everything in input modules. - * rgerhards, 2007-12-14 - */ - startInputModules(); - - if(Debug) { - dbgPrintInitInfo(); - } - - /* we now generate the startup message. It now includes everything to - * identify this instance. -- rgerhards, 2005-08-17 - */ - snprintf(bufStartUpMsg, sizeof(bufStartUpMsg)/sizeof(char), - " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ - "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] restart", - (int) myPid); - logmsgInternal(LOG_SYSLOG|LOG_INFO, bufStartUpMsg, ADDDATE); - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = sighup_handler; - sigaction(SIGHUP, &sigAct, NULL); - - dbgprintf(" (re)started.\n"); - ENDfunc -} - - -/* add a completely-processed selector (after config line parsing) to - * the linked list of selectors. We now need to check - * if it has any actions associated and, if so, link it to the linked - * list. If it has nothing associated with it, we can simply discard - * it. - * We have one special case during initialization: then, the current - * selector is NULL, which means we do not need to care about it at - * all. -- rgerhards, 2007-08-01 - */ -rsRetVal -selectorAddList(selector_t *f) -{ - DEFiRet; - int iActionCnt; - - static selector_t *nextp = NULL; /* TODO: make this go away (see comment below) */ - - if(f != NULL) { - CHKiRet(llGetNumElts(&f->llActList, &iActionCnt)); - if(iActionCnt == 0) { - errmsg.LogError(NO_ERRCODE, "warning: selector line without actions will be discarded"); - selectorDestruct(f); - } else { - /* successfully created an entry */ - dbgprintf("selector line successfully processed\n"); - /* TODO: we should use the linked list class for the selector list, else we need to add globals - * ... well nextp could be added temporarily... - * Thanks to varmojfekoj for having the idea to just use "Files" to make this - * code work. I had actually forgotten to fix the code here before moving to 1.18.0. - * And, of course, I also did not migrate the selector_t structure to the linked list class. - * However, that should still be one of the very next things to happen. - * rgerhards, 2007-08-06 - */ - if(Files == NULL) { - Files = f; - } else { - nextp->f_next = f; - } - nextp = f; - } - } - -finalize_it: - RETiRet; -} - - -/* set the main message queue mode - * rgerhards, 2008-01-03 - */ -static rsRetVal setMainMsgQueType(void __attribute__((unused)) *pVal, uchar *pszType) -{ - DEFiRet; - - if (!strcasecmp((char *) pszType, "fixedarray")) { - MainMsgQueType = QUEUETYPE_FIXED_ARRAY; - dbgprintf("main message queue type set to FIXED_ARRAY\n"); - } else if (!strcasecmp((char *) pszType, "linkedlist")) { - MainMsgQueType = QUEUETYPE_LINKEDLIST; - dbgprintf("main message queue type set to LINKEDLIST\n"); - } else if (!strcasecmp((char *) pszType, "disk")) { - MainMsgQueType = QUEUETYPE_DISK; - dbgprintf("main message queue type set to DISK\n"); - } else if (!strcasecmp((char *) pszType, "direct")) { - MainMsgQueType = QUEUETYPE_DIRECT; - dbgprintf("main message queue type set to DIRECT (no queueing at all)\n"); - } else { - errmsg.LogError(NO_ERRCODE, "unknown mainmessagequeuetype parameter: %s", (char *) pszType); - iRet = RS_RET_INVALID_PARAMS; - } - free(pszType); /* no longer needed */ - - RETiRet; -} - - -/* - * The following function is resposible for handling a SIGHUP signal. Since - * we are now doing mallocs/free as part of init we had better not being - * doing this during a signal handler. Instead this function simply sets - * a flag variable which will tell the main loop to go through a restart. - */ -void sighup_handler() -{ - struct sigaction sigAct; - - restart = 1; - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = sighup_handler; - sigaction(SIGHUP, &sigAct, NULL); - - return; -} - - -/** - * getSubString - * - * Copy a string byte by byte until the occurrence - * of a given separator. - * - * \param ppSrc Pointer to a pointer of the source array of characters. If a - separator detected the Pointer points to the next char after the - separator. Except if the end of the string is dedected ('\n'). - Then it points to the terminator char. - * \param pDst Pointer to the destination array of characters. Here the substing - will be stored. - * \param DstSize Maximum numbers of characters to store. - * \param cSep Separator char. - * \ret int Returns 0 if no error occured. - * - * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time - * so that it treats ' ' as a request for whitespace. But in general, the function and its callers - * should be changed over time, this is not really very good code... - */ -int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) -{ - uchar *pSrc = *ppSrc; - int iErr = 0; /* 0 = no error, >0 = error */ - while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { - *pDst++ = *(pSrc)++; - DstSize--; - } - /* check if the Dst buffer was to small */ - if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { - dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); - iErr = 1; - } - if (*pSrc == '\0' || *pSrc == '\n') - /* this line was missing, causing ppSrc to be invalid when it - * was returned in case of end-of-string. rgerhards 2005-07-29 - */ - *ppSrc = pSrc; - else - *ppSrc = pSrc+1; - *pDst = '\0'; - return iErr; -} - - -/* this function pulls all internal messages from the buffer - * and puts them into the processing engine. - * We can only do limited error handling, as this would not - * really help us. TODO: add error messages? - * rgerhards, 2007-08-03 - */ -static void processImInternal(void) -{ - int iPri; - int iFlags; - msg_t *pMsg; - - while(iminternalRemoveMsg(&iPri, &pMsg, &iFlags) == RS_RET_OK) { - logmsg(pMsg, iFlags); - } -} - - -/* This is the main processing loop. It is called after successful initialization. - * When it returns, the syslogd terminates. - * Its sole function is to provide some housekeeping things. The real work is done - * by the other threads spawned. - */ -static void -mainloop(void) -{ - struct timeval tvSelectTimeout; - - BEGINfunc - while(!bFinished){ - /* first check if we have any internal messages queued and spit them out */ - /* TODO: do we need this any longer? I doubt it, but let's care about it - * later -- rgerhards, 2007-12-21 - */ - processImInternal(); - - /* this is now just a wait */ - tvSelectTimeout.tv_sec = TIMERINTVL; - tvSelectTimeout.tv_usec = 0; - select(1, NULL, NULL, NULL, &tvSelectTimeout); - if(bFinished) - break; /* exit as quickly as possible - see long comment below */ - - /* If we received a HUP signal, we call doFlushRptdMsgs() a bit early. This - * doesn't matter, because doFlushRptdMsgs() checks timestamps. What may happen, - * however, is that the too-early call may lead to a bit too-late output - * of "last message repeated n times" messages. But that is quite acceptable. - * rgerhards, 2007-12-21 - * ... and just to explain, we flush here because that is exactly what the mainloop - * shall do - provide a periodic interval in which not-yet-flushed messages will - * be flushed. Be careful, there is a potential race condition: doFlushRptdMsgs() - * needs to aquire a lock on the action objects. If, however, long-running consumers - * cause the main queue worker threads to lock them for a long time, we may receive - * a starvation condition, resulting in the mainloop being held on lock for an extended - * period of time. That, in turn, could lead to unresponsiveness to termination - * requests. It is especially important that the bFinished flag is checked before - * doFlushRptdMsgs() is called (I know because I ran into that situation). I am - * not yet sure if the remaining probability window of a termination-related - * problem is large enough to justify changing the code - I would consider it - * extremely unlikely that the problem ever occurs in practice. Fixing it would - * require not only a lot of effort but would cost considerable performance. So - * for the time being, I think the remaining risk can be accepted. - * rgerhards, 2008-01-10 - */ - doFlushRptdMsgs(); - - if(restart) { - dbgprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); - /* main queue is stopped as part of init() */ - init(); - restart = 0; - continue; - } - } - ENDfunc -} - -/* If user is not root, prints warnings or even exits - * TODO: check all dynafiles for write permission - * ... but it is probably better to wait here until we have - * a module interface - rgerhards, 2007-07-23 - */ -static void checkPermissions() -{ -#if 0 - /* TODO: this function must either be redone or removed - now with the input modules, - * there is no such simple check we can do. What we can check, however, is if there is - * any input module active and terminate, if not. -- rgerhards, 2007-12-26 - */ - /* we are not root */ - if (geteuid() != 0) - { - fputs("WARNING: Local messages will not be logged! If you want to log them, run rsyslog as root.\n",stderr); -#ifdef SYSLOG_INET - /* udp enabled and port number less than or equal to 1024 */ - if ( AcceptRemote && (atoi(LogPort) <= 1024) ) - fprintf(stderr, "WARNING: Will not listen on UDP port %s. Use port number higher than 1024 or run rsyslog as root!\n", LogPort); - - /* tcp enabled and port number less or equal to 1024 */ - if( bEnableTCP && (atoi(TCPLstnPort) <= 1024) ) - fprintf(stderr, "WARNING: Will not listen on TCP port %s. Use port number higher than 1024 or run rsyslog as root!\n", TCPLstnPort); - - /* Neither explicit high UDP port nor explicit high TCP port. - * It is useless to run anymore */ - if( !(AcceptRemote && (atoi(LogPort) > 1024)) && !( bEnableTCP && (atoi(TCPLstnPort) > 1024)) ) - { -#endif - fprintf(stderr, "ERROR: Nothing to log, no reason to run. Please run rsyslog as root.\n"); - exit(EXIT_FAILURE); -#ifdef SYSLOG_INET - } -#endif - } -#endif -} - - -/* load build-in modules - * very first version begun on 2007-07-23 by rgerhards - */ -static rsRetVal loadBuildInModules(void) -{ - DEFiRet; - - if((iRet = module.doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) { - RETiRet; - } -#ifdef SYSLOG_INET - if((iRet = module.doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) { - RETiRet; - } -#endif - if((iRet = module.doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) { - RETiRet; - } - if((iRet = module.doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) { - RETiRet; - } - - /* dirty, but this must be for the time being: the usrmsg module must always be - * loaded as last module. This is because it processes any time of action selector. - * If we load it before other modules, these others will never have a chance of - * working with the config file. We may change that implementation so that a user name - * must start with an alnum, that would definitely help (but would it break backwards - * compatibility?). * rgerhards, 2007-07-23 - * User names now must begin with: - * [a-zA-Z0-9_.] - */ - if((iRet = module.doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK) - RETiRet; - - /* ok, initialization of the command handler probably does not 100% belong right in - * this space here. However, with the current design, this is actually quite a good - * place to put it. We might decide to shuffle it around later, but for the time - * being, the code has found its home here. A not-just-sideeffect of this decision - * is that rsyslog will terminate if we can not register our built-in config commands. - * This, I think, is the right thing to do. -- rgerhards, 2007-07-31 - */ - CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, NULL, &pszWorkDir, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeretrycount", 0, eCmdHdlrInt, NULL, &glbliActionResumeRetryCount, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord, NULL, &pszMainMsgQFName, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt, NULL, &iMainMsgQueueSize, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQHighWtrMark, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQLowWtrMark, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt, NULL, &iMainMsgQDiscardMark, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity, NULL, &iMainMsgQDiscardSeverity, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &iMainMsgQPersistUpdCnt, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord, setMainMsgQueType, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt, NULL, &iMainMsgQueueNumWorkers, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoQShutdown, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &iMainMsgQtoActShutdown, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &iMainMsgQtoEnq, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworketimeoutrthreadshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoWrkShutdown, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &iMainMsgQDeqSlowdown, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &iMainMsgQWrkMinMsgs, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxFileSize, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxDiskSpace, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &bMainMsgQSaveOnShutdown, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinFromHr, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinToHr, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, NULL, &bReduceRepeatMsgs, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &bActExecWhenPrevSusp, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyonceeveryinterval", 0, eCmdHdlrInt, NULL, &iActExecOnceInterval, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt, setActionResumeInterval, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_TEMPLATE, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_OUTCHANNEL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_ALLOWEDSENDER, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, conf.doModLoad, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, conf.doIncludeLine, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode, setUmask, NULL, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintTemplateList, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintModuleList, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"debugprintcfsyslinehandlerlist", 0, eCmdHdlrBinary, - NULL, &bDebugPrintCfSysLineHandlerList, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord, NULL, &pModDir, NULL)); - CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); - - /* now add other modules handlers (we should work on that to be able to do it in ClassInit(), but so far - * that is not possible). -- rgerhards, 2008-01-28 - */ - CHKiRet(actionAddCfSysLineHdrl()); - -finalize_it: - RETiRet; -} - - -/* print version and compile-time setting information. - */ -static void printVersion(void) -{ - printf("rsyslogd %s, ", VERSION); - printf("compiled with:\n"); -#ifdef FEATURE_REGEXP - printf("\tFEATURE_REGEXP:\t\t\t\tYes\n"); -#else - printf("\tFEATURE_REGEXP:\t\t\t\tNo\n"); -#endif -#ifndef NOLARGEFILE - printf("\tFEATURE_LARGEFILE:\t\t\tYes\n"); -#else - printf("\tFEATURE_LARGEFILE:\t\t\tNo\n"); -#endif -#ifdef USE_NETZIP - printf("\tFEATURE_NETZIP (message compression):\tYes\n"); -#else - printf("\tFEATURE_NETZIP (message compression):\tNo\n"); -#endif -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - printf("\tGSSAPI Kerberos 5 support:\t\tYes\n"); -#else - printf("\tGSSAPI Kerberos 5 support:\t\tNo\n"); -#endif -#ifndef NDEBUG - printf("\tFEATURE_DEBUG (debug build, slow code):\tYes\n"); -#else - printf("\tFEATURE_DEBUG (debug build, slow code):\tNo\n"); -#endif -#ifdef RTINST - printf("\tRuntime Instrumentation (slow code):\tYes\n"); -#else - printf("\tRuntime Instrumentation (slow code):\tNo\n"); -#endif - printf("\nSee http://www.rsyslog.com for more information.\n"); -} - - -/* This function is called after initial initalization. It is used to - * move code out of the too-long main() function. - * rgerhards, 2007-10-17 - */ -static void mainThread() -{ - BEGINfunc - uchar *pTmp; - - /* Note: signals MUST be processed by the thread this code is running in. The reason - * is that we need to interrupt the select() system call. -- rgerhards, 2007-10-17 - */ - - /* initialize the build-in templates */ - pTmp = template_SyslogProtocol23Format; - tplAddLine("RSYSLOG_SyslogProtocol23Format", &pTmp); - pTmp = template_FileFormat; /* new format for files with high-precision stamp */ - tplAddLine("RSYSLOG_FileFormat", &pTmp); - pTmp = template_TraditionalFileFormat; - tplAddLine("RSYSLOG_TraditionalFileFormat", &pTmp); - pTmp = template_WallFmt; - tplAddLine(" WallFmt", &pTmp); - pTmp = template_ForwardFormat; - tplAddLine("RSYSLOG_ForwardFormat", &pTmp); - pTmp = template_TraditionalForwardFormat; - tplAddLine("RSYSLOG_TraditionalForwardFormat", &pTmp); - pTmp = template_StdUsrMsgFmt; - tplAddLine(" StdUsrMsgFmt", &pTmp); - pTmp = template_StdDBFmt; - tplAddLine(" StdDBFmt", &pTmp); - pTmp = template_StdPgSQLFmt; - tplLastStaticInit(tplAddLine(" StdPgSQLFmt", &pTmp)); - - init(); - if(Debug) { - dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); - debugging_on = 1; - } - /* Send a signal to the parent so it can terminate. - */ - if (myPid != ppid) - kill (ppid, SIGTERM); - - /* END OF INTIALIZATION - * ... but keep in mind that we might do a restart and thus init() might - * be called again. If that happens, we must shut down the worker thread, - * do the init() and then restart things. - * rgerhards, 2005-10-24 - */ - dbgprintf("initialization completed, transitioning to regular run mode\n"); - - mainloop(); - ENDfunc -} - - -/* Method to initialize all global classes and use the objects that we need. - * rgerhards, 2008-01-04 - * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime - */ -static rsRetVal -InitGlobalClasses(void) -{ - DEFiRet; - char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */ - - /* Intialize the runtime system */ - pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */ - CHKiRet(rsrtInit(&pErrObj, &obj)); - - /* Now tell the system which classes we need ourselfs */ - pErrObj = "errmsg"; - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - pErrObj = "module"; - CHKiRet(objUse(module, CORE_COMPONENT)); - pErrObj = "var"; - CHKiRet(objUse(var, CORE_COMPONENT)); - pErrObj = "datetime"; - CHKiRet(objUse(datetime, CORE_COMPONENT)); - pErrObj = "vm"; - CHKiRet(objUse(vm, CORE_COMPONENT)); - pErrObj = "expr"; - CHKiRet(objUse(expr, CORE_COMPONENT)); - pErrObj = "conf"; - CHKiRet(objUse(conf, CORE_COMPONENT)); - - /* intialize some dummy classes that are not part of the runtime */ - pErrObj = "action"; - CHKiRet(actionClassInit()); - pErrObj = "template"; - CHKiRet(templateInit()); - - /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ - pErrObj = "net"; - CHKiRet(objUse(net, LM_NET_FILENAME)); - -finalize_it: - if(iRet != RS_RET_OK) { - /* we know we are inside the init sequence, so we can safely emit - * messages to stderr. -- rgerhards, 2008-04-02 - */ - fprintf(stderr, "Error during class init for object '%s' - failing...\n", pErrObj); - } - - RETiRet; -} - - -/* Method to exit all global classes. We do not do any error checking here, - * because that wouldn't help us at all. So better try to deinit blindly - * as much as succeeds (which usually means everything will). We just must - * be careful to do the de-init in the opposite order of the init, because - * of the dependencies. However, its not as important this time, because - * we have reference counting. - * rgerhards, 2008-03-10 - */ -static rsRetVal -GlobalClassExit(void) -{ - DEFiRet; - - /* first, release everything we used ourself */ - objRelease(net, LM_NET_FILENAME);/* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ - objRelease(conf, CORE_COMPONENT); - objRelease(expr, CORE_COMPONENT); - objRelease(vm, CORE_COMPONENT); - objRelease(var, CORE_COMPONENT); - objRelease(datetime, CORE_COMPONENT); - - /* TODO: implement the rest of the deinit */ -#if 0 - CHKiRet(datetimeClassInit(NULL)); - CHKiRet(msgClassInit(NULL)); - CHKiRet(strmClassInit(NULL)); - CHKiRet(wtiClassInit(NULL)); - CHKiRet(wtpClassInit(NULL)); - CHKiRet(queueClassInit(NULL)); - CHKiRet(vmstkClassInit(NULL)); - CHKiRet(sysvarClassInit(NULL)); - CHKiRet(vmClassInit(NULL)); - CHKiRet(vmopClassInit(NULL)); - CHKiRet(vmprgClassInit(NULL)); - CHKiRet(ctok_tokenClassInit(NULL)); - CHKiRet(ctokClassInit(NULL)); - CHKiRet(exprClassInit(NULL)); - - /* dummy "classes" */ - CHKiRet(actionClassInit()); - CHKiRet(templateInit()); -#endif - /* dummy "classes */ - strExit(); - -#if 0 - CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ - /* the following classes were intialized by objClassInit() */ - CHKiRet(objUse(errmsg, CORE_COMPONENT)); - CHKiRet(objUse(module, CORE_COMPONENT)); -#endif - rsrtExit(&obj); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ - - RETiRet; -} - - -/* some support for command line option parsing. Any non-trivial options must be - * buffered until the complete command line has been parsed. This is necessary to - * prevent dependencies between the options. That, in turn, means we need to have - * something that is capable of buffering options and there values. The follwing - * functions handle that. - * rgerhards, 2008-04-04 - */ -typedef struct bufOpt { - struct bufOpt *pNext; - char optchar; - char *arg; -} bufOpt_t; -static bufOpt_t *bufOptRoot = NULL; -static bufOpt_t *bufOptLast = NULL; - -/* add option buffer */ -static rsRetVal -bufOptAdd(char opt, char *arg) -{ - DEFiRet; - bufOpt_t *pBuf; - - if((pBuf = malloc(sizeof(bufOpt_t))) == NULL) - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - - pBuf->optchar = opt; - pBuf->arg = arg; - pBuf->pNext = NULL; - - if(bufOptLast == NULL) { - bufOptRoot = pBuf; /* then there is also no root! */ - } else { - bufOptLast->pNext = pBuf; - } - bufOptLast = pBuf; - -finalize_it: - RETiRet; -} - - - -/* remove option buffer from top of list, return values and destruct buffer itself. - * returns RS_RET_END_OF_LINKEDLIST when no more options are present. - * (we use int *opt instead of char *opt to keep consistent with getopt()) - */ -static rsRetVal -bufOptRemove(int *opt, char **arg) -{ - DEFiRet; - bufOpt_t *pBuf; - - if(bufOptRoot == NULL) - ABORT_FINALIZE(RS_RET_END_OF_LINKEDLIST); - pBuf = bufOptRoot; - - *opt = pBuf->optchar; - *arg = pBuf->arg; - - bufOptRoot = pBuf->pNext; - free(pBuf); - -finalize_it: - RETiRet; -} - - -/* This is the main entry point into rsyslogd. Over time, we should try to - * modularize it a bit more... - */ -int realMain(int argc, char **argv) -{ - DEFiRet; - - register int i; - register char *p; - int num_fds; - int ch; - struct hostent *hent; - extern int optind; - extern char *optarg; - struct sigaction sigAct; - int bIsFirstOption = 1; - int bEOptionWasGiven = 0; - int bImUxSockLoaded = 0; /* already generated a $ModLoad imuxsock? */ - char *arg; /* for command line option processing */ - uchar legacyConfLine[80]; - - /* first, parse the command line options. We do not carry out any actual work, just - * see what we should do. This relieves us from certain anomalies and we can process - * the parameters down below in the correct order. For example, we must know the - * value of -M before we can do the init, but at the same time we need to have - * the base classes init before we can process most of the options. Now, with the - * split of functionality, this is no longer a problem. Thanks to varmofekoj for - * suggesting this algo. - * Note: where we just need to set some flags and can do so without knowledge - * of other options, we do this during the inital option processing. With later - * versions (if a dependency on -c option is introduced), we must move that code - * to other places, but I think it is quite appropriate and saves code to do this - * only when actually neeeded. - * rgerhards, 2008-04-04 - */ - while ((ch = getopt(argc, argv, "46aAc:def:g:hi:l:m:M:nopqQr::s:t:u:vwx")) != EOF) { - switch((char)ch) { - case '4': - case '6': - case 'A': - case 'a': - case 'f': /* configuration file */ - case 'h': - case 'i': /* pid file name */ - case 'l': - case 'm': /* mark interval */ - case 'n': /* don't fork */ - case 'o': - case 'p': - case 'q': /* add hostname if DNS resolving has failed */ - case 'Q': /* dont resolve hostnames in ACL to IPs */ - case 's': - case 'u': /* misc user settings */ - case 'w': /* disable disallowed host warnigs */ - case 'x': /* disable dns for remote messages */ - CHKiRet(bufOptAdd(ch, optarg)); - break; - case 'c': /* compatibility mode */ - if(!bIsFirstOption) { - fprintf(stderr, "-c option MUST be specified as the first option - aborting...\n"); - usage(); - exit(1); - } - iCompatibilityMode = atoi(optarg); - break; - case 'd': /* debug - must be handled now, so that debug is active during init! */ - Debug = 1; - break; - case 'e': /* log every message (no repeat message supression) */ - fprintf(stderr, "note: -e option is no longer supported, every message is now logged by default\n"); - bEOptionWasGiven = 1; - break; - case 'g': /* enable tcp gssapi logging */ -#if defined(SYSLOG_INET) && defined(USE_GSSAPI) - CHKiRet(bufOptAdd('g', optarg)); -#else - fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support"); -#endif - break; - case 'M': /* default module load path -- this MUST be carried out immediately! */ - glblModPath = (uchar*) optarg; - break; - case 'r': /* accept remote messages */ -#ifdef SYSLOG_INET - CHKiRet(bufOptAdd(ch, optarg)); -#else - fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support\n"); -#endif - break; - case 't': /* enable tcp logging */ -#ifdef SYSLOG_INET - CHKiRet(bufOptAdd(ch, optarg)); -#else - fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support\n"); -#endif - break; - case 'v': /* MUST be carried out immediately! */ - printVersion(); - exit(0); /* exit for -v option - so this is a "good one" */ - case '?': - default: - usage(); - } - bIsFirstOption = 0; /* we already saw an option character */ - } - - if ((argc -= optind)) - usage(); - - dbgprintf("rsyslogd %s startup, compatibility mode %d, module path '%s'\n", - VERSION, iCompatibilityMode, glblModPath == NULL ? "" : (char*)glblModPath); - - /* we are done with the initial option parsing and processing. Now we init the system. */ - - ppid = getpid(); - - if(chdir ("/") != 0) - fprintf(stderr, "Can not do 'cd /' - still trying to run\n"); - - CHKiRet_Hdlr(InitGlobalClasses()) { - fprintf(stderr, "rsyslogd initializiation failed - global classes could not be initialized.\n" - "Did you do a \"make install\"?\n" - "Suggested action: run rsyslogd with -d -n options to see what exactly " - "fails.\n"); - FINALIZE; - } - - /* doing some core initializations */ - - /* get our host and domain names - we need to do this early as we may emit - * error log messages, which need the correct hostname. -- rgerhards, 2008-04-04 - */ - net.getLocalHostname(&LocalHostName); - if((p = strchr((char*)LocalHostName, '.'))) { - *p++ = '\0'; - LocalDomain = p; - } else { - LocalDomain = ""; - - /* It's not clearly defined whether gethostname() - * should return the simple hostname or the fqdn. A - * good piece of software should be aware of both and - * we want to distribute good software. Joey - * - * Good software also always checks its return values... - * If syslogd starts up before DNS is up & /etc/hosts - * doesn't have LocalHostName listed, gethostbyname will - * return NULL. - */ - /* TODO: gethostbyname() is not thread-safe, but replacing it is - * not urgent as we do not run on multiple threads here. rgerhards, 2007-09-25 - */ - hent = gethostbyname((char*)LocalHostName); - if(hent) { - free(LocalHostName); - CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_name)); - - if((p = strchr((char*)LocalHostName, '.'))) - { - *p++ = '\0'; - LocalDomain = p; - } - } - } - - /* Convert to lower case to recognize the correct domain laterly */ - for (p = (char *)LocalDomain ; *p ; p++) - *p = (char)tolower((int)*p); - - /* initialize the objects */ - if((iRet = modInitIminternal()) != RS_RET_OK) { - fprintf(stderr, "fatal error: could not initialize errbuf object (error code %d).\n", - iRet); - exit(1); /* "good" exit, leaving at init for fatal error */ - } - - if((iRet = loadBuildInModules()) != RS_RET_OK) { - fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n", - iRet); - exit(1); /* "good" exit, leaving at init for fatal error */ - } - - /* END core initializations - we now come back to carrying out command line options*/ - - while((iRet = bufOptRemove(&ch, &arg)) == RS_RET_OK) { - dbgprintf("deque option %c, optarg '%s'\n", ch, arg); - switch((char)ch) { - case '4': - family = PF_INET; - break; - case '6': - family = PF_INET6; - break; - case 'A': - send_to_all++; - break; - case 'a': - if(iCompatibilityMode < 3) { - if(!bImUxSockLoaded) { - legacyOptsEnq((uchar *) "ModLoad imuxsock"); - bImUxSockLoaded = 1; - } - snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "addunixlistensocket %s", arg); - legacyOptsEnq(legacyConfLine); - } else { - fprintf(stderr, "error -a is no longer supported, use module imuxsock instead"); - } - break; - case 'f': /* configuration file */ - ConfFile = (uchar*) arg; - break; - case 'g': /* enable tcp gssapi logging */ - if(iCompatibilityMode < 3) { - legacyOptsParseTCP(ch, arg); - } else - fprintf(stderr, "-g option only supported in compatibility modes 0 to 2 - ignored\n"); - break; - case 'h': - if(iCompatibilityMode < 3) { - errmsg.LogError(NO_ERRCODE, "WARNING: -h option is no longer supported - ignored"); - } else { - usage(); /* for v3 and above, it simply is an error */ - } - break; - case 'i': /* pid file name */ - PidFile = arg; - break; - case 'l': - if (LocalHosts) { - fprintf (stderr, "rsyslogd: Only one -l argument allowed, the first one is taken.\n"); - } else { - LocalHosts = crunch_list(arg); - } - break; - case 'm': /* mark interval */ - if(iCompatibilityMode < 3) { - MarkInterval = atoi(arg) * 60; - } else - fprintf(stderr, - "-m option only supported in compatibility modes 0 to 2 - ignored\n"); - break; - case 'n': /* don't fork */ - NoFork = 1; - break; - case 'o': - if(iCompatibilityMode < 3) { - if(!bImUxSockLoaded) { - legacyOptsEnq((uchar *) "ModLoad imuxsock"); - bImUxSockLoaded = 1; - } - legacyOptsEnq((uchar *) "OmitLocalLogging"); - } else { - fprintf(stderr, "error -o is no longer supported, use module imuxsock instead"); - } - break; - case 'p': - if(iCompatibilityMode < 3) { - if(!bImUxSockLoaded) { - legacyOptsEnq((uchar *) "ModLoad imuxsock"); - bImUxSockLoaded = 1; - } - snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "SystemLogSocketName %s", arg); - legacyOptsEnq(legacyConfLine); - } else { - fprintf(stderr, "error -p is no longer supported, use module imuxsock instead"); - } - case 'q': /* add hostname if DNS resolving has failed */ - *net.pACLAddHostnameOnFail = 1; - break; - case 'Q': /* dont resolve hostnames in ACL to IPs */ - *net.pACLDontResolve = 1; - break; - case 'r': /* accept remote messages */ - if(iCompatibilityMode < 3) { - legacyOptsEnq((uchar *) "ModLoad imudp"); - snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "UDPServerRun %s", arg); - legacyOptsEnq(legacyConfLine); - } else - fprintf(stderr, "-r option only supported in compatibility modes 0 to 2 - ignored\n"); - break; - case 's': - if (StripDomains) { - fprintf (stderr, "rsyslogd: Only one -s argument allowed, the first one is taken.\n"); - } else { - StripDomains = crunch_list(arg); - } - break; - case 't': /* enable tcp logging */ - if(iCompatibilityMode < 3) { - legacyOptsParseTCP(ch, arg); - } else - fprintf(stderr, "-t option only supported in compatibility modes 0 to 2 - ignored\n"); - break; - case 'u': /* misc user settings */ - if(atoi(arg) == 1) - bParseHOSTNAMEandTAG = 0; - break; - case 'w': /* disable disallowed host warnigs */ - option_DisallowWarning = 0; - break; - case 'x': /* disable dns for remote messages */ - DisableDNS = 1; - break; - case '?': - default: - usage(); - } - } - - if(iRet != RS_RET_END_OF_LINKEDLIST) - FINALIZE; - - /* process compatibility mode settings */ - if(iCompatibilityMode < 3) { - errmsg.LogError(NO_ERRCODE, "WARNING: rsyslogd is running in compatibility mode. Automatically " - "generated config directives may interfer with your rsyslog.conf settings. " - "We suggest upgrading your config and adding -c3 as the first " - "rsyslogd option."); - if(MarkInterval > 0) { - legacyOptsEnq((uchar *) "ModLoad immark"); - snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "MarkMessagePeriod %d", MarkInterval); - legacyOptsEnq(legacyConfLine); - } - if(!bImUxSockLoaded) { - legacyOptsEnq((uchar *) "ModLoad imuxsock"); - } - } - - if(bEOptionWasGiven && iCompatibilityMode < 3) { - errmsg.LogError(NO_ERRCODE, "WARNING: \"message repeated n times\" feature MUST be turned on in " - "rsyslog.conf - CURRENTLY EVERY MESSAGE WILL BE LOGGED. Visit " - "http://www.rsyslog.com/rptdmsgreduction to learn " - "more and cast your vote if you want us to keep this feature."); - } - - checkPermissions(); - thrdInit(); - - if( !(Debug || NoFork) ) - { - dbgprintf("Checking pidfile.\n"); - if (!check_pid(PidFile)) - { - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - sigAct.sa_handler = doexit; - sigaction(SIGTERM, &sigAct, NULL); - - if (fork()) { - /* - * Parent process - */ - sleep(300); - /* - * Not reached unless something major went wrong. 5 - * minutes should be a fair amount of time to wait. - * Please note that this procedure is important since - * the father must not exit before syslogd isn't - * initialized or the klogd won't be able to flush its - * logs. -Joey - */ - exit(1); /* "good" exit - after forking, not diasabling anything */ - } - num_fds = getdtablesize(); - for (i= 0; i < num_fds; i++) - (void) close(i); - untty(); - } - else - { - fputs(" Already running.\n", stderr); - exit(1); /* "good" exit, done if syslogd is already running */ - } - } - else - debugging_on = 1; - - /* tuck my process id away */ - dbgprintf("Writing pidfile %s.\n", PidFile); - if (!check_pid(PidFile)) - { - if (!write_pid(PidFile)) - { - fputs("Can't write pid.\n", stderr); - exit(1); /* exit during startup - questionable */ - } - } - else - { - fputs("Pidfile (and pid) already exist.\n", stderr); - exit(1); /* exit during startup - questionable */ - } - myPid = getpid(); /* save our pid for further testing (also used for messages) */ - - memset(&sigAct, 0, sizeof (sigAct)); - sigemptyset(&sigAct.sa_mask); - - sigAct.sa_handler = sigsegvHdlr; - sigaction(SIGSEGV, &sigAct, NULL); - sigAct.sa_handler = sigsegvHdlr; - sigaction(SIGABRT, &sigAct, NULL); - sigAct.sa_handler = doDie; - sigaction(SIGTERM, &sigAct, NULL); - sigAct.sa_handler = Debug ? doDie : SIG_IGN; - sigaction(SIGINT, &sigAct, NULL); - sigaction(SIGQUIT, &sigAct, NULL); - sigAct.sa_handler = reapchild; - sigaction(SIGCHLD, &sigAct, NULL); - sigAct.sa_handler = Debug ? debug_switch : SIG_IGN; - sigaction(SIGUSR1, &sigAct, NULL); - sigAct.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &sigAct, NULL); - sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */ - - mainThread(); - - /* do any de-init's that need to be done AFTER this comment */ - - die(bFinished); - - thrdExit(); - -finalize_it: - if(iRet != RS_RET_OK) - fprintf(stderr, "rsyslogd run failed with error %d\n(see rsyslog.h " - "or http://www.rsyslog.com/errcode to learn what that number means)\n", iRet); - - ENDfunc - return 0; -} - - -/* This is the main entry point into rsyslogd. This must be a function in its own - * right in order to intialize the debug system in a portable way (otherwise we would - * need to have a statement before variable definitions. - * rgerhards, 20080-01-28 - */ -int main(int argc, char **argv) -{ - dbgClassInit(); - return realMain(argc, argv); -} - -/* vim:set ai: - */ diff --git a/syslogd.h b/syslogd.h deleted file mode 100644 index a26af519..00000000 --- a/syslogd.h +++ /dev/null @@ -1,165 +0,0 @@ -/* common header for syslogd - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of rsyslog. - * - * Rsyslog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Rsyslog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Rsyslog. If not, see . - * - * A copy of the GPL can be found in the file "COPYING" in this distribution. - */ -#ifndef SYSLOGD_H_INCLUDED -#define SYSLOGD_H_INCLUDED 1 - -#include "syslogd-types.h" -#include "objomsr.h" -#include "modules.h" -#include "template.h" -#include "action.h" -#include "linkedlist.h" -#include "expr.h" - -/* portability: not all platforms have these defines, so we - * define them here if they are missing. -- rgerhards, 2008-03-04 - */ -#ifndef LOG_MAKEPRI -# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) -#endif -#ifndef LOG_PRI -# define LOG_PRI(p) ((p) & LOG_PRIMASK) -#endif -#ifndef LOG_FAC -# define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) -#endif - - -#ifdef USE_NETZIP -/* config param: minimum message size to try compression. The smaller - * the message, the less likely is any compression gain. We check for - * gain before we submit the message. But to do so we still need to - * do the (costly) compress() call. The following setting sets a size - * for which no call to compress() is done at all. This may result in - * a few more bytes being transmited but better overall performance. - * Note: I have not yet checked the minimum UDP packet size. It might be - * that we do not save anything by compressing very small messages, because - * UDP might need to pad ;) - * rgerhards, 2006-11-30 - */ -#define MIN_SIZE_FOR_COMPRESS 60 -#endif - -#define MAXLINE 2048 /* maximum line length */ - -/* Flags to logmsg(). - */ -#define NOFLAG 0x000 /* no flag is set (to be used when a flag must be specified and none is required) */ -#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ -#define SYNC_FILE 0x002 /* do fsync on file after printing */ -#define ADDDATE 0x004 /* add a date to the message */ -#define MARK 0x008 /* this message is a mark */ - -/* This structure represents the files that will have log - * copies printed. - * RGerhards 2004-11-08: Each instance of the filed structure - * describes what I call an "output channel". This is important - * to mention as we now allow database connections to be - * present in the filed structure. If helps immensely, if we - * think of it as the abstraction of an output channel. - * rgerhards, 2005-10-26: The structure below provides ample - * opportunity for non-thread-safety. Each of the variable - * accesses must be carefully evaluated, many of them probably - * be guarded by mutexes. But beware of deadlocks... - * rgerhards, 2007-08-01: as you can see, the structure has shrunk pretty much. I will - * remove some of the comments some time. It's still the structure that controls much - * of the processing that goes on in syslogd, but it now has lots of helpers. - */ -struct filed { - struct filed *f_next; /* next in linked list */ - /* filter properties */ - enum { - FILTER_PRI = 0, /* traditional PRI based filer */ - FILTER_PROP = 1, /* extended filter, property based */ - FILTER_EXPR = 2 /* extended filter, expression based */ - } f_filter_type; - EHostnameCmpMode eHostnameCmpMode; - cstr_t *pCSHostnameComp; /* hostname to check */ - cstr_t *pCSProgNameComp; /* tag to check or NULL, if not to be checked */ - union { - u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ - struct { - cstr_t *pCSPropName; - enum { - FIOP_NOP = 0, /* do not use - No Operation */ - FIOP_CONTAINS = 1, /* contains string? */ - FIOP_ISEQUAL = 2, /* is (exactly) equal? */ - FIOP_STARTSWITH = 3, /* starts with a string? */ - FIOP_REGEX = 4 /* matches a regular expression? */ - } operation; - cstr_t *pCSCompValue; /* value to "compare" against */ - char isNegated; /* actually a boolean ;) */ - } prop; - expr_t *f_expr; /* expression object */ - } f_filterData; - - linkedList_t llActList; /* list of configured actions */ -}; - - -#define MSG_PARSE_HOSTNAME 1 -#define MSG_DONT_PARSE_HOSTNAME 0 -rsRetVal parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType); -#include "net.h" /* TODO: remove when you remoe isAllowedSender from here! */ -void untty(void); -rsRetVal selectorConstruct(selector_t **ppThis); -rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName); -rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl); -int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); -rsRetVal selectorDestruct(void *pVal); -rsRetVal selectorAddList(selector_t *f); -/* the following prototypes should go away once we have an input - * module interface -- rgerhards, 2007-12-12 - */ -rsRetVal logmsgInternal(int pri, char *msg, int flags); -void logmsg(msg_t *pMsg, int flags); -rsRetVal submitMsg(msg_t *pMsg); -extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */ -extern uchar *LocalHostName; -extern int family; -extern int NoHops; -extern int send_to_all; -extern int option_DisallowWarning; -extern int Debug; -extern char**LocalHosts; -extern int DisableDNS; -extern char **StripDomains; -extern char *LocalDomain; -extern int bDropMalPTRMsgs; -extern char ctty[]; -extern int MarkInterval; -extern int bReduceRepeatMsgs; -extern int bActExecWhenPrevSusp; -extern int iActExecOnceInterval; - -/* Intervals at which we flush out "message repeated" messages, - * in seconds after previous message is logged. After each flush, - * we move to the next interval until we reach the largest. - * TODO: move this to action object! - */ -extern int repeatinterval[2]; -#define MAXREPEAT ((int)((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)) -#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) -#define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ - (f)->f_repeatcount = MAXREPEAT; \ - } - -#endif /* #ifndef SYSLOGD_H_INCLUDED */ diff --git a/tcpclt.c b/tcpclt.c index 824e8294..7216caae 100644 --- a/tcpclt.c +++ b/tcpclt.c @@ -38,7 +38,7 @@ #if HAVE_FCNTL_H #include #endif -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "net.h" #include "tcpclt.h" diff --git a/tcps_sess.c b/tcps_sess.c index 001f32f0..b5c9c31f 100644 --- a/tcps_sess.c +++ b/tcps_sess.c @@ -44,7 +44,7 @@ #include #endif #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "module-template.h" #include "net.h" #include "tcpsrv.h" diff --git a/tcpsrv.c b/tcpsrv.c index daa373c1..7cf94e9d 100644 --- a/tcpsrv.c +++ b/tcpsrv.c @@ -54,7 +54,7 @@ #include #endif #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "cfsysline.h" #include "module-template.h" #include "net.h" diff --git a/template.c b/template.c index 844c5aec..e5021f35 100644 --- a/template.c +++ b/template.c @@ -33,7 +33,7 @@ #include "syslogd-types.h" #include "template.h" #include "msg.h" -#include "syslogd.h" +#include "dirty.h" #include "obj.h" #include "errmsg.h" diff --git a/threads.c b/threads.c index e32ff0d9..f4f604fc 100644 --- a/threads.c +++ b/threads.c @@ -33,7 +33,7 @@ #include #include "rsyslog.h" -#include "syslogd.h" +#include "dirty.h" #include "linkedlist.h" #include "threads.h" diff --git a/tools/iminternal.c b/tools/iminternal.c new file mode 100644 index 00000000..60460a99 --- /dev/null +++ b/tools/iminternal.c @@ -0,0 +1,190 @@ +/* iminternal.c + * This file set implements the internal messages input module for rsyslog. + * Note: we currently do not have an input module spec, but + * we will have one in the future. This module needs then to be + * adapted. + * + * File begun on 2007-08-03 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" + +#include +#include +#include +#include + +#include "syslogd.h" +#include "linkedlist.h" +#include "iminternal.h" + +static linkedList_t llMsgs; + + +/* destructs an iminternal object + */ +static rsRetVal iminternalDestruct(iminternal_t *pThis) +{ + DEFiRet; + + assert(pThis != NULL); + + if(pThis->pMsg != NULL) + msgDestruct(&pThis->pMsg); + + free(pThis); + + RETiRet; +} + + +/* Construct an iminternal object + */ +static rsRetVal iminternalConstruct(iminternal_t **ppThis) +{ + DEFiRet; + iminternal_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (iminternal_t*) calloc(1, sizeof(iminternal_t))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis != NULL) + iminternalDestruct(pThis); + } + + *ppThis = pThis; + + RETiRet; +} + + +/* add a message to the linked list + * Note: the pMsg reference counter is not incremented. Consequently, + * the caller must NOT decrement it. The caller actually hands over + * full ownership of the pMsg object. + * The interface of this function is modelled after syslogd/logmsg(), + * for which it is an "replacement". + */ +rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags) +{ + DEFiRet; + iminternal_t *pThis; + + assert(pMsg != NULL); + + CHKiRet(iminternalConstruct(&pThis)); + + pThis->pri = pri; + pThis->pMsg = pMsg; + pThis->flags = flags; + + CHKiRet(llAppend(&llMsgs, NULL, (void*) pThis)); + +finalize_it: + if(iRet != RS_RET_OK) { + dbgprintf("iminternalAddMsg() error %d - can not otherwise report this error, message lost\n", iRet); + if(pThis != NULL) + iminternalDestruct(pThis); + } + + RETiRet; +} + + +/* pull the first error message from the linked list, remove it + * from the list and return it to the caller. The caller is + * responsible for freeing the message! + */ +rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags) +{ + DEFiRet; + iminternal_t *pThis; + linkedListCookie_t llCookie = NULL; + + assert(pPri != NULL); + assert(ppMsg != NULL); + assert(pFlags != NULL); + + CHKiRet(llGetNextElt(&llMsgs, &llCookie, (void*)&pThis)); + *pPri = pThis->pri; + *pFlags = pThis->flags; + *ppMsg = pThis->pMsg; + pThis->pMsg = NULL; /* we do no longer own it - important for destructor */ + + if(llDestroyRootElt(&llMsgs) != RS_RET_OK) { + dbgprintf("Root element of iminternal linked list could not be destroyed - there is " + "nothing we can do against it, we ignore it for now. Things may go wild " + "from here on. This is most probably a program logic error.\n"); + } + +finalize_it: + RETiRet; +} + +/* tell the caller if we have any messages ready for processing. + * 0 means we have none, everything else means there is at least + * one message ready. + */ +rsRetVal iminternalHaveMsgReady(int* pbHaveOne) +{ + assert(pbHaveOne != NULL); + + return llGetNumElts(&llMsgs, pbHaveOne); +} + + +/* initialize the iminternal subsystem + * must be called once at the start of the program + */ +rsRetVal modInitIminternal(void) +{ + DEFiRet; + + iRet = llInit(&llMsgs, iminternalDestruct, NULL, NULL); + + RETiRet; +} + + +/* de-initialize the iminternal subsystem + * must be called once at the end of the program + * Note: the error list must have been pulled first. We do + * NOT care if there are any errors left - we simply destroy + * them. + */ +rsRetVal modExitIminternal(void) +{ + DEFiRet; + + iRet = llDestroy(&llMsgs); + + RETiRet; +} + +/* + * vi:set ai: + */ diff --git a/tools/iminternal.h b/tools/iminternal.h new file mode 100644 index 00000000..8dc0f171 --- /dev/null +++ b/tools/iminternal.h @@ -0,0 +1,49 @@ +/* Definition of the internal messages input module. + * + * Note: we currently do not have an input module spec, but + * we will have one in the future. This module needs then to be + * adapted. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#ifndef IMINTERNAL_H_INCLUDED +#define IMINTERNAL_H_INCLUDED +#include "template.h" + +/* this is a single entry for a parse routine. It describes exactly + * one entry point/handler. + * The short name is cslch (Configfile SysLine CommandHandler) + */ +struct iminternal_s { /* config file sysline parse entry */ + int pri; + msg_t *pMsg; /* the message (in all its glory) */ + int flags; +}; +typedef struct iminternal_s iminternal_t; + +/* prototypes */ +rsRetVal modInitIminternal(void); +rsRetVal modExitIminternal(void); +rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags); +rsRetVal iminternalHaveMsgReady(int* pbHaveOne); +rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags); + +#endif /* #ifndef IMINTERNAL_H_INCLUDED */ diff --git a/tools/omdiscard.c b/tools/omdiscard.c new file mode 100644 index 00000000..f13144e8 --- /dev/null +++ b/tools/omdiscard.c @@ -0,0 +1,121 @@ +/* omdiscard.c + * This is the implementation of the built-in discard output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-24 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include "syslogd.h" +#include "syslogd-types.h" +#include "omdiscard.h" +#include "module-template.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { +} instanceData; + +/* we do not need a createInstance()! +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance +*/ + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* do nothing */ +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = RS_RET_DISCARDMSG; +ENDdoAction + + +BEGINfreeInstance +CODESTARTfreeInstance + /* we do not have instance data, so we do not need to + * do anything here. -- rgerhards, 2007-07-25 + */ +ENDfreeInstance + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(0) + pData = NULL; /* this action does not have any instance data */ + p = *pp; + + if(*p == '~') { + /* TODO: check the rest of the selector line - error reporting */ + dbgprintf("discard\n"); + } else { + iRet = RS_RET_CONFLINE_UNPROCESSED; + } +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(Discard) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr +ENDmodInit +/* + * vi:set ai: + */ diff --git a/tools/omdiscard.h b/tools/omdiscard.h new file mode 100644 index 00000000..116308a4 --- /dev/null +++ b/tools/omdiscard.h @@ -0,0 +1,34 @@ +/* omdiscard.h + * These are the definitions for the built-in discard output module. + * + * File begun on 2007-07-24 by RGerhards + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMDISCARD_H_INCLUDED +#define OMDISCARD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMDISCARD_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/omfile.c b/tools/omfile.c new file mode 100644 index 00000000..6bdd17eb --- /dev/null +++ b/tools/omfile.c @@ -0,0 +1,847 @@ +/* omfile.c + * This is the implementation of the build-in file output module. + * + * Handles: eTypeCONSOLE, eTypeTTY, eTypeFILE, eTypePIPE + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "outchannel.h" +#include "omfile.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* The following structure is a dynafile name cache entry. + */ +struct s_dynaFileCacheEntry { + uchar *pName; /* name currently open, if dynamic name */ + short fd; /* name associated with file name in cache */ + time_t lastUsed; /* for LRU - last access */ +}; +typedef struct s_dynaFileCacheEntry dynaFileCacheEntry; + + +/* globals for default values */ +static int iDynaFileCacheSize = 10; /* max cache for dynamic files */ +static int fCreateMode = 0644; /* mode to use when creating files */ +static int fDirCreateMode = 0644; /* mode to use when creating files */ +static int bFailOnChown; /* fail if chown fails? */ +static uid_t fileUID; /* UID to be used for newly created files */ +static uid_t fileGID; /* GID to be used for newly created files */ +static uid_t dirUID; /* UID to be used for newly created directories */ +static uid_t dirGID; /* GID to be used for newly created directories */ +static int bCreateDirs; /* auto-create directories for dynaFiles: 0 - no, 1 - yes */ +static int bEnableSync = 0;/* enable syncing of files (no dash in front of pathname in conf): 0 - no, 1 - yes */ +static uchar *pszTplName = NULL; /* name of the default template to use */ +/* end globals for default values */ + + +typedef struct _instanceData { + uchar f_fname[MAXFNAME];/* file or template name (display only) */ + short fd; /* file descriptor for (current) file */ + enum { + eTypeFILE, + eTypeTTY, + eTypeCONSOLE, + eTypePIPE + } fileType; + char bDynamicName; /* 0 - static name, 1 - dynamic name (with properties) */ + int fCreateMode; /* file creation mode for open() */ + int fDirCreateMode; /* creation mode for mkdir() */ + int bCreateDirs; /* auto-create directories? */ + int bSyncFile; /* should the file by sync()'ed? 1- yes, 0- no */ + uid_t fileUID; /* IDs for creation */ + uid_t dirUID; + gid_t fileGID; + gid_t dirGID; + int bFailOnChown; /* fail creation if chown fails? */ + int iCurrElt; /* currently active cache element (-1 = none) */ + int iCurrCacheSize; /* currently cache size (1-based) */ + int iDynaFileCacheSize; /* size of file handle cache */ + /* The cache is implemented as an array. An empty element is indicated + * by a NULL pointer. Memory is allocated as needed. The following + * pointer points to the overall structure. + */ + dynaFileCacheEntry **dynCache; + off_t f_sizeLimit; /* file size limit, 0 = no limit */ + char *f_sizeLimitCmd; /* command to carry out when size limit is reached */ +} instanceData; + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + if(pData->bDynamicName) { + printf("[dynamic]\n\ttemplate='%s'" + "\tfile cache size=%d\n" + "\tcreate directories: %s\n" + "\tfile owner %d, group %d\n" + "\tdirectory owner %d, group %d\n" + "\tfail if owner/group can not be set: %s\n", + pData->f_fname, + pData->iDynaFileCacheSize, + pData->bCreateDirs ? "yes" : "no", + pData->fileUID, pData->fileGID, + pData->dirUID, pData->dirGID, + pData->bFailOnChown ? "yes" : "no" + ); + } else { /* regular file */ + printf("%s", pData->f_fname); + if (pData->fd == -1) + printf(" (unused)"); + } +ENDdbgPrintInstInfo + + +/* set the dynaFile cache size. Does some limit checking. + * rgerhards, 2007-07-31 + */ +rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal) +{ + DEFiRet; + uchar errMsg[128]; /* for dynamic error messages */ + + if(iNewVal < 1) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + iRet = RS_RET_VAL_OUT_OF_RANGE; + iNewVal = 1; + } else if(iNewVal > 10000) { + snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), + "DynaFileCacheSize maximum is 10,000 (%d given), changed to 10,000.", iNewVal); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + iRet = RS_RET_VAL_OUT_OF_RANGE; + iNewVal = 10000; + } + + iDynaFileCacheSize = iNewVal; + dbgprintf("DynaFileCacheSize changed to %d.\n", iNewVal); + + RETiRet; +} + + +/* Helper to cfline(). Parses a output channel name up until the first + * comma and then looks for the template specifier. Tries + * to find that template. Maps the output channel to the + * proper filed structure settings. Everything is stored in the + * filed struct. Over time, the dependency on filed might be + * removed. + * rgerhards 2005-06-21 + */ +static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts) +{ + DEFiRet; + size_t i; + struct outchannel *pOch; + char szBuf[128]; /* should be more than sufficient */ + + /* this must always be a file, because we can not set a size limit + * on a pipe... + * rgerhards 2005-06-21: later, this will be a separate type, but let's + * emulate things for the time being. When everything runs, we can + * extend it... + */ + pData->fileType = eTypeFILE; + + ++p; /* skip '$' */ + i = 0; + /* get outchannel name */ + while(*p && *p != ';' && *p != ' ' && + i < sizeof(szBuf) / sizeof(char)) { + szBuf[i++] = *p++; + } + szBuf[i] = '\0'; + + /* got the name, now look up the channel... */ + pOch = ochFind(szBuf, i); + + if(pOch == NULL) { + char errMsg[128]; + errno = 0; + snprintf(errMsg, sizeof(errMsg)/sizeof(char), + "outchannel '%s' not found - ignoring action line", + szBuf); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + + /* check if there is a file name in the outchannel... */ + if(pOch->pszFileTemplate == NULL) { + char errMsg[128]; + errno = 0; + snprintf(errMsg, sizeof(errMsg)/sizeof(char), + "outchannel '%s' has no file name template - ignoring action line", + szBuf); + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_ERR); + } + + /* OK, we finally got a correct template. So let's use it... */ + strncpy((char*) pData->f_fname, (char*) pOch->pszFileTemplate, MAXFNAME); + pData->f_sizeLimit = pOch->uSizeLimit; + /* WARNING: It is dangerous "just" to pass the pointer. As we + * never rebuild the output channel description, this is acceptable here. + */ + pData->f_sizeLimitCmd = (char*) pOch->cmdOnSizeLimit; + +RUNLOG_VAR("%p", pszTplName); + iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2005-06-21: Try to resolve a size limit + * situation. This first runs the command, and then + * checks if we are still above the treshold. + * returns 0 if ok, 1 otherwise + * TODO: consider moving the initial check in here, too + */ +int resolveFileSizeLimit(instanceData *pData) +{ + uchar *pParams; + uchar *pCmd; + uchar *p; + off_t actualFileSize; + ASSERT(pData != NULL); + + if(pData->f_sizeLimitCmd == NULL) + return 1; /* nothing we can do in this case... */ + + /* the execProg() below is probably not great, but at least is is + * fairly secure now. Once we change the way file size limits are + * handled, we should also revisit how this command is run (and + * with which parameters). rgerhards, 2007-07-20 + */ + /* we first check if we have command line parameters. We assume this, + * when we have a space in the program name. If we find it, everything after + * the space is treated as a single argument. + */ + if((pCmd = (uchar*)strdup((char*)pData->f_sizeLimitCmd)) == NULL) { + /* there is not much we can do - we make syslogd close the file in this case */ + glblHadMemShortage = 1; + return 1; + } + + for(p = pCmd ; *p && *p != ' ' ; ++p) { + /* JUST SKIP */ + } + + if(*p == ' ') { + *p = '\0'; /* pretend string-end */ + pParams = p+1; + } else + pParams = NULL; + + execProg(pCmd, 1, pParams); + + pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + pData->fCreateMode); + + actualFileSize = lseek(pData->fd, 0, SEEK_END); + if(actualFileSize >= pData->f_sizeLimit) { + /* OK, it didn't work out... */ + return 1; + } + + return 0; +} + + +/* This function deletes an entry from the dynamic file name + * cache. A pointer to the cache must be passed in as well + * as the index of the to-be-deleted entry. This index may + * point to an unallocated entry, in whcih case the + * function immediately returns. Parameter bFreeEntry is 1 + * if the entry should be d_free()ed and 0 if not. + */ +static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) +{ + ASSERT(pCache != NULL); + + BEGINfunc; + + if(pCache[iEntry] == NULL) + FINALIZE; + + dbgprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry, + pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName); + /* if the name is NULL, this is an improperly initilized entry which + * needs to be discarded. In this case, neither the file is to be closed + * not the name to be freed. + */ + if(pCache[iEntry]->pName != NULL) { + close(pCache[iEntry]->fd); + d_free(pCache[iEntry]->pName); + pCache[iEntry]->pName = NULL; + } + + if(bFreeEntry) { + d_free(pCache[iEntry]); + pCache[iEntry] = NULL; + } + +finalize_it: + ENDfunc; +} + + +/* This function frees the dynamic file name cache. + */ +static void dynaFileFreeCache(instanceData *pData) +{ + register int i; + ASSERT(pData != NULL); + + BEGINfunc; + for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { + dynaFileDelCacheEntry(pData->dynCache, i, 1); + } + + if(pData->dynCache != NULL) + d_free(pData->dynCache); + ENDfunc; +} + + +/* This is a shared code for both static and dynamic files. + */ +static void prepareFile(instanceData *pData, uchar *newFileName) +{ + if(access((char*)newFileName, F_OK) == 0) { + /* file already exists */ + pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + pData->fCreateMode); + } else { + pData->fd = -1; + /* file does not exist, create it (and eventually parent directories */ + if(pData->bCreateDirs) { + /* we fist need to create parent dirs if they are missing + * We do not report any errors here ourselfs but let the code + * fall through to error handler below. + */ + if(makeFileParentDirs(newFileName, strlen((char*)newFileName), + pData->fDirCreateMode, pData->dirUID, + pData->dirGID, pData->bFailOnChown) == 0) { + pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + pData->fCreateMode); + if(pData->fd != -1) { + /* check and set uid/gid */ + if(pData->fileUID != (uid_t)-1 || pData->fileGID != (gid_t) -1) { + /* we need to set owner/group */ + if(fchown(pData->fd, pData->fileUID, + pData->fileGID) != 0) { + if(pData->bFailOnChown) { + int eSave = errno; + close(pData->fd); + pData->fd = -1; + errno = eSave; + } + /* we will silently ignore the chown() failure + * if configured to do so. + */ + } + } + } + } + } + } +} + + +/* This function handles dynamic file names. It checks if the + * requested file name is already open and, if not, does everything + * needed to switch to the it. + * Function returns 0 if all went well and non-zero otherwise. + * On successful return pData->fd must point to the correct file to + * be written. + * This is a helper to writeFile(). rgerhards, 2007-07-03 + */ +static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) +{ + time_t ttOldest; /* timestamp of oldest element */ + int iOldest; + int i; + int iFirstFree; + dynaFileCacheEntry **pCache; + + ASSERT(pData != NULL); + ASSERT(newFileName != NULL); + + pCache = pData->dynCache; + + /* first check, if we still have the current file + * I *hope* this will be a performance enhancement. + */ + if( (pData->iCurrElt != -1) + && !strcmp((char*) newFileName, (char*) pCache[pData->iCurrElt]->pName)) { + /* great, we are all set */ + pCache[pData->iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */ + return 0; + } + + /* ok, no luck. Now let's search the table if we find a matching spot. + * While doing so, we also prepare for creation of a new one. + */ + iFirstFree = -1; /* not yet found */ + iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */ + ttOldest = time(NULL) + 1; /* there must always be an older one */ + for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { + if(pCache[i] == NULL) { + if(iFirstFree == -1) + iFirstFree = i; + } else { /* got an element, let's see if it matches */ + if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) { + /* we found our element! */ + pData->fd = pCache[i]->fd; + pData->iCurrElt = i; + pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */ + return 0; + } + /* did not find it - so lets keep track of the counters for LRU */ + if(pCache[i]->lastUsed < ttOldest) { + ttOldest = pCache[i]->lastUsed; + iOldest = i; + } + } + } + + /* we have not found an entry */ + if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) { + /* there is space left, so set it to that index */ + iFirstFree = pData->iCurrCacheSize++; + } + + if(iFirstFree == -1) { + dynaFileDelCacheEntry(pCache, iOldest, 0); + iFirstFree = iOldest; /* this one *is* now free ;) */ + } else { + /* we need to allocate memory for the cache structure */ + pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)); + if(pCache[iFirstFree] == NULL) { + glblHadMemShortage = TRUE; + dbgprintf("prepareDynfile(): could not alloc mem, discarding this request\n"); + return -1; + } + } + + /* Ok, we finally can open the file */ + prepareFile(pData, newFileName); + + /* file is either open now or an error state set */ + if(pData->fd == -1) { + /* do not report anything if the message is an internally-generated + * message. Otherwise, we could run into a never-ending loop. The bad + * news is that we also lose errors on startup messages, but so it is. + */ + if(iMsgOpts & INTERNAL_MSG) + dbgprintf("Could not open dynaFile, discarding message\n"); + else + errmsg.LogError(NO_ERRCODE, "Could not open dynamic file '%s' - discarding message", (char*)newFileName); + dynaFileDelCacheEntry(pCache, iFirstFree, 1); + pData->iCurrElt = -1; + return -1; + } + + pCache[iFirstFree]->fd = pData->fd; + pCache[iFirstFree]->pName = (uchar*)strdup((char*)newFileName); /* TODO: check for NULL (very unlikely) */ + pCache[iFirstFree]->lastUsed = time(NULL); + pData->iCurrElt = iFirstFree; + dbgprintf("Added new entry %d for file cache, file '%s'.\n", + iFirstFree, newFileName); + + return 0; +} + + +/* rgerhards 2004-11-11: write to a file output. This + * will be called for all outputs using file semantics, + * for example also for pipes. + */ +static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData) +{ + off_t actualFileSize; + DEFiRet; + + ASSERT(pData != NULL); + + /* first check if we have a dynamic file name and, if so, + * check if it still is ok or a new file needs to be created + */ + if(pData->bDynamicName) { + if(prepareDynFile(pData, ppString[1], iMsgOpts) != 0) + ABORT_FINALIZE(RS_RET_ERR); + } + + /* create the message based on format specified */ +again: + /* check if we have a file size limit and, if so, + * obey to it. + */ + if(pData->f_sizeLimit != 0) { + actualFileSize = lseek(pData->fd, 0, SEEK_END); + if(actualFileSize >= pData->f_sizeLimit) { + char errMsg[256]; + /* for now, we simply disable a file once it is + * beyond the maximum size. This is better than having + * us aborted by the OS... rgerhards 2005-06-21 + */ + (void) close(pData->fd); + /* try to resolve the situation */ + if(resolveFileSizeLimit(pData) != 0) { + /* didn't work out, so disable... */ + snprintf(errMsg, sizeof(errMsg), + "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation", + pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } else { + snprintf(errMsg, sizeof(errMsg), + "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation", + pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize); + errno = 0; + errmsg.LogError(NO_ERRCODE, "%s", errMsg); + } + } + } + + if (write(pData->fd, ppString[0], strlen((char*)ppString[0])) < 0) { + int e = errno; + + /* If a named pipe is full, just ignore it for now + - mrn 24 May 96 */ + if (pData->fileType == eTypePIPE && e == EAGAIN) + ABORT_FINALIZE(RS_RET_OK); + + /* If the filesystem is filled up, just ignore + * it for now and continue writing when possible + * based on patch for sysklogd by Martin Schulze on 2007-05-24 + */ + if (pData->fileType == eTypeFILE && e == ENOSPC) + ABORT_FINALIZE(RS_RET_OK); + + (void) close(pData->fd); + /* + * Check for EBADF on TTY's due to vhangup() + * Linux uses EIO instead (mrn 12 May 96) + */ + if ((pData->fileType == eTypeTTY || pData->fileType == eTypeCONSOLE) +#ifdef linux + && e == EIO) { +#else + && e == EBADF) { +#endif + pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_NOCTTY); + if (pData->fd < 0) { + iRet = RS_RET_DISABLE_ACTION; + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); + } else { + untty(); + goto again; + } + } else { + iRet = RS_RET_DISABLE_ACTION; + errno = e; + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); + } + } else if (pData->bSyncFile) { + fsync(pData->fd); + } + +finalize_it: + RETiRet; +} + + +BEGINcreateInstance +CODESTARTcreateInstance + pData->fd = -1; +ENDcreateInstance + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->bDynamicName) { + dynaFileFreeCache(pData); + } else if(pData->fd != -1) + close(pData->fd); +ENDfreeInstance + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf(" (%s)\n", pData->f_fname); + /* pData->fd == -1 is an indicator that the we couldn't + * open the file at startup. For dynaFiles, this is ok, + * all others are doomed. + */ + if(pData->bDynamicName || (pData->fd != -1)) + iRet = writeFile(ppString, iMsgOpts, pData); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + /* yes, the if below is redundant, but I need it now. Will go away as + * the code further changes. -- rgerhards, 2007-07-25 + */ + if(*p == '$' || *p == '?' || *p == '|' || *p == '/' || *p == '-') { + if((iRet = createInstance(&pData)) != RS_RET_OK) { + ENDfunc + return iRet; /* this can not use RET_iRet! */ + } + } else { + /* this is not clean, but we need it for the time being + * TODO: remove when cleaning up modularization + */ + ENDfunc + return RS_RET_CONFLINE_UNPROCESSED; + } + + if(*p == '-') { + pData->bSyncFile = 0; + p++; + } else { + pData->bSyncFile = bEnableSync ? 1 : 0; + } + + pData->f_sizeLimit = 0; /* default value, use outchannels to configure! */ + + switch (*p) + { + case '$': + CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* rgerhards 2005-06-21: this is a special setting for output-channel + * definitions. In the long term, this setting will probably replace + * anything else, but for the time being we must co-exist with the + * traditional mode lines. + * rgerhards, 2007-07-24: output-channels will go away. We keep them + * for compatibility reasons, but seems to have been a bad idea. + */ + if((iRet = cflineParseOutchannel(pData, p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) == RS_RET_OK) { + pData->bDynamicName = 0; + pData->fCreateMode = fCreateMode; /* preserve current setting */ + pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */ + pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + pData->fCreateMode); + } + break; + + case '?': /* This is much like a regular file handle, but we need to obtain + * a template name. rgerhards, 2007-07-03 + */ + CODE_STD_STRING_REQUESTparseSelectorAct(2) + ++p; /* eat '?' */ + if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) + != RS_RET_OK) + break; + /* "filename" is actually a template name, we need this as string 1. So let's add it + * to the pOMSR. -- rgerhards, 2007-07-27 + */ + if((iRet = OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) pData->f_fname), OMSR_NO_RQD_TPL_OPTS)) != RS_RET_OK) + break; + + pData->bDynamicName = 1; + pData->iCurrElt = -1; /* no current element */ + pData->fCreateMode = fCreateMode; /* freeze current setting */ + pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */ + pData->bCreateDirs = bCreateDirs; + pData->bFailOnChown = bFailOnChown; + pData->fileUID = fileUID; + pData->fileGID = fileGID; + pData->dirUID = dirUID; + pData->dirGID = dirGID; + pData->iDynaFileCacheSize = iDynaFileCacheSize; /* freeze current setting */ + /* we now allocate the cache table. We use calloc() intentionally, as we + * need all pointers to be initialized to NULL pointers. + */ + if((pData->dynCache = (dynaFileCacheEntry**) + calloc(iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))) == NULL) { + iRet = RS_RET_OUT_OF_MEMORY; + dbgprintf("Could not allocate memory for dynaFileCache - selector disabled.\n"); + } + break; + + case '|': + case '/': + CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* rgerhards, 2007-0726: first check if file or pipe */ + if(*p == '|') { + pData->fileType = eTypePIPE; + ++p; + } else { + pData->fileType = eTypeFILE; + } + /* rgerhards 2004-11-17: from now, we need to have different + * processing, because after the first comma, the template name + * to use is specified. So we need to scan for the first coma first + * and then look at the rest of the line. + */ + if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName)) + != RS_RET_OK) + break; + + pData->bDynamicName = 0; + pData->fCreateMode = fCreateMode; /* preserve current setting */ + pData->fDirCreateMode = fDirCreateMode; + pData->bCreateDirs = bCreateDirs; + pData->bFailOnChown = bFailOnChown; + pData->fileUID = fileUID; + pData->fileGID = fileGID; + pData->dirUID = dirUID; + pData->dirGID = dirGID; + + if(pData->fileType == eTypePIPE) { + pData->fd = open((char*) pData->f_fname, O_RDWR|O_NONBLOCK); + } else { + prepareFile(pData, pData->f_fname); + } + + if ( pData->fd < 0 ){ + pData->fd = -1; + dbgprintf("Error opening log file: %s\n", pData->f_fname); + errmsg.LogError(NO_ERRCODE, "%s", pData->f_fname); + break; + } + if (isatty(pData->fd)) { + pData->fileType = eTypeTTY; + untty(); + } + if (strcmp((char*) p, _PATH_CONSOLE) == 0) + pData->fileType = eTypeCONSOLE; + break; + default: + iRet = RS_RET_CONFLINE_UNPROCESSED; + break; + } +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Reset config variables for this module to default values. + * rgerhards, 2007-07-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + fileUID = -1; + fileGID = -1; + dirUID = -1; + dirGID = -1; + bFailOnChown = 1; + iDynaFileCacheSize = 10; + fCreateMode = 0644; + fDirCreateMode = 0644; + bCreateDirs = 1; + bEnableSync = 0; + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } + + return RS_RET_OK; +} + + +BEGINmodExit +CODESTARTmodExit + if(pszTplName != NULL) + free(pszTplName); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(File) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &dirUID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &dirGID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"fileowner", 0, eCmdHdlrUID, NULL, &fileUID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"filegroup", 0, eCmdHdlrGID, NULL, &fileGID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"dircreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fDirCreateMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fCreateMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &bCreateDirs, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &bFailOnChown, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfileenablesync", 0, eCmdHdlrBinary, NULL, &bEnableSync, STD_LOADABLE_MODULE_ID)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionfiledefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/tools/omfile.h b/tools/omfile.h new file mode 100644 index 00000000..03e081f3 --- /dev/null +++ b/tools/omfile.h @@ -0,0 +1,34 @@ +/* omfile.h + * These are the definitions for the build-in file output module. + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMFILE_H_INCLUDED +#define OMFILE_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMFILE_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/omfwd.c b/tools/omfwd.c new file mode 100644 index 00000000..ddaf496d --- /dev/null +++ b/tools/omfwd.c @@ -0,0 +1,643 @@ +/* omfwd.c + * This is the implementation of the build-in forwarding output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#ifdef SYSLOG_INET +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_NETZIP +#include +#endif +#include +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "net.h" +#include "omfwd.h" +#include "template.h" +#include "msg.h" +#include "tcpclt.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) +DEFobjCurrIf(tcpclt) + +typedef struct _instanceData { + char *f_hname; + short sock; /* file descriptor */ + int *pSockArray; /* sockets to use for UDP */ + enum { /* TODO: we shoud revisit these definitions */ + eDestFORW, + eDestFORW_SUSP, + eDestFORW_UNKN + } eDestState; + struct addrinfo *f_addr; + int compressionLevel; /* 0 - no compression, else level for zlib */ + char *port; + int protocol; +# define FORW_UDP 0 +# define FORW_TCP 1 + /* following fields for TCP-based delivery */ + time_t ttSuspend; /* time selector was suspended */ + tcpclt_t *pTCPClt; /* our tcpclt object */ +} instanceData; + +/* config data */ +static uchar *pszTplName = NULL; /* name of the default template to use */ + + +/* get the syslog forward port from selector_t. The passed in + * struct must be one that is setup for forwarding. + * rgerhards, 2007-06-28 + * We may change the implementation to try to lookup the port + * if it is unspecified. So far, we use the IANA default auf 514. + */ +static char *getFwdSyslogPt(instanceData *pData) +{ + assert(pData != NULL); + if(pData->port == NULL) + return("514"); + else + return(pData->port); +} + +BEGINcreateInstance +CODESTARTcreateInstance + pData->sock = -1; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + switch (pData->eDestState) { + case eDestFORW: + case eDestFORW_SUSP: + freeaddrinfo(pData->f_addr); + /* fall through */ + case eDestFORW_UNKN: + if(pData->port != NULL) + free(pData->port); + break; + } + + /* final cleanup */ + if(pData->sock >= 0) + close(pData->sock); + if(pData->pSockArray != NULL) + net.closeUDPListenSockets(pData->pSockArray); + + if(pData->protocol == FORW_TCP) { + tcpclt.Destruct(&pData->pTCPClt); + } + + if(pData->f_hname != NULL) + free(pData->f_hname); + +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("%s", pData->f_hname); +ENDdbgPrintInstInfo + + +/* Send a message via UDP + * rgehards, 2007-12-20 + */ +static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len) +{ + DEFiRet; + struct addrinfo *r; + int i; + unsigned lsent = 0; + int bSendSuccess; + + if(pData->pSockArray != NULL) { + /* we need to track if we have success sending to the remote + * peer. Success is indicated by at least one sendto() call + * succeeding. We track this be bSendSuccess. We can not simply + * rely on lsent, as a call might initially work, but a later + * call fails. Then, lsent has the error status, even though + * the sendto() succeeded. + * rgerhards, 2007-06-22 + */ + bSendSuccess = FALSE; + for (r = pData->f_addr; r; r = r->ai_next) { + for (i = 0; i < *pData->pSockArray; i++) { + lsent = sendto(pData->pSockArray[i+1], msg, len, 0, r->ai_addr, r->ai_addrlen); + if (lsent == len) { + bSendSuccess = TRUE; + break; + } else { + int eno = errno; + char errStr[1024]; + dbgprintf("sendto() error: %d = %s.\n", + eno, rs_strerror_r(eno, errStr, sizeof(errStr))); + } + } + if (lsent == len && !send_to_all) + break; + } + /* finished looping */ + if (bSendSuccess == FALSE) { + dbgprintf("error forwarding via udp, suspending\n"); + iRet = RS_RET_SUSPENDED; + } + } + + RETiRet; +} + +/* CODE FOR SENDING TCP MESSAGES */ + + +/* Send a frame via plain TCP protocol + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len) +{ + DEFiRet; + ssize_t lenSend; + instanceData *pData = (instanceData *) pvData; + + lenSend = send(pData->sock, msg, len, 0); + dbgprintf("TCP sent %ld bytes, requested %ld\n", (long) lenSend, (long) len); + + if(lenSend == -1) { + /* we have an error case - check what we can live with */ + switch(errno) { + case EMSGSIZE: + dbgprintf("message not (tcp)send, too large\n"); + /* This is not a real error, so it is not flagged as one */ + break; + default: + dbgprintf("message not (tcp)send"); + iRet = RS_RET_TCP_SEND_ERROR; + break; + } + } else if(lenSend != (ssize_t) len) { + /* no real error, could "just" not send everything... + * For the time being, we ignore this... + * rgerhards, 2005-10-25 + */ + dbgprintf("message not completely (tcp)send, ignoring %ld\n", (long) lenSend); + usleep(1000); /* experimental - might be benefitial in this situation */ + /* TODO: we need to revisit this code -- rgerhards, 2007-12-28 */ + } + + RETiRet; +} + + +/* This function is called immediately before a send retry is attempted. + * It shall clean up whatever makes sense. + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendPrepRetry(void *pvData) +{ + DEFiRet; + instanceData *pData = (instanceData *) pvData; + + assert(pData != NULL); + close(pData->sock); + pData->sock = -1; + RETiRet; +} + + +/* initialies everything so that TCPSend can work. + * rgerhards, 2007-12-28 + */ +static rsRetVal TCPSendInit(void *pvData) +{ + DEFiRet; + instanceData *pData = (instanceData *) pvData; + + assert(pData != NULL); + if(pData->sock < 0) { + if((pData->sock = tcpclt.CreateSocket(pData->f_addr)) < 0) + iRet = RS_RET_TCP_SOCKCREATE_ERR; + } + + RETiRet; +} + + +/* try to resume connection if it is not ready + * rgerhards, 2007-08-02 + */ +static rsRetVal doTryResume(instanceData *pData) +{ + DEFiRet; + struct addrinfo *res; + struct addrinfo hints; + unsigned e; + + switch (pData->eDestState) { + case eDestFORW_SUSP: + iRet = RS_RET_OK; /* the actual check happens during doAction() only */ + pData->eDestState = eDestFORW; + break; + + case eDestFORW_UNKN: + /* The remote address is not yet known and needs to be obtained */ + dbgprintf(" %s\n", pData->f_hname); + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requests this */ + /* TODO: this code is a duplicate from cfline() - we should later create + * a common function. + */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = family; + hints.ai_socktype = pData->protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM; + if((e = getaddrinfo(pData->f_hname, + getFwdSyslogPt(pData), &hints, &res)) == 0) { + dbgprintf("%s found, resuming.\n", pData->f_hname); + pData->f_addr = res; + pData->eDestState = eDestFORW; + } else { + iRet = RS_RET_SUSPENDED; + } + break; + case eDestFORW: + /* rgerhards, 2007-09-11: this can not happen, but I've included it to + * a) make the compiler happy, b) detect any logic errors */ + assert(0); + break; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doTryResume(pData); +ENDtryResume + +BEGINdoAction + char *psz; /* temporary buffering */ + register unsigned l; +CODESTARTdoAction + switch (pData->eDestState) { + case eDestFORW_SUSP: + dbgprintf("internal error in omfwd.c, eDestFORW_SUSP in doAction()!\n"); + iRet = RS_RET_SUSPENDED; + break; + + case eDestFORW_UNKN: + dbgprintf("doAction eDestFORW_UNKN\n"); + iRet = doTryResume(pData); + break; + + case eDestFORW: + dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdSyslogPt(pData), + pData->protocol == FORW_UDP ? "udp" : "tcp"); + /* with UDP, check if the socket is there and, if not, alloc + * it. TODO: there should be a better place for that code. + * rgerhards, 2007-12-26 + */ + if(pData->protocol == FORW_UDP) { + if(pData->pSockArray == NULL) { + pData->pSockArray = net.create_udp_socket((uchar*)pData->f_hname, NULL, 0); + } + } + pData->ttSuspend = time(NULL); + psz = (char*) ppString[0]; + l = strlen((char*) psz); + if (l > MAXLINE) + l = MAXLINE; + +# ifdef USE_NETZIP + /* Check if we should compress and, if so, do it. We also + * check if the message is large enough to justify compression. + * The smaller the message, the less likely is a gain in compression. + * To save CPU cycles, we do not try to compress very small messages. + * What "very small" means needs to be configured. Currently, it is + * hard-coded but this may be changed to a config parameter. + * rgerhards, 2006-11-30 + */ + if(pData->compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) { + Bytef out[MAXLINE+MAXLINE/100+12] = "z"; + uLongf destLen = sizeof(out) / sizeof(Bytef); + uLong srcLen = l; + int ret; + ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz, + srcLen, pData->compressionLevel); + dbgprintf("Compressing message, length was %d now %d, return state %d.\n", + l, (int) destLen, ret); + if(ret != Z_OK) { + /* if we fail, we complain, but only in debug mode + * Otherwise, we are silent. In any case, we ignore the + * failed compression and just sent the uncompressed + * data, which is still valid. So this is probably the + * best course of action. + * rgerhards, 2006-11-30 + */ + dbgprintf("Compression failed, sending uncompressed message\n"); + } else if(destLen+1 < l) { + /* only use compression if there is a gain in using it! */ + dbgprintf("there is gain in compression, so we do it\n"); + psz = (char*) out; + l = destLen + 1; /* take care for the "z" at message start! */ + } + ++destLen; + } +# endif + + if(pData->protocol == FORW_UDP) { + /* forward via UDP */ + CHKiRet(UDPSend(pData, psz, l)); + } else { + /* forward via TCP */ + rsRetVal ret; + ret = tcpclt.Send(pData->pTCPClt, pData, psz, l); + if(ret != RS_RET_OK) { + /* error! */ + dbgprintf("error forwarding via tcp, suspending\n"); + pData->eDestState = eDestFORW_SUSP; + iRet = RS_RET_SUSPENDED; + } + } + break; + } +finalize_it: +ENDdoAction + + +BEGINparseSelectorAct + uchar *q; + int i; + int error; + int bErr; + struct addrinfo hints, *res; + TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(*p == '@') { + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + ++p; /* eat '@' */ + if(*p == '@') { /* indicator for TCP! */ + pData->protocol = FORW_TCP; + ++p; /* eat this '@', too */ + } else { + pData->protocol = FORW_UDP; + } + /* we are now after the protocol indicator. Now check if we should + * use compression. We begin to use a new option format for this: + * @(option,option)host:port + * The first option defined is "z[0..9]" where the digit indicates + * the compression level. If it is not given, 9 (best compression) is + * assumed. An example action statement might be: + * @@(z5,o)127.0.0.1:1400 + * Which means send via TCP with medium (5) compresion (z) to the local + * host on port 1400. The '0' option means that octet-couting (as in + * IETF I-D syslog-transport-tls) is to be used for framing (this option + * applies to TCP-based syslog only and is ignored when specified with UDP). + * That is not yet implemented. + * rgerhards, 2006-12-07 + */ + if(*p == '(') { + /* at this position, it *must* be an option indicator */ + do { + ++p; /* eat '(' or ',' (depending on when called) */ + /* check options */ + if(*p == 'z') { /* compression */ +# ifdef USE_NETZIP + ++p; /* eat */ + if(isdigit((int) *p)) { + int iLevel; + iLevel = *p - '0'; + ++p; /* eat */ + pData->compressionLevel = iLevel; + } else { + errmsg.LogError(NO_ERRCODE, "Invalid compression level '%c' specified in " + "forwardig action - NOT turning on compression.", + *p); + } +# else + errmsg.LogError(NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " + "with compression support - request ignored."); +# endif /* #ifdef USE_NETZIP */ + } else if(*p == 'o') { /* octet-couting based TCP framing? */ + ++p; /* eat */ + /* no further options settable */ + tcp_framing = TCP_FRAMING_OCTET_COUNTING; + } else { /* invalid option! Just skip it... */ + errmsg.LogError(NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p); + ++p; /* eat invalid option */ + } + /* the option processing is done. We now do a generic skip + * to either the next option or the end of the option + * block. + */ + while(*p && *p != ')' && *p != ',') + ++p; /* just skip it */ + } while(*p && *p == ','); /* Attention: do.. while() */ + if(*p == ')') + ++p; /* eat terminator, on to next */ + else + /* we probably have end of string - leave it for the rest + * of the code to handle it (but warn the user) + */ + errmsg.LogError(NO_ERRCODE, "Option block not terminated in forwarding action."); + } + /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') + * now skip to port and then template name. rgerhards 2005-07-06 + */ + for(q = p ; *p && *p != ';' && *p != ':' ; ++p) + /* JUST SKIP */; + + pData->port = NULL; + if(*p == ':') { /* process port */ + uchar * tmp; + + *p = '\0'; /* trick to obtain hostname (later)! */ + tmp = ++p; + for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) + /* SKIP AND COUNT */; + pData->port = malloc(i + 1); + if(pData->port == NULL) { + errmsg.LogError(NO_ERRCODE, "Could not get memory to store syslog forwarding port, " + "using default port, results may not be what you intend\n"); + /* we leave f_forw.port set to NULL, this is then handled by + * getFwdSyslogPt(). + */ + } else { + memcpy(pData->port, tmp, i); + *(pData->port + i) = '\0'; + } + } + + /* now skip to template */ + bErr = 0; + while(*p && *p != ';') { + if(*p && *p != ';' && !isspace((int) *p)) { + if(bErr == 0) { /* only 1 error msg! */ + bErr = 1; + errno = 0; + errmsg.LogError(NO_ERRCODE, "invalid selector line (port), probably not doing " + "what was intended"); + } + } + ++p; + } + + /* TODO: make this if go away! */ + if(*p == ';') { + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = ';'; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); + + /* first set the pData->eDestState */ + memset(&hints, 0, sizeof(hints)); + /* port must be numeric, because config file syntax requests this */ + hints.ai_flags = AI_NUMERICSERV; + hints.ai_family = family; + hints.ai_socktype = pData->protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM; + if( (error = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) != 0) { + pData->eDestState = eDestFORW_UNKN; + pData->ttSuspend = time(NULL); + } else { + pData->eDestState = eDestFORW; + pData->f_addr = res; + } + /* + * Otherwise the host might be unknown due to an + * inaccessible nameserver (perhaps on the same + * host). We try to get the ip number later, like + * FORW_SUSP. + */ + if(pData->protocol == FORW_TCP) { + /* create our tcpclt */ + CHKiRet(tcpclt.Construct(&pData->pTCPClt)); + /* and set callbacks */ + CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendInit)); + CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendFrame)); + CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendPrepRetry)); + CHKiRet(tcpclt.SetFraming(pData->pTCPClt, tcp_framing)); + } + + } else { + iRet = RS_RET_CONFLINE_UNPROCESSED; + } + + /* TODO: do we need to call freeInstance if we failed - this is a general question for + * all output modules. I'll address it lates as the interface evolves. rgerhards, 2007-07-25 + */ +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + * rgerhards, 2008-03-28 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } + + return RS_RET_OK; +} + + +BEGINmodInit(Fwd) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + + CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +#endif /* #ifdef SYSLOG_INET */ +/* vim:set ai: + */ diff --git a/tools/omfwd.h b/tools/omfwd.h new file mode 100644 index 00000000..dea432e5 --- /dev/null +++ b/tools/omfwd.h @@ -0,0 +1,34 @@ +/* omfwd.h + * These are the definitions for the build-in forwarding output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMFWD_H_INCLUDED +#define OMFWD_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMFWD_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/omshell.c b/tools/omshell.c new file mode 100644 index 00000000..2176c101 --- /dev/null +++ b/tools/omshell.c @@ -0,0 +1,148 @@ +/* omshell.c + * This is the implementation of the build-in shell output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * shell support was initially written by bkalkbrenner 2005-09-20 + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "omshell.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + uchar progName[MAXFNAME]; /* program to execute */ +} instanceData; + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("%s", pData->progName); +ENDdbgPrintInstInfo + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + /* TODO: using pData->progName is not clean from the point of + * modularization. We'll change that as we go ahead with modularization. + * rgerhards, 2007-07-20 + */ + dbgprintf("\n"); + if(execProg((uchar*) pData->progName, 1, ppString[0]) == 0) + errmsg.LogError(NO_ERRCODE, "Executing program '%s' failed", (char*)pData->progName); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + /* yes, the if below is redundant, but I need it now. Will go away as + * the code further changes. -- rgerhards, 2007-07-25 + */ + if(*p == '^') { + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + } + + + switch (*p) + { + case '^': /* bkalkbrenner 2005-09-20: execute shell command */ + dbgprintf("exec\n"); + ++p; + iRet = cflineParseFileName(p, (uchar*) pData->progName, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (uchar*)"RSYSLOG_TraditionalFileFormat"); + break; + default: + iRet = RS_RET_CONFLINE_UNPROCESSED; + break; + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(Shell) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit + +/* + * vi:set ai: + */ diff --git a/tools/omshell.h b/tools/omshell.h new file mode 100644 index 00000000..3061ad07 --- /dev/null +++ b/tools/omshell.h @@ -0,0 +1,34 @@ +/* omshell.c + * These are the definitions for the build-in shell output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef ACTSHELL_H_INCLUDED +#define ACTSHELL_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef ACTSHELL_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/omusrmsg.c b/tools/omusrmsg.c new file mode 100644 index 00000000..42d3291d --- /dev/null +++ b/tools/omusrmsg.c @@ -0,0 +1,352 @@ +/* omusrmsg.c + * This is the implementation of the build-in output module for sending + * user messages. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_FCNTL_H +#include +#else +#include +#endif +#if HAVE_PATHS_H +#include +#endif +#include "srUtils.h" +#include "stringbuf.h" +#include "syslogd-types.h" +#include "syslogd.h" +#include "omusrmsg.h" +#include "module-template.h" +#include "errmsg.h" + + +/* portability: */ +#ifndef _PATH_DEV +# define _PATH_DEV "/dev/" +#endif + + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +typedef struct _instanceData { + int bIsWall; /* 1- is wall, 0 - individual users */ + char uname[MAXUNAMES][UNAMESZ+1]; +} instanceData; + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + /* TODO: free the instance pointer (currently a leak, will go away) */ +ENDfreeInstance + + +BEGINdbgPrintInstInfo + register int i; +CODESTARTdbgPrintInstInfo + for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++) + dbgprintf("%s, ", pData->uname[i]); +ENDdbgPrintInstInfo + + +static jmp_buf ttybuf; + +static void endtty() +{ + longjmp(ttybuf, 1); +} + +/** + * BSD setutent/getutent() replacement routines + * The following routines emulate setutent() and getutent() under + * BSD because they are not available there. We only emulate what we actually + * need! rgerhards 2005-03-18 + */ +#ifdef OS_BSD +static FILE *BSD_uf = NULL; +void setutent(void) +{ + assert(BSD_uf == NULL); + if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) { + errmsg.LogError(NO_ERRCODE, "%s", _PATH_UTMP); + return; + } +} + +struct utmp* getutent(void) +{ + static struct utmp st_utmp; + + if(fread((char *)&st_utmp, sizeof(st_utmp), 1, BSD_uf) != 1) + return NULL; + + return(&st_utmp); +} + +void endutent(void) +{ + fclose(BSD_uf); + BSD_uf = NULL; +} +#endif /* #ifdef OS_BSD */ + + +/* + * WALLMSG -- Write a message to the world at large + * + * Write the specified message to either the entire + * world, or a list of approved users. + * + * rgerhards, 2005-10-19: applying the following sysklogd patch: + * Tue May 4 16:52:01 CEST 2004: Solar Designer + * Adjust the size of a variable to prevent a buffer overflow + * should _PATH_DEV ever contain something different than "/dev/". + */ +static rsRetVal wallmsg(uchar* pMsg, instanceData *pData) +{ + + char p[sizeof(_PATH_DEV) + UNAMESZ]; + register int i; + int ttyf; + static int reenter = 0; + struct utmp ut; + struct utmp *uptr; + struct sigaction sigAct; + + assert(pMsg != NULL); + + if (reenter++) + return RS_RET_OK; + + /* open the user login file */ + setutent(); + + /* + * Might as well fork instead of using nonblocking I/O + * and doing notty(). + */ + if (fork() == 0) { + memset(&sigAct, 0, sizeof(sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = SIG_DFL; + sigaction(SIGTERM, &sigAct, NULL); + alarm(0); + +# ifdef SIGTTOU + sigAct.sa_handler = SIG_DFL; + sigaction(SIGTERM, &sigAct, NULL); +# endif + /* It is save to call sigprocmask here, as we are now executing the child (no threads) */ + sigprocmask(SIG_SETMASK, &sigAct.sa_mask, NULL); + /* TODO: find a way to limit the max size of the message. hint: this + * should go into the template! + */ + + /* rgerhards 2005-10-24: HINT: this code might be run in a seperate thread + * instead of a seperate process once we have multithreading... + */ + + /* scan the user login file */ + while ((uptr = getutent())) { + memcpy(&ut, uptr, sizeof(ut)); + /* is this slot used? */ + if (ut.ut_name[0] == '\0') + continue; +#ifndef OS_BSD + if (ut.ut_type != USER_PROCESS) + continue; +#endif + if (!(strncmp (ut.ut_name,"LOGIN", 6))) /* paranoia */ + continue; + + /* should we send the message to this user? */ + if (pData->bIsWall == 0) { + for (i = 0; i < MAXUNAMES; i++) { + if (!pData->uname[i][0]) { + i = MAXUNAMES; + break; + } + if (strncmp(pData->uname[i], + ut.ut_name, UNAMESZ) == 0) + break; + } + if (i >= MAXUNAMES) + continue; + } + + /* compute the device name */ + strcpy(p, _PATH_DEV); + strncat(p, ut.ut_line, UNAMESZ); + + if (setjmp(ttybuf) == 0) { + sigAct.sa_handler = endtty; + sigaction(SIGALRM, &sigAct, NULL); + (void) alarm(15); + /* open the terminal */ + ttyf = open(p, O_WRONLY|O_NOCTTY); + if (ttyf >= 0) { + struct stat statb; + + if (fstat(ttyf, &statb) == 0 && + (statb.st_mode & S_IWRITE)) { + (void) write(ttyf, pMsg, strlen((char*)pMsg)); + } + close(ttyf); + ttyf = -1; + } + } + (void) alarm(0); + } + exit(0); /* "good" exit - this terminates the child forked just for message delivery */ + } + /* close the user login file */ + endutent(); + reenter = 0; + return RS_RET_OK; +} + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = wallmsg(ppString[0], pData); +ENDdoAction + + +BEGINparseSelectorAct + uchar *q; + int i; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + + /* User names must begin with a gnu e-regex: + * [a-zA-Z0-9_.] + * plus '*' for wall + */ + if (!*p || !((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') + || (*p >= '0' && *p <= '9') || *p == '_' || *p == '.' || *p == '*')) + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + + + if(*p == '*') { /* wall */ + dbgprintf("write-all"); + ++p; /* eat '*' */ + pData->bIsWall = 1; /* write to all users */ + if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " WallFmt")) + != RS_RET_OK) + goto finalize_it; + } else { + /* everything else beginning with the regex above + * is currently treated as a user name + * TODO: is this portable? + */ + dbgprintf("users: %s\n", p); /* ASP */ + pData->bIsWall = 0; /* write to individual users */ + for (i = 0; i < MAXUNAMES && *p && *p != ';'; i++) { + for (q = p; *q && *q != ',' && *q != ';'; ) + q++; + (void) strncpy((char*) pData->uname[i], (char*) p, UNAMESZ); + if ((q - p) > UNAMESZ) + pData->uname[i][UNAMESZ] = '\0'; + else + pData->uname[i][q - p] = '\0'; + while (*q == ',' || *q == ' ') + q++; + p = q; + } + /* done, on to the template + * TODO: we need to handle the case where i >= MAXUNAME! + */ + if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*)" StdUsrMsgFmt")) + != RS_RET_OK) + goto finalize_it; + } +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit(UsrMsg) +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); +ENDmodInit + +/* + * vi:set ai: + */ diff --git a/tools/omusrmsg.h b/tools/omusrmsg.h new file mode 100644 index 00000000..52e780f7 --- /dev/null +++ b/tools/omusrmsg.h @@ -0,0 +1,34 @@ +/* omusrmsg.c + * These are the definitions for the build-in user message output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMUSRMSG_H_INCLUDED +#define OMUSRMSG_H_INCLUDED 1 + +/* prototypes */ +rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*); + +#endif /* #ifndef OMUSRMSG_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/tools/pidfile.c b/tools/pidfile.c new file mode 100644 index 00000000..2be13da6 --- /dev/null +++ b/tools/pidfile.c @@ -0,0 +1,156 @@ +/* + pidfile.c - interact with pidfiles + Copyright (c) 1995 Martin Schulze + + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ +#include "config.h" + + +#include "rsyslog.h" + +/* + * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze + * First version (v0.2) released + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef __sun +#include +#endif + +#include "srUtils.h" + +/* read_pid + * + * Reads the specified pidfile and returns the read pid. + * 0 is returned if either there's no pidfile, it's empty + * or no pid can be read. + */ +int read_pid (char *pidfile) +{ + FILE *f; + int pid; + + if (!(f=fopen(pidfile,"r"))) + return 0; + fscanf(f,"%d", &pid); + fclose(f); + return pid; +} + +/* check_pid + * + * Reads the pid using read_pid and looks up the pid in the process + * table (using /proc) to determine if the process already exists. If + * so 1 is returned, otherwise 0. + */ +int check_pid (char *pidfile) +{ + int pid = read_pid(pidfile); + + /* Amazing ! _I_ am already holding the pid file... */ + if ((!pid) || (pid == getpid ())) + return 0; + + /* + * The 'standard' method of doing this is to try and do a 'fake' kill + * of the process. If an ESRCH error is returned the process cannot + * be found -- GW + */ + /* But... errno is usually changed only on error.. */ + if (kill(pid, 0) && errno == ESRCH) + return(0); + + return pid; +} + +/* write_pid + * + * Writes the pid to the specified file. If that fails 0 is + * returned, otherwise the pid. + */ +int write_pid (char *pidfile) +{ + FILE *f; + int fd; + int pid; + + if ( ((fd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1) + || ((f = fdopen(fd, "r+")) == NULL) ) { + fprintf(stderr, "Can't open or create %s.\n", pidfile); + return 0; + } + + /* It seems to be acceptable that we do not lock the pid file + * if we run under Solaris. In any case, it is highly unlikely + * that two instances try to access this file. And flock is really + * causing me grief on my initial steps on Solaris. Some time later, + * we might re-enable it (or use some alternate method). + * 2006-02-16 rgerhards + */ + +#if HAVE_FLOCK + if (flock(fd, LOCK_EX|LOCK_NB) == -1) { + fscanf(f, "%d", &pid); + fclose(f); + printf("Can't lock, lock is held by pid %d.\n", pid); + return 0; + } +#endif + + pid = getpid(); + if (!fprintf(f,"%d\n", pid)) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + printf("Can't write pid , %s.\n", errStr); + close(fd); + return 0; + } + fflush(f); + +#if HAVE_FLOCK + if (flock(fd, LOCK_UN) == -1) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + printf("Can't unlock pidfile %s, %s.\n", pidfile, errStr); + close(fd); + return 0; + } +#endif + close(fd); + + return pid; +} + +/* remove_pid + * + * Remove the the specified file. The result from unlink(2) + * is returned + */ +int remove_pid (char *pidfile) +{ + return unlink (pidfile); +} + diff --git a/tools/pidfile.h b/tools/pidfile.h new file mode 100644 index 00000000..40be9069 --- /dev/null +++ b/tools/pidfile.h @@ -0,0 +1,51 @@ +/* + pidfile.h - interact with pidfiles + Copyright (c) 1995 Martin Schulze + + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ + +/* read_pid + * + * Reads the specified pidfile and returns the read pid. + * 0 is returned if either there's no pidfile, it's empty + * or no pid can be read. + */ +int read_pid (char *pidfile); + +/* check_pid + * + * Reads the pid using read_pid and looks up the pid in the process + * table (using /proc) to determine if the process already exists. If + * so 1 is returned, otherwise 0. + */ +int check_pid (char *pidfile); + +/* write_pid + * + * Writes the pid to the specified file. If that fails 0 is + * returned, otherwise the pid. + */ +int write_pid (char *pidfile); + +/* remove_pid + * + * Remove the the specified file. The result from unlink(2) + * is returned + */ +int remove_pid (char *pidfile); diff --git a/tools/rsyslog.conf.5 b/tools/rsyslog.conf.5 new file mode 100644 index 00000000..1c47f535 --- /dev/null +++ b/tools/rsyslog.conf.5 @@ -0,0 +1,728 @@ +.\" rsyslog.conf - rsyslogd(8) configuration file +.\" Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. +.\" +.\" This file is part of the rsyslog package, an enhanced system log daemon. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. +.\" +.TH RSYSLOG.CONF 5 "07 April 2008" "Version 3.17.0" "Linux System Administration" +.SH NAME +rsyslog.conf \- rsyslogd(8) configuration file +.SH DESCRIPTION +The +.I rsyslog.conf +file is the main configuration file for the +.BR rsyslogd (8) +which logs system messages on *nix systems. This file specifies rules +for logging. For special features see the +.BR rsyslogd (8) +manpage. Ryslog.conf is backward-compatible with sysklogd's syslog.conf file. So if you migrate +from syklogd you can rename it and it should work. + +.B Note that this version of rsyslog ships with extensive documentation in html format. +This is provided in the ./doc subdirectory and probably +in a separate package if you installed rsyslog via a packaging system. +To use rsyslog's advanced features, you +.B need +to look at the html documentation, because the man pages only cover +basic aspects of operation. + + +.SH MODULES + +Rsyslog has a modular design. Consequently, there is a growing number +of modules. See the html documentation for their full description. + +.TP +.I omsnmp +SNMP trap output module +.TP +.I omgssapi +Output module for GSS-enabled syslog +.TP +.I ommysql +Output module for MySQL +.TP +.I omprelp +Output module for the reliable RELP protocol (prevents message loss). +For details, see below at imrelp and the html documentation. +It can be used like this: +.IP +*.* :omrelp:server:port +.IP +*.* :omrelp:192.168.0.1:2514 # actual sample +.TP +.I ompgsql +Output module for PostgreSQL +.TP +.I omlibdbi +Generic database output module (Firebird/Interbase, MS SQL, Sybase, +SQLLite, Ingres, Oracle, mSQL) +.TP +.I imfile +Input module for text files +.TP +.I imudp +Input plugin for UDP syslog. Replaces the deprecated -r option. Can be +used like this: +.IP +$ModLoad imudp +.IP +$InputUDPServerRun 514 +.TP +.I imtcp +Input plugin for plain TCP syslog. Replaces the deprecated -t +option. Can be used like this: +.IP +$ModLoad imtcp +.IP +$InputTCPServerRun 514 +.TP +.TP +.I imtcp +Input plugin for the RELP protocol. RELP can be used instead +of UDP or plain TCP syslog to provide reliable delivery of +syslog messages. Please note that plain TCP syslog does NOT +provide truly reliable delivery, with it messages may be lost +when there is a connection problem or the server shuts down. +RELP prevents message loss in those cases. +It can be used like this: +.IP +$ModLoad imrelp +.IP +$InputRELPServerRun 2514 +.TP +.I imgssapi +Input plugin for plain TCP and GSS-enable syslog +.TP +.I immark +Support for mark messages +.TP +.I imklog +Kernel logging. To include kernel log messages, you need to do +.IP +$ModLoad imklog + +Please note that the klogd daemon is no longer necessary and consequently +no longer provided by the rsyslog package. +.TP +.I imuxsock +Unix sockets, including the system log socket. You need to specify +.IP +$ModLoad imudp + +in order to receive log messages from local system processes. This +config directive should only left out if you know exactly what you +are doing. + + +.SH BASIC STRUCTURE + +Lines starting with a hash mark ('#') and empty lines are ignored. +Rsyslog.conf should contain following sections (sorted by recommended order in file): + +.TP +Global directives +Global directives set some global properties of whole rsyslog daemon, for example size of main +message queue ($MainMessageQueueSize), loading external modules ($ModLoad) and so on. +All global directives need to be specified on a line by their own and must start with +a dollar-sign. The complete list of global directives can be found in html documentation in doc +directory or online on web pages. + +.TP +Templates +Templates allow you to specify format of the logged message. They are also used for dynamic +file name generation. They have to be defined before they are used in rules. For more info +about templates see TEMPLATES section of this manpage. + +.TP +Output channels +Output channels provide an umbrella for any type of output that the user might want. +They have to be defined before they are used in rules. For more info about output channels +see OUTPUT CHANNELS section of this manpage. + +.TP +Rules (selector + action) +Every rule line consists of two fields, a selector field and an action field. These +two fields are separated by one or more spaces or tabs. The selector field specifies +a pattern of facilities and priorities belonging to the specified action. + +.SH ACTIONS +The action field of a rule describes what to do with the message. In general, message content +is written to a kind of "logfile". But also other actions might be done, like writing to a +database table or forwarding to another host. + +.SS Regular file +Typically messages are logged to real files. The file has to be specified with full pathname, +beginning with a slash ('/'). + +.B Example: +.RS +*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFormat # log to a file in the traditional format +.RE + +Note: if you would like to use high-precision timestamps in your log files, +just remove the ";RSYSLOG_TraditionalFormat". That will select the default +template, which, if not changed, uses RFC 3339 timestamps. + +.B Example: +.RS +*.* /var/log/file.log # log to a file with RFC3339 timestamps +.RE + +.SS Named pipes +This version of rsyslogd(8) has support for logging output to named pipes (fifos). A fifo or +named pipe can be used as a destination for log messages by prepending a pipe symbol ('|') +to the name of the file. This is handy for debugging. Note that the fifo must be created with +the mkfifo(1) command before rsyslogd(8) is started. + +.SS Terminal and console +If the file you specified is a tty, special tty-handling is done, same with /dev/console. + +.SS Remote machine +There are three ways to forward message: the traditional UDP transport, which is extremely +lossy but standard, the plain TCP based transport which loses messages only during certain +situations but is widely available and the RELP transport which does not lose messages +but is currently available only as part of rsyslogd 3.15.0 and above. + +To forward messages to another host via UDP, prepend the hostname with the at sign ("@"). +To forward it via plain tcp, prepend two at signs ("@@"). To forward via RELP, prepend the +string ":omrelp:" in front of the hostname. + +.B Example: +.RS +*.* @192.168.0.1 +.RE +.sp +In the example above, messages are forwarded via UDP to the machine 192.168.0.1, the destination +port defaults to 514. Due to the nature of UDP, you will probably lose some messages in transit. +If you expect high traffic volume, you can expect to lose a quite noticable number of messages +(the higher the traffic, the more likely and severe is message loss). + +.B If you would like to prevent message loss, use RELP: +.RS +*.* :omrelp:192.168.0.1:2514 +.RE +.sp +Note that a port number was given as there is no standard port for relp. + +Keep in mind that you need to load the correct input and output plugins (see "Modules" above). + +Please note that rsyslogd offers a variety of options in regarding to remote +forwarding. For full details, please see the html documentation. + +.SS List of users +Usually critical messages are also directed to ``root'' on that machine. You can specify a list +of users that shall get the message by simply writing the login. You may specify more than one +user by separating them with commas (','). If they're logged in they get the message. Don't +think a mail would be sent, that might be too late. + +.SS Everyone logged on +Emergency messages often go to all users currently online to notify them that something strange +is happening with the system. To specify this wall(1)-feature use an asterisk ('*'). + +.SS Database table +This allows logging of the message to a database table. +By default, a MonitorWare-compatible schema is required for this to work. You can +create that schema with the createDB.SQL file that came with the rsyslog package. You can also +use any other schema of your liking - you just need to define a proper template and assign this +template to the action. + +See the html documentation for further details on database logging. + +.SS Discard +If the discard action is carried out, the received message is immediately discarded. Discard +can be highly effective if you want to filter out some annoying messages that otherwise would +fill your log files. To do that, place the discard actions early in your log files. +This often plays well with property-based filters, giving you great freedom in specifying +what you do not want. + +Discard is just the single tilde character with no further parameters. +.sp +.B Example: +.RS +*.* ~ # discards everything. +.RE + + +.SS Output channel +Binds an output channel definition (see there for details) to this action. Output channel actions +must start with a $-sign, e.g. if you would like to bind your output channel definition "mychannel" +to the action, use "$mychannel". Output channels support template definitions like all all other +actions. + +.SS Shell execute +This executes a program in a subshell. The program is passed the template-generated message as the +only command line parameter. Rsyslog waits until the program terminates and only then continues to run. + +.B Example: +.RS +^program-to-execute;template +.RE + +The program-to-execute can be any valid executable. It receives the template string as a single parameter +(argv[1]). + +.SH FILTER CONDITIONS +Rsyslog offers three different types "filter conditions": +.sp 0 + * "traditional" severity and facility based selectors +.sp 0 + * property-based filters +.sp 0 + * expression-based filters +.RE + +.SS Blocks +Rsyslogd supports BSD-style blocks inside rsyslog.conf. Each block of lines is separated from +the previous block by a program or hostname specification. A block will only log messages +corresponding to the most recent program and hostname specifications given. Thus, a block which +selects "ppp" as the program, directly followed by a block that selects messages from the +hostname "dialhost", then the second block will only log messages from the ppp program on dialhost. + +.SS Selectors +.B Selectors are the traditional way of filtering syslog messages. +They have been kept in rsyslog with their original syntax, because it is well-known, highly +effective and also needed for compatibility with stock syslogd configuration files. If you just +need to filter based on priority and facility, you should do this with selector lines. They are +not second-class citizens in rsyslog and offer the best performance for this job. + +.SS Property-Based Filters +Property-based filters are unique to rsyslogd. They allow to filter on any property, like HOSTNAME, +syslogtag and msg. + +A property-based filter must start with a colon in column 0. This tells rsyslogd that it is the new +filter type. The colon must be followed by the property name, a comma, the name of the compare +operation to carry out, another comma and then the value to compare against. This value must be quoted. +There can be spaces and tabs between the commas. Property names and compare operations are +case-sensitive, so "msg" works, while "MSG" is an invalid property name. In brief, the syntax is as follows: +.sp +.RS +:property, [!]compare-operation, "value" +.RE + +The following compare-operations are currently supported: +.sp +.RS +.B contains +.RS +Checks if the string provided in value is contained in the property +.RE +.sp +.B isequal +.RS +Compares the "value" string provided and the property contents. These two values must be exactly equal to match. +.RE +.sp +.B startswith +.RS +Checks if the value is found exactly at the beginning of the property value +.RE +.sp +.B regex +.RS +Compares the property against the provided regular expression. +.RE + +.SS Expression-Based Filters +See the html documentation for this feature. + + +.SH TEMPLATES + +Every output in rsyslog uses templates - this holds true for files, user +messages and so on. Templates compatible with the stock syslogd +formats are hardcoded into rsyslogd. If no template is specified, we use +one of these hardcoded templates. Search for "template_" in syslogd.c and +you will find the hardcoded ones. + +A template consists of a template directive, a name, the actual template text +and optional options. A sample is: + +.RS +.B $template MyTemplateName,"\\\\7Text %property% some more text\\\\n", +.RE + +The "$template" is the template directive. It tells rsyslog that this line +contains a template. The backslash is an escape character. For example, \\7 rings the +bell (this is an ASCII value), \\n is a new line. The set in rsyslog is a bit restricted +currently. + +All text in the template is used literally, except for things within percent +signs. These are properties and allow you access to the contents of the syslog +message. Properties are accessed via the property replacer and it can for example +pick a substring or do date-specific formatting. More on this is the PROPERTY REPLACER +section of this manpage. + +To escape: +.sp 0 + % = \\% +.sp 0 + \\ = \\\\ --> '\\' is used to escape (as in C) +.sp 0 +$template TraditionalFormat,%timegenerated% %HOSTNAME% %syslogtag%%msg%\n" + +Properties can be accessed by the property replacer (see there for details). + +.B Please note that templates can also by used to generate selector lines with dynamic file names. +For example, if you would like to split syslog messages from different hosts +to different files (one per host), you can define the following template: + +.RS +.B $template DynFile,"/var/log/system-%HOSTNAME%.log" +.RE + +This template can then be used when defining an output selector line. It will +result in something like "/var/log/system-localhost.log" + +.SS Template options +The part is optional. It carries options influencing the template as whole. +See details below. Be sure NOT to mistake template options with property options - the +later ones are processed by the property replacer and apply to a SINGLE property, only +(and not the whole template). + +Template options are case-insensitive. Currently defined are: + +.RS +.TP +sql +format the string suitable for a SQL statement in MySQL format. This will replace single +quotes ("'") and the backslash character by their backslash-escaped counterpart +("\'" and "\\") inside each field. Please note that in MySQL configuration, the NO_BACKSLASH_ESCAPES +mode must be turned off for this format to work (this is the default). + +.TP +stdsql +format the string suitable for a SQL statement that is to be sent to a standards-compliant +sql server. This will replace single quotes ("'") by two single quotes ("''") inside each field. +You must use stdsql together with MySQL if in MySQL configuration the NO_BACKSLASH_ESCAPES +is turned on. +.RE + +Either the +.B sql +or +.B stdsql +option +.B MUST +be specified when a template is used for writing to a database, +otherwise injection might occur. Please note that due to the unfortunate fact +that several vendors have violated the sql standard and introduced their own +escape methods, it is impossible to have a single option doing all the work. +So you yourself must make sure you are using the right format. +.B If you choose the wrong one, you are still vulnerable to sql injection. + +Please note that the database writer *checks* that the sql option is present +in the template. If it is not present, the write database action is disabled. +This is to guard you against accidental forgetting it and then becoming +vulnerable to SQL injection. The sql option can also be useful with files - +especially if you want to import them into a database on another machine for +performance reasons. However, do NOT use it if you do not have a real need for +it - among others, it takes some toll on the processing time. Not much, but on +a really busy system you might notice it ;) + +The default template for the write to database action has the sql option set. + +.SS Template examples +Please note that the samples are split across multiple lines. A template MUST +NOT actually be split across multiple lines. + +A template that resembles traditional syslogd file output: +.sp +.RS +$template TraditionalFormat,"%timegenerated% %HOSTNAME% +.sp 0 +%syslogtag%%msg:::drop-last-lf%\n" +.RE + +A template that tells you a little more about the message: +.sp +.RS +$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%, +.sp 0 +%syslogtag%,%msg%\n" +.RE + +A template for RFC 3164 format: +.sp +.RS +$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%" +.RE + +A template for the format traditionally used for user messages: +.sp +.RS +$template usermsg," XXXX%syslogtag%%msg%\n\r" +.RE + +And a template with the traditional wall-message format: +.sp +.RS +$template wallmsg,"\\r\\n\\7Message from syslogd@%HOSTNAME% at %timegenerated%" +.RE + +.B A template that can be used for writing to a database (please note the SQL template option) +.sp +.RS +.ad l +$template MySQLInsert,"insert iut, message, receivedat values +('%iut%', '%msg:::UPPERCASE%', '%timegenerated:::date-mysql%') +into systemevents\\r\\n", SQL + +NOTE 1: This template is embedded into core application under name +.B StdDBFmt +, so you don't need to define it. +.sp +NOTE 2: You have to have MySQL module installed to use this template. +.ad +.RE + +.SH OUTPUT CHANNELS + +Output Channels are a new concept first introduced in rsyslog 0.9.0. As of this writing, +it is most likely that they will be replaced by something different in the future. +So if you use them, be prepared to change you configuration file syntax when you upgrade +to a later release. + +Output channels are defined via an $outchannel directive. It's syntax is as follows: +.sp +.RS +.B $outchannel name,file-name,max-size,action-on-max-size +.RE + +name is the name of the output channel (not the file), file-name is the file name to be +written to, max-size the maximum allowed size and action-on-max-size a command to be issued +when the max size is reached. This command always has exactly one parameter. The binary is +that part of action-on-max-size before the first space, its parameter is everything behind +that space. + +Keep in mind that $outchannel just defines a channel with "name". It does not activate it. +To do so, you must use a selector line (see below). That selector line includes the channel +name plus an $ sign in front of it. A sample might be: +.sp +.RS +*.* $mychannel +.RE + +.SH PROPERTY REPLACER +The property replacer is a core component in rsyslogd's output system. A syslog message has +a number of well-defined properties (see below). Each of this properties can be accessed and +manipulated by the property replacer. With it, it is easy to use only part of a property value +or manipulate the value, e.g. by converting all characters to lower case. + +.SS Accessing Properties +Syslog message properties are used inside templates. They are accessed by putting them between +percent signs. Properties can be modified by the property replacer. The full syntax is as follows: +.sp +.RS +.B %propname:fromChar:toChar:options% +.RE + +propname is the name of the property to access. +.B It is case-sensitive. + +.SS Available Properties +.TP +.B msg +the MSG part of the message (aka "the message" ;)) +.TP +.B rawmsg +the message exactly as it was received from the socket. Should be useful for debugging. +.TP +.B HOSTNAME +hostname from the message +.TP +.B FROMHOST +hostname of the system the message was received from (in a relay chain, this is the system immediately +in front of us and not necessarily the original sender) +.TP +.B syslogtag +TAG from the message +.TP +.B programname +the "static" part of the tag, as defined by BSD syslogd. For example, when TAG is "named[12345]", +programname is "named". +.TP +.B PRI +PRI part of the message - undecoded (single value) +.TP +.B PRI-text +the PRI part of the message in a textual form (e.g. "syslog.info") +.TP +.B IUT +the monitorware InfoUnitType - used when talking to a MonitorWare backend (also for phpLogCon) +.TP +.B syslogfacility +the facility from the message - in numerical form +.TP +.B syslogfacility-text +the facility from the message - in text form +.TP +.B syslogseverity +severity from the message - in numerical form +.TP +.B syslogseverity-text +severity from the message - in text form +.TP +.B timegenerated +timestamp when the message was RECEIVED. Always in high resolution +.TP +.B timereported +timestamp from the message. Resolution depends on what was provided in the message (in most cases, only seconds) +.TP +.B TIMESTAMP +alias for timereported +.TP +.B PROTOCOL-VERSION +The contents of the PROTOCOL-VERSION field from IETF draft draft-ietf-syslog-protocol +.TP +.B STRUCTURED-DATA +The contents of the STRUCTURED-DATA field from IETF draft draft-ietf-syslog-protocol +.TP +.B APP-NAME +The contents of the APP-NAME field from IETF draft draft-ietf-syslog-protocol +.TP +.B PROCID +The contents of the PROCID field from IETF draft draft-ietf-syslog-protocol +.TP +.B MSGID +The contents of the MSGID field from IETF draft draft-ietf-syslog-protocol +.TP +.B $NOW +The current date stamp in the format YYYY-MM-DD +.TP +.B $YEAR +The current year (4-digit) +.TP +.B $MONTH +The current month (2-digit) +.TP +.B $DAY +The current day of the month (2-digit) +.TP +.B $HOUR +The current hour in military (24 hour) time (2-digit) +.TP +.B $MINUTE +The current minute (2-digit) + +.P +Properties starting with a $-sign are so-called system properties. These do NOT stem from the +message but are rather internally-generated. + +.SS Character Positions +FromChar and toChar are used to build substrings. They specify the offset within the string that +should be copied. Offset counting starts at 1, so if you need to obtain the first 2 characters of +the message text, you can use this syntax: "%msg:1:2%". If you do not wish to specify from and to, +but you want to specify options, you still need to include the colons. For example, if you would +like to convert the full message text to lower case, use "%msg:::lowercase%". If you would like to +extract from a position until the end of the string, you can place a dollar-sign ("$") in toChar +(e.g. %msg:10:$%, which will extract from position 10 to the end of the string). + +There is also support for +.B regular expressions. +To use them, you need to place a "R" into FromChar. +This tells rsyslog that a regular expression instead of position-based extraction is desired. The +actual regular expression +.B must +then be provided in toChar. The regular expression must be followed +by the string "--end". It denotes the end of the regular expression and will not become part of it. +If you are using regular expressions, the property replacer will return the part of the property text +that matches the regular expression. An example for a property replacer sequence with a regular +expression is: "%msg:R:.*Sev:. \\(.*\\) \\[.*--end%" + +Also, extraction can be done based on so-called "fields". To do so, place a "F" into FromChar. A field +in its current definition is anything that is delimited by a delimiter character. The delimiter by +default is TAB (US-ASCII value 9). However, if can be changed to any other US-ASCII character by +specifying a comma and the decimal US-ASCII value of the delimiter immediately after the "F". For example, +to use comma (",") as a delimiter, use this field specifier: "F,44". If your syslog data is delimited, +this is a quicker way to extract than via regular expressions (actually, a *much* quicker way). Field +counting starts at 1. Field zero is accepted, but will always lead to a "field not found" error. The same +happens if a field number higher than the number of fields in the property is requested. The field number +must be placed in the "ToChar" parameter. An example where the 3rd field (delimited by TAB) from the msg +property is extracted is as follows: "%msg:F:3%". The same example with semicolon as delimiter is +"%msg:F,59:3%". + +Please note that the special characters "F" and "R" are case-sensitive. Only upper case works, lower case +will return an error. There are no white spaces permitted inside the sequence (that will lead to error +messages and will NOT provide the intended result). + +.SS Property Options +Property options are case-insensitive. Currently, the following options are defined: +.TP +uppercase +convert property to lowercase only +.TP +lowercase +convert property text to uppercase only +.TP +drop-last-lf +The last LF in the message (if any), is dropped. Especially useful for PIX. +.TP +date-mysql +format as mysql date +.TP +date-rfc3164 +format as RFC 3164 date +.TP +date-rfc3339 +format as RFC 3339 date +.TP +escape-cc +replace control characters (ASCII value 127 and values less then 32) with an escape sequence. The sequence is "#" where charval is the 3-digit decimal value of the control character. For example, a tabulator would be replaced by "#009". +.TP +space-cc +replace control characters by spaces +.TP +drop-cc +drop control characters - the resulting string will neither contain control characters, escape sequences nor any other replacement character like space. + +.SH QUEUED OPERATIONS +Rsyslogd supports queued operations to handle offline outputs +(like remote syslogd's or database servers being down). When running in +queued mode, rsyslogd buffers messages to memory and optionally to disk +(on an as-needed basis). Queues survive rsyslogd restarts. + +It is highly suggested to use remote forwarding and database writing +in queued mode, only. + +To learn more about queued operations, see the html documentation. + +.SH FILES +.PD 0 +.TP +.I /etc/rsyslog.conf +Configuration file for +.B rsyslogd + +.SH SEE ALSO +.BR rsyslogd (8), +.BR logger (1), +.BR syslog (3) + +The complete documentation can be found in the doc folder of the rsyslog distribution or online at + +.RS +.B http://www.rsyslog.com/doc + +.RE +Please note that the man page reflects only a subset of the configuration options. Be sure to read +the html documentation for all features and details. This is especially vital if you plan to set +up a more-then-extremely-simple system. + +.SH AUTHORS +.B rsyslogd +is taken from sysklogd sources, which have been heavily modified +by Rainer Gerhards (rgerhards@adiscon.com) and others. diff --git a/tools/rsyslogd.8 b/tools/rsyslogd.8 new file mode 100644 index 00000000..2aa911d9 --- /dev/null +++ b/tools/rsyslogd.8 @@ -0,0 +1,375 @@ +.\" Copyright 2004-2008 Rainer Gerhards and Adiscon for the rsyslog modifications +.\" May be distributed under the GNU General Public License +.\" +.TH RSYSLOGD 8 "07 April 2008" "Version 3.17.0" "Linux System Administration" +.SH NAME +rsyslogd \- reliable and extended syslogd +.SH SYNOPSIS +.B rsyslogd +.RB [ " \-4 " ] +.RB [ " \-6 " ] +.RB [ " \-A " ] +.RB [ " \-d " ] +.RB [ " \-f " +.I config file +] +.br +.RB [ " \-i " +.I pid file +] +.RB [ " \-l " +.I hostlist +] +.RB [ " \-n " ] +.br +.RB [ " \-q " ] +.RB [ " \-Q " ] +.RB [ " \-s " +.I domainlist +] +.RB [ " \-v " ] +.RB [ " \-w " ] +.RB [ " \-x " ] +.LP +.SH DESCRIPTION +.B Rsyslogd +is a system utility providing support for message logging. +Support of both internet and +unix domain sockets enables this utility to support both local +and remote logging. + +.B Note that this version of rsyslog ships with extensive documentation in html format. +This is provided in the ./doc subdirectory and probably +in a separate package if you installed rsyslog via a packaging system. +To use rsyslog's advanced features, you +.B need +to look at the html documentation, because the man pages only cover +basic aspects of operation. +.B For details and configuration examples, see the rsyslog.conf (5) +.B man page and the online documentation at http://www.rsyslog.com/doc + +.BR Rsyslogd (8) +is derived from the sysklogd package which in turn is derived from the +stock BSD sources. + +.B Rsyslogd +provides a kind of logging that many modern programs use. Every logged +message contains at least a time and a hostname field, normally a +program name field, too, but that depends on how trusty the logging +program is. The rsyslog package supports free definition of output formats +via templates. It also supports precise timestamps and writing directly +to databases. If the database option is used, tools like phpLogCon can +be used to view the log data. + +While the +.B rsyslogd +sources have been heavily modified a couple of notes +are in order. First of all there has been a systematic attempt to +ensure that rsyslogd follows its default, standard BSD behavior. Of course, +some configuration file changes are necessary in order to support the +template system. However, rsyslogd should be able to use a standard +syslog.conf and act like the orginal syslogd. However, an original syslogd +will not work correctly with a rsyslog-enhanced configuration file. At +best, it will generate funny looking file names. +The second important concept to note is that this version of rsyslogd +interacts transparently with the version of syslog found in the +standard libraries. If a binary linked to the standard shared +libraries fails to function correctly we would like an example of the +anomalous behavior. + +The main configuration file +.I /etc/rsyslog.conf +or an alternative file, given with the +.B "\-f" +option, is read at startup. Any lines that begin with the hash mark +(``#'') and empty lines are ignored. If an error occurs during parsing +the error element is ignored. It is tried to parse the rest of the line. + +.LP +.SH OPTIONS +.B Note that in version 3 of rsyslog a number of command line options +.B have been deprecated and replaced with config file directives. The +.B -c option controls the backward compatibility mode in use. +.TP +.BI "\-A" +When sending UDP messages, there are potentially multiple pathes to +the target destination. By default, +.B rsyslogd +only sends to the first target it can successfully send to. If -A +is given, messages are sent to all targets. This may improve +reliability, but may also cause message duplicaton. This option +should enabled only if it is fully understood. +.TP +.BI "\-4" +Causes +.B rsyslogd +to listen to IPv4 addresses only. +If neither -4 nor -6 is given, +.B rsyslogd +listens to all configured addresses of the system. +.TP +.BI "\-6" +Causes +.B rsyslogd +to listen to IPv6 addresses only. +If neither -4 nor -6 is given, +.B rsyslogd +listens to all configured addresses of the system. +.TP +.BI "\-c " "version" +Selects the desired backward compatibility mode. It must always be the +first option on the command line, as it influences processing of the +other options. To use the rsyslog v3 native interface, specify -c3. To +use compatibility mode , either do not use -c at all or use +-c where +.IR version +is the rsyslog version that it shall be +compatible with. Using -c0 tells rsyslog to be command-line compatible +to sysklogd, which is the default if -c is not given. +.B Please note that rsyslogd issues warning messages if the -c3 +.B command line option is not given. +This is to alert you that your are running in compatibility +mode. Compatibility mode interfers with you rsyslog.conf commands and +may cause some undesired side-effects. It is meant to be used with a +plain old rsyslog.conf - if you use new features, things become +messy. So the best advice is to work through this document, convert +your options and config file and then use rsyslog in native mode. In +order to aid you in this process, rsyslog logs every +compatibility-mode config file directive it has generated. So you can +simply copy them from your logfile and paste them to the config. +.TP +.B "\-d" +Turns on debug mode. Using this the daemon will not proceed a +.BR fork (2) +to set itself in the background, but opposite to that stay in the +foreground and write much debug information on the current tty. See the +DEBUGGING section for more information. +.TP +.BI "\-f " "config file" +Specify an alternative configuration file instead of +.IR /etc/rsyslog.conf "," +which is the default. +.TP +.BI "\-i " "pid file" +Specify an alternative pid file instead of the default one. +This option must be used if multiple instances of rsyslogd should +run on a single machine. +.TP +.BI "\-l " "hostlist" +Specify a hostname that should be logged only with its simple hostname +and not the fqdn. Multiple hosts may be specified using the colon +(``:'') separator. +.TP +.B "\-n" +Avoid auto-backgrounding. This is needed especially if the +.B rsyslogd +is started and controlled by +.BR init (8). +.TP +.BI "\-q " "add hostname if DNS fails during ACL processing" +During ACL processing, hostnames are resolved to IP addreses for +performance reasons. If DNS fails during that process, the hostname +is added as wildcard text, which results in proper, but somewhat +slower operation once DNS is up again. +.TP +.BI "\-Q " "do not resolve hostnames during ACL processing" +Do not resolve hostnames to IP addresses during ACL processing. +.TP +.BI "\-s " "domainlist" +Specify a domainname that should be stripped off before +logging. Multiple domains may be specified using the colon (``:'') +separator. +Please be advised that no sub-domains may be specified but only entire +domains. For example if +.B "\-s north.de" +is specified and the host logging resolves to satu.infodrom.north.de +no domain would be cut, you will have to specify two domains like: +.BR "\-s north.de:infodrom.north.de" . +.TP +.B "\-v" +Print version and exit. +.TP +.B "\-w" +Supress warnings issued when messages are received from non-authorized +machines (those, that are in no AllowedSender list). +.TP +.B "\-x" +Disable DNS for remote messages. +.LP +.SH SIGNALS +.B Rsyslogd +reacts to a set of signals. You may easily send a signal to +.B rsyslogd +using the following: +.IP +.nf +kill -SIGNAL $(cat /var/run/syslogd.pid) +.fi +.PP +Note that -SIGNAL must be replaced with the actual signal +you are trying to send, e.g. with HUP. So it then becomes: +.IP +.nf +kill -HUP $(cat /var/run/syslogd.pid) +.fi +.PP +.TP +.B HUP +This lets +.B rsyslogd +perform a re-initialization. All open files are closed, the +configuration file (default is +.IR /etc/rsyslog.conf ")" +will be reread and the +.BR rsyslog (3) +facility is started again. +.TP +.B TERM ", " INT ", " QUIT +.B Rsyslogd +will die. +.TP +.B USR1 +Switch debugging on/off. This option can only be used if +.B rsyslogd +is started with the +.B "\-d" +debug option. +.TP +.B CHLD +Wait for childs if some were born, because of wall'ing messages. +.LP +.SH SECURITY THREATS +There is the potential for the rsyslogd daemon to be +used as a conduit for a denial of service attack. +A rogue program(mer) could very easily flood the rsyslogd daemon with +syslog messages resulting in the log files consuming all the remaining +space on the filesystem. Activating logging over the inet domain +sockets will of course expose a system to risks outside of programs or +individuals on the local machine. + +There are a number of methods of protecting a machine: +.IP 1. +Implement kernel firewalling to limit which hosts or networks have +access to the 514/UDP socket. +.IP 2. +Logging can be directed to an isolated or non-root filesystem which, +if filled, will not impair the machine. +.IP 3. +The ext2 filesystem can be used which can be configured to limit a +certain percentage of a filesystem to usage by root only. \fBNOTE\fP +that this will require rsyslogd to be run as a non-root process. +\fBALSO NOTE\fP that this will prevent usage of remote logging on the default port since +rsyslogd will be unable to bind to the 514/UDP socket. +.IP 4. +Disabling inet domain sockets will limit risk to the local machine. +.SS Message replay and spoofing +If remote logging is enabled, messages can easily be spoofed and replayed. +As the messages are transmitted in clear-text, an attacker might use +the information obtained from the packets for malicious things. Also, an +attacker might reply recorded messages or spoof a sender's IP address, +which could lead to a wrong perception of system activity. These can +be prevented by using GSS-API authentication and encryption. Be sure +to think about syslog network security before enabling it. +.LP +.SH DEBUGGING +When debugging is turned on using +.B "\-d" +option then +.B rsyslogd +will be very verbose by writing much of what it does on stdout. +.SH FILES +.PD 0 +.TP +.I /etc/rsyslog.conf +Configuration file for +.BR rsyslogd . +See +.BR rsyslog.conf (5) +for exact information. +.TP +.I /dev/log +The Unix domain socket to from where local syslog messages are read. +.TP +.I /var/run/rsyslogd.pid +The file containing the process id of +.BR rsyslogd . +.TP +.I prefix/lib/rsyslog +Default directory for +.B rsyslogd +modules. The +.I prefix +is specified during compilation (e.g. /usr/local). +.SH ENVIRONMENT +.TP +.B RSYSLOG_DEBUG +Controls runtime debug support.It contains an option string with the +following options possible (all are case insensitive): + +.RS +.IP LogFuncFlow +Print out the logical flow of functions (entering and exiting them) +.IP FileTrace +Ppecifies which files to trace LogFuncFlow. If not set (the +default), a LogFuncFlow trace is provided for all files. Set to +limit it to the files specified.FileTrace may be specified multiple +times, one file each (e.g. export RSYSLOG_DEBUG="LogFuncFlow +FileTrace=vm.c FileTrace=expr.c" +.IP PrintFuncDB +Print the content of the debug function database whenever debug +information is printed (e.g. abort case)! +.IP PrintAllDebugInfoOnExit +Print all debug information immediately before rsyslogd exits +(currently not implemented!) +.IP PrintMutexAction +Print mutex action as it happens. Useful for finding deadlocks and +such. +.IP NoLogTimeStamp +Do not prefix log lines with a timestamp (default is to do that). +.IP NoStdOut +Do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not +set, this means no messages will be displayed at all. +.IP Help +Display a very short list of commands - hopefully a life saver if +you can't access the documentation... +.RE + +.TP +.B RSYSLOG_DEBUGLOG +If set, writes (allmost) all debug message to the specified log file +in addition to stdout. +.TP +.B RSYSLOG_MODDIR +Provides the default directory in which loadable modules reside. +.PD +.SH BUGS +Please review the file BUGS for up-to-date information on known +bugs and annouyances. +.SH Further Information +Please visit +.BR http://www.rsyslog.com/doc +for additional information, tutorials and a support forum. +.SH SEE ALSO +.BR rsyslog.conf (5), +.BR logger (1), +.BR syslog (2), +.BR syslog (3), +.BR services (5), +.BR savelog (8) +.LP +.SH COLLABORATORS +.B rsyslogd +is derived from sysklogd sources, which in turn was taken from +the BSD sources. Special thanks to Greg Wettstein (greg@wind.enjellic.com) +and Martin Schulze (joey@linux.de) for the fine sysklogd package. + +.PD 0 +.TP +Rainer Gerhards +.TP +Adiscon GmbH +.TP +Grossrinderfeld, Germany +.TP +rgerhards@adiscon.com +.PD diff --git a/tools/syslogd.c b/tools/syslogd.c new file mode 100644 index 00000000..95a23e99 --- /dev/null +++ b/tools/syslogd.c @@ -0,0 +1,3441 @@ +/** + * \brief This is the main file of the rsyslogd daemon. + * + * Please visit the rsyslog project at + * + * http://www.rsyslog.com + * + * to learn more about it and discuss any questions you may have. + * + * rsyslog had initially been forked from the sysklogd project. + * I would like to express my thanks to the developers of the sysklogd + * package - without it, I would have had a much harder start... + * + * Please note that while rsyslog started from the sysklogd code base, + * it nowadays has almost nothing left in common with it. Allmost all + * parts of the code have been rewritten. + * + * This Project was intiated and is maintained by + * Rainer Gerhards . See + * AUTHORS to learn who helped make it become a reality. + * + * If you have questions about rsyslogd in general, please email + * info@adiscon.com. To learn more about rsyslogd, please visit + * http://www.rsyslog.com. + * + * \author Rainer Gerhards + * \date 2003-10-17 + * Some initial modifications on the sysklogd package to support + * liblogging. These have actually not yet been merged to the + * source you see currently (but they hopefully will) + * + * \date 2004-10-28 + * Restarted the modifications of sysklogd. This time, we + * focus on a simpler approach first. The initial goal is to + * provide MySQL database support (so that syslogd can log + * to the database). + * + * rsyslog - An Enhanced syslogd Replacement. + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" + +/* change the following setting to e.g. 32768 if you would like to + * support large message sizes for IHE (32k is the current maximum + * needed for IHE). I was initially tempted to increase it to 32k, + * but there is a large memory footprint with the current + * implementation in rsyslog. This will change as the processing + * changes, but I have re-set it to 1k, because the vast majority + * of messages is below that and the memory savings is huge, at + * least compared to the overall memory footprint. + * + * If you intend to receive Windows Event Log data (e.g. via + * EventReporter - www.eventreporter.com), you might want to + * increase this number to an even higher value, as event + * log messages can be very lengthy. + * rgerhards, 2005-07-05 + * + * during my recent testing, it showed that 4k seems to be + * the typical maximum for UDP based syslog. This is a IP stack + * restriction. Not always ... but very often. If you go beyond + * that value, be sure to test that rsyslogd actually does what + * you think it should do ;) Also, it is a good idea to check the + * doc set for anything on IHE - it most probably has information on + * message sizes. + * rgerhards, 2005-08-05 + * + * I have increased the default message size to 2048 to be in sync + * with recent IETF syslog standardization efforts. + * rgerhards, 2006-11-30 + */ +#define DEFUPRI (LOG_USER|LOG_NOTICE) +#define TIMERINTVL 30 /* interval for checking flush, mark */ + +#include +#include +#include +#include +#include +#include +#define GNU_SOURCE +#include +#include +#include +#include +#include + +#ifdef __sun +# include +#else +# include +#endif +#include +#include +#include + +#if HAVE_SYS_TIMESPEC_H +# include +#endif + +#if HAVE_SYS_STAT_H +# include +#endif + +#include + +#if HAVE_PATHS_H +#include +#endif + +#ifdef USE_NETZIP +#include +#endif + +#include + +#include "pidfile.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "syslogd-types.h" +#include "template.h" +#include "outchannel.h" +#include "syslogd.h" + +#include "msg.h" +#include "modules.h" +#include "action.h" +#include "iminternal.h" +#include "cfsysline.h" +#include "omshell.h" +#include "omusrmsg.h" +#include "omfwd.h" +#include "omfile.h" +#include "omdiscard.h" +#include "threads.h" +#include "queue.h" +#include "stream.h" +#include "conf.h" +#include "vm.h" +#include "errmsg.h" +#include "datetime.h" +#include "sysvar.h" + +/* definitions for objects we access */ +DEFobjCurrIf(obj) +DEFobjCurrIf(datetime) +DEFobjCurrIf(conf) +DEFobjCurrIf(expr) +DEFobjCurrIf(vm) +DEFobjCurrIf(var) +DEFobjCurrIf(module) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(net) /* TODO: make go away! */ + + +/* forward definitions */ +static rsRetVal GlobalClassExit(void); + +/* We define our own set of syslog defintions so that we + * do not need to rely on (possibly different) implementations. + * 2007-07-19 rgerhards + */ +/* missing definitions for solaris + * 2006-02-16 Rger + */ +#ifdef __sun +# define LOG_AUTHPRIV LOG_AUTH +#endif +#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ +#define LOG_FTP (11<<3) /* ftp daemon */ + + +#ifndef UTMP_FILE +#ifdef UTMP_FILENAME +#define UTMP_FILE UTMP_FILENAME +#else +#ifdef _PATH_UTMP +#define UTMP_FILE _PATH_UTMP +#else +#define UTMP_FILE "/etc/utmp" +#endif +#endif +#endif + +#ifndef _PATH_LOGCONF +#define _PATH_LOGCONF "/etc/rsyslog.conf" +#endif + +#ifndef _PATH_MODDIR +#define _PATH_MODDIR "/lib/rsyslog/" +#endif + +#if defined(SYSLOGD_PIDNAME) +# undef _PATH_LOGPID +# if defined(FSSTND) +# ifdef OS_BSD +# define _PATH_VARRUN "/var/run/" +# endif +# if defined(__sun) || defined(__hpux) +# define _PATH_VARRUN "/var/run/" +# endif +# define _PATH_LOGPID _PATH_VARRUN SYSLOGD_PIDNAME +# else +# define _PATH_LOGPID "/etc/" SYSLOGD_PIDNAME +# endif +#else +# ifndef _PATH_LOGPID +# if defined(__sun) || defined(__hpux) +# define _PATH_VARRUN "/var/run/" +# endif +# if defined(FSSTND) +# define _PATH_LOGPID _PATH_VARRUN "rsyslogd.pid" +# else +# define _PATH_LOGPID "/etc/rsyslogd.pid" +# endif +# endif +#endif + +#ifndef _PATH_DEV +# define _PATH_DEV "/dev/" +#endif + +#ifndef _PATH_TTY +#define _PATH_TTY "/dev/tty" +#endif + +static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */ +static char *PidFile = _PATH_LOGPID; /* read-only after startup */ + +static pid_t myPid; /* our pid for use in self-generated messages, e.g. on startup */ +/* mypid is read-only after the initial fork() */ +static int restart = 0; /* do restart (config read) - multithread safe */ + +int glblHadMemShortage = 0; /* indicates if we had memory shortage some time during the run */ + + +static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be + * parsed inside message - rgerhards, 2006-03-13 */ +static int bFinished = 0; /* used by termination signal handler, read-only except there + * is either 0 or the number of the signal that requested the + * termination. + */ + +/* Intervals at which we flush out "message repeated" messages, + * in seconds after previous message is logged. After each flush, + * we move to the next interval until we reach the largest. + * TODO: this shall go into action object! -- rgerhards, 2008-01-29 + */ +int repeatinterval[2] = { 30, 60 }; /* # of secs before flush */ + +#define LIST_DELIMITER ':' /* delimiter between two hosts */ + +struct filed *Files = NULL; /* read-only after init() (but beware of sigusr1!) */ + +static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */ + +typedef struct legacyOptsLL_s { + uchar *line; + struct legacyOptsLL_s *next; +} legacyOptsLL_t; +legacyOptsLL_t *pLegacyOptsLL = NULL; + +/* global variables for config file state */ +static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */ +int iCompatibilityMode = 0; /* version we should be compatible with; 0 means sysklogd. It is + the default, so if no -c option is given, we make ourselvs + as compatible to sysklogd as possible. */ +static int bDebugPrintTemplateList = 1;/* output template list in debug mode? */ +static int bDebugPrintCfSysLineHandlerList = 1;/* output cfsyslinehandler list in debug mode? */ +static int bDebugPrintModuleList = 1;/* output module list in debug mode? */ +int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ +static uchar cCCEscapeChar = '\\';/* character to be used to start an escape sequence for control chars */ +static int bEscapeCCOnRcv = 1; /* escape control characters on reception: 0 - no, 1 - yes */ +int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ +int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ +int iActExecOnceInterval = 0; /* execute action once every nn seconds */ +uchar *pszWorkDir = NULL;/* name of rsyslog's spool directory (without trailing slash) */ +uchar *glblModPath = NULL; /* module load path - only used during initial init, only settable via -M command line option */ +/* end global config file state variables */ + +uchar *LocalHostName;/* our hostname - read-only after startup */ +char *LocalDomain; /* our local domain name - read-only after startup */ +int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ +int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */ +int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ +static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */ +int DisableDNS = 0; /* don't look up IP addresses of remote messages */ +char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ +char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ +static int bHaveMainQueue = 0;/* set to 1 if the main queue - in queueing mode - is available + * If the main queue is either not yet ready or not running in + * queueing mode (mode DIRECT!), then this is set to 0. + */ + +extern int errno; + +/* main message queue and its configuration parameters */ +static queue_t *pMsgQueue = NULL; /* the main message queue */ +static int iMainMsgQueueSize = 10000; /* size of the main message queue above */ +static int iMainMsgQHighWtrMark = 8000; /* high water mark for disk-assisted queues */ +static int iMainMsgQLowWtrMark = 2000; /* low water mark for disk-assisted queues */ +static int iMainMsgQDiscardMark = 9800; /* begin to discard messages */ +static int iMainMsgQDiscardSeverity = 8; /* by default, discard nothing to prevent unintentional loss */ +static int iMainMsgQueueNumWorkers = 1; /* number of worker threads for the mm queue above */ +static queueType_t MainMsgQueType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */ +static uchar *pszMainMsgQFName = NULL; /* prefix for the main message queue file */ +static int64 iMainMsgQueMaxFileSize = 1024*1024; +static int iMainMsgQPersistUpdCnt = 0; /* persist queue info every n updates */ +static int iMainMsgQtoQShutdown = 0; /* queue shutdown */ +static int iMainMsgQtoActShutdown = 1000; /* action shutdown (in phase 2) */ +static int iMainMsgQtoEnq = 2000; /* timeout for queue enque */ +static int iMainMsgQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */ +static int iMainMsgQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */ +static int iMainMsgQDeqSlowdown = 0; /* dequeue slowdown (simple rate limiting) */ +static int64 iMainMsgQueMaxDiskSpace = 0; /* max disk space allocated 0 ==> unlimited */ +static int bMainMsgQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */ +static int iMainMsgQueueDeqtWinFromHr = 0; /* hour begin of time frame when queue is to be dequeued */ +static int iMainMsgQueueDeqtWinToHr = 25; /* hour begin of time frame when queue is to be dequeued */ + + +/* support for simple textual representation of FIOP names + * rgerhards, 2005-09-27 + */ +static char* getFIOPName(unsigned iFIOP) +{ + char *pRet; + switch(iFIOP) { + case FIOP_CONTAINS: + pRet = "contains"; + break; + case FIOP_ISEQUAL: + pRet = "isequal"; + break; + case FIOP_STARTSWITH: + pRet = "startswith"; + break; + case FIOP_REGEX: + pRet = "regex"; + break; + default: + pRet = "NOP"; + break; + } + return pRet; +} + + +/* Reset config variables to default values. + * rgerhards, 2007-07-17 + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + cCCEscapeChar = '#'; + bActExecWhenPrevSusp = 0; + iActExecOnceInterval = 0; + bDebugPrintTemplateList = 1; + bDebugPrintCfSysLineHandlerList = 1; + bDebugPrintModuleList = 1; + bEscapeCCOnRcv = 1; /* default is to escape control characters */ + bReduceRepeatMsgs = 0; + bDropMalPTRMsgs = 0; + if(pszWorkDir != NULL) { + free(pszWorkDir); + pszWorkDir = NULL; + } + if(pszMainMsgQFName != NULL) { + free(pszMainMsgQFName); + pszMainMsgQFName = NULL; + } + iMainMsgQueueSize = 10000; + iMainMsgQHighWtrMark = 8000; + iMainMsgQLowWtrMark = 2000; + iMainMsgQDiscardMark = 9800; + iMainMsgQDiscardSeverity = 4; + iMainMsgQueMaxFileSize = 1024 * 1024; + iMainMsgQueueNumWorkers = 1; + iMainMsgQPersistUpdCnt = 0; + iMainMsgQtoQShutdown = 0; + iMainMsgQtoActShutdown = 1000; + iMainMsgQtoEnq = 2000; + iMainMsgQtoWrkShutdown = 60000; + iMainMsgQWrkMinMsgs = 100; + iMainMsgQDeqSlowdown = 0; + bMainMsgQSaveOnShutdown = 1; + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + iMainMsgQueMaxDiskSpace = 0; + glbliActionResumeRetryCount = 0; + + return RS_RET_OK; +} + + + +int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ + + +/* hardcoded standard templates (used for defaults) */ +static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\""; +static uchar template_TraditionalFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n\""; +static uchar template_FileFormat[] = "\"%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::drop-last-lf%\n\""; +static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\""; +static uchar template_ForwardFormat[] = "\"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg%\""; +static uchar template_TraditionalForwardFormat[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg%\""; +static uchar template_StdUsrMsgFmt[] = "\" %syslogtag%%msg%\n\r\""; +static uchar template_StdDBFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')\",SQL"; +static uchar template_StdPgSQLFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-pgsql%', '%timegenerated:::date-pgsql%', %iut%, '%syslogtag%')\",STDSQL"; +/* end template */ + + +/* up to the next comment, prototypes that should be removed by reordering */ +/* Function prototypes. */ +static char **crunch_list(char *list); +static void reapchild(); +static void debug_switch(); +static void sighup_handler(); +static void freeSelectors(void); +static void processImInternal(void); + + +static int usage(void) +{ + fprintf(stderr, "usage: rsyslogd [-cversion] [-46AdnqQvwx] [-lhostlist] [-sdomainlist]\n" + " [-fconffile] [-ipidfile]\n" + "To run rsyslogd in native mode, use \"rsyslogd -c3 \"\n\n" + "For further information see http://www.rsyslog.com/doc\n"); + exit(1); /* "good" exit - done to terminate usage() */ +} + + +/* function to destruct a selector_t object + * rgerhards, 2007-08-01 + */ +rsRetVal +selectorDestruct(void *pVal) +{ + selector_t *pThis = (selector_t *) pVal; + + assert(pThis != NULL); + + if(pThis->pCSHostnameComp != NULL) + rsCStrDestruct(&pThis->pCSHostnameComp); + if(pThis->pCSProgNameComp != NULL) + rsCStrDestruct(&pThis->pCSProgNameComp); + + if(pThis->f_filter_type == FILTER_PROP) { + if(pThis->f_filterData.prop.pCSPropName != NULL) + rsCStrDestruct(&pThis->f_filterData.prop.pCSPropName); + if(pThis->f_filterData.prop.pCSCompValue != NULL) + rsCStrDestruct(&pThis->f_filterData.prop.pCSCompValue); + } else if(pThis->f_filter_type == FILTER_EXPR) { + if(pThis->f_filterData.f_expr != NULL) + expr.Destruct(&pThis->f_filterData.f_expr); + } + + llDestroy(&pThis->llActList); + free(pThis); + + return RS_RET_OK; +} + + +/* function to construct a selector_t object + * rgerhards, 2007-08-01 + */ +rsRetVal +selectorConstruct(selector_t **ppThis) +{ + DEFiRet; + selector_t *pThis; + + assert(ppThis != NULL); + + if((pThis = (selector_t*) calloc(1, sizeof(selector_t))) == NULL) { + glblHadMemShortage = 1; + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + CHKiRet(llInit(&pThis->llActList, actionDestruct, NULL, NULL)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis != NULL) { + selectorDestruct(pThis); + } + } + *ppThis = pThis; + RETiRet; +} + + +/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So + * it is never called once rsyslogd is running (not even when HUPed). This code + * contains some exits, but they are considered safe because they only happen + * during startup. Anyhow, when we review the code here, we might want to + * reconsider the exit()s. + */ +static char **crunch_list(char *list) +{ + int count, i; + char *p, *q; + char **result = NULL; + + p = list; + + /* strip off trailing delimiters */ + while (p[strlen(p)-1] == LIST_DELIMITER) { + count--; + p[strlen(p)-1] = '\0'; + } + /* cut off leading delimiters */ + while (p[0] == LIST_DELIMITER) { + count--; + p++; + } + + /* count delimiters to calculate elements */ + for (count=i=0; p[i]; i++) + if (p[i] == LIST_DELIMITER) count++; + + if ((result = (char **)malloc(sizeof(char *) * (count+2))) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + + /* + * We now can assume that the first and last + * characters are different from any delimiters, + * so we don't have to care about this. + */ + count = 0; + while ((q=strchr(p, LIST_DELIMITER))) { + result[count] = (char *) malloc((q - p + 1) * sizeof(char)); + if (result[count] == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + strncpy(result[count], p, q - p); + result[count][q - p] = '\0'; + p = q; p++; + count++; + } + if ((result[count] = \ + (char *)malloc(sizeof(char) * strlen(p) + 1)) == NULL) { + printf ("Sorry, can't get enough memory, exiting.\n"); + exit(0); /* safe exit, because only called during startup */ + } + strcpy(result[count],p); + result[++count] = NULL; + +#if 0 + count=0; + while (result[count]) + dbgprintf("#%d: %s\n", count, StripDomains[count++]); +#endif + return result; +} + + +void untty(void) +#ifdef HAVE_SETSID +{ + if ( !Debug ) { + setsid(); + } + return; +} +#else +{ + int i; + + if ( !Debug ) { + i = open(_PATH_TTY, O_RDWR); + if (i >= 0) { +# if !defined(__hpux) + (void) ioctl(i, (int) TIOCNOTTY, (char *)0); +# else + /* TODO: we need to implement something for HP UX! -- rgerhards, 2008-03-04 */ + /* actually, HP UX should have setsid, so the code directly above should + * trigger. So the actual question is why it doesn't do that... + */ +# endif + (void) close(i); + } + } +} +#endif + + +/* Take a raw input line, decode the message, and print the message + * on the appropriate log files. + * rgerhards 2004-11-08: Please note + * that this function does only a partial decoding. At best, it splits + * the PRI part. No further decode happens. The rest is done in + * logmsg(). + * Added the iSource parameter so that we know if we have to parse + * HOSTNAME or not. rgerhards 2004-11-16. + * changed parameter iSource to bParseHost. For details, see comment in + * printchopped(). rgerhards 2005-10-06 + * rgerhards: 2008-03-06: added "flags" to allow an input module to specify + * flags, most importantly to request ignoring the messages' timestamp. + * + * rgerhards, 2008-03-19: + * I added an additional calling parameter to permit specifying the flow + * control capability of the source. + */ +rsRetVal printline(char *hname, char *msg, int bParseHost, int flags, flowControl_t flowCtlType) +{ + DEFiRet; + register char *p; + int pri; + msg_t *pMsg; + + /* Now it is time to create the message object (rgerhards) + */ + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, flowCtlType); + MsgSetRawMsg(pMsg, msg); + + pMsg->bParseHOSTNAME = bParseHost; + /* test for special codes */ + pri = DEFUPRI; + p = msg; + if (*p == '<') { + pri = 0; + while (isdigit((int) *++p)) + { + pri = 10 * pri + (*p - '0'); + } + if (*p == '>') + ++p; + } + if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) + pri = DEFUPRI; + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + + /* Now we look at the HOSTNAME. That is a bit complicated... + * If we have a locally received message, it does NOT + * contain any hostname information in the message itself. + * As such, the HOSTNAME is the same as the system that + * the message was received from (that, for obvious reasons, + * being the local host). rgerhards 2004-11-16 + */ + if(bParseHost == 0) + MsgSetHOSTNAME(pMsg, hname); + MsgSetRcvFrom(pMsg, hname); + + /* rgerhards 2004-11-19: well, well... we've now seen that we + * have the "hostname problem" also with the traditional Unix + * message. As we like to emulate it, we need to add the hostname + * to it. + */ + if(MsgSetUxTradMsg(pMsg, p) != 0) + ABORT_FINALIZE(RS_RET_ERR); + + logmsg(pMsg, flags | SYNC_FILE); + +finalize_it: + RETiRet; +} + + +/* This takes a received message that must be decoded and submits it to + * the main message queue. The function calls the necessary parser. + * + * rgerhards, 2006-11-30: I have greatly changed this function. Formerly, + * it tried to reassemble multi-part messages, which is a legacy stock + * sysklogd concept. In essence, that was that messages not ending with + * \0 were glued together. As far as I can see, this is a sysklogd + * specific feature and, from looking at the code, seems to be used + * pretty seldom (if at all). I remove this now, not the least because it is totally + * incompatible with upcoming IETF syslog standards. If you experience + * strange behaviour with messages beeing split across multiple lines, + * this function here might be the place to look at. + * + * Some previous history worth noting: + * I added the "iSource" parameter. This is needed to distinguish between + * messages that have a hostname in them (received from the internet) and + * those that do not have (most prominently /dev/log). rgerhards 2004-11-16 + * And now I removed the "iSource" parameter and changed it to be "bParseHost", + * because all that it actually controls is whether the host is parsed or not. + * For rfc3195 support, we needed to modify the algo for host parsing, so we can + * no longer rely just on the source (rfc3195d forwarded messages arrive via + * unix domain sockets but contain the hostname). rgerhards, 2005-10-06 + * + * rgerhards, 2008-02-18: + * This function was previously called "printchopped"() and has been renamed + * as part of the effort to create a clean internal message submission interface. + * It also has been adopted to our usual calling interface, but currently does + * not provide any useful return states. But we now have the hook and things can + * improve in the future. <-- TODO! + * + * rgerhards, 2008-03-19: + * I added an additional calling parameter to permit specifying the flow + * control capability of the source. + */ +rsRetVal +parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType) +{ + DEFiRet; + register int iMsg; + char *pMsg; + char *pData; + char *pEnd; + char tmpline[MAXLINE + 1]; +# ifdef USE_NETZIP + char deflateBuf[MAXLINE + 1]; + uLongf iLenDefBuf; +# endif + + assert(hname != NULL); + assert(msg != NULL); + assert(len >= 0); + + /* we first check if we have a NUL character at the very end of the + * message. This seems to be a frequent problem with a number of senders. + * So I have now decided to drop these NULs. However, if they are intentional, + * that may cause us some problems, e.g. with syslog-sign. On the other hand, + * current code always has problems with intentional NULs (as it needs to escape + * them to prevent problems with the C string libraries), so that does not + * really matter. Just to be on the save side, we'll log destruction of such + * NULs in the debug log. + * rgerhards, 2007-09-14 + */ + if(*(msg + len - 1) == '\0') { + dbgprintf("dropped NUL at very end of message\n"); + len--; + } + + /* then we check if we need to drop trailing LFs, which often make + * their way into syslog messages unintentionally. In order to remain + * compatible to recent IETF developments, we allow the user to + * turn on/off this handling. rgerhards, 2007-07-23 + */ + if(bDropTrailingLF && *(msg + len - 1) == '\n') { + dbgprintf("dropped LF at very end of message (DropTrailingLF is set)\n"); + len--; + } + + iMsg = 0; /* initialize receiving buffer index */ + pMsg = tmpline; /* set receiving buffer pointer */ + pData = msg; /* set source buffer pointer */ + pEnd = msg + len; /* this is one off, which is intensional */ + +# ifdef USE_NETZIP + /* we first need to check if we have a compressed record. If so, + * we must decompress it. + */ + if(len > 0 && *msg == 'z') { /* compressed data present? (do NOT change order if conditions!) */ + /* we have compressed data, so let's deflate it. We support a maximum + * message size of MAXLINE. If it is larger, an error message is logged + * and the message is dropped. We do NOT try to decompress larger messages + * as such might be used for denial of service. It might happen to later + * builds that such functionality be added as an optional, operator-configurable + * feature. + */ + int ret; + iLenDefBuf = MAXLINE; + ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) msg+1, len-1); + dbgprintf("Compressed message uncompressed with status %d, length: new %ld, old %d.\n", + ret, (long) iLenDefBuf, len-1); + /* Now check if the uncompression worked. If not, there is not much we can do. In + * that case, we log an error message but ignore the message itself. Storing the + * compressed text is dangerous, as it contains control characters. So we do + * not do this. If someone would like to have a copy, this code here could be + * modified to do a hex-dump of the buffer in question. We do not include + * this functionality right now. + * rgerhards, 2006-12-07 + */ + if(ret != Z_OK) { + errmsg.LogError(NO_ERRCODE, "Uncompression of a message failed with return code %d " + "- enable debug logging if you need further information. " + "Message ignored.", ret); + FINALIZE; /* unconditional exit, nothing left to do... */ + } + pData = deflateBuf; + pEnd = deflateBuf + iLenDefBuf; + } +# else /* ifdef USE_NETZIP */ + /* in this case, we still need to check if the message is compressed. If so, we must + * tell the user we can not accept it. + */ + if(len > 0 && *msg == 'z') { + errmsg.LogError(NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression " + "support enabled. The message will be ignored."); + FINALIZE; + } +# endif /* ifdef USE_NETZIP */ + + while(pData < pEnd) { + if(iMsg >= MAXLINE) { + /* emergency, we now need to flush, no matter if + * we are at end of message or not... + */ + if(iMsg == MAXLINE) { + *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ + printline(hname, tmpline, bParseHost, flags, flowCtlType); + } else { + /* This case in theory never can happen. If it happens, we have + * a logic error. I am checking for it, because if I would not, + * we would address memory invalidly with the code above. I + * do not care much about this case, just a debug log entry + * (I couldn't do any more smart things anyway...). + * rgerhards, 2007-9-20 + */ + dbgprintf("internal error: iMsg > MAXLINE in printchopped()\n"); + } + FINALIZE; /* in this case, we are done... nothing left we can do */ + } + if(*pData == '\0') { /* guard against \0 characters... */ + /* changed to the sequence (somewhat) proposed in + * draft-ietf-syslog-protocol-19. rgerhards, 2006-11-30 + */ + if(iMsg + 3 < MAXLINE) { /* do we have space? */ + *(pMsg + iMsg++) = cCCEscapeChar; + *(pMsg + iMsg++) = '0'; + *(pMsg + iMsg++) = '0'; + *(pMsg + iMsg++) = '0'; + } /* if we do not have space, we simply ignore the '\0'... */ + /* log an error? Very questionable... rgerhards, 2006-11-30 */ + /* decided: we do not log an error, it won't help... rger, 2007-06-21 */ + ++pData; + } else if(bEscapeCCOnRcv && iscntrl((int) *pData)) { + /* we are configured to escape control characters. Please note + * that this most probably break non-western character sets like + * Japanese, Korean or Chinese. rgerhards, 2007-07-17 + * Note: sysklogd logs octal values only for DEL and CCs above 127. + * For others, it logs ^n where n is the control char converted to an + * alphabet character. We like consistency and thus escape it to octal + * in all cases. If someone complains, we may change the mode. At least + * we known now what's going on. + * rgerhards, 2007-07-17 + */ + if(iMsg + 3 < MAXLINE) { /* do we have space? */ + *(pMsg + iMsg++) = cCCEscapeChar; + *(pMsg + iMsg++) = '0' + ((*pData & 0300) >> 6); + *(pMsg + iMsg++) = '0' + ((*pData & 0070) >> 3); + *(pMsg + iMsg++) = '0' + ((*pData & 0007)); + } /* again, if we do not have space, we ignore the char - see comment at '\0' */ + ++pData; + } else { + *(pMsg + iMsg++) = *pData++; + } + } + + *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */ + + /* typically, we should end up here! */ + printline(hname, tmpline, bParseHost, flags, flowCtlType); + +finalize_it: + RETiRet; +} + +/* rgerhards 2004-11-09: the following is a function that can be used + * to log a message orginating from the syslogd itself. In sysklogd code, + * this is done by simply calling logmsg(). However, logmsg() is changed in + * rsyslog so that it takes a msg "object". So it can no longer be called + * directly. This method here solves the need. It provides an interface that + * allows to construct a locally-generated message. Please note that this + * function here probably is only an interim solution and that we need to + * think on the best way to do this. + */ +rsRetVal +logmsgInternal(int pri, char *msg, int flags) +{ + DEFiRet; + msg_t *pMsg; + + CHKiRet(msgConstruct(&pMsg)); + MsgSetUxTradMsg(pMsg, msg); + MsgSetRawMsg(pMsg, msg); + MsgSetHOSTNAME(pMsg, (char*)LocalHostName); + MsgSetRcvFrom(pMsg, (char*)LocalHostName); + MsgSetTAG(pMsg, "rsyslogd:"); + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + pMsg->bParseHOSTNAME = 0; + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + flags |= INTERNAL_MSG; + + if(bHaveMainQueue == 0) { /* not yet in queued mode */ + iminternalAddMsg(pri, pMsg, flags); + } else { + /* we have the queue, so we can simply provide the + * message to the queue engine. + */ + logmsg(pMsg, flags); + } +finalize_it: + RETiRet; +} + +/* This functions looks at the given message and checks if it matches the + * provided filter condition. If so, it returns true, else it returns + * false. This is a helper to logmsg() and meant to drive the decision + * process if a message is to be processed or not. As I expect this + * decision code to grow more complex over time AND logmsg() is already + * a very lengthy function, I thought a separate function is more appropriate. + * 2005-09-19 rgerhards + * 2008-02-25 rgerhards: changed interface, now utilizes iRet, bProcessMsg + * returns is message should be procesed. + */ +static rsRetVal shouldProcessThisMessage(selector_t *f, msg_t *pMsg, int *bProcessMsg) +{ + DEFiRet; + unsigned short pbMustBeFreed; + char *pszPropVal; + int bRet = 0; + vm_t *pVM = NULL; + var_t *pResult = NULL; + + assert(f != NULL); + assert(pMsg != NULL); + + /* we first have a look at the global, BSD-style block filters (for tag + * and host). Only if they match, we evaluate the actual filter. + * rgerhards, 2005-10-18 + */ + if(f->eHostnameCmpMode == HN_NO_COMP) { + /* EMPTY BY INTENSION - we check this value first, because + * it is the one most often used, so this saves us time! + */ + } else if(f->eHostnameCmpMode == HN_COMP_MATCH) { + if(rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { + /* not equal, so we are already done... */ + dbgprintf("hostname filter '+%s' does not match '%s'\n", + rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); + FINALIZE; + } + } else { /* must be -hostname */ + if(!rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { + /* not equal, so we are already done... */ + dbgprintf("hostname filter '-%s' does not match '%s'\n", + rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg)); + FINALIZE; + } + } + + if(f->pCSProgNameComp != NULL) { + int bInv = 0, bEqv = 0, offset = 0; + if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp)) == '-') { + if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp) + 1) == '-') + offset = 1; + else { + bInv = 1; + offset = 1; + } + } + if(!rsCStrOffsetSzStrCmp(f->pCSProgNameComp, offset, (uchar*) getProgramName(pMsg), getProgramNameLen(pMsg))) + bEqv = 1; + + if((!bEqv && !bInv) || (bEqv && bInv)) { + /* not equal or inverted selection, so we are already done... */ + dbgprintf("programname filter '%s' does not match '%s'\n", + rsCStrGetSzStrNoNULL(f->pCSProgNameComp), getProgramName(pMsg)); + FINALIZE; + } + } + + /* done with the BSD-style block filters */ + + if(f->f_filter_type == FILTER_PRI) { + /* skip messages that are incorrect priority */ + if ( (f->f_filterData.f_pmask[pMsg->iFacility] == TABLE_NOPRI) || \ + ((f->f_filterData.f_pmask[pMsg->iFacility] & (1<iSeverity)) == 0) ) + bRet = 0; + else + bRet = 1; + } else if(f->f_filter_type == FILTER_EXPR) { + CHKiRet(vm.Construct(&pVM)); + CHKiRet(vm.ConstructFinalize(pVM)); + CHKiRet(vm.SetMsg(pVM, pMsg)); + CHKiRet(vm.ExecProg(pVM, f->f_filterData.f_expr->pVmprg)); + CHKiRet(vm.PopBoolFromStack(pVM, &pResult)); + dbgprintf("result of expression evaluation: %lld\n", pResult->val.num); + /* VM is destructed on function exit */ + bRet = (pResult->val.num) ? 1 : 0; + } else { + assert(f->f_filter_type == FILTER_PROP); /* assert() just in case... */ + pszPropVal = MsgGetProp(pMsg, NULL, f->f_filterData.prop.pCSPropName, &pbMustBeFreed); + + /* Now do the compares (short list currently ;)) */ + switch(f->f_filterData.prop.operation ) { + case FIOP_CONTAINS: + if(rsCStrLocateInSzStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal) != -1) + bRet = 1; + break; + case FIOP_ISEQUAL: + if(rsCStrSzStrCmp(f->f_filterData.prop.pCSCompValue, + (uchar*) pszPropVal, strlen(pszPropVal)) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_STARTSWITH: + if(rsCStrSzStrStartsWithCStr(f->f_filterData.prop.pCSCompValue, + (uchar*) pszPropVal, strlen(pszPropVal)) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_REGEX: + if(rsCStrSzStrMatchRegex(f->f_filterData.prop.pCSCompValue, + (unsigned char*) pszPropVal) == 0) + bRet = 1; + break; + default: + /* here, it handles NOP (for performance reasons) */ + assert(f->f_filterData.prop.operation == FIOP_NOP); + bRet = 1; /* as good as any other default ;) */ + break; + } + + /* now check if the value must be negated */ + if(f->f_filterData.prop.isNegated) + bRet = (bRet == 1) ? 0 : 1; + + if(Debug) { + dbgprintf("Filter: check for property '%s' (value '%s') ", + rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName), + pszPropVal); + if(f->f_filterData.prop.isNegated) + dbgprintf("NOT "); + dbgprintf("%s '%s': %s\n", + getFIOPName(f->f_filterData.prop.operation), + rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue), + bRet ? "TRUE" : "FALSE"); + } + + /* cleanup */ + if(pbMustBeFreed) + free(pszPropVal); + } + +finalize_it: + /* destruct in any case, not just on error, but it makes error handling much easier */ + if(pVM != NULL) + vm.Destruct(&pVM); + + if(pResult != NULL) + var.Destruct(&pResult); + + *bProcessMsg = bRet; + RETiRet; +} + + +/* helper to processMsg(), used to call the configured actions. It is + * executed from within llExecFunc() of the action list. + * rgerhards, 2007-08-02 + */ +typedef struct processMsgDoActions_s { + int bPrevWasSuspended; /* was the previous action suspended? */ + msg_t *pMsg; +} processMsgDoActions_t; +DEFFUNC_llExecFunc(processMsgDoActions) +{ + DEFiRet; + rsRetVal iRetMod; /* return value of module - we do not always pass that back */ + action_t *pAction = (action_t*) pData; + processMsgDoActions_t *pDoActData = (processMsgDoActions_t*) pParam; + + assert(pAction != NULL); + + if((pAction->bExecWhenPrevSusp == 1) && (pDoActData->bPrevWasSuspended == 0)) { + dbgprintf("not calling action because the previous one is not suspended\n"); + ABORT_FINALIZE(RS_RET_OK); + } + + iRetMod = actionCallAction(pAction, pDoActData->pMsg); + if(iRetMod == RS_RET_DISCARDMSG) { + ABORT_FINALIZE(RS_RET_DISCARDMSG); + } else if(iRetMod == RS_RET_SUSPENDED) { + /* indicate suspension for next module to be called */ + pDoActData->bPrevWasSuspended = 1; + } else { + pDoActData->bPrevWasSuspended = 0; + } + +finalize_it: + RETiRet; +} + + +/* Process (consume) a received message. Calls the actions configured. + * rgerhards, 2005-10-13 + */ +static void +processMsg(msg_t *pMsg) +{ + selector_t *f; + int bContinue; + int bProcessMsg; + processMsgDoActions_t DoActData; + rsRetVal iRet; + + BEGINfunc + assert(pMsg != NULL); + + /* log the message to the particular outputs */ + + bContinue = 1; + for (f = Files; f != NULL && bContinue ; f = f->f_next) { + /* first check the filters... */ + iRet = shouldProcessThisMessage(f, pMsg, &bProcessMsg); + if(!bProcessMsg) { + continue; + } + + /* ok -- from here, we have action-specific code, nothing really selector-specific -- rger 2007-08-01 */ + DoActData.pMsg = pMsg; + DoActData.bPrevWasSuspended = 0; + if(llExecFunc(&f->llActList, processMsgDoActions, (void*)&DoActData) == RS_RET_DISCARDMSG) + bContinue = 0; + } + ENDfunc +} + + +/* The consumer of dequeued messages. This function is called by the + * queue engine on dequeueing of a message. It runs on a SEPARATE + * THREAD. + * NOTE: Having more than one worker requires guarding of some + * message object structures and potentially others - need to be checked + * before we support multiple worker threads on the message queue. + * Please note: the message object is destructed by the queue itself! + */ +static rsRetVal +msgConsumer(void __attribute__((unused)) *notNeeded, void *pUsr) +{ + DEFiRet; + msg_t *pMsg = (msg_t*) pUsr; + + assert(pMsg != NULL); + + processMsg(pMsg); + msgDestruct(&pMsg); + + RETiRet; +} + + +/* Helper to parseRFCSyslogMsg. This function parses a field up to + * (and including) the SP character after it. The field contents is + * returned in a caller-provided buffer. The parsepointer is advanced + * to after the terminating SP. The caller must ensure that the + * provided buffer is large enough to hold the to be extracted value. + * Returns 0 if everything is fine or 1 if either the field is not + * SP-terminated or any other error occurs. + * rger, 2005-11-24 + */ +static int parseRFCField(char **pp2parse, char *pResult) +{ + char *p2parse; + int iRet = 0; + + assert(pp2parse != NULL); + assert(*pp2parse != NULL); + assert(pResult != NULL); + + p2parse = *pp2parse; + + /* this is the actual parsing loop */ + while(*p2parse && *p2parse != ' ') { + *pResult++ = *p2parse++; + } + + if(*p2parse == ' ') + ++p2parse; /* eat SP, but only if not at end of string */ + else + iRet = 1; /* there MUST be an SP! */ + *pResult = '\0'; + + /* set the new parse pointer */ + *pp2parse = p2parse; + return 0; +} + + +/* Helper to parseRFCSyslogMsg. This function parses the structured + * data field of a message. It does NOT parse inside structured data, + * just gets the field as whole. Parsing the single entities is left + * to other functions. The parsepointer is advanced + * to after the terminating SP. The caller must ensure that the + * provided buffer is large enough to hold the to be extracted value. + * Returns 0 if everything is fine or 1 if either the field is not + * SP-terminated or any other error occurs. + * rger, 2005-11-24 + */ +static int parseRFCStructuredData(char **pp2parse, char *pResult) +{ + char *p2parse; + int bCont = 1; + int iRet = 0; + + assert(pp2parse != NULL); + assert(*pp2parse != NULL); + assert(pResult != NULL); + + p2parse = *pp2parse; + + /* this is the actual parsing loop + * Remeber: structured data starts with [ and includes any characters + * until the first ] followed by a SP. There may be spaces inside + * structured data. There may also be \] inside the structured data, which + * do NOT terminate an element. + */ + if(*p2parse != '[') + return 1; /* this is NOT structured data! */ + + while(bCont) { + if(*p2parse == '\0') { + iRet = 1; /* this is not valid! */ + bCont = 0; + } else if(*p2parse == '\\' && *(p2parse+1) == ']') { + /* this is escaped, need to copy both */ + *pResult++ = *p2parse++; + *pResult++ = *p2parse++; + } else if(*p2parse == ']' && *(p2parse+1) == ' ') { + /* found end, just need to copy the ] and eat the SP */ + *pResult++ = *p2parse; + p2parse += 2; + bCont = 0; + } else { + *pResult++ = *p2parse++; + } + } + + if(*p2parse == ' ') + ++p2parse; /* eat SP, but only if not at end of string */ + else + iRet = 1; /* there MUST be an SP! */ + *pResult = '\0'; + + /* set the new parse pointer */ + *pp2parse = p2parse; + return 0; +} + +/* parse a RFC-formatted syslog message. This function returns + * 0 if processing of the message shall continue and 1 if something + * went wrong and this messe should be ignored. This function has been + * implemented in the effort to support syslog-protocol. Please note that + * the name (parse *RFC*) stems from the hope that syslog-protocol will + * some time become an RFC. Do not confuse this with informational + * RFC 3164 (which is legacy syslog). + * + * currently supported format: + * + * VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG + * + * is already stripped when this function is entered. VERSION already + * has been confirmed to be "1", but has NOT been stripped from the message. + * + * rger, 2005-11-24 + */ +static int parseRFCSyslogMsg(msg_t *pMsg, int flags) +{ + char *p2parse; + char *pBuf; + int bContParse = 1; + + assert(pMsg != NULL); + assert(pMsg->pszUxTradMsg != NULL); + p2parse = (char*) pMsg->pszUxTradMsg; + + /* do a sanity check on the version and eat it */ + assert(p2parse[0] == '1' && p2parse[1] == ' '); + p2parse += 2; + + /* Now get us some memory we can use as a work buffer while parsing. + * We simply allocated a buffer sufficiently large to hold all of the + * message, so we can not run into any troubles. I think this is + * more wise then to use individual buffers. + */ + if((pBuf = malloc(sizeof(char)* strlen(p2parse) + 1)) == NULL) + return 1; + + /* IMPORTANT NOTE: + * Validation is not actually done below nor are any errors handled. I have + * NOT included this for the current proof of concept. However, it is strongly + * advisable to add it when this code actually goes into production. + * rgerhards, 2005-11-24 + */ + + /* TIMESTAMP */ + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) { + dbgprintf("no TIMESTAMP detected!\n"); + bContParse = 0; + flags |= ADDDATE; + } + + if (flags & ADDDATE) { + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + } + + /* HOSTNAME */ + if(bContParse) { + parseRFCField(&p2parse, pBuf); + MsgSetHOSTNAME(pMsg, pBuf); + } else { + /* we can not parse, so we get the system we + * received the data from. + */ + MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); + } + + /* APP-NAME */ + if(bContParse) { + parseRFCField(&p2parse, pBuf); + MsgSetAPPNAME(pMsg, pBuf); + } + + /* PROCID */ + if(bContParse) { + parseRFCField(&p2parse, pBuf); + MsgSetPROCID(pMsg, pBuf); + } + + /* MSGID */ + if(bContParse) { + parseRFCField(&p2parse, pBuf); + MsgSetMSGID(pMsg, pBuf); + } + + /* STRUCTURED-DATA */ + if(bContParse) { + parseRFCStructuredData(&p2parse, pBuf); + MsgSetStructuredData(pMsg, pBuf); + } + + /* MSG */ + MsgSetMSG(pMsg, p2parse); + + free(pBuf); + return 0; /* all ok */ +} + + +/* parse a legay-formatted syslog message. This function returns + * 0 if processing of the message shall continue and 1 if something + * went wrong and this messe should be ignored. This function has been + * implemented in the effort to support syslog-protocol. + * rger, 2005-11-24 + * As of 2006-01-10, I am removing the logic to continue parsing only + * when a valid TIMESTAMP is detected. Validity of other fields already + * is ignored. This is due to the fact that the parser has grown smarter + * and is now more able to understand different dialects of the syslog + * message format. I do not expect any bad side effects of this change, + * but I thought I log it in this comment. + * rgerhards, 2006-01-10 + */ +static int parseLegacySyslogMsg(msg_t *pMsg, int flags) +{ + char *p2parse; + char *pBuf; + char *pWork; + cstr_t *pStrB; + int iCnt; + int bTAGCharDetected; + + assert(pMsg != NULL); + assert(pMsg->pszUxTradMsg != NULL); + p2parse = (char*) pMsg->pszUxTradMsg; + + /* Check to see if msg contains a timestamp. We stary trying with a + * high-precision one... + */ + if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) { + /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */; + } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse) == TRUE) { + p2parse += 16; + } else if(*p2parse == ' ') { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */ + if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse+1) == TRUE) { + /* indeed, we got it! */ + p2parse += 17; + } else { + flags |= ADDDATE; + } + } else { + flags |= ADDDATE; + } + + /* here we need to check if the timestamp is valid. If it is not, + * we can not continue to parse but must treat the rest as the + * MSG part of the message (as of RFC 3164). + * rgerhards 2004-12-03 + */ + if(flags & ADDDATE) { + datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ + } + + /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we + * do this only when the user has not forbidden this. I now introduce some + * code that allows a user to configure rsyslogd to treat the rest of the + * message as MSG part completely. In this case, the hostname will be the + * machine that we received the message from and the tag will be empty. This + * is meant to be an interim solution, but for now it is in the code. + */ + if(bParseHOSTNAMEandTAG && !(flags & INTERNAL_MSG)) { + /* parse HOSTNAME - but only if this is network-received! + * rger, 2005-11-14: we still have a problem with BSD messages. These messages + * do NOT include a host name. In most cases, this leads to the TAG to be treated + * as hostname and the first word of the message as the TAG. Clearly, this is not + * of advantage ;) I think I have now found a way to handle this situation: there + * are certain characters which are frequently used in TAG (e.g. ':'), which are + * *invalid* in host names. So while parsing the hostname, I check for these characters. + * If I find them, I set a simple flag but continue. After parsing, I check the flag. + * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change + * the fields. I think this logic shall work with any type of syslog message. + */ + bTAGCharDetected = 0; + if(pMsg->bParseHOSTNAME) { + /* TODO: quick and dirty memory allocation */ + /* the memory allocated is far too much in most cases. But on the plus side, + * it is quite fast... - rgerhards, 2007-09-20 + */ + if((pBuf = malloc(sizeof(char)* (strlen(p2parse) +1))) == NULL) + return 1; + pWork = pBuf; + /* this is the actual parsing loop */ + while(*p2parse && *p2parse != ' ' && *p2parse != ':') { + if(*p2parse == '[' || *p2parse == ']' || *p2parse == '/') + bTAGCharDetected = 1; + *pWork++ = *p2parse++; + } + /* we need to handle ':' seperately, because it terminates the + * TAG - so we also need to terminate the parser here! + * rgerhards, 2007-09-10 *p2parse points to a valid address here in + * any case. We can reach this point only if we are at end of string, + * or we have a ':' or ' '. What the if below does is check if we are + * not at end of string and, if so, advance the parse pointer. If we + * are already at end of string, *p2parse is equal to '\0', neither if + * will be true and the parse pointer remain as is. This is perfectly + * well. + */ + if(*p2parse == ':') { + bTAGCharDetected = 1; + /* We will move hostname to tag, so preserve ':' (otherwise we + * will needlessly change the message format) */ + *pWork++ = *p2parse++; + } else if(*p2parse == ' ') + ++p2parse; + *pWork = '\0'; + MsgAssignHOSTNAME(pMsg, pBuf); + } + /* check if we seem to have a TAG */ + if(bTAGCharDetected) { + /* indeed, this smells like a TAG, so lets use it for this. We take + * the HOSTNAME from the sender system instead. + */ + dbgprintf("HOSTNAME contains invalid characters, assuming it to be a TAG.\n"); + moveHOSTNAMEtoTAG(pMsg); + MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); + } + + /* now parse TAG - that should be present in message from all sources. + * This code is somewhat not compliant with RFC 3164. As of 3164, + * the TAG field is ended by any non-alphanumeric character. In + * practice, however, the TAG often contains dashes and other things, + * which would end the TAG. So it is not desirable. As such, we only + * accept colon and SP to be terminators. Even there is a slight difference: + * a colon is PART of the TAG, while a SP is NOT part of the tag + * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character + * size limit (from RFC3164) on the tag. This had bad effects on existing + * envrionments, as sysklogd didn't obey it either (probably another bug + * in RFC3164...). We now receive the full size, but will modify the + * outputs so that only 32 characters max are used by default. + */ + /* The following code in general is quick & dirty - I need to get + * it going for a test, rgerhards 2004-11-16 */ + /* lol.. we tried to solve it, just to remind ourselfs that 32 octets + * is the max size ;) we need to shuffle the code again... Just for + * the records: the code is currently clean, but we could optimize it! */ + if(!bTAGCharDetected) { + uchar *pszTAG; + if(rsCStrConstruct(&pStrB) != RS_RET_OK) + return 1; + rsCStrSetAllocIncrement(pStrB, 33); + pWork = pBuf; + iCnt = 0; + while(*p2parse && *p2parse != ':' && *p2parse != ' ') { + rsCStrAppendChar(pStrB, *p2parse++); + ++iCnt; + } + if(*p2parse == ':') { + ++p2parse; + rsCStrAppendChar(pStrB, ':'); + } + rsCStrFinish(pStrB); + + rsCStrConvSzStrAndDestruct(pStrB, &pszTAG, 1); + if(pszTAG == NULL) + { /* rger, 2005-11-10: no TAG found - this implies that what + * we have considered to be the HOSTNAME is most probably the + * TAG. We consider it so probable, that we now adjust it + * that way. So we pick up the previously set hostname, assign + * it to tag and use the sender system (from IP stack) as + * the hostname. This situation is the standard case with + * stock BSD syslogd. + */ + dbgprintf("No TAG in message, assuming that HOSTNAME is missing.\n"); + moveHOSTNAMEtoTAG(pMsg); + MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); + } else { /* we have a TAG, so we can happily set it ;) */ + MsgAssignTAG(pMsg, pszTAG); + } + } else { + /* we have no TAG, so we ... */ + /*DO NOTHING*/; + } + } else { + /* we enter this code area when the user has instructed rsyslog NOT + * to parse HOSTNAME and TAG - rgerhards, 2006-03-13 + */ + if(!(flags & INTERNAL_MSG)) + { + dbgprintf("HOSTNAME and TAG not parsed by user configuraton.\n"); + MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg)); + } + } + + /* The rest is the actual MSG */ + MsgSetMSG(pMsg, p2parse); + + return 0; /* all ok */ +} + + +/* submit a fully created message to the main message queue. The message is + * fully processed and parsed, so no parsing at all happens. This is primarily + * a hook to prevent the need for callers to know about the main message queue + * (which may change in the future as we will probably have multiple rule + * sets and thus queues...). + * rgerhards, 2008-02-13 + */ +rsRetVal +submitMsg(msg_t *pMsg) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pMsg, msg); + + MsgPrepareEnqueue(pMsg); + queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); + + RETiRet; +} + + +/* + * Log a message to the appropriate log files, users, etc. based on + * the priority. + * rgerhards 2004-11-08: actually, this also decodes all but the PRI part. + * rgerhards 2004-11-09: ... but only, if syslogd could properly be initialized + * if not, we use emergency logging to the console and in + * this case, no further decoding happens. + * changed to no longer receive a plain message but a msg object instead. + * rgerhards-2004-11-16: OK, we are now up to another change... This method + * actually needs to PARSE the message. How exactly this needs to happen depends on + * a number of things. Most importantly, it depends on the source. For example, + * locally received messages (SOURCE_UNIXAF) do NOT have a hostname in them. So + * we need to treat them differntly form network-received messages which have. + * Well, actually not all network-received message really have a hostname. We + * can just hope they do, but we can not be sure. So this method tries to find + * whatever can be found in the message and uses that... Obviously, there is some + * potential for misinterpretation, which we simply can not solve under the + * circumstances given. + */ +void +logmsg(msg_t *pMsg, int flags) +{ + char *msg; + + BEGINfunc + assert(pMsg != NULL); + assert(pMsg->pszUxTradMsg != NULL); + msg = (char*) pMsg->pszUxTradMsg; + dbgprintf("logmsg: flags %x, from '%s', msg %s\n", flags, getRcvFrom(pMsg), msg); + + /* rger 2005-11-24 (happy thanksgiving!): we now need to check if we have + * a traditional syslog message or one formatted according to syslog-protocol. + * We need to apply different parsers depending on that. We use the + * -protocol VERSION field for the detection. + */ + if(msg[0] == '1' && msg[1] == ' ') { + dbgprintf("Message has syslog-protocol format.\n"); + setProtocolVersion(pMsg, 1); + if(parseRFCSyslogMsg(pMsg, flags) == 1) { + msgDestruct(&pMsg); + return; + } + } else { /* we have legacy syslog */ + dbgprintf("Message has legacy syslog format.\n"); + setProtocolVersion(pMsg, 0); + if(parseLegacySyslogMsg(pMsg, flags) == 1) { + msgDestruct(&pMsg); + return; + } + } + + /* ---------------------- END PARSING ---------------- */ + + /* now submit the message to the main queue - then we are done */ + pMsg->msgFlags = flags; + MsgPrepareEnqueue(pMsg); + queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg); + ENDfunc +} + + +static void +reapchild() +{ + int saved_errno = errno; + struct sigaction sigAct; + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = reapchild; + sigaction(SIGCHLD, &sigAct, NULL); /* reset signal handler -ASP */ + + while(waitpid(-1, NULL, WNOHANG) > 0); + errno = saved_errno; +} + + +/* helper to doFlushRptdMsgs() to flush the individual action links via llExecFunc + * rgerhards, 2007-08-02 + */ +DEFFUNC_llExecFunc(flushRptdMsgsActions) +{ + action_t *pAction = (action_t*) pData; + + assert(pAction != NULL); + + BEGINfunc + LockObj(pAction); + if (pAction->f_prevcount && time(NULL) >= REPEATTIME(pAction)) { + dbgprintf("flush %s: repeated %d times, %d sec.\n", + module.GetStateName(pAction->pMod), pAction->f_prevcount, + repeatinterval[pAction->f_repeatcount]); + actionWriteToAction(pAction); + BACKOFF(pAction); + } + UnlockObj(pAction); + + ENDfunc + return RS_RET_OK; /* we ignore errors, we can not do anything either way */ +} + + +/* This method flushes reapeat messages. + */ +static void +doFlushRptdMsgs(void) +{ + register selector_t *f; + + /* see if we need to flush any "message repeated n times"... + * Note that this interferes with objects running on other threads. + * We are using appropriate locking inside the function to handle that. + */ + for (f = Files; f != NULL ; f = f->f_next) { + llExecFunc(&f->llActList, flushRptdMsgsActions, NULL); + } +} + + +static void debug_switch() +{ + struct sigaction sigAct; + + if(debugging_on == 0) { + debugging_on = 1; + dbgprintf("Switching debugging_on to true\n"); + } else { + dbgprintf("Switching debugging_on to false\n"); + debugging_on = 0; + } + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = debug_switch; + sigaction(SIGUSR1, &sigAct, NULL); +} + + +void legacyOptsEnq(uchar *line) +{ + legacyOptsLL_t *pNew; + + pNew = malloc(sizeof(legacyOptsLL_t)); + if(line == NULL) + pNew->line = NULL; + else + pNew->line = (uchar *) strdup((char *) line); + pNew->next = NULL; + + if(pLegacyOptsLL == NULL) + pLegacyOptsLL = pNew; + else { + legacyOptsLL_t *pThis = pLegacyOptsLL; + + while(pThis->next != NULL) + pThis = pThis->next; + pThis->next = pNew; + } +} + + +void legacyOptsFree(void) +{ + legacyOptsLL_t *pThis = pLegacyOptsLL, *pNext; + + while(pThis != NULL) { + if(pThis->line != NULL) + free(pThis->line); + pNext = pThis->next; + free(pThis); + pThis = pNext; + } +} + + +void legacyOptsHook(void) +{ + legacyOptsLL_t *pThis = pLegacyOptsLL; + + while(pThis != NULL) { + if(pThis->line != NULL) { + errno = 0; + errmsg.LogError(NO_ERRCODE, "Warning: backward compatibility layer added to following " + "directive to rsyslog.conf: %s", pThis->line); + conf.cfsysline(pThis->line); + } + pThis = pThis->next; + } +} + + +void legacyOptsParseTCP(char ch, char *arg) +{ + register int i; + register char *pArg = arg; + static char conflict = '\0'; + + if((conflict == 'g' && ch == 't') || (conflict == 't' && ch == 'g')) { + fprintf(stderr, "rsyslog: If you want to use both -g and -t, use directives instead, -%c ignored.\n", ch); + return; + } else + conflict = ch; + + /* extract port */ + i = 0; + while(isdigit((int) *pArg)) + i = i * 10 + *pArg++ - '0'; + + /* number of sessions */ + if(*pArg == '\0' || *pArg == ',') { + if(ch == 't') + legacyOptsEnq((uchar *) "ModLoad imtcp"); + else if(ch == 'g') + legacyOptsEnq((uchar *) "ModLoad imgssapi"); + + if(i >= 0 && i <= 65535) { + uchar line[30]; + + if(ch == 't') { + snprintf((char *) line, sizeof(line), "InputTCPServerRun %d", i); + } else if(ch == 'g') { + snprintf((char *) line, sizeof(line), "InputGSSServerRun %d", i); + } + legacyOptsEnq(line); + } else { + if(ch == 't') { + fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i); + legacyOptsEnq((uchar *) "InputTCPServerRun 514"); + } else if(ch == 'g') { + fprintf(stderr, "rsyslogd: Invalid GSS listen port %d - changed to 514.\n", i); + legacyOptsEnq((uchar *) "InputGSSServerRun 514"); + } + } + + if(*pArg == ',') { + ++pArg; + while(isspace((int) *pArg)) + ++pArg; + i = 0; + while(isdigit((int) *pArg)) { + i = i * 10 + *pArg++ - '0'; + } + if(i > 0) { + uchar line[30]; + + snprintf((char *) line, sizeof(line), "InputTCPMaxSessions %d", i); + legacyOptsEnq(line); + } else { + if(ch == 't') { + fprintf(stderr, "rsyslogd: TCP session max configured " + "to %d [-t %s] - changing to 1.\n", i, arg); + legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); + } else if (ch == 'g') { + fprintf(stderr, "rsyslogd: GSS session max configured " + "to %d [-g %s] - changing to 1.\n", i, arg); + legacyOptsEnq((uchar *) "InputTCPMaxSessions 1"); + } + } + } + } else + fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", arg); +} + + +/* doDie() is a signal handler. If called, it sets the bFinished variable + * to indicate the program should terminate. However, it does not terminate + * it itself, because that causes issues with multi-threading. The actual + * termination is then done on the main thread. This solution might introduce + * a minimal delay, but it is much cleaner than the approach of doing everything + * inside the signal handler. + * rgerhards, 2005-10-26 + */ +static void doDie(int sig) +{ + static int iRetries = 0; /* debug aid */ + printf("DoDie called.\n"); + if(iRetries++ == 4) { + printf("DoDie called 5 times - unconditional exit\n"); + abort(); + } + bFinished = sig; +} + + +/* This function frees all dynamically allocated memory for program termination. + * It must be called only immediately before exit(). It is primarily an aid + * for memory debuggers, which prevents cluttered outupt. + * rgerhards, 2008-03-20 + */ +static void +freeAllDynMemForTermination(void) +{ + if(pszWorkDir != NULL) + free(pszWorkDir); + if(pszMainMsgQFName != NULL) + free(pszMainMsgQFName); + if(pModDir != NULL) + free(pModDir); + if(LocalHostName != NULL) + free(LocalHostName); +} + + +/* die() is called when the program shall end. This typically only occurs + * during sigterm or during the initialization. + * As die() is intended to shutdown rsyslogd, it is + * safe to call exit() here. Just make sure that die() itself is not called + * at inapropriate places. As a general rule of thumb, it is a bad idea to add + * any calls to die() in new code! + * rgerhards, 2005-10-24 + */ +static void +die(int sig) +{ + char buf[256]; + + dbgprintf("exiting on signal %d\n", sig); + + /* IMPORTANT: we should close the inputs first, and THEN send our termination + * message. If we do it the other way around, logmsgInternal() may block on + * a full queue and the inputs still fill up that queue. Depending on the + * scheduling order, we may end up with logmsgInternal being held for a quite + * long time. When the inputs are terminated first, that should not happen + * because the queue is drained in parallel. The situation could only become + * an issue with extremely long running actions in a queue full environment. + * However, such actions are at least considered poorly written, if not + * outright wrong. So we do not care about this very remote problem. + * rgerhards, 2008-01-11 + */ + + /* close the inputs */ + dbgprintf("Terminating input threads...\n"); + thrdTerminateAll(); /* TODO: inputs only, please */ + + /* and THEN send the termination log message (see long comment above) */ + if (sig) { + (void) snprintf(buf, sizeof(buf) / sizeof(char), + " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"]" " exiting on signal %d.", + (int) myPid, sig); + errno = 0; + logmsgInternal(LOG_SYSLOG|LOG_INFO, buf, ADDDATE); + } + + /* drain queue (if configured so) and stop main queue worker thread pool */ + dbgprintf("Terminating main queue...\n"); + queueDestruct(&pMsgQueue); + pMsgQueue = NULL; + + /* Free ressources and close connections. This includes flushing any remaining + * repeated msgs. + */ + dbgprintf("Terminating outputs...\n"); + freeSelectors(); + + dbgprintf("all primary multi-thread sources have been terminated - now doing aux cleanup...\n"); + /* rger 2005-02-22 + * now clean up the in-memory structures. OK, the OS + * would also take care of that, but if we do it + * ourselfs, this makes finding memory leaks a lot + * easier. + */ + tplDeleteAll(); + + remove_pid(PidFile); + if(glblHadMemShortage) + dbgprintf("Had memory shortage at least once during the run.\n"); + + /* de-init some modules */ + modExitIminternal(); + + /*dbgPrintAllDebugInfo(); / * this is the last spot where this can be done - below output modules are unloaded! */ + + /* the following line cleans up CfSysLineHandlers that were not based on loadable + * modules. As such, they are not yet cleared. + */ + unregCfSysLineHdlrs(); + + legacyOptsFree(); + + /* terminate the remaining classes */ + GlobalClassExit(); + + /* TODO: this would also be the right place to de-init the builtin output modules. We + * do not currently do that, because the module interface does not allow for + * it. This will come some time later (it's essential with loadable modules). + * For the time being, this is a memory leak on exit, but as the process is + * terminated, we do not really bother about it. + * rgerhards, 2007-08-03 + * I have added some code now, but all that mod init/de-init should be moved to + * init, so that modules are unloaded and reloaded on HUP to. Eventually it should go + * into freeSelectors() - but that needs to be seen. -- rgerhards, 2007-08-09 + */ + module.UnloadAndDestructAll(eMOD_LINK_ALL); + + dbgprintf("Clean shutdown completed, bye\n"); + /* dbgClassExit MUST be the last one, because it de-inits the debug system */ + dbgClassExit(); + + /* free all remaining memory blocks - this is not absolutely necessary, but helps + * us keep memory debugger logs clean and this is in aid in developing. It doesn't + * cost much time, so we do it always. -- rgerhards, 2008-03-20 + */ + freeAllDynMemForTermination(); + /* NO CODE HERE - feeelAllDynMemForTermination() must be the last thing before exit()! */ + exit(0); /* "good" exit, this is the terminator function for rsyslog [die()] */ +} + +/* + * Signal handler to terminate the parent process. + * rgerhards, 2005-10-24: this is only called during forking of the + * detached syslogd. I consider this method to be safe. + */ +static void doexit() +{ + exit(0); /* "good" exit, only during child-creation */ +} + + +/* set the action resume interval + */ +static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal) +{ + return actionSetGlobalResumeInterval(iNewVal); +} + + +/* set the processes umask (upon configuration request) + */ +static rsRetVal setUmask(void __attribute__((unused)) *pVal, int iUmask) +{ + umask(iUmask); + dbgprintf("umask set to 0%3.3o.\n", iUmask); + + return RS_RET_OK; +} + + +/* helper to freeSelectors(), used with llExecFunc() to flush + * pending output. -- rgerhards, 2007-08-02 + * We do not need to lock the action object here as the processing + * queue is already empty and no other threads are running when + * we call this function. -- rgerhards, 2007-12-12 + */ +DEFFUNC_llExecFunc(freeSelectorsActions) +{ + action_t *pAction = (action_t*) pData; + + assert(pAction != NULL); + + /* flush any pending output */ + if(pAction->f_prevcount) { + actionWriteToAction(pAction); + } + + return RS_RET_OK; /* never fails ;) */ +} + + +/* Close all open log files and free selector descriptor array. + */ +static void freeSelectors(void) +{ + selector_t *f; + selector_t *fPrev; + + if(Files != NULL) { + dbgprintf("Freeing log structures.\n"); + + for(f = Files ; f != NULL ; f = f->f_next) { + llExecFunc(&f->llActList, freeSelectorsActions, NULL); + } + + /* actions flushed and ready for destruction - so do that... */ + f = Files; + while (f != NULL) { + fPrev = f; + f = f->f_next; + selectorDestruct(fPrev); + } + + /* Reflect the deletion of the selectors linked list. */ + Files = NULL; + bHaveMainQueue = 0; + } +} + + +/* helper to dbPrintInitInfo, to print out all actions via + * the llExecFunc() facility. + * rgerhards, 2007-08-02 + */ +DEFFUNC_llExecFunc(dbgPrintInitInfoAction) +{ + DEFiRet; + iRet = actionDbgPrint((action_t*) pData); + dbgprintf("\n"); + + RETiRet; +} + +/* print debug information as part of init(). This pretty much + * outputs the whole config of rsyslogd. I've moved this code + * out of init() to clean it somewhat up. + * rgerhards, 2007-07-31 + */ +static void dbgPrintInitInfo(void) +{ + register selector_t *f; + int iSelNbr = 1; + int i; + + dbgprintf("\nActive selectors:\n"); + for (f = Files; f != NULL ; f = f->f_next) { + dbgprintf("Selector %d:\n", iSelNbr++); + if(f->pCSProgNameComp != NULL) + dbgprintf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp)); + if(f->eHostnameCmpMode != HN_NO_COMP) + dbgprintf("hostname: %s '%s'\n", + f->eHostnameCmpMode == HN_COMP_MATCH ? + "only" : "allbut", + rsCStrGetSzStrNoNULL(f->pCSHostnameComp)); + if(f->f_filter_type == FILTER_PRI) { + for (i = 0; i <= LOG_NFACILITIES; i++) + if (f->f_filterData.f_pmask[i] == TABLE_NOPRI) + dbgprintf(" X "); + else + dbgprintf("%2X ", f->f_filterData.f_pmask[i]); + } else if(f->f_filter_type == FILTER_EXPR) { + dbgprintf("EXPRESSION-BASED Filter: can currently not be displayed"); + } else { + dbgprintf("PROPERTY-BASED Filter:\n"); + dbgprintf("\tProperty.: '%s'\n", + rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName)); + dbgprintf("\tOperation: "); + if(f->f_filterData.prop.isNegated) + dbgprintf("NOT "); + dbgprintf("'%s'\n", getFIOPName(f->f_filterData.prop.operation)); + dbgprintf("\tValue....: '%s'\n", + rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue)); + dbgprintf("\tAction...: "); + } + + dbgprintf("\nActions:\n"); + llExecFunc(&f->llActList, dbgPrintInitInfoAction, NULL); /* actions */ + + dbgprintf("\n"); + } + dbgprintf("\n"); + if(bDebugPrintTemplateList) + tplPrintList(); + if(bDebugPrintModuleList) + module.PrintList(); + ochPrintList(); + + if(bDebugPrintCfSysLineHandlerList) + dbgPrintCfSysLineHandlers(); + + dbgprintf("Messages with malicious PTR DNS Records are %sdropped.\n", + bDropMalPTRMsgs ? "" : "not "); + + dbgprintf("Control characters are %sreplaced upon reception.\n", + bEscapeCCOnRcv? "" : "not "); + + if(bEscapeCCOnRcv) + dbgprintf("Control character escape sequence prefix is '%c'.\n", + cCCEscapeChar); + + dbgprintf("Main queue size %d messages.\n", iMainMsgQueueSize); + dbgprintf("Main queue worker threads: %d, Perists every %d updates.\n", + iMainMsgQueueNumWorkers, iMainMsgQPersistUpdCnt); + dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n", + iMainMsgQtoQShutdown, iMainMsgQtoActShutdown, iMainMsgQtoEnq); + dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n", + iMainMsgQHighWtrMark, iMainMsgQLowWtrMark, iMainMsgQDiscardMark, iMainMsgQDiscardSeverity); + dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n", + bMainMsgQSaveOnShutdown, iMainMsgQueMaxDiskSpace); + /* TODO: add + iActionRetryCount = 0; + iActionRetryInterval = 30000; + static int iMainMsgQtoWrkShutdown = 60000; + static int iMainMsgQtoWrkMinMsgs = 100; + static int iMainMsgQbSaveOnShutdown = 1; + iMainMsgQueMaxDiskSpace = 0; + setQPROP(queueSettoWrkShutdown, "$MainMsgQueueTimeoutWorkerThreadShutdown", 5000); + setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100); + setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1); + */ + dbgprintf("Work Directory: '%s'.\n", pszWorkDir); +} + + +/* Start the input modules. This function will probably undergo big changes + * while we implement the input module interface. For now, it does the most + * important thing to get at least my poor initial input modules up and + * running. Almost no config option is taken. + * rgerhards, 2007-12-14 + */ +static rsRetVal +startInputModules(void) +{ + DEFiRet; + modInfo_t *pMod; + + /* loop through all modules and activate them (brr...) */ + pMod = module.GetNxtType(NULL, eMOD_IN); + while(pMod != NULL) { + if((iRet = pMod->mod.im.willRun()) == RS_RET_OK) { + /* activate here */ + thrdCreate(pMod->mod.im.runInput, pMod->mod.im.afterRun); + } else { + dbgprintf("module %lx will not run, iRet %d\n", (unsigned long) pMod, iRet); + } + pMod = module.GetNxtType(pMod, eMOD_IN); + } + + ENDfunc + return RS_RET_OK; /* intentional: we do not care about module errors */ +} + + +/* INIT -- Initialize syslogd from configuration table + * init() is called at initial startup AND each time syslogd is HUPed + */ +static void +init(void) +{ + DEFiRet; + char cbuf[BUFSIZ]; + char bufStartUpMsg[512]; + struct sigaction sigAct; + + thrdTerminateAll(); /* stop all running input threads - TODO: reconsider location! */ + + /* initialize some static variables */ + pDfltHostnameCmp = NULL; + pDfltProgNameCmp = NULL; + eDfltHostnameCmpMode = HN_NO_COMP; + + dbgprintf("rsyslog %s - called init()\n", VERSION); + + /* delete the message queue, which also flushes all messages left over */ + if(pMsgQueue != NULL) { + dbgprintf("deleting main message queue\n"); + queueDestruct(&pMsgQueue); /* delete pThis here! */ + pMsgQueue = NULL; + } + + /* Close all open log files and free log descriptor array. This also frees + * all output-modules instance data. + */ + freeSelectors(); + + /* Unload all non-static modules */ + dbgprintf("Unloading non-static modules.\n"); + module.UnloadAndDestructAll(eMOD_LINK_DYNAMIC_LOADED); + + dbgprintf("Clearing templates.\n"); + tplDeleteNew(); + + /* re-setting values to defaults (where applicable) */ + /* TODO: once we have loadable modules, we must re-visit this code. The reason is + * that config variables are not re-set, because the module is not yet loaded. On + * the other hand, that doesn't matter, because the module got unloaded and is then + * re-loaded, so the variables should be re-set via that way. In any case, we should + * think about the whole situation when we implement loadable plugins. + * rgerhards, 2007-07-31 + */ + conf.cfsysline((uchar*)"ResetConfigVariables"); + + /* open the configuration file */ + if((iRet = conf.processConfFile(ConfFile)) != RS_RET_OK) { + /* rgerhards: this code is executed to set defaults when the + * config file could not be opened. We might think about + * abandoning the run in this case - but this, too, is not + * very clever... So we stick with what we have. + * We ignore any errors while doing this - we would be lost anyhow... + */ + selector_t *f = NULL; + char szTTYNameBuf[_POSIX_TTY_NAME_MAX+1]; /* +1 for NULL character */ + dbgprintf("primary config file could not be opened - using emergency definitions.\n"); + conf.cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f); + conf.cfline((uchar*)"*.PANIC\t*", &f); + if(ttyname_r(0, szTTYNameBuf, sizeof(szTTYNameBuf)) == 0) { + snprintf(cbuf,sizeof(cbuf), "*.*\t%s", szTTYNameBuf); + conf.cfline((uchar*)cbuf, &f); + } + selectorAddList(f); + } + + legacyOptsHook(); + + /* we are now done with reading the configuration. This is the right time to + * free some objects that were just needed for loading it. rgerhards 2005-10-19 + */ + if(pDfltHostnameCmp != NULL) { + rsCStrDestruct(&pDfltHostnameCmp); + } + + if(pDfltProgNameCmp != NULL) { + rsCStrDestruct(&pDfltProgNameCmp); + } + + /* some checks */ + if(iMainMsgQueueNumWorkers < 1) { + errmsg.LogError(NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n"); + iMainMsgQueueNumWorkers = 1; + } + + if(MainMsgQueType == QUEUETYPE_DISK) { + errno = 0; /* for logerror! */ + if(pszWorkDir == NULL) { + errmsg.LogError(NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. " + "Using 'FixedArray' instead.\n"); + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + } + if(pszMainMsgQFName == NULL) { + errmsg.LogError(NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in " + "'disk' mode. Using 'FixedArray' instead.\n"); + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + } + } + + /* switch the message object to threaded operation, if necessary */ + if(MainMsgQueType == QUEUETYPE_DIRECT || iMainMsgQueueNumWorkers > 1) { + MsgEnableThreadSafety(); + } + + /* create message queue */ + CHKiRet_Hdlr(queueConstruct(&pMsgQueue, MainMsgQueType, iMainMsgQueueNumWorkers, iMainMsgQueueSize, msgConsumer)) { + /* no queue is fatal, we need to give up in that case... */ + fprintf(stderr, "fatal error %d: could not create message queue - rsyslogd can not run!\n", iRet); + exit(1); + } + /* name our main queue object (it's not fatal if it fails...) */ + obj.SetName((obj_t*) pMsgQueue, (uchar*) "main queue"); + + /* ... set some properties ... */ +# define setQPROP(func, directive, data) \ + CHKiRet_Hdlr(func(pMsgQueue, data)) { \ + errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } +# define setQPROPstr(func, directive, data) \ + CHKiRet_Hdlr(func(pMsgQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \ + errmsg.LogError(NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \ + } + + setQPROP(queueSetMaxFileSize, "$MainMsgQueueFileSize", iMainMsgQueMaxFileSize); + setQPROP(queueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", iMainMsgQueMaxDiskSpace); + setQPROPstr(queueSetFilePrefix, "$MainMsgQueueFileName", pszMainMsgQFName); + setQPROP(queueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval", iMainMsgQPersistUpdCnt); + setQPROP(queueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", iMainMsgQtoQShutdown ); + setQPROP(queueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion", iMainMsgQtoActShutdown); + setQPROP(queueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown", iMainMsgQtoWrkShutdown); + setQPROP(queueSettoEnq, "$MainMsgQueueTimeoutEnqueue", iMainMsgQtoEnq); + setQPROP(queueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", iMainMsgQHighWtrMark); + setQPROP(queueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", iMainMsgQLowWtrMark); + setQPROP(queueSetiDiscardMrk, "$MainMsgQueueDiscardMark", iMainMsgQDiscardMark); + setQPROP(queueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity", iMainMsgQDiscardSeverity); + setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", iMainMsgQWrkMinMsgs); + setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", bMainMsgQSaveOnShutdown); + setQPROP(queueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", iMainMsgQDeqSlowdown); + setQPROP(queueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin", iMainMsgQueueDeqtWinFromHr); + setQPROP(queueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", iMainMsgQueueDeqtWinToHr); + +# undef setQPROP +# undef setQPROPstr + + /* ... and finally start the queue! */ + CHKiRet_Hdlr(queueStart(pMsgQueue)) { + /* no queue is fatal, we need to give up in that case... */ + fprintf(stderr, "fatal error %d: could not start message queue - rsyslogd can not run!\n", iRet); + exit(1); + } + + bHaveMainQueue = (MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1; + dbgprintf("Main processing queue is initialized and running\n"); + + /* the output part and the queue is now ready to run. So it is a good time + * to start the inputs. Please note that the net code above should be + * shuffled to down here once we have everything in input modules. + * rgerhards, 2007-12-14 + */ + startInputModules(); + + if(Debug) { + dbgPrintInitInfo(); + } + + /* we now generate the startup message. It now includes everything to + * identify this instance. -- rgerhards, 2005-08-17 + */ + snprintf(bufStartUpMsg, sizeof(bufStartUpMsg)/sizeof(char), + " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ + "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] restart", + (int) myPid); + logmsgInternal(LOG_SYSLOG|LOG_INFO, bufStartUpMsg, ADDDATE); + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sighup_handler; + sigaction(SIGHUP, &sigAct, NULL); + + dbgprintf(" (re)started.\n"); + ENDfunc +} + + +/* add a completely-processed selector (after config line parsing) to + * the linked list of selectors. We now need to check + * if it has any actions associated and, if so, link it to the linked + * list. If it has nothing associated with it, we can simply discard + * it. + * We have one special case during initialization: then, the current + * selector is NULL, which means we do not need to care about it at + * all. -- rgerhards, 2007-08-01 + */ +rsRetVal +selectorAddList(selector_t *f) +{ + DEFiRet; + int iActionCnt; + + static selector_t *nextp = NULL; /* TODO: make this go away (see comment below) */ + + if(f != NULL) { + CHKiRet(llGetNumElts(&f->llActList, &iActionCnt)); + if(iActionCnt == 0) { + errmsg.LogError(NO_ERRCODE, "warning: selector line without actions will be discarded"); + selectorDestruct(f); + } else { + /* successfully created an entry */ + dbgprintf("selector line successfully processed\n"); + /* TODO: we should use the linked list class for the selector list, else we need to add globals + * ... well nextp could be added temporarily... + * Thanks to varmojfekoj for having the idea to just use "Files" to make this + * code work. I had actually forgotten to fix the code here before moving to 1.18.0. + * And, of course, I also did not migrate the selector_t structure to the linked list class. + * However, that should still be one of the very next things to happen. + * rgerhards, 2007-08-06 + */ + if(Files == NULL) { + Files = f; + } else { + nextp->f_next = f; + } + nextp = f; + } + } + +finalize_it: + RETiRet; +} + + +/* set the main message queue mode + * rgerhards, 2008-01-03 + */ +static rsRetVal setMainMsgQueType(void __attribute__((unused)) *pVal, uchar *pszType) +{ + DEFiRet; + + if (!strcasecmp((char *) pszType, "fixedarray")) { + MainMsgQueType = QUEUETYPE_FIXED_ARRAY; + dbgprintf("main message queue type set to FIXED_ARRAY\n"); + } else if (!strcasecmp((char *) pszType, "linkedlist")) { + MainMsgQueType = QUEUETYPE_LINKEDLIST; + dbgprintf("main message queue type set to LINKEDLIST\n"); + } else if (!strcasecmp((char *) pszType, "disk")) { + MainMsgQueType = QUEUETYPE_DISK; + dbgprintf("main message queue type set to DISK\n"); + } else if (!strcasecmp((char *) pszType, "direct")) { + MainMsgQueType = QUEUETYPE_DIRECT; + dbgprintf("main message queue type set to DIRECT (no queueing at all)\n"); + } else { + errmsg.LogError(NO_ERRCODE, "unknown mainmessagequeuetype parameter: %s", (char *) pszType); + iRet = RS_RET_INVALID_PARAMS; + } + free(pszType); /* no longer needed */ + + RETiRet; +} + + +/* + * The following function is resposible for handling a SIGHUP signal. Since + * we are now doing mallocs/free as part of init we had better not being + * doing this during a signal handler. Instead this function simply sets + * a flag variable which will tell the main loop to go through a restart. + */ +void sighup_handler() +{ + struct sigaction sigAct; + + restart = 1; + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = sighup_handler; + sigaction(SIGHUP, &sigAct, NULL); + + return; +} + + +/** + * getSubString + * + * Copy a string byte by byte until the occurrence + * of a given separator. + * + * \param ppSrc Pointer to a pointer of the source array of characters. If a + separator detected the Pointer points to the next char after the + separator. Except if the end of the string is dedected ('\n'). + Then it points to the terminator char. + * \param pDst Pointer to the destination array of characters. Here the substing + will be stored. + * \param DstSize Maximum numbers of characters to store. + * \param cSep Separator char. + * \ret int Returns 0 if no error occured. + * + * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time + * so that it treats ' ' as a request for whitespace. But in general, the function and its callers + * should be changed over time, this is not really very good code... + */ +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) +{ + uchar *pSrc = *ppSrc; + int iErr = 0; /* 0 = no error, >0 = error */ + while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { + *pDst++ = *(pSrc)++; + DstSize--; + } + /* check if the Dst buffer was to small */ + if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { + dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); + iErr = 1; + } + if (*pSrc == '\0' || *pSrc == '\n') + /* this line was missing, causing ppSrc to be invalid when it + * was returned in case of end-of-string. rgerhards 2005-07-29 + */ + *ppSrc = pSrc; + else + *ppSrc = pSrc+1; + *pDst = '\0'; + return iErr; +} + + +/* this function pulls all internal messages from the buffer + * and puts them into the processing engine. + * We can only do limited error handling, as this would not + * really help us. TODO: add error messages? + * rgerhards, 2007-08-03 + */ +static void processImInternal(void) +{ + int iPri; + int iFlags; + msg_t *pMsg; + + while(iminternalRemoveMsg(&iPri, &pMsg, &iFlags) == RS_RET_OK) { + logmsg(pMsg, iFlags); + } +} + + +/* This is the main processing loop. It is called after successful initialization. + * When it returns, the syslogd terminates. + * Its sole function is to provide some housekeeping things. The real work is done + * by the other threads spawned. + */ +static void +mainloop(void) +{ + struct timeval tvSelectTimeout; + + BEGINfunc + while(!bFinished){ + /* first check if we have any internal messages queued and spit them out */ + /* TODO: do we need this any longer? I doubt it, but let's care about it + * later -- rgerhards, 2007-12-21 + */ + processImInternal(); + + /* this is now just a wait */ + tvSelectTimeout.tv_sec = TIMERINTVL; + tvSelectTimeout.tv_usec = 0; + select(1, NULL, NULL, NULL, &tvSelectTimeout); + if(bFinished) + break; /* exit as quickly as possible - see long comment below */ + + /* If we received a HUP signal, we call doFlushRptdMsgs() a bit early. This + * doesn't matter, because doFlushRptdMsgs() checks timestamps. What may happen, + * however, is that the too-early call may lead to a bit too-late output + * of "last message repeated n times" messages. But that is quite acceptable. + * rgerhards, 2007-12-21 + * ... and just to explain, we flush here because that is exactly what the mainloop + * shall do - provide a periodic interval in which not-yet-flushed messages will + * be flushed. Be careful, there is a potential race condition: doFlushRptdMsgs() + * needs to aquire a lock on the action objects. If, however, long-running consumers + * cause the main queue worker threads to lock them for a long time, we may receive + * a starvation condition, resulting in the mainloop being held on lock for an extended + * period of time. That, in turn, could lead to unresponsiveness to termination + * requests. It is especially important that the bFinished flag is checked before + * doFlushRptdMsgs() is called (I know because I ran into that situation). I am + * not yet sure if the remaining probability window of a termination-related + * problem is large enough to justify changing the code - I would consider it + * extremely unlikely that the problem ever occurs in practice. Fixing it would + * require not only a lot of effort but would cost considerable performance. So + * for the time being, I think the remaining risk can be accepted. + * rgerhards, 2008-01-10 + */ + doFlushRptdMsgs(); + + if(restart) { + dbgprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); + /* main queue is stopped as part of init() */ + init(); + restart = 0; + continue; + } + } + ENDfunc +} + +/* If user is not root, prints warnings or even exits + * TODO: check all dynafiles for write permission + * ... but it is probably better to wait here until we have + * a module interface - rgerhards, 2007-07-23 + */ +static void checkPermissions() +{ +#if 0 + /* TODO: this function must either be redone or removed - now with the input modules, + * there is no such simple check we can do. What we can check, however, is if there is + * any input module active and terminate, if not. -- rgerhards, 2007-12-26 + */ + /* we are not root */ + if (geteuid() != 0) + { + fputs("WARNING: Local messages will not be logged! If you want to log them, run rsyslog as root.\n",stderr); +#ifdef SYSLOG_INET + /* udp enabled and port number less than or equal to 1024 */ + if ( AcceptRemote && (atoi(LogPort) <= 1024) ) + fprintf(stderr, "WARNING: Will not listen on UDP port %s. Use port number higher than 1024 or run rsyslog as root!\n", LogPort); + + /* tcp enabled and port number less or equal to 1024 */ + if( bEnableTCP && (atoi(TCPLstnPort) <= 1024) ) + fprintf(stderr, "WARNING: Will not listen on TCP port %s. Use port number higher than 1024 or run rsyslog as root!\n", TCPLstnPort); + + /* Neither explicit high UDP port nor explicit high TCP port. + * It is useless to run anymore */ + if( !(AcceptRemote && (atoi(LogPort) > 1024)) && !( bEnableTCP && (atoi(TCPLstnPort) > 1024)) ) + { +#endif + fprintf(stderr, "ERROR: Nothing to log, no reason to run. Please run rsyslog as root.\n"); + exit(EXIT_FAILURE); +#ifdef SYSLOG_INET + } +#endif + } +#endif +} + + +/* load build-in modules + * very first version begun on 2007-07-23 by rgerhards + */ +static rsRetVal loadBuildInModules(void) +{ + DEFiRet; + + if((iRet = module.doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) { + RETiRet; + } +#ifdef SYSLOG_INET + if((iRet = module.doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) { + RETiRet; + } +#endif + if((iRet = module.doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) { + RETiRet; + } + if((iRet = module.doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) { + RETiRet; + } + + /* dirty, but this must be for the time being: the usrmsg module must always be + * loaded as last module. This is because it processes any time of action selector. + * If we load it before other modules, these others will never have a chance of + * working with the config file. We may change that implementation so that a user name + * must start with an alnum, that would definitely help (but would it break backwards + * compatibility?). * rgerhards, 2007-07-23 + * User names now must begin with: + * [a-zA-Z0-9_.] + */ + if((iRet = module.doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK) + RETiRet; + + /* ok, initialization of the command handler probably does not 100% belong right in + * this space here. However, with the current design, this is actually quite a good + * place to put it. We might decide to shuffle it around later, but for the time + * being, the code has found its home here. A not-just-sideeffect of this decision + * is that rsyslog will terminate if we can not register our built-in config commands. + * This, I think, is the right thing to do. -- rgerhards, 2007-07-31 + */ + CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, NULL, &pszWorkDir, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeretrycount", 0, eCmdHdlrInt, NULL, &glbliActionResumeRetryCount, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord, NULL, &pszMainMsgQFName, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt, NULL, &iMainMsgQueueSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQHighWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQLowWtrMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt, NULL, &iMainMsgQDiscardMark, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity, NULL, &iMainMsgQDiscardSeverity, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &iMainMsgQPersistUpdCnt, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord, setMainMsgQueType, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt, NULL, &iMainMsgQueueNumWorkers, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoQShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &iMainMsgQtoActShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &iMainMsgQtoEnq, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworketimeoutrthreadshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoWrkShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &iMainMsgQDeqSlowdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &iMainMsgQWrkMinMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxFileSize, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxDiskSpace, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &bMainMsgQSaveOnShutdown, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinFromHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinToHr, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, NULL, &bReduceRepeatMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &bActExecWhenPrevSusp, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyonceeveryinterval", 0, eCmdHdlrInt, NULL, &iActExecOnceInterval, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt, setActionResumeInterval, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_TEMPLATE, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_OUTCHANNEL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler, conf.doNameLine, (void*)DIR_ALLOWEDSENDER, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, conf.doModLoad, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, conf.doIncludeLine, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode, setUmask, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintTemplateList, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintModuleList, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"debugprintcfsyslinehandlerlist", 0, eCmdHdlrBinary, + NULL, &bDebugPrintCfSysLineHandlerList, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord, NULL, &pModDir, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL)); + + /* now add other modules handlers (we should work on that to be able to do it in ClassInit(), but so far + * that is not possible). -- rgerhards, 2008-01-28 + */ + CHKiRet(actionAddCfSysLineHdrl()); + +finalize_it: + RETiRet; +} + + +/* print version and compile-time setting information. + */ +static void printVersion(void) +{ + printf("rsyslogd %s, ", VERSION); + printf("compiled with:\n"); +#ifdef FEATURE_REGEXP + printf("\tFEATURE_REGEXP:\t\t\t\tYes\n"); +#else + printf("\tFEATURE_REGEXP:\t\t\t\tNo\n"); +#endif +#ifndef NOLARGEFILE + printf("\tFEATURE_LARGEFILE:\t\t\tYes\n"); +#else + printf("\tFEATURE_LARGEFILE:\t\t\tNo\n"); +#endif +#ifdef USE_NETZIP + printf("\tFEATURE_NETZIP (message compression):\tYes\n"); +#else + printf("\tFEATURE_NETZIP (message compression):\tNo\n"); +#endif +#if defined(SYSLOG_INET) && defined(USE_GSSAPI) + printf("\tGSSAPI Kerberos 5 support:\t\tYes\n"); +#else + printf("\tGSSAPI Kerberos 5 support:\t\tNo\n"); +#endif +#ifndef NDEBUG + printf("\tFEATURE_DEBUG (debug build, slow code):\tYes\n"); +#else + printf("\tFEATURE_DEBUG (debug build, slow code):\tNo\n"); +#endif +#ifdef RTINST + printf("\tRuntime Instrumentation (slow code):\tYes\n"); +#else + printf("\tRuntime Instrumentation (slow code):\tNo\n"); +#endif + printf("\nSee http://www.rsyslog.com for more information.\n"); +} + + +/* This function is called after initial initalization. It is used to + * move code out of the too-long main() function. + * rgerhards, 2007-10-17 + */ +static void mainThread() +{ + BEGINfunc + uchar *pTmp; + + /* Note: signals MUST be processed by the thread this code is running in. The reason + * is that we need to interrupt the select() system call. -- rgerhards, 2007-10-17 + */ + + /* initialize the build-in templates */ + pTmp = template_SyslogProtocol23Format; + tplAddLine("RSYSLOG_SyslogProtocol23Format", &pTmp); + pTmp = template_FileFormat; /* new format for files with high-precision stamp */ + tplAddLine("RSYSLOG_FileFormat", &pTmp); + pTmp = template_TraditionalFileFormat; + tplAddLine("RSYSLOG_TraditionalFileFormat", &pTmp); + pTmp = template_WallFmt; + tplAddLine(" WallFmt", &pTmp); + pTmp = template_ForwardFormat; + tplAddLine("RSYSLOG_ForwardFormat", &pTmp); + pTmp = template_TraditionalForwardFormat; + tplAddLine("RSYSLOG_TraditionalForwardFormat", &pTmp); + pTmp = template_StdUsrMsgFmt; + tplAddLine(" StdUsrMsgFmt", &pTmp); + pTmp = template_StdDBFmt; + tplAddLine(" StdDBFmt", &pTmp); + pTmp = template_StdPgSQLFmt; + tplLastStaticInit(tplAddLine(" StdPgSQLFmt", &pTmp)); + + init(); + if(Debug) { + dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); + debugging_on = 1; + } + /* Send a signal to the parent so it can terminate. + */ + if (myPid != ppid) + kill (ppid, SIGTERM); + + /* END OF INTIALIZATION + * ... but keep in mind that we might do a restart and thus init() might + * be called again. If that happens, we must shut down the worker thread, + * do the init() and then restart things. + * rgerhards, 2005-10-24 + */ + dbgprintf("initialization completed, transitioning to regular run mode\n"); + + mainloop(); + ENDfunc +} + + +/* Method to initialize all global classes and use the objects that we need. + * rgerhards, 2008-01-04 + * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime + */ +static rsRetVal +InitGlobalClasses(void) +{ + DEFiRet; + char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */ + + /* Intialize the runtime system */ + pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */ + CHKiRet(rsrtInit(&pErrObj, &obj)); + + /* Now tell the system which classes we need ourselfs */ + pErrObj = "errmsg"; + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + pErrObj = "module"; + CHKiRet(objUse(module, CORE_COMPONENT)); + pErrObj = "var"; + CHKiRet(objUse(var, CORE_COMPONENT)); + pErrObj = "datetime"; + CHKiRet(objUse(datetime, CORE_COMPONENT)); + pErrObj = "vm"; + CHKiRet(objUse(vm, CORE_COMPONENT)); + pErrObj = "expr"; + CHKiRet(objUse(expr, CORE_COMPONENT)); + pErrObj = "conf"; + CHKiRet(objUse(conf, CORE_COMPONENT)); + + /* intialize some dummy classes that are not part of the runtime */ + pErrObj = "action"; + CHKiRet(actionClassInit()); + pErrObj = "template"; + CHKiRet(templateInit()); + + /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ + pErrObj = "net"; + CHKiRet(objUse(net, LM_NET_FILENAME)); + +finalize_it: + if(iRet != RS_RET_OK) { + /* we know we are inside the init sequence, so we can safely emit + * messages to stderr. -- rgerhards, 2008-04-02 + */ + fprintf(stderr, "Error during class init for object '%s' - failing...\n", pErrObj); + } + + RETiRet; +} + + +/* Method to exit all global classes. We do not do any error checking here, + * because that wouldn't help us at all. So better try to deinit blindly + * as much as succeeds (which usually means everything will). We just must + * be careful to do the de-init in the opposite order of the init, because + * of the dependencies. However, its not as important this time, because + * we have reference counting. + * rgerhards, 2008-03-10 + */ +static rsRetVal +GlobalClassExit(void) +{ + DEFiRet; + + /* first, release everything we used ourself */ + objRelease(net, LM_NET_FILENAME);/* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */ + objRelease(conf, CORE_COMPONENT); + objRelease(expr, CORE_COMPONENT); + objRelease(vm, CORE_COMPONENT); + objRelease(var, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); + + /* TODO: implement the rest of the deinit */ +#if 0 + CHKiRet(datetimeClassInit(NULL)); + CHKiRet(msgClassInit(NULL)); + CHKiRet(strmClassInit(NULL)); + CHKiRet(wtiClassInit(NULL)); + CHKiRet(wtpClassInit(NULL)); + CHKiRet(queueClassInit(NULL)); + CHKiRet(vmstkClassInit(NULL)); + CHKiRet(sysvarClassInit(NULL)); + CHKiRet(vmClassInit(NULL)); + CHKiRet(vmopClassInit(NULL)); + CHKiRet(vmprgClassInit(NULL)); + CHKiRet(ctok_tokenClassInit(NULL)); + CHKiRet(ctokClassInit(NULL)); + CHKiRet(exprClassInit(NULL)); + + /* dummy "classes" */ + CHKiRet(actionClassInit()); + CHKiRet(templateInit()); +#endif + /* dummy "classes */ + strExit(); + +#if 0 + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + /* the following classes were intialized by objClassInit() */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(module, CORE_COMPONENT)); +#endif + rsrtExit(&obj); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */ + + RETiRet; +} + + +/* some support for command line option parsing. Any non-trivial options must be + * buffered until the complete command line has been parsed. This is necessary to + * prevent dependencies between the options. That, in turn, means we need to have + * something that is capable of buffering options and there values. The follwing + * functions handle that. + * rgerhards, 2008-04-04 + */ +typedef struct bufOpt { + struct bufOpt *pNext; + char optchar; + char *arg; +} bufOpt_t; +static bufOpt_t *bufOptRoot = NULL; +static bufOpt_t *bufOptLast = NULL; + +/* add option buffer */ +static rsRetVal +bufOptAdd(char opt, char *arg) +{ + DEFiRet; + bufOpt_t *pBuf; + + if((pBuf = malloc(sizeof(bufOpt_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pBuf->optchar = opt; + pBuf->arg = arg; + pBuf->pNext = NULL; + + if(bufOptLast == NULL) { + bufOptRoot = pBuf; /* then there is also no root! */ + } else { + bufOptLast->pNext = pBuf; + } + bufOptLast = pBuf; + +finalize_it: + RETiRet; +} + + + +/* remove option buffer from top of list, return values and destruct buffer itself. + * returns RS_RET_END_OF_LINKEDLIST when no more options are present. + * (we use int *opt instead of char *opt to keep consistent with getopt()) + */ +static rsRetVal +bufOptRemove(int *opt, char **arg) +{ + DEFiRet; + bufOpt_t *pBuf; + + if(bufOptRoot == NULL) + ABORT_FINALIZE(RS_RET_END_OF_LINKEDLIST); + pBuf = bufOptRoot; + + *opt = pBuf->optchar; + *arg = pBuf->arg; + + bufOptRoot = pBuf->pNext; + free(pBuf); + +finalize_it: + RETiRet; +} + + +/* This is the main entry point into rsyslogd. Over time, we should try to + * modularize it a bit more... + */ +int realMain(int argc, char **argv) +{ + DEFiRet; + + register int i; + register char *p; + int num_fds; + int ch; + struct hostent *hent; + extern int optind; + extern char *optarg; + struct sigaction sigAct; + int bIsFirstOption = 1; + int bEOptionWasGiven = 0; + int bImUxSockLoaded = 0; /* already generated a $ModLoad imuxsock? */ + char *arg; /* for command line option processing */ + uchar legacyConfLine[80]; + + /* first, parse the command line options. We do not carry out any actual work, just + * see what we should do. This relieves us from certain anomalies and we can process + * the parameters down below in the correct order. For example, we must know the + * value of -M before we can do the init, but at the same time we need to have + * the base classes init before we can process most of the options. Now, with the + * split of functionality, this is no longer a problem. Thanks to varmofekoj for + * suggesting this algo. + * Note: where we just need to set some flags and can do so without knowledge + * of other options, we do this during the inital option processing. With later + * versions (if a dependency on -c option is introduced), we must move that code + * to other places, but I think it is quite appropriate and saves code to do this + * only when actually neeeded. + * rgerhards, 2008-04-04 + */ + while ((ch = getopt(argc, argv, "46aAc:def:g:hi:l:m:M:nopqQr::s:t:u:vwx")) != EOF) { + switch((char)ch) { + case '4': + case '6': + case 'A': + case 'a': + case 'f': /* configuration file */ + case 'h': + case 'i': /* pid file name */ + case 'l': + case 'm': /* mark interval */ + case 'n': /* don't fork */ + case 'o': + case 'p': + case 'q': /* add hostname if DNS resolving has failed */ + case 'Q': /* dont resolve hostnames in ACL to IPs */ + case 's': + case 'u': /* misc user settings */ + case 'w': /* disable disallowed host warnigs */ + case 'x': /* disable dns for remote messages */ + CHKiRet(bufOptAdd(ch, optarg)); + break; + case 'c': /* compatibility mode */ + if(!bIsFirstOption) { + fprintf(stderr, "-c option MUST be specified as the first option - aborting...\n"); + usage(); + exit(1); + } + iCompatibilityMode = atoi(optarg); + break; + case 'd': /* debug - must be handled now, so that debug is active during init! */ + Debug = 1; + break; + case 'e': /* log every message (no repeat message supression) */ + fprintf(stderr, "note: -e option is no longer supported, every message is now logged by default\n"); + bEOptionWasGiven = 1; + break; + case 'g': /* enable tcp gssapi logging */ +#if defined(SYSLOG_INET) && defined(USE_GSSAPI) + CHKiRet(bufOptAdd('g', optarg)); +#else + fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support"); +#endif + break; + case 'M': /* default module load path -- this MUST be carried out immediately! */ + glblModPath = (uchar*) optarg; + break; + case 'r': /* accept remote messages */ +#ifdef SYSLOG_INET + CHKiRet(bufOptAdd(ch, optarg)); +#else + fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support\n"); +#endif + break; + case 't': /* enable tcp logging */ +#ifdef SYSLOG_INET + CHKiRet(bufOptAdd(ch, optarg)); +#else + fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support\n"); +#endif + break; + case 'v': /* MUST be carried out immediately! */ + printVersion(); + exit(0); /* exit for -v option - so this is a "good one" */ + case '?': + default: + usage(); + } + bIsFirstOption = 0; /* we already saw an option character */ + } + + if ((argc -= optind)) + usage(); + + dbgprintf("rsyslogd %s startup, compatibility mode %d, module path '%s'\n", + VERSION, iCompatibilityMode, glblModPath == NULL ? "" : (char*)glblModPath); + + /* we are done with the initial option parsing and processing. Now we init the system. */ + + ppid = getpid(); + + if(chdir ("/") != 0) + fprintf(stderr, "Can not do 'cd /' - still trying to run\n"); + + CHKiRet_Hdlr(InitGlobalClasses()) { + fprintf(stderr, "rsyslogd initializiation failed - global classes could not be initialized.\n" + "Did you do a \"make install\"?\n" + "Suggested action: run rsyslogd with -d -n options to see what exactly " + "fails.\n"); + FINALIZE; + } + + /* doing some core initializations */ + + /* get our host and domain names - we need to do this early as we may emit + * error log messages, which need the correct hostname. -- rgerhards, 2008-04-04 + */ + net.getLocalHostname(&LocalHostName); + if((p = strchr((char*)LocalHostName, '.'))) { + *p++ = '\0'; + LocalDomain = p; + } else { + LocalDomain = ""; + + /* It's not clearly defined whether gethostname() + * should return the simple hostname or the fqdn. A + * good piece of software should be aware of both and + * we want to distribute good software. Joey + * + * Good software also always checks its return values... + * If syslogd starts up before DNS is up & /etc/hosts + * doesn't have LocalHostName listed, gethostbyname will + * return NULL. + */ + /* TODO: gethostbyname() is not thread-safe, but replacing it is + * not urgent as we do not run on multiple threads here. rgerhards, 2007-09-25 + */ + hent = gethostbyname((char*)LocalHostName); + if(hent) { + free(LocalHostName); + CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_name)); + + if((p = strchr((char*)LocalHostName, '.'))) + { + *p++ = '\0'; + LocalDomain = p; + } + } + } + + /* Convert to lower case to recognize the correct domain laterly */ + for (p = (char *)LocalDomain ; *p ; p++) + *p = (char)tolower((int)*p); + + /* initialize the objects */ + if((iRet = modInitIminternal()) != RS_RET_OK) { + fprintf(stderr, "fatal error: could not initialize errbuf object (error code %d).\n", + iRet); + exit(1); /* "good" exit, leaving at init for fatal error */ + } + + if((iRet = loadBuildInModules()) != RS_RET_OK) { + fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n", + iRet); + exit(1); /* "good" exit, leaving at init for fatal error */ + } + + /* END core initializations - we now come back to carrying out command line options*/ + + while((iRet = bufOptRemove(&ch, &arg)) == RS_RET_OK) { + dbgprintf("deque option %c, optarg '%s'\n", ch, arg); + switch((char)ch) { + case '4': + family = PF_INET; + break; + case '6': + family = PF_INET6; + break; + case 'A': + send_to_all++; + break; + case 'a': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; + } + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "addunixlistensocket %s", arg); + legacyOptsEnq(legacyConfLine); + } else { + fprintf(stderr, "error -a is no longer supported, use module imuxsock instead"); + } + break; + case 'f': /* configuration file */ + ConfFile = (uchar*) arg; + break; + case 'g': /* enable tcp gssapi logging */ + if(iCompatibilityMode < 3) { + legacyOptsParseTCP(ch, arg); + } else + fprintf(stderr, "-g option only supported in compatibility modes 0 to 2 - ignored\n"); + break; + case 'h': + if(iCompatibilityMode < 3) { + errmsg.LogError(NO_ERRCODE, "WARNING: -h option is no longer supported - ignored"); + } else { + usage(); /* for v3 and above, it simply is an error */ + } + break; + case 'i': /* pid file name */ + PidFile = arg; + break; + case 'l': + if (LocalHosts) { + fprintf (stderr, "rsyslogd: Only one -l argument allowed, the first one is taken.\n"); + } else { + LocalHosts = crunch_list(arg); + } + break; + case 'm': /* mark interval */ + if(iCompatibilityMode < 3) { + MarkInterval = atoi(arg) * 60; + } else + fprintf(stderr, + "-m option only supported in compatibility modes 0 to 2 - ignored\n"); + break; + case 'n': /* don't fork */ + NoFork = 1; + break; + case 'o': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; + } + legacyOptsEnq((uchar *) "OmitLocalLogging"); + } else { + fprintf(stderr, "error -o is no longer supported, use module imuxsock instead"); + } + break; + case 'p': + if(iCompatibilityMode < 3) { + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + bImUxSockLoaded = 1; + } + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "SystemLogSocketName %s", arg); + legacyOptsEnq(legacyConfLine); + } else { + fprintf(stderr, "error -p is no longer supported, use module imuxsock instead"); + } + case 'q': /* add hostname if DNS resolving has failed */ + *net.pACLAddHostnameOnFail = 1; + break; + case 'Q': /* dont resolve hostnames in ACL to IPs */ + *net.pACLDontResolve = 1; + break; + case 'r': /* accept remote messages */ + if(iCompatibilityMode < 3) { + legacyOptsEnq((uchar *) "ModLoad imudp"); + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "UDPServerRun %s", arg); + legacyOptsEnq(legacyConfLine); + } else + fprintf(stderr, "-r option only supported in compatibility modes 0 to 2 - ignored\n"); + break; + case 's': + if (StripDomains) { + fprintf (stderr, "rsyslogd: Only one -s argument allowed, the first one is taken.\n"); + } else { + StripDomains = crunch_list(arg); + } + break; + case 't': /* enable tcp logging */ + if(iCompatibilityMode < 3) { + legacyOptsParseTCP(ch, arg); + } else + fprintf(stderr, "-t option only supported in compatibility modes 0 to 2 - ignored\n"); + break; + case 'u': /* misc user settings */ + if(atoi(arg) == 1) + bParseHOSTNAMEandTAG = 0; + break; + case 'w': /* disable disallowed host warnigs */ + option_DisallowWarning = 0; + break; + case 'x': /* disable dns for remote messages */ + DisableDNS = 1; + break; + case '?': + default: + usage(); + } + } + + if(iRet != RS_RET_END_OF_LINKEDLIST) + FINALIZE; + + /* process compatibility mode settings */ + if(iCompatibilityMode < 3) { + errmsg.LogError(NO_ERRCODE, "WARNING: rsyslogd is running in compatibility mode. Automatically " + "generated config directives may interfer with your rsyslog.conf settings. " + "We suggest upgrading your config and adding -c3 as the first " + "rsyslogd option."); + if(MarkInterval > 0) { + legacyOptsEnq((uchar *) "ModLoad immark"); + snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "MarkMessagePeriod %d", MarkInterval); + legacyOptsEnq(legacyConfLine); + } + if(!bImUxSockLoaded) { + legacyOptsEnq((uchar *) "ModLoad imuxsock"); + } + } + + if(bEOptionWasGiven && iCompatibilityMode < 3) { + errmsg.LogError(NO_ERRCODE, "WARNING: \"message repeated n times\" feature MUST be turned on in " + "rsyslog.conf - CURRENTLY EVERY MESSAGE WILL BE LOGGED. Visit " + "http://www.rsyslog.com/rptdmsgreduction to learn " + "more and cast your vote if you want us to keep this feature."); + } + + checkPermissions(); + thrdInit(); + + if( !(Debug || NoFork) ) + { + dbgprintf("Checking pidfile.\n"); + if (!check_pid(PidFile)) + { + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + sigAct.sa_handler = doexit; + sigaction(SIGTERM, &sigAct, NULL); + + if (fork()) { + /* + * Parent process + */ + sleep(300); + /* + * Not reached unless something major went wrong. 5 + * minutes should be a fair amount of time to wait. + * Please note that this procedure is important since + * the father must not exit before syslogd isn't + * initialized or the klogd won't be able to flush its + * logs. -Joey + */ + exit(1); /* "good" exit - after forking, not diasabling anything */ + } + num_fds = getdtablesize(); + for (i= 0; i < num_fds; i++) + (void) close(i); + untty(); + } + else + { + fputs(" Already running.\n", stderr); + exit(1); /* "good" exit, done if syslogd is already running */ + } + } + else + debugging_on = 1; + + /* tuck my process id away */ + dbgprintf("Writing pidfile %s.\n", PidFile); + if (!check_pid(PidFile)) + { + if (!write_pid(PidFile)) + { + fputs("Can't write pid.\n", stderr); + exit(1); /* exit during startup - questionable */ + } + } + else + { + fputs("Pidfile (and pid) already exist.\n", stderr); + exit(1); /* exit during startup - questionable */ + } + myPid = getpid(); /* save our pid for further testing (also used for messages) */ + + memset(&sigAct, 0, sizeof (sigAct)); + sigemptyset(&sigAct.sa_mask); + + sigAct.sa_handler = sigsegvHdlr; + sigaction(SIGSEGV, &sigAct, NULL); + sigAct.sa_handler = sigsegvHdlr; + sigaction(SIGABRT, &sigAct, NULL); + sigAct.sa_handler = doDie; + sigaction(SIGTERM, &sigAct, NULL); + sigAct.sa_handler = Debug ? doDie : SIG_IGN; + sigaction(SIGINT, &sigAct, NULL); + sigaction(SIGQUIT, &sigAct, NULL); + sigAct.sa_handler = reapchild; + sigaction(SIGCHLD, &sigAct, NULL); + sigAct.sa_handler = Debug ? debug_switch : SIG_IGN; + sigaction(SIGUSR1, &sigAct, NULL); + sigAct.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sigAct, NULL); + sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */ + + mainThread(); + + /* do any de-init's that need to be done AFTER this comment */ + + die(bFinished); + + thrdExit(); + +finalize_it: + if(iRet != RS_RET_OK) + fprintf(stderr, "rsyslogd run failed with error %d\n(see rsyslog.h " + "or http://www.rsyslog.com/errcode to learn what that number means)\n", iRet); + + ENDfunc + return 0; +} + + +/* This is the main entry point into rsyslogd. This must be a function in its own + * right in order to intialize the debug system in a portable way (otherwise we would + * need to have a statement before variable definitions. + * rgerhards, 20080-01-28 + */ +int main(int argc, char **argv) +{ + dbgClassInit(); + return realMain(argc, argv); +} + +/* vim:set ai: + */ diff --git a/tools/syslogd.h b/tools/syslogd.h new file mode 100644 index 00000000..01580a51 --- /dev/null +++ b/tools/syslogd.h @@ -0,0 +1,100 @@ +/* common header for syslogd + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef SYSLOGD_H_INCLUDED +#define SYSLOGD_H_INCLUDED 1 + +#include "syslogd-types.h" +#include "objomsr.h" +#include "modules.h" +#include "template.h" +#include "action.h" +#include "linkedlist.h" +#include "expr.h" + + +#ifndef _PATH_CONSOLE +#define _PATH_CONSOLE "/dev/console" +#endif + + +/* This structure represents the files that will have log + * copies printed. + * RGerhards 2004-11-08: Each instance of the filed structure + * describes what I call an "output channel". This is important + * to mention as we now allow database connections to be + * present in the filed structure. If helps immensely, if we + * think of it as the abstraction of an output channel. + * rgerhards, 2005-10-26: The structure below provides ample + * opportunity for non-thread-safety. Each of the variable + * accesses must be carefully evaluated, many of them probably + * be guarded by mutexes. But beware of deadlocks... + * rgerhards, 2007-08-01: as you can see, the structure has shrunk pretty much. I will + * remove some of the comments some time. It's still the structure that controls much + * of the processing that goes on in syslogd, but it now has lots of helpers. + */ +struct filed { + struct filed *f_next; /* next in linked list */ + /* filter properties */ + enum { + FILTER_PRI = 0, /* traditional PRI based filer */ + FILTER_PROP = 1, /* extended filter, property based */ + FILTER_EXPR = 2 /* extended filter, expression based */ + } f_filter_type; + EHostnameCmpMode eHostnameCmpMode; + cstr_t *pCSHostnameComp; /* hostname to check */ + cstr_t *pCSProgNameComp; /* tag to check or NULL, if not to be checked */ + union { + u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ + struct { + cstr_t *pCSPropName; + enum { + FIOP_NOP = 0, /* do not use - No Operation */ + FIOP_CONTAINS = 1, /* contains string? */ + FIOP_ISEQUAL = 2, /* is (exactly) equal? */ + FIOP_STARTSWITH = 3, /* starts with a string? */ + FIOP_REGEX = 4 /* matches a regular expression? */ + } operation; + cstr_t *pCSCompValue; /* value to "compare" against */ + char isNegated; /* actually a boolean ;) */ + } prop; + expr_t *f_expr; /* expression object */ + } f_filterData; + + linkedList_t llActList; /* list of configured actions */ +}; + + +#include "net.h" /* TODO: remove when you remoe isAllowedSender from here! */ +void untty(void); +rsRetVal selectorConstruct(selector_t **ppThis); +rsRetVal selectorDestruct(void *pVal); +rsRetVal selectorAddList(selector_t *f); +/* the following prototypes should go away once we have an input + * module interface -- rgerhards, 2007-12-12 + */ +rsRetVal logmsgInternal(int pri, char *msg, int flags); +void logmsg(msg_t *pMsg, int flags); +extern int NoHops; +extern int send_to_all; +extern int Debug; +#include "dirty.h" + +#endif /* #ifndef SYSLOGD_H_INCLUDED */ -- cgit v1.2.3 From d071de578454754c4701285b3569e55c5cef1ee4 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Wed, 16 Apr 2008 15:45:13 +0200 Subject: removed no longer needed things ... and some more cleanup. Also moved a file that I forgot (thanks to Michael Biebl for pointing that out). --- conf.c | 1 - dirty.h | 40 +++++++++++++++++++--------------------- glbl.h | 41 ----------------------------------------- runtime/glbl.h | 41 +++++++++++++++++++++++++++++++++++++++++ runtime/msg.c | 11 ----------- runtime/net.c | 4 ---- runtime/srUtils.h | 1 + runtime/srutils.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ tools/omfile.c | 2 -- tools/syslogd.c | 53 +---------------------------------------------------- 10 files changed, 108 insertions(+), 132 deletions(-) delete mode 100644 glbl.h create mode 100644 runtime/glbl.h diff --git a/conf.c b/conf.c index 721ea4a7..dad9ebf2 100644 --- a/conf.c +++ b/conf.c @@ -505,7 +505,6 @@ rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEn } else { /* template specified, pick it up */ if(rsCStrConstruct(&pStrB) != RS_RET_OK) { - glblHadMemShortage = 1; iRet = RS_RET_OUT_OF_MEMORY; goto finalize_it; } diff --git a/dirty.h b/dirty.h index 5783daf8..f0664639 100644 --- a/dirty.h +++ b/dirty.h @@ -29,33 +29,14 @@ #define MAXLINE 2048 /* maximum line length */ -#define MSG_PARSE_HOSTNAME 1 -#define MSG_DONT_PARSE_HOSTNAME 0 - /* Flags to logmsg(). */ #define NOFLAG 0x000 /* no flag is set (to be used when a flag must be specified and none is required) */ #define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ -#define SYNC_FILE 0x002 /* do fsync on file after printing */ +/* NO LONGER USED: #define SYNC_FILE 0x002 / * do fsync on file after printing */ #define ADDDATE 0x004 /* add a date to the message */ #define MARK 0x008 /* this message is a mark */ -#ifdef USE_NETZIP -/* config param: minimum message size to try compression. The smaller - * the message, the less likely is any compression gain. We check for - * gain before we submit the message. But to do so we still need to - * do the (costly) compress() call. The following setting sets a size - * for which no call to compress() is done at all. This may result in - * a few more bytes being transmited but better overall performance. - * Note: I have not yet checked the minimum UDP packet size. It might be - * that we do not save anything by compressing very small messages, because - * UDP might need to pad ;) - * rgerhards, 2006-11-30 - */ -#define MIN_SIZE_FOR_COMPRESS 60 -#endif - -extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */ extern int DisableDNS; extern char **StripDomains; extern char *LocalDomain; @@ -65,8 +46,10 @@ extern int family; extern int bDropMalPTRMsgs; extern int option_DisallowWarning; +#define MSG_PARSE_HOSTNAME 1 +#define MSG_DONT_PARSE_HOSTNAME 0 + rsRetVal submitMsg(msg_t *pMsg); -int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); rsRetVal logmsgInternal(int pri, char *msg, int flags); rsRetVal parseAndSubmitMessage(char *hname, char *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType); @@ -89,4 +72,19 @@ extern int bReduceRepeatMsgs; #define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ (f)->f_repeatcount = MAXREPEAT; \ } +#ifdef USE_NETZIP +/* config param: minimum message size to try compression. The smaller + * the message, the less likely is any compression gain. We check for + * gain before we submit the message. But to do so we still need to + * do the (costly) compress() call. The following setting sets a size + * for which no call to compress() is done at all. This may result in + * a few more bytes being transmited but better overall performance. + * Note: I have not yet checked the minimum UDP packet size. It might be + * that we do not save anything by compressing very small messages, because + * UDP might need to pad ;) + * rgerhards, 2006-11-30 + */ +#define MIN_SIZE_FOR_COMPRESS 60 +#endif + #endif /* #ifndef DIRTY_H_INCLUDED */ diff --git a/glbl.h b/glbl.h deleted file mode 100644 index 5385006a..00000000 --- a/glbl.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Definition of globally-accessible data items. - * - * This module provides access methods to items of global scope. Most often, - * these globals serve as defaults to initialize local settings. Currently, - * many of them are either constants or global variable references. However, - * this module provides the necessary hooks to change that at any time. - * - * Please note that there currently is no glbl.c file as we do not yet - * have any implementations. - * - * Copyright 2008 Rainer Gerhards and Adiscon GmbH. - * - * This file is part of the rsyslog runtime library. - * - * The rsyslog runtime library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The rsyslog runtime library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the rsyslog runtime library. If not, see . - * - * 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 GLOBALS_H_INCLUDED -#define GLOBALS_H_INCLUDED - -#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */ - -extern uchar *glblModPath; /* module load path */ -extern uchar *pszWorkDir; -#define glblGetWorkDir() (pszWorkDir == NULL ? (uchar*) "" : pszWorkDir) - -#endif /* #ifndef GLOBALS_H_INCLUDED */ diff --git a/runtime/glbl.h b/runtime/glbl.h new file mode 100644 index 00000000..5385006a --- /dev/null +++ b/runtime/glbl.h @@ -0,0 +1,41 @@ +/* Definition of globally-accessible data items. + * + * This module provides access methods to items of global scope. Most often, + * these globals serve as defaults to initialize local settings. Currently, + * many of them are either constants or global variable references. However, + * this module provides the necessary hooks to change that at any time. + * + * Please note that there currently is no glbl.c file as we do not yet + * have any implementations. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * 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 GLOBALS_H_INCLUDED +#define GLOBALS_H_INCLUDED + +#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */ + +extern uchar *glblModPath; /* module load path */ +extern uchar *pszWorkDir; +#define glblGetWorkDir() (pszWorkDir == NULL ? (uchar*) "" : pszWorkDir) + +#endif /* #ifndef GLOBALS_H_INCLUDED */ diff --git a/runtime/msg.c b/runtime/msg.c index e5ed19c6..96bd8cc5 100644 --- a/runtime/msg.c +++ b/runtime/msg.c @@ -685,7 +685,6 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszTIMESTAMP3164 == NULL) { if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -697,7 +696,6 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszTIMESTAMP_MySQL == NULL) { if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -709,7 +707,6 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszTIMESTAMP_PgSQL == NULL) { if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -721,7 +718,6 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszTIMESTAMP3164 == NULL) { if((pM->pszTIMESTAMP3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -733,7 +729,6 @@ char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszTIMESTAMP3339 == NULL) { if((pM->pszTIMESTAMP3339 = malloc(33)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; /* TODO: check this: can it cause a free() of constant memory?) */ } @@ -755,7 +750,6 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszRcvdAt3164 == NULL) { if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -767,7 +761,6 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszRcvdAt_MySQL == NULL) { if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -779,7 +772,6 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszRcvdAt_PgSQL == NULL) { if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -791,7 +783,6 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszRcvdAt3164 == NULL) { if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -803,7 +794,6 @@ char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt) MsgLock(pM); if(pM->pszRcvdAt3339 == NULL) { if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { - glblHadMemShortage = 1; MsgUnlock(pM); return ""; } @@ -1499,7 +1489,6 @@ static uchar *getNOW(eNOWType eNow) struct syslogTime t; if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { - glblHadMemShortage = 1; return NULL; } diff --git a/runtime/net.c b/runtime/net.c index 70e7d6f6..cf033383 100644 --- a/runtime/net.c +++ b/runtime/net.c @@ -131,7 +131,6 @@ static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct All assert(iAllow != NULL); if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { - glblHadMemShortage = 1; return RS_RET_OUT_OF_MEMORY; /* no options left :( */ } @@ -275,7 +274,6 @@ static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedS iSignificantBits = 32; allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { - glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); @@ -292,7 +290,6 @@ static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedS allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(sizeof(struct sockaddr_in))) == NULL) { - glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; @@ -314,7 +311,6 @@ static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedS iSignificantBits = 128; allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { - glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); diff --git a/runtime/srUtils.h b/runtime/srUtils.h index 81d20357..bfce4cbb 100644 --- a/runtime/srUtils.h +++ b/runtime/srUtils.h @@ -91,6 +91,7 @@ void mutexCancelCleanup(void *arg); void srSleep(int iSeconds, int iuSeconds); char *rs_strerror_r(int errnum, char *buf, size_t buflen); int decodeSyslogName(uchar *name, syslogName_t *codetab); +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep); /* mutex operations */ /* some macros to cancel-safe lock a mutex (it will automatically be released diff --git a/runtime/srutils.c b/runtime/srutils.c index f1208c26..cf36493a 100644 --- a/runtime/srutils.c +++ b/runtime/srutils.c @@ -505,5 +505,51 @@ int decodeSyslogName(uchar *name, syslogName_t *codetab) } +/** + * getSubString + * + * Copy a string byte by byte until the occurrence + * of a given separator. + * + * \param ppSrc Pointer to a pointer of the source array of characters. If a + separator detected the Pointer points to the next char after the + separator. Except if the end of the string is dedected ('\n'). + Then it points to the terminator char. + * \param pDst Pointer to the destination array of characters. Here the substing + will be stored. + * \param DstSize Maximum numbers of characters to store. + * \param cSep Separator char. + * \ret int Returns 0 if no error occured. + * + * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time + * so that it treats ' ' as a request for whitespace. But in general, the function and its callers + * should be changed over time, this is not really very good code... + */ +int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) +{ + uchar *pSrc = *ppSrc; + int iErr = 0; /* 0 = no error, >0 = error */ + while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { + *pDst++ = *(pSrc)++; + DstSize--; + } + /* check if the Dst buffer was to small */ + if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { + dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); + iErr = 1; + } + if (*pSrc == '\0' || *pSrc == '\n') + /* this line was missing, causing ppSrc to be invalid when it + * was returned in case of end-of-string. rgerhards 2005-07-29 + */ + *ppSrc = pSrc; + else + *ppSrc = pSrc+1; + *pDst = '\0'; + return iErr; +} + + + /* vim:set ai: */ diff --git a/tools/omfile.c b/tools/omfile.c index 6bdd17eb..4b5eb280 100644 --- a/tools/omfile.c +++ b/tools/omfile.c @@ -281,7 +281,6 @@ int resolveFileSizeLimit(instanceData *pData) */ if((pCmd = (uchar*)strdup((char*)pData->f_sizeLimitCmd)) == NULL) { /* there is not much we can do - we make syslogd close the file in this case */ - glblHadMemShortage = 1; return 1; } @@ -481,7 +480,6 @@ static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsg /* we need to allocate memory for the cache structure */ pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)); if(pCache[iFirstFree] == NULL) { - glblHadMemShortage = TRUE; dbgprintf("prepareDynfile(): could not alloc mem, discarding this request\n"); return -1; } diff --git a/tools/syslogd.c b/tools/syslogd.c index 95a23e99..a356c338 100644 --- a/tools/syslogd.c +++ b/tools/syslogd.c @@ -248,9 +248,6 @@ static pid_t myPid; /* our pid for use in self-generated messages, e.g. on start /* mypid is read-only after the initial fork() */ static int restart = 0; /* do restart (config read) - multithread safe */ -int glblHadMemShortage = 0; /* indicates if we had memory shortage some time during the run */ - - static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be * parsed inside message - rgerhards, 2006-03-13 */ static int bFinished = 0; /* used by termination signal handler, read-only except there @@ -488,7 +485,6 @@ selectorConstruct(selector_t **ppThis) assert(ppThis != NULL); if((pThis = (selector_t*) calloc(1, sizeof(selector_t))) == NULL) { - glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } CHKiRet(llInit(&pThis->llActList, actionDestruct, NULL, NULL)); @@ -669,7 +665,7 @@ rsRetVal printline(char *hname, char *msg, int bParseHost, int flags, flowContro if(MsgSetUxTradMsg(pMsg, p) != 0) ABORT_FINALIZE(RS_RET_ERR); - logmsg(pMsg, flags | SYNC_FILE); + logmsg(pMsg, flags); finalize_it: RETiRet; @@ -1926,8 +1922,6 @@ die(int sig) tplDeleteAll(); remove_pid(PidFile); - if(glblHadMemShortage) - dbgprintf("Had memory shortage at least once during the run.\n"); /* de-init some modules */ modExitIminternal(); @@ -2466,51 +2460,6 @@ void sighup_handler() } -/** - * getSubString - * - * Copy a string byte by byte until the occurrence - * of a given separator. - * - * \param ppSrc Pointer to a pointer of the source array of characters. If a - separator detected the Pointer points to the next char after the - separator. Except if the end of the string is dedected ('\n'). - Then it points to the terminator char. - * \param pDst Pointer to the destination array of characters. Here the substing - will be stored. - * \param DstSize Maximum numbers of characters to store. - * \param cSep Separator char. - * \ret int Returns 0 if no error occured. - * - * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time - * so that it treats ' ' as a request for whitespace. But in general, the function and its callers - * should be changed over time, this is not really very good code... - */ -int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) -{ - uchar *pSrc = *ppSrc; - int iErr = 0; /* 0 = no error, >0 = error */ - while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { - *pDst++ = *(pSrc)++; - DstSize--; - } - /* check if the Dst buffer was to small */ - if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { - dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); - iErr = 1; - } - if (*pSrc == '\0' || *pSrc == '\n') - /* this line was missing, causing ppSrc to be invalid when it - * was returned in case of end-of-string. rgerhards 2005-07-29 - */ - *ppSrc = pSrc; - else - *ppSrc = pSrc+1; - *pDst = '\0'; - return iErr; -} - - /* this function pulls all internal messages from the buffer * and puts them into the processing engine. * We can only do limited error handling, as this would not -- cgit v1.2.3