summaryrefslogtreecommitdiffstats
path: root/contrib/imtuxedoulog/imtuxedoulog.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--contrib/imtuxedoulog/imtuxedoulog.c860
1 files changed, 860 insertions, 0 deletions
diff --git a/contrib/imtuxedoulog/imtuxedoulog.c b/contrib/imtuxedoulog/imtuxedoulog.c
new file mode 100644
index 0000000..ffb863c
--- /dev/null
+++ b/contrib/imtuxedoulog/imtuxedoulog.c
@@ -0,0 +1,860 @@
+/* imtuxedoulog.c
+ *
+ * This is the input module for reading Tuxedo ULOG files. The particularity of this file
+ * is that the timestamp is split between the filename (date) and the log line (time).
+ * So this module switches on the date base betwwen files to open only the current file.
+ * The log line is parsed according to the Tuxedo format. The ECID is extracted as a
+ * structured data attribute.
+ *
+ * Work originally begun on 2019-01-11 by Philippe Duveau
+ *
+ * This file is contribution of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h> /* do NOT remove: will soon be done by the module generation macros */
+#include <sys/types.h>
+#include <unistd.h>
+#include <glob.h>
+#include <poll.h>
+#include <fnmatch.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef _AIX
+# include <alloca.h>
+#endif
+#include "rsyslog.h" /* error codes etc... */
+#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 */
+#include "msg.h"
+#include "stream.h"
+#include "errmsg.h"
+#include "glbl.h"
+#include "unicode-helper.h"
+#include "prop.h"
+#include "stringbuf.h"
+#include "ruleset.h"
+#include "ratelimit.h"
+
+struct instanceConf_s {
+ uchar *pszUlogBaseName;
+ uchar *pszCurrFName;
+ struct tm currTm;
+ uchar *pszTag;
+ size_t lenTag;
+ uchar *pszStateFile;
+ uchar *pszBindRuleset;
+ int nMultiSub;
+ int iPersistStateInterval;
+ int iFacility;
+ int iSeverity;
+ strm_t *pStrm; /* its stream (NULL if not assigned) */
+ int maxLinesAtOnce;
+ ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */
+ ratelimit_t *ratelimiter;
+ multi_submit_t multiSub;
+ int nRecords;
+ struct instanceConf_s *next;
+ struct instanceConf_s *prev;
+};
+
+/* config variables */
+struct modConfData_s {
+ rsconf_t *pConf; /* our overall config object */
+};
+
+static instanceConf_t *confRoot = NULL;
+static modConfData_t *loadModConf = NULL; /* modConf ptr to use for the current load process */
+static modConfData_t *runModConf = NULL; /* modConf ptr to use for run process */
+
+
+MODULE_TYPE_INPUT /* must be present for input modules, do not remove */
+MODULE_TYPE_NOKEEP
+MODULE_CNFNAME("imtuxedoulog")
+
+/* defines */
+
+/* Module static data */
+DEF_IMOD_STATIC_DATA /* must be present, starts static data */
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(strm)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(ruleset)
+
+#define NUM_MULTISUB 1024 /* default max number of submits */
+#define DFLT_PollInterval 10
+
+int iPollInterval = DFLT_PollInterval;
+int iPersistStateInterval = 0; /* how often if state file to be persisted? (default 0->never) */
+
+struct syslogTime syslogTz;
+
+static prop_t *pInputName = NULL;
+/* there is only one global inputName for all messages generated by this input */
+
+/* module-global parameters */
+/* input instance parameters */
+static struct cnfparamdescr inppdescr[] = {
+ { "ulogbase", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "tag", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "severity", eCmdHdlrSeverity, 0 },
+ { "facility", eCmdHdlrFacility, 0 },
+ { "ruleset", eCmdHdlrString, 0 },
+ { "maxlinesatonce", eCmdHdlrInt, 0 },
+ { "persiststateinterval", eCmdHdlrInt, 0 },
+ { "maxsubmitatonce", eCmdHdlrInt, 0 }
+};
+static struct cnfparamblk inppblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(inppdescr)/sizeof(struct cnfparamdescr),
+ inppdescr
+ };
+
+#include "im-helper.h" /* must be included AFTER the type definitions! */
+
+static uchar * mkFileNameWithTime(instanceConf_t *in)
+{
+ uchar out[MAXFNAME];
+ struct timeval tp;
+#if defined(__hpux)
+ struct timezone tz;
+ gettimeofday(&tp, &tz);
+#else
+ gettimeofday(&tp, NULL);
+#endif
+ localtime_r(&tp.tv_sec, &(in->currTm));
+ snprintf((char*)out, MAXFNAME, "%s.%02d%02d%02d", (char*)in->pszUlogBaseName,
+ in->currTm.tm_mon+1, in->currTm.tm_mday, in->currTm.tm_year % 100);
+ return ustrdup(out);
+}
+
+/*
+* Helper function to combine statefile and workdir
+*/
+static int getFullStateFileName(uchar* pszstatefile, uchar* pszout, int ilenout)
+{
+ int lenout;
+ const uchar* pszworkdir;
+
+ /* Get Raw Workdir, if it is NULL we need to propper handle it */
+ pszworkdir = glblGetWorkDirRaw(runModConf->pConf);
+
+ /* Construct file name */
+ lenout = snprintf((char*)pszout, ilenout, "%s/%s",
+ (char*) (pszworkdir == NULL ? "." : (char*) pszworkdir), (char*)pszstatefile);
+
+ /* return out length */
+ return lenout;
+}
+
+/* this generates a state file name suitable for the current file. To avoid
+ * malloc calls, it must be passed a buffer which should be MAXFNAME large.
+ * Note: the buffer is not necessarily populated ... always ONLY use the
+ * RETURN VALUE!
+ */
+static uchar * ATTR_NONNULL(2) getStateFileName(instanceConf_t *const __restrict__ pInst,
+ uchar *const __restrict__ buf,
+ size_t lenbuf,
+ const uchar *pszFileName)
+{
+ uchar *ret;
+
+ /* Use pszFileName parameter if set */
+ pszFileName = pszFileName == NULL ? pInst->pszUlogBaseName : pszFileName;
+
+ DBGPRINTF("getStateFileName for '%s'\n", pszFileName);
+ if(pInst == NULL || pInst->pszStateFile == NULL) {
+ lenbuf = snprintf((char*)buf, lenbuf - 1, "imtuxedoulog-state:%s", pszFileName);
+ buf[lenbuf] = '\0'; /* be on the safe side... */
+ uchar *p = buf;
+ for( ; *p ; ++p) {
+ if(*p == '/')
+ *p = '-';
+ }
+ ret = buf;
+ } else {
+ ret = pInst->pszStateFile;
+ }
+ return ret;
+}
+
+/* this func parses the line according to samples described in README.md
+ */
+static rsRetVal parseMsg(smsg_t *pMsg, char *rawMsg, size_t msgLen,
+ instanceConf_t *const __restrict__ pInst) {
+ char *prog, *host, *text = NULL, *strtData = NULL, *tmp;
+ int hour, min, sec;
+ rsRetVal ret;
+
+ hour = (rawMsg[0] ^ 0x30) * 10 + (rawMsg[1] ^ 0x30);
+ min = (rawMsg[2] ^ 0x30) * 10 + (rawMsg[3] ^ 0x30);
+ sec = (rawMsg[4] ^ 0x30) * 10 + (rawMsg[5] ^ 0x30);
+
+ if (hour < 0 || hour > 23 || min < 0 || min > 59
+ || sec < 0 || sec > 59)
+ return RS_RET_COULD_NOT_PARSE;
+
+ host = rawMsg + ((rawMsg[10] == '.') ? 11 : 10);
+
+ prog = memchr(host, '!', msgLen-(host - rawMsg));
+
+ if (prog == NULL)
+ return RS_RET_COULD_NOT_PARSE;
+
+ prog++;
+
+ strtData = memchr(prog, ':', msgLen-(prog - rawMsg));
+
+ if (strtData == NULL)
+ return RS_RET_COULD_NOT_PARSE;
+
+ pMsg->tTIMESTAMP.year = pInst->currTm.tm_year + 1900;
+ pMsg->tTIMESTAMP.month = pInst->currTm.tm_mon + 1;
+ pMsg->tTIMESTAMP.day = pInst->currTm.tm_mday;
+ pMsg->tTIMESTAMP.hour = hour;
+ pMsg->tTIMESTAMP.minute = min;
+ pMsg->tTIMESTAMP.second = sec;
+ pMsg->tTIMESTAMP.OffsetMode = syslogTz.OffsetMode;
+ pMsg->tTIMESTAMP.OffsetHour = syslogTz.OffsetHour;
+ pMsg->tTIMESTAMP.OffsetMinute = syslogTz.OffsetMinute;
+
+ pMsg->tTIMESTAMP.secfrac = atoi(rawMsg+7);
+ /* secfracprecision depends on the char on position 9 (case 1 or case 2) */
+ pMsg->tTIMESTAMP.secfracPrecision = (rawMsg[9]=='.') ? 2 : 3;
+
+ for (tmp = strtData ; prog < tmp && *tmp!='.'; tmp--)
+ ;
+ if (tmp > prog)
+ *tmp = '\0';
+ else
+ *strtData = '\0';
+
+ strtData = strtData + 2;
+
+ /* Case 4 */
+ if (memcmp(strtData, "gtrid", 5) == 0)
+ {
+ strtData = memchr(strtData, ':', msgLen-(strtData - rawMsg));
+ if (strtData != NULL) strtData += 2;
+ }
+
+ text = strtData;
+
+ /* ecid point to message text or the word ECID */
+ if (strtData != NULL && memcmp(strtData, "ECID", 4) == 0)
+ {
+ text = memchr(strtData+6, '>', msgLen-(strtData-rawMsg));
+ /* case 3 : we have the word ECID */
+ if (text != NULL)
+ {
+ *(--strtData) = '[';
+ strtData[5] = '=';
+ strtData[6] = '\"';
+ *text++ = '\"';
+ *text++ = ']';
+ text++;
+ ret = MsgAddToStructuredData(pMsg, (uchar*)strtData, text - strtData);
+ if (ret!=RS_RET_OK)
+ LogMsg(0, ret, LOG_WARNING, "Add StructuredData to message failed.");
+ }
+ }
+
+ /* now compute the new length */
+ msgLen -= text - rawMsg;
+
+ if (text != NULL)
+ MsgSetRawMsg(pMsg, text, msgLen);
+
+ MsgSetMSGoffs(pMsg, 0);
+
+ /* set hostname */
+ MsgSetHOSTNAME(pMsg, (const uchar*)host, prog - host - 1);
+
+ if (*prog == '\0')
+ return 0;
+
+ /* set procid */
+ ret = MsgSetPROCID(pMsg, prog);
+ if (ret != RS_RET_OK)
+ LogMsg(0, ret, LOG_WARNING, "Set PROCID to message failed.");
+
+ return RS_RET_OK;
+}
+
+/* enqueue the read file line as a message. The provided string is
+ * not freed - this must be done by the caller.
+ */
+#define MAX_OFFSET_REPRESENTATION_NUM_BYTES 20
+static rsRetVal enqLine(instanceConf_t *const __restrict__ pInst,
+ cstr_t *const __restrict__ cstrLine)
+{
+ DEFiRet;
+ smsg_t *pMsg;
+ const size_t msgLen = cstrLen(cstrLine);
+ rsRetVal ret;
+
+ if(msgLen == 0) {
+ /* we do not process empty lines */
+ FINALIZE;
+ }
+
+ CHKiRet(msgConstruct(&pMsg));
+
+ if (parseMsg(pMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine), msgLen, pInst) != RS_RET_OK) {
+ if ((ret = msgDestruct(&pMsg)) != RS_RET_OK)
+ LogMsg(0, ret, LOG_ERR, "msgDestruct failed.");
+ FINALIZE;
+ }
+
+ MsgSetInputName(pMsg, pInputName);
+ MsgSetTAG(pMsg, pInst->pszTag, pInst->lenTag);
+ msgSetPRI(pMsg, pInst->iFacility | pInst->iSeverity);
+ MsgSetRuleset(pMsg, pInst->pBindRuleset);
+ if ((ret = MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY)) != RS_RET_OK)
+ LogMsg(0, ret, LOG_WARNING, "Set Flow Control to message failed.");
+ if ((ret = MsgSetAPPNAME(pMsg, (const char*)pInst->pszTag)) != RS_RET_OK)
+ LogMsg(0, ret, LOG_WARNING, "Set APPNAME to message failed.");
+
+ if ((iRet = ratelimitAddMsg(pInst->ratelimiter, &pInst->multiSub, pMsg)) != RS_RET_OK) {
+ if ((ret = msgDestruct(&pMsg)) != RS_RET_OK)
+ LogMsg(0, ret, LOG_ERR, "msgDestruct failed.");
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* try to open a file which has a state file. If the state file does not
+ * exist or cannot be read, an error is returned.
+ */
+static rsRetVal ATTR_NONNULL(1) openFileWithStateFile(instanceConf_t *const __restrict__ pInst)
+{
+ DEFiRet;
+ strm_t *psSF = NULL;
+ uchar pszSFNam[MAXFNAME];
+ size_t lenSFNam;
+ struct stat stat_buf;
+ uchar statefile[MAXFNAME];
+
+ uchar *const statefn = getStateFileName(pInst, statefile, sizeof(statefile), NULL);
+ DBGPRINTF("trying to open state for '%s', state file '%s'\n",
+ pInst->pszUlogBaseName, statefn);
+
+ /* Get full path and file name */
+ lenSFNam = getFullStateFileName(statefn, pszSFNam, sizeof(pszSFNam));
+
+ /* check if the file exists */
+ if(stat((char*) pszSFNam, &stat_buf) == -1) {
+ if(errno == ENOENT) {
+ DBGPRINTF("NO state file (%s) exists for '%s'\n", pszSFNam, pInst->pszUlogBaseName);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ } else {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ DBGPRINTF("error trying to access state file for '%s':%s\n",
+ pInst->pszUlogBaseName, errStr);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ }
+
+ /* If we reach this point, we have a state file */
+
+ CHKiRet(strm.Construct(&psSF));
+ CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
+ CHKiRet(strm.SetFName(psSF, pszSFNam, lenSFNam));
+ CHKiRet(strm.SetFileNotFoundError(psSF, 1));
+ CHKiRet(strm.ConstructFinalize(psSF));
+
+ /* read back in the object */
+ CHKiRet(obj.Deserialize(&pInst->pStrm, (uchar*) "strm", psSF, NULL, pInst));
+ DBGPRINTF("deserialized state file, state file base name '%s', "
+ "configured base name '%s'\n", pInst->pStrm->pszFName,
+ pInst->pszUlogBaseName);
+ if(ustrcmp(pInst->pStrm->pszFName, pInst->pszCurrFName)) {
+ LogError(0, RS_RET_STATEFILE_WRONG_FNAME, "imtuxedoulog: state file '%s' "
+ "contains file name '%s', but is used for file '%s'. State "
+ "file deleted, starting from begin of file.",
+ pszSFNam, pInst->pStrm->pszFName, pInst->pszCurrFName);
+
+ unlink((char*)pszSFNam);
+ ABORT_FINALIZE(RS_RET_STATEFILE_WRONG_FNAME);
+ }
+
+ strm.CheckFileChange(pInst->pStrm);
+ CHKiRet(strm.SeekCurrOffs(pInst->pStrm));
+
+ /* note: we do not delete the state file, so that the last position remains
+ * known even in the case that rsyslogd aborts for some reason (like powerfail)
+ */
+
+finalize_it:
+ if(psSF != NULL)
+ strm.Destruct(&psSF);
+
+ RETiRet;
+}
+
+/* try to open a file for which no state file exists. This function does NOT
+ * check if a state file actually exists or not -- this must have been
+ * checked before calling it.
+ */
+static rsRetVal openFileWithoutStateFile(instanceConf_t *const __restrict__ pInst)
+{
+ DEFiRet;
+
+ DBGPRINTF("clean startup withOUT state file for '%s'\n", pInst->pszUlogBaseName);
+ if(pInst->pStrm != NULL)
+ strm.Destruct(&pInst->pStrm);
+ CHKiRet(strm.Construct(&pInst->pStrm));
+ CHKiRet(strm.SettOperationsMode(pInst->pStrm, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(pInst->pStrm, STREAMTYPE_FILE_MONITOR));
+ CHKiRet(strm.SetFName(pInst->pStrm, pInst->pszCurrFName, strlen((char*) pInst->pszCurrFName)));
+ CHKiRet(strm.SetFileNotFoundError(pInst->pStrm, 1));
+ CHKiRet(strm.ConstructFinalize(pInst->pStrm));
+
+finalize_it:
+ RETiRet;
+}
+
+/* try to open a file. This involves checking if there is a status file and,
+ * if so, reading it in. Processing continues from the last known location.
+ */
+static rsRetVal openFile(instanceConf_t *const __restrict__ pInst)
+{
+ DEFiRet;
+
+ CHKiRet_Hdlr(openFileWithStateFile(pInst)) {
+ CHKiRet(openFileWithoutStateFile(pInst));
+ }
+
+ CHKiRet(strm.SetbReopenOnTruncate(pInst->pStrm, 1));
+
+finalize_it:
+ RETiRet;
+}
+
+/* This function persists information for a specific file being monitored.
+ * To do so, it simply persists the stream object. We do NOT abort on error
+ * iRet as that makes matters worse (at least we can try persisting the others...).
+ * rgerhards, 2008-02-13
+ */
+static void persistStrmState(instanceConf_t *pInst)
+{
+ DEFiRet;
+ strm_t *psSF = NULL; /* state file (stream) */
+ size_t lenDir;
+ uchar statefile[MAXFNAME];
+
+ uchar *const statefn = getStateFileName(pInst, statefile, sizeof(statefile), NULL);
+ DBGPRINTF("persisting state for '%s' to file '%s'\n",
+ pInst->pszUlogBaseName, statefn);
+ CHKiRet(strm.Construct(&psSF));
+ lenDir = ustrlen(glbl.GetWorkDir(runModConf->pConf));
+ if(lenDir > 0)
+ CHKiRet(strm.SetDir(psSF, glbl.GetWorkDir(runModConf->pConf), lenDir));
+ CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_WRITE_TRUNC));
+ CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
+ CHKiRet(strm.SetFName(psSF, statefn, strlen((char*) statefn)));
+ CHKiRet(strm.SetFileNotFoundError(psSF, 1));
+ CHKiRet(strm.ConstructFinalize(psSF));
+
+ CHKiRet(strm.Serialize(pInst->pStrm, psSF));
+ CHKiRet(strm.Flush(psSF));
+
+ CHKiRet(strm.Destruct(&psSF));
+
+finalize_it:
+ if(psSF != NULL)
+ strm.Destruct(&psSF);
+
+ if(iRet != RS_RET_OK) {
+ LogError(0, iRet, "imtuxedoulog: could not persist state "
+ "file %s - data may be repeated on next "
+ "startup. Is WorkDirectory set?",
+ statefn);
+ }
+}
+
+/* The following is a cancel cleanup handler for strmReadLine(). It is necessary in case
+ * strmReadLine() is cancelled while processing the stream. -- rgerhards, 2008-03-27
+ */
+static void pollFileCancelCleanup(void *pArg)
+{
+ cstr_t **ppCStr = (cstr_t**) pArg;
+ if(*ppCStr != NULL) {
+ rsCStrDestruct(ppCStr);
+ }
+}
+
+static void pollFileReal(instanceConf_t *pInst, int *pbHadFileData, cstr_t **ppCStr)
+{
+ DEFiRet;
+
+ int nProcessed = 0;
+ if(pInst->pStrm == NULL) {
+ CHKiRet(openFile(pInst)); /* open file */
+ }
+
+ /* loop below will be exited when strmReadLine() returns EOF */
+ while(glbl.GetGlobalInputTermState() == 0) {
+ if(pInst->maxLinesAtOnce != 0 && nProcessed >= pInst->maxLinesAtOnce)
+ break;
+ CHKiRet(strm.ReadLine(pInst->pStrm, ppCStr, 0, 0, NULL, -1, NULL));
+ ++nProcessed;
+ if(pbHadFileData != NULL)
+ *pbHadFileData = 1; /* this is just a flag, so set it and forget it */
+ CHKiRet(enqLine(pInst, *ppCStr)); /* process line */
+ rsCStrDestruct(ppCStr); /* discard string (must be done by us!) */
+
+ if(pInst->iPersistStateInterval > 0 && ++pInst->nRecords >= pInst->iPersistStateInterval) {
+ persistStrmState(pInst);
+ pInst->nRecords = 0;
+ }
+ }
+
+finalize_it:
+ multiSubmitFlush(&pInst->multiSub);
+
+ if(*ppCStr != NULL) {
+ rsCStrDestruct(ppCStr);
+ }
+}
+
+/* poll a file, need to check file rollover etc. open file if not open */
+static void pollFile(instanceConf_t *pInst, int *pbHadFileData)
+{
+ cstr_t *pCStr = NULL;
+ /* Note: we must do pthread_cleanup_push() immediately, because the POSIX macros
+ * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14
+ */
+ pthread_cleanup_push(pollFileCancelCleanup, &pCStr);
+ pollFileReal(pInst, pbHadFileData, &pCStr);
+ pthread_cleanup_pop(0);
+}
+
+
+/* create input instance, set default parameters, and
+ * add it to the list of instances.
+ */
+static rsRetVal ATTR_NONNULL(1) createInstance(instanceConf_t **const pinst)
+{
+ instanceConf_t *inst;
+ DEFiRet;
+ CHKmalloc(inst = malloc(sizeof(instanceConf_t)));
+ inst->next = NULL;
+ inst->pBindRuleset = NULL;
+ inst->ratelimiter = NULL;
+ inst->pStrm = NULL;
+ inst->multiSub.ppMsgs = NULL;
+
+ inst->pszBindRuleset = NULL;
+ inst->pszUlogBaseName = NULL;
+ inst->pszCurrFName = NULL;
+ inst->pszTag = NULL;
+ inst->pszStateFile = NULL;
+ inst->nMultiSub = NUM_MULTISUB;
+ inst->iSeverity = 5;
+ inst->iFacility = 128;
+ inst->maxLinesAtOnce = 0;
+ inst->iPersistStateInterval = 0;
+ inst->nRecords = 0;
+
+ *pinst = inst;
+finalize_it:
+ RETiRet;
+}
+
+/* This adds a new listener object to the bottom of the list, but
+ * it does NOT initialize any data members except for the list
+ * pointers themselves.
+ */
+static rsRetVal ATTR_NONNULL() lstnAdd(instanceConf_t *pInst)
+{
+ DEFiRet;
+ CHKiRet(ratelimitNew(&pInst->ratelimiter, "imtuxedoulog", (char*)pInst->pszUlogBaseName));
+ CHKmalloc(pInst->multiSub.ppMsgs = malloc(pInst->nMultiSub * sizeof(smsg_t *)));
+ pInst->multiSub.maxElem = pInst->nMultiSub;
+ pInst->multiSub.nElem = 0;
+
+ /* insert it at the begin of the list */
+ pInst->prev = NULL;
+ pInst->next = confRoot;
+
+ if (confRoot != NULL)
+ confRoot->prev = pInst;
+
+ confRoot = pInst;
+
+finalize_it:
+ RETiRet;
+}
+
+/* delete a listener object */
+static void ATTR_NONNULL(1) lstnDel(instanceConf_t *pInst)
+{
+ DBGPRINTF("lstnDel called for %s\n", pInst->pszUlogBaseName);
+ if(pInst->pStrm != NULL) { /* stream open? */
+ persistStrmState(pInst);
+ strm.Destruct(&(pInst->pStrm));
+ }
+ if (pInst->ratelimiter != NULL)
+ ratelimitDestruct(pInst->ratelimiter);
+ if (pInst->multiSub.ppMsgs != NULL)
+ free(pInst->multiSub.ppMsgs);
+
+ free(pInst->pszUlogBaseName);
+ if (pInst->pszCurrFName != NULL)
+ free(pInst->pszCurrFName);
+ if (pInst->pszTag)
+ free(pInst->pszTag);
+ if (pInst->pszStateFile)
+ free(pInst->pszStateFile);
+ if (pInst->pszBindRuleset != NULL)
+ free(pInst->pszBindRuleset);
+ free(pInst);
+}
+
+/* Monitor files in traditional polling mode.
+ */
+static void do_polling(void)
+{
+ int bHadFileData; /* were there at least one file with data during this run? */
+ struct stat sb;
+ while(glbl.GetGlobalInputTermState() == 0) {
+ do {
+ instanceConf_t *pInst;
+ bHadFileData = 0;
+ for(pInst = confRoot ; pInst != NULL ; pInst = pInst->next) {
+ uchar *temp = mkFileNameWithTime(pInst);
+
+ DBGPRINTF("imtuxedoulog: do_polling start '%s' / '%s'\n", pInst->pszUlogBaseName, temp);
+ /*
+ * Is the file name is different : a rotation time is reached
+ * If so, then it the new file exists ? and is a file ?
+ */
+ if (temp && stat((const char*)temp, &sb) == 0 && S_ISREG(sb.st_mode) &&
+ (pInst->pszCurrFName == NULL ||
+ strcmp((char*)temp,(char*)pInst->pszCurrFName) != 0))
+ {
+ DBGPRINTF("imtuxedoulog: timed file : rotation reach "
+ "switching form '%s' to '%s' !",
+ (char*)pInst->pszUlogBaseName, temp );
+
+ /* first of all change the listener datas */
+ if (pInst->pszCurrFName != NULL) {
+ free(pInst->pszCurrFName);
+ strm.Destruct(&pInst->pStrm);
+ }
+ pInst->pszCurrFName = temp;
+ temp = NULL;
+
+ /* And finish by destroy the stream object, so the next polling will recreate
+ * it based on new data.
+ */
+ if(glbl.GetGlobalInputTermState() == 1)
+ break; /* terminate input! */
+ }
+ if (temp) free(temp);
+
+ /* let's poll the file */
+ if (pInst->pszCurrFName != NULL)
+ pollFile(pInst, &bHadFileData);
+
+ DBGPRINTF("imtuxedoulog: do_polling end for '%s'\n", pInst->pszUlogBaseName);
+ if (pInst->iPersistStateInterval == -1)
+ persistStrmState(pInst);
+ }
+ } while(bHadFileData == 1 && glbl.GetGlobalInputTermState() == 0);
+
+ /* Note: the additional 10ns wait is vitally important. It guards rsyslog
+ * against totally hogging the CPU if the users selects a polling interval
+ * of 0 seconds. It doesn't hurt any other valid scenario. So do not remove.
+ * rgerhards, 2008-02-14
+ */
+ if(glbl.GetGlobalInputTermState() == 0)
+ srSleep(iPollInterval, 10);
+ }
+}
+
+BEGINnewInpInst
+ struct cnfparamvals *pvals;
+ instanceConf_t *inst;
+ int i;
+CODESTARTnewInpInst
+ DBGPRINTF("newInpInst (imtuxedoulog)\n");
+
+ pvals = nvlstGetParams(lst, &inppblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ if(Debug) {
+ DBGPRINTF("input param blk in imtuxedoulog:\n");
+ cnfparamsPrint(&inppblk, pvals);
+ }
+
+ CHKiRet(createInstance(&inst));
+
+ for(i = 0 ; i < inppblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(inppblk.descr[i].name, "ulogbase")) {
+ inst->pszUlogBaseName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "tag")) {
+ inst->pszTag = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ inst->lenTag = es_strlen(pvals[i].val.d.estr);
+ } else if(!strcmp(inppblk.descr[i].name, "ruleset")) {
+ inst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(inppblk.descr[i].name, "severity")) {
+ inst->iSeverity = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "facility")) {
+ inst->iFacility = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxlinesatonce")) {
+ inst->maxLinesAtOnce = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "persiststateinterval")) {
+ inst->iPersistStateInterval = pvals[i].val.d.n;
+ } else if(!strcmp(inppblk.descr[i].name, "maxsubmitatonce")) {
+ inst->nMultiSub = pvals[i].val.d.n;
+ } else {
+ DBGPRINTF("program error, non-handled "
+ "param '%s'\n", inppblk.descr[i].name);
+ }
+ }
+ if(inst->pszUlogBaseName == NULL) {
+ lstnDel(inst);
+ LogError(0, RS_RET_FILE_NOT_SPECIFIED,
+ "ulogbase is not configured - no input will be gathered");
+ ABORT_FINALIZE(RS_RET_FILE_NOT_SPECIFIED);
+ }
+
+ if ((iRet = lstnAdd(inst)) != RS_RET_OK) {
+ LogError(0, iRet,
+ "add input %s to list failed", inst->pszUlogBaseName);
+ lstnDel(inst);
+ ABORT_FINALIZE(iRet);
+ }
+
+finalize_it:
+CODE_STD_FINALIZERnewInpInst
+ cnfparamvalsDestruct(pvals, &inppblk);
+ENDnewInpInst
+
+BEGINbeginCnfLoad
+CODESTARTbeginCnfLoad
+ loadModConf = pModConf;
+ pModConf->pConf = pConf;
+ENDbeginCnfLoad
+
+BEGINendCnfLoad
+CODESTARTendCnfLoad
+ENDendCnfLoad
+
+BEGINcheckCnf
+instanceConf_t *inst;
+CODESTARTcheckCnf
+ for(inst = confRoot ; inst != NULL ; inst = inst->next) {
+ std_checkRuleset(pModConf , inst);
+ }
+ENDcheckCnf
+
+BEGINactivateCnf
+CODESTARTactivateCnf
+ runModConf = pModConf;
+ENDactivateCnf
+
+BEGINfreeCnf
+CODESTARTfreeCnf
+ENDfreeCnf
+
+BEGINrunInput
+CODESTARTrunInput
+ do_polling();
+ DBGPRINTF("terminating upon request of rsyslog core\n");
+ENDrunInput
+
+BEGINwillRun
+CODESTARTwillRun
+ /* we need to create the inputName property (only once during our lifetime) */
+ CHKiRet(prop.Construct(&pInputName));
+ CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imtuxedoulog"), sizeof("imtuxedoulog") - 1));
+ CHKiRet(prop.ConstructFinalize(pInputName));
+finalize_it:
+ENDwillRun
+
+BEGINafterRun
+CODESTARTafterRun
+ while(confRoot != NULL) {
+ instanceConf_t *inst = confRoot;
+ confRoot = confRoot->next;
+ lstnDel(inst);
+ }
+
+ if(pInputName != NULL)
+ prop.Destruct(&pInputName);
+ENDafterRun
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURENonCancelInputTermination)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+BEGINmodExit
+CODESTARTmodExit
+ /* release objects we used */
+ objRelease(strm, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(ruleset, CORE_COMPONENT);
+ENDmodExit
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_IMOD_QUERIES
+CODEqueryEtryPt_STD_CONF2_QUERIES
+CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES
+CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES
+ENDqueryEtryPt
+
+static inline void
+std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst)
+{
+ LogError(0, NO_ERRCODE, "imtuxedoulog: ruleset '%s' for ULOG base %s not found - "
+ "using default ruleset instead", inst->pszBindRuleset,
+ inst->pszUlogBaseName);
+}
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(strm, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ENDmodInit