/* omjournal.c
 * send messages to the Linux Journal. This is meant to be used
 * in cases where journal serves as the whole system log database.
 * Note that we may get into a loop if journald re-injects messages
 * into the syslog stream and we read that via imuxsock. Thus there
 * is an option in imuxsock to ignore messages from ourselves
 * (actually from our pid). So there are some module-interdependencies.
 *
 * Copyright 2013-2017 Adiscon GmbH.
 *
 * This file is part 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 "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 "conf.h"
#include "cfsysline.h"
#include <json.h>
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include <systemd/sd-journal.h>
#include "unicode-helper.h"
#include <sys/uio.h>
#include "parserif.h"

MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("omjournal")


DEF_OMOD_STATIC_DATA;

/* config variables */


typedef struct _instanceData {
    uchar *tplName;
    uchar *namespace;
} instanceData;

typedef struct wrkrInstanceData {
    instanceData *pData;
} wrkrInstanceData_t;

static struct cnfparamdescr actpdescr[] = {{"template", eCmdHdlrGetWord, 0}, {"namespace", eCmdHdlrGetWord, 0}};
static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, sizeof(actpdescr) / sizeof(struct cnfparamdescr), actpdescr};


struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
};
static modConfData_t *runModConf = NULL; /* modConf ptr to use for the current exec process */

BEGINbeginCnfLoad
    CODESTARTbeginCnfLoad;
ENDbeginCnfLoad

BEGINendCnfLoad
    CODESTARTendCnfLoad;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
    runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
ENDfreeCnf


BEGINcreateInstance
    CODESTARTcreateInstance;
ENDcreateInstance


BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
ENDcreateWrkrInstance


BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
    if (eFeat == sFEATURERepeatedMsgReduction) iRet = RS_RET_OK;
ENDisCompatibleWithFeature


BEGINfreeInstance
    CODESTARTfreeInstance;
    free(pData->tplName);
    free(pData->namespace);
ENDfreeInstance


BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
ENDfreeWrkrInstance

static inline void setInstParamDefaults(instanceData *pData) {
    pData->tplName = NULL;
    pData->namespace = NULL;
}

BEGINnewActInst
    struct cnfparamvals *pvals;
    int i;
    CODESTARTnewActInst;
    DBGPRINTF("newActInst (mmjournal)\n");
    pvals = nvlstGetParams(lst, &actpblk, NULL);
    if (pvals == NULL) {
        parser_errmsg(
            "error processing module "
            "config parameters [module(...)]");
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }


    CHKiRet(createInstance(&pData));
    setInstParamDefaults(pData);

    CODE_STD_STRING_REQUESTnewActInst(1);
    for (i = 0; i < actpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;

        if (!strcmp(actpblk.descr[i].name, "template")) {
            pData->tplName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
        } else if (!strcmp(actpblk.descr[i].name, "namespace")) {
            pData->namespace = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
        } else {
            dbgprintf(
                "omjournal: program error, non-handled "
                "param '%s'\n",
                actpblk.descr[i].name);
        }
    }

    if (pData->tplName == NULL) {
        CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG));
    } else {
        CHKiRet(OMSRsetEntry(*ppOMSR, 0, ustrdup(pData->tplName), OMSR_TPL_AS_JSON));
    }

    CODE_STD_FINALIZERnewActInst;
    cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst


BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
ENDdbgPrintInstInfo


BEGINtryResume
    CODESTARTtryResume;
ENDtryResume


static struct iovec *build_iovec(size_t *retargc, struct json_object *json) {
    struct iovec *iov;
    const char *key;
    const char *val;
    size_t key_len;
    size_t val_len;
    size_t vec_len;
    size_t i;

    const size_t argc = json_object_object_length(json);
    if (argc == 0) return NULL;
    iov = malloc(sizeof(struct iovec) * argc);
    if (NULL == iov) goto fail;

    /* note: as we know the number of subobjects, we use the for loop
     * to iterate over them instead of the _iter_ API. This is guaranteed
     * to work. The somewhat cleaner case causes clang static analyzer to
     * complain and we need to avoid that.
     */
    struct json_object_iterator it = json_object_iter_begin(json);
    for (i = 0; i < argc; ++i) {
        key = json_object_iter_peek_name(&it);
        val = json_object_get_string(json_object_iter_peek_value(&it));

        key_len = strlen(key);
        val_len = strlen(val);
        // vec length is len(key=val)
        vec_len = key_len + val_len + 1;

        char *buf = malloc(vec_len + 1);
        if (NULL == buf) goto fail;

        memcpy(buf, key, key_len);
        memcpy(buf + key_len, "=", 1);
        memcpy(buf + key_len + 1, val, val_len + 1);

        iov[i].iov_base = buf;
        iov[i].iov_len = vec_len;

        json_object_iter_next(&it);
    }
    *retargc = argc;
    return iov;

fail:
    if (NULL == iov) return NULL;

    size_t j;
    // iterate over any iovecs that were initalised above and free them.
    for (j = 0; j < i; j++) {
        free(iov[j].iov_base);
    }

    free(iov);
    return NULL;
}

static rsRetVal send_non_template_message_with_namespace(uchar *namespace, smsg_t *const __restrict__ pMsg) {
    DEFiRet;
    int fd;
    FILE *log;

    int sev;

    MsgGetSeverity(pMsg, &sev);

#ifdef HAVE_SD_JOURNAL_STREAM_FD_WITH_NAMESPACE
    fd = sd_journal_stream_fd_with_namespace((char *)namespace, (const char *)getMSG(pMsg),
                                             (pMsg->iFacility << 3) | sev, 1);
#else
    LogError(0, RS_RET_SYSTEMD_VERSION_ERR,
             "omjournal: "
             "namespace='%s', however you need systemd V256 to make use of this feature.\n",
             (char *)namespace);
    ABORT_FINALIZE(RS_RET_SYSTEMD_VERSION_ERR);
#endif
    if (fd < 0) {
        LogError(errno, RS_RET_FILE_OPEN_ERROR,
                 "omjournal: "
                 "Failed to create stream fd.\n");
        ABORT_FINALIZE(RS_RET_FILE_OPEN_ERROR);
    }

    log = fdopen(fd, "w");
    if (!log) {
        LogError(errno, RS_RET_FOPEN_FAILURE,
                 "omjournal: "
                 "failed to open journal namespace fd.\n");
        close(fd);
        ABORT_FINALIZE(RS_RET_FOPEN_FAILURE);
    }
    if (fprintf(log, "%s\n", (char *)getMSG(pMsg)) < 0) {
        LogError(errno, RS_RET_IO_ERROR,
                 "omjournal: "
                 "failed to write journal namespace file.\n");
        fclose(log);
        ABORT_FINALIZE(RS_RET_IO_ERROR);
    }
    fclose(log);

finalize_it:
    RETiRet;
}

static void send_non_template_message(smsg_t *const __restrict__ pMsg) {
    uchar *tag;
    int lenTag;
    int sev;

    MsgGetSeverity(pMsg, &sev);
    getTAG(pMsg, &tag, &lenTag, LOCK_MUTEX);
    /* we can use more properties here, but let's see if there
     * is some real user interest. We can always add later...
     */
    sd_journal_send("MESSAGE=%s", getMSG(pMsg), "PRIORITY=%d", (pMsg->iFacility * 8) | sev, "SYSLOG_FACILITY=%d",
                    pMsg->iFacility, "SYSLOG_IDENTIFIER=%s", tag, NULL);
}

static void send_template_message(struct json_object *const __restrict__ json) {
    size_t argc;
    struct iovec *iovec;
    size_t i;

    iovec = build_iovec(&argc, json);
    if (NULL != iovec) {
        sd_journal_sendv(iovec, argc);
        for (i = 0; i < argc; i++) free(iovec[i].iov_base);
        free(iovec);
    }
}

BEGINdoAction_NoStrings
    instanceData *pData;
    CODESTARTdoAction;
    pData = pWrkrData->pData;

    if (pData->namespace != NULL && pData->tplName != NULL) {
        LogError(0, RS_RET_NO_TEMPLATE_SUPPORT_ERR,
                 "omjournal: "
                 "journald namespace feature does not support templates yet.");
        ABORT_FINALIZE(RS_RET_NO_TEMPLATE_SUPPORT_ERR);
    } else if (pData->namespace != NULL) {
        CHKiRet(send_non_template_message_with_namespace(pData->namespace, (smsg_t *)((void **)pMsgData)[0]));
    } else if (pData->tplName == NULL) {
        send_non_template_message((smsg_t *)((void **)pMsgData)[0]);
    } else {
        send_template_message((struct json_object *)((void **)pMsgData)[0]);
    }
finalize_it:
ENDdoAction

BEGINmodExit
    CODESTARTmodExit;
ENDmodExit


NO_LEGACY_CONF_parseSelectorAct

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


BEGINmodInit()
    CODESTARTmodInit;
    *ipIFVersProvided = CURR_MOD_IF_VERSION;
    CODEmodInit_QueryRegCFSLineHdlr DBGPRINTF("omjournal: module compiled with rsyslog version %s.\n", VERSION);
ENDmodInit
