/* The errmsg object.
 *
 * Module begun 2008-03-05 by Rainer Gerhards, based on some code
 * from syslogd.c. I converted this module to lgpl and have checked that
 * all contributors agreed to that step.
 * Now moving to ASL 2.0, and contributor checks tell that there is no need
 * to take further case, as the code now boils to be either my own or, a few lines,
 * of the original BSD-licenses sysklogd code. rgerhards, 2012-01-16
 *
 * Copyright 2008-2018 Adiscon GmbH.
 *
 * This file is part of the rsyslog runtime library.
 *
 * 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 <stdarg.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#include "rsyslog.h"
#include "obj.h"
#include "msg.h"
#include "errmsg.h"
#include "operatingstate.h"
#include "srUtils.h"
#include "stringbuf.h"
#include "rsconf.h"

/* static data */
#ifndef O_LARGEFILE
    #define O_LARGEFILE 0
#endif

static int bHadErrMsgs; /* indicates if we had error messages since reset of this flag
                         * This is used to abort a run if the config is unclean.
                         */

static int fdOversizeMsgLog = -1;
static pthread_mutex_t oversizeMsgLogMut = PTHREAD_MUTEX_INITIALIZER;

/* ------------------------------ methods ------------------------------ */

/* Resets the error message flag. Must be done before processing config
 * files.
 */
void resetErrMsgsFlag(void) {
    bHadErrMsgs = 0;
}

int hadErrMsgs(void) {
    return bHadErrMsgs;
}

/** @internal
 * Build and emit a single diagnostic line.
 *
 * - If iErrno != 0, append strerror(iErrno).
 * - If iErrCode is neither NO_ERRCODE nor RS_RET_ERR, append a help URL.
 * - Truncate to the configured max line length.
 * - Set the "had error" flag for LOG_ERR.
 */
static void doLogMsg(const int iErrno, const int iErrCode, const int severity, const char *msg) {
    char buf[2048];
    char errStr[1024];

    dbgprintf("Called LogMsg, msg: %s\n", msg);
    osf_write(OSF_TAG_MSG, msg);

    if (iErrno != 0) {
        rs_strerror_r(iErrno, errStr, sizeof(errStr));
        if (iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
            snprintf(buf, sizeof(buf), "%s: %s [v%s]", msg, errStr, VERSION);
        } else {
            snprintf(buf, sizeof(buf), "%s: %s [v%s try https://www.rsyslog.com/e/%d ]", msg, errStr, VERSION,
                     iErrCode * -1);
        }
    } else {
        if (iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
            snprintf(buf, sizeof(buf), "%s [v%s]", msg, VERSION);
        } else {
            snprintf(buf, sizeof(buf), "%s [v%s try https://www.rsyslog.com/e/%d ]", msg, VERSION, iErrCode * -1);
        }
    }
    buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */
    errno = 0;

    const int msglen = (int)strlen(buf);
    if (msglen > glblGetMaxLine(ourConf)) {
        /* in extreme cases, our error messages may be longer than the configured
         * max message size. If so, we just truncate without further indication, as
         * anything else would probably lead to a death loop on error messages.
         * Note that we do not split, as we really do not anticipate there is
         * much value in supporting extremely short max message sizes - we assume
         * it's just a testbench thing. -- rgerhards, 2018-05-11
         */
        buf[glblGetMaxLine(ourConf)] = '\0'; /* space must be available! */
    }

    glblErrLogger(severity, iErrCode, (uchar *)buf);

    if (severity == LOG_ERR) bHadErrMsgs = 1;
}

/* We now receive three parameters: one is the internal error code
 * which will also become the error message number, the second is
 * errno - if it is non-zero, the corresponding error message is included
 * in the text and finally the message text itself. Note that it is not
 * 100% clean to use the internal errcode, as it may be reached from
 * multiple actual error causes. However, it is much better than having
 * no error code at all (and in most cases, a single internal error code
 * maps to a specific error event).
 * rgerhards, 2008-06-27
 */
void __attribute__((format(printf, 3, 4))) LogError(const int iErrno, const int iErrCode, const char *fmt, ...) {
    va_list ap;
    char buf[2048];
    int lenBuf;

    va_start(ap, fmt);
    lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap);
    if (lenBuf < 0) {
        strncpy(buf, "error message lost due to problem with vsnprintf", sizeof(buf));
    }
    va_end(ap);
    buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */

    doLogMsg(iErrno, iErrCode, LOG_ERR, buf);
}

/* We now receive three parameters: one is the internal error code
 * which will also become the error message number, the second is
 * errno - if it is non-zero, the corresponding error message is included
 * in the text and finally the message text itself. Note that it is not
 * 100% clean to use the internal errcode, as it may be reached from
 * multiple actual error causes. However, it is much better than having
 * no error code at all (and in most cases, a single internal error code
 * maps to a specific error event).
 * rgerhards, 2008-06-27
 */
void __attribute__((format(printf, 4, 5))) LogMsg(
    const int iErrno, const int iErrCode, const int severity, const char *fmt, ...) {
    va_list ap;
    char buf[2048];
    int lenBuf;

    va_start(ap, fmt);
    lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap);
    if (lenBuf < 0) {
        strncpy(buf, "error message lost due to problem with vsnprintf", sizeof(buf));
    }
    va_end(ap);
    buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */

    doLogMsg(iErrno, iErrCode, severity, buf);
}


/* Write an oversize message to the oversize message error log.
 * We do NOT handle errors during writing that log other than emitting
 * yet another error message. The reason is that there really is nothing
 * else that we could do in that case.
 * rgerhards, 2018-05-03
 */
rsRetVal ATTR_NONNULL() writeOversizeMessageLog(const smsg_t *const pMsg) {
    struct json_object *json = NULL;
    char *rendered = NULL;
    struct json_object *jval;
    uchar *buf;
    size_t toWrite;
    ssize_t wrRet;
    int dummy;
    int mutexLocked = 0;
    DEFiRet;
    ISOBJ_TYPE_assert(pMsg, msg);

    if (glblGetOversizeMsgErrorFile(runConf) == NULL) {
        FINALIZE;
    }

    pthread_mutex_lock(&oversizeMsgLogMut);
    mutexLocked = 1;

    if (fdOversizeMsgLog == -1) {
        fdOversizeMsgLog =
            open((char *)glblGetOversizeMsgErrorFile(runConf), O_WRONLY | O_CREAT | O_APPEND | O_LARGEFILE | O_CLOEXEC,
                 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
        if (fdOversizeMsgLog == -1) {
            LogError(errno, RS_RET_ERR, "error opening oversize message log file %s",
                     glblGetOversizeMsgErrorFile(runConf));
            FINALIZE;
        }
    }

    assert(fdOversizeMsgLog != -1);
    json = json_object_new_object();
    if (json == NULL) {
        FINALIZE;
    }

    getRawMsg(pMsg, &buf, &dummy);
    jval = json_object_new_string((char *)buf);
    json_object_object_add(json, "rawmsg", jval);

    getInputName(pMsg, &buf, &dummy);
    jval = json_object_new_string((char *)buf);
    json_object_object_add(json, "input", jval);

    CHKmalloc(rendered = strdup((char *)fjson_object_to_json_string(json)));

    toWrite = strlen(rendered) + 1;
    /* Note: we overwrite the '\0' terminator with '\n' -- so we avoid
     * calling malloc() -- write() does NOT need '\0'!
     */
    rendered[toWrite - 1] = '\n'; /* NO LONGER A STRING! */
    wrRet = write(fdOversizeMsgLog, rendered, toWrite);
    if (wrRet != (ssize_t)toWrite) {
        LogError(errno, RS_RET_IO_ERROR, "error writing oversize message log file %s, write returned %lld",
                 glblGetOversizeMsgErrorFile(runConf), (long long)wrRet);
    }

finalize_it:
    free(rendered);
    if (mutexLocked) {
        pthread_mutex_unlock(&oversizeMsgLogMut);
    }
    if (json != NULL) {
        fjson_object_put(json);
    }
    RETiRet;
}


void errmsgDoHUP(void) {
    pthread_mutex_lock(&oversizeMsgLogMut);
    if (fdOversizeMsgLog != -1) {
        close(fdOversizeMsgLog);
        fdOversizeMsgLog = -1;
    }
    pthread_mutex_unlock(&oversizeMsgLogMut);
}


void errmsgExit(void) {
    if (fdOversizeMsgLog != -1) {
        close(fdOversizeMsgLog);
    }
}
