/*
 * Copyright (c) 2016 MariaDB Corporation Ab
 *
 * Use of this software is governed by the Business Source License included
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11. 
 *
 * Change Date: 2024-08-24
 *
 * On the date above, in accordance with the Business Source License, use
 * of this software will be governed by version 2 or later of the General
 * Public License. 
 */

/**
 * @file gateway.c - The entry point of MaxScale
 */

#include <maxscale/cdefs.h>

#ifdef HAVE_GLIBC
#include <execinfo.h>
#endif
#include <ftw.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif

#include <set>
#include <map>
#include <fstream>

#include <ini.h>
#include <openssl/opensslconf.h>
#include <pwd.h>
#include <sys/file.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <maxbase/maxbase.hh>
#include <maxbase/stacktrace.hh>
#include <maxsql/mariadb.hh>
#include <maxbase/alloc.h>
#include <maxscale/adminusers.h>
#include <maxscale/dcb.hh>
#include <maxscale/housekeeper.h>
#include <maxscale/mainworker.hh>
#include <maxscale/maxscale.h>
#include <maxscale/mysql_utils.hh>
#include <maxscale/paths.h>
#include <maxscale/query_classifier.hh>
#include <maxscale/server.hh>
#include <maxscale/sqlite3.h>
#include <maxscale/session.hh>
#include <maxscale/utils.h>
#include <maxscale/version.h>
#include <maxscale/random.h>
#include <maxscale/routingworker.hh>

#include "internal/admin.hh"
#include "internal/config.hh"
#include "internal/maxscale.hh"
#include "internal/modules.hh"
#include "internal/monitor.hh"
#include "internal/monitormanager.hh"
#include "internal/poll.hh"
#include "internal/service.hh"

using namespace maxscale;

#define STRING_BUFFER_SIZE 1024
#define PIDFD_CLOSED       -1

/** for procname */
#if ! defined (_GNU_SOURCE)
#  define _GNU_SOURCE
#endif

#if defined (OPENSSL_THREADS)
#define HAVE_OPENSSL_THREADS 1
#else
#define HAVE_OPENSSL_THREADS 0
#endif

extern char* program_invocation_name;
extern char* program_invocation_short_name;

/* The data directory we created for this gateway instance */
static char datadir[PATH_MAX + 1] = "";
static bool datadir_defined = false;    /*< If the datadir was already set */
/* The data directory we created for this gateway instance */
static char pidfile[PATH_MAX + 1] = "";
static int pidfd = PIDFD_CLOSED;
/* Map containing paths to directory locks with their fds */
static std::map<std::string, int> directory_locks;

/**
 * If MaxScale is started to run in daemon process the value is true. 
 */
static bool daemon_mode = true;

static const char* maxscale_commit = MAXSCALE_COMMIT;

const char* progname = NULL;

#ifdef HAVE_GLIBC
// getopt_long is a GNU extension
static struct option long_options[] =
{
    {"config-check",        no_argument,       0, 'c'},
    {"export-config",       required_argument, 0, 'e'},
    {"daemon",              no_argument,       0, 'n'},
    {"nodaemon",            no_argument,       0, 'd'},
    {"config",              required_argument, 0, 'f'},
    {"log",                 required_argument, 0, 'l'},
    {"logdir",              required_argument, 0, 'L'},
    {"cachedir",            required_argument, 0, 'A'},
    {"libdir",              required_argument, 0, 'B'},
    {"configdir",           required_argument, 0, 'C'},
    {"datadir",             required_argument, 0, 'D'},
    {"execdir",             required_argument, 0, 'E'},
    {"persistdir",          required_argument, 0, 'F'},
    {"module_configdir",    required_argument, 0, 'M'},
    {"language",            required_argument, 0, 'N'},
    {"piddir",              required_argument, 0, 'P'},
    {"basedir",             required_argument, 0, 'R'},
    {"runtimedir",          required_argument, 0, 'r'},
    {"user",                required_argument, 0, 'U'},
    {"syslog",              required_argument, 0, 's'},
    {"maxlog",              required_argument, 0, 'S'},
    {"log_augmentation",    required_argument, 0, 'G'},
    {"version",             no_argument,       0, 'v'},
    {"version-full",        no_argument,       0, 'V'},
    {"help",                no_argument,       0, '?'}, 
    {"connector_plugindir", required_argument, 0, 'H'},
    {"passive",             no_argument,       0, 'p'},
    {"debug",               required_argument, 0, 'g'},
    {0,                     0,                 0, 0  }
};
#endif

static bool syslog_configured = false;
static bool maxlog_configured = false;
static volatile sig_atomic_t last_signal = 0;
static bool unload_modules_at_exit = true;
static std::string redirect_output_to;

static int   cnf_preparser(void* data, const char* section, const char* name, const char* value);
static int   write_pid_file();  /* write MaxScale pidfile */
static bool  lock_dir(const std::string& path);
static bool  lock_directories();
static void  unlock_directories();
static void  unlink_pidfile(void);  /* remove pidfile */
static void  unlock_pidfile();
static bool  file_write_header(FILE* outfile);
static bool  file_write_footer(FILE* outfile);
static void  write_footer(void);
static int   ntfw_cb(const char*, const struct stat*, int, struct FTW*);
static bool  is_file_and_readable(const char* absolute_pathname);
static bool  path_is_readable(const char* absolute_pathname);
static bool  path_is_writable(const char* absolute_pathname);
bool         handle_path_arg(char** dest, const char* path, const char* arg, bool rd, bool wr);
static bool  handle_debug_args(char* args);
static void  set_log_augmentation(const char* value);
static void  usage(void);
static char* get_expanded_pathname(char** abs_path,
                                   const char* input_path,
                                   const char* fname);
static void print_log_n_stderr(bool do_log,
                               bool do_stderr,
                               const char* logstr,
                               const char* fprstr,
                               int eno);
static bool resolve_maxscale_conf_fname(char** cnf_full_path,
                                        const char* home_dir,
                                        char* cnf_file_arg);

static char* check_dir_access(char* dirname, bool, bool);
static int   set_user(const char* user);
bool         pid_file_exists();
void         write_child_exit_code(int fd, int code);
static bool  change_cwd();
static void  log_exit_status();
static bool  daemonize();
static bool  sniff_configuration(const char* filepath);
static bool  modules_process_init();
static void  modules_process_finish();
static void  disable_module_unloading(const char* arg);
static void  enable_module_unloading(const char* arg);
static void  enable_statement_logging(const char* arg);
static void  disable_statement_logging(const char* arg);
static void  redirect_output_to_file(const char* arg);
static bool  user_is_acceptable(const char* specified_user);
static bool  init_sqlite3();

struct DEBUG_ARGUMENT
{
    const char* name;                       /**< The name of the debug argument */
    void        (* action)(const char* arg);/**< The function implementing the argument */
    const char* description;                /**< Help text */
};

#define SPACER "                              "

const DEBUG_ARGUMENT debug_arguments[] =
{
    {
        "disable-module-unloading", disable_module_unloading,
        "disable module unloading at exit.  Will produce better\n"
        SPACER "Valgrind leak reports if leaked memory was allocated in\n"
        SPACER "a shared library"
    },
    {
        "enable-module-unloading", enable_module_unloading,
        "cancels disable-module-unloading"
    },
    {
        "redirect-output-to-file", redirect_output_to_file,
        "redirect stdout and stderr to the file given as an argument"
    },
    {
        "enable-statement-logging", enable_statement_logging,
        "enable the logging of monitor and authenticator SQL statements sent by MaxScale to the servers"
    },
    {
        "disable-statement-logging", disable_statement_logging,
        "disable the logging of monitor and authenticator SQL statements sent by MaxScale to the servers"
    },
    {NULL, NULL, NULL}
};

#ifndef OPENSSL_1_1
/** SSL multi-threading functions and structures */

static pthread_mutex_t* ssl_locks;

static void ssl_locking_function(int mode, int n, const char* file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        pthread_mutex_lock(&ssl_locks[n]);
    }
    else
    {
        pthread_mutex_unlock(&ssl_locks[n]);
    }
}
/**
 * OpenSSL requires this struct to be defined in order to use dynamic locks
 */
struct CRYPTO_dynlock_value
{
    pthread_mutex_t lock;
};

/**
 * Create a dynamic OpenSSL lock.  The dynamic lock is just a wrapper structure
 * around a SPINLOCK structure. 
 * @param file File name
 * @param line Line number
 * @return Pointer to new lock or NULL of an error occurred
 */
static struct CRYPTO_dynlock_value* ssl_create_dynlock(const char* file, int line)
{
    struct CRYPTO_dynlock_value* lock =
        (struct CRYPTO_dynlock_value*) MXS_MALLOC(sizeof(struct CRYPTO_dynlock_value));
    if (lock)
    {
        pthread_mutex_init(&lock->lock, NULL);
    }
    return lock;
}

/**
 * Lock a dynamic lock for OpenSSL. 
 * @param mode
 * @param n pointer to lock
 * @param file File name
 * @param line Line number
 */
static void ssl_lock_dynlock(int mode, struct CRYPTO_dynlock_value* n, const char* file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        pthread_mutex_lock(&n->lock);
    }
    else
    {
        pthread_mutex_unlock(&n->lock);
    }
}

/**
 * Free a dynamic OpenSSL lock. 
 * @param n Lock to free
 * @param file File name
 * @param line Line number
 */
static void ssl_free_dynlock(struct CRYPTO_dynlock_value* n, const char* file, int line)
{
    MXS_FREE(n);
}

#ifdef OPENSSL_1_0
/**
 * The thread ID callback function for OpenSSL dynamic locks. 
 * @param id Id to modify
 */
static void maxscale_ssl_id(CRYPTO_THREADID* id)
{
    CRYPTO_THREADID_set_numeric(id, pthread_self());
}
#endif
#endif

/**
 * Handler for SIGHUP signal. 
 */
static void sighup_handler(int i)
{
    // Legacy configuration reload handler
}

/**
 * Handler for SIGUSR1 signal.  A SIGUSR1 signal will cause
 * maxscale to rotate all log files. 
 */
static void sigusr1_handler(int i)
{
    MXS_NOTICE("Log file flush following reception of SIGUSR1\n");
    mxs_log_rotate();
}

static const char shutdown_msg[] = "\n\nShutting down MaxScale\n\n";
static const char patience_msg[] =
    "\n"
    "Patience is a virtue...\n"
    "Shutdown in progress, but one more Ctrl-C or SIGTERM and MaxScale goes down,\n"
    "no questions asked.\n";

static void sigterm_handler(int i)
{
    last_signal = i;
    int n_shutdowns = maxscale_shutdown();

    if (n_shutdowns == 1)
    {
        if (! daemon_mode)
        {
            if (write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1) == -1)
            {
                printf("Failed to write shutdown message!\n");
            }
        }
    }
    else
    {
        exit(EXIT_FAILURE);
    }
}

static void sigint_handler(int i)
{
    last_signal = i;
    int n_shutdowns = maxscale_shutdown();

    if (n_shutdowns == 1)
    {
        if (! daemon_mode)
        {
            if (write(STDERR_FILENO, shutdown_msg, sizeof(shutdown_msg) - 1) == -1)
            {
                printf("Failed to write shutdown message!\n");
            }
        }
    }
    else if (n_shutdowns == 2)
    {
        if (! daemon_mode)
        {
            if (write(STDERR_FILENO, patience_msg, sizeof(patience_msg) - 1) == -1)
            {
                printf("Failed to write shutdown message!\n");
            }
        }
    }
    else
    {
        exit(EXIT_FAILURE);
    }
}

volatile sig_atomic_t fatal_handling = 0;

static int signal_set(int sig, void (* handler)(int));

static void sigfatal_handler(int i)
{
    thread_local std::thread::id current_id;
    std::thread::id no_id;

    if (current_id ! = no_id)
    {
        // Fatal error when processing a fatal error. 
        // TODO: This should be overhauled to proper signal handling (MXS-599). 
        signal_set(i, SIG_DFL);
        raise(i);
    }

    current_id = std::this_thread::get_id();

    MXS_CONFIG* cnf = config_get_global_options();
    fprintf(stderr,
            "Fatal: MaxScale %s received fatal signal %d. "
            "Commit ID: %s System name: %s Release string: %s\n\n",
            MAXSCALE_VERSION, i, maxscale_commit, cnf->sysname, cnf->release_string);

    MXS_ALERT("Fatal: MaxScale %s received fatal signal %d. "
              "Commit ID: %s System name: %s Release string: %s",
              MAXSCALE_VERSION, i, maxscale_commit, cnf->sysname, cnf->release_string);

    const char* pStmt;
    size_t nStmt;

    if (! qc_get_current_stmt(&pStmt, &nStmt))
    {
        pStmt = "none/unknown";
        nStmt = strlen(pStmt);
    }

    MXS_ALERT("Statement currently being classified: %.*s", (int)nStmt, pStmt);

    if (DCB* dcb = dcb_get_current())
    {
        if (dcb->session)
        {
            session_dump_statements(dcb->session);
            session_dump_log(dcb->session);
        }
    }

    thread_local char msg[4096] = "";
    msg[0] = '\0';

    auto cb = [](const char* symbol, const char* cmd) {
            char buf[512];
            snprintf(buf, sizeof(buf), "  %s: %s\n", symbol, cmd);
            strcat(msg, buf);
        };

    mxb::dump_stacktrace(cb);

    MXS_ALERT("\n%s", msg);

    /* re-raise signal to enforce core dump */
    fprintf(stderr, "\n\nWriting core dump\n");
    signal_set(i, SIG_DFL);
    raise(i);
}

/**
 * @node Wraps sigaction calls
 *
 * Parameters:
 * @param sig Signal to set
 * @param void Handler function for signal *
 *
 * @return 0 in success, 1 otherwise
 *
 *
 * @details (write detailed description here)
 *
 */
static int signal_set(int sig, void (* handler)(int))
{
    int rc = 0;

    struct sigaction sigact = {};
    sigact.sa_handler = handler;

    int err;

    do
    {
        errno = 0;
        err = sigaction(sig, &sigact, NULL);
    }
    while (errno == EINTR);

    if (err < 0)
    {
        MXS_ERROR("Failed call sigaction() in %s due to %d, %s.",
                  program_invocation_short_name,
                  errno,
                  mxs_strerror(errno));
        rc = 1;
    }

    return rc;
}

/**
 * @brief Create the data directory for this process
 *
 * This will prevent conflicts when multiple MaxScale instances are run on the
 * same machine. 
 * @param base Base datadir path
 * @param datadir The result where the process specific datadir is stored
 * @return True if creation was successful and false on error
 */
static bool create_datadir(const char* base, char* datadir)
{
    bool created = false;
    int len = 0;

    if ((len = snprintf(datadir, PATH_MAX, "%s", base)) < PATH_MAX
        && (mkdir(datadir, 0777) == 0 || errno == EEXIST))
    {
        if ((len = snprintf(datadir, PATH_MAX, "%s/data%d", base, getpid())) < PATH_MAX)
        {
            if ((mkdir(datadir, 0777) == 0) || (errno == EEXIST))
            {
                created = true;
            }
            else
            {
                MXS_ERROR("Cannot create data directory '%s': %d %s\n",
                          datadir,
                          errno,
                          mxs_strerror(errno));
            }
        }
    }
    else
    {
        if (len < PATH_MAX)
        {
            fprintf(stderr,
                    "Error: Cannot create data directory '%s': %d %s\n",
                    datadir,
                    errno,
                    mxs_strerror(errno));
        }
        else
        {
            MXS_ERROR("Data directory pathname exceeds the maximum allowed pathname "
                      "length: %s/data%d.", 
                      base,
                      getpid());
        }
    }

    return created;
}

/**
 * Cleanup the temporary data directory we created for the gateway
 */
int ntfw_cb(const char* filename,
            const struct stat* filestat,
            int fileflags,
            struct FTW* pfwt)
{
    int rc = 0;
    int datadir_len = strlen(get_datadir());
    std::string filename_string(filename + datadir_len);

    if (strncmp(filename_string.c_str(), "/data", 5) == 0)
    {
        rc = remove(filename);
        if (rc ! = 0)
        {
            int eno = errno;
            errno = 0;
            MXS_ERROR("Failed to remove the data directory %s of MaxScale due to %d, %s.",
                      filename_string.c_str(),
                      eno,
                      mxs_strerror(eno));
        }
    }
    return rc;
}

/**
 * @brief Clean up the data directory
 *
 * This removes the process specific datadir which is currently only used by
 * the embedded library.  In the future this directory could contain other
 * temporary files and relocating this to to, for example, /tmp/ could make sense. 
 */
void cleanup_process_datadir()
{
    int depth = 1;
    int flags = FTW_CHDIR | FTW_DEPTH | FTW_MOUNT;
    const char* proc_datadir = get_process_datadir();

    if (strcmp(proc_datadir, get_datadir()) ! = 0 && access(proc_datadir, F_OK) == 0)
    {
        nftw(proc_datadir, ntfw_cb, depth, flags);
    }
}

void cleanup_old_process_datadirs()
{
    int depth = 1;
    int flags = FTW_CHDIR | FTW_DEPTH | FTW_MOUNT;
    nftw(get_datadir(), ntfw_cb, depth, flags);
}

static void write_footer(void)
{
    file_write_footer(stdout);
}

static bool file_write_footer(FILE* outfile)
{
    bool succp = false;
    size_t len1;
    const char* header_buf1;

    header_buf1 = "------------------------------------------------------"
                  "\n\n";
    len1 = strlen(header_buf1);
    fwrite((void*)header_buf1, len1, 1, outfile);

    succp = true;

    return succp;
}

// Documentation says 26 bytes is enough, but 32 is a nice round number. 
#define ASCTIME_BUF_LEN 32
static bool file_write_header(FILE* outfile)
{
    bool succp = false;
    size_t len1;
    size_t len2;
    size_t len3;
    const char* header_buf1;
    char header_buf2[ASCTIME_BUF_LEN];
    const char* header_buf3;
    time_t t;
    struct tm tm;
#if defined (LAPTOP_TEST)
    struct timespec ts1;
    ts1.tv_sec = 0;
    ts1.tv_nsec = DISKWRITE_LATENCY * 1000000;
#endif

#if ! defined (SS_DEBUG)
    return true;
#endif

    t = time(NULL);
    localtime_r(&t, &tm);

    header_buf1 = "\n\nMariaDB Corporation MaxScale " MAXSCALE_VERSION "\t";
    asctime_r(&tm, header_buf2);
    header_buf3 = "------------------------------------------------------\n";

    len1 = strlen(header_buf1);
    len2 = strlen(header_buf2);
    len3 = strlen(header_buf3);
#if defined (LAPTOP_TEST)
    nanosleep(&ts1, NULL);
#else
    fwrite((void*)header_buf1, len1, 1, outfile);
    fwrite((void*)header_buf2, len2, 1, outfile);
    fwrite((void*)header_buf3, len3, 1, outfile);
#endif

    succp = true;

    return succp;
}

static bool resolve_maxscale_conf_fname(char** cnf_full_path,
                                        const char* home_dir,
                                        char* cnf_file_arg)
{
    if (cnf_file_arg)
    {
        *cnf_full_path = (char*)MXS_MALLOC(PATH_MAX + 1);
        MXS_ABORT_IF_NULL(*cnf_full_path);

        if (! realpath(cnf_file_arg, *cnf_full_path))
        {
            const char* logstr = "Failed to open read access to configuration file."; 
            int eno = errno;
            print_log_n_stderr(true, true, logstr, logstr, eno);
            MXS_FREE(*cnf_full_path);
            *cnf_full_path = NULL;
        }
    }
    else    /*< default config file name is used */
    {
        *cnf_full_path = get_expanded_pathname(NULL, home_dir, default_cnf_fname);
    }

    return *cnf_full_path && is_file_and_readable(*cnf_full_path);
}

/**
 * Check read and write accessibility to a directory. 
 * @param dirname       directory to be checked
 *
 * @return NULL if directory can be read and written, an error message if either
 *      read or write is not permitted. 
 */
static char* check_dir_access(char* dirname, bool rd, bool wr)
{
    char errbuf[PATH_MAX * 2];
    char* errstr = NULL;

    if (dirname == NULL)
    {
        errstr = MXS_STRDUP_A("Directory argument is NULL");
        goto retblock;
    }

    if (access(dirname, F_OK) ! = 0)
    {
        snprintf(errbuf, PATH_MAX * 2 - 1, "Can't access '%s'.",  dirname);
        errbuf[PATH_MAX * 2 - 1] = '\0';
        errstr = MXS_STRDUP_A(errbuf);
        goto retblock;
    }

    if (rd && ! path_is_readable(dirname))
    {
        snprintf(errbuf,
                 PATH_MAX * 2 - 1,
                 "MaxScale doesn't have read permission "
                 "to '%s'.", 
                 dirname);
        errbuf[PATH_MAX * 2 - 1] = '\0';
        errstr = MXS_STRDUP_A(errbuf);
        goto retblock;
    }

    if (wr && ! path_is_writable(dirname))
    {
        snprintf(errbuf,
                 PATH_MAX * 2 - 1,
                 "MaxScale doesn't have write permission "
                 "to '%s'.", 
                 dirname);
        errbuf[PATH_MAX * 2 - 1] = '\0';
        errstr = MXS_STRDUP_A(errbuf);
        goto retblock;
    }

retblock:
    return errstr;
}

static bool init_log()
{
    bool rval = false;
    MXS_CONFIG* cnf = config_get_global_options();

    if (! cnf->config_check && mkdir(get_logdir(), 0777) ! = 0 && errno ! = EEXIST)
    {
        fprintf(stderr,
                "Error: Cannot create log directory '%s': %d, %s\n",
                default_logdir,
                errno,
                strerror(errno));
    }
    else if (mxs_log_init(NULL, get_logdir(), cnf->log_target))
    {

        mxs_log_set_syslog_enabled(cnf->syslog);
        mxs_log_set_maxlog_enabled(cnf->maxlog);

        atexit(mxs_log_finish);
        rval = true;
    }

    return rval;
}

/**
 * @node Provides error printing for non-formatted error strings. 
 *
 * @param do_log Specifies whether printing to log is enabled
 *
 * @param do_stderr Specifies whether printing to stderr is enabled
 *
 * @param logstr String to be printed to log
 *
 * @param fprstr String to be printed to stderr
 *
 * @param eno Errno, if it is set, zero, otherwise
 */
static void print_log_n_stderr(bool do_log,         /*< is printing to log enabled */
                               bool do_stderr,      /*< is printing to stderr enabled */
                               const char* logstr,  /*< string to be printed to log */
                               const char* fprstr,  /*< string to be printed to stderr */
                               int eno)             /*< errno, if it is set, zero, otherwise */
{
    if (do_log)
    {
        if (mxb_log_inited() || init_log())
        {
            MXS_ERROR("%s%s%s%s",
                      logstr,
                      eno == 0 ? "" : " (", 
                      eno == 0 ? "" :  mxs_strerror(eno),
                      eno == 0 ? "" : ")"); 
        }
    }
    if (do_stderr)
    {
        fprintf(stderr,
                "* Error: %s%s%s%s\n",
                fprstr,
                eno == 0 ? "" : " (", 
                eno == 0 ? "" :  mxs_strerror(eno),
                eno == 0 ? "" : ")"); 
    }
}

/**
 * Check that a path refers to a readable file. 
 *
 * @param absolute_pathname The path to check. 
 * @return True if the path refers to a readable file.  is readable
 */
static bool is_file_and_readable(const char* absolute_pathname)
{
    bool rv = false;

    struct stat info;

    if (stat(absolute_pathname, &info) == 0)
    {
        if ((info.st_mode & S_IFMT) == S_IFREG)
        {
            // There is a race here as the file can be deleted and a directory
            // created in its stead between the stat() call here and the access()
            // call in file_is_readable(). 
            rv = path_is_readable(absolute_pathname);
        }
        else
        {
            const char FORMAT[] = "'%s' does not refer to a regular file."; 
            char buff[sizeof(FORMAT) + strlen(absolute_pathname)];
            snprintf(buff, sizeof(buff), FORMAT, absolute_pathname);
            print_log_n_stderr(true, true, buff, buff, 0);
        }
    }
    else
    {
        int eno = errno;
        errno = 0;
        const char FORMAT[] = "Could not access '%s'."; 
        char buff[sizeof(FORMAT) + strlen(absolute_pathname)];
        snprintf(buff, sizeof(buff), FORMAT, absolute_pathname);
        print_log_n_stderr(true, true, buff, buff, eno);
    }

    return rv;
}

/**
 * Check if the file or directory is readable
 * @param absolute_pathname Path of the file or directory to check
 * @return True if file is readable
 */
static bool path_is_readable(const char* absolute_pathname)
{
    bool succp = true;

    if (access(absolute_pathname, R_OK) ! = 0)
    {
        int eno = errno;
        errno = 0;
        char buff[PATH_MAX + 100];
        snprintf(buff, sizeof(buff), "Opening file '%s' for reading failed.",  absolute_pathname);
        print_log_n_stderr(true, true, buff, buff, eno);
        succp = false;
    }
    return succp;
}

/**
 * Check if the file or directory is writable
 * @param absolute_pathname Path of the file or directory to check
 * @return True if file is writable
 */
static bool path_is_writable(const char* absolute_pathname)
{
    bool succp = true;

    if (access(absolute_pathname, W_OK) ! = 0)
    {
        int eno = errno;
        errno = 0;
        char buff[PATH_MAX + 100];
        snprintf(buff, sizeof(buff), "Opening file '%s' for writing failed.",  absolute_pathname);
        print_log_n_stderr(true, true, buff, buff, eno);
        succp = false;
    }
    return succp;
}


/**
 * @node Expand path expression and if fname is provided, concatenate
 * it to path to for an absolute pathname.  If fname is provided
 * its readability is tested. 
 *
 * Parameters:
 * @param output_path memory address where expanded path is stored,
 * if output_path ! = NULL
 *
 * @param relative_path path to be expanded
 *
 * @param fname file name to be concatenated to the path, may be NULL
 *
 * @return expanded path and if fname was NULL, absolute pathname of it. 
 * Both return value and *output_path are NULL in case of failure. 
 *
 *
 */
static char* get_expanded_pathname(char** output_path,
                                   const char* relative_path,
                                   const char* fname)
{
    char* cnf_file_buf = NULL;
    char* expanded_path;

    if (relative_path == NULL)
    {
        goto return_cnf_file_buf;
    }

    expanded_path = (char*)MXS_MALLOC(PATH_MAX);

    if (! expanded_path)
    {
        if (output_path)
        {
            *output_path = NULL;
        }
        goto return_cnf_file_buf;
    }

    /*<
     * Expand possible relative pathname to absolute path
     */
    if (realpath(relative_path, expanded_path) == NULL)
    {
        int eno = errno;
        errno = 0;
        char buff[PATH_MAX + 100];

        snprintf(buff, sizeof(buff), "Failed to read the directory '%s'.",  relative_path);
        print_log_n_stderr(true, true, buff, buff, eno);

        MXS_FREE(expanded_path);
        if (output_path)
        {
            *output_path = NULL;
        }
        goto return_cnf_file_buf;
    }

    if (fname ! = NULL)
    {
        /*<
         * Concatenate an absolute filename and test its existence and
         * readability. 
         */
        size_t pathlen = strnlen(expanded_path, PATH_MAX)
            + 1 + strnlen(fname, PATH_MAX) + 1;
        cnf_file_buf = (char*)MXS_MALLOC(pathlen);

        if (cnf_file_buf == NULL)
        {
            MXS_FREE(expanded_path);
            expanded_path = NULL;
            goto return_cnf_file_buf;
        }
        snprintf(cnf_file_buf, pathlen, "%s/%s", expanded_path, fname);

        if (! path_is_readable(cnf_file_buf))
        {
            MXS_FREE(expanded_path);
            MXS_FREE(cnf_file_buf);
            expanded_path = NULL;
            cnf_file_buf = NULL;
            goto return_cnf_file_buf;
        }
    }
    else
    {
        /*<
         * If only directory was provided, check that it is
         * readable. 
         */
        if (! path_is_readable(expanded_path))
        {
            MXS_FREE(expanded_path);
            expanded_path = NULL;
            goto return_cnf_file_buf;
        }
    }

    if (output_path == NULL)
    {
        MXS_FREE(expanded_path);
    }
    else
    {
        *output_path = expanded_path;
    }

return_cnf_file_buf:

    return cnf_file_buf;
}

static void usage(void)
{
    fprintf(stderr,
            "\nUsage : %s [OPTION]...\n\n"
            "  -c, --config-check          validate configuration file and exit\n"
            "  -e, --export-config=FILE    export configuration to a single file\n"
            "  -d, --nodaemon              enable running in terminal process\n"
            "  -f, --config=FILE           relative or absolute pathname of config file\n"
            "  -l, --log=[file|stdout]     log to file or stdout\n"
            "                              (default: file)\n"
            "  -L, --logdir=PATH           path to log file directory\n"
            "  -A, --cachedir=PATH         path to cache directory\n"
            "  -B, --libdir=PATH           path to module directory\n"
            "  -C, --configdir=PATH        path to configuration file directory\n"
            "  -D, --datadir=PATH          path to data directory,\n"
            "                              stores internal MaxScale data\n"
            "  -E, --execdir=PATH          path to the maxscale and other executable files\n"
            "  -F, --persistdir=PATH       path to persisted configuration directory\n"
            "  -M, --module_configdir=PATH path to module configuration directory\n"
            "  -H, --connector_plugindir=PATH\n"
            "                              path to MariaDB Connector-C plugin directory\n"
            "  -N, --language=PATH         path to errmsg.sys file\n"
            "  -P, --piddir=PATH           path to PID file directory\n"
            "  -R, --basedir=PATH          base path for all other paths\n"
            "  -r  --runtimedir=PATH       base path for all other paths expect binaries\n"
            "  -U, --user=USER             user ID and group ID of specified user are used to\n"
            "                              run MaxScale\n"
            "  -s, --syslog=[yes|no]       log messages to syslog (default:yes)\n"
            "  -S, --maxlog=[yes|no]       log messages to MaxScale log (default: yes)\n"
            "  -G, --log_augmentation=0|1  augment messages with the name of the function\n"
            "                              where the message was logged (default: 0)\n"
            "  -p, --passive               start MaxScale as a passive standby\n"
            "  -g, --debug=arg1,arg2,...   enable or disable debug features.  Supported arguments:\n",
            progname);
    for (int i = 0; debug_arguments[i].action ! = NULL; i++)
    {
        fprintf(stderr,
                "   %-25s  %s\n",
                debug_arguments[i].name,
                debug_arguments[i].description);
    }
    fprintf(stderr,
            "  -v, --version               print version info and exit\n"
            "  -V, --version-full          print full version info and exit\n"
            "  -?,  --help                  show this help\n"
            "\n"
            "Defaults paths:\n"
            "  config file       : %s/%s\n"
            "  configdir         : %s\n"
            "  logdir            : %s\n"
            "  cachedir          : %s\n"
            "  libdir            : %s\n"
            "  datadir           : %s\n"
            "  execdir           : %s\n"
            "  language          : %s\n"
            "  piddir            : %s\n"
            "  persistdir        : %s\n"
            "  module configdir  : %s\n"
            "  connector plugins : %s\n"
            "\n"
            "If '--basedir' is provided then all other paths, including the default\n"
            "configuration file path, are defined relative to that.  As an example,\n"
            "if '--basedir /path/maxscale' is specified, then, for instance, the log\n"
            "dir will be '/path/maxscale/var/log/maxscale', the config dir will be\n"
            "'/path/maxscale/etc' and the default config file will be\n"
            "'/path/maxscale/etc/maxscale.cnf'.\n\n"
            "MaxScale documentation: https://mariadb.com/kb/en/maxscale/ \n",
            get_configdir(),
            default_cnf_fname,
            get_configdir(),
            get_logdir(),
            get_cachedir(),
            get_libdir(),
            get_datadir(),
            get_execdir(),
            get_langdir(),
            get_piddir(),
            get_config_persistdir(),
            get_module_configdir(),
            get_connector_plugindir());
}

/**
 * Deletes a particular signal from a provided signal set. 
 *
 * @param sigset  The signal set to be manipulated. 
 * @param signum  The signal to be deleted. 
 * @param signame The name of the signal. 
 *
 * @return True, if the signal could be deleted from the set, false otherwise. 
 */
static bool delete_signal(sigset_t* sigset, int signum, const char* signame)
{
    int rc = sigdelset(sigset, signum);

    if (rc ! = 0)
    {
        int e = errno;
        errno = 0;

        static const char FORMAT[] = "Failed to delete signal %s from the signal set of MaxScale.  Exiting."; 
        char message[sizeof(FORMAT) + 16];      // Enough for any "SIG..." string. 

        sprintf(message, FORMAT, signame);

        print_log_n_stderr(true, true, message, message, e);
    }

    return rc == 0;
}

/**
 * Disables all signals. 
 *
 * @return True, if all signals could be disabled, false otherwise. 
 */
bool disable_signals(void)
{
    sigset_t sigset;

    if (sigfillset(&sigset) ! = 0)
    {
        int e = errno;
        errno = 0;
        static const char message[] = "Failed to initialize set the signal set for MaxScale.  Exiting."; 
        print_log_n_stderr(true, true, message, message, e);
        return false;
    }

    if (! delete_signal(&sigset, SIGHUP, "SIGHUP"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGUSR1, "SIGUSR1"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGTERM, "SIGTERM"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGSEGV, "SIGSEGV"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGABRT, "SIGABRT"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGILL, "SIGILL"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGFPE, "SIGFPE"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGCHLD, "SIGCHLD"))
    {
        return false;
    }

#ifdef SIGBUS
    if (! delete_signal(&sigset, SIGBUS, "SIGBUS"))
    {
        return false;
    }
#endif

    if (sigprocmask(SIG_SETMASK, &sigset, NULL) ! = 0)
    {
        int e = errno;
        errno = 0;
        static const char message[] = "Failed to set the signal set for MaxScale.  Exiting";
        print_log_n_stderr(true, true, message, message, e);
        return false;
    }

    return true;
}

bool disable_normal_signals(void)
{
    sigset_t sigset;

    if (sigfillset(&sigset) ! = 0)
    {
        int e = errno;
        errno = 0;
        static const char message[] = "Failed to initialize set the signal set for MaxScale.  Exiting."; 
        print_log_n_stderr(true, true, message, message, e);
        return false;
    }

    if (! delete_signal(&sigset, SIGHUP, "SIGHUP"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGUSR1, "SIGUSR1"))
    {
        return false;
    }

    if (! delete_signal(&sigset, SIGTERM, "SIGTERM"))
    {
        return false;
    }

    if (sigprocmask(SIG_SETMASK, &sigset, NULL) ! = 0)
    {
        int e = errno;
        errno = 0;
        static const char message[] = "Failed to set the signal set for MaxScale.  Exiting";
        print_log_n_stderr(true, true, message, message, e);
        return false;
    }

    return true;
}

/**
 * Configures the handling of a particular signal. 
 *
 * @param signum  The signal number. 
 * @param signame The name of the signal. 
 * @param handler The handler function for the signal. 
 *
 * @return True, if the signal could be configured, false otherwise. 
 */
static bool configure_signal(int signum, const char* signame, void (* handler)(int))
{
    int rc = signal_set(signum, handler);

    if (rc ! = 0)
    {
        static const char FORMAT[] = "Failed to set signal handler for %s. Exiting."; 
        char message[sizeof(FORMAT) + 16];      // Enough for any "SIG..." string. 

        sprintf(message, FORMAT, signame);

        print_log_n_stderr(true, true, message, message, 0);
    }

    return rc == 0;
}

/**
 * Configure fatal signal handlers
 *
 * @return True if signal handlers were installed correctly
 */
bool configure_critical_signals(void)
{

    if (! configure_signal(SIGSEGV, "SIGSEGV", sigfatal_handler))
    {
        return false;
    }

    if (! configure_signal(SIGABRT, "SIGABRT", sigfatal_handler))
    {
        return false;
    }

    if (! configure_signal(SIGILL, "SIGILL", sigfatal_handler))
    {
        return false;
    }

    if (! configure_signal(SIGFPE, "SIGFPE", sigfatal_handler))
    {
        return false;
    }

#ifdef SIGBUS
    if (! configure_signal(SIGBUS, "SIGBUS", sigfatal_handler))
    {
        return false;
    }
#endif

    return true;
}

/**
 * Configures signal handling of MaxScale. 
 *
 * @return True, if all signals could be configured, false otherwise. 
 */
bool configure_normal_signals(void)
{
    if (! configure_signal(SIGHUP, "SIGHUP", sighup_handler))
    {
        return false;
    }

    if (! configure_signal(SIGUSR1, "SIGUSR1", sigusr1_handler))
    {
        return false;
    }

    if (! configure_signal(SIGTERM, "SIGTERM", sigterm_handler))
    {
        return false;
    }

    if (! configure_signal(SIGINT, "SIGINT", sigint_handler))
    {
        return false;
    }

    return true;
}

bool set_runtime_dirs(const char* basedir)
{
    bool rv = true;
    char* path;

    if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LOG_SUBPATH, true, false)))
    {
        set_logdir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_CACHE_SUBPATH, true, true)))
    {
        set_cachedir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_CONFIG_SUBPATH, true, false)))
    {
        set_configdir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_MODULE_CONFIG_SUBPATH, true, false)))
    {
        set_module_configdir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_DATA_SUBPATH, true, false)))
    {
        set_datadir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LANG_SUBPATH, true, false)))
    {
        set_langdir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_PID_SUBPATH, true, true)))
    {
        set_piddir(path);
    }

    if (rv && (rv = handle_path_arg(&path,
                                    basedir,
                                    "var/" MXS_DEFAULT_DATA_SUBPATH "/"
                                    MXS_DEFAULT_CONFIG_PERSIST_SUBPATH,
                                    true,
                                    true)))
    {
        set_config_persistdir(path);
    }

    if (rv && (rv = handle_path_arg(&path,
                                    basedir,
                                    "var/" MXS_DEFAULT_CONNECTOR_PLUGIN_SUBPATH,
                                    true,
                                    true)))
    {
        set_connector_plugindir(path);
    }

    return rv;
}

/**
 * Set the directories of MaxScale relative to a basedir
 *
 * @param basedir The base directory relative to which the other are set. 
 *
 * @return True if the directories could be set, false otherwise. 
 */
bool set_dirs(const char* basedir)
{
    bool rv = true;
    char* path;

    rv = set_runtime_dirs(basedir);

    if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_LIB_SUBPATH, true, false)))
    {
        set_libdir(path);
    }

    if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_EXEC_SUBPATH, true, false)))
    {
        set_execdir(path);
    }

    return rv;
}

/**
 * @mainpage
 * The main entry point into MaxScale
 *
 * Logging and error printing
 * ---
 * What is printed to the terminal is something that the user can understand,
 * and/or something what the user can do for.  For example, fix configuration. 
 * More detailed messages are printed to error log, and optionally to trace
 * and debug log. 
 *
 * As soon as process switches to daemon process, stderr printing is stopped -
 * except when it comes to command-line argument processing. 
 * This is not an obvious solution because stderr is often directed to somewhere,
 * but currently this is the case. 
 *
 * The configuration file is by default /etc/maxscale.cnf
 * The name of configuration file and its location can also be specified with a
 * command-line argument. 
 *
 * @param argc The argument count
 * @param argv The array of arguments themselves
 * @return 0 if process exited normally, otherwise a non-zero value is returned
 */
int main(int argc, char** argv)
{
    int rc = MAXSCALE_SHUTDOWN;
    int n_services;
    int eno = 0;    /*< local variable for errno */
    int opt;
    int daemon_pipe[2] = {-1, -1};
    bool parent_process;
    int child_status;
    char* cnf_file_path = NULL;         /*< conf file, to be freed */
    char* cnf_file_arg = NULL;          /*< conf filename from cmd-line arg */
    char* tmp_path;
    int option_index;
    MXS_CONFIG* cnf = config_get_global_options();
    mxb_assert(cnf);
    int* syslog_enabled = &cnf->syslog;     /** Log to syslog */
    int* maxlog_enabled = &cnf->maxlog;     /** Log with MaxScale */
    sigset_t sigpipe_mask;
    sigset_t saved_mask;
    int numlocks = 0;
    bool pid_file_created = false;
    mxb::Worker* worker;
    const char* specified_user = NULL;
    char export_cnf[PATH_MAX + 1] = "";
    maxscale::MainWorker* main_worker = nullptr;

    /**
     * The following lambda function is executed as the first event on the main worker.  This is what starts
     * up the listeners for all services. 
     *
     * Due to the fact that the main thread runs a worker thread we have to queue the starting
     * of the listeners to happen after all workers have started.  This allows worker messages to be used
     * when listeners are being started. 
     *
     * Once the main worker is dedicated to doing work other than handling traffic the code could be executed
     * immediately after the worker thread have been started.  This would make the startup logic clearer as
     * the order of the events would be the way they appear to be. 
     */
    auto do_startup = [&]() {
            if (! service_launch_all())
            {
                const char* logerr = "Failed to start all MaxScale services.  Exiting."; 
                print_log_n_stderr(true, true, logerr, logerr, 0);
                rc = MAXSCALE_NOSERVICES;
                maxscale_shutdown();
            }
            else
            {
                if (daemon_mode)
                {
                    // Successful start, notify the parent process that it can exit. 
                    write_child_exit_code(daemon_pipe[1], rc);
                }
                /** Start all monitors */
                MonitorManager::start_all_monitors();
            }
        };

    config_set_global_defaults();
    mxb_assert(cnf);

    maxscale_reset_starttime();

    sigemptyset(&sigpipe_mask);
    sigaddset(&sigpipe_mask, SIGPIPE);
    progname = *argv;
    snprintf(datadir, PATH_MAX, "%s", default_datadir);
    datadir[PATH_MAX] = '\0';

    // Option string for getopt
    const char accepted_opts[] = "dnce:f:g:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:p";

#ifdef HAVE_GLIBC
    while ((opt = getopt_long(argc,
                              argv,
                              accepted_opts,
                              long_options,
                              &option_index)) ! = -1)
#else
    while ((opt = getopt(argc, argv, accepted_opts)) ! = -1)
#endif
    {
        bool succp = true;

        switch (opt)
        {
        case 'n':
            /*< Daemon mode, MaxScale forks and parent exits. */ 
            daemon_mode = true;
            break;

        case 'd':
            /*< Non-daemon mode, MaxScale does not fork. */ 
            daemon_mode = false;
            break;

        case 'f':
            /*<
             * Simply copy the conf file argument.  Expand or validate
             * it when MaxScale home directory is resolved. 
             */
            if (optarg[0] ! = '-')
            {
                cnf_file_arg = strndup(optarg, PATH_MAX);
            }
            if (cnf_file_arg == NULL)
            {
                const char* logerr =
                    "Configuration file argument "
                    "identifier \'-f\' was specified but "
                    "the argument didn't specify\n  a valid "
                    "configuration file or the argument "
                    "was missing."; 
                print_log_n_stderr(true, true, logerr, logerr, 0);
                usage();
                succp = false;
            }
            break;

        case 'v':
            rc = EXIT_SUCCESS;
            printf("MaxScale %s\n", MAXSCALE_VERSION);
            goto return_main;

        case 'V':
            rc = EXIT_SUCCESS;
            printf("MaxScale %s - %s\n", MAXSCALE_VERSION, maxscale_commit);

            // MAXSCALE_SOURCE is two values separated by a space, see CMakeLists.txt
            if (strcmp(MAXSCALE_SOURCE, " ") ! = 0)
            {
                printf("Source:        %s\n", MAXSCALE_SOURCE);
            }
            if (strcmp(MAXSCALE_CMAKE_FLAGS, "") ! = 0)
            {
                printf("CMake flags:   %s\n", MAXSCALE_CMAKE_FLAGS);
            }
            if (strcmp(MAXSCALE_JENKINS_BUILD_TAG, "") ! = 0)
            {
                printf("Jenkins build: %s\n", MAXSCALE_JENKINS_BUILD_TAG);
            }
            goto return_main;

        case 'l':
            if (strncasecmp(optarg, "file", PATH_MAX) == 0)
            {
                cnf->log_target = MXB_LOG_TARGET_FS;
            }
            else if (strncasecmp(optarg, "stdout", PATH_MAX) == 0)
            {
                cnf->log_target = MXB_LOG_TARGET_STDOUT;
            }
            else
            {
                const char* logerr =
                    "Configuration file argument "
                    "identifier \'-l\' was specified but "
                    "the argument didn't specify\n  a valid "
                    "configuration file or the argument "
                    "was missing."; 
                print_log_n_stderr(true, true, logerr, logerr, 0);
                usage();
                succp = false;
            }
            break;

        case 'L':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_logdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'N':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_langdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'P':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, true))
            {
                set_piddir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'D':
            snprintf(datadir, PATH_MAX, "%s", optarg);
            datadir[PATH_MAX] = '\0';
            set_datadir(MXS_STRDUP_A(optarg));
            datadir_defined = true;
            break;

        case 'C':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_configdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'B':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_libdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'A':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, true))
            {
                set_cachedir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'E':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_execdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'H':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                set_connector_plugindir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'F':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, true))
            {
                set_config_persistdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'M':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, true))
            {
                set_module_configdir(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'R':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                succp = set_dirs(tmp_path);
                free(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'r':
            if (handle_path_arg(&tmp_path, optarg, NULL, true, false))
            {
                succp = set_runtime_dirs(tmp_path);
                free(tmp_path);
            }
            else
            {
                succp = false;
            }
            break;

        case 'S':
            {
                char* tok = strstr(optarg, "=");
                if (tok)
                {
                    tok++;
                    if (tok)
                    {
                        *maxlog_enabled = config_truth_value(tok);
                        maxlog_configured = true;
                    }
                }
                else
                {
                    *maxlog_enabled = config_truth_value(optarg);
                    maxlog_configured = true;
                }
            }
            break;

        case 's':
            {
                char* tok = strstr(optarg, "=");
                if (tok)
                {
                    tok++;
                    if (tok)
                    {
                        *syslog_enabled = config_truth_value(tok);
                        syslog_configured = true;
                    }
                }
                else
                {
                    *syslog_enabled = config_truth_value(optarg);
                    syslog_configured = true;
                }
            }
            break;

        case 'U':
            specified_user = optarg;
            if (set_user(specified_user) ! = 0)
            {
                succp = false;
            }
            break;

        case 'G':
            set_log_augmentation(optarg);
            break;

        case '?': 
            usage();
            rc = EXIT_SUCCESS;
            goto return_main;

        case 'c':
            cnf->config_check = true;
            break;

        case 'e':
            cnf->config_check = true;
            strcpy(export_cnf, optarg);
            break;

        case 'p':
            cnf->passive = true;
            break;

        case 'g':
            if (! handle_debug_args(optarg))
            {
                succp = false;
            }
            break;

        default:
            usage();
            succp = false;
            break;
        }

        if (! succp)
        {
            rc = MAXSCALE_BADARG;
            goto return_main;
        }
    }

    if (! user_is_acceptable(specified_user))
    {
        // Error was logged in user_is_acceptable(). 
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    if (cnf->config_check)
    {
        daemon_mode = false;
        cnf->log_target = MXB_LOG_TARGET_STDOUT;
    }

#ifdef HAVE_SYSTEMD
    // Systemd watchdog.  Must be called in the initial thread */
    uint64_t systemd_interval;      // in microseconds
    if (sd_watchdog_enabled(false, &systemd_interval) > 0)
    {
        RoutingWorker::set_watchdog_interval(systemd_interval);
    }
#endif

    if (! daemon_mode)
    {
        fprintf(stderr,
                "Info : MaxScale will be run in the terminal process.\n");
#if defined (SS_DEBUG)
        fprintf(stderr,
                "\tSee "
                "the log from the following log files : \n\n");
#endif
    }
    else
    {
        if (pipe(daemon_pipe) == -1)
        {
            fprintf(stderr,
                    "Error: Failed to create pipe for inter-process communication: %d %s",
                    errno,
                    strerror(errno));
            rc = MAXSCALE_INTERNALERROR;
            goto return_main;
        }

        /*<
         * Maxscale must be daemonized before opening files, initializing
         * embedded MariaDB and in general, as early as possible. 
         */

        if (! disable_signals())
        {
            rc = MAXSCALE_INTERNALERROR;
            goto return_main;
        }

        /** Daemonize the process and wait for the child process to notify
         * the parent process of its exit status. */ 
        parent_process = daemonize();

        if (parent_process)
        {
            close(daemon_pipe[1]);
            int nread = read(daemon_pipe[0], (void*)&child_status, sizeof(int));
            close(daemon_pipe[0]);

            if (nread == -1)
            {
                const char* logerr = "Failed to read data from child process pipe."; 
                print_log_n_stderr(true, true, logerr, logerr, errno);
                exit(MAXSCALE_INTERNALERROR);
            }
            else if (nread == 0)
            {
                /** Child process has exited or closed write pipe */
                const char* logerr = "No data read from child process pipe."; 
                print_log_n_stderr(true, true, logerr, logerr, 0);
                exit(MAXSCALE_INTERNALERROR);
            }

            _exit(child_status);
        }

        /** This is the child process and we can close the read end of
         * the pipe. */ 
        close(daemon_pipe[0]);
    }

    if (! configure_critical_signals())
    {
        static const char* logerr = "Failed to configure fatal signal handlers.  Exiting."; 

        print_log_n_stderr(true, true, logerr, logerr, 0);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }
    eno = pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask);

    if (eno ! = 0)
    {
        const char* logerr = "Failed to initialise signal mask for MaxScale.  Exiting."; 
        print_log_n_stderr(true, true, logerr, logerr, eno);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    if (! utils_init())
    {
        const char* logerr = "Failed to initialise utility library."; 
        print_log_n_stderr(true, true, logerr, logerr, eno);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    /** OpenSSL initialization */
    if (! HAVE_OPENSSL_THREADS)
    {
        const char* logerr = "OpenSSL library does not support multi-threading";
        print_log_n_stderr(true, true, logerr, logerr, eno);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }
    SSL_library_init();
    SSL_load_error_strings();
    OPENSSL_add_all_algorithms_noconf();

#ifndef OPENSSL_1_1
    numlocks = CRYPTO_num_locks();
    if ((ssl_locks = (pthread_mutex_t*)MXS_MALLOC(sizeof(pthread_mutex_t) * (numlocks + 1))) == NULL)
    {
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    for (int i = 0; i < numlocks + 1; i++)
    {
        pthread_mutex_init(&ssl_locks[i], NULL);
    }
    CRYPTO_set_locking_callback(ssl_locking_function);
    CRYPTO_set_dynlock_create_callback(ssl_create_dynlock);
    CRYPTO_set_dynlock_destroy_callback(ssl_free_dynlock);
    CRYPTO_set_dynlock_lock_callback(ssl_lock_dynlock);
#ifdef OPENSSL_1_0
    CRYPTO_THREADID_set_callback(maxscale_ssl_id);
#else
    CRYPTO_set_id_callback(pthread_self);
#endif
#endif

    /**
     * Resolve the full pathname for configuration file and check for
     * read accessibility. 
     */
    char pathbuf[PATH_MAX + 1];
    snprintf(pathbuf, PATH_MAX, "%s", get_configdir());
    pathbuf[PATH_MAX] = '\0';
    if (pathbuf[strlen(pathbuf) - 1] ! = '/')
    {
        strcat(pathbuf, "/");
    }

    if (! resolve_maxscale_conf_fname(&cnf_file_path, pathbuf, cnf_file_arg))
    {
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    if (! sniff_configuration(cnf_file_path))
    {
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    if (mxb_log_inited())
    {
        // If the log was inited due to some error logging *and* we did not exit,
        // we need to close it so that it can be opened again, this time with
        // the final settings. 
        mxs_log_finish();
    }

    if (cnf->log_target ! = MXB_LOG_TARGET_STDOUT && daemon_mode)
    {
        mxs_log_redirect_stdout(true);
    }

    if (! init_log())
    {
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    if (! config_load_global(cnf_file_path))
    {
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    if (daemon_mode)
    {
        if (! change_cwd())
        {
            rc = MAXSCALE_INTERNALERROR;
            goto return_main;
        }
    }

    if (! init_sqlite3())
    {
        MXS_ERROR("Could not initialize sqlite3, exiting."); 
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    MXS_NOTICE("MariaDB MaxScale %s started (Commit: %s)", MAXSCALE_VERSION, MAXSCALE_COMMIT);
    MXS_NOTICE("MaxScale is running in process %i", getpid());

    cleanup_old_process_datadirs();
    if (! cnf->config_check)
    {
        /*
         * Set the data directory.  We use a unique directory name to avoid conflicts
         * if multiple instances of MaxScale are being run on the same machine. 
         */
        if (create_datadir(get_datadir(), datadir))
        {
            set_process_datadir(datadir);
            atexit(cleanup_process_datadir);
        }
        else
        {
            char errbuf[MXS_STRERROR_BUFLEN];
            MXS_ERROR("Cannot create data directory '%s': %d %s\n",
                      datadir,
                      errno,
                      strerror_r(errno, errbuf, sizeof(errbuf)));
            goto return_main;
        }
    }

    if (! daemon_mode)
    {
        fprintf(stderr,
                "Configuration file : %s\n"
                "Log directory      : %s\n"
                "Data directory     : %s\n"
                "Module directory   : %s\n"
                "Service cache      : %s\n\n",
                cnf_file_path,
                get_logdir(),
                get_datadir(),
                get_libdir(),
                get_cachedir());
    }

    if (!(* syslog_enabled) && !(* maxlog_enabled))
    {
        fprintf(stderr, "warning: Both MaxScale and Syslog logging disabled.\n");
    }

    MXS_NOTICE("Configuration file: %s", cnf_file_path);
    MXS_NOTICE("Log directory: %s", get_logdir());
    MXS_NOTICE("Data directory: %s", get_datadir());
    MXS_NOTICE("Module directory: %s", get_libdir());
    MXS_NOTICE("Service cache: %s", get_cachedir());

    if (! maxbase::init())
    {
        MXS_ERROR("Failed to initialize MaxScale base library."); 
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    // Initialize the housekeeper
    main_worker = new maxscale::MainWorker;

    if (! qc_setup(&cnf->qc_cache_properties, cnf->qc_sql_mode, cnf->qc_name, cnf->qc_args))
    {
        const char* logerr = "Failed to initialise query classifier library."; 
        print_log_n_stderr(true, true, logerr, logerr, eno);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    if (! cnf->config_check)
    {
        /** Check if a MaxScale process is already running */
        if (pid_file_exists())
        {
            /** There is a process with the PID of the maxscale.pid file running. 
             * Assuming that this is an already running MaxScale process, we
             * should exit with an error code.   */
            rc = MAXSCALE_ALREADYRUNNING;
            goto return_main;
        }

        /* Write process pid into MaxScale pidfile */
        if (write_pid_file() ! = 0)
        {
            rc = MAXSCALE_ALREADYRUNNING;
            goto return_main;
        }

        pid_file_created = true;

        if (! lock_directories())
        {
            rc = MAXSCALE_ALREADYRUNNING;
            goto return_main;
        }

        atexit(unlock_directories);
    }

    if (! redirect_output_to.empty())
    {
        if (freopen(redirect_output_to.c_str(), "a", stdout))
        {
            if (! freopen(redirect_output_to.c_str(), "a", stderr))
            {
                const char* logstr = "Failed to redirect stderr to file."; 
                // No point logging to stderr as its state is now not known. 
                print_log_n_stderr(true, false, logstr, logstr, errno);
                rc = MAXSCALE_INTERNALERROR;
                goto return_main;
            }
        }
        else
        {
            const char* logstr = "Failed to redirect stdout to file."; 
            print_log_n_stderr(true, true, logstr, logstr, errno);
            rc = MAXSCALE_INTERNALERROR;
            goto return_main;
        }
    }

    /* Init MaxScale poll system */
    poll_init();

    dcb_global_init();

    /** Load the admin users */
    admin_users_init();

    /* Initialize the internal query classifier.  The plugin will be initialized
     * via the module initialization below. 
     */
    if (! qc_process_init(QC_INIT_SELF))
    {
        MXS_ERROR("Failed to initialize the internal query classifier."); 
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    // Before we start the workers we need to check if a shutdown signal has been received
    if (maxscale_is_shutting_down())
    {
        rc = MAXSCALE_SHUTDOWN;
        goto return_main;
    }

    if (! RoutingWorker::init())
    {
        MXS_ERROR("Failed to initialize routing workers."); 
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    /**
     * If a shutdown signal was received while we were initializing the workers, we need to exit. 
     * After this point, the shutdown will be driven by the workers. 
     */
    if (maxscale_is_shutting_down())
    {
        rc = MAXSCALE_SHUTDOWN;
        goto return_main;
    }

    if (! config_load(cnf_file_path))
    {
        const char* fprerr =
            "Failed to open, read or process the MaxScale configuration "
            "file.  Exiting.  See the error log for details."; 
        print_log_n_stderr(false, true, fprerr, fprerr, 0);
        MXS_ERROR("Failed to open, read or process the MaxScale configuration file %s. "
                  "Exiting.", 
                  cnf_file_path);
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    /* Init MaxScale modules */
    if (! modules_process_init())
    {
        MXS_ERROR("Failed to initialize all modules at startup.  Exiting."); 
        rc = MAXSCALE_BADCONFIG;
        goto return_main;
    }

    if (cnf->config_check)
    {
        MXS_NOTICE("Configuration was successfully verified."); 

        if (*export_cnf && export_config_file(export_cnf))
        {
            MXS_NOTICE("Configuration exported to '%s'", export_cnf);
        }

        rc = MAXSCALE_SHUTDOWN;
        goto return_main;
    }

    /*<
     * Start the routing workers running in their own thread. 
     */
    if (! RoutingWorker::start_workers())
    {
        const char* logerr = "Failed to start routing workers."; 
        print_log_n_stderr(true, true, logerr, logerr, 0);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    if (cnf->admin_enabled)
    {
        bool success = mxs_admin_init();

        if (! success && strcmp(cnf->admin_host, "::") == 0)
        {
            MXS_WARNING("Failed to bind on address '::', attempting to "
                        "bind on IPv4 address '0.0.0.0'."); 
            strcpy(cnf->admin_host, "0.0.0.0");
            success = mxs_admin_init();
        }

        if (success)
        {
            MXS_NOTICE("Started REST API on [%s]:%u", cnf->admin_host, cnf->admin_port);
        }
        else
        {
            const char* logerr = "Failed to initialize admin interface";
            print_log_n_stderr(true, true, logerr, logerr, 0);
            rc = MAXSCALE_INTERNALERROR;
            goto return_main;
        }
    }

    MXS_NOTICE("MaxScale started with %d worker threads, each with a stack size of %lu bytes.", 
               config_threadcount(),
               config_thread_stack_size());

    if (! configure_normal_signals())
    {
        static const char* logerr = "Failed to configure all signal handlers.  Exiting."; 

        print_log_n_stderr(true, true, logerr, logerr, 0);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    worker = RoutingWorker::get(RoutingWorker::MAIN);
    mxb_assert(worker);

    // Configuration read and items created.  Changes should now come through the main routing worker. 
    set_admin_worker(worker);
    if (! worker->execute(do_startup, RoutingWorker::EXECUTE_QUEUED))
    {
        const char* logerr = "Failed to queue startup task."; 
        print_log_n_stderr(true, true, logerr, logerr, 0);
        rc = MAXSCALE_INTERNALERROR;
        goto return_main;
    }

    main_worker->run();

    disable_normal_signals();

    /*<
     * Wait for worker threads to exit. 
     */
    RoutingWorker::join_workers();
    MXS_NOTICE("All workers have shut down."); 

    set_admin_worker(nullptr);      // Main worker has quit, re-assign to non-worker. 

    /*< Destroy all monitors */
    MonitorManager::destroy_all_monitors();

    maxscale_start_teardown();

    /*<
     * Destroy the router and filter instances of all services. 
     */
    service_destroy_instances();
    filter_destroy_instances();

    RoutingWorker::finish();
    maxbase::finish();

    /*< Call finish on all modules. */ 
    modules_process_finish();

    /* Finalize the internal query classifier.  The plugin was finalized
     * via the module finalizarion above. 
     */
    qc_process_end(QC_INIT_SELF);

    log_exit_status();
    MXS_NOTICE("MaxScale is shutting down."); 

    utils_end();
    MXS_NOTICE("MaxScale shutdown completed."); 

    if (unload_modules_at_exit)
    {
        unload_all_modules();
    }

    ERR_free_strings();
    EVP_cleanup();

return_main:
    if (pid_file_created)
    {
        unlock_pidfile();
        unlink_pidfile();
    }

    if (daemon_mode && rc ! = MAXSCALE_SHUTDOWN)
    {
        /** Notify the parent process that an error has occurred */
        write_child_exit_code(daemon_pipe[1], rc);
    }

    MXS_FREE(cnf_file_arg);

    if (cnf_file_path)
    {
        MXS_FREE(cnf_file_path);
    }

    config_finish();

    delete main_worker;
    main_worker = nullptr;

    return rc;
}   /*< End of main */

static void unlock_pidfile()
{
    if (pidfd ! = PIDFD_CLOSED)
    {
        if (flock(pidfd, LOCK_UN | LOCK_NB) ! = 0)
        {
            char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
            const char* logerr = "Failed to unlock PID file '%s'."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pidfile);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
        }
        close(pidfd);
    }
}

/**
 * Unlink pid file, called at program exit
 */
static void unlink_pidfile(void)
{
    if (strlen(pidfile))
    {
        if (unlink(pidfile))
        {
            fprintf(stderr,
                    "MaxScale failed to remove pidfile %s: error %d, %s\n",
                    pidfile,
                    errno,
                    mxs_strerror(errno));
        }
    }
}

bool pid_is_maxscale(int pid)
{
    bool rval = false;
    std::stringstream ss;
    ss << "/proc/" << pid << "/comm";
    std::ifstream file(ss.str());
    std::string line;

    if (file && std::getline(file, line))
    {
        if (line == "maxscale" && pid ! = getpid())
        {
            rval = true;
        }
    }

    return rval;
}

/**
 * Check if the maxscale.pid file exists and has a valid PID in it.  If one has already been
 * written and a MaxScale process is running, this instance of MaxScale should shut down. 
 * @return True if the conditions for starting MaxScale are not met and false if
 * no PID file was found or there is no process running with the PID of the maxscale.pid
 * file.  If false is returned, this process should continue normally. 
 */
bool pid_file_exists()
{
    char pathbuf[PATH_MAX + 1];
    char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
    char pidbuf[STRING_BUFFER_SIZE];
    pid_t pid;
    bool lock_failed = false;

    snprintf(pathbuf, PATH_MAX, "%s/maxscale.pid", get_piddir());
    pathbuf[PATH_MAX] = '\0';

    if (access(pathbuf, F_OK) ! = 0)
    {
        return false;
    }

    if (access(pathbuf, R_OK) == 0)
    {
        int fd, b;

        if ((fd = open(pathbuf, O_RDWR)) == -1)
        {
            const char* logerr = "Failed to open PID file '%s'."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            return true;
        }
        if (flock(fd, LOCK_EX | LOCK_NB))
        {
            if (errno ! = EWOULDBLOCK)
            {
                const char* logerr = "Failed to lock PID file '%s'."; 
                snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
                print_log_n_stderr(true, true, logbuf, logbuf, errno);
                close(fd);
                return true;
            }
            lock_failed = true;
        }

        pidfd = fd;
        b = read(fd, pidbuf, sizeof(pidbuf));

        if (b == -1)
        {
            const char* logerr = "Failed to read from PID file '%s'."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            unlock_pidfile();
            return true;
        }
        else if (b == 0)
        {
            /** Empty file */
            const char* logerr =
                "PID file read from '%s'.  File was empty.\n"
                "If the file is the correct PID file and no other MaxScale processes "
                "are running, please remove it manually and start MaxScale again."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            unlock_pidfile();
            return true;
        }

        pidbuf[(size_t)b < sizeof(pidbuf) ? ( size_t)b : sizeof(pidbuf) - 1] = '\0';
        pid = strtol(pidbuf, NULL, 0);

        if (pid < 1)
        {
            /** Bad PID */
            const char* logerr =
                "PID file read from '%s'.  File contents not valid.\n"
                "If the file is the correct PID file and no other MaxScale processes "
                "are running, please remove it manually and start MaxScale again."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            unlock_pidfile();
            return true;
        }

        if (pid_is_maxscale(pid))
        {
            const char* logerr =
                "MaxScale is already running.  Process id: %d. "
                "Use another location for the PID file to run multiple "
                "instances of MaxScale on the same machine."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pid);
            print_log_n_stderr(true, true, logbuf, logbuf, 0);
            unlock_pidfile();
        }
        else
        {
            /** no such process, old PID file */
            if (lock_failed)
            {
                const char* logerr =
                    "Locking the PID file '%s' failed. " 
                    "Read PID from file and no process found with PID %d. "
                    "Confirm that no other process holds the lock on the PID file."; 
                snprintf(logbuf, sizeof(logbuf), logerr, pathbuf, pid);
                print_log_n_stderr(true, true, logbuf, logbuf, 0);
                close(fd);
            }
            return lock_failed;
        }
    }
    else
    {
        const char* logerr =
            "Cannot open PID file '%s', no read permissions. " 
            "Please confirm that the user running MaxScale has read permissions on the file."; 
        snprintf(logbuf, sizeof(logbuf), logerr, pathbuf);
        print_log_n_stderr(true, true, logbuf, logbuf, errno);
    }
    return true;
}

/**
 * Write process pid into pidfile anc close it
 * Parameters:
 * @param home_dir The MaxScale home dir
 * @return 0 on success, 1 on failure
 *
 */

static int write_pid_file()
{
    if (! mxs_mkdir_all(get_piddir(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH))
    {
        MXS_ERROR("Failed to create PID directory."); 
        return 1;
    }

    char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
    char pidstr[STRING_BUFFER_SIZE];

    snprintf(pidfile, PATH_MAX, "%s/maxscale.pid", get_piddir());

    if (pidfd == PIDFD_CLOSED)
    {
        int fd = -1;

        fd = open(pidfile, O_WRONLY | O_CREAT, 0777);
        if (fd == -1)
        {
            const char* logerr = "Failed to open PID file '%s'."; 
            snprintf(logbuf, sizeof(logbuf), logerr, pidfile);
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            return -1;
        }

        if (flock(fd, LOCK_EX | LOCK_NB))
        {
            if (errno == EWOULDBLOCK)
            {
                snprintf(logbuf,
                         sizeof(logbuf),
                         "Failed to lock PID file '%s', another process is holding a lock on it. " 
                         "Please confirm that no other MaxScale process is using the same "
                         "PID file location.", 
                         pidfile);
            }
            else
            {
                snprintf(logbuf, sizeof(logbuf), "Failed to lock PID file '%s'.",  pidfile);
            }
            print_log_n_stderr(true, true, logbuf, logbuf, errno);
            close(fd);
            return -1;
        }
        pidfd = fd;
    }

    /* truncate pidfile content */
    if (ftruncate(pidfd, 0))
    {
        const char* logerr = "MaxScale failed to truncate PID file '%s'."; 
        snprintf(logbuf, sizeof(logbuf), logerr, pidfile);
        print_log_n_stderr(true, true, logbuf, logbuf, errno);
        unlock_pidfile();
        return -1;
    }

    snprintf(pidstr, sizeof(pidstr) - 1, "%d", getpid());

    if (pwrite(pidfd, pidstr, strlen(pidstr), 0) ! = (ssize_t)strlen(pidstr))
    {
        const char* logerr = "MaxScale failed to write into PID file '%s'."; 
        snprintf(logbuf, sizeof(logbuf), logerr, pidfile);
        print_log_n_stderr(true, true, logbuf, logbuf, errno);
        unlock_pidfile();
        return -1;
    }

    /* success */
    return 0;
}

bool handle_path_arg(char** dest, const char* path, const char* arg, bool rd, bool wr)
{
    char pathbuffer[PATH_MAX + 2];
    char* errstr;
    bool rval = false;

    if (path == NULL && arg == NULL)
    {
        return rval;
    }

    if (path)
    {
        snprintf(pathbuffer, PATH_MAX, "%s", path);
        if (pathbuffer[strlen(path) - 1] ! = '/')
        {
            strcat(pathbuffer, "/");
        }
        if (arg && strlen(pathbuffer) + strlen(arg) + 1 < PATH_MAX)
        {
            strcat(pathbuffer, arg);
        }

        if ((errstr = check_dir_access(pathbuffer, rd, wr)) == NULL)
        {
            *dest = MXS_STRDUP_A(pathbuffer);
            rval = true;
        }
        else
        {
            print_log_n_stderr(true, true, errstr, errstr, 0);
            MXS_FREE(errstr);
            errstr = NULL;
        }
    }

    return rval;
}

void set_log_augmentation(const char* value)
{
    // Command line arguments are handled first, thus command line argument
    // has priority. 

    static bool augmentation_set = false;

    if (! augmentation_set)
    {
        mxs_log_set_augmentation(atoi(value));

        augmentation_set = true;
    }
}

/**
 * Pre-parse the configuration file for various directory paths. 
 * @param data    Pointer to variable where custom dynamically allocated
 *                error message can be stored. 
 * @param section Section name
 * @param name    Parameter name
 * @param value   Parameter value
 * @return 0 on error, 1 when successful
 */
static int cnf_preparser(void* data, const char* section, const char* name, const char* value)
{
    MXS_CONFIG* cnf = config_get_global_options();

    char* tmp;
    /** These are read from the configuration file.  These will not override
     * command line parameters but will override default values. */ 
    if (strcasecmp(section, "maxscale") == 0)
    {
        if (cnf->substitute_variables)
        {
            if (*value == '$')
            {
                char* env_value = getenv(value + 1);

                if (! env_value)
                {
                    char** s = (char**)data;

                    static const char FORMAT[] = "The environment variable %s does not exist."; 
                    *s = (char*)MXS_MALLOC(sizeof(FORMAT) + strlen(value));

                    if (*s)
                    {
                        sprintf(*s, FORMAT, value + 1);
                    }

                    return 0;
                }

                value = env_value;
            }
        }

        if (strcmp(name, CN_LOGDIR) == 0)
        {
            if (strcmp(get_logdir(), default_logdir) == 0)
            {
                if (handle_path_arg(&tmp, (char*)value, NULL, true, true))
                {
                    set_logdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_LIBDIR) == 0)
        {
            if (strcmp(get_libdir(), default_libdir) == 0)
            {
                if (handle_path_arg(&tmp, (char*)value, NULL, true, false))
                {
                    set_libdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_PIDDIR) == 0)
        {
            if (strcmp(get_piddir(), default_piddir) == 0)
            {
                if (handle_path_arg(&tmp, (char*)value, NULL, true, true))
                {
                    set_piddir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_DATADIR) == 0)
        {
            if (! datadir_defined)
            {
                if (handle_path_arg(&tmp, (char*)value, NULL, true, false))
                {
                    snprintf(datadir, PATH_MAX, "%s", tmp);
                    datadir[PATH_MAX] = '\0';
                    set_datadir(tmp);
                    datadir_defined = true;
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_CACHEDIR) == 0)
        {
            if (strcmp(get_cachedir(), default_cachedir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_cachedir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_LANGUAGE) == 0)
        {
            if (strcmp(get_langdir(), default_langdir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_langdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_EXECDIR) == 0)
        {
            if (strcmp(get_execdir(), default_execdir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_execdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_CONNECTOR_PLUGINDIR) == 0)
        {
            if (strcmp(get_connector_plugindir(), default_connector_plugindir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_connector_plugindir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_PERSISTDIR) == 0)
        {
            if (strcmp(get_config_persistdir(), default_config_persistdir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_config_persistdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_MODULE_CONFIGDIR) == 0)
        {
            if (strcmp(get_module_configdir(), default_module_configdir) == 0)
            {
                if (handle_path_arg((char**)&tmp, (char*)value, NULL, true, false))
                {
                    set_module_configdir(tmp);
                }
                else
                {
                    return 0;
                }
            }
        }
        else if (strcmp(name, CN_SYSLOG) == 0)
        {
            if (! syslog_configured)
            {
                cnf->syslog = config_truth_value((char*)value);
            }
        }
        else if (strcmp(name, CN_MAXLOG) == 0)
        {
            if (! maxlog_configured)
            {
                cnf->maxlog = config_truth_value((char*)value);
            }
        }
        else if (strcmp(name, CN_LOG_AUGMENTATION) == 0)
        {
            set_log_augmentation(value);
        }
        else if (strcmp(name, CN_SUBSTITUTE_VARIABLES) == 0)
        {
            cnf->substitute_variables = config_truth_value(value);
        }
    }

    return 1;
}

static int set_user(const char* user)
{
    errno = 0;
    struct passwd* pwname;
    int rval;

    pwname = getpwnam(user);
    if (pwname == NULL)
    {
        printf("Error: Failed to retrieve user information for '%s': %d %s\n",
               user,
               errno,
               errno == 0 ? " User not found" : mxs_strerror(errno));
        return -1;
    }

    rval = setgid(pwname->pw_gid);
    if (rval ! = 0)
    {
        printf("Error: Failed to change group to '%d': %d %s\n",
               pwname->pw_gid,
               errno,
               mxs_strerror(errno));
        return rval;
    }

    rval = setuid(pwname->pw_uid);
    if (rval ! = 0)
    {
        printf("Error: Failed to change user to '%s': %d %s\n",
               pwname->pw_name,
               errno,
               mxs_strerror(errno));
        return rval;
    }
    if (prctl(PR_GET_DUMPABLE) == 0)
    {
        if (prctl(PR_SET_DUMPABLE, 1) == -1)
        {
            printf("Error: Failed to set dumpable flag on for the process '%s': %d %s\n",
                   pwname->pw_name,
                   errno,
                   mxs_strerror(errno));
            return -1;
        }
    }
#ifdef SS_DEBUG
    else
    {
        printf("Running MaxScale as: %s %d:%d\n", pwname->pw_name, pwname->pw_uid, pwname->pw_gid);
    }
#endif


    return rval;
}

/**
 * Write the exit status of the child process to the parent process. 
 * @param fd File descriptor to write to
 * @param code Exit status of the child process
 */
void write_child_exit_code(int fd, int code)
{
    /** Notify the parent process that an error has occurred */
    if (write(fd, &code, sizeof(int)) == -1)
    {
        printf("Failed to write child process message!\n");
    }
    close(fd);
}

/**
 * Change the current working directory
 *
 * Change the current working directory to the log directory.  If this is not
 * possible, try to change location to the file system root.  If this also fails,
 * return with an error. 
 * @return True if changing the current working directory was successful. 
 */
static bool change_cwd()
{
    bool rval = true;

    if (chdir(get_logdir()) ! = 0)
    {
        MXS_ERROR("Failed to change working directory to '%s': %d, %s. "
                  "Trying to change working directory to '/'.", 
                  get_logdir(),
                  errno,
                  mxs_strerror(errno));
        if (chdir("/") ! = 0)
        {
            MXS_ERROR("Failed to change working directory to '/': %d, %s",
                      errno,
                      mxs_strerror(errno));
            rval = false;
        }
        else
        {
            MXS_WARNING("Using '/' instead of '%s' as the current working directory.", 
                        get_logdir());
        }
    }
    else
    {
        MXS_NOTICE("Working directory: %s", get_logdir());
    }

    return rval;
}

/**
 * @brief Log a message about the last received signal
 */
static void log_exit_status()
{
    switch (last_signal)
    {
    case SIGTERM:
        MXS_NOTICE("MaxScale received signal SIGTERM.  Exiting."); 
        break;

    case SIGINT:
        MXS_NOTICE("MaxScale received signal SIGINT.  Exiting."); 
        break;

    default:
        break;
    }
}

/**
 * Daemonize the process by forking and putting the process into the
 * background. 
 *
 * @return True if context is that of the parent process, false if that of the
 *         child process. 
 */
static bool daemonize(void)
{
    pid_t pid;

    pid = fork();

    if (pid < 0)
    {
        fprintf(stderr, "fork() error %s\n", mxs_strerror(errno));
        exit(1);
    }

    if (pid ! = 0)
    {
        /* exit from main */
        return true;
    }

    if (setsid() < 0)
    {
        fprintf(stderr, "setsid() error %s\n", mxs_strerror(errno));
        exit(1);
    }
    return false;
}

/**
 * Sniffs the configuration file, primarily for various directory paths,
 * so that certain settings take effect immediately. 
 *
 * @param filepath The path of the configuration file. 
 *
 * @return True, if the sniffing succeeded, false otherwise. 
 */
static bool sniff_configuration(const char* filepath)
{
    char* s = NULL;

    int rv = ini_parse(filepath, cnf_preparser, &s);

    if (rv ! = 0)
    {
        const char FORMAT_CUSTOM[] =
            "Failed to pre-parse configuration file %s. Error on line %d. %s";
        const char FORMAT_SYNTAX[] =
            "Failed to pre-parse configuration file %s. Error on line %d.";
        const char FORMAT_OPEN[] =
            "Failed to pre-parse configuration file %s. Failed to open file."; 
        const char FORMAT_MALLOC[] =
            "Failed to pre-parse configuration file %s. Memory allocation failed."; 

        size_t extra = strlen(filepath) + UINTLEN(abs(rv)) + (s ?  strlen(s) : 0);
        // We just use the largest one. 
        char errorbuffer[sizeof(FORMAT_MALLOC) + extra];

        if (rv > 0)
        {
            if (s)
            {
                snprintf(errorbuffer, sizeof(errorbuffer), FORMAT_CUSTOM, filepath, rv, s);
                MXS_FREE(s);
            }
            else
            {
                snprintf(errorbuffer, sizeof(errorbuffer), FORMAT_SYNTAX, filepath, rv);
            }
        }
        else if (rv == -1)
        {
            snprintf(errorbuffer, sizeof(errorbuffer), FORMAT_OPEN, filepath);
        }
        else
        {
            snprintf(errorbuffer, sizeof(errorbuffer), FORMAT_MALLOC, filepath);
        }

        print_log_n_stderr(false, true, errorbuffer, errorbuffer, 0);
    }

    return rv == 0;
}

/**
 * Calls init on all loaded modules. 
 *
 * @return True, if all modules were successfully initialized. 
 */
static bool modules_process_init()
{
    bool initialized = false;

    MXS_MODULE_ITERATOR i = mxs_module_iterator_get(NULL);
    MXS_MODULE* module = NULL;

    while ((module = mxs_module_iterator_get_next(&i)) ! = NULL)
    {
        if (module->process_init)
        {
            int rc = (module->process_init)();

            if (rc ! = 0)
            {
                break;
            }
        }
    }

    if (module)
    {
        // If module is non-NULL it means that the initialization failed for
        // that module.  We now need to call finish on all modules that were
        // successfully initialized. 
        MXS_MODULE* failed_module = module;
        i = mxs_module_iterator_get(NULL);

        while ((module = mxs_module_iterator_get_next(&i)) ! = failed_module)
        {
            if (module->process_finish)
            {
                (module->process_finish)();
            }
        }
    }
    else
    {
        initialized = true;
    }

    return initialized;
}

/**
 * Calls process_finish on all loaded modules. 
 */
static void modules_process_finish()
{
    MXS_MODULE_ITERATOR i = mxs_module_iterator_get(NULL);
    MXS_MODULE* module = NULL;

    while ((module = mxs_module_iterator_get_next(&i)) ! = NULL)
    {
        if (module->process_finish)
        {
            (module->process_finish)();
        }
    }
}

static void enable_module_unloading(const char* arg)
{
    unload_modules_at_exit = true;
}

static void disable_module_unloading(const char* arg)
{
    unload_modules_at_exit = false;
}

static void enable_statement_logging(const char* arg)
{
    maxsql::mysql_set_log_statements(true);
}

static void disable_statement_logging(const char* arg)
{
    maxsql::mysql_set_log_statements(false);
}

static void redirect_output_to_file(const char* arg)
{
    if (arg)
    {
        redirect_output_to = arg;
    }
}

/**
 * Process command line debug arguments
 *
 * @param args The debug argument list
 * @return True on success, false on error
 */
static bool handle_debug_args(char* args)
{
    bool arg_error = false;
    int args_found = 0;
    char* endptr = NULL;
    char* token = strtok_r(args, ",", &endptr);
    while (token)
    {
        char* value = strchr(token, '=');

        if (value)
        {
            *value++ = '\0';
        }

        bool found = false;
        for (int i = 0; debug_arguments[i].action ! = NULL; i++)
        {

            // Debug features are activated by running functions in the struct-array. 
            if (strcmp(token, debug_arguments[i].name) == 0)
            {
                found = true;
                args_found++;
                debug_arguments[i].action(value);
                break;
            }
        }
        if (! found)
        {
            const char UNRECOG_P1[] = "Unrecognized debug setting: '";
            const char UNRECOG_P2[] = "'."; 
            size_t unrecog_msg_len = sizeof(UNRECOG_P1) + strlen(token) + sizeof(UNRECOG_P2);
            char unrecog_msg[unrecog_msg_len];
            snprintf(unrecog_msg, unrecog_msg_len, "%s%s%s", UNRECOG_P1, token, UNRECOG_P2);
            print_log_n_stderr(true, true, unrecog_msg, unrecog_msg, 0);
            arg_error = true;
        }
        token = strtok_r(NULL, ",", &endptr);
    }
    if (args_found == 0)
    {
        arg_error = true;
    }
    if (arg_error)
    {
        // Form a string with all debug argument names listed. 
        size_t total_len = 1;
        for (int i = 0; debug_arguments[i].action ! = NULL; i++)
        {
            total_len += strlen(debug_arguments[i].name) + 1;
        }
        char arglist[total_len];
        arglist[0] = '\0';
        for (int i = 0; debug_arguments[i].action ! = NULL; i++)
        {
            strcat(arglist, debug_arguments[i].name);
            // If not the last element, add a comma
            if (debug_arguments[i + 1].action ! = NULL)
            {
                strcat(arglist, ", ");
            }
        }
        const char DEBUG_ERROR_P1[] =
            "Debug argument identifier '-g' or '--debug' was specified "
            "but no arguments were found or one of them was invalid.  Supported "
            "arguments are: ";
        const char DEBUG_ERROR_P2[] = "."; 
        size_t arg_error_msg_len = sizeof(DEBUG_ERROR_P1) + total_len + sizeof(DEBUG_ERROR_P2);
        char arg_error_msg[arg_error_msg_len];
        snprintf(arg_error_msg,
                 arg_error_msg_len,
                 "%s%s%s",
                 DEBUG_ERROR_P1,
                 arglist,
                 DEBUG_ERROR_P2);
        print_log_n_stderr(true, true, arg_error_msg, arg_error_msg, 0);
    }
    return ! arg_error;
}

static bool user_is_acceptable(const char* specified_user)
{
    bool acceptable = false;

    // This is very early, so we do not have logging available, but write to stderr. 
    // As this is security related, we want to do as little as possible. 

    uid_t uid = getuid();   // Always succeeds
    errno = 0;
    struct passwd* pw = getpwuid(uid);
    if (pw)
    {
        if (strcmp(pw->pw_name, "root") == 0)
        {
            if (specified_user && (strcmp(specified_user, "root") == 0))
            {
                // MaxScale was invoked as root and with --user=root. 
                acceptable = true;
            }
            else
            {
                fprintf(stderr, "Error: MaxScale cannot be run as root.\n");
            }
        }
        else
        {
            acceptable = true;
        }
    }
    else
    {
        fprintf(stderr,
                "Error: Could not obtain user information, MaxScale will not run: %s",
                strerror(errno));
    }

    return acceptable;
}

static bool init_sqlite3()
{
    bool rv = true;

    // Collecting the memstatus introduces locking that, according to customer reports,
    // has a significant impact on the performance. 
    if (sqlite3_config(SQLITE_CONFIG_MEMSTATUS, (int)0) == SQLITE_OK)   // 0 turns off. 
    {
        MXS_NOTICE("The collection of SQLite memory allocation statistics turned off."); 
    }
    else
    {
        MXS_WARNING("Could not turn off the collection of SQLite memory allocation statistics."); 
        // Non-fatal, we simply will take a small performance hit. 
    }

    if (sqlite3_config(SQLITE_CONFIG_MULTITHREAD) == SQLITE_OK)
    {
        MXS_NOTICE("Threading mode of SQLite set to Multi-thread."); 
    }
    else
    {
        MXS_ERROR("Could not set the threading mode of SQLite to Multi-thread. " 
                  "MaxScale will terminate."); 
        rv = false;
    }

    return rv;
}

static bool lock_dir(const std::string& path)
{
    std::string lock = path + "/maxscale.lock";
    int fd = open(lock.c_str(), O_WRONLY | O_CREAT, 0777);
    std::string pid = std::to_string(getpid());

    if (fd == -1)
    {
        MXS_ERROR("Failed to open lock file %s: %s", lock.c_str(), mxs_strerror(errno));
        return false;
    }

    if (lockf(fd, F_TLOCK, 0) == -1)
    {
        if (errno == EACCES || errno == EAGAIN)
        {
            MXS_ERROR("Failed to lock directory with file '%s', another process is holding a lock on it. " 
                      "Please confirm that no other MaxScale process is using the "
                      "directory %s",
                      lock.c_str(),
                      path.c_str());
        }
        else
        {
            MXS_ERROR("Failed to lock file %s. %s", lock.c_str(), mxs_strerror(errno));
        }
        close(fd);
        return false;
    }

    if (ftruncate(fd, 0) == -1)
    {
        MXS_ERROR("Failed to truncate lock file %s: %s", lock.c_str(), mxs_strerror(errno));
        close(fd);
        unlink(lock.c_str());
        return false;
    }

    if (write(fd, pid.c_str(), pid.length()) == -1)
    {
        MXS_ERROR("Failed to write into lock file %s: %s", lock.c_str(), mxs_strerror(errno));
        close(fd);
        unlink(lock.c_str());
        return false;
    }

    directory_locks.insert(std::pair<std::string, int>(lock, fd));

    return true;
}

bool lock_directories()
{
    std::set<std::string> paths {get_cachedir(), get_datadir()};
    return std::all_of(paths.begin(), paths.end(), lock_dir);
}

static void unlock_directories()
{
    std::for_each(directory_locks.begin(),
                  directory_locks.end(),
                  [&](std::pair<std::string, int> pair) {
                      close(pair.second);
                      unlink(pair.first.c_str());
                  });
}
