diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/imtuxedoulog/imtuxedoulog.c | 860 |
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 |