/* * 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-02-10 * * 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 #ifdef HAVE_GLIBC #include #endif #include #include #include #include #include #include #include #ifdef HAVE_SYSTEMD #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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); 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/mariadb-enterprise/mariadb-maxscale-21/ \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; } /** * 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; } /** * Configures signal handling of MaxScale. * * @return True, if all signals could be configured, false otherwise. */ bool configure_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; } 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; } 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]); } /*< * Set signal handlers for SIGHUP, SIGTERM, SIGINT and critical signals like SIGSEGV. */ if (!configure_signals()) { static const char* logerr = "Failed to configure 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()); 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(); /*< * 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(lock, fd)); return true; } bool lock_directories() { std::set 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 pair) { close(pair.second); unlink(pair.first.c_str()); }); }