/* 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
