diff options
Diffstat (limited to 'plugins/imfile')
-rw-r--r-- | plugins/imfile/Makefile.in | 27 | ||||
-rw-r--r-- | plugins/imfile/imfile.c | 847 |
2 files changed, 776 insertions, 98 deletions
diff --git a/plugins/imfile/Makefile.in b/plugins/imfile/Makefile.in index bce4baa..3e8aab2 100644 --- a/plugins/imfile/Makefile.in +++ b/plugins/imfile/Makefile.in @@ -153,7 +153,6 @@ GREP = @GREP@ GSS_LIBS = @GSS_LIBS@ GUARDTIME_CFLAGS = @GUARDTIME_CFLAGS@ GUARDTIME_LIBS = @GUARDTIME_LIBS@ -HAVE_LIBGCRYPT_CONFIG = @HAVE_LIBGCRYPT_CONFIG@ HAVE_MYSQL_CONFIG = @HAVE_MYSQL_CONFIG@ HAVE_ORACLE_CONFIG = @HAVE_ORACLE_CONFIG@ HAVE_PGSQL_CONFIG = @HAVE_PGSQL_CONFIG@ @@ -174,14 +173,15 @@ LEXLIB = @LEXLIB@ LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ LIBDBI_CFLAGS = @LIBDBI_CFLAGS@ LIBDBI_LIBS = @LIBDBI_LIBS@ -LIBEE_CFLAGS = @LIBEE_CFLAGS@ -LIBEE_LIBS = @LIBEE_LIBS@ LIBESTR_CFLAGS = @LIBESTR_CFLAGS@ LIBESTR_LIBS = @LIBESTR_LIBS@ LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ LIBLOGGING_CFLAGS = @LIBLOGGING_CFLAGS@ LIBLOGGING_LIBS = @LIBLOGGING_LIBS@ +LIBLOGGING_STDLOG_CFLAGS = @LIBLOGGING_STDLOG_CFLAGS@ +LIBLOGGING_STDLOG_LIBS = @LIBLOGGING_STDLOG_LIBS@ LIBLOGNORM_CFLAGS = @LIBLOGNORM_CFLAGS@ LIBLOGNORM_LIBS = @LIBLOGNORM_LIBS@ LIBM = @LIBM@ @@ -206,6 +206,8 @@ NM = @NM@ NMEDIT = @NMEDIT@ OBJDUMP = @OBJDUMP@ OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ ORACLE_CFLAGS = @ORACLE_CFLAGS@ ORACLE_LIBS = @ORACLE_LIBS@ OTOOL = @OTOOL@ @@ -386,22 +388,25 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imfile_la-imfile.Plo@am__quote@ .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $< +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'` +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c index 349acea..d37cb03 100644 --- a/plugins/imfile/imfile.c +++ b/plugins/imfile/imfile.c @@ -5,7 +5,7 @@ * * Work originally begun on 2008-02-01 by Rainer Gerhards * - * Copyright 2008-2012 Adiscon GmbH. + * Copyright 2008-2014 Adiscon GmbH. * * This file is part of rsyslog. * @@ -23,7 +23,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "config.h" /* this is for autotools and always must be the first include */ +#include "config.h" #include <stdio.h> #include <stdlib.h> #include <assert.h> @@ -31,6 +31,12 @@ #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 <fnmatch.h> +#ifdef HAVE_SYS_INOTIFY_H +#include <sys/inotify.h> +#endif #ifdef HAVE_SYS_STAT_H # include <sys/stat.h> #endif @@ -70,8 +76,17 @@ static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config para #define NUM_MULTISUB 1024 /* default max number of submits */ #define DFLT_PollInterval 10 +#define INIT_FILE_TAB_SIZE 4 /* default file table size - is extended as needed, use 2^x value */ +#define INIT_FILE_IN_DIR_TAB_SIZE 1 /* initial size for "associated files tab" in directory table */ +#define INIT_WDMAP_TAB_SIZE 1 /* default wdMap table size - is extended as needed, use 2^x value */ + +/* this structure is used in pure polling mode as well one of the support + * structures for inotify. + */ typedef struct fileInfo_s { uchar *pszFileName; + uchar *pszDirName; + uchar *pszBaseName; uchar *pszTag; size_t lenTag; uchar *pszStateFile; /* file in which state between runs is to be stored */ @@ -81,7 +96,8 @@ typedef struct fileInfo_s { int nRecords; /**< How many records did we process before persisting the stream? */ int iPersistStateInterval; /**< how often should state be persisted? (0=on close only) */ strm_t *pStrm; /* its stream (NULL if not assigned) */ - int readMode; /* which mode to use in ReadMulteLine call? */ + uint8_t readMode; /* which mode to use in ReadMulteLine call? */ + sbool escapeLF; /* escape LF inside the MSG content? */ ruleset_t *pRuleset; /* ruleset to bind listener to (use system default if unspecified) */ ratelimit_t *ratelimiter; multi_submit_t multiSub; @@ -103,6 +119,8 @@ static struct configSettings_s { struct instanceConf_s { uchar *pszFileName; + uchar *pszDirName; + uchar *pszFileBaseName; uchar *pszTag; uchar *pszStateFile; uchar *pszBindRuleset; @@ -110,7 +128,8 @@ struct instanceConf_s { int iPersistStateInterval; int iFacility; int iSeverity; - int readMode; + uint8_t readMode; + sbool escapeLF; int maxLinesAtOnce; ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ struct instanceConf_s *next; @@ -121,25 +140,76 @@ struct instanceConf_s { static rsRetVal persistStrmState(fileInfo_t *pInfo); static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal); + +#define OPMODE_POLLING 0 +#define OPMODE_INOTIFY 1 + /* config variables */ struct modConfData_s { rsconf_t *pConf; /* our overall config object */ int iPollInterval; /* number of seconds to sleep when there was no file activity */ instanceConf_t *root, *tail; + uint8_t opMode; sbool configSetViaV2Method; }; static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */ static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */ static int iFilPtr = 0; /* number of files to be monitored; pointer to next free spot during config */ -#define MAX_INPUT_FILES 100 -static fileInfo_t files[MAX_INPUT_FILES]; +static fileInfo_t *files = NULL; +static int allocMaxFiles; /* max file table size currently allocated */ + +#if HAVE_INOTIFY_INIT +/* support for inotify mode */ + +/* we need to track directories */ +struct dirInfoFiles_s { /* associated files */ + int idx; + int refcnt; /* due to inotify's async nature, we may have multiple + * references to a single file inside our cache - e.g. when + * inodes are removed, and the file name is re-created BUT another + * process (like rsyslogd ;)) holds open the old inode. + */ +}; +typedef struct dirInfoFiles_s dirInfoFiles_t; + +struct dirInfo_s { + uchar *dirName; + dirInfoFiles_t *files; /* associated file entries */ + int currMaxFiles; + int allocMaxFiles; +}; +typedef struct dirInfo_s dirInfo_t; +static dirInfo_t *dirs = NULL; +static int allocMaxDirs; +static int currMaxDirs; + +/* We need to map watch descriptors to our actual objects. Unfortunately, the + * inotify API does not provide us with any cookie, so a simple O(1) algorithm + * cannot be done (what a shame...). We assume that maintaining the array is much + * less often done than looking it up, so we keep the array sorted by watch descriptor + * and do a binary search on the wd we get back. This is at least O(log n), which + * is not too bad for the anticipated use case. + */ +struct wd_map_s { + int wd; /* ascending sort key */ + int fileIdx; /* -1, if this is a dir entry, otherwise index into files table */ + int dirIdx; /* index into dirs table, undefined if fileIdx != -1 */ +}; +typedef struct wd_map_s wd_map_t; +static wd_map_t *wdmap = NULL; +static int nWdmap; +static int allocMaxWdmap; +static int ino_fd; /* fd for inotify calls */ + +#endif /* #if HAVE_INOTIFY_INIT -------------------------------------------------- */ static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this input */ /* module-global parameters */ static struct cnfparamdescr modpdescr[] = { - { "pollinginterval", eCmdHdlrPositiveInt, 0 } + { "pollinginterval", eCmdHdlrPositiveInt, 0 }, + { "mode", eCmdHdlrGetWord, 0 } }; static struct cnfparamblk modpblk = { CNFPARAMBLK_VERSION, @@ -156,6 +226,7 @@ static struct cnfparamdescr inppdescr[] = { { "facility", eCmdHdlrFacility, 0 }, { "ruleset", eCmdHdlrString, 0 }, { "readmode", eCmdHdlrInt, 0 }, + { "escapelf", eCmdHdlrBinary, 0 }, { "maxlinesatonce", eCmdHdlrInt, 0 }, { "maxsubmitatonce", eCmdHdlrInt, 0 }, { "persiststateinterval", eCmdHdlrInt, 0 } @@ -168,6 +239,123 @@ static struct cnfparamblk inppblk = #include "im-helper.h" /* must be included AFTER the type definitions! */ + +#if HAVE_INOTIFY_INIT +/* support for inotify mode */ + +#if 0 /* enable if you need this for debugging */ +static void +dbg_wdmapPrint(char *msg) +{ + int i; + dbgprintf("%s\n", msg); + for(i = 0 ; i < nWdmap ; ++i) + dbgprintf("wdmap[%d]: wd: %d, file %d, dir %d\n", i, + wdmap[i].wd, wdmap[i].fileIdx, wdmap[i].dirIdx); +} +#endif + +static inline rsRetVal +wdmapInit(void) +{ + DEFiRet; + free(wdmap); + CHKmalloc(wdmap = malloc(sizeof(wd_map_t) * INIT_WDMAP_TAB_SIZE)); + allocMaxWdmap = INIT_WDMAP_TAB_SIZE; + nWdmap = 0; +finalize_it: + RETiRet; +} + + +/* compare function for bsearch() */ +static int +wdmap_cmp(const void *k, const void *a) +{ + int key = *((int*) k); + wd_map_t *etry = (wd_map_t*) a; + if(key < etry->wd) + return -1; + else if(key > etry->wd) + return 1; + else + return 0; +} +/* looks up a wdmap entry and returns it's index if found + * or -1 if not found. + */ +static wd_map_t * +wdmapLookup(int wd) +{ + return bsearch(&wd, wdmap, nWdmap, sizeof(wd_map_t), wdmap_cmp); +} + +/* note: we search backwards, as inotify tends to return increasing wd's */ +static rsRetVal +wdmapAdd(int wd, int dirIdx, int fileIdx) +{ + wd_map_t *newmap; + int newmapsize; + int i; + DEFiRet; + + for(i = nWdmap-1 ; i >= 0 && wdmap[i].wd > wd ; --i) + ; /* just scan */ + if(i >= 0 && wdmap[i].wd == wd) { + DBGPRINTF("imfile: wd %d already in wdmap!\n", wd); + FINALIZE; + } + ++i; + /* i now points to the entry that is to be moved upwards (or end of map) */ + if(nWdmap == allocMaxWdmap) { + newmapsize = 2 * allocMaxWdmap; + CHKmalloc(newmap = realloc(wdmap, sizeof(wd_map_t) * newmapsize)); + // TODO: handle the error more intelligently? At all possible? -- 2013-10-15 + wdmap = newmap; + allocMaxWdmap = newmapsize; + } + if(i < nWdmap) { + /* we need to shift to make room for new entry */ + dbgprintf("DDDD: imfile doing wdmap mmemmov(%d, %d, %d) for ADD\n", i,i+1,nWdmap-i); + memmove(wdmap + i, wdmap + i + 1, nWdmap - i); + } + wdmap[i].wd = wd; + wdmap[i].dirIdx = dirIdx; + wdmap[i].fileIdx = fileIdx; + ++nWdmap; + dbgprintf("DDDD: imfile: enter into wdmap[%d]: wd %d, dir %d, file %d\n",i,wd,dirIdx,fileIdx); + +finalize_it: + RETiRet; +} + +static rsRetVal +wdmapDel(int wd) +{ + int i; + DEFiRet; + + for(i = 0 ; i < nWdmap && wdmap[i].wd < wd ; ++i) + ; /* just scan */ + if(i == nWdmap || wdmap[i].wd != wd) { + DBGPRINTF("imfile: wd %d shall be deleted but not in wdmap!\n", wd); + FINALIZE; + } + if(i < nWdmap-1) { + /* we need to shift to delete it (see comment at wdmap definition) */ + dbgprintf("DDDD: imfile doing wdmap mmemmov(%d, %d, %d) for DEL\n", i,i+1,nWdmap-i-1); + memmove(wdmap + i, wdmap + i+1, nWdmap - i-1); + } + --nWdmap; + dbgprintf("DDDD: imfile: wd %d deleted, was idx %d\n", wd, i); + +finalize_it: + RETiRet; +} + +#endif /* #if HAVE_INOTIFY_INIT */ + + /* enqueue the read file line as a message. The provided string is * not freed - thuis must be done by the caller. */ @@ -188,8 +376,8 @@ static rsRetVal enqLine(fileInfo_t *pInfo, cstr_t *cstrLine) MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); MsgSetTAG(pMsg, pInfo->pszTag, pInfo->lenTag); - pMsg->iFacility = LOG_FAC(pInfo->iFacility); - pMsg->iSeverity = LOG_PRI(pInfo->iSeverity); + pMsg->iFacility = pri2fac(pInfo->iFacility); + pMsg->iSeverity = pri2sev(pInfo->iSeverity); MsgSetRuleset(pMsg, pInfo->pRuleset); ratelimitAddMsg(pInfo->ratelimiter, &pInfo->multiSub, pMsg); finalize_it: @@ -234,6 +422,18 @@ openFile(fileInfo_t *pThis) /* read back in the object */ CHKiRet(obj.Deserialize(&pThis->pStrm, (uchar*) "strm", psSF, NULL, pThis)); + DBGPRINTF("imfile: deserialized state file, state file base name '%s', " + "configured base name '%s'\n", pThis->pStrm->pszFName, + pThis->pszFileName); + if(ustrcmp(pThis->pStrm->pszFName, pThis->pszFileName)) { + errmsg.LogError(0, RS_RET_STATEFILE_WRONG_FNAME, "imfile: state file '%s' " + "contains file name '%s', but is used for file '%s'. State " + "file deleted, starting from begin of file.", + pszSFNam, pThis->pStrm->pszFName, pThis->pszFileName); + + unlink((char*)pszSFNam); + ABORT_FINALIZE(RS_RET_STATEFILE_WRONG_FNAME); + } strm.CheckFileChange(pThis->pStrm); CHKiRet(strm.SeekCurrOffs(pThis->pStrm)); @@ -281,8 +481,6 @@ static rsRetVal pollFile(fileInfo_t *pThis, int *pbHadFileData) int nProcessed = 0; DEFiRet; - ASSERT(pbHadFileData != NULL); - /* Note: we must do pthread_cleanup_push() immediately, because the POXIS macros * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14 */ @@ -295,9 +493,10 @@ static rsRetVal pollFile(fileInfo_t *pThis, int *pbHadFileData) while(glbl.GetGlobalInputTermState() == 0) { if(pThis->maxLinesAtOnce != 0 && nProcessed >= pThis->maxLinesAtOnce) break; - CHKiRet(strm.ReadLine(pThis->pStrm, &pCStr, pThis->readMode)); + CHKiRet(strm.ReadLine(pThis->pStrm, &pCStr, pThis->readMode, pThis->escapeLF)); ++nProcessed; - *pbHadFileData = 1; /* this is just a flag, so set it and forget it */ + if(pbHadFileData != NULL) + *pbHadFileData = 1; /* this is just a flag, so set it and forget it */ CHKiRet(enqLine(pThis, pCStr)); /* process line */ rsCStrDestruct(&pCStr); /* discard string (must be done by us!) */ if(pThis->iPersistStateInterval > 0 && pThis->nRecords++ >= pThis->iPersistStateInterval) { @@ -341,6 +540,7 @@ createInstance(instanceConf_t **pinst) inst->maxLinesAtOnce = 10240; inst->iPersistStateInterval = 0; inst->readMode = 0; + inst->escapeLF = 1; /* node created, let's add to config */ if(loadModConf->tail == NULL) { @@ -356,6 +556,65 @@ finalize_it: } +/* this function checks instance parameters and does some required pre-processing + * (e.g. split filename in path and actual name) + * Note: we do NOT use dirname()/basename() as they have portability problems. + */ +static rsRetVal +checkInstance(instanceConf_t *inst) +{ + char dirn[MAXFNAME]; + char basen[MAXFNAME]; + int i; + int lenName; + struct stat sb; + int r; + int eno; + char errStr[512]; + DEFiRet; + + lenName = ustrlen(inst->pszFileName); + for(i = lenName ; i >= 0 ; --i) { + if(inst->pszFileName[i] == '/') { + /* found basename component */ + if(i == lenName) + basen[0] = '\0'; + else { + memcpy(basen, inst->pszFileName+i+1, lenName-i); + /* Note \0 is copied above! */ + //basen[(lenName-i+1)+1] = '\0'; + } + break; + } + } + memcpy(dirn, inst->pszFileName, i); /* do not copy slash */ + dirn[i] = '\0'; + CHKmalloc(inst->pszFileBaseName = (uchar*) strdup(basen)); + CHKmalloc(inst->pszDirName = (uchar*) strdup(dirn)); + + if(dirn[0] == '\0') { + dirn[0] = '/'; + dirn[1] = '\0'; + } + r = stat(dirn, &sb); + if(r != 0) { + eno = errno; + rs_strerror_r(eno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile warning: directory '%s': %s", + dirn, errStr); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + if(!S_ISDIR(sb.st_mode)) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile warning: configured directory " + "'%s' is NOT a directory", dirn); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + +finalize_it: + RETiRet; +} + + /* add a new monitor */ static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) { @@ -389,6 +648,9 @@ static rsRetVal addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal) inst->maxLinesAtOnce = cs.maxLinesAtOnce; inst->iPersistStateInterval = cs.iPersistStateInterval; inst->readMode = cs.readMode; + inst->escapeLF = 0; + + CHKiRet(checkInstance(inst)); /* reset legacy system */ cs.iPersistStateInterval = 0; @@ -405,33 +667,46 @@ static inline rsRetVal addListner(instanceConf_t *inst) { DEFiRet; + int newMax; + fileInfo_t *newFileTab; fileInfo_t *pThis; - if(iFilPtr < MAX_INPUT_FILES) { - pThis = &files[iFilPtr]; - //TODO: optimize, save strdup? - pThis->pszFileName = (uchar*) strdup((char*) inst->pszFileName); - pThis->pszTag = (uchar*) strdup((char*) inst->pszTag); - pThis->lenTag = ustrlen(pThis->pszTag); - pThis->pszStateFile = (uchar*) strdup((char*) inst->pszStateFile); - - CHKiRet(ratelimitNew(&pThis->ratelimiter, "imfile", (char*)inst->pszFileName)); - CHKmalloc(pThis->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(msg_t*))); - pThis->multiSub.maxElem = inst->nMultiSub; - pThis->multiSub.nElem = 0; - pThis->iSeverity = inst->iSeverity; - pThis->iFacility = inst->iFacility; - pThis->maxLinesAtOnce = inst->maxLinesAtOnce; - pThis->iPersistStateInterval = inst->iPersistStateInterval; - pThis->readMode = inst->readMode; - pThis->pRuleset = inst->pBindRuleset; - pThis->nRecords = 0; - } else { - errmsg.LogError(0, RS_RET_OUT_OF_DESRIPTORS, - "Too many file monitors configured - ignoring %s", - inst->pszFileName); - ABORT_FINALIZE(RS_RET_OUT_OF_DESRIPTORS); + if(iFilPtr == allocMaxFiles) { + newMax = 2 * allocMaxFiles; + newFileTab = realloc(files, newMax * sizeof(fileInfo_t)); + if(newFileTab == NULL) { + errmsg.LogError(0, RS_RET_OUT_OF_MEMORY, + "cannot alloc memory to monitor file '%s' - ignoring", + inst->pszFileName); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + files = newFileTab; + allocMaxFiles = newMax; + DBGPRINTF("imfile: increased file table to %d entries\n", allocMaxFiles); } + + /* if we reach this point, there is space in the file table for the new entry */ + pThis = &files[iFilPtr]; + pThis->pszFileName = (uchar*) strdup((char*) inst->pszFileName); + pThis->pszDirName = inst->pszDirName; /* use value from inst! */ + pThis->pszBaseName = inst->pszFileBaseName; /* use value from inst! */ + pThis->pszTag = (uchar*) strdup((char*) inst->pszTag); + pThis->lenTag = ustrlen(pThis->pszTag); + pThis->pszStateFile = (uchar*) strdup((char*) inst->pszStateFile); + + CHKiRet(ratelimitNew(&pThis->ratelimiter, "imfile", (char*)inst->pszFileName)); + CHKmalloc(pThis->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(msg_t*))); + pThis->multiSub.maxElem = inst->nMultiSub; + pThis->multiSub.nElem = 0; + pThis->iSeverity = inst->iSeverity; + pThis->iFacility = inst->iFacility; + pThis->maxLinesAtOnce = inst->maxLinesAtOnce; + pThis->iPersistStateInterval = inst->iPersistStateInterval; + pThis->readMode = inst->readMode; + pThis->escapeLF = inst->escapeLF; + pThis->pRuleset = inst->pBindRuleset; + pThis->nRecords = 0; + pThis->pStrm = NULL; ++iFilPtr; /* we got a new file to monitor */ resetConfigVariables(NULL, NULL); /* values are both dummies */ @@ -449,8 +724,6 @@ CODESTARTnewInpInst pvals = nvlstGetParams(lst, &inppblk, NULL); if(pvals == NULL) { - errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, - "imfile: required parameter are missing\n"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } @@ -475,12 +748,14 @@ CODESTARTnewInpInst } else if(!strcmp(inppblk.descr[i].name, "severity")) { inst->iSeverity = pvals[i].val.d.n; } else if(!strcmp(inppblk.descr[i].name, "facility")) { - inst->iSeverity = pvals[i].val.d.n; + inst->iFacility = pvals[i].val.d.n; } else if(!strcmp(inppblk.descr[i].name, "readmode")) { - inst->readMode = pvals[i].val.d.n; + inst->readMode = (uint8_t) pvals[i].val.d.n; + } else if(!strcmp(inppblk.descr[i].name, "escapelf")) { + inst->escapeLF = (sbool) 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")) { + } 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; @@ -489,6 +764,7 @@ CODESTARTnewInpInst "param '%s'\n", inppblk.descr[i].name); } } + CHKiRet(checkInstance(inst)); finalize_it: CODE_STD_FINALIZERnewInpInst cnfparamvalsDestruct(pvals, &inppblk); @@ -499,6 +775,7 @@ CODESTARTbeginCnfLoad loadModConf = pModConf; pModConf->pConf = pConf; /* init our settings */ + loadModConf->opMode = OPMODE_POLLING; loadModConf->iPollInterval = DFLT_PollInterval; loadModConf->configSetViaV2Method = 0; bLegacyCnfModGlobalsPermitted = 1; @@ -520,6 +797,7 @@ BEGINsetModCnf struct cnfparamvals *pvals = NULL; int i; CODESTARTsetModCnf + loadModConf->opMode = OPMODE_INOTIFY; /* new style config has different default! */ pvals = nvlstGetParams(lst, &modpblk, NULL); if(pvals == NULL) { errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "imfile: error processing module " @@ -537,6 +815,17 @@ CODESTARTsetModCnf continue; if(!strcmp(modpblk.descr[i].name, "pollinginterval")) { loadModConf->iPollInterval = (int) pvals[i].val.d.n; + } else if(!strcmp(modpblk.descr[i].name, "mode")) { + if(!es_strconstcmp(pvals[i].val.d.estr, "polling")) + loadModConf->opMode = OPMODE_POLLING; + else if(!es_strconstcmp(pvals[i].val.d.estr, "inotify")) + loadModConf->opMode = OPMODE_INOTIFY; + else { + char *cstr = es_str2cstr(pvals[i].val.d.estr, NULL); + errmsg.LogError(0, RS_RET_PARAM_ERROR, "imfile: unknown " + "mode '%s'", cstr); + free(cstr); + } } else { dbgprintf("imfile: program error, non-handled " "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); @@ -561,7 +850,9 @@ CODESTARTendCnfLoad /* persist module-specific settings from legacy config system */ loadModConf->iPollInterval = cs.iPollInterval; } - dbgprintf("imfile: polling interval is %d\n", loadModConf->iPollInterval); + dbgprintf("imfile: opmode is %d, polling interval is %d\n", + loadModConf->opMode, + loadModConf->iPollInterval); loadModConf = NULL; /* done loading */ /* free legacy config vars */ @@ -593,10 +884,16 @@ BEGINactivateCnf instanceConf_t *inst; CODESTARTactivateCnf runModConf = pModConf; + free(files); /* clear any previous instance */ + CHKmalloc(files = (fileInfo_t*) malloc(sizeof(fileInfo_t) * INIT_FILE_TAB_SIZE)); + allocMaxFiles = INIT_FILE_TAB_SIZE; + iFilPtr = 0; + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { addListner(inst); } - /* if we could not set up any listners, there is no point in running... */ + + /* if we could not set up any listeners, there is no point in running... */ if(iFilPtr == 0) { errmsg.LogError(0, NO_ERRCODE, "imfile: no file monitors could be started, " "input not activated.\n"); @@ -612,39 +909,19 @@ CODESTARTfreeCnf for(inst = pModConf->root ; inst != NULL ; ) { free(inst->pszBindRuleset); free(inst->pszFileName); + free(inst->pszDirName); + free(inst->pszFileBaseName); free(inst->pszTag); free(inst->pszStateFile); del = inst; inst = inst->next; free(del); } + free(files); ENDfreeCnf - -/* This function is the cancel cleanup handler. It is called when rsyslog decides the - * module must be stopped, what most probably happens during shutdown of rsyslogd. When - * this function is called, the runInput() function (below) is already terminated - somewhere - * in the middle of what it was doing. The cancel cleanup handler below should take - * care of any locked mutexes and such, things that really need to be cleaned up - * before processing continues. In general, many plugins do not need to provide - * any code at all here. - * - * IMPORTANT: the calling interface of this function can NOT be modified. It actually is - * called by pthreads. The provided argument is currently not being used. - */ -static void -inputModuleCleanup(void __attribute__((unused)) *arg) -{ - BEGINfunc - ENDfunc -} - - -/* This function is called by the framework to gather the input. The module stays - * most of its lifetime inside this function. It MUST NEVER exit this function. Doing - * so would end module processing and rsyslog would NOT reschedule the module. If - * you exit from this function, you violate the interface specification! +/* Monitor files in traditional polling mode. * * We go through all files and remember if at least one had data. If so, we do * another run (until no data was present in any file). Then we sleep for @@ -661,12 +938,12 @@ inputModuleCleanup(void __attribute__((unused)) *arg) * On spamming the main queue: keep in mind that it will automatically rate-limit * ourselfes if we begin to overrun it. So we really do not need to care here. */ -#pragma GCC diagnostic ignored "-Wempty-body" -BEGINrunInput +static rsRetVal +doPolling(void) +{ int i; int bHadFileData; /* were there at least one file with data during this run? */ -CODESTARTrunInput - pthread_cleanup_push(inputModuleCleanup, NULL); + DEFiRet; while(glbl.GetGlobalInputTermState() == 0) { do { bHadFileData = 0; @@ -675,24 +952,421 @@ CODESTARTrunInput break; /* terminate input! */ pollFile(&files[i], &bHadFileData); } - } while(iFilPtr > 1 && bHadFileData == 1 && glbl.GetGlobalInputTermState() == 0); /* warning: do...while()! */ + } while(iFilPtr > 1 && bHadFileData == 1 && glbl.GetGlobalInputTermState() == 0); + /* warning: do...while()! */ - /* 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 + /* 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(runModConf->iPollInterval, 10); } - DBGPRINTF("imfile: terminating upon request of rsyslog core\n"); - pthread_cleanup_pop(0); /* just for completeness, but never called... */ - RETiRet; /* use it to make sure the housekeeping is done! */ -ENDrunInput -#pragma GCC diagnostic warning "-Wempty-body" - /* END no-touch zone * - * ------------------------------------------------------------------------------------------ */ + RETiRet; +} + + +#if HAVE_INOTIFY_INIT +/* add entry to dirs array */ +static rsRetVal +dirsAdd(uchar *dirName) +{ + int newMax; + dirInfo_t *newDirTab; + DEFiRet; + if(currMaxDirs == allocMaxDirs) { + newMax = 2 * allocMaxDirs; + newDirTab = realloc(dirs, newMax * sizeof(dirInfo_t)); + if(newDirTab == NULL) { + errmsg.LogError(0, RS_RET_OUT_OF_MEMORY, + "cannot alloc memory to monitor directory '%s' - ignoring", + dirName); + } + dirs = newDirTab; + allocMaxDirs = newMax; + DBGPRINTF("imfile: increased dir table to %d entries\n", allocMaxDirs); + } + + /* if we reach this point, there is space in the file table for the new entry */ + dirs[currMaxDirs].dirName = dirName; + CHKmalloc(dirs[currMaxDirs].files= malloc(sizeof(dirInfoFiles_t) * INIT_FILE_IN_DIR_TAB_SIZE)); + dirs[currMaxDirs].allocMaxFiles = INIT_FILE_IN_DIR_TAB_SIZE; + dirs[currMaxDirs].currMaxFiles= 0; + + ++currMaxDirs; +finalize_it: + RETiRet; +} + +/* checks if a file name is already inside the dirs array. Note that wildcards + * apply. Returns either the array index or -1 if not found. + * i is the index of the dir entry to search. + */ +static int +dirsFindFile(int i, uchar *fn) +{ + int f; + uchar *baseName; + + for(f = 0 ; f < dirs[i].currMaxFiles ; ++f) { + baseName = files[dirs[i].files[f].idx].pszBaseName; + if(!fnmatch((char*)fn, (char*)baseName, FNM_PATHNAME | FNM_PERIOD)) + break; /* found */ + } + if(f == dirs[i].currMaxFiles) + f = -1; + //dbgprintf("DDDD: dir '%s', file '%s', found:%d\n", dirs[i].dirName, fn, f); + return f; +} + +/* checks if a dir name is already inside the dirs array. If so, returns + * its index. If not present, -1 is returned. + */ +static int +dirsFindDir(uchar *dir) +{ + int i; + + for(i = 0 ; i < currMaxDirs && ustrcmp(dir, dirs[i].dirName) ; ++i) + ; /* just scan, all done in for() */ + if(i == currMaxDirs) + i = -1; + //dbgprintf("DDDD: dir '%s', found:%d\n", dir, i); + return i; +} + +static rsRetVal +dirsInit(void) +{ + instanceConf_t *inst; + DEFiRet; + + free(dirs); + CHKmalloc(dirs = malloc(sizeof(dirInfo_t) * INIT_FILE_TAB_SIZE)); + allocMaxDirs = INIT_FILE_TAB_SIZE; + currMaxDirs = 0; + + for(inst = runModConf->root ; inst != NULL ; inst = inst->next) { + if(dirsFindDir(inst->pszDirName) == -1) + dirsAdd(inst->pszDirName); + } + +finalize_it: + RETiRet; +} + +/* add file to directory (create association) + * i is index into file table, all other information is pulled from that table. + */ +static rsRetVal +dirsAddFile(int i) +{ + int dirIdx; + int j; + int newMax; + dirInfoFiles_t *newFileTab; + dirInfo_t *dir; + DEFiRet; + + dirIdx = dirsFindDir(files[i].pszDirName); + if(dirIdx == -1) { + errmsg.LogError(0, RS_RET_INTERNAL_ERROR, "imfile: could not find " + "directory '%s' in dirs array - ignoring", + files[i].pszDirName); + FINALIZE; + } + + dir = dirs + dirIdx; + for(j = 0 ; j < dir->currMaxFiles && dir->files[j].idx != i ; ++j) + ; /* just scan */ + if(j < dir->currMaxFiles) { + /* this is not important enough to send an user error, as all will + * continue to work. */ + ++dir->files[j].refcnt; + DBGPRINTF("imfile: file '%s' already registered in directory '%s', recnt now %d\n", + files[i].pszFileName, dir->dirName, dir->files[j].refcnt); + FINALIZE; + } + + if(dir->currMaxFiles == dir->allocMaxFiles) { + newMax = 2 * allocMaxFiles; + newFileTab = realloc(dirs, newMax * sizeof(dirInfoFiles_t)); + if(newFileTab == NULL) { + errmsg.LogError(0, RS_RET_OUT_OF_MEMORY, + "cannot alloc memory to map directory '%s' file relationship " + "'%s' - ignoring", files[i].pszFileName, dir->dirName); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + dir->files = newFileTab; + dir->allocMaxFiles = newMax; + DBGPRINTF("imfile: increased dir table to %d entries\n", allocMaxDirs); + } + + dir->files[dir->currMaxFiles].idx = i; + dir->files[dir->currMaxFiles].refcnt = 1; + dbgprintf("DDDD: associated file %d[%s] to directory %d[%s]\n", + i, files[i].pszFileName, dirIdx, dir->dirName); + ++dir->currMaxFiles; +finalize_it: + RETiRet; +} + +/* delete a file from directory (remove association) + * fIdx is index into file table, all other information is pulled from that table. + */ +static rsRetVal +dirsDelFile(int fIdx) +{ + int dirIdx; + int j; + dirInfo_t *dir; + DEFiRet; + + dirIdx = dirsFindDir(files[fIdx].pszDirName); + if(dirIdx == -1) { + DBGPRINTF("imfile: could not find directory '%s' in dirs array - ignoring", + files[fIdx].pszDirName); + FINALIZE; + } + + dir = dirs + dirIdx; + for(j = 0 ; j < dir->currMaxFiles && dir->files[j].idx != fIdx ; ++j) + ; /* just scan */ + if(j == dir->currMaxFiles) { + DBGPRINTF("imfile: no association for file '%s' in directory '%s' " + "found - ignoring\n", files[fIdx].pszFileName, dir->dirName); + FINALIZE; + } + dir->files[j].refcnt--; + if(dir->files[j].refcnt == 0) { + /* we remove that entry (but we never shrink the table) */ + if(j < dir->currMaxFiles - 1) { + /* entry in middle - need to move others */ + memmove(dir->files+j, dir->files+j+1, + (dir->currMaxFiles -j-1) * sizeof(dirInfoFiles_t)); + } + --dir->currMaxFiles; + } + DBGPRINTF("imfile: removed association of file '%s' to directory '%s'\n", + files[fIdx].pszFileName, dir->dirName); + +finalize_it: + RETiRet; +} + +static void +in_setupDirWatch(int i) +{ + int wd; + wd = inotify_add_watch(ino_fd, (char*)dirs[i].dirName, IN_CREATE); + if(wd < 0) { + DBGPRINTF("imfile: could not create dir watch for '%s'\n", + files[i].pszFileName); + goto done; + } + wdmapAdd(wd, i, -1); + dbgprintf("DDDD: watch %d added for dir %s\n", wd, dirs[i].dirName); +done: return; +} + +/* Setup a new file watch. + * Note: we need to try to read this file, as it may already contain data this + * needs to be processed, and we won't get an event for that as notifications + * happen only for things after the watch has been activated. + */ +static void +in_setupFileWatch(int i) +{ + int wd; + wd = inotify_add_watch(ino_fd, (char*)files[i].pszFileName, IN_MODIFY); + if(wd < 0) { + DBGPRINTF("imfile: could not create initial file for '%s'\n", + files[i].pszFileName); + goto done; + } + wdmapAdd(wd, -1, i); + dbgprintf("DDDD: watch %d added for file %s\n", wd, files[i].pszFileName); + dirsAddFile(i); + pollFile(&files[i], NULL); +done: return; +} + +/* setup our initial set of watches, based on user config */ +static rsRetVal +in_setupInitialWatches() +{ + int i; + DEFiRet; + + for(i = 0 ; i < currMaxDirs ; ++i) { + in_setupDirWatch(i); + } + for(i = 0 ; i < iFilPtr ; ++i) { + in_setupFileWatch(i); + } + RETiRet; +} + +static void +in_dbg_showEv(struct inotify_event *ev) +{ + if(ev->mask & IN_IGNORED) { + dbgprintf("watch was REMOVED\n"); + } else if(ev->mask & IN_MODIFY) { + dbgprintf("watch was MODIFID\n"); + } else if(ev->mask & IN_ACCESS) { + dbgprintf("watch IN_ACCESS\n"); + } else if(ev->mask & IN_ATTRIB) { + dbgprintf("watch IN_ATTRIB\n"); + } else if(ev->mask & IN_CLOSE_WRITE) { + dbgprintf("watch IN_CLOSE_WRITE\n"); + } else if(ev->mask & IN_CLOSE_NOWRITE) { + dbgprintf("watch IN_CLOSE_NOWRITE\n"); + } else if(ev->mask & IN_CREATE) { + dbgprintf("file was CREATED: %s\n", ev->name); + } else if(ev->mask & IN_DELETE) { + dbgprintf("watch IN_DELETE\n"); + } else if(ev->mask & IN_DELETE_SELF) { + dbgprintf("watch IN_DELETE_SELF\n"); + } else if(ev->mask & IN_MOVE_SELF) { + dbgprintf("watch IN_MOVE_SELF\n"); + } else if(ev->mask & IN_MOVED_FROM) { + dbgprintf("watch IN_MOVED_FROM\n"); + } else if(ev->mask & IN_MOVED_TO) { + dbgprintf("watch IN_MOVED_TO\n"); + } else if(ev->mask & IN_OPEN) { + dbgprintf("watch IN_OPEN\n"); + } else if(ev->mask & IN_ISDIR) { + dbgprintf("watch IN_ISDIR\n"); + } else { + dbgprintf("unknown mask code %8.8x\n", ev->mask); + } +} + +static void +in_handleDirEvent(struct inotify_event *ev, int dirIdx) +{ + int fileIdx; + dbgprintf("DDDD: handle dir event for %s\n", dirs[dirIdx].dirName); + if(!(ev->mask & IN_CREATE)) { + DBGPRINTF("imfile: got non-expected inotify event:\n"); + in_dbg_showEv(ev); + goto done; + } + fileIdx = dirsFindFile(dirIdx, (uchar*)ev->name); + if(fileIdx == -1) { + dbgprintf("imfile: file '%s' not associated with dir '%s'\n", + ev->name, dirs[dirIdx].dirName); + goto done; + } + dbgprintf("DDDD: file '%s' associated with dir '%s'\n", ev->name, dirs[dirIdx].dirName); + in_setupFileWatch(fileIdx); +done: return; +} + +/* inotify told us that a file's wd was closed. We now need to remove + * the file from our internal structures. Remember that a different inode + * with the same name may already be in processing. + */ +static void +in_removeFile(struct inotify_event *ev, int fIdx) +{ + wdmapDel(ev->wd); + dirsDelFile(fIdx); +} + + +static void +in_handleFileEvent(struct inotify_event *ev, int fIdx) +{ + if(ev->mask & IN_MODIFY) { + pollFile(&files[fIdx], NULL); + } else if(ev->mask & IN_IGNORED) { + in_removeFile(ev, fIdx); + } else { + DBGPRINTF("imfile: got non-expected inotify event:\n"); + in_dbg_showEv(ev); + } +} + +static void +in_processEvent(struct inotify_event *ev) +{ + wd_map_t *etry; + + etry = wdmapLookup(ev->wd); + if(etry == NULL) { + DBGPRINTF("imfile: could not lookup wd %d\n", ev->wd); + goto done; + } + dbgprintf("DDDD: imfile: wd %d got file %d, dir %d\n", ev->wd, etry->fileIdx, etry->dirIdx); + if(etry->fileIdx == -1) { /* directory? */ + in_handleDirEvent(ev, etry->dirIdx); + } else { + in_handleFileEvent(ev, etry->fileIdx); + } +done: return; +} + +/* Monitor files in inotify mode */ +static rsRetVal +do_inotify() +{ + char iobuf[8192]; + struct inotify_event *ev; + int rd; + int currev; + DEFiRet; + + CHKiRet(wdmapInit()); + CHKiRet(dirsInit()); + ino_fd = inotify_init(); + DBGPRINTF("imfile: inotify fd %d\n", ino_fd); + CHKiRet(in_setupInitialWatches()); + + while(glbl.GetGlobalInputTermState() == 0) { + rd = read(ino_fd, iobuf, sizeof(iobuf)); + if(rd < 0) { + perror("inotify read"); exit(1); + } + currev = 0; + while(currev < rd) { + ev = (struct inotify_event*) (iobuf+currev); + dbgprintf("DDDD: imfile event notification: rd %d[%d], wd (%d, mask " + "%8.8x, cookie %4.4x, len %d)\n", + (int) rd, currev, ev->wd, ev->mask, ev->cookie, ev->len); + in_dbg_showEv(ev); + in_processEvent(ev); + currev += sizeof(struct inotify_event) + ev->len; + } + } + +finalize_it: + close(ino_fd); + RETiRet; +} + +#endif /* #if HAVE_INOTIFY_INIT */ + +/* This function is called by the framework to gather the input. The module stays + * most of its lifetime inside this function. It MUST NEVER exit this function. Doing + * so would end module processing and rsyslog would NOT reschedule the module. If + * you exit from this function, you violate the interface specification! + */ +BEGINrunInput +CODESTARTrunInput + DBGPRINTF("imfile: working in %s mode\n", + (runModConf->opMode == OPMODE_POLLING) ? "polling" : "inotify"); + if(runModConf->opMode == OPMODE_POLLING) + iRet = doPolling(); + else + iRet = do_inotify(); + + DBGPRINTF("imfile: terminating upon request of rsyslog core\n"); +ENDrunInput /* The function is called by rsyslog before runInput() is called. It is a last chance @@ -834,8 +1508,8 @@ resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unus cs.pszFileName = NULL; free(cs.pszFileTag); cs.pszFileTag = NULL; - free(cs.pszFileTag); - cs.pszFileTag = NULL; + free(cs.pszStateFile); + cs.pszStateFile = NULL; /* set defaults... */ cs.iPollInterval = DFLT_PollInterval; @@ -862,8 +1536,7 @@ std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, insta * complexity of processing is depending on the actual module. However, only * thing absolutely necessary should be done here. Actual app-level processing * is to be performed in runInput(). A good sample of what to do here may be to - * set some variable defaults. The most important thing probably is registration - * of config command handlers. + * set some variable defaults. */ BEGINmodInit() CODESTARTmodInit |