Merge branch 'develop' into binlog_server_waitdata_encryption

This commit is contained in:
MassimilianoPinto
2016-11-22 10:39:49 +01:00
46 changed files with 2730 additions and 985 deletions

View File

@ -1,4 +1,4 @@
add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c)
add_library(maxscale-common SHARED adminusers.c alloc.c authenticator.c atomic.c buffer.c config.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c hashtable.c hint.c housekeeper.c listmanager.c load_utils.c log_manager.cc maxscale_pcre2.c memlog.c misc.c mlist.c modutil.c monitor.c queuemanager.c query_classifier.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c skygw_utils.cc statistics.c listener.c gw_ssl.c mysql_utils.c mysql_binlog.c modulecmd.c)
target_link_libraries(maxscale-common ${MARIADB_CONNECTOR_LIBRARIES} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ssl pthread crypt dl crypto inih z rt m stdc++)
@ -12,7 +12,7 @@ add_dependencies(maxscale-common pcre2 connector-c)
set_target_properties(maxscale-common PROPERTIES VERSION "1.0.0")
install_module(maxscale-common core)
add_executable(maxscale gateway.c)
add_executable(maxscale gateway.cc)
add_dependencies(maxscale pcre2)
if(WITH_JEMALLOC)

View File

@ -2010,7 +2010,7 @@ config_truth_value(char *str)
{
return 0;
}
MXS_ERROR("Not a boolean value: %s", str);
return -1;
}

View File

@ -3294,7 +3294,14 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name)
return -1;
}
if (listen(listener_socket, 10 * SOMAXCONN) != 0)
/**
* The use of INT_MAX for backlog length in listen() allows the end-user to
* control the backlog length with the net.ipv4.tcp_max_syn_backlog kernel
* option since the parameter is silently truncated to the configured value.
*
* @see man 2 listen
*/
if (listen(listener_socket, INT_MAX) != 0)
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to start listening on '%s' with protocol '%s': %d, %s",

View File

@ -159,7 +159,7 @@ static void write_footer(void);
static int ntfw_cb(const char*, const struct stat*, int, struct FTW*);
static bool file_is_readable(const char* absolute_pathname);
static bool file_is_writable(const char* absolute_pathname);
bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr);
bool handle_path_arg(char** dest, const char* path, const char* arg, bool rd, bool wr);
static void set_log_augmentation(const char* value);
static void usage(void);
static char* get_expanded_pathname(
@ -218,7 +218,8 @@ struct CRYPTO_dynlock_value
*/
static struct CRYPTO_dynlock_value *ssl_create_dynlock(const char* file, int line)
{
struct CRYPTO_dynlock_value* lock = MXS_MALLOC(sizeof(struct CRYPTO_dynlock_value));
struct CRYPTO_dynlock_value* lock =
(struct CRYPTO_dynlock_value*) MXS_MALLOC(sizeof(struct CRYPTO_dynlock_value));
if (lock)
{
spinlock_init(&lock->lock);
@ -383,9 +384,9 @@ sigfatal_handler(int i)
}
fatal_handling = 1;
GATEWAY_CONF* cnf = config_get_global_options();
fprintf(stderr, "\n\nMaxScale "MAXSCALE_VERSION" received fatal signal %d\n", i);
fprintf(stderr, "\n\nMaxScale " MAXSCALE_VERSION " received fatal signal %d\n", i);
MXS_ALERT("Fatal: MaxScale "MAXSCALE_VERSION" received fatal signal %d. Attempting backtrace.", i);
MXS_ALERT("Fatal: MaxScale " MAXSCALE_VERSION " received fatal signal %d. Attempting backtrace.", i);
MXS_ALERT("Commit ID: %s System name: %s "
"Release string: %s",
@ -1298,6 +1299,8 @@ int main(int argc, char **argv)
bool config_check = false;
bool to_stdout = false;
void (*exitfunp[4])(void) = { mxs_log_finish, cleanup_process_datadir, write_footer, NULL };
GATEWAY_CONF* cnf = NULL;
int numlocks = 0;
*syslog_enabled = 1;
*maxlog_enabled = 1;
@ -1321,7 +1324,7 @@ int main(int argc, char **argv)
if (l != 0)
{
char* fprerr = "Failed to register exit functions for MaxScale";
const char* fprerr = "Failed to register exit functions for MaxScale";
print_log_n_stderr(false, true, NULL, fprerr, 0);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1351,11 +1354,12 @@ int main(int argc, char **argv)
}
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.";
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;
@ -1391,11 +1395,12 @@ int main(int argc, char **argv)
}
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.";
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;
@ -1618,14 +1623,14 @@ int main(int argc, char **argv)
if (nread == -1)
{
char* logerr = "Failed to read data from child process pipe.";
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 */
char* logerr = "No data read from child process pipe.";
const char* logerr = "No data read from child process pipe.";
print_log_n_stderr(true, true, logerr, logerr, 0);
exit(MAXSCALE_INTERNALERROR);
}
@ -1652,8 +1657,7 @@ int main(int argc, char **argv)
if (eno != 0)
{
char* logerr = "Failed to initialise signal mask for MaxScale. "
"Exiting.";
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;
@ -1661,7 +1665,7 @@ int main(int argc, char **argv)
if (!utils_init())
{
char* logerr = "Failed to initialise utility library.";
const char* logerr = "Failed to initialise utility library.";
print_log_n_stderr(true, true, logerr, logerr, eno);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1670,7 +1674,7 @@ int main(int argc, char **argv)
/** OpenSSL initialization */
if (!HAVE_OPENSSL_THREADS)
{
char* logerr = "OpenSSL library does not support multi-threading";
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;
@ -1679,8 +1683,8 @@ int main(int argc, char **argv)
SSL_load_error_strings();
OPENSSL_add_all_algorithms_noconf();
int numlocks = CRYPTO_num_locks();
if ((ssl_locks = MXS_MALLOC(sizeof(SPINLOCK) * (numlocks + 1))) == NULL)
numlocks = CRYPTO_num_locks();
if ((ssl_locks = (SPINLOCK*)MXS_MALLOC(sizeof(SPINLOCK) * (numlocks + 1))) == NULL)
{
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1705,10 +1709,12 @@ int main(int argc, char **argv)
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.";
const char* fprerr =
"Failed to register exit function for\n* "
"embedded MySQL library.\n* Exiting.";
const 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;
@ -1837,7 +1843,7 @@ int main(int argc, char **argv)
if (!config_load(cnf_file_path))
{
char* fprerr =
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);
@ -1848,12 +1854,12 @@ int main(int argc, char **argv)
goto return_main;
}
GATEWAY_CONF* cnf = config_get_global_options();
cnf = config_get_global_options();
ss_dassert(cnf);
if (!qc_init(cnf->qc_name, cnf->qc_args))
{
char* logerr = "Failed to initialise query classifier library.";
const char* logerr = "Failed to initialise query classifier library.";
print_log_n_stderr(true, true, logerr, logerr, eno);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1870,8 +1876,7 @@ int main(int argc, char **argv)
{
if (!daemon_mode)
{
char* fprerr = "Failed to initialise the MySQL library. "
"Exiting.";
const char* fprerr = "Failed to initialise the MySQL library. Exiting.";
print_log_n_stderr(false, true, fprerr, fprerr, 0);
if (mysql_errno(NULL) == 2000)
@ -1950,7 +1955,7 @@ int main(int argc, char **argv)
if (n_services == 0)
{
char* logerr = "Failed to start all MaxScale services. Exiting.";
const char* logerr = "Failed to start all MaxScale services. Exiting.";
print_log_n_stderr(true, true, logerr, logerr, 0);
rc = MAXSCALE_NOSERVICES;
goto return_main;
@ -1962,7 +1967,7 @@ int main(int argc, char **argv)
if (thread_start(&log_flush_thr, log_flush_cb, (void *) &log_flush_timeout_ms) == NULL)
{
char* logerr = "Failed to start log flushing thread.";
const char* logerr = "Failed to start log flushing thread.";
print_log_n_stderr(true, true, logerr, logerr, 0);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1973,7 +1978,7 @@ int main(int argc, char **argv)
*/
if (!hkinit())
{
char* logerr = "Failed to start housekeeper thread.";
const char* logerr = "Failed to start housekeeper thread.";
print_log_n_stderr(true, true, logerr, logerr, 0);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -1993,7 +1998,7 @@ int main(int argc, char **argv)
if (thread_start(&threads[thread_id], worker_thread_main,
(void *)(thread_id + 1)) == NULL)
{
char* logerr = "Failed to start worker thread.";
const char* logerr = "Failed to start worker thread.";
print_log_n_stderr(true, true, logerr, logerr, 0);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
@ -2133,7 +2138,7 @@ static void unlock_pidfile()
if (flock(pidfd, LOCK_UN | LOCK_NB) != 0)
{
char logbuf[STRING_BUFFER_SIZE + PATH_MAX];
char* logerr = "Failed to unlock PID file '%s'.";
const char* logerr = "Failed to unlock PID file '%s'.";
snprintf(logbuf, sizeof(logbuf), logerr, pidfile);
print_log_n_stderr(true, true, logbuf, logbuf, errno);
}
@ -2189,7 +2194,7 @@ bool pid_file_exists()
if ((fd = open(pathbuf, O_RDWR)) == -1)
{
char* logerr = "Failed to open PID file '%s'.";
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;
@ -2198,7 +2203,7 @@ bool pid_file_exists()
{
if (errno != EWOULDBLOCK)
{
char* logerr = "Failed to lock PID file '%s'.";
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);
@ -2212,7 +2217,7 @@ bool pid_file_exists()
if (b == -1)
{
char* logerr = "Failed to read from PID file '%s'.";
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();
@ -2221,24 +2226,26 @@ bool pid_file_exists()
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.";
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[b < sizeof(pidbuf) ? b : sizeof(pidbuf) - 1] = '\0';
pidbuf[(size_t)b < sizeof(pidbuf) ? (size_t)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.";
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();
@ -2252,7 +2259,7 @@ bool pid_file_exists()
/** no such process, old PID file */
if (lock_failed)
{
char* logerr =
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.";
@ -2264,7 +2271,7 @@ bool pid_file_exists()
}
else
{
char* logerr = "Failed to check the existence of process %d read from file '%s'";
const 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();
@ -2272,7 +2279,7 @@ bool pid_file_exists()
}
else
{
char* logerr =
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.";
@ -2283,8 +2290,9 @@ bool pid_file_exists()
}
else
{
char* logerr = "Cannot open PID file '%s', no read permissions. "
"Please confirm that the user running MaxScale has read permissions on the file.";
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);
}
@ -2313,7 +2321,7 @@ static int write_pid_file()
fd = open(pidfile, O_WRONLY | O_CREAT, 0777);
if (fd == -1)
{
char* logerr = "Failed to open PID file '%s'.";
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;
@ -2342,7 +2350,7 @@ static int write_pid_file()
/* truncate pidfile content */
if (ftruncate(pidfd, 0))
{
char* logerr = "MaxScale failed to truncate PID file '%s'.";
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();
@ -2353,7 +2361,7 @@ static int write_pid_file()
if (pwrite(pidfd, pidstr, strlen(pidstr), 0) != (ssize_t)strlen(pidstr))
{
char* logerr = "MaxScale failed to write into PID file '%s'.";
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();
@ -2364,7 +2372,7 @@ static int write_pid_file()
return 0;
}
bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr)
bool handle_path_arg(char** dest, const char* path, const char* arg, bool rd, bool wr)
{
char pathbuffer[PATH_MAX + 2];
char* errstr;

667
server/core/modulecmd.c Normal file
View File

@ -0,0 +1,667 @@
/*
* 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/bsl.
*
* Change Date: 2019-07-01
*
* 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.
*/
#include <maxscale/alloc.h>
#include <maxscale/config.h>
#include <maxscale/modulecmd.h>
#include <maxscale/pcre2.h>
#include <maxscale/platform.h>
#include <maxscale/spinlock.h>
/** Size of the error buffer */
#define MODULECMD_ERRBUF_SIZE 512
/** Thread local error buffer */
thread_local char *errbuf = NULL;
/** Parameter passed to functions that do not always expect arguments */
static const MODULECMD_ARG MODULECMD_NO_ARGUMENTS = {0, NULL};
/**
* A registered domain
*/
typedef struct modulecmd_domain
{
char *domain; /**< The domain */
MODULECMD *commands; /**< List of registered commands */
struct modulecmd_domain *next; /**< Next domain */
} MODULECMD_DOMAIN;
/**
* Internal functions
*/
/** The global list of registered domains */
static MODULECMD_DOMAIN *modulecmd_domains = NULL;
static SPINLOCK modulecmd_lock = SPINLOCK_INIT;
static inline void prepare_error()
{
if (errbuf == NULL)
{
errbuf = MXS_MALLOC(MODULECMD_ERRBUF_SIZE);
MXS_ABORT_IF_NULL(errbuf);
errbuf[0] = '\0';
}
}
/**
* @brief Reset error message
*
* This should be the first function called in every API function that can
* generate errors.
*/
static void reset_error()
{
prepare_error();
errbuf[0] = '\0';
}
static void report_argc_mismatch(const MODULECMD *cmd, int argc)
{
if (cmd->arg_count_min == cmd->arg_count_max)
{
modulecmd_set_error("Expected %d arguments, got %d.", cmd->arg_count_min, argc);
}
else
{
modulecmd_set_error("Expected between %d and %d arguments, got %d.", cmd->arg_count_min, cmd->arg_count_max,
argc);
}
}
static MODULECMD_DOMAIN* domain_create(const char *domain)
{
MODULECMD_DOMAIN *rval = MXS_MALLOC(sizeof(*rval));
char *dm = MXS_STRDUP(domain);
if (rval && dm)
{
rval->domain = dm;
rval->commands = NULL;
rval->next = NULL;
}
else
{
MXS_FREE(rval);
MXS_FREE(dm);
rval = NULL;
}
return rval;
}
static void domain_free(MODULECMD_DOMAIN *dm)
{
if (dm)
{
MXS_FREE(dm->domain);
MXS_FREE(dm);
}
}
static MODULECMD_DOMAIN* get_or_create_domain(const char *domain)
{
MODULECMD_DOMAIN *dm;
for (dm = modulecmd_domains; dm; dm = dm->next)
{
if (strcmp(dm->domain, domain) == 0)
{
return dm;
}
}
if ((dm = domain_create(domain)))
{
dm->next = modulecmd_domains;
modulecmd_domains = dm;
}
return dm;
}
static MODULECMD* command_create(const char *identifier, const char *domain,
MODULECMDFN entry_point, int argc,
modulecmd_arg_type_t* argv)
{
MODULECMD *rval = MXS_MALLOC(sizeof(*rval));
char *id = MXS_STRDUP(identifier);
char *dm = MXS_STRDUP(domain);
modulecmd_arg_type_t *types = MXS_MALLOC(sizeof(*types) * argc);
if (rval && id && dm && types)
{
int argc_min = 0;
for (int i = 0; i < argc; i++)
{
if (MODULECMD_ARG_IS_REQUIRED(&argv[i]))
{
argc_min++;
}
types[i] = argv[i];
}
rval->func = entry_point;
rval->identifier = id;
rval->domain = dm;
rval->arg_types = types;
rval->arg_count_min = argc_min;
rval->arg_count_max = argc;
rval->next = NULL;
}
else
{
MXS_FREE(rval);
MXS_FREE(id);
MXS_FREE(dm);
MXS_FREE(types);
rval = NULL;
}
return rval;
}
static void command_free(MODULECMD *cmd)
{
if (cmd)
{
MXS_FREE(cmd->identifier);
MXS_FREE(cmd->domain);
MXS_FREE(cmd->arg_types);
MXS_FREE(cmd);
}
}
static bool domain_has_command(MODULECMD_DOMAIN *dm, const char *id)
{
for (MODULECMD *cmd = dm->commands; cmd; cmd = cmd->next)
{
if (strcmp(cmd->identifier, id) == 0)
{
return true;
}
}
return false;
}
static bool process_argument(modulecmd_arg_type_t *type, const void* value,
struct arg_node *arg, const char **err)
{
bool rval = false;
if (!MODULECMD_ARG_IS_REQUIRED(type) && value == NULL)
{
arg->type.type = MODULECMD_ARG_NONE;
rval = true;
}
else if (value)
{
switch (MODULECMD_GET_TYPE(type))
{
case MODULECMD_ARG_NONE:
arg->type.type = MODULECMD_ARG_NONE;
rval = true;
break;
case MODULECMD_ARG_STRING:
if ((arg->value.string = MXS_STRDUP((char*)value)))
{
arg->type.type = MODULECMD_ARG_STRING;
rval = true;
}
else
{
*err = "memory allocation failed";
}
break;
case MODULECMD_ARG_BOOLEAN:
{
int truthval = config_truth_value((char*)value);
if (truthval != -1)
{
arg->value.boolean = truthval;
arg->type.type = MODULECMD_ARG_BOOLEAN;
rval = true;
}
else
{
*err = "not a boolean value";
}
}
break;
case MODULECMD_ARG_SERVICE:
if ((arg->value.service = service_find((char*)value)))
{
arg->type.type = MODULECMD_ARG_SERVICE;
rval = true;
}
else
{
*err = "service not found";
}
break;
case MODULECMD_ARG_SERVER:
if ((arg->value.server = server_find_by_unique_name((char*)value)))
{
arg->type.type = MODULECMD_ARG_SERVER;
rval = true;
}
else
{
*err = "server not found";
}
break;
case MODULECMD_ARG_SESSION:
arg->type.type = MODULECMD_ARG_SESSION;
arg->value.session = (SESSION*)strtol((char*)value, NULL, 0);
rval = true;
break;
case MODULECMD_ARG_SESSION_PTR:
arg->type.type = MODULECMD_ARG_SESSION_PTR;
arg->value.session = (SESSION*)value;
rval = true;
break;
case MODULECMD_ARG_DCB:
arg->type.type = MODULECMD_ARG_DCB;
arg->value.dcb = (DCB*)strtol((char*)value, NULL, 0);
rval = true;
break;
case MODULECMD_ARG_DCB_PTR:
arg->type.type = MODULECMD_ARG_DCB_PTR;
arg->value.dcb = (DCB*)value;
rval = true;
break;
case MODULECMD_ARG_MONITOR:
if ((arg->value.monitor = monitor_find((char*)value)))
{
arg->type.type = MODULECMD_ARG_MONITOR;
rval = true;
}
else
{
*err = "monitor not found";
}
break;
case MODULECMD_ARG_FILTER:
if ((arg->value.filter = filter_find((char*)value)))
{
arg->type.type = MODULECMD_ARG_FILTER;
rval = true;
}
else
{
*err = "filter not found";
}
break;
case MODULECMD_ARG_OUTPUT:
arg->type.type = MODULECMD_ARG_OUTPUT;
arg->value.dcb = (DCB*)value;
rval = true;
break;
default:
ss_dassert(false);
MXS_ERROR("Undefined argument type: %0lx", type->type);
*err = "internal error";
break;
}
}
else
{
*err = "required argument";
}
return rval;
}
static MODULECMD_ARG* modulecmd_arg_create(int argc)
{
MODULECMD_ARG* arg = MXS_MALLOC(sizeof(*arg));
struct arg_node *argv = MXS_CALLOC(argc, sizeof(*argv));
if (arg && argv)
{
arg->argc = argc;
arg->argv = argv;
}
else
{
MXS_FREE(argv);
MXS_FREE(arg);
arg = NULL;
}
return arg;
}
static void free_argument(struct arg_node *arg)
{
switch (arg->type.type)
{
case MODULECMD_ARG_STRING:
MXS_FREE(arg->value.string);
break;
default:
break;
}
}
/**
* Public functions declared in modulecmd.h
*/
bool modulecmd_register_command(const char *domain, const char *identifier,
MODULECMDFN entry_point, int argc, modulecmd_arg_type_t *argv)
{
reset_error();
bool rval = false;
spinlock_acquire(&modulecmd_lock);
MODULECMD_DOMAIN *dm = get_or_create_domain(domain);
if (dm)
{
if (domain_has_command(dm, identifier))
{
modulecmd_set_error("Command registered more than once: %s::%s", domain, identifier);
MXS_ERROR("Command registered more than once: %s::%s", domain, identifier);
}
else
{
MODULECMD *cmd = command_create(identifier, domain, entry_point, argc, argv);
if (cmd)
{
cmd->next = dm->commands;
dm->commands = cmd;
rval = true;
}
}
}
spinlock_release(&modulecmd_lock);
return rval;
}
const MODULECMD* modulecmd_find_command(const char *domain, const char *identifier)
{
reset_error();
MODULECMD *rval = NULL;
spinlock_acquire(&modulecmd_lock);
for (MODULECMD_DOMAIN *dm = modulecmd_domains; dm; dm = dm->next)
{
if (strcmp(domain, dm->domain) == 0)
{
for (MODULECMD *cmd = dm->commands; cmd; cmd = cmd->next)
{
if (strcmp(cmd->identifier, identifier) == 0)
{
rval = cmd;
break;
}
}
break;
}
}
spinlock_release(&modulecmd_lock);
if (rval == NULL)
{
modulecmd_set_error("Command not found: %s::%s", domain, identifier);
}
return rval;
}
MODULECMD_ARG* modulecmd_arg_parse(const MODULECMD *cmd, int argc, const void **argv)
{
reset_error();
MODULECMD_ARG* arg = NULL;
if (argc >= cmd->arg_count_min && argc <= cmd->arg_count_max)
{
arg = modulecmd_arg_create(cmd->arg_count_max);
bool error = false;
if (arg)
{
for (int i = 0; i < cmd->arg_count_max && i < argc; i++)
{
const char *err = "";
if (!process_argument(&cmd->arg_types[i], argv[i], &arg->argv[i], &err))
{
error = true;
modulecmd_set_error("Argument %d, %s: %s", i + 1, err, argv[i] ? argv[i] : "No argument given");
break;
}
}
if (error)
{
modulecmd_arg_free(arg);
arg = NULL;
}
}
}
else
{
report_argc_mismatch(cmd, argc);
}
return arg;
}
void modulecmd_arg_free(MODULECMD_ARG* arg)
{
if (arg)
{
for (int i = 0; i < arg->argc; i++)
{
free_argument(&arg->argv[i]);
}
MXS_FREE(arg->argv);
MXS_FREE(arg);
}
}
bool modulecmd_call_command(const MODULECMD *cmd, const MODULECMD_ARG *args)
{
bool rval = false;
reset_error();
if (cmd->arg_count_min > 0 && args == NULL)
{
report_argc_mismatch(cmd, 0);
}
else
{
if (args == NULL)
{
args = &MODULECMD_NO_ARGUMENTS;
}
rval = cmd->func(args);
}
return rval;
}
void modulecmd_set_error(const char *format, ...)
{
prepare_error();
va_list list;
va_start(list, format);
vsnprintf(errbuf, MODULECMD_ERRBUF_SIZE, format, list);
va_end(list);
}
const char* modulecmd_get_error()
{
prepare_error();
return errbuf;
}
bool modulecmd_foreach(const char *domain_re, const char *ident_re,
bool(*fn)(const MODULECMD *cmd, void *data), void *data)
{
bool rval = true;
bool stop = false;
spinlock_acquire(&modulecmd_lock);
for (MODULECMD_DOMAIN *domain = modulecmd_domains; domain && rval && !stop; domain = domain->next)
{
int err;
mxs_pcre2_result_t d_res = domain_re ?
mxs_pcre2_simple_match(domain_re, domain->domain, 0, &err) :
MXS_PCRE2_MATCH;
if (d_res == MXS_PCRE2_MATCH)
{
for (MODULECMD *cmd = domain->commands; cmd && rval; cmd = cmd->next)
{
mxs_pcre2_result_t i_res = ident_re ?
mxs_pcre2_simple_match(ident_re, cmd->identifier, 0, &err) :
MXS_PCRE2_MATCH;
if (i_res == MXS_PCRE2_MATCH)
{
if (!fn(cmd, data))
{
stop = true;
break;
}
}
else if (i_res == MXS_PCRE2_ERROR)
{
PCRE2_UCHAR errbuf[MXS_STRERROR_BUFLEN];
pcre2_get_error_message(err, errbuf, sizeof(errbuf));
MXS_ERROR("Failed to match command identifier with '%s': %s", ident_re, errbuf);
modulecmd_set_error("Failed to match command identifier with '%s': %s", ident_re, errbuf);
rval = false;
}
}
}
else if (d_res == MXS_PCRE2_ERROR)
{
PCRE2_UCHAR errbuf[MXS_STRERROR_BUFLEN];
pcre2_get_error_message(err, errbuf, sizeof(errbuf));
MXS_ERROR("Failed to match command domain with '%s': %s", domain_re, errbuf);
modulecmd_set_error("Failed to match command domain with '%s': %s", domain_re, errbuf);
rval = false;
}
}
spinlock_release(&modulecmd_lock);
return rval;
}
char* modulecmd_argtype_to_str(modulecmd_arg_type_t *type)
{
const char *strtype = "UNKNOWN";
switch (MODULECMD_GET_TYPE(type))
{
case MODULECMD_ARG_NONE:
strtype = "NONE";
break;
case MODULECMD_ARG_STRING:
strtype = "STRING";
break;
case MODULECMD_ARG_BOOLEAN:
strtype = "BOOLEAN";
break;
case MODULECMD_ARG_SERVICE:
strtype = "SERVICE";
break;
case MODULECMD_ARG_SERVER:
strtype = "SERVER";
break;
case MODULECMD_ARG_SESSION:
strtype = "SESSION";
break;
case MODULECMD_ARG_SESSION_PTR:
strtype = "SESSION_PTR";
break;
case MODULECMD_ARG_DCB:
strtype = "DCB";
break;
case MODULECMD_ARG_DCB_PTR:
strtype = "DCB_PTR";
break;
case MODULECMD_ARG_MONITOR:
strtype = "MONITOR";
break;
case MODULECMD_ARG_FILTER:
strtype = "FILTER";
break;
case MODULECMD_ARG_OUTPUT:
strtype = "OUTPUT";
break;
default:
ss_dassert(false);
MXS_ERROR("Unknown type");
break;
}
size_t slen = strlen(strtype);
size_t extra = MODULECMD_ARG_IS_REQUIRED(type) ? 0 : 2;
char *rval = MXS_MALLOC(slen + extra + 1);
if (rval)
{
const char *fmtstr = extra ? "[%s]" : "%s";
sprintf(rval, fmtstr, strtype);
}
return rval;
}
bool modulecmd_arg_is_present(const MODULECMD_ARG *arg, int idx)
{
return arg->argc > idx &&
MODULECMD_GET_TYPE(&arg->argv[idx].type) != MODULECMD_ARG_NONE;
}

View File

@ -424,9 +424,46 @@ monitorShowAll(DCB *dcb)
void
monitorShow(DCB *dcb, MONITOR *monitor)
{
const char *state;
switch (monitor->state)
{
case MONITOR_STATE_RUNNING:
state = "Running";
break;
case MONITOR_STATE_STOPPING:
state = "Stopping";
break;
case MONITOR_STATE_STOPPED:
state = "Stopped";
break;
case MONITOR_STATE_ALLOC:
state = "Allocated";
break;
default:
state = "Unknown";
break;
}
dcb_printf(dcb, "Monitor: %p\n", monitor);
dcb_printf(dcb, "Name: %s\n", monitor->name);
dcb_printf(dcb, "State: %s\n", state);
dcb_printf(dcb, "Sampling interval: %lu milliseconds\n", monitor->interval);
dcb_printf(dcb, "Connect Timeout: %i seconds\n", monitor->connect_timeout);
dcb_printf(dcb, "Read Timeout: %i seconds\n", monitor->read_timeout);
dcb_printf(dcb, "Write Timeout: %i seconds\n", monitor->write_timeout);
dcb_printf(dcb, "Monitored servers: ");
const char *sep = "";
for (MONITOR_SERVERS *db = monitor->databases; db; db = db->next)
{
dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port);
sep = ", ";
}
dcb_printf(dcb, "\n");
dcb_printf(dcb, "Monitor: %p\n", monitor);
dcb_printf(dcb, "\tName: %s\n", monitor->name);
if (monitor->handle)
{
if (monitor->module->diagnostics)
@ -442,6 +479,7 @@ monitorShow(DCB *dcb, MONITOR *monitor)
{
dcb_printf(dcb, "\tMonitor failed\n");
}
dcb_printf(dcb, "\n");
}
/**

View File

@ -456,8 +456,14 @@ struct type_name_info type_to_type_name_info(qc_query_type_t type)
}
break;
/** Not implemented yet */
//case QUERY_TYPE_USERVAR_WRITE:
case QUERY_TYPE_USERVAR_WRITE:
{
static const char name[] = "QUERY_TYPE_USERVAR_WRITE";
info.name = name;
info.name_len = sizeof(name) - 1;
}
break;
case QUERY_TYPE_USERVAR_READ:
{
static const char name[] = "QUERY_TYPE_USERVAR_READ";
@ -615,8 +621,7 @@ static const qc_query_type_t QUERY_TYPES[] =
QUERY_TYPE_WRITE,
QUERY_TYPE_MASTER_READ,
QUERY_TYPE_SESSION_WRITE,
/** Not implemented yet */
//QUERY_TYPE_USERVAR_WRITE,
QUERY_TYPE_USERVAR_WRITE,
QUERY_TYPE_USERVAR_READ,
QUERY_TYPE_SYSVAR_READ,
/** Not implemented yet */

View File

@ -1031,6 +1031,7 @@ static struct
{ "ndb", SERVER_NDB },
{ "maintenance", SERVER_MAINT },
{ "maint", SERVER_MAINT },
{ "stale", SERVER_STALE_STATUS },
{ NULL, 0 }
};

View File

@ -802,6 +802,10 @@ serviceAddBackend(SERVICE *service, SERVER *server)
atomic_synchronize();
prev->next = new_ref;
}
else
{
MXS_FREE(new_ref);
}
}
else
{
@ -828,7 +832,7 @@ void serviceRemoveBackend(SERVICE *service, const SERVER *server)
for (SERVER_REF *ref = service->dbref; ref; ref = ref->next)
{
if (ref->server == server)
if (ref->server == server && ref->active)
{
ref->active = false;
service->n_dbref--;
@ -852,8 +856,12 @@ serviceHasBackend(SERVICE *service, SERVER *server)
spinlock_acquire(&service->spin);
ptr = service->dbref;
while (ptr && ptr->server != server)
while (ptr)
{
if (ptr->server == server && ptr->active)
{
break;
}
ptr = ptr->next;
}
spinlock_release(&service->spin);

View File

@ -18,6 +18,7 @@ add_executable(test_users testusers.c)
add_executable(testfeedback testfeedback.c)
add_executable(testmaxscalepcre2 testmaxscalepcre2.c)
add_executable(testmemlog testmemlog.c)
add_executable(testmodulecmd testmodulecmd.c)
target_link_libraries(test_adminusers maxscale-common)
target_link_libraries(test_buffer maxscale-common)
target_link_libraries(test_dcb maxscale-common)
@ -38,6 +39,7 @@ target_link_libraries(test_users maxscale-common)
target_link_libraries(testfeedback maxscale-common)
target_link_libraries(testmaxscalepcre2 maxscale-common)
target_link_libraries(testmemlog maxscale-common)
target_link_libraries(testmodulecmd maxscale-common)
add_test(TestAdminUsers test_adminusers)
add_test(TestBuffer test_buffer)
add_test(TestDCB test_dcb)
@ -58,6 +60,7 @@ add_test(TestServer test_server)
add_test(TestService test_service)
add_test(TestSpinlock test_spinlock)
add_test(TestUsers test_users)
add_test(TestModulecmd testmodulecmd)
# This test requires external dependencies and thus cannot be run
# as a part of the core test set

View File

@ -0,0 +1,373 @@
/*
* 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/bsl.
*
* Change Date: 2019-07-01
*
* 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.
*/
/**
* Test modulecmd.h functionality
*/
#include <maxscale/dcb.h>
#include <maxscale/modulecmd.h>
#include <maxscale/session.h>
#define TEST(a, b) do{if (!(a)){printf("%s:%d "b"\n", __FILE__, __LINE__);return 1;}}while(false)
static bool ok = false;
bool test_fn(const MODULECMD_ARG *arg)
{
ok = (arg->argc == 2 && strcmp(arg->argv[0].value.string, "Hello") == 0 &&
arg->argv[1].value.boolean);
return true;
}
int test_arguments()
{
const void *params1[] = {"Hello", "true"};
const void *params2[] = {"Hello", "1"};
const void *wrong_params1[] = {"Hi", "true"};
const void *wrong_params2[] = {"Hello", "false"};
const void *bad_params1[] = {"Hello", "World!"};
const void *bad_params2[] = {"Hello", NULL};
const void *bad_params3[] = {NULL, NULL};
const void *bad_params4[] = {NULL, "World!"};
const char *ns = "test_arguments";
const char *id = "test_arguments";
modulecmd_arg_type_t args1[] =
{
{MODULECMD_ARG_STRING, ""},
{MODULECMD_ARG_BOOLEAN, ""}
};
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
/**
* Test command creation
*/
TEST(modulecmd_find_command(ns, id) == NULL, "The registered command should not yet be found");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_register_command(ns, id, test_fn, 2, args1),
"Registering a command should succeed");
TEST(!modulecmd_register_command(ns, id, test_fn, 2, args1),
"Registering the command a second time should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
/**
* Test bad arguments
*/
TEST(modulecmd_arg_parse(cmd, 0, NULL) == NULL, "Passing no arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 1, params1) == NULL, "Passing one argument should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 3, params1) == NULL, "Passing three arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params1) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params2) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params3) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(modulecmd_arg_parse(cmd, 2, bad_params4) == NULL, "Passing bad arguments should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
/**
* Test valid arguments
*/
MODULECMD_ARG* alist = modulecmd_arg_parse(cmd, 2, params1);
TEST(alist, "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist), "Module call should be successful");
TEST(ok, "Function should receive right parameters");
ok = false;
TEST(modulecmd_call_command(cmd, alist), "Second Module call should be successful");
TEST(ok, "Function should receive right parameters");
ok = false;
modulecmd_arg_free(alist);
TEST((alist = modulecmd_arg_parse(cmd, 2, params2)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist), "Module call should be successful");
TEST(ok, "Function should receive right parameters");
modulecmd_arg_free(alist);
/**
* Test valid but wrong arguments
*/
TEST((alist = modulecmd_arg_parse(cmd, 2, wrong_params1)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(!ok, "Function should receive wrong parameters");
modulecmd_arg_free(alist);
TEST((alist = modulecmd_arg_parse(cmd, 2, wrong_params2)), "Arguments should be parsed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, alist), "Module call should be successful");
TEST(!ok, "Function should receive wrong parameters");
modulecmd_arg_free(alist);
return 0;
}
bool test_fn2(const MODULECMD_ARG *arg)
{
return true;
}
int test_optional_arguments()
{
const void *params1[] = {"Hello", "true"};
const void *params2[] = {NULL, "true"};
const void *params3[] = {"Hello", NULL};
const void *params4[] = {NULL, NULL};
const void *ns = "test_optional_arguments";
const void *id = "test_optional_arguments";
modulecmd_arg_type_t args1[] =
{
{MODULECMD_ARG_STRING | MODULECMD_ARG_OPTIONAL, ""},
{MODULECMD_ARG_BOOLEAN | MODULECMD_ARG_OPTIONAL, ""}
};
TEST(modulecmd_register_command(ns, id, test_fn2, 2, args1),
"Registering a command should succeed");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 2, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params2);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params3);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 2, params4);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 1, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 1, params2);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
arg = modulecmd_arg_parse(cmd, 0, params1);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
TEST(modulecmd_call_command(cmd, NULL), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
return 0;
}
bool test_fn3(const MODULECMD_ARG *arg)
{
modulecmd_set_error("Something went wrong!");
return false;
}
int test_module_errors()
{
const char *ns = "test_module_errors";
const char *id = "test_module_errors";
TEST(modulecmd_register_command(ns, id, test_fn3, 0, NULL),
"Registering a command should succeed");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
TEST(!modulecmd_call_command(cmd, NULL), "Module call should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
return 0;
}
bool test_fn_map(const MODULECMD_ARG *args)
{
return true;
}
const char *map_dom = "test_map";
bool mapfn(const MODULECMD *cmd, void *data)
{
int *i = (int*)data;
(*i)++;
return true;
}
int test_map()
{
for (int i = 0; i < 10; i++)
{
char id[200];
sprintf(id, "test_map%d", i + 1);
TEST(modulecmd_register_command(map_dom, id, test_fn_map, 0, NULL),
"Registering a command should succeed");
}
int n = 0;
TEST(modulecmd_foreach(NULL, NULL, mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n >= 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", NULL, mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach(NULL, "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 10, "Every registered command should be called");
n = 0;
TEST(modulecmd_foreach("wrong domain", "test_map", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 0, "No registered command should be called");
n = 0;
TEST(modulecmd_foreach("test_map", "test_map[2-4]", mapfn, &n), "Mapping function should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(n == 3, "Three registered commands should be called");
n = 0;
TEST(!modulecmd_foreach("(", NULL, mapfn, &n), "Mapping function should fail");
TEST(strlen(modulecmd_get_error()), "Error message should not be empty");
TEST(n == 0, "No registered command should be called");
return 0;
}
static DCB my_dcb;
static SESSION my_session;
bool ptrfn(const MODULECMD_ARG *argv)
{
bool rval = false;
if (argv->argc == 4 && argv->argv[0].value.dcb == &my_dcb &&
argv->argv[1].value.dcb == &my_dcb &&
argv->argv[2].value.session == &my_session &&
argv->argv[3].value.session == &my_session)
{
rval = true;
}
return rval;
}
int test_pointers()
{
const char *ns = "test_pointers";
const char *id = "test_pointers";
modulecmd_arg_type_t args[] =
{
{MODULECMD_ARG_DCB, ""},
{MODULECMD_ARG_DCB_PTR, ""},
{MODULECMD_ARG_SESSION, ""},
{MODULECMD_ARG_SESSION_PTR, ""}
};
TEST(modulecmd_register_command(ns, id, ptrfn, 4, args), "Registering a command should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
const MODULECMD *cmd = modulecmd_find_command(ns, id);
TEST(cmd, "The registered command should be found");
char dcb_str[200];
char session_str[200];
sprintf(dcb_str, "%p", &my_dcb);
sprintf(session_str, "%p", &my_session);
const void* params[] = {dcb_str, &my_dcb, session_str, &my_session};
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 4, params);
TEST(arg, "Parsing arguments should succeed");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
TEST(modulecmd_call_command(cmd, arg), "Module call should be successful");
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
modulecmd_arg_free(arg);
return 0;
}
int main(int argc, char **argv)
{
int rc = 0;
rc += test_arguments();
rc += test_optional_arguments();
rc += test_module_errors();
rc += test_map();
rc += test_pointers();
return rc;
}

View File

@ -156,4 +156,8 @@ null_auth_is_client_ssl_capable(DCB *dcb)
* @param dcb Request handler DCB connected to the client
*/
static void
null_auth_free_client_data(DCB *dcb) {}
null_auth_free_client_data(DCB *dcb)
{
free(dcb->data);
dcb->data = NULL;
}

View File

@ -12,6 +12,7 @@
*/
#define MXS_MODULE_NAME "cache"
#include "cache.h"
#include <maxscale/alloc.h>
#include <maxscale/filter.h>
#include <maxscale/gwdirs.h>
@ -20,7 +21,6 @@
#include <maxscale/modutil.h>
#include <maxscale/mysql_utils.h>
#include <maxscale/query_classifier.h>
#include "cache.h"
#include "rules.h"
#include "storage.h"
@ -95,9 +95,11 @@ typedef struct cache_config
{
uint32_t max_resultset_rows;
uint32_t max_resultset_size;
const char* rules;
const char *rules;
const char *storage;
const char *storage_options;
char *storage_options;
char **storage_argv;
int storage_argc;
uint32_t ttl;
uint32_t debug;
} CACHE_CONFIG;
@ -109,6 +111,8 @@ static const CACHE_CONFIG DEFAULT_CONFIG =
NULL,
NULL,
NULL,
NULL,
0,
CACHE_DEFAULT_TTL,
CACHE_DEFAULT_DEBUG
};
@ -214,7 +218,11 @@ static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER
if (module)
{
CACHE_STORAGE *storage = module->api->createInstance(name, config.ttl, 0, NULL);
uint32_t ttl = config.ttl;
int argc = config.storage_argc;
char** argv = config.storage_argv;
CACHE_STORAGE *storage = module->api->createInstance(name, ttl, argc, argv);
if (storage)
{
@ -976,7 +984,47 @@ static bool process_params(char **options, FILTER_PARAMETER **params, CACHE_CONF
}
else if (strcmp(param->name, "storage_options") == 0)
{
config->storage_options = param->value;
config->storage_options = MXS_STRDUP(param->value);
if (config->storage_options)
{
int argc = 1;
char *arg = config->storage_options;
while ((arg = strchr(config->storage_options, ',')))
{
++argc;
}
config->storage_argv = (char**) MXS_MALLOC((argc + 1) * sizeof(char*));
if (config->storage_argv)
{
config->storage_argc = argc;
int i = 0;
arg = config->storage_options;
config->storage_argv[i++] = arg;
while ((arg = strchr(config->storage_options, ',')))
{
*arg = 0;
++arg;
config->storage_argv[i++] = arg;
}
config->storage_argv[i] = NULL;
}
else
{
MXS_FREE(config->storage_options);
config->storage_options = NULL;
}
}
else
{
error = true;
}
}
else if (strcmp(param->name, "storage") == 0)
{

View File

@ -14,6 +14,7 @@
* Public License.
*/
#include <maxscale/cdefs.h>
#include <limits.h>
MXS_BEGIN_DECLS

View File

@ -15,6 +15,7 @@
#include <openssl/sha.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fts.h>
#include <algorithm>
#include <set>
#include <rocksdb/env.h>
@ -37,8 +38,6 @@ using std::unique_ptr;
namespace
{
string u_storageDirectory;
const size_t ROCKSDB_KEY_LENGTH = 2 * SHA512_DIGEST_LENGTH;
#if ROCKSDB_KEY_LENGTH > CACHE_KEY_MAXLEN
@ -85,8 +84,116 @@ string toString(const StorageRocksDBVersion& version)
const char STORAGE_ROCKSDB_VERSION_KEY[] = "MaxScale_Storage_RocksDB_Version";
/**
* Deletes a path, irrespective of whether it represents a file, a directory
* or a directory hierarchy. If the path does not exist, then the path is
* considered to have been removed.
*
* @param path A path (file or directory).
*
* @return True if the path could be deleted, false otherwise.
*/
bool deletePath(const string& path)
{
int rv = false;
struct stat st;
if (stat(path.c_str(), &st) == -1)
{
if (errno == ENOENT)
{
rv = true;
}
else
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Could not stat: %s", strerror_r(errno, errbuf, sizeof(errbuf)));
}
}
else
{
MXS_NOTICE("Deleting cache storage at '%s'.", path.c_str());
rv = true;
char* files[] = { (char *) path.c_str(), NULL };
// FTS_NOCHDIR - Do not change CWD while traversing.
// FTS_PHYSICAL - Don't follow symlinks.
// FTS_XDEV - Don't cross filesystem boundaries
FTS *pFts = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
if (pFts) {
FTSENT* pCurrent;
while ((pCurrent = fts_read(pFts)))
{
switch (pCurrent->fts_info)
{
case FTS_NS:
case FTS_DNR:
case FTS_ERR:
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Error while traversing %s: %s",
pCurrent->fts_accpath,
strerror_r(pCurrent->fts_errno, errbuf, sizeof(errbuf)));
rv = false;
}
break;
case FTS_DC:
case FTS_DOT:
case FTS_NSOK:
// Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
// passed to fts_open()
break;
case FTS_D:
// Do nothing. Need depth-first search, so directories are deleted
// in FTS_DP
break;
case FTS_DP:
case FTS_F:
case FTS_SL:
case FTS_SLNONE:
case FTS_DEFAULT:
if (remove(pCurrent->fts_accpath) < 0)
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Could not remove '%s', the cache directory may need to "
"be deleted manually: %s",
pCurrent->fts_accpath,
strerror_r(errno, errbuf, sizeof(errbuf)));
rv = false;
}
break;
default:
ss_dassert(!true);
}
}
if (rv)
{
MXS_NOTICE("Deleted cache storage at '%s'.", path.c_str());
}
if (pFts) {
fts_close(pFts);
}
}
}
return rv;
}
}
//private
rocksdb::WriteOptions RocksDBStorage::s_writeOptions;
//private
RocksDBStorage::RocksDBStorage(unique_ptr<rocksdb::DBWithTTL>& sDb,
const string& name,
@ -106,69 +213,98 @@ RocksDBStorage::~RocksDBStorage()
//static
bool RocksDBStorage::Initialize()
{
bool initialized = true;
auto pEnv = rocksdb::Env::Default();
pEnv->SetBackgroundThreads(ROCKSDB_N_LOW_THREADS, rocksdb::Env::LOW);
pEnv->SetBackgroundThreads(ROCKSDB_N_HIGH_THREADS, rocksdb::Env::HIGH);
u_storageDirectory = get_cachedir();
u_storageDirectory += "/storage_rocksdb";
// No logging; the database will always be deleted at startup, so there's
// no reason for usinf space and processing for writing the write ahead log.
s_writeOptions.disableWAL = true;
if (mkdir(u_storageDirectory.c_str(), S_IRWXU) == 0)
{
MXS_NOTICE("Created storage directory %s.", u_storageDirectory.c_str());
}
else if (errno != EEXIST)
{
initialized = false;
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to create storage directory %s: %s",
u_storageDirectory.c_str(),
strerror_r(errno, errbuf, sizeof(errbuf)));
}
else
{
auto pEnv = rocksdb::Env::Default();
pEnv->SetBackgroundThreads(ROCKSDB_N_LOW_THREADS, rocksdb::Env::LOW);
pEnv->SetBackgroundThreads(ROCKSDB_N_HIGH_THREADS, rocksdb::Env::HIGH);
}
return initialized;
return true;
}
//static
RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc, char* argv[])
{
ss_dassert(zName);
string path(u_storageDirectory);
string storageDirectory = get_cachedir();
path += "/";
path += zName;
rocksdb::Options options;
options.env = rocksdb::Env::Default();
options.max_background_compactions = ROCKSDB_N_LOW_THREADS;
options.max_background_flushes = ROCKSDB_N_HIGH_THREADS;
rocksdb::DBWithTTL* pDb;
rocksdb::Status status;
rocksdb::Slice key(STORAGE_ROCKSDB_VERSION_KEY);
do
for (int i = 0; i < argc; ++i)
{
// Try to open existing.
options.create_if_missing = false;
options.error_if_exists = false;
size_t len = strlen(argv[i]);
char arg[len + 1];
strcpy(arg, argv[i]);
status = rocksdb::DBWithTTL::Open(options, path, &pDb, ttl);
const char* zValue = NULL;
char *zEq = strchr(arg, '=');
if (status.IsInvalidArgument()) // Did not exist
if (zEq)
{
MXS_NOTICE("Database \"%s\" does not exist, creating.", path.c_str());
*zEq = 0;
zValue = trim(zEq + 1);
}
const char* zKey = trim(arg);
if (strcmp(zKey, "cache_directory") == 0)
{
if (zValue)
{
storageDirectory = zValue;
}
else
{
MXS_WARNING("No value specified for '%s', using default '%s' instead.",
zKey, get_cachedir());
}
}
else
{
MXS_WARNING("Unknown argument '%s'.", zKey);
}
}
storageDirectory += "/storage_rocksdb";
return Create(storageDirectory, zName, ttl);
}
// static
RocksDBStorage* RocksDBStorage::Create(const string& storageDirectory, const char* zName, uint32_t ttl)
{
RocksDBStorage* pStorage = nullptr;
if (mkdir(storageDirectory.c_str(), S_IRWXU) == 0)
{
MXS_NOTICE("Created storage directory %s.", storageDirectory.c_str());
}
else if (errno != EEXIST)
{
char errbuf[MXS_STRERROR_BUFLEN];
MXS_ERROR("Failed to create storage directory %s: %s",
storageDirectory.c_str(),
strerror_r(errno, errbuf, sizeof(errbuf)));
}
else
{
string path(storageDirectory + "/" + zName);
if (deletePath(path))
{
rocksdb::Options options;
options.env = rocksdb::Env::Default();
options.max_background_compactions = ROCKSDB_N_LOW_THREADS;
options.max_background_flushes = ROCKSDB_N_HIGH_THREADS;
options.create_if_missing = true;
options.error_if_exists = true;
rocksdb::DBWithTTL* pDb;
rocksdb::Status status;
rocksdb::Slice key(STORAGE_ROCKSDB_VERSION_KEY);
status = rocksdb::DBWithTTL::Open(options, path, &pDb, ttl);
if (status.ok())
@ -179,7 +315,7 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
rocksdb::Slice value(reinterpret_cast<const char*>(&STORAGE_ROCKSDB_VERSION),
sizeof(STORAGE_ROCKSDB_VERSION));
status = pDb->Put(rocksdb::WriteOptions(), key, value);
status = pDb->Put(writeOptions(), key, value);
if (!status.ok())
{
@ -188,36 +324,6 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
path.c_str(),
status.ToString().c_str());
}
}
}
}
while (status.IsInvalidArgument());
RocksDBStorage* pStorage = nullptr;
if (status.ok())
{
std::string value;
status = pDb->Get(rocksdb::ReadOptions(), key, &value);
if (status.ok())
{
const StorageRocksDBVersion* pVersion =
reinterpret_cast<const StorageRocksDBVersion*>(value.data());
// When the version is bumped, it needs to be decided what if any
// backward compatibility is provided. After all, it's a cache, so
// you should be able to delete it at any point and pay a small
// price while the cache is rebuilt.
if ((pVersion->major == STORAGE_ROCKSDB_MAJOR) &&
(pVersion->minor == STORAGE_ROCKSDB_MINOR) &&
(pVersion->correction == STORAGE_ROCKSDB_CORRECTION))
{
MXS_NOTICE("Version of \"%s\" is %s, version of storage_rocksdb is %s.",
path.c_str(),
toString(*pVersion).c_str(),
toString(STORAGE_ROCKSDB_VERSION).c_str());
unique_ptr<rocksdb::DBWithTTL> sDb(pDb);
@ -225,31 +331,14 @@ RocksDBStorage* RocksDBStorage::Create(const char* zName, uint32_t ttl, int argc
}
else
{
MXS_ERROR("Version of RocksDB database \"%s\" is %s, while version required "
"is %s. You need to delete the database and restart.",
path.c_str(),
toString(*pVersion).c_str(),
toString(STORAGE_ROCKSDB_VERSION).c_str());
delete pDb;
}
}
else
{
MXS_ERROR("Could not read version information from RocksDB database %s. "
"You may need to delete the database and retry. RocksDB error: \"%s\"",
path.c_str(),
status.ToString().c_str());
delete pDb;
}
}
else
{
MXS_ERROR("Could not open/initialize RocksDB database %s. RocksDB error: \"%s\"",
path.c_str(), status.ToString().c_str());
MXS_ERROR("Could not create RocksDB database %s. RocksDB error: \"%s\"",
path.c_str(), status.ToString().c_str());
if (status.IsIOError())
{
MXS_ERROR("Is an other MaxScale process running?");
if (status.IsIOError())
{
MXS_ERROR("Is an other MaxScale process running?");
}
}
}
}
@ -374,7 +463,7 @@ cache_result_t RocksDBStorage::putValue(const char* pKey, const GWBUF* pValue)
rocksdb::Slice key(pKey, ROCKSDB_KEY_LENGTH);
rocksdb::Slice value(static_cast<const char*>(GWBUF_DATA(pValue)), GWBUF_LENGTH(pValue));
rocksdb::Status status = m_sDb->Put(rocksdb::WriteOptions(), key, value);
rocksdb::Status status = m_sDb->Put(writeOptions(), key, value);
return status.ok() ? CACHE_RESULT_OK : CACHE_RESULT_ERROR;
}

View File

@ -42,11 +42,22 @@ private:
RocksDBStorage(const RocksDBStorage&) = delete;
RocksDBStorage& operator = (const RocksDBStorage&) = delete;
static RocksDBStorage* Create(const std::string& storageDirectory,
const char* zName,
uint32_t ttl);
static const rocksdb::WriteOptions& writeOptions()
{
return s_writeOptions;
}
private:
std::unique_ptr<rocksdb::DBWithTTL> m_sDb;
std::string m_name;
std::string m_path;
uint32_t m_ttl;
static rocksdb::WriteOptions s_writeOptions;
};
#endif

View File

@ -11,12 +11,11 @@ if(BISON_FOUND AND FLEX_FOUND)
set_target_properties(dbfwfilter PROPERTIES VERSION "1.0.0")
install_module(dbfwfilter core)
if(BUILD_TOOLS)
add_executable(dbfwruleparser dbfwfilter.c ${BISON_ruleparser_OUTPUTS} ${FLEX_token_OUTPUTS})
target_compile_definitions(dbfwruleparser PUBLIC "BUILD_RULE_PARSER")
target_link_libraries(dbfwruleparser maxscale-common)
install_module(dbfwruleparser core)
endif()
# The offline rule check utility
add_executable(dbfwchk dbfw_rule_check.c ${BISON_ruleparser_OUTPUTS} ${FLEX_token_OUTPUTS})
target_link_libraries(dbfwchk maxscale-common)
install_executable(dbfwchk core)
else()
message(FATAL_ERROR "Could not find Bison or Flex: ${BISON_EXECUTABLE} ${FLEX_EXECUTABLE}")
endif()

View File

@ -0,0 +1,55 @@
/*
* 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/bsl.
*
* Change Date: 2019-07-01
*
* 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.
*/
#include "dbfwfilter.c"
int main(int argc, char **argv)
{
int rval = 1;
if (argc < 2)
{
printf("Usage: dbfw_rule_check FILE\n");
}
else
{
mxs_log_init("dbfwfilter_rule_parser", ".", MXS_LOG_TARGET_STDOUT);
if (access(argv[1], R_OK) == 0)
{
MXS_NOTICE("Parsing rule file: %s", argv[1]);
RULE* rules;
HASHTABLE *users;
if (process_rule_file(argv[1], &rules, &users))
{
MXS_NOTICE("Rule parsing was successful.");
rval = 0;
}
else
{
MXS_ERROR("Failed to parse rules.");
}
}
else
{
MXS_ERROR("Failed to read file '%s': %d, %s", argv[1], errno, strerror(errno));
}
mxs_log_finish();
}
return rval;
}

View File

@ -136,108 +136,6 @@ typedef enum
RT_CLAUSE /*< WHERE-clause requirement rule */
} ruletype_t;
const char* rule_names[] =
{
"UNDEFINED",
"COLUMN",
"THROTTLE",
"PERMISSION",
"WILDCARD",
"REGEX",
"CLAUSE"
};
/**
* Linked list of strings.
*/
typedef struct strlink_t
{
struct strlink_t *next; /*< Next node in the list */
char* value; /*< Value of the current node */
} STRLINK;
/**
* A structure defining a range of time
*/
typedef struct timerange_t
{
struct timerange_t* next; /*< Next node in the list */
struct tm start; /*< Start of the time range */
struct tm end; /*< End of the time range */
} TIMERANGE;
/**
* Query speed measurement and limitation structure
*/
typedef struct queryspeed_t
{
time_t first_query; /*< Time when the first query occurred */
time_t triggered; /*< Time when the limit was exceeded */
int period; /*< Measurement interval in seconds */
int cooldown; /*< Time the user is denied access for */
int count; /*< Number of queries done */
int limit; /*< Maximum number of queries */
long id; /*< Unique id of the rule */
bool active; /*< If the rule has been triggered */
struct queryspeed_t* next; /*< Next node in the list */
} QUERYSPEED;
/**
* A structure used to identify individual rules and to store their contents
*
* Each type of rule has different requirements that are expressed as void pointers.
* This allows to match an arbitrary set of rules against a user.
*/
typedef struct rule_t
{
void* data; /*< Actual implementation of the rule */
char* name; /*< Name of the rule */
ruletype_t type; /*< Type of the rule */
qc_query_op_t on_queries; /*< Types of queries to inspect */
int times_matched; /*< Number of times this rule has been matched */
TIMERANGE* active; /*< List of times when this rule is active */
struct rule_t *next;
} RULE;
/**
* Linked list of pointers to a global pool of RULE structs
*/
typedef struct rulelist_t
{
RULE* rule; /*< The rule structure */
struct rulelist_t* next; /*< Next node in the list */
} RULELIST;
typedef struct user_template
{
char *name;
enum match_type type; /** Matching type */
STRLINK *rulenames; /** names of the rules */
struct user_template *next;
} user_template_t;
typedef struct user_t
{
char* name; /*< Name of the user */
SPINLOCK lock; /*< User spinlock */
QUERYSPEED* qs_limit; /*< The query speed structure unique to this user */
RULELIST* rules_or; /*< If any of these rules match the action is triggered */
RULELIST* rules_and; /*< All of these rules must match for the action to trigger */
RULELIST* rules_strict_and; /*< rules that skip the rest of the rules if one of them
* fails. This is only for rules paired with 'match strict_all'. */
} USER;
/**
* Linked list of IP adresses and subnet masks
*/
typedef struct iprange_t
{
struct iprange_t* next; /*< Next node in the list */
uint32_t ip; /*< IP address */
uint32_t mask; /*< Network mask */
} IPRANGE;
/**
* Possible actions to take when the query matches a rule
*/
@ -258,18 +156,116 @@ enum fw_actions
/** Maximum length of the match/nomatch messages */
#define FW_MAX_SQL_LEN 400
const char* rule_names[] =
{
"UNDEFINED",
"COLUMN",
"THROTTLE",
"PERMISSION",
"WILDCARD",
"REGEX",
"CLAUSE"
};
/**
* Linked list of strings.
*/
typedef struct strlink_t
{
struct strlink_t *next; /*< Next node in the list */
char* value; /*< Value of the current node */
} STRLINK;
/**
* A structure defining a range of time
*/
typedef struct timerange_t
{
struct timerange_t* next; /*< Next node in the list */
struct tm start; /*< Start of the time range */
struct tm end; /*< End of the time range */
} TIMERANGE;
/**
* Query speed measurement and limitation structure
*/
typedef struct queryspeed_t
{
time_t first_query; /*< Time when the first query occurred */
time_t triggered; /*< Time when the limit was exceeded */
int period; /*< Measurement interval in seconds */
int cooldown; /*< Time the user is denied access for */
int count; /*< Number of queries done */
int limit; /*< Maximum number of queries */
long id; /*< Unique id of the rule */
bool active; /*< If the rule has been triggered */
struct queryspeed_t* next; /*< Next node in the list */
} QUERYSPEED;
/**
* A structure used to identify individual rules and to store their contents
*
* Each type of rule has different requirements that are expressed as void pointers.
* This allows to match an arbitrary set of rules against a user.
*/
typedef struct rule_t
{
void* data; /*< Actual implementation of the rule */
char* name; /*< Name of the rule */
ruletype_t type; /*< Type of the rule */
qc_query_op_t on_queries; /*< Types of queries to inspect */
int times_matched; /*< Number of times this rule has been matched */
TIMERANGE* active; /*< List of times when this rule is active */
struct rule_t *next;
} RULE;
/**
* A set of rules that the filter follows
*/
typedef struct rulebook_t
{
RULE* rule; /*< The rule structure */
struct rulebook_t* next; /*< The next rule in the book */
} RULE_BOOK;
/**
* A temporary template structure used in the creation of actual users.
* This is also used to link the user definitions with the rules.
* @see struct user_t
*/
typedef struct user_template
{
char *name;
enum match_type type; /** Matching type */
STRLINK *rulenames; /** names of the rules */
struct user_template *next;
} user_template_t;
/**
* A user definition
*/
typedef struct user_t
{
char* name; /*< Name of the user */
SPINLOCK lock; /*< User spinlock */
QUERYSPEED* qs_limit; /*< The query speed structure unique to this user */
RULE_BOOK* rules_or; /*< If any of these rules match the action is triggered */
RULE_BOOK* rules_and; /*< All of these rules must match for the action to trigger */
RULE_BOOK* rules_strict_and; /*< rules that skip the rest of the rules if one of them
* fails. This is only for rules paired with 'match strict_all'. */
} DBFW_USER;
/**
* The Firewall filter instance.
*/
typedef struct
{
HASHTABLE* htable; /*< User hashtable */
RULE* rules; /*< List of all the rules */
STRLINK* userstrings; /*< Temporary list of raw strings of users */
enum fw_actions action; /*< Default operation mode, defaults to deny */
int log_match; /*< Log matching and/or non-matching queries */
SPINLOCK lock; /*< Instance spinlock */
int idgen; /*< UID generator */
HASHTABLE* users; /*< User hashtable */
RULE* rules; /*< List of all the rules */
enum fw_actions action; /*< Default operation mode, defaults to deny */
int log_match; /*< Log matching and/or non-matching queries */
SPINLOCK lock; /*< Instance spinlock */
int idgen; /*< UID generator */
} FW_INSTANCE;
/**
@ -277,10 +273,10 @@ typedef struct
*/
typedef struct
{
SESSION* session; /*< Client session structure */
char* errmsg; /*< Rule specific error message */
DOWNSTREAM down; /*< Next object in the downstream chain */
UPSTREAM up; /*< Next object in the upstream chain */
SESSION* session; /*< Client session structure */
char* errmsg; /*< Rule specific error message */
DOWNSTREAM down; /*< Next object in the downstream chain */
UPSTREAM up; /*< Next object in the upstream chain */
} FW_SESSION;
bool parse_at_times(const char** tok, char** saveptr, RULE* ruledef);
@ -366,9 +362,15 @@ static STRLINK* strlink_reverse_clone(STRLINK* head)
return clone;
}
static RULELIST* rulelist_push(RULELIST *head, RULE *rule)
/**
* Add a rule to a rulebook
* @param head
* @param rule
* @return
*/
static RULE_BOOK* rulebook_push(RULE_BOOK *head, RULE *rule)
{
RULELIST *rval = MXS_MALLOC(sizeof(RULELIST));
RULE_BOOK *rval = MXS_MALLOC(sizeof(RULE_BOOK));
if (rval)
{
@ -378,16 +380,16 @@ static RULELIST* rulelist_push(RULELIST *head, RULE *rule)
return rval;
}
static void* rulelist_clone(void* fval)
static void* rulebook_clone(void* fval)
{
RULELIST *rule = NULL,
*ptr = (RULELIST*) fval;
RULE_BOOK *rule = NULL,
*ptr = (RULE_BOOK*) fval;
while (ptr)
{
RULELIST* tmp = (RULELIST*) MXS_MALLOC(sizeof(RULELIST));
RULE_BOOK* tmp = (RULE_BOOK*) MXS_MALLOC(sizeof(RULE_BOOK));
MXS_ABORT_IF_NULL(tmp);
tmp->next = rule;
tmp->rule = ptr->rule;
@ -398,30 +400,42 @@ static void* rulelist_clone(void* fval)
return (void*) rule;
}
static void* rulelist_free(void* fval)
static void* rulebook_free(void* fval)
{
RULELIST *ptr = (RULELIST*) fval;
RULE_BOOK *ptr = (RULE_BOOK*) fval;
while (ptr)
{
RULELIST *tmp = ptr;
RULE_BOOK *tmp = ptr;
ptr = ptr->next;
MXS_FREE(tmp);
}
return NULL;
}
static void huserfree(void* fval)
static void dbfw_user_free(void* fval)
{
USER* value = (USER*) fval;
DBFW_USER* value = (DBFW_USER*) fval;
rulelist_free(value->rules_and);
rulelist_free(value->rules_or);
rulelist_free(value->rules_strict_and);
rulebook_free(value->rules_and);
rulebook_free(value->rules_or);
rulebook_free(value->rules_strict_and);
MXS_FREE(value->qs_limit);
MXS_FREE(value->name);
MXS_FREE(value);
}
HASHTABLE *dbfw_userlist_create()
{
HASHTABLE *ht = hashtable_alloc(100, hashtable_item_strhash, hashtable_item_strcmp);
if (ht)
{
hashtable_memory_fns(ht, hashtable_item_strdup, NULL, hashtable_item_free, dbfw_user_free);
}
return ht;
}
/**
* Parses a string that contains an IP address and converts the last octet to '%'.
* This modifies the string passed as the parameter.
@ -694,36 +708,25 @@ FILTER_OBJECT * GetModuleObject()
return &MyObject;
}
/**
* Adds the given rule string to the list of strings to be parsed for users.
* @param rule The rule string, assumed to be null-terminated
* @param instance The FW_FILTER instance
*/
void add_users(char* rule, FW_INSTANCE* instance)
{
assert(rule != NULL && instance != NULL);
instance->userstrings = strlink_push(instance->userstrings, rule);
}
/**
* Apply a rule set to a user
*
* @param instance Filter instance
* @param user User name
* @param rulelist List of rules to apply
* @param rulebook List of rules to apply
* @param type Matching type, one of FWTOK_MATCH_ANY, FWTOK_MATCH_ALL or FWTOK_MATCH_STRICT_ALL
* @return True of the rules were successfully applied. False if memory allocation
* fails
*/
static bool apply_rule_to_user(FW_INSTANCE *instance, char *username,
RULELIST *rulelist, enum match_type type)
RULE_BOOK *rulebook, enum match_type type)
{
USER* user;
DBFW_USER* user;
ss_dassert(type == FWTOK_MATCH_ANY || type == FWTOK_MATCH_STRICT_ALL || type == FWTOK_MATCH_ALL);
if ((user = (USER*) hashtable_fetch(instance->htable, username)) == NULL)
if ((user = (DBFW_USER*) hashtable_fetch(instance->users, username)) == NULL)
{
/**New user*/
if ((user = (USER*) MXS_CALLOC(1, sizeof(USER))) == NULL)
if ((user = (DBFW_USER*) MXS_CALLOC(1, sizeof(DBFW_USER))) == NULL)
{
return false;
}
@ -732,8 +735,8 @@ static bool apply_rule_to_user(FW_INSTANCE *instance, char *username,
user->name = (char*) MXS_STRDUP_A(username);
user->qs_limit = NULL;
RULELIST *tl = (RULELIST*) rulelist_clone(rulelist);
RULELIST *tail = tl;
RULE_BOOK *tl = (RULE_BOOK*) rulebook_clone(rulebook);
RULE_BOOK *tail = tl;
while (tail && tail->next)
{
@ -755,7 +758,7 @@ static bool apply_rule_to_user(FW_INSTANCE *instance, char *username,
user->rules_and = tl;
break;
}
hashtable_add(instance->htable, (void *) username, (void *) user);
hashtable_add(instance->users, (void *) username, (void *) user);
return true;
}
@ -763,7 +766,7 @@ static bool apply_rule_to_user(FW_INSTANCE *instance, char *username,
* Free a TIMERANGE struct
* @param tr pointer to a TIMERANGE struct
*/
void tr_free(TIMERANGE* tr)
void timerange_free(TIMERANGE* tr)
{
TIMERANGE *node, *tmp;
@ -899,16 +902,14 @@ bool create_rule(void* scanner, const char* name)
* Free a list of rules
* @param rule Rules to free
*/
static void free_rules(RULE* rule)
static void rule_free_all(RULE* rule)
{
while (rule)
{
RULE *tmp = rule->next;
while (rule->active)
if (rule->active)
{
TIMERANGE *tr = rule->active;
rule->active = rule->active->next;
MXS_FREE(tr);
timerange_free(rule->active);
}
switch (rule->type)
@ -930,6 +931,7 @@ static void free_rules(RULE* rule)
}
MXS_FREE(rule->name);
MXS_FREE(rule);
rule = tmp;
}
}
@ -1214,7 +1216,7 @@ static RULE* find_rule_by_name(RULE* rules, const char* name)
* @param rules List of all rules
* @return True on success, false on error.
*/
static bool process_user_templates(FW_INSTANCE *instance, user_template_t *templates,
static bool process_user_templates(HASHTABLE *users, user_template_t *templates,
RULE* rules)
{
bool rval = true;
@ -1227,17 +1229,17 @@ static bool process_user_templates(FW_INSTANCE *instance, user_template_t *templ
while (templates)
{
USER *user = hashtable_fetch(instance->htable, templates->name);
DBFW_USER *user = hashtable_fetch(users, templates->name);
if (user == NULL)
{
if ((user = MXS_MALLOC(sizeof(USER))) && (user->name = MXS_STRDUP(templates->name)))
if ((user = MXS_MALLOC(sizeof(DBFW_USER))) && (user->name = MXS_STRDUP(templates->name)))
{
user->rules_and = NULL;
user->rules_or = NULL;
user->rules_strict_and = NULL;
spinlock_init(&user->lock);
hashtable_add(instance->htable, user->name, user);
hashtable_add(users, user->name, user);
}
else
{
@ -1247,19 +1249,19 @@ static bool process_user_templates(FW_INSTANCE *instance, user_template_t *templ
}
}
RULELIST *foundrules = NULL;
RULE_BOOK *foundrules = NULL;
RULE *rule;
STRLINK *names = templates->rulenames;
while (names && (rule = find_rule_by_name(rules, names->value)))
{
foundrules = rulelist_push(foundrules, rule);
foundrules = rulebook_push(foundrules, rule);
names = names->next;
}
if (foundrules)
{
RULELIST *tail = foundrules;
RULE_BOOK *tail = foundrules;
while (tail->next)
{
@ -1302,7 +1304,7 @@ static bool process_user_templates(FW_INSTANCE *instance, user_template_t *templ
* @param instance Filter instance
* @return True on success, false on error.
*/
static bool process_rule_file(const char* filename, FW_INSTANCE* instance)
static bool process_rule_file(const char* filename, RULE** rules, HASHTABLE **users)
{
int rc = 1;
FILE *file = fopen(filename, "r");
@ -1328,15 +1330,18 @@ static bool process_rule_file(const char* filename, FW_INSTANCE* instance)
dbfw_yy_delete_buffer(buf, scanner);
dbfw_yylex_destroy(scanner);
fclose(file);
HASHTABLE *new_users = dbfw_userlist_create();
if (rc == 0 && process_user_templates(instance, pstack.templates, pstack.rule))
if (rc == 0 && new_users && process_user_templates(new_users, pstack.templates, pstack.rule))
{
instance->rules = pstack.rule;
*rules = pstack.rule;
*users = new_users;
}
else
{
rc = 1;
free_rules(pstack.rule);
rule_free_all(pstack.rule);
hashtable_free(new_users);
MXS_ERROR("Failed to process rule file '%s'.", filename);
}
@ -1355,6 +1360,41 @@ static bool process_rule_file(const char* filename, FW_INSTANCE* instance)
return rc == 0;
}
/**
* @brief Replace the rule file used by this filter instance
*
* This function does no locking. An external lock needs to protect this function
* call to prevent any connections from using the data when it is being replaced.
*
* @param filename File where the rules are located
* @param instance Filter instance
* @return True on success, false on error. If the return value is false, the
* old rules remain active.
*/
bool replace_rule_file(const char* filename, FW_INSTANCE* instance)
{
bool rval = false;
RULE *rules;
HASHTABLE *users;
if (process_rule_file(filename, &rules, &users))
{
/** Rules processed successfully, free the old ones */
rule_free_all(instance->rules);
hashtable_free(instance->users);
instance->rules = rules;
instance->users = users;
rval = true;
}
else
{
MXS_ERROR("Failed to process rule file at '%s', old rules are still active.", filename);
}
return rval;
}
/**
* Create an instance of the filter for a particular service
* within MaxScale.
@ -1370,7 +1410,6 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
{
FW_INSTANCE *my_instance;
int i;
HASHTABLE* ht;
char *filename = NULL;
bool err = false;
@ -1381,20 +1420,8 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
}
spinlock_init(&my_instance->lock);
if ((ht = hashtable_alloc(100, hashtable_item_strhash, hashtable_item_strcmp)) == NULL)
{
MXS_ERROR("Unable to allocate hashtable.");
MXS_FREE(my_instance);
return NULL;
}
hashtable_memory_fns(ht, hashtable_item_strdup, NULL, hashtable_item_free, huserfree);
my_instance->htable = ht;
my_instance->action = FW_ACTION_BLOCK;
my_instance->log_match = FW_LOG_NONE;
my_instance->userstrings = NULL;
for (i = 0; params[i]; i++)
{
@ -1447,9 +1474,8 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
err = true;
}
if (err || !process_rule_file(filename, my_instance))
if (err || !process_rule_file(filename, &my_instance->rules, &my_instance->users))
{
hashtable_free(my_instance->htable);
MXS_FREE(my_instance);
my_instance = NULL;
}
@ -1691,15 +1717,15 @@ static char* create_parse_error(FW_INSTANCE* my_instance,
* @param my_instance Fwfilter instance
* @param my_session Fwfilter session
* @param queue The GWBUF containing the query
* @param rulelist The rule to check
* @param rulebook The rule to check
* @param query Pointer to the null-terminated query string
* @return true if the query matches the rule
*/
bool rule_matches(FW_INSTANCE* my_instance,
FW_SESSION* my_session,
GWBUF *queue,
USER* user,
RULELIST *rulelist,
DBFW_USER* user,
RULE_BOOK *rulebook,
char* query)
{
char *ptr, *msg = NULL;
@ -1735,23 +1761,23 @@ bool rule_matches(FW_INSTANCE* my_instance,
if (parse_result != QC_QUERY_PARSED)
{
if ((rulelist->rule->type == RT_COLUMN) ||
(rulelist->rule->type == RT_WILDCARD) ||
(rulelist->rule->type == RT_CLAUSE))
if ((rulebook->rule->type == RT_COLUMN) ||
(rulebook->rule->type == RT_WILDCARD) ||
(rulebook->rule->type == RT_CLAUSE))
{
switch (optype)
{
case QUERY_OP_SELECT:
case QUERY_OP_UPDATE:
case QUERY_OP_INSERT:
case QUERY_OP_DELETE:
// In these cases, we have to be able to trust what qc_get_field_info
// returns. Unless the query was parsed completely, we cannot do that.
msg = create_parse_error(my_instance, "parsed completely", query, &matches);
goto queryresolved;
case QUERY_OP_SELECT:
case QUERY_OP_UPDATE:
case QUERY_OP_INSERT:
case QUERY_OP_DELETE:
// In these cases, we have to be able to trust what qc_get_field_info
// returns. Unless the query was parsed completely, we cannot do that.
msg = create_parse_error(my_instance, "parsed completely", query, &matches);
goto queryresolved;
default:
break;
default:
break;
}
}
}
@ -1762,12 +1788,12 @@ bool rule_matches(FW_INSTANCE* my_instance,
is_real = false;
}
if (rulelist->rule->on_queries == QUERY_OP_UNDEFINED ||
rulelist->rule->on_queries & optype ||
if (rulebook->rule->on_queries == QUERY_OP_UNDEFINED ||
rulebook->rule->on_queries & optype ||
(MYSQL_IS_COM_INIT_DB((uint8_t*)GWBUF_DATA(queue)) &&
rulelist->rule->on_queries & QUERY_OP_CHANGE_DB))
rulebook->rule->on_queries & QUERY_OP_CHANGE_DB))
{
switch (rulelist->rule->type)
switch (rulebook->rule->type)
{
case RT_UNDEFINED:
MXS_ERROR("Undefined rule type found.");
@ -1777,11 +1803,11 @@ bool rule_matches(FW_INSTANCE* my_instance,
if (query)
{
pcre2_match_data *mdata = pcre2_match_data_create_from_pattern(
rulelist->rule->data, NULL);
rulebook->rule->data, NULL);
if (mdata)
{
if (pcre2_match((pcre2_code*) rulelist->rule->data,
if (pcre2_match((pcre2_code*) rulebook->rule->data,
(PCRE2_SPTR) query, PCRE2_ZERO_TERMINATED,
0, 0, mdata, NULL) > 0)
{
@ -1791,7 +1817,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
if (matches)
{
msg = MXS_STRDUP_A("Permission denied, query matched regular expression.");
MXS_INFO("dbfwfilter: rule '%s': regex matched on query", rulelist->rule->name);
MXS_INFO("dbfwfilter: rule '%s': regex matched on query", rulebook->rule->name);
goto queryresolved;
}
}
@ -1804,15 +1830,15 @@ bool rule_matches(FW_INSTANCE* my_instance,
break;
case RT_PERMISSION:
{
matches = true;
msg = MXS_STRDUP_A("Permission denied at this time.");
char buffer[32]; // asctime documentation requires 26
asctime_r(&tm_now, buffer);
MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulelist->rule->name, buffer);
goto queryresolved;
}
break;
{
matches = true;
msg = MXS_STRDUP_A("Permission denied at this time.");
char buffer[32]; // asctime documentation requires 26
asctime_r(&tm_now, buffer);
MXS_INFO("dbfwfilter: rule '%s': query denied at: %s", rulebook->rule->name, buffer);
goto queryresolved;
}
break;
case RT_COLUMN:
if (is_sql && is_real)
@ -1825,7 +1851,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
{
const char* tok = infos[i].column;
STRLINK* strln = (STRLINK*) rulelist->rule->data;
STRLINK* strln = (STRLINK*) rulebook->rule->data;
while (strln)
{
if (strcasecmp(tok, strln->value) == 0)
@ -1834,7 +1860,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
sprintf(emsg, "Permission denied to column '%s'.", strln->value);
MXS_INFO("dbfwfilter: rule '%s': query targets forbidden column: %s",
rulelist->rule->name, strln->value);
rulebook->rule->name, strln->value);
msg = MXS_STRDUP_A(emsg);
goto queryresolved;
}
@ -1860,7 +1886,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
matches = true;
msg = MXS_STRDUP_A("Usage of wildcard denied.");
MXS_INFO("dbfwfilter: rule '%s': query contains a wildcard.",
rulelist->rule->name);
rulebook->rule->name);
goto queryresolved;
}
}
@ -1873,7 +1899,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
* and initialize a new QUERYSPEED struct for this session.
*/
spinlock_acquire(&my_instance->lock);
rule_qs = (QUERYSPEED*) rulelist->rule->data;
rule_qs = (QUERYSPEED*) rulebook->rule->data;
spinlock_release(&my_instance->lock);
spinlock_acquire(&user->lock);
@ -1913,7 +1939,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
sprintf(emsg, "Queries denied for %f seconds", blocked_for);
MXS_INFO("dbfwfilter: rule '%s': user denied for %f seconds",
rulelist->rule->name, blocked_for);
rulebook->rule->name, blocked_for);
msg = MXS_STRDUP_A(emsg);
matches = true;
}
@ -1933,7 +1959,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
MXS_INFO("dbfwfilter: rule '%s': query limit triggered (%d queries in %d seconds), "
"denying queries from user for %d seconds.",
rulelist->rule->name,
rulebook->rule->name,
queryspeed->limit,
queryspeed->period,
queryspeed->cooldown);
@ -1962,7 +1988,7 @@ bool rule_matches(FW_INSTANCE* my_instance,
matches = true;
msg = MXS_STRDUP_A("Required WHERE/HAVING clause is missing.");
MXS_INFO("dbfwfilter: rule '%s': query has no where/having "
"clause, query is denied.", rulelist->rule->name);
"clause, query is denied.", rulebook->rule->name);
}
break;
@ -1985,45 +2011,45 @@ queryresolved:
if (matches)
{
rulelist->rule->times_matched++;
rulebook->rule->times_matched++;
}
return matches;
}
/**
* Check if the query matches any of the rules in the user's rulelist.
* Check if the query matches any of the rules in the user's rulebook.
* @param my_instance Fwfilter instance
* @param my_session Fwfilter session
* @param queue The GWBUF containing the query
* @param user The user whose rulelist is checked
* @param user The user whose rulebook is checked
* @return True if the query matches at least one of the rules otherwise false
*/
bool check_match_any(FW_INSTANCE* my_instance, FW_SESSION* my_session,
GWBUF *queue, USER* user, char** rulename)
GWBUF *queue, DBFW_USER* user, char** rulename)
{
RULELIST* rulelist;
RULE_BOOK* rulebook;
bool rval = false;
if ((rulelist = user->rules_or) &&
if ((rulebook = user->rules_or) &&
(modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue) ||
MYSQL_IS_COM_INIT_DB((uint8_t*)GWBUF_DATA(queue))))
{
char *fullquery = modutil_get_SQL(queue);
while (rulelist)
while (rulebook)
{
if (!rule_is_active(rulelist->rule))
if (!rule_is_active(rulebook->rule))
{
rulelist = rulelist->next;
rulebook = rulebook->next;
continue;
}
if (rule_matches(my_instance, my_session, queue, user, rulelist, fullquery))
if (rule_matches(my_instance, my_session, queue, user, rulebook, fullquery))
{
*rulename = MXS_STRDUP_A(rulelist->rule->name);
*rulename = MXS_STRDUP_A(rulebook->rule->name);
rval = true;
break;
}
rulelist = rulelist->next;
rulebook = rulebook->next;
}
MXS_FREE(fullquery);
@ -2067,39 +2093,39 @@ void append_string(char** dest, size_t* size, const char* src)
}
/**
* Check if the query matches all rules in the user's rulelist.
* Check if the query matches all rules in the user's rulebook.
* @param my_instance Fwfilter instance
* @param my_session Fwfilter session
* @param queue The GWBUF containing the query
* @param user The user whose rulelist is checked
* @param user The user whose rulebook is checked
* @return True if the query matches all of the rules otherwise false
*/
bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session,
GWBUF *queue, USER* user, bool strict_all, char** rulename)
GWBUF *queue, DBFW_USER* user, bool strict_all, char** rulename)
{
bool rval = false;
bool have_active_rule = false;
RULELIST* rulelist = strict_all ? user->rules_strict_and : user->rules_and;
RULE_BOOK* rulebook = strict_all ? user->rules_strict_and : user->rules_and;
char *matched_rules = NULL;
size_t size = 0;
if (rulelist && (modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue)))
if (rulebook && (modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue)))
{
char *fullquery = modutil_get_SQL(queue);
rval = true;
while (rulelist)
while (rulebook)
{
if (!rule_is_active(rulelist->rule))
if (!rule_is_active(rulebook->rule))
{
rulelist = rulelist->next;
rulebook = rulebook->next;
continue;
}
have_active_rule = true;
if (rule_matches(my_instance, my_session, queue, user, rulelist, fullquery))
if (rule_matches(my_instance, my_session, queue, user, rulebook, fullquery))
{
append_string(&matched_rules, &size, rulelist->rule->name);
append_string(&matched_rules, &size, rulebook->rule->name);
}
else
{
@ -2110,7 +2136,7 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session,
}
}
rulelist = rulelist->next;
rulebook = rulebook->next;
}
if (!have_active_rule)
@ -2135,17 +2161,17 @@ bool check_match_all(FW_INSTANCE* my_instance, FW_SESSION* my_session,
* @param remote Remove network address
* @return The user data or NULL if it was not found
*/
USER* find_user_data(HASHTABLE *hash, const char *name, const char *remote)
DBFW_USER* find_user_data(HASHTABLE *hash, const char *name, const char *remote)
{
char nameaddr[strlen(name) + strlen(remote) + 2];
snprintf(nameaddr, sizeof(nameaddr), "%s@%s", name, remote);
USER* user = (USER*) hashtable_fetch(hash, nameaddr);
DBFW_USER* user = (DBFW_USER*) hashtable_fetch(hash, nameaddr);
if (user == NULL)
{
char *ip_start = strchr(nameaddr, '@') + 1;
while (user == NULL && next_ip_class(ip_start))
{
user = (USER*) hashtable_fetch(hash, nameaddr);
user = (DBFW_USER*) hashtable_fetch(hash, nameaddr);
}
if (user == NULL)
@ -2154,7 +2180,7 @@ USER* find_user_data(HASHTABLE *hash, const char *name, const char *remote)
ip_start = strchr(nameaddr, '@') + 1;
while (user == NULL && next_ip_class(ip_start))
{
user = (USER*) hashtable_fetch(hash, nameaddr);
user = (DBFW_USER*) hashtable_fetch(hash, nameaddr);
}
}
}
@ -2191,7 +2217,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
}
else
{
USER *user = find_user_data(my_instance->htable, dcb->user, dcb->remote);
DBFW_USER *user = find_user_data(my_instance->users, dcb->user, dcb->remote);
bool query_ok = false;
if (user)
@ -2335,86 +2361,3 @@ static uint64_t getCapabilities(void)
{
return RCAP_TYPE_STMT_INPUT;
}
#ifdef BUILD_RULE_PARSER
// TODO: Not ok to include file from other component's test directory.
#include "../../../core/test/test_utils.h"
int main(int argc, char** argv)
{
char ch;
bool have_icase = false;
char *home;
char cwd[PATH_MAX];
char* opts[2] = {NULL, NULL};
FILTER_PARAMETER ruleparam;
FILTER_PARAMETER * paramlist[2];
opterr = 0;
while ((ch = getopt(argc, argv, "h?")) != -1)
{
switch (ch)
{
case '?':
case 'h':
printf("Usage: %s [OPTION]... RULEFILE\n"
"Options:\n"
"\t-?\tPrint this information\n",
argv[0]);
return 0;
default:
printf("Unknown option '%c'.\n", ch);
return 1;
}
}
if (argc < 2)
{
printf("Usage: %s [OPTION]... RULEFILE\n"
"-?\tPrint this information\n",
argv[0]);
return 1;
}
home = MXS_MALLOC(sizeof(char) * (PATH_MAX + 1));
MXS_ABORT_IF_NULL(home);
if (getcwd(home, PATH_MAX) == NULL)
{
MXS_FREE(home);
home = NULL;
}
printf("Log files written to: %s\n", home ? home : "/tpm");
int argc_ = 2;
char* argv_[] =
{
"log_manager",
"-o",
NULL
};
mxs_log_init(NULL, NULL, MXS_LOG_TARGET_DEFAULT);
init_test_env(home);
ruleparam.name = MXS_STRDUP_A("rules");
ruleparam.value = MXS_STRDUP_A(argv[1]);
paramlist[0] = &ruleparam;
paramlist[1] = NULL;
if (createInstance(opts, paramlist))
{
printf("Rule parsing was successful.\n");
}
else
{
printf("Failed to parse rule. Read the error log for the reason of the failure.\n");
}
mxs_log_flush_sync();
return 0;
}
#endif

View File

@ -250,41 +250,11 @@ static void
diagnostics(DCB *dcb, const MONITOR *mon)
{
const GALERA_MONITOR *handle = (const GALERA_MONITOR *) mon->handle;
MONITOR_SERVERS *db;
char *sep;
switch (handle->status)
{
case MONITOR_RUNNING:
dcb_printf(dcb, "\tMonitor running\n");
break;
case MONITOR_STOPPING:
dcb_printf(dcb, "\tMonitor stopping\n");
break;
case MONITOR_STOPPED:
dcb_printf(dcb, "\tMonitor stopped\n");
break;
}
dcb_printf(dcb, "\tSampling interval:\t%lu milliseconds\n", mon->interval);
dcb_printf(dcb, "\tMaster Failback:\t%s\n", (handle->disableMasterFailback == 1) ? "off" : "on");
dcb_printf(dcb, "\tAvailable when Donor:\t%s\n", (handle->availableWhenDonor == 1) ? "on" : "off");
dcb_printf(dcb, "\tMaster Role Setting Disabled:\t%s\n",
dcb_printf(dcb, "Master Failback:\t%s\n", (handle->disableMasterFailback == 1) ? "off" : "on");
dcb_printf(dcb, "Available when Donor:\t%s\n", (handle->availableWhenDonor == 1) ? "on" : "off");
dcb_printf(dcb, "Master Role Setting Disabled:\t%s\n",
(handle->disableMasterRoleSetting == 1) ? "on" : "off");
dcb_printf(dcb, "\tConnect Timeout:\t%i seconds\n", mon->connect_timeout);
dcb_printf(dcb, "\tRead Timeout:\t\t%i seconds\n", mon->read_timeout);
dcb_printf(dcb, "\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout);
dcb_printf(dcb, "\tMonitored servers: ");
db = mon->databases;
sep = "";
while (db)
{
dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port);
sep = ", ";
db = db->next;
}
dcb_printf(dcb, "\n");
}
/**

View File

@ -218,35 +218,8 @@ stopMonitor(MONITOR *mon)
static void diagnostics(DCB *dcb, const MONITOR *mon)
{
const MM_MONITOR *handle = (const MM_MONITOR *) mon->handle;
MONITOR_SERVERS *db;
char *sep;
switch (handle->status)
{
case MONITOR_RUNNING:
dcb_printf(dcb, "\tMonitor running\n");
break;
case MONITOR_STOPPING:
dcb_printf(dcb, "\tMonitor stopping\n");
break;
case MONITOR_STOPPED:
dcb_printf(dcb, "\tMonitor stopped\n");
break;
}
dcb_printf(dcb, "\tSampling interval:\t%lu milliseconds\n", mon->interval);
dcb_printf(dcb, "\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "\tMonitored servers: ");
db = mon->databases;
sep = "";
while (db)
{
dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port);
sep = ", ";
db = db->next;
}
dcb_printf(dcb, "\n");
dcb_printf(dcb, "Detect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
}
/**

View File

@ -403,35 +403,14 @@ stopMonitor(MONITOR *mon)
*/
static void diagnostics(DCB *dcb, const MONITOR *mon)
{
const MYSQL_MONITOR *handle = (const MYSQL_MONITOR *) mon->handle;
MONITOR_SERVERS *db;
char *sep;
const MYSQL_MONITOR *handle = (const MYSQL_MONITOR *)mon->handle;
switch (handle->status)
{
case MONITOR_RUNNING:
dcb_printf(dcb, "\tMonitor running\n");
break;
case MONITOR_STOPPING:
dcb_printf(dcb, "\tMonitor stopping\n");
break;
case MONITOR_STOPPED:
dcb_printf(dcb, "\tMonitor stopped\n");
break;
}
dcb_printf(dcb, "MaxScale MonitorId:\t%lu\n", handle->id);
dcb_printf(dcb, "Replication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "Detect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "Server information\n\n");
dcb_printf(dcb, "\tSampling interval:\t%lu milliseconds\n", mon->interval);
dcb_printf(dcb, "\tMaxScale MonitorId:\t%lu\n", handle->id);
dcb_printf(dcb, "\tReplication lag:\t%s\n", (handle->replicationHeartbeat == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "\tDetect Stale Master:\t%s\n", (handle->detectStaleMaster == 1) ? "enabled" : "disabled");
dcb_printf(dcb, "\tConnect Timeout:\t%i seconds\n", mon->connect_timeout);
dcb_printf(dcb, "\tRead Timeout:\t\t%i seconds\n", mon->read_timeout);
dcb_printf(dcb, "\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout);
dcb_printf(dcb, "\nMonitored servers\n\n");
db = mon->databases;
while (db)
for (MONITOR_SERVERS *db = mon->databases; db; db = db->next)
{
MYSQL_SERVER_INFO *serv_info = hashtable_fetch(handle->server_info, db->server->unique_name);
dcb_printf(dcb, "Server: %s\n", db->server->unique_name);
@ -450,7 +429,6 @@ static void diagnostics(DCB *dcb, const MONITOR *mon)
}
dcb_printf(dcb, "\n");
db = db->next;
}
}

View File

@ -210,38 +210,6 @@ stopMonitor(MONITOR *mon)
static void
diagnostics(DCB *dcb, const MONITOR *mon)
{
const MYSQL_MONITOR *handle = (const MYSQL_MONITOR *) mon->handle;
MONITOR_SERVERS *db;
char *sep;
switch (handle->status)
{
case MONITOR_RUNNING:
dcb_printf(dcb, "\tMonitor running\n");
break;
case MONITOR_STOPPING:
dcb_printf(dcb, "\tMonitor stopping\n");
break;
case MONITOR_STOPPED:
dcb_printf(dcb, "\tMonitor stopped\n");
break;
}
dcb_printf(dcb, "\tSampling interval:\t%lu milliseconds\n", mon->interval);
dcb_printf(dcb, "\tConnect Timeout:\t%i seconds\n", mon->connect_timeout);
dcb_printf(dcb, "\tRead Timeout:\t\t%i seconds\n", mon->read_timeout);
dcb_printf(dcb, "\tWrite Timeout:\t\t%i seconds\n", mon->write_timeout);
dcb_printf(dcb, "\tMonitored servers: ");
db = mon->databases;
sep = "";
while (db)
{
dcb_printf(dcb, "%s%s:%d", sep, db->server->name, db->server->port);
sep = ", ";
db = db->next;
}
dcb_printf(dcb, "\n");
}
/**

View File

@ -40,9 +40,9 @@
#include <maxscale/log_manager.h>
#include <maxscale/resultset.h>
/* @see function load_module in load_utils.c for explanation of the following
* lint directives.
*/
/* @see function load_module in load_utils.c for explanation of the following
* lint directives.
*/
/*lint -e14 */
MODULE_INFO info =
{
@ -148,9 +148,9 @@ static int httpd_read_event(DCB* dcb)
SESSION *session = dcb->session;
int numchars = 1;
char buf[HTTPD_REQUESTLINE_MAXLEN-1] = "";
char buf[HTTPD_REQUESTLINE_MAXLEN - 1] = "";
char *query_string = NULL;
char method[HTTPD_METHOD_MAXLEN-1] = "";
char method[HTTPD_METHOD_MAXLEN - 1] = "";
char url[HTTPD_SMALL_BUFFER] = "";
size_t i, j;
int headers_read = 0;
@ -166,11 +166,13 @@ static int httpd_read_event(DCB* dcb)
numchars = httpd_get_line(dcb->fd, buf, sizeof(buf));
i = 0; j = 0;
i = 0;
j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
i++;
j++;
}
method[i] = '\0';
@ -193,7 +195,8 @@ static int httpd_read_event(DCB* dcb)
while ((j < sizeof(buf) - 1) && !ISspace(buf[j]) && (i < sizeof(url) - 1))
{
url[i] = buf[j];
i++; j++;
i++;
j++;
}
url[i] = '\0';
@ -233,7 +236,7 @@ static int httpd_read_event(DCB* dcb)
{
*value = '\0';
value++;
end = &value[strlen(value) -1];
end = &value[strlen(value) - 1];
*end = '\0';
if (strncasecmp(buf, "Hostname", 6) == 0)

View File

@ -734,37 +734,42 @@ struct subcommand disableoptions[] =
static void telnetdAddUser(DCB *, char *user, char *password);
static void cmd_AddServer(DCB *dcb, void *a, void *b)
static void cmd_AddServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
char *v10, char *v11)
{
SERVER *server = (SERVER*)a;
char *name = (char*)b;
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
const int items = sizeof(values) / sizeof(values[0]);
SERVICE *service = service_find(name);
MONITOR *monitor = monitor_find(name);
if (service || monitor)
for (int i = 0; i < items && values[i]; i++)
{
ss_dassert(service == NULL || monitor == NULL);
SERVICE *service = service_find(values[i]);
MONITOR *monitor = monitor_find(values[i]);
if (service)
if (service || monitor)
{
serviceAddBackend(service, server);
service_serialize_servers(service);
ss_dassert(service == NULL || monitor == NULL);
if (service)
{
serviceAddBackend(service, server);
service_serialize_servers(service);
}
else if (monitor)
{
monitorAddServer(monitor, server);
monitor_serialize_servers(monitor);
}
const char *target = service ? "service" : "monitor";
MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, target, values[i]);
dcb_printf(dcb, "Added server '%s' to %s '%s'\n", server->unique_name, target, values[i]);
}
else if (monitor)
else
{
monitorAddServer(monitor, server);
monitor_serialize_servers(monitor);
dcb_printf(dcb, "No service or monitor with the name '%s'\n", values[i]);
}
const char *target = service ? "service" : "monitor";
MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, target, name);
dcb_printf(dcb, "Added server '%s' to %s '%s'\n", server->unique_name, target, name);
}
else
{
dcb_printf(dcb, "No service or monitor with the name '%s'\n", name);
}
}
@ -781,11 +786,15 @@ struct subcommand addoptions[] =
{ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
},
{
"server", 2, 2, cmd_AddServer,
"server", 2, 12, cmd_AddServer,
"Add a new server to a service",
"Usage: add server SERVER TARGET\n"
"The TARGET must be either a service or a monitor",
{ARG_TYPE_SERVER, ARG_TYPE_STRING, 0}
"Usage: add server SERVER TARGET...\n"
"The TARGET must be a list of service and monitor names\n"
"e.g. add server my-db my-service 'Cluster Monitor'\n"
"A server can be assigned to a maximum of 11 objects in one command",
{ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING}
},
{ EMPTY_OPTION}
};
@ -793,35 +802,41 @@ struct subcommand addoptions[] =
static void telnetdRemoveUser(DCB *, char *user, char *password);
static void cmd_RemoveServer(DCB *dcb, void *a, void *b)
static void cmd_RemoveServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
char *v10, char *v11)
{
SERVER *server = (SERVER*)a;
char *name = (char*)b;
SERVICE *service = service_find(name);
MONITOR *monitor = monitor_find(name);
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
const int items = sizeof(values) / sizeof(values[0]);
if (service || monitor)
for (int i = 0; i < items && values[i]; i++)
{
ss_dassert(service == NULL || monitor == NULL);
SERVICE *service = service_find(values[i]);
MONITOR *monitor = monitor_find(values[i]);
if (service)
if (service || monitor)
{
serviceRemoveBackend(service, server);
service_serialize_servers(service);
}
else if (monitor)
{
monitorRemoveServer(monitor, server);
monitor_serialize_servers(monitor);
}
ss_dassert(service == NULL || monitor == NULL);
const char *target = service ? "service" : "monitor";
MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, target, name);
dcb_printf(dcb, "Removed server '%s' from %s '%s'\n", server->unique_name, target, name);
}
else
{
dcb_printf(dcb, "No service or monitor with the name '%s'\n", name);
if (service)
{
serviceRemoveBackend(service, server);
service_serialize_servers(service);
}
else if (monitor)
{
monitorRemoveServer(monitor, server);
monitor_serialize_servers(monitor);
}
const char *target = service ? "service" : "monitor";
MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, target, values[i]);
dcb_printf(dcb, "Removed server '%s' from %s '%s'\n", server->unique_name, target, values[i]);
}
else
{
dcb_printf(dcb, "No service or monitor with the name '%s'\n", values[i]);
}
}
}
@ -840,11 +855,15 @@ struct subcommand removeoptions[] =
{ARG_TYPE_STRING, ARG_TYPE_STRING}
},
{
"server", 2, 2, cmd_RemoveServer,
"server", 2, 12, cmd_RemoveServer,
"Remove a server from a service or a monitor",
"Usage: remove server SERVER TARGET\n"
"The TARGET must be either a service or a monitor",
{ARG_TYPE_SERVER, ARG_TYPE_STRING}
"Usage: remove server SERVER TARGET...\n"
"The TARGET must be a list of service and monitor names\n"
"e.g. remove server my-db my-service 'Cluster Monitor'\n"
"A server can be removed from a maximum of 11 objects in one command",
{ARG_TYPE_SERVER, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING}
},
{
EMPTY_OPTION
@ -1116,41 +1135,38 @@ static void alterServer(DCB *dcb, SERVER *server, char *v1, char *v2, char *v3,
const int items = sizeof(values) / sizeof(values[0]);
CONFIG_CONTEXT *obj = NULL;
for (int i = 0; i < items; i++)
for (int i = 0; i < items && values[i]; i++)
{
if (values[i])
char *key = values[i];
char *value = strchr(key, '=');
if (value)
{
char *key = values[i];
char *value = strchr(key, '=');
*value++ = '\0';
if (value)
if (config_is_ssl_parameter(key))
{
*value++ = '\0';
if (config_is_ssl_parameter(key))
/**
* All the required SSL parameters must be defined at once to
* enable SSL for created servers. This removes the problem
* of partial configuration and allows a somewhat atomic
* operation.
*/
if ((obj == NULL && (obj = config_context_create(server->unique_name)) == NULL) ||
(!config_add_param(obj, key, value)))
{
/**
* All the required SSL parameters must be defined at once to
* enable SSL for created servers. This removes the problem
* of partial configuration and allows a somewhat atomic
* operation.
*/
if ((obj == NULL && (obj = config_context_create(server->unique_name)) == NULL) ||
(!config_add_param(obj, key, value)))
{
dcb_printf(dcb, "Internal error, see log for more details\n");
}
}
else if (!handle_alter_server(server, key, value))
{
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
dcb_printf(dcb, "Internal error, see log for more details\n");
}
}
else
else if (!handle_alter_server(server, key, value))
{
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
}
}
else
{
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
}
}
if (obj)
@ -1243,27 +1259,24 @@ static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v
char *values[11] = {v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11};
const int items = sizeof(values) / sizeof(values[0]);
for (int i = 0; i < items; i++)
for (int i = 0; i < items && values[i]; i++)
{
if (values[i])
char *key = values[i];
char *value = strchr(key, '=');
if (value)
{
char *key = values[i];
char *value = strchr(key, '=');
*value++ = '\0';
if (value)
if (!handle_alter_monitor(monitor, key, value))
{
*value++ = '\0';
if (!handle_alter_monitor(monitor, key, value))
{
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
}
}
else
{
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
}
}
else
{
dcb_printf(dcb, "Error: not a key-value parameter: %s\n", values[i]);
}
}
}

View File

@ -801,8 +801,8 @@ typedef void *(*STATSFUNC)();
*/
static struct
{
char *name;
int type;
char *name;
int type;
STATSFUNC func;
} variables[] =
{
@ -820,10 +820,9 @@ static struct
typedef struct
{
int index;
int index;
char *like;
} VARCONTEXT;
/**
* Callback function to populate rows of the show variable
* command
@ -860,10 +859,14 @@ variable_row(RESULTSET *result, void *data)
(long)(*variables[context->index].func)());
resultset_row_set(row, 1, buf);
break;
default:
ss_dassert(!true);
}
context->index++;
return row;
}
// We only get to this point once all variables have been printed
MXS_FREE(data);
return NULL;
}
@ -909,15 +912,19 @@ RESULTSET *
maxinfo_variables()
{
RESULTSET *result;
static VARCONTEXT context;
context.like = NULL;
context.index = 0;
if ((result = resultset_create(variable_row, &context)) == NULL)
VARCONTEXT *context;
if ((context = MXS_MALLOC(sizeof(VARCONTEXT))) == NULL)
{
return NULL;
}
context->like = NULL;
context->index = 0;
if ((result = resultset_create(variable_row, context)) == NULL)
{
MXS_FREE(context);
return NULL;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
return result;
@ -1138,10 +1145,14 @@ status_row(RESULTSET *result, void *data)
(long)(*status[context->index].func)());
resultset_row_set(row, 1, buf);
break;
default:
ss_dassert(!true);
}
context->index++;
return row;
}
// We only get to this point once all status elements have been printed
MXS_FREE(data);
return NULL;
}
@ -1186,16 +1197,20 @@ exec_show_status(DCB *dcb, MAXINFO_TREE *filter)
RESULTSET *
maxinfo_status()
{
RESULTSET *result;
static VARCONTEXT context;
context.like = NULL;
context.index = 0;
if ((result = resultset_create(status_row, &context)) == NULL)
RESULTSET *result;
VARCONTEXT *context;
if ((context = MXS_MALLOC(sizeof(VARCONTEXT))) == NULL)
{
return NULL;
}
context->like = NULL;
context->index = 0;
if ((result = resultset_create(status_row, context)) == NULL)
{
MXS_FREE(context);
return NULL;
}
resultset_add_column(result, "Variable_name", 40, COL_TYPE_VARCHAR);
resultset_add_column(result, "Value", 40, COL_TYPE_VARCHAR);
return result;
@ -1220,14 +1235,13 @@ exec_select(DCB *dcb, MAXINFO_TREE *tree)
*
* @param pattern Pattern to match
* @param str String to match against pattern
* @return Zero on match
* @return Zero on match
*/
static int
maxinfo_pattern_match(char *pattern, char *str)
{
int anchor = 0, len, trailing;
char *fixed;
extern char *strcasestr();
if (*pattern != '%')
{

View File

@ -718,7 +718,7 @@ return_succp:
* if the query would otherwise be routed to slave.
*/
route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
qc_query_type_t qtype, HINT *hint)
qc_query_type_t qtype, HINT *hint)
{
bool trx_active = rses->rses_transaction_active;
bool load_active = rses->rses_load_active;
@ -735,9 +735,10 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
*/
else if (!load_active &&
(qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) ||
/** Configured to allow writing variables to all nodes */
/** Configured to allow writing user variables to all nodes */
(use_sql_variables_in == TYPE_ALL &&
qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE)) ||
qc_query_is_type(qtype, QUERY_TYPE_USERVAR_WRITE)) ||
qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) ||
/** enable or disable autocommit are always routed to all */
qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) ||
qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)))
@ -777,44 +778,29 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
* Hints may affect on routing of the following queries
*/
else if (!trx_active && !load_active &&
!qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) &&
!qc_query_is_type(qtype, QUERY_TYPE_WRITE) &&
(qc_query_is_type(qtype, QUERY_TYPE_READ) || /*< any SELECT */
qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */
qc_query_is_type(qtype,
QUERY_TYPE_USERVAR_READ) || /*< read user var */
qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || /*< read sys var */
qc_query_is_type(qtype,
QUERY_TYPE_EXEC_STMT) || /*< prepared stmt exec */
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) ||
qc_query_is_type(qtype,
QUERY_TYPE_GSYSVAR_READ))) /*< read global sys var */
(qc_query_is_type(qtype, QUERY_TYPE_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) ||
qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)))
{
/** First set expected targets before evaluating hints */
if (!qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) &&
(qc_query_is_type(qtype, QUERY_TYPE_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */
/** Configured to allow reading variables from slaves */
(use_sql_variables_in == TYPE_ALL &&
(qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)))))
if (qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ))
{
if (use_sql_variables_in == TYPE_ALL)
{
target = TARGET_SLAVE;
}
}
else if (qc_query_is_type(qtype, QUERY_TYPE_READ) || // Normal read
qc_query_is_type(qtype, QUERY_TYPE_SHOW_TABLES) || // SHOW TABLES
qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ) || // System variable
qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_READ)) // Global system variable
{
target = TARGET_SLAVE;
}
if (qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) ||
/** Configured not to allow reading variables from slaves */
(use_sql_variables_in == TYPE_MASTER &&
(qc_query_is_type(qtype, QUERY_TYPE_USERVAR_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SYSVAR_READ))))
{
target = TARGET_MASTER;
}
/** If nothing matches then choose the master */
if ((target & (TARGET_ALL | TARGET_SLAVE | TARGET_MASTER)) == 0)
{
@ -823,8 +809,7 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
}
else
{
/** hints don't affect on routing */
ss_dassert(trx_active ||
ss_dassert(trx_active || load_active ||
(qc_query_is_type(qtype, QUERY_TYPE_WRITE) ||
qc_query_is_type(qtype, QUERY_TYPE_MASTER_READ) ||
qc_query_is_type(qtype, QUERY_TYPE_SESSION_WRITE) ||
@ -836,6 +821,8 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
use_sql_variables_in == TYPE_MASTER) ||
(qc_query_is_type(qtype, QUERY_TYPE_GSYSVAR_WRITE) &&
use_sql_variables_in == TYPE_MASTER) ||
(qc_query_is_type(qtype, QUERY_TYPE_USERVAR_WRITE) &&
use_sql_variables_in == TYPE_MASTER) ||
qc_query_is_type(qtype, QUERY_TYPE_BEGIN_TRX) ||
qc_query_is_type(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) ||
qc_query_is_type(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT) ||
@ -844,7 +831,10 @@ route_target_t get_route_target(ROUTER_CLIENT_SES *rses,
qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_CREATE_TMP_TABLE) ||
qc_query_is_type(qtype, QUERY_TYPE_READ_TMP_TABLE) ||
qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN)));
qc_query_is_type(qtype, QUERY_TYPE_UNKNOWN)) ||
qc_query_is_type(qtype, QUERY_TYPE_EXEC_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_STMT) ||
qc_query_is_type(qtype, QUERY_TYPE_PREPARE_NAMED_STMT));
target = TARGET_MASTER;
}