/*
    Copyright (C) 2010  ABRT team
    Copyright (C) 2010, 2014  RedHat Inc

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <syslog.h>
/* Suppress automatic CODE_* fields as we handle those here */
#define SD_JOURNAL_SUPPRESS_LOCATION
#include <systemd/sd-journal.h>
#include "internal_libreport.h"

void (*g_custom_logger)(const char*);
const char *msg_prefix = "";
const char *msg_eol = "\n";
int logmode = LOGMODE_STDIO;
int xfunc_error_retval = EXIT_FAILURE;
static enum libreport_diemode xfunc_diemode = DIEMODE_EXIT;
int g_verbose;

void set_xfunc_error_retval(int retval)
{
    xfunc_error_retval = retval;
}

void set_xfunc_diemode(enum libreport_diemode mode)
{
    xfunc_diemode = mode;
}

/* [p]error_msg[_and_die] must be safe after fork in multi-threaded programs.
 * Therefore we avoid stdio, fflush(), and use _exit() instead of exit().
 *
 */
void xfunc_die(void)
{
    char *const envmode = getenv("LIBREPORT_DIEMODE");
    if (   xfunc_diemode == DIEMODE_ABORT
        || (envmode != NULL && strcmp("abort", envmode) == 0))
        abort();

    _exit(xfunc_error_retval);
}

static bool should_log(int level)
{
    // LOG_DEBUG = 7, LOG_INFO = 6, LOG_NOTICE = 5, LOG_WARNING = 4, LOG_ERR = 3
    // output only messages with LOG_ERR by default, overridden by g_verbose
    if(
          (g_verbose == 0 && level <= LOG_WARNING) ||
          (g_verbose == 1 && level <= LOG_NOTICE) ||
          (g_verbose == 2 && level <= LOG_INFO) ||
          (g_verbose == 3)
      )
      return true;

    return false;
}


static void log_handler(int level,
                        const char *format,
                        va_list p,
                        const char *strerr, /* perror messages */
                        int flags,
                        const char *file,
                        int line,
                        const char *func)
{
    if (!logmode || !should_log(level))
        return;

    /* This is ugly and costs +60 bytes compared to multiple
     * fprintf's, but is guaranteed to do a single write.
     * This is needed for e.g. when multiple children
     * can produce log messages simultaneously. */

    int prefix_len = msg_prefix[0] ? strlen(msg_prefix) + 2 : 0;
    int strerr_len = strerr ? strlen(strerr) : 0;
    int msgeol_len = strlen(msg_eol);
    int used;

    /* Try to format the message in a fixed buffer.
     * This eliminates malloc.
     * Main reason isn't the speed optimization, but to make
     * short logging safe after fork in multithreaded libraries.
     */
    char buf[1024];
    va_list p2;
    va_copy(p2, p);
    if (prefix_len < sizeof(buf))
        used = vsnprintf(buf + prefix_len, sizeof(buf) - prefix_len, format, p2);
    else
        used = vsnprintf(buf, 0, format, p2);
    va_end(p2);

    char *msg = buf;

    /* +3 is for ": " before strerr and for terminating NUL */
    unsigned total_len = prefix_len + used + strerr_len + msgeol_len + 3;

    if (total_len >= sizeof(buf))
    {
        msg = alloca(total_len);
        used = vsnprintf(msg + prefix_len, total_len - prefix_len, format, p);
    }

    if (prefix_len) {
        char *p;
        used += prefix_len;
        p = stpcpy(msg, msg_prefix);
        p[0] = ':';
        p[1] = ' ';
    }
    if (strerr) {
        if (format[0]) {
            msg[used++] = ':';
            msg[used++] = ' ';
        }
        strcpy(&msg[used], strerr);
        used += strerr_len;
    }
    strcpy(&msg[used], msg_eol);

    if (flags & LOGMODE_STDIO) {
        full_write(STDERR_FILENO, msg, used + msgeol_len);
    }
    msg[used] = '\0'; /* remove msg_eol (usually "\n") */
    if (flags & LOGMODE_SYSLOG) {
        syslog(level, "%s", msg + prefix_len);
    }

    if ((flags & LOGMODE_CUSTOM) && g_custom_logger) {
        g_custom_logger(msg + prefix_len);
    }

    if (flags & LOGMODE_JOURNAL) {
        sd_journal_send("MESSAGE=%s", msg + prefix_len,
                        "PRIORITY=%d", level,
                        "CODE_FILE=%s", file,
                        "CODE_LINE=%d", line,
                        "CODE_FUNC=%s", func,
                        "SYSLOG_FACILITY=1",
                        NULL);
    }
}

void log_wrapper(int level,
                 const char *file,
                 int line,
                 const char *func,
                 bool process_perror,
                 bool use_custom_logger,
                 const char *format,
                 ...)
{
    va_list p;

    va_start(p, format);
    log_handler(level,
                format,
                p,
                (process_perror && errno) ? strerror(errno) : NULL, /* Guard against "<error message>: Success" */
                logmode | (use_custom_logger ? LOGMODE_CUSTOM : 0),
                file,
                line,
                func);
    va_end(p);
}

void log_and_die_wrapper(int level,
                         const char *file,
                         int line,
                         const char *func,
                         bool process_perror,
                         bool use_custom_logger,
                         const char *format,
                         ...)
{
    va_list p;

    va_start(p, format);
    log_handler(level,
                format,
                p,
                (process_perror && errno) ? strerror(errno) : NULL, /* Guard against "<error message>: Success" */
                logmode | (use_custom_logger ? LOGMODE_CUSTOM : 0),
                file,
                line,
                func);
    va_end(p);
    xfunc_die();
}


void die_out_of_memory(void)
{
    error_msg_and_die("Out of memory, exiting");
}
