
There were many combinations of tab-width used so making the indentation look right in the editor wasn't really possible. The changes made: - All tabs replaced with spaces. - Indentation depth 4. - , followed by space. - Most binary operators (*, =, -) surrounded by one space. - No space following ( or before ). - Keywords follwed by 1 space.
2581 lines
74 KiB
C
2581 lines
74 KiB
C
/*
|
|
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
|
* software: you can redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License as published by the Free Software Foundation,
|
|
* version 2.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright MariaDB Corporation Ab 2013-2015
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @file gateway.c - The gateway entry point.
|
|
*
|
|
* @verbatim
|
|
* Revision History
|
|
*
|
|
* Date Who Description
|
|
* 23-05-2013 Massimiliano Pinto epoll loop test
|
|
* 12-06-2013 Mark Riddoch Add the -p option to set the
|
|
* listening port
|
|
* and bind addr is 0.0.0.0
|
|
* 19/06/13 Mark Riddoch Extract the epoll functionality
|
|
* 21/06/13 Mark Riddoch Added initial config support
|
|
* 27/06/13
|
|
* 28/06/13 Vilho Raatikka Added necessary headers, example functions and
|
|
* calls to log manager and to query classifier.
|
|
* Put example code behind SS_DEBUG macros.
|
|
* 05/02/14 Mark Riddoch Addition of version string
|
|
* 29/06/14 Massimiliano Pinto Addition of pidfile
|
|
* 10/08/15 Markus Makela Added configurable directory locations
|
|
* @endverbatim
|
|
*/
|
|
#define _XOPEN_SOURCE 700
|
|
#define OPENSSL_THREAD_DEFINES
|
|
#include <my_config.h>
|
|
|
|
#include <openssl/opensslconf.h>
|
|
#if defined(OPENSSL_THREADS)
|
|
#define HAVE_OPENSSL_THREADS 1
|
|
#else
|
|
#define HAVE_OPENSSL_THREADS 0
|
|
#endif
|
|
|
|
#include <ftw.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <gw.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <getopt.h>
|
|
#include <service.h>
|
|
#include <server.h>
|
|
#include <dcb.h>
|
|
#include <session.h>
|
|
#include <modules.h>
|
|
#include <maxconfig.h>
|
|
#include <poll.h>
|
|
#include <housekeeper.h>
|
|
#include <service.h>
|
|
#include <memlog.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <mysql.h>
|
|
#include <monitor.h>
|
|
#include <version.h>
|
|
#include <maxscale.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
# include <skygw_utils.h>
|
|
# include <log_manager.h>
|
|
|
|
#include <execinfo.h>
|
|
|
|
#include <ini.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/file.h>
|
|
|
|
#define STRING_BUFFER_SIZE 1024
|
|
#define PIDFD_CLOSED -1
|
|
|
|
/** for procname */
|
|
#if !defined(_GNU_SOURCE)
|
|
# define _GNU_SOURCE
|
|
#endif
|
|
|
|
time_t MaxScaleStarted;
|
|
|
|
extern char *program_invocation_name;
|
|
extern char *program_invocation_short_name;
|
|
|
|
/**
|
|
* Variable holding the enabled logfiles information.
|
|
* Used from log users to check enabled logs prior calling
|
|
* actual library calls such as skygw_log_write.
|
|
*/
|
|
/** Defined in log_manager.cc */
|
|
extern int lm_enabled_logfiles_bitmask;
|
|
extern size_t log_ses_count[];
|
|
extern __thread log_info_t tls_log_info;
|
|
|
|
/*
|
|
* Server options are passed to the mysql_server_init. Each gateway must have a unique
|
|
* data directory that is passed to the mysql_server_init, therefore the data directory
|
|
* is not fixed here and will be updated elsewhere.
|
|
*/
|
|
static char* server_options[] = {
|
|
"MariaDB Corporation MaxScale",
|
|
"--no-defaults",
|
|
"--datadir=",
|
|
"--language=",
|
|
"--skip-innodb",
|
|
"--default-storage-engine=myisam",
|
|
NULL
|
|
};
|
|
|
|
const int num_elements = (sizeof(server_options) / sizeof(char *)) - 1;
|
|
|
|
static char* server_groups[] = {
|
|
"embedded",
|
|
"server",
|
|
"server",
|
|
"embedded",
|
|
"server",
|
|
"server",
|
|
NULL
|
|
};
|
|
|
|
/* 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;
|
|
|
|
/**
|
|
* exit flag for log flusher.
|
|
*/
|
|
static bool do_exit = FALSE;
|
|
|
|
/**
|
|
* Flag to indicate whether libmysqld is successfully initialized.
|
|
*/
|
|
static bool libmysqld_started = FALSE;
|
|
|
|
/**
|
|
* 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;
|
|
static struct option long_options[] = {
|
|
{"homedir", required_argument, 0, 'c'},
|
|
{"config", required_argument, 0, 'f'},
|
|
{"nodaemon", no_argument, 0, 'd'},
|
|
{"log", required_argument, 0, 'l'},
|
|
{"logdir", required_argument, 0, 'L'},
|
|
{"datadir", required_argument, 0, 'D'},
|
|
{"configdir", required_argument, 0, 'C'},
|
|
{"piddir", required_argument, 0, 'P'},
|
|
{"libdir", required_argument, 0, 'B'},
|
|
{"cachedir", required_argument, 0, 'A'},
|
|
{"language", required_argument, 0, 'N'},
|
|
{"syslog", required_argument, 0, 's'},
|
|
{"maxscalelog", required_argument, 0, 'S'},
|
|
{"user", required_argument, 0, 'U'},
|
|
{"version", no_argument, 0, 'v'},
|
|
{"help", no_argument, 0, '?'},
|
|
{"version-full", no_argument, 0, 'V'},
|
|
{"log_augmentation", required_argument, 0, 'G'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
static int cnf_preparser(void* data, const char* section, const char* name, const char* value);
|
|
static void log_flush_shutdown(void);
|
|
static void log_flush_cb(void* arg);
|
|
static int write_pid_file(); /* write MaxScale pidfile */
|
|
static void unlink_pidfile(void); /* remove pidfile */
|
|
static void unlock_pidfile();
|
|
static void libmysqld_done(void);
|
|
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 file_is_readable(char* absolute_pathname);
|
|
static bool file_is_writable(char* absolute_pathname);
|
|
bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr);
|
|
static void set_log_augmentation(const char* value);
|
|
static void usage(void);
|
|
static char* get_expanded_pathname(
|
|
char** abs_path,
|
|
char* input_path,
|
|
const char* fname);
|
|
static void print_log_n_stderr(
|
|
bool do_log,
|
|
bool do_stderr,
|
|
char* logstr,
|
|
char* fprstr,
|
|
int eno);
|
|
static bool resolve_maxscale_conf_fname(
|
|
char** cnf_full_path,
|
|
char* home_dir,
|
|
char* cnf_file_arg);
|
|
|
|
static char* check_dir_access(char* dirname, bool, bool);
|
|
static int set_user();
|
|
bool pid_file_exists();
|
|
void write_child_exit_code(int fd, int code);
|
|
/** SSL multi-threading functions and structures */
|
|
|
|
static SPINLOCK* ssl_locks;
|
|
|
|
static void ssl_locking_function(int mode, int n, const char* file, int line)
|
|
{
|
|
if (mode & CRYPTO_LOCK)
|
|
spinlock_acquire(&ssl_locks[n]);
|
|
else
|
|
spinlock_release(&ssl_locks[n]);
|
|
}
|
|
/**
|
|
* OpenSSL requires this struct to be defined in order to use dynamic locks
|
|
*/
|
|
struct CRYPTO_dynlock_value
|
|
{
|
|
SPINLOCK 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 = malloc(sizeof(struct CRYPTO_dynlock_value));
|
|
if (lock)
|
|
{
|
|
spinlock_init(&lock->lock);
|
|
}
|
|
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)
|
|
{
|
|
spinlock_acquire(&n->lock);
|
|
}
|
|
else
|
|
{
|
|
spinlock_release(&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)
|
|
{
|
|
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
|
|
|
|
/**
|
|
* Handler for SIGHUP signal. Reload the configuration for the
|
|
* gateway.
|
|
*/
|
|
static void sighup_handler (int i)
|
|
{
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"Refreshing configuration following SIGHUP\n")));
|
|
config_reload();
|
|
}
|
|
|
|
/**
|
|
* Handler for SIGUSR1 signal. A SIGUSR1 signal will cause
|
|
* maxscale to rotate all log files.
|
|
*/
|
|
static void sigusr1_handler (int i)
|
|
{
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"Log file flush following reception of SIGUSR1\n")));
|
|
skygw_log_rotate(LOGFILE_ERROR);
|
|
skygw_log_rotate(LOGFILE_MESSAGE);
|
|
skygw_log_rotate(LOGFILE_TRACE);
|
|
skygw_log_rotate(LOGFILE_DEBUG);
|
|
}
|
|
|
|
static void sigterm_handler (int i) {
|
|
extern void shutdown_server();
|
|
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"MaxScale received signal SIGTERM. Exiting.");
|
|
skygw_log_sync_all();
|
|
shutdown_server();
|
|
}
|
|
|
|
static void
|
|
sigint_handler (int i)
|
|
{
|
|
extern void shutdown_server();
|
|
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"MaxScale received signal SIGINT. Shutting down.");
|
|
skygw_log_sync_all();
|
|
shutdown_server();
|
|
fprintf(stderr, "\n\nShutting down MaxScale\n\n");
|
|
}
|
|
|
|
static void
|
|
sigchld_handler (int i)
|
|
{
|
|
int exit_status = 0;
|
|
pid_t child = -1;
|
|
|
|
if ((child = wait(&exit_status)) == -1)
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
skygw_log_write_flush(LE, "Error: failed to wait child process: %d %s",
|
|
errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
}
|
|
else
|
|
{
|
|
if (WIFEXITED(exit_status))
|
|
{
|
|
skygw_log_write_flush(WEXITSTATUS(exit_status) != 0 ? LE : LT,
|
|
"Child process %d exited with status %d",
|
|
child, WEXITSTATUS(exit_status));
|
|
}
|
|
else if (WIFSIGNALED(exit_status))
|
|
{
|
|
skygw_log_write_flush((LE|LT),
|
|
"Child process %d was stopped by signal %d.",
|
|
child, WTERMSIG(exit_status));
|
|
}
|
|
else
|
|
{
|
|
skygw_log_write_flush((LE|LT),
|
|
"Child process %d did not exit normally. Exit status: %d",
|
|
child, exit_status);
|
|
}
|
|
}
|
|
}
|
|
|
|
int fatal_handling = 0;
|
|
|
|
static int signal_set(int sig, void (*handler)(int));
|
|
|
|
static void
|
|
sigfatal_handler(int i)
|
|
{
|
|
if (fatal_handling) {
|
|
fprintf(stderr, "Fatal signal %d while backtracing\n", i);
|
|
_exit(1);
|
|
}
|
|
fatal_handling = 1;
|
|
GATEWAY_CONF* cnf = config_get_global_options();
|
|
fprintf(stderr, "\n\nMaxScale "MAXSCALE_VERSION" received fatal signal %d\n", i);
|
|
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Fatal: MaxScale "MAXSCALE_VERSION" received fatal signal %d. Attempting backtrace.", i);
|
|
|
|
skygw_log_write_flush(LE, "Commit ID: %s System name: %s "
|
|
"Release string: %s Embedded library version: %s",
|
|
maxscale_commit, cnf->sysname, cnf->release_string, cnf->version_string);
|
|
|
|
{
|
|
void *addrs[128];
|
|
int n, count = backtrace(addrs, 128);
|
|
char** symbols = backtrace_symbols(addrs, count);
|
|
|
|
if (symbols) {
|
|
for (n = 0; n < count; n++) {
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
" %s\n", symbols[n]);
|
|
}
|
|
free(symbols);
|
|
} else {
|
|
fprintf(stderr, "\nresolving symbols to error log failed, writing call trace to stderr:\n");
|
|
backtrace_symbols_fd(addrs, count, fileno(stderr));
|
|
}
|
|
}
|
|
|
|
skygw_log_sync_all();
|
|
|
|
/* 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)) {
|
|
static struct sigaction sigact;
|
|
static int err;
|
|
int rc = 0;
|
|
|
|
memset(&sigact, 0, sizeof(struct sigaction));
|
|
sigact.sa_handler = handler;
|
|
GW_NOINTR_CALL(err = sigaction(sig, &sigact, NULL));
|
|
|
|
if (err < 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
char errbuf[STRERROR_BUFLEN];
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Failed call sigaction() in %s due to %d, %s.",
|
|
program_invocation_short_name,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
rc = 1;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 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 = remove(filename);
|
|
|
|
if (rc != 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
char errbuf[STRERROR_BUFLEN];
|
|
skygw_log_write(
|
|
LOGFILE_ERROR,
|
|
"Error : Failed to remove the data directory %s of "
|
|
"MaxScale due to %d, %s.",
|
|
datadir,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void datadir_cleanup()
|
|
{
|
|
int depth = 1;
|
|
int flags = FTW_CHDIR|FTW_DEPTH|FTW_MOUNT;
|
|
|
|
if (datadir[0] != 0 && access(datadir, F_OK) == 0)
|
|
{
|
|
nftw(datadir, ntfw_cb, depth, flags);
|
|
}
|
|
}
|
|
|
|
static void libmysqld_done(void)
|
|
{
|
|
if (libmysqld_started) {
|
|
mysql_library_end();
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
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 = NULL;
|
|
const char* header_buf3;
|
|
time_t* t = NULL;
|
|
struct tm* tm = NULL;
|
|
#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
|
|
|
|
if ((t = (time_t *)malloc(sizeof(time_t))) == NULL) {
|
|
goto return_succp;
|
|
}
|
|
|
|
if ((tm = (struct tm *)malloc(sizeof(struct tm))) == NULL) {
|
|
goto return_succp;
|
|
}
|
|
|
|
*t = time(NULL);
|
|
*tm = *localtime(t);
|
|
|
|
header_buf1 = "\n\nMariaDB Corporation MaxScale " MAXSCALE_VERSION "\t";
|
|
header_buf2 = strdup(asctime(tm));
|
|
|
|
if (header_buf2 == NULL) {
|
|
goto return_succp;
|
|
}
|
|
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:
|
|
if (tm != NULL) {
|
|
free(tm);
|
|
}
|
|
if (t != NULL) {
|
|
free(t);
|
|
}
|
|
if (header_buf2 != NULL) {
|
|
free(header_buf2);
|
|
}
|
|
return succp;
|
|
}
|
|
|
|
static bool resolve_maxscale_conf_fname(
|
|
char** cnf_full_path,
|
|
char* home_dir,
|
|
char* cnf_file_arg)
|
|
{
|
|
bool succp = false;
|
|
|
|
if (cnf_file_arg != NULL)
|
|
{
|
|
char* home_etc_dir;
|
|
/*<
|
|
* 1. argument is valid full pathname
|
|
* '- /home/jdoe/MaxScale/myconf.cnf'
|
|
*/
|
|
if (file_is_readable(cnf_file_arg))
|
|
{
|
|
*cnf_full_path = cnf_file_arg;
|
|
succp = true;
|
|
goto return_succp;
|
|
}
|
|
/*<
|
|
* 2. argument is file name only and file is located in home
|
|
* directory.
|
|
* '-f MaxScale.cnf'
|
|
*/
|
|
*cnf_full_path = get_expanded_pathname(NULL,
|
|
home_dir,
|
|
cnf_file_arg);
|
|
|
|
if (*cnf_full_path != NULL)
|
|
{
|
|
if (file_is_readable(*cnf_full_path))
|
|
{
|
|
succp = true;
|
|
goto return_succp;
|
|
}
|
|
else
|
|
{
|
|
char* logstr = "Found config file but wasn't "
|
|
"able to read it.";
|
|
int eno = errno;
|
|
print_log_n_stderr(true, true, logstr, logstr, eno);
|
|
goto return_succp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/** Allocate memory for use of realpath */
|
|
*cnf_full_path = (char *)malloc(PATH_MAX+1);
|
|
}
|
|
/*<
|
|
* 3. argument is valid relative pathname
|
|
* '-f ../myconf.cnf'
|
|
*/
|
|
if (realpath(cnf_file_arg, *cnf_full_path) != NULL)
|
|
{
|
|
if (file_is_readable(*cnf_full_path))
|
|
{
|
|
succp = true;
|
|
goto return_succp;
|
|
}
|
|
else
|
|
{
|
|
char* logstr = "Failed to open read access to "
|
|
"config file.";
|
|
int eno = errno;
|
|
print_log_n_stderr(true, true, logstr, logstr, eno);
|
|
goto return_succp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char* logstr = "Failed to expand config file name to "
|
|
"complete path.";
|
|
int eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, logstr, logstr, eno);
|
|
goto return_succp;
|
|
}
|
|
}
|
|
else /*< default config file name is used */
|
|
{
|
|
*cnf_full_path = get_expanded_pathname(NULL, home_dir, default_cnf_fname);
|
|
|
|
if (*cnf_full_path != NULL)
|
|
{
|
|
if (file_is_readable(*cnf_full_path))
|
|
{
|
|
succp = true;
|
|
goto return_succp;
|
|
}
|
|
else
|
|
{
|
|
char* logstr = "Found config file but wasn't "
|
|
"able to read it.";
|
|
int eno = errno;
|
|
print_log_n_stderr(true, true, logstr, logstr, eno);
|
|
goto return_succp;
|
|
}
|
|
}
|
|
goto return_succp;
|
|
}
|
|
return_succp:
|
|
return succp;
|
|
}
|
|
|
|
/**
|
|
* 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 = strdup("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 = strdup(errbuf);
|
|
goto retblock;
|
|
}
|
|
|
|
if (rd && !file_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 = strdup(errbuf);
|
|
goto retblock;
|
|
}
|
|
|
|
if (wr && !file_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 = strdup(errbuf);
|
|
goto retblock;
|
|
}
|
|
|
|
retblock:
|
|
return errstr;
|
|
}
|
|
|
|
|
|
/**
|
|
* @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 */
|
|
char* logstr, /*< string to be printed to log */
|
|
char* fprstr, /*< string to be printed to stderr */
|
|
int eno) /*< errno, if it is set, zero, otherwise */
|
|
{
|
|
char* log_err = "Error :";
|
|
char* fpr_err = "*\n* Error :";
|
|
char* fpr_end = "\n*\n";
|
|
|
|
if (do_log) {
|
|
char errbuf[STRERROR_BUFLEN];
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"%s %s %s %s",
|
|
log_err,
|
|
logstr,
|
|
eno == 0 ? " " : "Error :",
|
|
eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
}
|
|
if (do_stderr) {
|
|
char errbuf[STRERROR_BUFLEN];
|
|
fprintf(stderr,
|
|
"%s %s %s %s %s",
|
|
fpr_err,
|
|
fprstr,
|
|
eno == 0 ? " " : "Error :",
|
|
eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf)),
|
|
fpr_end);
|
|
}
|
|
}
|
|
|
|
static bool file_is_readable(
|
|
char* absolute_pathname)
|
|
{
|
|
bool succp = true;
|
|
|
|
if (access(absolute_pathname, R_OK) != 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
char errbuf[STRERROR_BUFLEN];
|
|
|
|
if (!daemon_mode)
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Warning : Failed to read the configuration "
|
|
"file %s. %s.\n*\n",
|
|
absolute_pathname,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
}
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Warning : Failed to read the configuration file %s due "
|
|
"to %d, %s.",
|
|
absolute_pathname,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
skygw_log_sync_all();
|
|
succp = false;
|
|
}
|
|
return succp;
|
|
}
|
|
|
|
static bool file_is_writable(
|
|
char* absolute_pathname)
|
|
{
|
|
bool succp = true;
|
|
|
|
if (access(absolute_pathname, W_OK) != 0)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
char errbuf[STRERROR_BUFLEN];
|
|
|
|
if (!daemon_mode)
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : unable to open file %s for write "
|
|
"due %d, %s.\n*\n",
|
|
absolute_pathname,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
}
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : unable to open file %s for write due "
|
|
"to %d, %s.",
|
|
absolute_pathname,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
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,
|
|
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*)malloc(PATH_MAX);
|
|
|
|
/*<
|
|
* Expand possible relative pathname to absolute path
|
|
*/
|
|
if (realpath(relative_path, expanded_path) == NULL)
|
|
{
|
|
int eno = errno;
|
|
errno = 0;
|
|
char errbuf[STRERROR_BUFLEN];
|
|
|
|
fprintf(stderr,
|
|
"*\n* Warning : Failed to read the "
|
|
"directory %s. %s.\n*\n",
|
|
relative_path,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Warning : Failed to read the "
|
|
"directory %s, due "
|
|
"to %d, %s.",
|
|
relative_path,
|
|
eno,
|
|
strerror_r(eno, errbuf, sizeof(errbuf)));
|
|
free(expanded_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*)malloc(pathlen);
|
|
|
|
if (cnf_file_buf == NULL)
|
|
{
|
|
ss_dassert(cnf_file_buf != NULL);
|
|
char errbuf[STRERROR_BUFLEN];
|
|
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Memory allocation failed due to %s.",
|
|
strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
|
|
free(expanded_path);
|
|
expanded_path = NULL;
|
|
goto return_cnf_file_buf;
|
|
}
|
|
snprintf(cnf_file_buf, pathlen, "%s/%s", expanded_path, fname);
|
|
|
|
if (!file_is_readable(cnf_file_buf))
|
|
{
|
|
free(expanded_path);
|
|
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 (!file_is_readable(expanded_path))
|
|
{
|
|
free(expanded_path);
|
|
expanded_path = NULL;
|
|
goto return_cnf_file_buf;
|
|
}
|
|
}
|
|
|
|
if (output_path == NULL)
|
|
{
|
|
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"
|
|
" -d, --nodaemon enable running in terminal process (default:disabled)\n"
|
|
" -f, --config=FILE relative|absolute pathname of MaxScale configuration file\n"
|
|
" (default:/etc/maxscale.cnf)\n"
|
|
" -l, --log=[file|shm] log to file or shared memory (default: shm)\n"
|
|
" -L, --logdir=PATH path to log file directory\n"
|
|
" (default: /var/log/maxscale)\n"
|
|
" -A, --cachedir=PATH path to cache directory\n"
|
|
" (default: /var/cache/maxscale)\n"
|
|
" -B, --libdir=PATH path to module directory\n"
|
|
" (default: /usr/lib64/maxscale)\n"
|
|
" -C, --configdir=PATH path to configuration file directory\n"
|
|
" (default: /etc/)\n"
|
|
" -D, --datadir=PATH path to data directory, stored embedded mysql tables\n"
|
|
" (default: /var/cache/maxscale)\n"
|
|
" -N, --language=PATH apth to errmsg.sys file\n"
|
|
" (default: /var/lib/maxscale)\n"
|
|
" -P, --piddir=PATH path to PID file directory\n"
|
|
" (default: /var/run/maxscale)\n"
|
|
" -U, --user=USER run MaxScale as another user.\n"
|
|
" The user ID and group ID of this user are used to run MaxScale.\n"
|
|
" -s, --syslog=[yes|no] log messages to syslog (default:yes)\n"
|
|
" -S, --maxscalelog=[yes|no] log messages to MaxScale log (default: yes)\n"
|
|
" -v, --version print version info and exit\n"
|
|
" -V, --version-full print full version info and exit\n"
|
|
" -?, --help show this help\n"
|
|
, progname);
|
|
}
|
|
|
|
/**
|
|
* The main entry point into the gateway
|
|
*
|
|
* @param argc The argument count
|
|
* @param argv The array of arguments themselves
|
|
*
|
|
* @return 0 in success, 1 otherwise
|
|
*
|
|
*
|
|
* @details 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 arguments processing.
|
|
* This is not 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 be specified by
|
|
* command-line argument.
|
|
*
|
|
* \<config filename\> is resolved in the following order:
|
|
* 1. from '-f \<config filename\>' command-line argument
|
|
* 2. by using default value "maxscale.cnf"
|
|
*
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
int rc = MAXSCALE_SHUTDOWN;
|
|
int l;
|
|
int i;
|
|
int n;
|
|
int ini_rval;
|
|
intptr_t thread_id;
|
|
int n_threads; /*< number of epoll listener threads */
|
|
int n_services;
|
|
int eno = 0; /*< local variable for errno */
|
|
int opt;
|
|
int daemon_pipe[2];
|
|
bool parent_process;
|
|
int child_status;
|
|
void** threads = NULL; /*< thread list */
|
|
char mysql_home[PATH_MAX+1];
|
|
char datadir_arg[10+PATH_MAX+1]; /*< '--datadir=' + PATH_MAX */
|
|
char language_arg[11+PATH_MAX+1]; /*< '--language=' + PATH_MAX */
|
|
char* cnf_file_path = NULL; /*< conf file, to be freed */
|
|
char* cnf_file_arg = NULL; /*< conf filename from cmd-line arg */
|
|
void* log_flush_thr = NULL;
|
|
char* tmp_path;
|
|
char* tmp_var;
|
|
int option_index;
|
|
int logtofile = 0; /* Use shared memory or file */
|
|
int *syslog_enabled = &config_get_global_options()->syslog; /** Log to syslog */
|
|
int *maxscalelog_enabled = &config_get_global_options()->maxlog; /** Log with MaxScale */
|
|
ssize_t log_flush_timeout_ms = 0;
|
|
sigset_t sigset;
|
|
sigset_t sigpipe_mask;
|
|
sigset_t saved_mask;
|
|
void (*exitfunp[4])(void) = {skygw_logmanager_exit,
|
|
datadir_cleanup,
|
|
write_footer,
|
|
NULL};
|
|
|
|
*syslog_enabled = 0;
|
|
*maxscalelog_enabled = 1;
|
|
|
|
sigemptyset(&sigpipe_mask);
|
|
sigaddset(&sigpipe_mask, SIGPIPE);
|
|
progname = *argv;
|
|
snprintf(datadir, PATH_MAX, "%s", default_datadir);
|
|
datadir[PATH_MAX] = '\0';
|
|
#if defined(FAKE_CODE)
|
|
memset(conn_open, 0, sizeof(bool) * 10240);
|
|
memset(dcb_fake_write_errno, 0, sizeof(unsigned char) * 10240);
|
|
memset(dcb_fake_write_ev, 0, sizeof(__int32_t) * 10240);
|
|
fail_next_backend_fd = false;
|
|
fail_next_client_fd = false;
|
|
fail_next_accept = 0;
|
|
fail_accept_errno = 0;
|
|
#endif /* FAKE_CODE */
|
|
file_write_header(stderr);
|
|
/*<
|
|
* Register functions which are called at exit except libmysqld-related,
|
|
* which must be registered later to avoid ordering issues.
|
|
*/
|
|
for (i = 0; exitfunp[i] != NULL; i++)
|
|
{
|
|
l = atexit(*exitfunp);
|
|
|
|
if (l != 0)
|
|
{
|
|
char* fprerr = "Failed to register exit functions for MaxScale";
|
|
print_log_n_stderr(false, true, NULL, fprerr, 0);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
}
|
|
|
|
while ((opt = getopt_long(argc, argv, "dc:f:l:vVs:S:?L:D:C:B:U:A:P:G:",
|
|
long_options, &option_index)) != -1)
|
|
{
|
|
bool succp = true;
|
|
|
|
switch (opt) {
|
|
case 'd':
|
|
/*< Debug mode, maxscale runs in this same process */
|
|
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)
|
|
{
|
|
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);
|
|
goto return_main;
|
|
|
|
case 'l':
|
|
if (strncasecmp(optarg, "file", PATH_MAX) == 0)
|
|
logtofile = 1;
|
|
else if (strncasecmp(optarg, "shm", PATH_MAX) == 0)
|
|
logtofile = 0;
|
|
else
|
|
{
|
|
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(strdup(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 'S':
|
|
{
|
|
char* tok = strstr(optarg, "=");
|
|
if (tok)
|
|
{
|
|
tok++;
|
|
if (tok)
|
|
*maxscalelog_enabled = config_truth_value(tok);
|
|
}
|
|
else
|
|
{
|
|
*maxscalelog_enabled = config_truth_value(optarg);
|
|
}
|
|
}
|
|
break;
|
|
case 's':
|
|
{
|
|
char* tok = strstr(optarg, "=");
|
|
if (tok)
|
|
{
|
|
tok++;
|
|
if (tok)
|
|
*syslog_enabled = config_truth_value(tok);
|
|
}
|
|
else
|
|
{
|
|
*syslog_enabled = config_truth_value(optarg);
|
|
}
|
|
}
|
|
break;
|
|
case 'U':
|
|
if (set_user(optarg) != 0)
|
|
{
|
|
succp = false;
|
|
}
|
|
break;
|
|
case 'G':
|
|
set_log_augmentation(optarg);
|
|
break;
|
|
case '?':
|
|
usage();
|
|
rc = EXIT_SUCCESS;
|
|
goto return_main;
|
|
|
|
default:
|
|
usage();
|
|
succp = false;
|
|
break;
|
|
}
|
|
|
|
if (!succp)
|
|
{
|
|
rc = MAXSCALE_BADARG;
|
|
goto return_main;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
int r;
|
|
int eno = 0;
|
|
char* fprerr = "Failed to initialize set the signal "
|
|
"set for MaxScale. Exiting.";
|
|
#if defined(SS_DEBUG)
|
|
fprintf(stderr,
|
|
"Info : MaxScale will be run in a daemon process.\n\tSee "
|
|
"the log from the following log files : \n\n");
|
|
#endif
|
|
r = sigfillset(&sigset);
|
|
|
|
if (r != 0)
|
|
{
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, fprerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGHUP);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGHUP from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGUSR1);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGUSR1 from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGTERM);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGTERM from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGSEGV);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGSEGV from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGABRT);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGABRT from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGILL);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGILL from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
r = sigdelset(&sigset, SIGFPE);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGFPE from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
#ifdef SIGBUS
|
|
r = sigdelset(&sigset, SIGBUS);
|
|
|
|
if (r != 0)
|
|
{
|
|
char* logerr = "Failed to delete signal SIGBUS from the "
|
|
"signal set of MaxScale. Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
#endif
|
|
r = sigprocmask(SIG_SETMASK, &sigset, NULL);
|
|
|
|
if (r != 0) {
|
|
char* logerr = "Failed to set the signal set for MaxScale."
|
|
" Exiting.";
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, true, fprerr, logerr, eno);
|
|
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 = gw_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)
|
|
{
|
|
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 */
|
|
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.
|
|
*/
|
|
{
|
|
char* fprerr = "Failed to initialize signal handlers. Exiting.";
|
|
char* logerr = NULL;
|
|
l = signal_set(SIGHUP, sighup_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGHUP. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGUSR1, sigusr1_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGUSR1. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGTERM, sigterm_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGTERM. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGINT, sigint_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGINT. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGSEGV, sigfatal_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGSEGV. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGABRT, sigfatal_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGABRT. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGILL, sigfatal_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGILL. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGFPE, sigfatal_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGFPE. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
l = signal_set(SIGCHLD, sigchld_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGCHLD. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
#ifdef SIGBUS
|
|
l = signal_set(SIGBUS, sigfatal_handler);
|
|
|
|
if (l != 0)
|
|
{
|
|
logerr = strdup("Failed to set signal handler for "
|
|
"SIGBUS. Exiting.");
|
|
goto sigset_err;
|
|
}
|
|
#endif
|
|
sigset_err:
|
|
if (l != 0)
|
|
{
|
|
eno = errno;
|
|
errno = 0;
|
|
print_log_n_stderr(true, !daemon_mode, logerr, fprerr, eno);
|
|
free(logerr);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
}
|
|
eno = pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask);
|
|
|
|
if (eno != 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/** OpenSSL initialization */
|
|
if (!HAVE_OPENSSL_THREADS)
|
|
{
|
|
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();
|
|
|
|
int numlocks = CRYPTO_num_locks();
|
|
if ((ssl_locks = malloc(sizeof(SPINLOCK) * (numlocks + 1))) == NULL)
|
|
{
|
|
char* logerr = "Memory allocation failed";
|
|
print_log_n_stderr(true, true, logerr, logerr, eno);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
|
|
for (i = 0;i<numlocks + 1;i++)
|
|
spinlock_init(&ssl_locks[i]);
|
|
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
|
|
|
|
/* register exit function for embedded MySQL library */
|
|
l = atexit(libmysqld_done);
|
|
|
|
if (l != 0) {
|
|
char* fprerr = "Failed to register exit function for\n* "
|
|
"embedded MySQL library.\n* Exiting.";
|
|
char* logerr = "Failed to register exit function libmysql_done "
|
|
"for MaxScale. Exiting.";
|
|
print_log_n_stderr(true, true, logerr, fprerr, 0);
|
|
rc = MAXSCALE_INTERNALERROR;
|
|
goto return_main;
|
|
}
|
|
|
|
/**
|
|
* 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 ((ini_rval = ini_parse(cnf_file_path, cnf_preparser, NULL)) != 0)
|
|
{
|
|
char errorbuffer[STRING_BUFFER_SIZE];
|
|
|
|
if (ini_rval > 0)
|
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
|
"Error: Failed to pre-parse configuration file. Error on line %d.", ini_rval);
|
|
else if (ini_rval == -1)
|
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
|
"Error: Failed to pre-parse configuration file. Failed to open file.");
|
|
else
|
|
snprintf(errorbuffer, sizeof(errorbuffer),
|
|
"Error: Failed to pre-parse configuration file. Memory allocation failed.");
|
|
|
|
skygw_log_write(LE, errorbuffer);
|
|
if (!daemon_mode)
|
|
{
|
|
strncat(errorbuffer, "\n", STRING_BUFFER_SIZE);
|
|
fprintf(stderr, "%s", errorbuffer);
|
|
}
|
|
|
|
rc = MAXSCALE_BADCONFIG;
|
|
goto return_main;
|
|
}
|
|
|
|
|
|
/** Use the cache dir for the mysql folder of the embedded library */
|
|
snprintf(mysql_home, PATH_MAX, "%s/mysql", get_cachedir());
|
|
mysql_home[PATH_MAX] = '\0';
|
|
setenv("MYSQL_HOME", mysql_home, 1);
|
|
|
|
|
|
/**
|
|
* Init Log Manager for MaxScale.
|
|
* The skygw_logmanager_init expects to take arguments as passed to main
|
|
* and proesses them with getopt, therefore we need to give it a dummy
|
|
* argv[0]
|
|
*/
|
|
{
|
|
char buf[1024];
|
|
char *argv[8];
|
|
bool succp;
|
|
|
|
if (mkdir(get_logdir(), 0777) != 0 && errno != EEXIST)
|
|
{
|
|
fprintf(stderr,
|
|
"Error: Cannot create log directory: %s\n",
|
|
default_logdir);
|
|
goto return_main;
|
|
}
|
|
|
|
argv[0] = "MaxScale";
|
|
argv[1] = "-j";
|
|
argv[2] = get_logdir();
|
|
|
|
if (!(*syslog_enabled))
|
|
{
|
|
printf("Syslog logging is disabled.\n");
|
|
}
|
|
|
|
if (!(*maxscalelog_enabled))
|
|
{
|
|
printf("MaxScale logging is disabled.\n");
|
|
}
|
|
logmanager_enable_syslog(*syslog_enabled);
|
|
logmanager_enable_maxscalelog(*maxscalelog_enabled);
|
|
|
|
if (logtofile)
|
|
{
|
|
argv[3] = "-l"; /*< write to syslog */
|
|
/** Logs that should be syslogged */
|
|
argv[4] = "LOGFILE_MESSAGE,LOGFILE_ERROR"
|
|
"LOGFILE_DEBUG,LOGFILE_TRACE";
|
|
argv[5] = NULL;
|
|
succp = skygw_logmanager_init(5, argv);
|
|
}
|
|
else
|
|
{
|
|
argv[3] = "-s"; /*< store to shared memory */
|
|
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< to shm */
|
|
argv[5] = "-l"; /*< write to syslog */
|
|
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< to syslog */
|
|
argv[7] = NULL;
|
|
succp = skygw_logmanager_init(7, argv);
|
|
}
|
|
|
|
if (!succp)
|
|
{
|
|
rc = MAXSCALE_BADCONFIG;
|
|
goto return_main;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Set a data directory for the mysqld library, we use
|
|
* a unique directory name to avoid clauses if multiple
|
|
* instances of the gateway are being run on the same
|
|
* machine.
|
|
*/
|
|
|
|
snprintf(datadir, PATH_MAX, "%s", get_datadir());
|
|
datadir[PATH_MAX] = '\0';
|
|
if (mkdir(datadir, 0777) != 0){
|
|
|
|
if (errno != EEXIST){
|
|
char errbuf[STRERROR_BUFLEN];
|
|
fprintf(stderr,
|
|
"Error: Cannot create data directory '%s': %d %s\n",
|
|
datadir, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
goto return_main;
|
|
}
|
|
}
|
|
|
|
snprintf(datadir, PATH_MAX, "%s/data%d", get_datadir(), getpid());
|
|
|
|
if (mkdir(datadir, 0777) != 0){
|
|
|
|
if (errno != EEXIST){
|
|
char errbuf[STRERROR_BUFLEN];
|
|
fprintf(stderr,
|
|
"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());
|
|
}
|
|
|
|
LOGIF(LM,
|
|
(skygw_log_write_flush(
|
|
LOGFILE_MESSAGE,
|
|
"Configuration file: %s",
|
|
cnf_file_path)));
|
|
LOGIF(LM,
|
|
(skygw_log_write_flush(
|
|
LOGFILE_MESSAGE,
|
|
"Log directory: %s/",
|
|
get_logdir())));
|
|
LOGIF(LM,
|
|
(skygw_log_write_flush(
|
|
LOGFILE_MESSAGE,
|
|
"Data directory: %s",
|
|
get_datadir())));
|
|
LOGIF(LM,
|
|
(skygw_log_write_flush(LOGFILE_MESSAGE,
|
|
"Module directory: %s",
|
|
get_libdir())));
|
|
LOGIF(LM,
|
|
(skygw_log_write_flush(LOGFILE_MESSAGE,
|
|
"Service cache: %s",
|
|
get_cachedir())));
|
|
|
|
/*< Update the server options */
|
|
for (i = 0; server_options[i]; i++)
|
|
{
|
|
if (!strcmp(server_options[i], "--datadir="))
|
|
{
|
|
snprintf(datadir_arg, 10+PATH_MAX+1, "--datadir=%s", datadir);
|
|
server_options[i] = datadir_arg;
|
|
}
|
|
else if (!strcmp(server_options[i], "--language="))
|
|
{
|
|
snprintf(language_arg,
|
|
11+PATH_MAX+1,
|
|
"--language=%s",
|
|
get_langdir());
|
|
server_options[i] = language_arg;
|
|
}
|
|
}
|
|
|
|
if (mysql_library_init(num_elements, server_options, server_groups))
|
|
{
|
|
if (!daemon_mode)
|
|
{
|
|
char* fprerr = "Failed to initialise the MySQL library. "
|
|
"Exiting.";
|
|
print_log_n_stderr(false, true, fprerr, fprerr, 0);
|
|
|
|
if (mysql_errno(NULL) == 2000)
|
|
{
|
|
if (strncmp(mysql_error(NULL),
|
|
"Unknown MySQL error",
|
|
strlen("Unknown MySQL error")) != 0)
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : MySQL Error should "
|
|
"be \"Unknown MySQL error\" "
|
|
"instead of\n* %s\n* Hint "
|
|
":\n* Ensure that you have "
|
|
"MySQL error messages file, errmsg.sys in "
|
|
"\n* %s/mysql\n* Ensure that Embedded "
|
|
"Server Library version matches "
|
|
"exactly with that of the errmsg.sys "
|
|
"file.\n*\n",
|
|
mysql_error(NULL),
|
|
get_langdir());
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"*\n* Error : MySQL Error %d, %s"
|
|
"\n*\n",
|
|
mysql_errno(NULL),
|
|
mysql_error(NULL));
|
|
}
|
|
}
|
|
}
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : mysql_library_init failed. It is a "
|
|
"mandatory component, required by router services and "
|
|
"the MaxScale core. Error %d, %s, %s : %d. Exiting.",
|
|
mysql_errno(NULL),
|
|
mysql_error(NULL),
|
|
__FILE__,
|
|
__LINE__);
|
|
rc = MAXSCALE_NOLIBRARY;
|
|
goto return_main;
|
|
}
|
|
libmysqld_started = TRUE;
|
|
|
|
if (!config_load(cnf_file_path))
|
|
{
|
|
char* fprerr = "Failed to load MaxScale configuration "
|
|
"file. Exiting. See the error log for details.";
|
|
print_log_n_stderr(false, !daemon_mode, fprerr, fprerr, 0);
|
|
skygw_log_write_flush(
|
|
LOGFILE_ERROR,
|
|
"Error : Failed to load MaxScale configuration file %s. "
|
|
"Exiting.",
|
|
cnf_file_path);
|
|
rc = MAXSCALE_BADCONFIG;
|
|
goto return_main;
|
|
}
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"MariaDB Corporation MaxScale %s (C) MariaDB Corporation Ab 2013-2015",
|
|
MAXSCALE_VERSION)));
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"MaxScale is running in process %i",
|
|
getpid())));
|
|
|
|
/** 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;
|
|
}
|
|
|
|
/* Init MaxScale poll system */
|
|
poll_init();
|
|
|
|
/**
|
|
* Init mysql thread context for main thread as well. Needed when users
|
|
* are queried from backends.
|
|
*/
|
|
mysql_thread_init();
|
|
|
|
/** Start the services that were created above */
|
|
n_services = serviceStartAll();
|
|
|
|
if (n_services == 0)
|
|
{
|
|
char* logerr = "Failed to start any MaxScale services. Exiting.";
|
|
print_log_n_stderr(true, !daemon_mode, logerr, logerr, 0);
|
|
rc = MAXSCALE_NOSERVICES;
|
|
goto return_main;
|
|
}
|
|
/*<
|
|
* Start periodic log flusher thread.
|
|
*/
|
|
log_flush_timeout_ms = 1000;
|
|
log_flush_thr = thread_start(
|
|
log_flush_cb,
|
|
(void *)&log_flush_timeout_ms);
|
|
|
|
/*
|
|
* Start the housekeeper thread
|
|
*/
|
|
hkinit();
|
|
|
|
/*<
|
|
* Start the polling threads, note this is one less than is
|
|
* configured as the main thread will also poll.
|
|
*/
|
|
n_threads = config_threadcount();
|
|
threads = (void **)calloc(n_threads, sizeof(void *));
|
|
/*<
|
|
* Start server threads.
|
|
*/
|
|
for (thread_id = 0; thread_id < n_threads - 1; thread_id++)
|
|
{
|
|
threads[thread_id] = thread_start(poll_waitevents, (void *)(thread_id + 1));
|
|
}
|
|
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
|
|
"MaxScale started with %d server threads.",
|
|
config_threadcount())));
|
|
/**
|
|
* Successful start, notify the parent process that it can exit.
|
|
*/
|
|
ss_dassert(rc == MAXSCALE_SHUTDOWN);
|
|
if (daemon_mode)
|
|
write_child_exit_code(daemon_pipe[1], rc);
|
|
|
|
MaxScaleStarted = time(0);
|
|
/*<
|
|
* Serve clients.
|
|
*/
|
|
poll_waitevents((void *)0);
|
|
|
|
/*<
|
|
* Wait server threads' completion.
|
|
*/
|
|
for (thread_id = 0; thread_id < n_threads - 1; thread_id++)
|
|
{
|
|
thread_wait(threads[thread_id]);
|
|
}
|
|
/*<
|
|
* Wait the flush thread.
|
|
*/
|
|
thread_wait(log_flush_thr);
|
|
|
|
/*< Stop all the monitors */
|
|
monitorStopAll();
|
|
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"MaxScale is shutting down.")));
|
|
/** Release mysql thread context*/
|
|
mysql_thread_end();
|
|
|
|
datadir_cleanup();
|
|
LOGIF(LM, (skygw_log_write(
|
|
LOGFILE_MESSAGE,
|
|
"MaxScale shutdown completed.")));
|
|
|
|
unload_all_modules();
|
|
/* Remove Pidfile */
|
|
unlock_pidfile();
|
|
unlink_pidfile();
|
|
|
|
return_main:
|
|
|
|
if (daemon_mode && rc != MAXSCALE_SHUTDOWN)
|
|
{
|
|
/** Notify the parent process that an error has occurred */
|
|
write_child_exit_code(daemon_pipe[1], rc);
|
|
}
|
|
|
|
if (threads)
|
|
free(threads);
|
|
if (cnf_file_path)
|
|
free(cnf_file_path);
|
|
|
|
return rc;
|
|
} /*< End of main */
|
|
|
|
/*<
|
|
* Shutdown MaxScale server
|
|
*/
|
|
void
|
|
shutdown_server()
|
|
{
|
|
service_shutdown();
|
|
poll_shutdown();
|
|
hkshutdown();
|
|
memlog_flush_all();
|
|
log_flush_shutdown();
|
|
}
|
|
|
|
static void log_flush_shutdown(void)
|
|
{
|
|
do_exit = TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Periodic log flusher to ensure that log buffers are
|
|
* written to log even if block buffer used for temporarily
|
|
* storing log contents are not full.
|
|
*
|
|
* @param arg - Flush frequency in milliseconds
|
|
*
|
|
*
|
|
*/
|
|
static void log_flush_cb(
|
|
void* arg)
|
|
{
|
|
ssize_t timeout_ms = *(ssize_t *)arg;
|
|
struct timespec ts1;
|
|
|
|
ts1.tv_sec = timeout_ms/1000;
|
|
ts1.tv_nsec = (timeout_ms%1000) * 1000000;
|
|
|
|
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
|
|
"Started MaxScale log flusher.")));
|
|
while (!do_exit) {
|
|
skygw_log_flush(LOGFILE_ERROR);
|
|
skygw_log_flush(LOGFILE_MESSAGE);
|
|
skygw_log_flush(LOGFILE_TRACE);
|
|
skygw_log_flush(LOGFILE_DEBUG);
|
|
nanosleep(&ts1, NULL);
|
|
}
|
|
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
|
|
"Finished MaxScale log flusher.")));
|
|
}
|
|
|
|
static void unlock_pidfile()
|
|
{
|
|
if (pidfd != PIDFD_CLOSED)
|
|
{
|
|
if (flock(pidfd, LOCK_UN|LOCK_NB) != 0)
|
|
{
|
|
char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
|
|
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))
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
fprintf(stderr,
|
|
"MaxScale failed to remove pidfile %s: error %d, %s\n",
|
|
pidfile,
|
|
errno,
|
|
strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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 */
|
|
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[b < sizeof(pidbuf) ? b : sizeof(pidbuf) - 1] = '\0';
|
|
pid = strtol(pidbuf, NULL, 0);
|
|
|
|
if (pid < 1)
|
|
{
|
|
/** Bad PID */
|
|
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 (kill(pid, 0) == -1)
|
|
{
|
|
if (errno == ESRCH)
|
|
{
|
|
/** no such process, old PID file */
|
|
if (lock_failed)
|
|
{
|
|
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
|
|
{
|
|
char* logerr = "Failed to check the existence of process %d read from file '%s'";
|
|
snprintf(logbuf, sizeof(logbuf), logerr, pid, pathbuf);
|
|
print_log_n_stderr(true, true, logbuf, logbuf, errno);
|
|
unlock_pidfile();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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
|
|
{
|
|
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() {
|
|
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) {
|
|
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)) {
|
|
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)) {
|
|
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;
|
|
}
|
|
|
|
int
|
|
MaxScaleUptime()
|
|
{
|
|
return time(0) - MaxScaleStarted;
|
|
}
|
|
|
|
bool handle_path_arg(char** dest, char* path, 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 = strdup(pathbuffer);
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
print_log_n_stderr(true, true, errstr, errstr, 0);
|
|
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)
|
|
{
|
|
skygw_log_set_augmentation(atoi(value));
|
|
|
|
augmentation_set = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pre-parse the MaxScale.cnf for config, log and module directories.
|
|
* @param data Parameter passed by inih
|
|
* @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)
|
|
{
|
|
GATEWAY_CONF* 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 (strcmp(name, "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, "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, "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, "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, "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, "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, "syslog") == 0)
|
|
{
|
|
cnf->syslog = config_truth_value((char*)value);
|
|
}
|
|
else if (strcmp(name, "maxlog") == 0)
|
|
{
|
|
cnf->maxlog = config_truth_value((char*)value);
|
|
}
|
|
else if (strcmp(name, "log_augmentation") == 0)
|
|
{
|
|
set_log_augmentation(value);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int set_user(char* user)
|
|
{
|
|
errno = 0;
|
|
struct passwd *pwname;
|
|
int rval;
|
|
|
|
pwname = getpwnam(user);
|
|
if (pwname == NULL)
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
printf("Error: Failed to retrieve user information for '%s': %d %s\n",
|
|
user, errno, errno == 0 ? "User not found" : strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
return -1;
|
|
}
|
|
|
|
rval = setgid(pwname->pw_gid);
|
|
if (rval != 0)
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
printf("Error: Failed to change group to '%d': %d %s\n",
|
|
pwname->pw_gid, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
return rval;
|
|
}
|
|
|
|
rval = setuid(pwname->pw_uid);
|
|
if (rval != 0)
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
printf("Error: Failed to change user to '%s': %d %s\n",
|
|
pwname->pw_name, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
return rval;
|
|
}
|
|
if (prctl(PR_GET_DUMPABLE) == 0)
|
|
{
|
|
if (prctl(PR_SET_DUMPABLE , 1) == -1)
|
|
{
|
|
char errbuf[STRERROR_BUFLEN];
|
|
printf("Error: Failed to set dumpable flag on for the process '%s': %d %s\n",
|
|
pwname->pw_name, errno, strerror_r(errno, errbuf, sizeof(errbuf)));
|
|
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 */
|
|
write(fd, &code, sizeof (int));
|
|
close(fd);
|
|
}
|
|
|