/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * Copyright (C) Jay Sorg 2004-2014
 *
 * 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
 *
 * 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.
 */

#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include "list.h"
#include "file.h"
#include "os_calls.h"
#include "thread_calls.h"
#include "string_calls.h"

/* Add a define here so that the log.h will hold more information
 * when compiled from this C file.
 * When compiled normally the log.h file only contain the public parts
 * of the operators in this file. */
#define LOGINTERNALSTUFF
#include "log.h"

/* Here we store the current state and configuration of the log */
static struct log_config *g_staticLogConfig = NULL;

/* This file first start with all private functions.
   In the end of the file the public functions is defined */

/**
 *
 * @brief Opens log file
 * @param fname log file name
 * @return see open(2) return values
 *
 */
int
internal_log_file_open(const char *fname)
{
    int ret = -1;

    if (fname != NULL)
    {
        if (g_strcmp(fname, "<stdout>") != 0)
        {
            ret = open(fname, O_WRONLY | O_CREAT | O_APPEND | O_SYNC,
                       S_IRUSR | S_IWUSR);
        }
        else
        {
            ret = dup(1);
        }
    }

    if (ret != -1)
    {
        g_file_set_cloexec(ret, 1);
    }

    return ret;
}

/**
 *
 * @brief Converts xrdp log level to syslog logging level
 * @param xrdp logging level
 * @return syslog equivalent logging level
 *
 */
int
internal_log_xrdp2syslog(const enum logLevels lvl)
{
    switch (lvl)
    {
        case LOG_LEVEL_ALWAYS:
            return LOG_CRIT;
        case LOG_LEVEL_ERROR:
            return LOG_ERR;
        case LOG_LEVEL_WARNING:
            return LOG_WARNING;
        case LOG_LEVEL_INFO:
            return LOG_INFO;
        case LOG_LEVEL_DEBUG:
        case LOG_LEVEL_TRACE:
            return LOG_DEBUG;
        default:
            g_writeln("Undefined log level - programming error");
            return LOG_DEBUG;
    }
}

/**
 * @brief Converts xrdp log levels to textual logging levels
 * @param lvl logging level
 * @param str pointer to a string, must be allocated before
 * @return The log string in str pointer.
 *
 */
void
internal_log_lvl2str(const enum logLevels lvl, char *str)
{
    switch (lvl)
    {
        case LOG_LEVEL_ALWAYS:
            snprintf(str, 9, "%s", "[CORE ] ");
            break;
        case LOG_LEVEL_ERROR:
            snprintf(str, 9, "%s", "[ERROR] ");
            break;
        case LOG_LEVEL_WARNING:
            snprintf(str, 9, "%s", "[WARN ] ");
            break;
        case LOG_LEVEL_INFO:
            snprintf(str, 9, "%s", "[INFO ] ");
            break;
        case LOG_LEVEL_DEBUG:
            snprintf(str, 9, "%s", "[DEBUG] ");
            break;
        case LOG_LEVEL_TRACE:
            snprintf(str, 9, "%s", "[TRACE] ");
            break;
        default:
            snprintf(str, 9, "%s", "PRG ERR!");
            g_writeln("Programming error - undefined log level!!!");
    }
}

/******************************************************************************/
enum logReturns
internal_log_start(struct log_config *l_cfg)
{
    enum logReturns ret = LOG_GENERAL_ERROR;

    if (0 == l_cfg)
    {
        ret = LOG_ERROR_MALLOC;
        return ret;
    }

    /* if progname is NULL, we return error */
    if (0 == l_cfg->program_name)
    {
        g_writeln("program_name not properly assigned");
        return ret;
    }

    if (l_cfg->dump_on_start)
    {
        internal_log_config_dump(l_cfg);
    }

    /* open file */
    if (l_cfg->log_file != NULL)
    {
        l_cfg->fd = internal_log_file_open(l_cfg->log_file);

        if (-1 == l_cfg->fd)
        {
            return LOG_ERROR_FILE_OPEN;
        }
    }

    /* if syslog is enabled, open it */
    if (l_cfg->enable_syslog)
    {
        openlog(l_cfg->program_name, LOG_CONS | LOG_PID, LOG_DAEMON);
    }

#ifdef LOG_ENABLE_THREAD
    pthread_mutexattr_init(&(l_cfg->log_lock_attr));
    pthread_mutex_init(&(l_cfg->log_lock), &(l_cfg->log_lock_attr));
#endif

    return LOG_STARTUP_OK;
}

/******************************************************************************/
enum logReturns
internal_log_end(struct log_config *l_cfg)
{
    enum logReturns ret = LOG_GENERAL_ERROR;

    /* if log is closed, quit silently */
    if (0 == l_cfg)
    {
        return ret;
    }

    if (-1 != l_cfg->fd)
    {
        /* closing logfile... */
        g_file_close(l_cfg->fd);
    }

    /* if syslog is enabled, close it */
    if (l_cfg->enable_syslog)
    {
        closelog();
    }

    /* freeing allocated memory */
    if (0 != l_cfg->log_file)
    {
        g_free(l_cfg->log_file);
        l_cfg->log_file = 0;
    }

    ret = LOG_STARTUP_OK;
    return ret;
}

/**
 * Converts a string representing the log level to a value
 * @param buf
 * @return
 */
enum logLevels
internal_log_text2level(const char *buf)
{
    if (0 == g_strcasecmp(buf, "0") ||
            0 == g_strcasecmp(buf, "core"))
    {
        return LOG_LEVEL_ALWAYS;
    }
    else if (0 == g_strcasecmp(buf, "1") ||
             0 == g_strcasecmp(buf, "error"))
    {
        return LOG_LEVEL_ERROR;
    }
    else if (0 == g_strcasecmp(buf, "2") ||
             0 == g_strcasecmp(buf, "warn") ||
             0 == g_strcasecmp(buf, "warning"))
    {
        return LOG_LEVEL_WARNING;
    }
    else if (0 == g_strcasecmp(buf, "3") ||
             0 == g_strcasecmp(buf, "info"))
    {
        return LOG_LEVEL_INFO;
    }
    else if (0 == g_strcasecmp(buf, "4") ||
             0 == g_strcasecmp(buf, "debug"))
    {
        return LOG_LEVEL_DEBUG;
    }
    else if (0 == g_strcasecmp(buf, "5") ||
             0 == g_strcasecmp(buf, "trace"))
    {
        return LOG_LEVEL_TRACE;
    }

    g_writeln("Your configured log level is corrupt - we use debug log level");
    return LOG_LEVEL_DEBUG;
}

/******************************************************************************/
struct log_config *
internal_config_read_logging(int file,
                             const char *applicationName,
                             const char *section_prefix)
{
    int i;
    char *buf;
    char *temp_buf;
    char section_name[512];
    struct log_config *lc;
    struct list *param_n;
    struct list *param_v;

    lc = internalInitAndAllocStruct();
    if (lc == NULL)
    {
        return NULL;
    }

    param_n = list_create();
    param_n->auto_free = 1;
    param_v = list_create();
    param_v->auto_free = 1;

    list_clear(param_v);
    list_clear(param_n);

    /* setting defaults */
    lc->program_name = applicationName;
    lc->log_file = 0;
    lc->fd = -1;
    lc->log_level = LOG_LEVEL_INFO;
    lc->enable_console = 0;
    lc->console_level = LOG_LEVEL_INFO;
    lc->enable_syslog = 0;
    lc->syslog_level = LOG_LEVEL_INFO;
    lc->dump_on_start = 0;
    lc->enable_pid = 0;

    g_snprintf(section_name, 511, "%s%s", section_prefix, SESMAN_CFG_LOGGING);
    file_read_section(file, section_name, param_n, param_v);

    for (i = 0; i < param_n->count; i++)
    {
        buf = (char *)list_get_item(param_n, i);

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_FILE))
        {
            lc->log_file = g_strdup((char *)list_get_item(param_v, i));

            if (lc->log_file != NULL)
            {
                if (lc->log_file[0] != '/' && g_strcmp(lc->log_file, "<stdout>") != 0)
                {
                    temp_buf = (char *)g_malloc(512, 0);
                    g_snprintf(temp_buf, 511, "%s/%s", XRDP_LOG_PATH, lc->log_file);
                    g_free(lc->log_file);
                    lc->log_file = temp_buf;
                }
            }
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_LEVEL))
        {
            lc->log_level = internal_log_text2level((char *)list_get_item(param_v, i));
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_ENABLE_SYSLOG))
        {
            lc->enable_syslog = g_text2bool((char *)list_get_item(param_v, i));
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_SYSLOG_LEVEL))
        {
            lc->syslog_level = internal_log_text2level((char *)list_get_item(param_v, i));
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_ENABLE_CONSOLE))
        {
            lc->enable_console = g_text2bool((char *)list_get_item(param_v, i));
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_CONSOLE_LEVEL))
        {
            lc->console_level = internal_log_text2level((char *)list_get_item(param_v, i));
        }

        if (0 == g_strcasecmp(buf, SESMAN_CFG_LOG_ENABLE_PID))
        {
            lc->enable_pid = g_text2bool((char *)list_get_item(param_v, i));
        }
    }

    if (0 == lc->log_file)
    {
        lc->log_file = g_strdup("./sesman.log");
    }

    /* try to create path if not exist */
    g_create_path(lc->log_file);

#ifdef LOG_PER_LOGGER_LEVEL
    int len;
    struct log_logger_level *logger;

    list_clear(param_v);
    list_clear(param_n);
    g_snprintf(section_name, 511, "%s%s", section_prefix, SESMAN_CFG_LOGGING_LOGGER);
    file_read_section(file, section_name, param_n, param_v);
    for (i = 0; i < param_n->count; i++)
    {
        logger = (struct log_logger_level *)g_malloc(sizeof(struct log_logger_level), 1);
        list_add_item(lc->per_logger_level, (tbus) logger);
        logger->log_level = internal_log_text2level((char *)list_get_item(param_v, i));

        g_strncpy(logger->logger_name, (char *)list_get_item(param_n, i), LOGGER_NAME_SIZE);
        logger->logger_name[LOGGER_NAME_SIZE] = '\0';
        len = g_strlen(logger->logger_name);
        if (len >= 2
                && logger->logger_name[len - 2] == '('
                && logger->logger_name[len - 1] == ')' )
        {
            logger->logger_type = LOG_TYPE_FUNCTION;
            logger->logger_name[len - 2] = '\0';
        }
        else
        {
            logger->logger_type = LOG_TYPE_FILE;
        }
    }
#endif

    list_delete(param_v);
    list_delete(param_n);
    return lc;
}

void
internal_log_config_dump(struct log_config *config)
{
    char str_level[20];
#ifdef LOG_PER_LOGGER_LEVEL
    struct log_logger_level *logger;
    int i;
#endif

    g_printf("logging configuration:\r\n");
    if (config->log_file)
    {
        internal_log_lvl2str(config->log_level, str_level);
        g_printf("\tLogFile:       %s\r\n", config->log_file);
        g_printf("\tLogLevel:      %s\r\n", str_level);
    }
    else
    {
        g_printf("\tLogFile:       %s\r\n", "<disabled>");
    }

    if (config->enable_console)
    {
        internal_log_lvl2str(config->console_level, str_level);
    }
    else
    {
        g_strcpy(str_level, "<disabled>");
    }
    g_printf("\tConsoleLevel:  %s\r\n", str_level);

    if (config->enable_syslog)
    {
        internal_log_lvl2str(config->syslog_level, str_level);
    }
    else
    {
        g_strcpy(str_level, "<disabled>");
    }
    g_printf("\tSyslogLevel:   %s\r\n", str_level);

#ifdef LOG_PER_LOGGER_LEVEL
    g_printf("per logger configuration:\r\n");
    for (i = 0; i < config->per_logger_level->count; i++)
    {
        logger = (struct log_logger_level *)list_get_item(config->per_logger_level, i);
        internal_log_lvl2str(logger->log_level, str_level);
        g_printf("\t%-*s: %s\r\n", LOGGER_NAME_SIZE, logger->logger_name, str_level);
    }
    if (config->per_logger_level->count == 0)
    {
        g_printf("\tNone\r\n");
    }
#endif
}

struct log_config *
internalInitAndAllocStruct(void)
{
    struct log_config *ret = g_new0(struct log_config, 1);

    if (ret != NULL)
    {
        ret->fd = -1;
        ret->enable_syslog = 0;
#ifdef LOG_PER_LOGGER_LEVEL
        ret->per_logger_level = list_create();
        if (ret->per_logger_level != NULL)
        {
            ret->per_logger_level->auto_free = 1;
        }
        else
        {
            g_writeln("could not allocate memory for log struct");
            g_free(ret);
            ret = NULL;
        }
#endif
    }
    else
    {
        g_writeln("could not allocate memory for log struct");
    }

    return ret;
}

/**
 * Copies logging levels only from one log_config structure to another
 **/

static void
internal_log_config_copy_levels(struct log_config *dest,
                                const struct log_config *src)
{
    dest->log_level = src->log_level;
    dest->enable_syslog = src->enable_syslog;
    dest->syslog_level = src->syslog_level;
    dest->enable_console = src->enable_console;
    dest->console_level = src->console_level;

#ifdef LOG_PER_LOGGER_LEVEL
    if (dest->per_logger_level == NULL)
    {
        dest->per_logger_level = list_create();
        if (dest->per_logger_level == NULL)
        {
            return;
        }
        dest->per_logger_level->auto_free = 1;
    }
    else
    {
        list_clear(dest->per_logger_level);
    }

    if (src->per_logger_level == NULL)
    {
        return;
    }

    int i;

    for (i = 0; i < src->per_logger_level->count; ++i)
    {
        struct log_logger_level *dst_logger =
            (struct log_logger_level *)g_malloc(sizeof(struct log_logger_level), 1);

        *dst_logger = *(struct log_logger_level *) list_get_item(src->per_logger_level, i),

         list_add_item(dest->per_logger_level, (tbus) dst_logger);
    }
#endif
}

void
internal_log_config_copy(struct log_config *dest, const struct log_config *src)
{
    if (src != NULL && dest != NULL)
    {
        dest->fd = src->fd;
        g_free(dest->log_file);
        dest->log_file = g_strdup(src->log_file);
#ifdef LOG_ENABLE_THREAD
        dest->log_lock = src->log_lock;
        dest->log_lock_attr = src->log_lock_attr;
#endif
        dest->program_name = src->program_name;
        dest->enable_pid = src->enable_pid;
        dest->dump_on_start = src->dump_on_start;

        internal_log_config_copy_levels(dest, src);
    }
}

bool_t
internal_log_is_enabled_for_level(const enum logLevels log_level,
                                  const bool_t override_destination_level,
                                  const enum logLevels override_log_level)
{
    /* Is log initialized? */
    if (g_staticLogConfig == NULL)
    {
        return 0;
    }
    else if (g_staticLogConfig->fd < 0
             && !g_staticLogConfig->enable_syslog
             && !g_staticLogConfig->enable_console)
    {
        /* all logging outputs are disabled */
        return 0;
    }
    else if (override_destination_level)
    {
        /* Override is enabled - should the message should be logged? */
        return log_level <= override_log_level;
    }
    /* Override is disabled - Is there at least one log destination
     * which will accept the message based on the log level? */
    else if (g_staticLogConfig->fd >= 0
             && log_level <= g_staticLogConfig->log_level)
    {
        return 1;
    }
    else if (g_staticLogConfig->enable_syslog
             && log_level <= g_staticLogConfig->syslog_level)
    {
        return 1;
    }
    else if (g_staticLogConfig->enable_console
             && log_level <= g_staticLogConfig->console_level)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

bool_t
internal_log_location_overrides_level(const char *function_name,
                                      const char *file_name,
                                      enum logLevels *log_level_return)
{
#ifdef LOG_PER_LOGGER_LEVEL
    struct log_logger_level *logger = NULL;
    int i;

    if (g_staticLogConfig == NULL)
    {
        return 0;
    }
    for (i = 0; i < g_staticLogConfig->per_logger_level->count; i++)
    {
        logger = (struct log_logger_level *)list_get_item(g_staticLogConfig->per_logger_level, i);

        if ((logger->logger_type == LOG_TYPE_FILE
                && 0 == g_strncmp(logger->logger_name, file_name, LOGGER_NAME_SIZE))
                || (logger->logger_type == LOG_TYPE_FUNCTION
                    && 0 == g_strncmp(logger->logger_name, function_name, LOGGER_NAME_SIZE)))
        {
            *log_level_return = logger->log_level;
            return 1;
        }
    }
#endif

    return 0;
}

/*
 * Here below the public functions
 */

struct log_config *
log_config_init_for_console(enum logLevels lvl, const char *override_name)
{
    struct log_config *config = internalInitAndAllocStruct();

    if (config != NULL)
    {
        config->program_name = "<null>";
        config->enable_console = 1;
        if (override_name != NULL && override_name[0] != '\0')
        {
            config->console_level = internal_log_text2level(override_name);
        }
        else
        {
            config->console_level = lvl;
        }
    }
    return config;
}


struct log_config *
log_config_init_from_config(const char *iniFilename,
                            const char *applicationName,
                            const char *section_prefix)
{
    int fd;
    struct log_config *config;

    if (applicationName == NULL)
    {
        g_writeln("Programming error your application name cannot be null");
        return NULL;
    }

    if (iniFilename == NULL)
    {
        g_writeln("The inifile is null to log_config_init_from_config!");
        return NULL;
    }

    fd = g_file_open_ro(iniFilename);

    if (-1 == fd)
    {
        g_writeln("We could not open the configuration file to read log parameters");
        return NULL;
    }

    /* read logging config */
    config = internal_config_read_logging(fd, applicationName, section_prefix);

    /* cleanup */
    g_file_close(fd);
    return config;
}

enum logReturns
log_config_free(struct log_config *config)
{
    if (config != NULL)
    {
#ifdef LOG_PER_LOGGER_LEVEL
        if (config->per_logger_level != NULL)
        {
            list_delete(config->per_logger_level);
            config->per_logger_level = NULL;
        }
#endif

        if (0 != config->log_file)
        {
            g_free(config->log_file);
            config->log_file = 0;
        }

        g_free(config);
    }

    return LOG_STARTUP_OK;
}

/**
 * Restarts the logging.
 *
 * The logging file is never changed, as it is common in xrdp to share a
 * log file between parents and children. The end result would be
 * confusing for the user.
 */
static enum logReturns
log_restart_from_param(const struct log_config *lc)
{
    enum logReturns rv = LOG_GENERAL_ERROR;

    if (g_staticLogConfig == NULL)
    {
        log_message(LOG_LEVEL_ALWAYS, "Log not already initialized");
    }
    else if (lc == NULL)
    {
        g_writeln("lc to log_start_from_param is NULL");
    }
    else
    {
        if (g_staticLogConfig->fd >= 0 &&
                g_strcmp(g_staticLogConfig->log_file, lc->log_file) != 0)
        {
            log_message(LOG_LEVEL_WARNING,
                        "Unable to change log file name from %s to %s",
                        g_staticLogConfig->log_file,
                        lc->log_file);
        }
        /* Reconfigure syslog logging, allowing for a program_name change */
        if (g_staticLogConfig->enable_syslog)
        {
            closelog();
        }
        if (lc->enable_syslog)
        {
            openlog(lc->program_name, LOG_CONS | LOG_PID, LOG_DAEMON);
        }

        /* Copy over simple values... */
#ifdef LOG_ENABLE_THREAD
        g_staticLogConfig->log_lock = lc->log_lock;
        g_staticLogConfig->log_lock_attr = lc->log_lock_attr;
#endif
        g_staticLogConfig->program_name = lc->program_name;
        g_staticLogConfig->enable_pid = lc->enable_pid;
        g_staticLogConfig->dump_on_start = lc->dump_on_start;

        /* ... and the log levels */
        internal_log_config_copy_levels(g_staticLogConfig, lc);
        rv = LOG_STARTUP_OK;
    }
    return rv;
}

enum logReturns
log_start_from_param(const struct log_config *src_log_config)
{
    enum logReturns ret = LOG_GENERAL_ERROR;

    if (g_staticLogConfig != NULL)
    {
        log_message(LOG_LEVEL_ALWAYS, "Log already initialized");
        return ret;
    }

    if (src_log_config == NULL)
    {
        g_writeln("src_log_config to log_start_from_param is NULL");
        return ret;
    }
    else
    {
        g_staticLogConfig = internalInitAndAllocStruct();
        if (g_staticLogConfig == NULL)
        {
            g_writeln("internalInitAndAllocStruct failed");
            return LOG_ERROR_MALLOC;
        }
        internal_log_config_copy(g_staticLogConfig, src_log_config);

        ret = internal_log_start(g_staticLogConfig);
        if (ret != LOG_STARTUP_OK)
        {
            g_writeln("Could not start log");

            log_config_free(g_staticLogConfig);
            g_staticLogConfig = NULL;
        }
    }

    return ret;
}

/**
 * This function initialize the log facilities according to the configuration
 * file, that is described by the in parameter.
 * @param iniFile
 * @param applicationName, the name that is used in the log for the running application
 * @return 0 on success
 */
enum logReturns
log_start(const char *iniFile, const char *applicationName,
          unsigned int flags)
{
    enum logReturns ret = LOG_GENERAL_ERROR;
    struct log_config *config;

    config = log_config_init_from_config(iniFile, applicationName, "");

    if (config != NULL)
    {
        config->dump_on_start = (flags & LOG_START_DUMP_CONFIG) ? 1 : 0;
        if (flags & LOG_START_RESTART)
        {
            ret = log_restart_from_param(config);
            if (ret != LOG_STARTUP_OK)
            {
                g_writeln("Could not restart log");
            }
        }
        else
        {
            ret = log_start_from_param(config);
            if (ret != LOG_STARTUP_OK)
            {
                g_writeln("Could not start log");
            }
        }
        log_config_free(config);
    }
    else
    {
        g_writeln("Error reading configuration for log based on config: %s",
                  iniFile);
    }

    return ret;
}

/**
 * Function that terminates all logging
 * @return
 */
enum logReturns
log_end(void)
{
    enum logReturns ret = LOG_GENERAL_ERROR;
    ret = internal_log_end(g_staticLogConfig);
    log_config_free(g_staticLogConfig);
    g_staticLogConfig = NULL;

    return ret;
}

/*****************************************************************************/
/* log a hex dump */
enum logReturns
log_hexdump(const enum logLevels log_level,
            const char *message,
            const char *src,
            int len)
{
    return log_hexdump_with_location("", "", 0, log_level, message, src, len);
}

/*****************************************************************************/
/* log a hex dump */
enum logReturns
log_hexdump_with_location(const char *function_name,
                          const char *file_name,
                          const int line_number,
                          const enum logLevels log_level,
                          const char *message,
                          const char *src,
                          int len)
{
    char *dump_buffer;
    enum logReturns rv = LOG_STARTUP_OK;
    enum logLevels override_log_level = LOG_LEVEL_NEVER;
    bool_t override_destination_level = 0;

    override_destination_level = internal_log_location_overrides_level(
        function_name,
        file_name,
        &override_log_level);
    if (!internal_log_is_enabled_for_level(log_level, override_destination_level, override_log_level))
    {
        return LOG_STARTUP_OK;
    }

    /* Start the dump on a new line so that the first line of the dump is
       aligned to the first column instead of to after the log message
       preamble (eg. time, log level, ...)
    */
#ifdef _WIN32
#define HEX_DUMP_HEADER ("Hex Dump:\r\n")
#else
#ifdef _MACOS
#define HEX_DUMP_HEADER ("Hex Dump:\r")
#else
#define HEX_DUMP_HEADER ("Hex Dump:\n")
#endif
#endif

    dump_buffer = g_bytes_to_hexdump(src, len);

    if (dump_buffer != NULL)
    {
        if (g_strlen(file_name) > 0)
        {
            rv = log_message_with_location(function_name, file_name, line_number,
                                           log_level, "%s %s%s",
                                           message, HEX_DUMP_HEADER, dump_buffer);
        }
        else
        {
            rv = log_message(log_level, "%s %s%s",
                             message, HEX_DUMP_HEADER, dump_buffer);
        }
        g_free(dump_buffer);
    }
    return rv;
}

enum logReturns
log_message_with_location(const char *function_name,
                          const char *file_name,
                          const int line_number,
                          const enum logLevels level,
                          const char *msg,
                          ...)
{
    va_list ap;
    enum logReturns rv;
    char buff[LOG_BUFFER_SIZE];
    enum logLevels override_log_level = LOG_LEVEL_NEVER;
    bool_t override_destination_level = 0;

    if (g_staticLogConfig == NULL)
    {
        g_writeln("The log reference is NULL - log not initialized properly "
                  "when called from [%s(%s:%d)]",
                  (function_name != NULL ? function_name : "unknown_function"),
                  (file_name != NULL ? file_name : "unknown_file"),
                  line_number);
        return LOG_ERROR_NO_CFG;
    }

    override_destination_level = internal_log_location_overrides_level(
        function_name,
        file_name,
        &override_log_level);
    if (!internal_log_is_enabled_for_level(level, override_destination_level, override_log_level))
    {
        return LOG_STARTUP_OK;
    }

    g_snprintf(buff, LOG_BUFFER_SIZE, "[%s(%s:%d)] %s",
               function_name, file_name, line_number, msg);

    va_start(ap, msg);
    rv = internal_log_message(level, override_destination_level, override_log_level, buff, ap);
    va_end(ap);
    return rv;
}

enum logReturns
log_message(const enum logLevels lvl, const char *msg, ...)
{
    va_list ap;
    enum logReturns rv;

    va_start(ap, msg);
    rv = internal_log_message(lvl, 0, LOG_LEVEL_NEVER, msg, ap);
    va_end(ap);
    return rv;
}

enum logReturns
internal_log_message(const enum logLevels lvl,
                     const bool_t override_destination_level,
                     const enum logLevels override_log_level,
                     const char *msg,
                     va_list ap)
{
    char buff[LOG_BUFFER_SIZE + 43]; /* 31 ("[2022-10-07T19:58:33.065+0900] ") + 8 (log level) + 4 (space+cr+lf+\0) */
    int len = 0;
    enum logReturns rv = LOG_STARTUP_OK;
    int writereply = 0;

    if (g_staticLogConfig == NULL)
    {
        g_writeln("The log reference is NULL - log not initialized properly");
        return LOG_ERROR_NO_CFG;
    }

    if (0 > g_staticLogConfig->fd
            && g_staticLogConfig->enable_syslog == 0
            && g_staticLogConfig->enable_console == 0)
    {
        return LOG_ERROR_FILE_NOT_OPEN;
    }

    if (!internal_log_is_enabled_for_level(lvl, override_destination_level, override_log_level))
    {
        return LOG_STARTUP_OK;
    }

    getFormattedDateTime(buff, 32);

    internal_log_lvl2str(lvl, buff + 31);

    if (g_staticLogConfig->enable_pid)
    {
        /* 31 (datetime) + 8 (log level) = 39 */
        g_snprintf(buff + 39, LOG_BUFFER_SIZE, "[pid:%d tid:%lld] ",
                   g_getpid(), (long long) tc_get_threadid());
        len = g_strlen(buff + 39);
    }
    len += vsnprintf(buff + 39 + len, LOG_BUFFER_SIZE - len, msg, ap);

    /* checking for truncated messages */
    if (len > LOG_BUFFER_SIZE)
    {
        log_message(LOG_LEVEL_WARNING, "next message will be truncated");
        len = LOG_BUFFER_SIZE;
    }

    /* forcing the end of message string */
    /* 31 (datetime) + 8 (log level) = 39 */
#ifdef _WIN32
    buff[len + 39] = '\r';
    buff[len + 40] = '\n';
    buff[len + 41] = '\0';
#else
#ifdef _MACOS
    buff[len + 39] = '\r';
    buff[len + 40] = '\0';
#else
    buff[len + 39] = '\n';
    buff[len + 40] = '\0';
#endif
#endif

    if (g_staticLogConfig->enable_syslog
            && ((override_destination_level && lvl <= override_log_level)
                || (!override_destination_level && lvl <= g_staticLogConfig->syslog_level)))
    {
        /* log to syslog*/
        /* %s fix compiler warning 'not a string literal' */
        syslog(internal_log_xrdp2syslog(lvl), "%s", buff + 31);
    }

    if (g_staticLogConfig->enable_console
            && ((override_destination_level && lvl <= override_log_level)
                || (!override_destination_level && lvl <= g_staticLogConfig->console_level)))
    {
        /* log to console */
        g_printf("%s", buff);
    }

    if ((override_destination_level && lvl <= override_log_level)
            || (!override_destination_level && lvl <= g_staticLogConfig->log_level))
    {
        /* log to application logfile */
        if (g_staticLogConfig->fd >= 0)
        {
#ifdef LOG_ENABLE_THREAD
            pthread_mutex_lock(&(g_staticLogConfig->log_lock));
#endif

            writereply = g_file_write(g_staticLogConfig->fd, buff, g_strlen(buff));

            if (writereply <= 0)
            {
                rv = LOG_ERROR_NULL_FILE;
            }

#ifdef LOG_ENABLE_THREAD
            pthread_mutex_unlock(&(g_staticLogConfig->log_lock));
#endif
        }
    }

    return rv;
}

/**
 * Return the configured log file name
 * @return
 */
char *
getLogFile(char *replybuf, int bufsize)
{
    if (g_staticLogConfig)
    {
        if (g_staticLogConfig->log_file)
        {
            g_strncpy(replybuf, g_staticLogConfig->log_file, bufsize);
        }
        else
        {
            g_sprintf(replybuf, "The log_file name is NULL");
        }
    }
    else
    {
        g_snprintf(replybuf, bufsize, "The log is not properly started");
    }

    return replybuf;
}

/**
 * Returns formatted datetime for log
 * @return
 */
char *
getFormattedDateTime(char *replybuf, int bufsize)
{
    char buf_datetime[21]; /* 2022-10-07T16:36:04 + . */
    char buf_millisec[4];  /* 357 */
    char buf_timezone[6];  /* +0900 */

    struct tm *now;
    struct timeval tv;
    int millisec;

    gettimeofday(&tv, NULL);
    now = localtime(&tv.tv_sec);

    millisec = (tv.tv_usec + 500 / 1000);
    g_snprintf(buf_millisec, sizeof(buf_millisec), "%03d", millisec);

    strftime(buf_datetime, sizeof(buf_datetime), "%FT%T.", now);
    strftime(buf_timezone, sizeof(buf_timezone), "%z", now);
    g_snprintf(replybuf, bufsize, "[%s%s%s] ", buf_datetime, buf_millisec, buf_timezone);

    return replybuf;
}

/*****************************************************************************/
#ifdef USE_DEVEL_LOGGING
void
log_devel_leaking_fds(const char *exe, int min, int max)
{
    struct list *fd_list = g_get_open_fds(min, max);

    if (fd_list != NULL)
    {
        int i;
        for (i = 0 ; i < fd_list->count ; ++i)
        {
            int fd = (int)fd_list->items[i];
            if (g_file_get_cloexec(fd) == 0)
            {
                LOG_DEVEL(LOG_LEVEL_WARNING,
                          "File descriptor %d is not CLOEXEC when running %s",
                          fd, exe);
            }
        }
    }
}
#endif // USE_DEVEL_LOGGING

