/* mmgrok.c
 * Grok the message is parsed into a structured json data inside JSON.
 */
#include "config.h"
#include "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <json.h>
#include <grok.h>
#include <glib.h>
#include "conf.h"
#include "syslogd-types.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "cfsysline.h"
#include "dirty.h"

MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("mmgrok");

static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal);

DEF_OMOD_STATIC_DATA;

typedef struct result_s {
    char *key;
    int key_len;
    const char *value;
    int value_len;
    char *type;
} result_t;

/* config variables */
typedef struct _instanceData {
    char *pszPatternDir;
    char *pszMatch;
    char *pszSource;
    char *pszTarget; /* as a json root for store parse json data */
    smsg_t *pmsg; /* store  origin messages*/
} instanceData;

typedef struct wrkrInstanceData {
    instanceData *pData;
} wrkrInstanceData_t;

struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
};

static modConfData_t *loadModConf = NULL;
static modConfData_t *runModConf = NULL;

/* action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {
    {"patterndir", eCmdHdlrString, 0},
    {"match", eCmdHdlrString, 0},
    {"source", eCmdHdlrString, 0},
    {"target", eCmdHdlrString, 0},
};

static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, sizeof(actpdescr) / sizeof(struct cnfparamdescr), actpdescr};

BEGINbeginCnfLoad
    CODESTARTbeginCnfLoad;
    loadModConf = pModConf;
    pModConf->pConf = pConf;
ENDbeginCnfLoad

BEGINendCnfLoad
    CODESTARTendCnfLoad;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
    runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
ENDfreeCnf

BEGINcreateInstance
    CODESTARTcreateInstance;
ENDcreateInstance

BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
ENDcreateWrkrInstance

BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
ENDisCompatibleWithFeature

BEGINfreeInstance
    CODESTARTfreeInstance;
    free(pData->pszPatternDir);
    free(pData->pszMatch);
    free(pData->pszSource);
    free(pData->pszTarget);
ENDfreeInstance

BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
ENDfreeWrkrInstance


static inline void setInstParamDefaults(instanceData *pData) {
    pData->pszPatternDir = NULL;
    pData->pszMatch = NULL;
    pData->pszSource = NULL;
    pData->pszTarget = NULL;
    pData->pmsg = NULL;
}


BEGINnewActInst
    struct cnfparamvals *pvals;
    int i;
    CODESTARTnewActInst;
    DBGPRINTF("newActInst (mmgrok)\n");
    if ((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    CODE_STD_STRING_REQUESTnewActInst(1);
    CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG));
    CHKiRet(createInstance(&pData));
    setInstParamDefaults(pData);

    for (i = 0; i < actpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(actpblk.descr[i].name, "patterndir")) {
            pData->pszPatternDir = es_str2cstr(pvals[i].val.d.estr, NULL);
            continue;
        } else if (!strcmp(actpblk.descr[i].name, "match")) {
            pData->pszMatch = es_str2cstr(pvals[i].val.d.estr, NULL);
            continue;
        } else if (!strcmp(actpblk.descr[i].name, "source")) {
            pData->pszSource = es_str2cstr(pvals[i].val.d.estr, NULL);
            continue;
        } else if (!strcmp(actpblk.descr[i].name, "target")) {
            pData->pszTarget = es_str2cstr(pvals[i].val.d.estr, NULL);
            continue;
        } else {
            DBGPRINTF(
                "mmgrok: program error, non-handled "
                "param '%s'\n",
                actpblk.descr[i].name);
        }
    }
    if (pData->pszTarget == NULL) {
        CHKmalloc(pData->pszTarget = strdup("!"));
    }
    CODE_STD_FINALIZERnewActInst;
    cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst

BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
    DBGPRINTF("mmgrok\n");
ENDdbgPrintInstInfo

BEGINtryResume
    CODESTARTtryResume;
ENDtryResume

static inline grok_t *CreateGrok(void) {
    grok_t *grok = grok_new();
    if (grok == NULL) {
        DBGPRINTF("mmgrok: create a grok faile!");
        exit(1);
    }
    grok_init(grok);
    return grok;
}

/* the parseing is complate message into json */
static rsRetVal smsg_to_json(GList *list, instanceData *pData) {
    GList *it = list;

    struct json_object *json;
    struct json_object *jval;

    DEFiRet;

    json = json_object_new_object();
    if (json == NULL) {
        ABORT_FINALIZE(RS_RET_ERR);
    }
    for (; it; it = it->next) {
        int key_len = ((result_t *)it->data)->key_len;
        char *key = (char *)malloc(key_len + 1);
        snprintf(key, key_len + 1, "%.*s", key_len, ((result_t *)it->data)->key);
        int value_len = ((result_t *)it->data)->value_len;
        char *value = (char *)malloc(value_len + 1);
        snprintf(value, value_len + 1, "%.*s", value_len, ((result_t *)it->data)->value);
        jval = json_object_new_string(value);
        json_object_object_add(json, key, jval);
        free(key);
        free(value);
    }
    msgAddJSON(pData->pmsg, (uchar *)pData->pszTarget, json, 0, 0);
finalize_it:
    RETiRet;
}

/* store parse result ,use list in glib*/
static rsRetVal parse_result_store(const grok_match_t gm, instanceData *pData) {
    GList *re_list = NULL;
    char *pname;
    const char *pdata;
    int pname_len, pdata_len;

    char *key;
    char *type;
    DEFiRet;

    grok_match_walk_init(&gm);  // grok API

    while (grok_match_walk_next(&gm, &pname, &pname_len, &pdata, &pdata_len) == 0) {
        /* parse key and value type from patterns */
        key = strchr(pname, ':');

        if (key != NULL) {
            int key_len;
            result_t *result = g_new0(result_t, 1);
            key_len = pname_len - ((key + 1) - pname);
            key = key + 1;
            pname_len = key_len;
            type = strchr(key, ':');
            int type_len;
            if (type != NULL) {
                key_len = (type - key);
                type = type + 1;
                type_len = pname_len - key_len - 1;
                type[type_len] = '\0';
            } else {
                type = (char *)"null";
            }
            /* store parse result into list */
            result->key = key;
            result->key_len = key_len;
            result->value = pdata;
            result->value_len = pdata_len;
            result->type = type;
            /* the value of merger the same key*/
            re_list = g_list_append(re_list, result);
        }
    }
    smsg_to_json(re_list, pData);
    g_list_free(re_list);
    grok_match_walk_end(&gm);
    RETiRet;
}

/* motify message for per line */
static rsRetVal MotifyLine(char *line, grok_t *grok, instanceData *pData) {
    grok_match_t gm;
    DEFiRet;
    grok_patterns_import_from_file(grok, pData->pszPatternDir);
    int compile = grok_compile(grok, pData->pszMatch);
    if (compile != GROK_OK) {
        DBGPRINTF("mmgrok: grok_compile faile!exit code: %d\n", compile);
        ABORT_FINALIZE(RS_RET_ERR);
    }
    int exe = grok_exec(grok, line, &gm);
    if (exe != GROK_OK) {
        DBGPRINTF("mmgrok: grok_exec faile!exit code: %d\n", exe);
        ABORT_FINALIZE(RS_RET_ERR);
    }
    parse_result_store(gm, pData);
finalize_it:
    RETiRet;
}

/* motify rsyslog messages */
static rsRetVal MotifyMessage(instanceData *pData) {
    char *saveptr = NULL;
    DEFiRet;
    grok_t *grok = CreateGrok();
    char *msg = strdup(pData->pszSource);
    char *line = NULL;
    line = strtok_r(msg, "\n", &saveptr);
    while (line != NULL) {
        MotifyLine(line, grok, pData);
        line = strtok_r(NULL, "\n", &saveptr);
    }
    free(msg);
    msg = NULL;
    RETiRet;
}


BEGINdoAction_NoStrings
    smsg_t **ppMsg = (smsg_t **)pMsgData;
    smsg_t *pMsg = ppMsg[0];
    uchar *buf;
    instanceData *pData;

    CODESTARTdoAction;
    pData = pWrkrData->pData;
    buf = getMSG(pMsg);
    pData->pmsg = pMsg;
    while (*buf && isspace(*buf)) {
        ++buf;
    }

    if (*buf == '\0') {
        DBGPRINTF("mmgrok:  not msg for mmgrok!");
        ABORT_FINALIZE(RS_RET_NO_CEE_MSG);
    }
    pData->pszSource = (char *)buf;
    CHKiRet(MotifyMessage(pData));

finalize_it:
ENDdoAction

BEGINparseSelectorAct
    CODESTARTparseSelectorAct;
    CODE_STD_STRING_REQUESTparseSelectorAct(1) if (strncmp((char *)p, ":mmgrok:", sizeof(":mmgrok:") - 1)) {
        ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
    }

    p += sizeof(":mmgrok:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
    CHKiRet(createInstance(&pData));

    if (*(p - 1) == ';') --p;
    CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar *)"RSYSLOG_FileFormat"));
    CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct

BEGINmodExit
    CODESTARTmodExit;
ENDmodExit


BEGINqueryEtryPt
    CODESTARTqueryEtryPt;
    CODEqueryEtryPt_STD_OMOD_QUERIES;
    CODEqueryEtryPt_STD_OMOD8_QUERIES;
    CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES;
    CODEqueryEtryPt_STD_CONF2_QUERIES;
ENDqueryEtryPt

static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal) {
    DEFiRet;
    RETiRet;
}

BEGINmodInit()
    rsRetVal localRet;
    rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts);
    unsigned long opts;
    int bMsgPassingSupported;
    CODESTARTmodInit;
    *ipIFVersProvided = CURR_MOD_IF_VERSION;
    CODEmodInit_QueryRegCFSLineHdlr DBGPRINTF("mmgrok: module compiled with rsyslog version %s.\n", VERSION);
    bMsgPassingSupported = 0;
    localRet = pHostQueryEtryPt((uchar *)"OMSRgetSupportedTplOpts", &pomsrGetSupportedTplOpts);
    if (localRet == RS_RET_OK) {
        CHKiRet((*pomsrGetSupportedTplOpts)(&opts));
        if (opts & OMSR_TPL_AS_MSG) bMsgPassingSupported = 1;
    } else if (localRet != RS_RET_ENTRY_POINT_NOT_FOUND) {
        ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */
    }

    if (!bMsgPassingSupported) {
        DBGPRINTF(
            "mmgrok: msg-passing is not supported by rsyslog core, "
            "can not continue.\n");
        ABORT_FINALIZE(RS_RET_NO_MSG_PASSING);
    }


    CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL,
                               STD_LOADABLE_MODULE_ID));
ENDmodInit
