Develop merge
Develop merge: this includes the new handling of events larger than 16MBytes
This commit is contained in:
commit
b2e8a2d8c5
@ -14,6 +14,25 @@ options. The `authenticator_options` parameter is supported by listeners
|
||||
and servers and expects a comma-separated list of key-value pairs. The
|
||||
following options contain examples on how to define it.
|
||||
|
||||
### `skip_authentication`
|
||||
|
||||
This option takes a boolean value which controls whether MaxScale will fully
|
||||
authenticate users. This option is disabled by default.
|
||||
|
||||
Disabling authentication in MaxScale will allow MaxScale to act as a security
|
||||
gateway to the server. The authentication of users is offloaded to the backend
|
||||
server.
|
||||
|
||||
For example, creating the user `jdoe@%` will allow the user _jdoe_ to connect
|
||||
from any IP address. This can be a problem if all traffic needs to go through
|
||||
MaxScale. By enabling this option and replacing the user with
|
||||
`jdoe@maxscale-IP`, the users can still connect from any client IP but will be
|
||||
forced to go though MaxScale.
|
||||
|
||||
```
|
||||
authenticator_options=skip_authentication=true
|
||||
```
|
||||
|
||||
### `cache_dir`
|
||||
|
||||
The location where the user credential cache is stored. The default value
|
||||
|
@ -25,14 +25,18 @@ filters=MyLogFilter
|
||||
|
||||
The QLA filter accepts the following options.
|
||||
|
||||
|Option |Description |
|
||||
|----------|--------------------------------------------|
|
||||
|ignorecase|Use case-insensitive matching |
|
||||
|case |Use case-sensitive matching |
|
||||
|extended |Use extended regular expression syntax (ERE)|
|
||||
|session_file| Use session-specific file (default)|
|
||||
|unified_file| Use one file for all sessions|
|
||||
|flush_writes| Flush after every write|
|
||||
Option | Description
|
||||
-------| -----------
|
||||
ignorecase | Use case-insensitive matching
|
||||
case | Use case-sensitive matching
|
||||
extended | Use extended regular expression syntax (ERE)
|
||||
session_file | Write to session-specific files (default)
|
||||
unified_file | Use one file for all sessions
|
||||
flush_writes | Flush after every write
|
||||
append | Append log entries instead of overwriting files
|
||||
print_service | Add service name to log entries
|
||||
print_session | Add session id to log entries (ignored for session-files)
|
||||
|
||||
To use multiple filter options, list them in a comma-separated list. If no file settings are given, default will be used. Multiple file settings can be enabled simultaneously.
|
||||
|
||||
```
|
||||
|
@ -104,9 +104,7 @@ following new commands were added to maxadmin, see output of `maxadmin help
|
||||
With these new features, you can start MaxScale without the servers and define
|
||||
them later.
|
||||
|
||||
# Module commands
|
||||
|
||||
## Module commands
|
||||
### Module commands
|
||||
|
||||
Introduced in MaxScale 2.1, the module commands are special, module-specific
|
||||
commands. They allow the modules to expand beyound the capabilities of the
|
||||
@ -145,6 +143,13 @@ aimed for two node master-slave clusters where the slave can act as a
|
||||
master in case the original master fails. For more details, please read
|
||||
the [MySQL Monitor Documentation](../Monitors/MySQL-Monitor.md).
|
||||
|
||||
### Permissive authentication mode for MySQLAuth
|
||||
|
||||
The MySQL authentication module supports the `skip_authentication` option which
|
||||
allows authentication to always succedd in MaxScale. This option offloads the
|
||||
actual authentication to the backend server and it can be used to implement a
|
||||
secure version of a wildcard user.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
[Here is a list of bugs fixed since the release of MaxScale 2.0.X.](https://jira.mariadb.org/browse/MXS-739?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.0.0)
|
||||
|
@ -110,6 +110,7 @@ typedef struct config_context
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
bool config_check; /**< Only check config */
|
||||
int n_threads; /**< Number of polling threads */
|
||||
char *version_string; /**< The version string of embedded db library */
|
||||
char release_string[_RELEASE_STR_LENGTH]; /**< The release name string of the system */
|
||||
@ -238,7 +239,7 @@ bool config_set_qualified_param(CONFIG_PARAMETER* param,
|
||||
config_param_type_t type);
|
||||
int config_threadcount();
|
||||
int config_truth_value(char *);
|
||||
void free_config_parameter(CONFIG_PARAMETER* p1);
|
||||
void config_parameter_free(CONFIG_PARAMETER* p1);
|
||||
bool is_internal_service(const char *router);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -109,17 +109,17 @@ typedef struct filter_def
|
||||
struct filter_def *next; /**< Next filter in the chain of all filters */
|
||||
} FILTER_DEF;
|
||||
|
||||
FILTER_DEF *filter_alloc(char *, char *);
|
||||
FILTER_DEF *filter_alloc(const char *, const char *);
|
||||
void filter_free(FILTER_DEF *);
|
||||
bool filter_load(FILTER_DEF* filter);
|
||||
FILTER_DEF *filter_find(char *);
|
||||
void filterAddOption(FILTER_DEF *, char *);
|
||||
void filterAddParameter(FILTER_DEF *, char *, char *);
|
||||
DOWNSTREAM *filterApply(FILTER_DEF *, SESSION *, DOWNSTREAM *);
|
||||
UPSTREAM *filterUpstream(FILTER_DEF *, void *, UPSTREAM *);
|
||||
int filter_standard_parameter(char *);
|
||||
FILTER_DEF *filter_find(const char *);
|
||||
void filter_add_option(FILTER_DEF *, const char *);
|
||||
void filter_add_parameter(FILTER_DEF *, const char *, const char *);
|
||||
DOWNSTREAM *filter_apply(FILTER_DEF *, SESSION *, DOWNSTREAM *);
|
||||
UPSTREAM *filter_upstream(FILTER_DEF *, void *, UPSTREAM *);
|
||||
int filter_standard_parameter(const char *);
|
||||
void dprintAllFilters(DCB *);
|
||||
void dprintFilter(DCB *, FILTER_DEF *);
|
||||
void dprintFilter(DCB *, const FILTER_DEF *);
|
||||
void dListFilters(DCB *);
|
||||
|
||||
/**
|
||||
|
@ -55,14 +55,12 @@ typedef struct
|
||||
#define MODULECMD_ARG_NONE 0 /**< Empty argument */
|
||||
#define MODULECMD_ARG_STRING 1 /**< String */
|
||||
#define MODULECMD_ARG_BOOLEAN 2 /**< Boolean value */
|
||||
#define MODULECMD_ARG_SERVICE 3 /**< Service name */
|
||||
#define MODULECMD_ARG_SERVER 4 /**< Server name */
|
||||
#define MODULECMD_ARG_SESSION 5 /**< SESSION pointer in string format */
|
||||
#define MODULECMD_ARG_SESSION_PTR 6 /**< Raw SESSION pointer */
|
||||
#define MODULECMD_ARG_DCB 7 /**< DCB pointer in string format*/
|
||||
#define MODULECMD_ARG_DCB_PTR 8 /**< Raw DCB pointer*/
|
||||
#define MODULECMD_ARG_MONITOR 9 /**< Monitor name */
|
||||
#define MODULECMD_ARG_FILTER 10 /**< Filter name */
|
||||
#define MODULECMD_ARG_SERVICE 3 /**< Service */
|
||||
#define MODULECMD_ARG_SERVER 4 /**< Server */
|
||||
#define MODULECMD_ARG_SESSION 6 /**< Session */
|
||||
#define MODULECMD_ARG_DCB 8 /**< DCB */
|
||||
#define MODULECMD_ARG_MONITOR 9 /**< Monitor */
|
||||
#define MODULECMD_ARG_FILTER 10 /**< Filter */
|
||||
#define MODULECMD_ARG_OUTPUT 11 /**< DCB suitable for writing results to.
|
||||
This should always be the first argument
|
||||
if the function requires an output DCB. */
|
||||
@ -157,6 +155,20 @@ const MODULECMD* modulecmd_find_command(const char *domain, const char *identifi
|
||||
/**
|
||||
* @brief Parse arguments for a command
|
||||
*
|
||||
* The argument types expect different forms of input.
|
||||
*
|
||||
* | Argument type | Expected input |
|
||||
* |-----------------------|-------------------|
|
||||
* | MODULECMD_ARG_SERVICE | Service name |
|
||||
* | MODULECMD_ARG_SERVER | Server name |
|
||||
* | MODULECMD_ARG_SESSION | Session unique ID |
|
||||
* | MODULECMD_ARG_MONITOR | Monitor name |
|
||||
* | MODULECMD_ARG_FILTER | Filter name |
|
||||
* | MODULECMD_ARG_STRING | String |
|
||||
* | MODULECMD_ARG_BOOLEAN | Boolean value |
|
||||
* | MODULECMD_ARG_DCB | Raw DCB pointer |
|
||||
* | MODULECMD_ARG_OUTPUT | DCB for output |
|
||||
*
|
||||
* @param cmd Command for which the parameters are parsed
|
||||
* @param argc Number of arguments
|
||||
* @param argv Argument list in string format of size @c argc
|
||||
|
@ -199,6 +199,7 @@ struct monitor
|
||||
char *module_name; /**< Name of the monitor module */
|
||||
void *handle; /**< Handle returned from startMonitor */
|
||||
size_t interval; /**< The monitor interval */
|
||||
bool created_online; /**< Whether this monitor was created at runtime */
|
||||
struct monitor *next; /**< Next monitor in the linked list */
|
||||
};
|
||||
|
||||
@ -209,6 +210,7 @@ extern void monitorAddServer(MONITOR *mon, SERVER *server);
|
||||
extern void monitorRemoveServer(MONITOR *mon, SERVER *server);
|
||||
extern void monitorAddUser(MONITOR *, char *, char *);
|
||||
extern void monitorAddParameters(MONITOR *monitor, CONFIG_PARAMETER *params);
|
||||
extern bool monitorRemoveParameter(MONITOR *monitor, const char *key);
|
||||
extern void monitorStop(MONITOR *);
|
||||
extern void monitorStart(MONITOR *, void*);
|
||||
extern void monitorStopAll();
|
||||
|
@ -242,6 +242,26 @@ SERVICE* service_find(const char *name);
|
||||
// TODO: Change binlogrouter to use the functions in config_runtime.h
|
||||
void serviceAddBackend(SERVICE *service, SERVER *server);
|
||||
|
||||
/**
|
||||
* @brief Check if a service uses a server
|
||||
* @param service Service to check
|
||||
* @param server Server being used
|
||||
* @return True if service uses the server
|
||||
*/
|
||||
bool serviceHasBackend(SERVICE *service, SERVER *server);
|
||||
|
||||
/**
|
||||
* @brief Check if a service has a listener
|
||||
*
|
||||
* @param service Service to check
|
||||
* @param protocol Listener protocol
|
||||
* @param address Listener address
|
||||
* @param port Listener port
|
||||
* @return True if service has the listener
|
||||
*/
|
||||
bool serviceHasListener(SERVICE *service, const char *protocol,
|
||||
const char* address, unsigned short port);
|
||||
|
||||
int serviceGetUser(SERVICE *service, char **user, char **auth);
|
||||
int serviceSetUser(SERVICE *service, char *user, char *auth);
|
||||
bool serviceSetFilters(SERVICE *service, char *filters);
|
||||
|
@ -204,7 +204,6 @@ typedef struct session
|
||||
|
||||
SESSION *session_alloc(struct service *, struct dcb *);
|
||||
SESSION *session_set_dummy(struct dcb *);
|
||||
bool session_free(SESSION *);
|
||||
int session_isvalid(SESSION *);
|
||||
int session_reply(void *inst, void *session, GWBUF *data);
|
||||
char *session_get_remote(SESSION *);
|
||||
@ -339,4 +338,36 @@ static inline bool session_set_autocommit(SESSION* ses, bool autocommit)
|
||||
return prev_autocommit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a session reference by ID
|
||||
*
|
||||
* This creates an additional reference to a session whose unique ID matches @c id.
|
||||
*
|
||||
* @param id Unique session ID
|
||||
* @return Reference to a SESSION or NULL if the session was not found
|
||||
*
|
||||
* @note The caller must free the session reference by calling session_put_ref
|
||||
*/
|
||||
SESSION* session_get_by_id(int id);
|
||||
|
||||
/**
|
||||
* @brief Get a session reference
|
||||
*
|
||||
* This creates an additional reference to a session which allows it to live
|
||||
* as long as it is needed.
|
||||
*
|
||||
* @param session Session reference to get
|
||||
* @return Reference to a SESSION
|
||||
*
|
||||
* @note The caller must free the session reference by calling session_put_ref
|
||||
*/
|
||||
SESSION* session_get_ref(SESSION *sessoin);
|
||||
|
||||
/**
|
||||
* @brief Release a session reference
|
||||
*
|
||||
* @param session Session reference to release
|
||||
*/
|
||||
void session_put_ref(SESSION *session);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -928,11 +928,23 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
|
||||
|
||||
/** System session variable */
|
||||
case Item_func::GSYSVAR_FUNC:
|
||||
func_qtype |= QUERY_TYPE_SYSVAR_READ;
|
||||
MXS_DEBUG("%lu [resolve_query_type] "
|
||||
"functype GSYSVAR_FUNC, system "
|
||||
"variable read.",
|
||||
pthread_self());
|
||||
{
|
||||
const char* name = item->name;
|
||||
if (name &&
|
||||
((strcasecmp(name, "@@last_insert_id") == 0) ||
|
||||
(strcasecmp(name, "@@identity") == 0)))
|
||||
{
|
||||
func_qtype |= QUERY_TYPE_MASTER_READ;
|
||||
}
|
||||
else
|
||||
{
|
||||
func_qtype |= QUERY_TYPE_SYSVAR_READ;
|
||||
}
|
||||
MXS_DEBUG("%lu [resolve_query_type] "
|
||||
"functype GSYSVAR_FUNC, system "
|
||||
"variable read.",
|
||||
pthread_self());
|
||||
}
|
||||
break;
|
||||
|
||||
/** User-defined variable read */
|
||||
|
@ -833,7 +833,15 @@ static void update_field_infos(QC_SQLITE_INFO* info,
|
||||
}
|
||||
else
|
||||
{
|
||||
info->types |= QUERY_TYPE_SYSVAR_READ;
|
||||
if ((strcasecmp(&zToken[2], "identity") == 0) ||
|
||||
(strcasecmp(&zToken[2], "last_insert_id") == 0))
|
||||
{
|
||||
info->types |= QUERY_TYPE_MASTER_READ;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->types |= QUERY_TYPE_SYSVAR_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -12,3 +12,6 @@ QUERY_TYPE_BEGIN_TRX
|
||||
QUERY_TYPE_ROLLBACK
|
||||
QUERY_TYPE_COMMIT
|
||||
QUERY_TYPE_SESSION_WRITE
|
||||
QUERY_TYPE_READ|QUERY_TYPE_MASTER_READ
|
||||
QUERY_TYPE_READ|QUERY_TYPE_MASTER_READ
|
||||
QUERY_TYPE_READ|QUERY_TYPE_MASTER_READ
|
||||
|
@ -12,3 +12,6 @@ BEGIN;
|
||||
ROLLBACK;
|
||||
COMMIT;
|
||||
use X;
|
||||
select last_insert_id();
|
||||
select @@last_insert_id;
|
||||
select @@identity;
|
||||
|
@ -1070,7 +1070,7 @@ return_p2:
|
||||
* Free a configuration parameter
|
||||
* @param p1 Parameter to free
|
||||
*/
|
||||
void free_config_parameter(CONFIG_PARAMETER* p1)
|
||||
void config_parameter_free(CONFIG_PARAMETER* p1)
|
||||
{
|
||||
while (p1)
|
||||
{
|
||||
@ -1089,7 +1089,7 @@ void config_context_free(CONFIG_CONTEXT *context)
|
||||
while (context)
|
||||
{
|
||||
obj = context->next;
|
||||
free_config_parameter(context->parameters);
|
||||
config_parameter_free(context->parameters);
|
||||
MXS_FREE(context->object);
|
||||
MXS_FREE(context);
|
||||
context = obj;
|
||||
@ -1561,6 +1561,7 @@ global_defaults()
|
||||
{
|
||||
uint8_t mac_addr[6] = "";
|
||||
struct utsname uname_data;
|
||||
gateway.config_check = false;
|
||||
gateway.n_threads = DEFAULT_NTHREADS;
|
||||
gateway.n_nbpoll = DEFAULT_NBPOLLS;
|
||||
gateway.pollsleep = DEFAULT_POLLSLEEP;
|
||||
@ -3110,7 +3111,7 @@ int create_new_listener(CONFIG_CONTEXT *obj)
|
||||
SSL_LISTENER *ssl_info = make_ssl_structure(obj, true, &error_count);
|
||||
if (socket)
|
||||
{
|
||||
if (serviceHasProtocol(service, protocol, address, 0))
|
||||
if (serviceHasListener(service, protocol, address, 0))
|
||||
{
|
||||
MXS_ERROR("Listener '%s' for service '%s' already has a socket at '%s.",
|
||||
obj->object, service_name, socket);
|
||||
@ -3125,7 +3126,7 @@ int create_new_listener(CONFIG_CONTEXT *obj)
|
||||
|
||||
if (port)
|
||||
{
|
||||
if (serviceHasProtocol(service, protocol, address, atoi(port)))
|
||||
if (serviceHasListener(service, protocol, address, atoi(port)))
|
||||
{
|
||||
MXS_ERROR("Listener '%s', for service '%s', already have port %s.",
|
||||
obj->object,
|
||||
@ -3183,7 +3184,7 @@ int create_new_filter(CONFIG_CONTEXT *obj)
|
||||
char *s = strtok_r(options, ",", &lasts);
|
||||
while (s)
|
||||
{
|
||||
filterAddOption(obj->element, s);
|
||||
filter_add_option(obj->element, s);
|
||||
s = strtok_r(NULL, ",", &lasts);
|
||||
}
|
||||
}
|
||||
@ -3193,7 +3194,7 @@ int create_new_filter(CONFIG_CONTEXT *obj)
|
||||
{
|
||||
if (strcmp(params->name, "module") && strcmp(params->name, "options"))
|
||||
{
|
||||
filterAddParameter(obj->element, params->name, params->value);
|
||||
filter_add_parameter(obj->element, params->name, params->value);
|
||||
}
|
||||
params = params->next;
|
||||
}
|
||||
|
@ -361,8 +361,27 @@ bool runtime_alter_monitor(MONITOR *monitor, char *key, char *value)
|
||||
monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/** We're modifying module specific parameters and we need to stop the monitor */
|
||||
monitorStop(monitor);
|
||||
|
||||
if (valid)
|
||||
if (monitorRemoveParameter(monitor, key) || value[0])
|
||||
{
|
||||
/** Either we're removing an existing parameter or adding a new one */
|
||||
valid = true;
|
||||
|
||||
if (value[0])
|
||||
{
|
||||
CONFIG_PARAMETER p = {.name = key, .value = value};
|
||||
monitorAddParameters(monitor, &p);
|
||||
}
|
||||
}
|
||||
|
||||
monitorStart(monitor, monitor->parameters);
|
||||
}
|
||||
|
||||
if (valid && monitor->created_online)
|
||||
{
|
||||
monitor_serialize(monitor);
|
||||
}
|
||||
@ -377,8 +396,6 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add
|
||||
const char *ssl_cert, const char *ssl_ca,
|
||||
const char *ssl_version, const char *ssl_depth)
|
||||
{
|
||||
SSL_LISTENER *ssl = NULL;
|
||||
bool rval = true;
|
||||
|
||||
if (addr == NULL || strcasecmp(addr, "default") == 0)
|
||||
{
|
||||
@ -407,34 +424,42 @@ bool runtime_create_listener(SERVICE *service, const char *name, const char *add
|
||||
|
||||
unsigned short u_port = atoi(port);
|
||||
|
||||
if (ssl_key && ssl_cert && ssl_ca)
|
||||
{
|
||||
ssl = create_ssl(name, ssl_key, ssl_cert, ssl_ca, ssl_version, ssl_depth);
|
||||
|
||||
if (ssl == NULL)
|
||||
{
|
||||
MXS_ERROR("SSL initialization for listener '%s' failed.", name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_acquire(&crt_lock);
|
||||
|
||||
if (rval)
|
||||
{
|
||||
const char *print_addr = addr ? addr : "0.0.0.0";
|
||||
SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr,
|
||||
u_port, auth, auth_opt, ssl);
|
||||
SSL_LISTENER *ssl = NULL;
|
||||
bool rval = false;
|
||||
|
||||
if (listener && listener_serialize(listener) && serviceLaunchListener(service, listener))
|
||||
if (!serviceHasListener(service, proto, addr, u_port))
|
||||
{
|
||||
rval = true;
|
||||
|
||||
if (ssl_key && ssl_cert && ssl_ca)
|
||||
{
|
||||
MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created",
|
||||
name, print_addr, port, service->name);
|
||||
ssl = create_ssl(name, ssl_key, ssl_cert, ssl_ca, ssl_version, ssl_depth);
|
||||
|
||||
if (ssl == NULL)
|
||||
{
|
||||
MXS_ERROR("SSL initialization for listener '%s' failed.", name);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (rval)
|
||||
{
|
||||
MXS_ERROR("Failed to start listener '%s' at %s:%s.", name, print_addr, port);
|
||||
rval = false;
|
||||
const char *print_addr = addr ? addr : "0.0.0.0";
|
||||
SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr,
|
||||
u_port, auth, auth_opt, ssl);
|
||||
|
||||
if (listener && listener_serialize(listener) && serviceLaunchListener(service, listener))
|
||||
{
|
||||
MXS_NOTICE("Listener '%s' at %s:%s for service '%s' created",
|
||||
name, print_addr, port, service->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to start listener '%s' at %s:%s.", name, print_addr, port);
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,11 +520,21 @@ bool runtime_create_monitor(const char *name, const char *module)
|
||||
{
|
||||
spinlock_acquire(&crt_lock);
|
||||
bool rval = false;
|
||||
MONITOR *monitor = monitor_alloc((char*)name, (char*)module);
|
||||
|
||||
if (monitor && monitor_serialize(monitor))
|
||||
if (monitor_find(name) == NULL)
|
||||
{
|
||||
rval = true;
|
||||
MONITOR *monitor = monitor_alloc((char*)name, (char*)module);
|
||||
|
||||
if (monitor)
|
||||
{
|
||||
/** Mark that this monitor was created after MaxScale was started */
|
||||
monitor->created_online = true;
|
||||
|
||||
if (monitor_serialize(monitor))
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spinlock_release(&crt_lock);
|
||||
|
@ -348,7 +348,7 @@ dcb_final_free(DCB *dcb)
|
||||
bool is_client_dcb = (DCB_ROLE_CLIENT_HANDLER == dcb->dcb_role ||
|
||||
DCB_ROLE_INTERNAL == dcb->dcb_role);
|
||||
|
||||
session_free(local_session);
|
||||
session_put_ref(local_session);
|
||||
|
||||
if (is_client_dcb)
|
||||
{
|
||||
@ -1714,7 +1714,7 @@ dcb_maybe_add_persistent(DCB *dcb)
|
||||
CHK_SESSION(local_session);
|
||||
if (SESSION_STATE_DUMMY != local_session->state)
|
||||
{
|
||||
session_free(local_session);
|
||||
session_put_ref(local_session);
|
||||
}
|
||||
}
|
||||
spinlock_acquire(&dcb->cb_lock);
|
||||
|
@ -48,22 +48,22 @@ static void filter_free_parameters(FILTER_DEF *filter);
|
||||
* @return The newly created filter or NULL if an error occured
|
||||
*/
|
||||
FILTER_DEF *
|
||||
filter_alloc(char *name, char *module)
|
||||
filter_alloc(const char *name, const char *module)
|
||||
{
|
||||
name = MXS_STRDUP(name);
|
||||
module = MXS_STRDUP(module);
|
||||
char* my_name = MXS_STRDUP(name);
|
||||
char* my_module = MXS_STRDUP(module);
|
||||
|
||||
FILTER_DEF *filter = (FILTER_DEF *)MXS_MALLOC(sizeof(FILTER_DEF));
|
||||
|
||||
if (!name || !module || !filter)
|
||||
if (!my_name || !my_module || !filter)
|
||||
{
|
||||
MXS_FREE(name);
|
||||
MXS_FREE(module);
|
||||
MXS_FREE(my_name);
|
||||
MXS_FREE(my_module);
|
||||
MXS_FREE(filter);
|
||||
return NULL;
|
||||
}
|
||||
filter->name = name;
|
||||
filter->module = module;
|
||||
filter->name = my_name;
|
||||
filter->module = my_module;
|
||||
filter->filter = NULL;
|
||||
filter->options = NULL;
|
||||
filter->obj = NULL;
|
||||
@ -140,7 +140,7 @@ filter_free(FILTER_DEF *filter)
|
||||
* @return The server or NULL if not found
|
||||
*/
|
||||
FILTER_DEF *
|
||||
filter_find(char *name)
|
||||
filter_find(const char *name)
|
||||
{
|
||||
FILTER_DEF *filter;
|
||||
|
||||
@ -164,7 +164,7 @@ filter_find(char *name)
|
||||
* @param name Parameter name to check
|
||||
*/
|
||||
int
|
||||
filter_standard_parameter(char *name)
|
||||
filter_standard_parameter(const char *name)
|
||||
{
|
||||
if (strcmp(name, "type") == 0 || strcmp(name, "module") == 0)
|
||||
{
|
||||
@ -220,7 +220,7 @@ dprintAllFilters(DCB *dcb)
|
||||
* to display all active filters in MaxScale
|
||||
*/
|
||||
void
|
||||
dprintFilter(DCB *dcb, FILTER_DEF *filter)
|
||||
dprintFilter(DCB *dcb, const FILTER_DEF *filter)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -287,7 +287,7 @@ dListFilters(DCB *dcb)
|
||||
* @param option The option string
|
||||
*/
|
||||
void
|
||||
filterAddOption(FILTER_DEF *filter, char *option)
|
||||
filter_add_option(FILTER_DEF *filter, const char *option)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -323,7 +323,7 @@ filterAddOption(FILTER_DEF *filter, char *option)
|
||||
* @param value The parameter value
|
||||
*/
|
||||
void
|
||||
filterAddParameter(FILTER_DEF *filter, char *name, char *value)
|
||||
filter_add_parameter(FILTER_DEF *filter, const char *name, const char *value)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -344,13 +344,13 @@ filterAddParameter(FILTER_DEF *filter, char *name, char *value)
|
||||
(i + 2) * sizeof(FILTER_PARAMETER *));
|
||||
}
|
||||
FILTER_PARAMETER *parameter = MXS_CALLOC(1, sizeof(FILTER_PARAMETER));
|
||||
name = MXS_STRDUP(name);
|
||||
value = MXS_STRDUP(value);
|
||||
char* my_name = MXS_STRDUP(name);
|
||||
char* my_value = MXS_STRDUP(value);
|
||||
|
||||
MXS_ABORT_IF_TRUE(!parameters || !parameter || !name || !value);
|
||||
MXS_ABORT_IF_TRUE(!parameters || !parameter || !my_name || !my_value);
|
||||
|
||||
parameter->name = name;
|
||||
parameter->value = value;
|
||||
parameter->name = my_name;
|
||||
parameter->value = my_value;
|
||||
parameters[i] = parameter;
|
||||
parameters[i + 1] = NULL;
|
||||
filter->parameters = parameters;
|
||||
@ -433,7 +433,7 @@ bool filter_load(FILTER_DEF* filter)
|
||||
* if the filter could not be created
|
||||
*/
|
||||
DOWNSTREAM *
|
||||
filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream)
|
||||
filter_apply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream)
|
||||
{
|
||||
DOWNSTREAM *me;
|
||||
|
||||
@ -468,7 +468,7 @@ filterApply(FILTER_DEF *filter, SESSION *session, DOWNSTREAM *downstream)
|
||||
* @return The upstream component for the next filter
|
||||
*/
|
||||
UPSTREAM *
|
||||
filterUpstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream)
|
||||
filter_upstream(FILTER_DEF *filter, void *fsession, UPSTREAM *upstream)
|
||||
{
|
||||
UPSTREAM *me = NULL;
|
||||
|
||||
|
@ -1865,12 +1865,7 @@ int main(int argc, char **argv)
|
||||
goto return_main;
|
||||
}
|
||||
|
||||
if (config_check)
|
||||
{
|
||||
MXS_NOTICE("Configuration was successfully verified.");
|
||||
rc = MAXSCALE_SHUTDOWN;
|
||||
goto return_main;
|
||||
}
|
||||
cnf->config_check = config_check;
|
||||
|
||||
if (mysql_library_init(0, NULL, NULL))
|
||||
{
|
||||
@ -1918,21 +1913,24 @@ int main(int argc, char **argv)
|
||||
}
|
||||
libmysql_initialized = TRUE;
|
||||
|
||||
/** Check if a MaxScale process is already running */
|
||||
if (pid_file_exists())
|
||||
if (!config_check)
|
||||
{
|
||||
/** There is a process with the PID of the maxscale.pid file running.
|
||||
* Assuming that this is an already running MaxScale process, we
|
||||
* should exit with an error code. */
|
||||
rc = MAXSCALE_ALREADYRUNNING;
|
||||
goto return_main;
|
||||
}
|
||||
/** Check if a MaxScale process is already running */
|
||||
if (pid_file_exists())
|
||||
{
|
||||
/** There is a process with the PID of the maxscale.pid file running.
|
||||
* Assuming that this is an already running MaxScale process, we
|
||||
* should exit with an error code. */
|
||||
rc = MAXSCALE_ALREADYRUNNING;
|
||||
goto return_main;
|
||||
}
|
||||
|
||||
/* Write process pid into MaxScale pidfile */
|
||||
if (write_pid_file() != 0)
|
||||
{
|
||||
rc = MAXSCALE_ALREADYRUNNING;
|
||||
goto return_main;
|
||||
/* Write process pid into MaxScale pidfile */
|
||||
if (write_pid_file() != 0)
|
||||
{
|
||||
rc = MAXSCALE_ALREADYRUNNING;
|
||||
goto return_main;
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize statistics */
|
||||
@ -1961,6 +1959,14 @@ int main(int argc, char **argv)
|
||||
rc = MAXSCALE_NOSERVICES;
|
||||
goto return_main;
|
||||
}
|
||||
|
||||
if (config_check)
|
||||
{
|
||||
MXS_NOTICE("Configuration was successfully verified.");
|
||||
rc = MAXSCALE_SHUTDOWN;
|
||||
goto return_main;
|
||||
}
|
||||
|
||||
/*<
|
||||
* Start periodic log flusher thread.
|
||||
*/
|
||||
|
@ -68,10 +68,8 @@ SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name,
|
||||
const char *protocol, const char *address,
|
||||
unsigned short port, const char *authenticator,
|
||||
const char *options, SSL_LISTENER *ssl);
|
||||
int serviceHasProtocol(SERVICE *service, const char *protocol,
|
||||
const char* address, unsigned short port);
|
||||
|
||||
void serviceRemoveBackend(SERVICE *service, const SERVER *server);
|
||||
bool serviceHasBackend(SERVICE *service, SERVER *server);
|
||||
|
||||
/**
|
||||
* @brief Serialize a service to a file
|
||||
|
@ -277,25 +277,15 @@ static bool process_argument(modulecmd_arg_type_t *type, const void* value,
|
||||
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;
|
||||
if ((arg->value.session = session_get_by_id(atoi(value))))
|
||||
{
|
||||
arg->type.type = MODULECMD_ARG_SESSION;
|
||||
}
|
||||
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;
|
||||
@ -373,6 +363,10 @@ static void free_argument(struct arg_node *arg)
|
||||
MXS_FREE(arg->value.string);
|
||||
break;
|
||||
|
||||
case MODULECMD_ARG_SESSION:
|
||||
session_put_ref(arg->value.session);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -625,18 +619,10 @@ char* modulecmd_argtype_to_str(modulecmd_arg_type_t *type)
|
||||
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;
|
||||
|
@ -104,6 +104,7 @@ monitor_alloc(char *name, char *module)
|
||||
mon->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
mon->interval = MONITOR_INTERVAL;
|
||||
mon->parameters = NULL;
|
||||
mon->created_online = false;
|
||||
spinlock_init(&mon->lock);
|
||||
spinlock_acquire(&monLock);
|
||||
mon->next = allMonitors;
|
||||
@ -144,7 +145,7 @@ monitor_free(MONITOR *mon)
|
||||
}
|
||||
}
|
||||
spinlock_release(&monLock);
|
||||
free_config_parameter(mon->parameters);
|
||||
config_parameter_free(mon->parameters);
|
||||
monitor_server_free_all(mon->databases);
|
||||
MXS_FREE(mon->name);
|
||||
MXS_FREE(mon->module_name);
|
||||
@ -396,8 +397,15 @@ void monitorRemoveServer(MONITOR *mon, SERVER *server)
|
||||
void
|
||||
monitorAddUser(MONITOR *mon, char *user, char *passwd)
|
||||
{
|
||||
snprintf(mon->user, sizeof(mon->user), "%s", user);
|
||||
snprintf(mon->password, sizeof(mon->password), "%s", passwd);
|
||||
if (user != mon->user)
|
||||
{
|
||||
snprintf(mon->user, sizeof(mon->user), "%s", user);
|
||||
}
|
||||
|
||||
if (passwd != mon->password)
|
||||
{
|
||||
snprintf(mon->password, sizeof(mon->password), "%s", passwd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -767,6 +775,32 @@ void monitorAddParameters(MONITOR *monitor, CONFIG_PARAMETER *params)
|
||||
}
|
||||
}
|
||||
|
||||
bool monitorRemoveParameter(MONITOR *monitor, const char *key)
|
||||
{
|
||||
CONFIG_PARAMETER *prev = NULL;
|
||||
|
||||
for (CONFIG_PARAMETER *p = monitor->parameters; p; p = p->next)
|
||||
{
|
||||
if (strcmp(p->name, key) == 0)
|
||||
{
|
||||
if (p == monitor->parameters)
|
||||
{
|
||||
monitor->parameters = monitor->parameters->next;
|
||||
p->next = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev->next = p->next;
|
||||
p->next = NULL;
|
||||
}
|
||||
config_parameter_free(p);
|
||||
return true;
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a pending status bit in the monitor server
|
||||
*
|
||||
@ -1323,6 +1357,7 @@ static bool create_monitor_config(const MONITOR *monitor, const char *filename)
|
||||
* TODO: Check for return values on all of the dprintf calls
|
||||
*/
|
||||
dprintf(file, "[%s]\n", monitor->name);
|
||||
dprintf(file, "type=monitor\n");
|
||||
dprintf(file, "module=%s\n", monitor->module_name);
|
||||
dprintf(file, "user=%s\n", monitor->user);
|
||||
dprintf(file, "password=%s\n", monitor->password);
|
||||
|
@ -478,7 +478,15 @@ int serviceInitialize(SERVICE *service)
|
||||
|
||||
if ((service->router_instance = service->router->createInstance(service, router_options)))
|
||||
{
|
||||
listeners = serviceStartAllPorts(service);
|
||||
if (!config_get_global_options()->config_check)
|
||||
{
|
||||
listeners = serviceStartAllPorts(service);
|
||||
}
|
||||
else
|
||||
{
|
||||
/** We're only checking that the configuration is valid */
|
||||
listeners++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -665,7 +673,7 @@ void service_free(SERVICE *service)
|
||||
MXS_FREE(service->credentials.name);
|
||||
MXS_FREE(service->credentials.authdata);
|
||||
|
||||
free_config_parameter(service->svc_config_param);
|
||||
config_parameter_free(service->svc_config_param);
|
||||
serviceClearRouterOptions(service);
|
||||
|
||||
MXS_FREE(service);
|
||||
@ -708,9 +716,9 @@ SERV_LISTENER* serviceCreateListener(SERVICE *service, const char *name, const c
|
||||
* @param protocol The name of the protocol module
|
||||
* @param address The address to listen on
|
||||
* @param port The port to listen on
|
||||
* @return TRUE if the protocol/port is already part of the service
|
||||
* @return True if the protocol/port is already part of the service
|
||||
*/
|
||||
int serviceHasProtocol(SERVICE *service, const char *protocol,
|
||||
bool serviceHasListener(SERVICE *service, const char *protocol,
|
||||
const char* address, unsigned short port)
|
||||
{
|
||||
SERV_LISTENER *proto;
|
||||
|
@ -336,31 +336,14 @@ session_simple_free(SESSION *session, DCB *dcb)
|
||||
*
|
||||
* @param session The session to deallocate
|
||||
*/
|
||||
bool
|
||||
session_free(SESSION *session)
|
||||
static void session_free(SESSION *session)
|
||||
{
|
||||
if (NULL == session || SESSION_STATE_DUMMY == session->state)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
CHK_SESSION(session);
|
||||
ss_dassert(session->refcount == 0);
|
||||
|
||||
/*
|
||||
* Remove one reference. If there are no references left,
|
||||
* free session.
|
||||
*/
|
||||
if (atomic_add(&session->refcount, -1) > 1)
|
||||
{
|
||||
/* Must be one or more references left */
|
||||
return false;
|
||||
}
|
||||
session->state = SESSION_STATE_TO_BE_FREED;
|
||||
|
||||
atomic_add(&session->service->stats.n_current, -1);
|
||||
|
||||
/***
|
||||
*
|
||||
*/
|
||||
if (session->client_dcb)
|
||||
{
|
||||
dcb_free_all_memory(session->client_dcb);
|
||||
@ -396,9 +379,7 @@ session_free(SESSION *session)
|
||||
MXS_FREE(session->filters);
|
||||
}
|
||||
|
||||
MXS_INFO("Stopped %s client session [%lu]",
|
||||
session->service->name,
|
||||
session->ses_id);
|
||||
MXS_INFO("Stopped %s client session [%lu]", session->service->name, session->ses_id);
|
||||
|
||||
/** Disable trace and decrease trace logger counter */
|
||||
session_disable_log_priority(session, LOG_INFO);
|
||||
@ -409,7 +390,6 @@ session_free(SESSION *session)
|
||||
session->state = SESSION_STATE_FREE;
|
||||
session_final_free(session);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -661,8 +641,8 @@ session_setup_filters(SESSION *session)
|
||||
MXS_ERROR("Service '%s' contians an unresolved filter.", service->name);
|
||||
return 0;
|
||||
}
|
||||
if ((head = filterApply(service->filters[i], session,
|
||||
&session->head)) == NULL)
|
||||
if ((head = filter_apply(service->filters[i], session,
|
||||
&session->head)) == NULL)
|
||||
{
|
||||
MXS_ERROR("Failed to create filter '%s' for "
|
||||
"service '%s'.\n",
|
||||
@ -679,9 +659,9 @@ session_setup_filters(SESSION *session)
|
||||
|
||||
for (i = 0; i < service->n_filters; i++)
|
||||
{
|
||||
if ((tail = filterUpstream(service->filters[i],
|
||||
session->filters[i].session,
|
||||
&session->tail)) == NULL)
|
||||
if ((tail = filter_upstream(service->filters[i],
|
||||
session->filters[i].session,
|
||||
&session->tail)) == NULL)
|
||||
{
|
||||
MXS_ERROR("Failed to create filter '%s' for service '%s'.",
|
||||
service->filters[i]->name,
|
||||
@ -690,7 +670,7 @@ session_setup_filters(SESSION *session)
|
||||
}
|
||||
|
||||
/*
|
||||
* filterUpstream may simply return the 3 parameter if
|
||||
* filter_upstream may simply return the 3 parameter if
|
||||
* the filter has no upstream entry point. So no need
|
||||
* to copy the contents or free tail in this case.
|
||||
*/
|
||||
@ -915,3 +895,47 @@ const char* session_trx_state_to_string(session_trx_state_t state)
|
||||
MXS_ERROR("Unknown session_trx_state_t value: %d", (int)state);
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static bool ses_find_id(DCB *dcb, void *data)
|
||||
{
|
||||
void **params = (void**)data;
|
||||
SESSION **ses = (SESSION**)params[0];
|
||||
int *id = (int*)params[1];
|
||||
bool rval = true;
|
||||
|
||||
if (dcb->session->ses_id == *id)
|
||||
{
|
||||
*ses = session_get_ref(dcb->session);
|
||||
rval = false;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
SESSION* session_get_by_id(int id)
|
||||
{
|
||||
SESSION *session = NULL;
|
||||
void *params[] = {&session, &id};
|
||||
|
||||
dcb_foreach(ses_find_id, params);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
SESSION* session_get_ref(SESSION *session)
|
||||
{
|
||||
atomic_add(&session->refcount, 1);
|
||||
return session;
|
||||
}
|
||||
|
||||
void session_put_ref(SESSION *session)
|
||||
{
|
||||
if (session && session->state != SESSION_STATE_DUMMY)
|
||||
{
|
||||
/** Remove one reference. If there are no references left, free session */
|
||||
if (atomic_add(&session->refcount, -1) == 1)
|
||||
{
|
||||
session_free(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,12 +83,12 @@ test2()
|
||||
fprintf(stderr, "filter_alloc: test 1 failed.\n");
|
||||
return 1;
|
||||
}
|
||||
filterAddOption(f1, "option1");
|
||||
filterAddOption(f1, "option2");
|
||||
filterAddOption(f1, "option3");
|
||||
filterAddParameter(f1, "name1", "value1");
|
||||
filterAddParameter(f1, "name2", "value2");
|
||||
filterAddParameter(f1, "name3", "value3");
|
||||
filter_add_option(f1, "option1");
|
||||
filter_add_option(f1, "option2");
|
||||
filter_add_option(f1, "option3");
|
||||
filter_add_parameter(f1, "name1", "value1");
|
||||
filter_add_parameter(f1, "name2", "value2");
|
||||
filter_add_parameter(f1, "name3", "value3");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -305,16 +305,12 @@ int test_map()
|
||||
}
|
||||
|
||||
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)
|
||||
if (argv->argc == 1 && argv->argv[0].value.dcb == &my_dcb)
|
||||
{
|
||||
rval = true;
|
||||
}
|
||||
@ -329,26 +325,18 @@ int test_pointers()
|
||||
|
||||
modulecmd_arg_type_t args[] =
|
||||
{
|
||||
{MODULECMD_ARG_DCB, ""},
|
||||
{MODULECMD_ARG_DCB_PTR, ""},
|
||||
{MODULECMD_ARG_SESSION, ""},
|
||||
{MODULECMD_ARG_SESSION_PTR, ""}
|
||||
{MODULECMD_ARG_DCB, ""}
|
||||
};
|
||||
|
||||
TEST(modulecmd_register_command(ns, id, ptrfn, 4, args), "Registering a command should succeed");
|
||||
TEST(modulecmd_register_command(ns, id, ptrfn, 1, 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[] = {&my_dcb};
|
||||
|
||||
const void* params[] = {dcb_str, &my_dcb, session_str, &my_session};
|
||||
|
||||
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 4, params);
|
||||
MODULECMD_ARG *arg = modulecmd_arg_parse(cmd, 1, params);
|
||||
TEST(arg, "Parsing arguments should succeed");
|
||||
TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty");
|
||||
|
||||
|
@ -72,7 +72,7 @@ test1()
|
||||
ss_info_dassert(serviceCreateListener(service, "TestProtocol", "testprotocol",
|
||||
"localhost", 9876, "MySQLAuth", NULL, NULL),
|
||||
"Add Protocol should succeed");
|
||||
ss_info_dassert(0 != serviceHasProtocol(service, "testprotocol", "localhost", 9876),
|
||||
ss_info_dassert(0 != serviceHasListener(service, "testprotocol", "localhost", 9876),
|
||||
"Service should have new protocol as requested");
|
||||
|
||||
return 0;
|
||||
|
6
server/modules/authenticator/CDCPlainAuth/CMakeLists.txt
Normal file
6
server/modules/authenticator/CDCPlainAuth/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
if(BUILD_CDC)
|
||||
add_library(CDCPlainAuth SHARED cdc_plain_auth.c)
|
||||
target_link_libraries(CDCPlainAuth maxscale-common)
|
||||
set_target_properties(CDCPlainAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(CDCPlainAuth core)
|
||||
endif()
|
@ -1,49 +1,11 @@
|
||||
add_subdirectory(CDCPlainAuth)
|
||||
add_subdirectory(GSSAPI)
|
||||
add_subdirectory(HTTPAuth)
|
||||
add_subdirectory(MaxAdminAuth)
|
||||
add_subdirectory(MySQLAuth)
|
||||
|
||||
add_library(MySQLBackendAuth SHARED mysql_backend_auth.c)
|
||||
target_link_libraries(MySQLBackendAuth maxscale-common MySQLCommon)
|
||||
set_target_properties(MySQLBackendAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(MySQLBackendAuth core)
|
||||
|
||||
if (GSSAPI_FOUND AND SQLITE_FOUND)
|
||||
if (NOT SQLITE_VERSION VERSION_LESS "3.7.7")
|
||||
include_directories(${GSSAPI_INCS})
|
||||
include_directories(${SQLITE_INCLUDE_DIR})
|
||||
|
||||
add_library(GSSAPIAuth SHARED gssapi_auth.c gssapi_auth_common.c)
|
||||
target_link_libraries(GSSAPIAuth maxscale-common ${GSSAPI_LIBS} ${SQLITE_LIBRARIES} MySQLCommon)
|
||||
set_target_properties(GSSAPIAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(GSSAPIAuth core)
|
||||
|
||||
add_library(GSSAPIBackendAuth SHARED gssapi_backend_auth.c gssapi_auth_common.c)
|
||||
target_link_libraries(GSSAPIBackendAuth maxscale-common ${GSSAPI_LIBS} MySQLCommon)
|
||||
set_target_properties(GSSAPIBackendAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(GSSAPIBackendAuth core)
|
||||
|
||||
else()
|
||||
message(STATUS "Minimum requires SQLite version for GSSAPIAuth is 3.7.7, current SQLite version is ${SQLITE_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(NullAuthAllow SHARED null_auth_allow.c)
|
||||
target_link_libraries(NullAuthAllow maxscale-common)
|
||||
set_target_properties(NullAuthAllow PROPERTIES VERSION "1.0.0")
|
||||
install_module(NullAuthAllow core)
|
||||
|
||||
add_library(NullAuthDeny SHARED null_auth_deny.c)
|
||||
target_link_libraries(NullAuthDeny maxscale-common)
|
||||
set_target_properties(NullAuthDeny PROPERTIES VERSION "1.0.0")
|
||||
install_module(NullAuthDeny core)
|
||||
|
||||
add_library(MaxAdminAuth SHARED max_admin_auth.c)
|
||||
target_link_libraries(MaxAdminAuth maxscale-common)
|
||||
set_target_properties(MaxAdminAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(MaxAdminAuth core)
|
||||
|
||||
add_library(HTTPAuth SHARED http_auth.c)
|
||||
target_link_libraries(HTTPAuth maxscale-common)
|
||||
set_target_properties(HTTPAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(HTTPAuth core)
|
||||
add_subdirectory(MySQLBackendAuth)
|
||||
add_subdirectory(NullAuthAllow)
|
||||
add_subdirectory(NullAuthDeny)
|
||||
|
||||
# if(BUILD_TESTS)
|
||||
# add_library(testprotocol SHARED testprotocol.c)
|
||||
@ -51,10 +13,4 @@ install_module(HTTPAuth core)
|
||||
# target_link_libraries(testprotocol maxscale-common)
|
||||
# install_module(testprotocol core)
|
||||
# endif()
|
||||
if(BUILD_CDC)
|
||||
add_library(CDCPlainAuth SHARED cdc_plain_auth.c)
|
||||
target_link_libraries(CDCPlainAuth maxscale-common)
|
||||
set_target_properties(CDCPlainAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(CDCPlainAuth core)
|
||||
endif()
|
||||
|
||||
|
11
server/modules/authenticator/GSSAPI/CMakeLists.txt
Normal file
11
server/modules/authenticator/GSSAPI/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
if (GSSAPI_FOUND AND SQLITE_FOUND)
|
||||
if (NOT SQLITE_VERSION VERSION_LESS "3.7.7")
|
||||
include_directories(${GSSAPI_INCS})
|
||||
include_directories(${SQLITE_INCLUDE_DIR})
|
||||
|
||||
add_subdirectory(GSSAPIAuth)
|
||||
add_subdirectory(GSSAPIBackendAuth)
|
||||
else()
|
||||
message(STATUS "Minimum requires SQLite version for GSSAPIAuth is 3.7.7, current SQLite version is ${SQLITE_VERSION}")
|
||||
endif()
|
||||
endif()
|
@ -0,0 +1,4 @@
|
||||
add_library(GSSAPIAuth SHARED gssapi_auth.c ../gssapi_auth_common.c)
|
||||
target_link_libraries(GSSAPIAuth maxscale-common ${GSSAPI_LIBS} ${SQLITE_LIBRARIES} MySQLCommon)
|
||||
set_target_properties(GSSAPIAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(GSSAPIAuth core)
|
@ -19,7 +19,7 @@
|
||||
#include <maxscale/secrets.h>
|
||||
#include <maxscale/mysql_utils.h>
|
||||
#include <sqlite3.h>
|
||||
#include "gssapi_auth.h"
|
||||
#include "../gssapi_auth.h"
|
||||
|
||||
/** Default timeout is one minute */
|
||||
#define MXS_SQLITE_BUSY_TIMEOUT 60000
|
@ -0,0 +1,4 @@
|
||||
add_library(GSSAPIBackendAuth SHARED gssapi_backend_auth.c ../gssapi_auth_common.c)
|
||||
target_link_libraries(GSSAPIBackendAuth maxscale-common ${GSSAPI_LIBS} MySQLCommon)
|
||||
set_target_properties(GSSAPIBackendAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(GSSAPIBackendAuth core)
|
@ -16,7 +16,7 @@
|
||||
#include <maxscale/dcb.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/protocol/mysql.h>
|
||||
#include "gssapi_auth.h"
|
||||
#include "../gssapi_auth.h"
|
||||
|
||||
/**
|
||||
* @file gssapi_backend_auth.c - GSSAPI backend authenticator
|
4
server/modules/authenticator/HTTPAuth/CMakeLists.txt
Normal file
4
server/modules/authenticator/HTTPAuth/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(HTTPAuth SHARED http_auth.c)
|
||||
target_link_libraries(HTTPAuth maxscale-common)
|
||||
set_target_properties(HTTPAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(HTTPAuth core)
|
4
server/modules/authenticator/MaxAdminAuth/CMakeLists.txt
Normal file
4
server/modules/authenticator/MaxAdminAuth/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(MaxAdminAuth SHARED max_admin_auth.c)
|
||||
target_link_libraries(MaxAdminAuth maxscale-common)
|
||||
set_target_properties(MaxAdminAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(MaxAdminAuth core)
|
@ -39,6 +39,7 @@ typedef struct mysql_auth
|
||||
{
|
||||
char *cache_dir; /**< Custom cache directory location */
|
||||
bool inject_service_user; /**< Inject the service user into the list of users */
|
||||
bool skip_auth; /**< Authentication will always be successful */
|
||||
} MYSQL_AUTH;
|
||||
|
||||
|
||||
@ -144,6 +145,7 @@ static void* mysql_auth_init(char **options)
|
||||
bool error = false;
|
||||
instance->cache_dir = NULL;
|
||||
instance->inject_service_user = true;
|
||||
instance->skip_auth = false;
|
||||
|
||||
for (int i = 0; options[i]; i++)
|
||||
{
|
||||
@ -165,6 +167,10 @@ static void* mysql_auth_init(char **options)
|
||||
{
|
||||
instance->inject_service_user = config_truth_value(value);
|
||||
}
|
||||
else if (strcmp(options[i], "skip_authentication") == 0)
|
||||
{
|
||||
instance->skip_auth = config_truth_value(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Unknown authenticator option: %s", options[i]);
|
||||
@ -248,17 +254,21 @@ mysql_auth_authenticate(DCB *dcb)
|
||||
auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len,
|
||||
protocol, client_data->user, client_data->client_sha1, client_data->db);
|
||||
|
||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance;
|
||||
|
||||
/* On failed authentication try to load user table from backend database */
|
||||
/* Success for service_refresh_users returns 0 */
|
||||
if (MXS_AUTH_SUCCEEDED != auth_ret && 0 == service_refresh_users(dcb->service))
|
||||
if (MXS_AUTH_SUCCEEDED != auth_ret && !instance->skip_auth &&
|
||||
0 == service_refresh_users(dcb->service))
|
||||
{
|
||||
auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, protocol,
|
||||
client_data->user, client_data->client_sha1, client_data->db);
|
||||
}
|
||||
|
||||
/* on successful authentication, set user into dcb field */
|
||||
if (MXS_AUTH_SUCCEEDED == auth_ret)
|
||||
if (MXS_AUTH_SUCCEEDED == auth_ret || instance->skip_auth)
|
||||
{
|
||||
auth_ret = MXS_AUTH_SUCCEEDED;
|
||||
dcb->user = MXS_STRDUP_A(client_data->user);
|
||||
/** Send an OK packet to the client */
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
add_library(MySQLBackendAuth SHARED mysql_backend_auth.c)
|
||||
target_link_libraries(MySQLBackendAuth maxscale-common MySQLCommon)
|
||||
set_target_properties(MySQLBackendAuth PROPERTIES VERSION "1.0.0")
|
||||
install_module(MySQLBackendAuth core)
|
@ -0,0 +1,4 @@
|
||||
add_library(NullAuthAllow SHARED null_auth_allow.c)
|
||||
target_link_libraries(NullAuthAllow maxscale-common)
|
||||
set_target_properties(NullAuthAllow PROPERTIES VERSION "1.0.0")
|
||||
install_module(NullAuthAllow core)
|
4
server/modules/authenticator/NullAuthDeny/CMakeLists.txt
Normal file
4
server/modules/authenticator/NullAuthDeny/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(NullAuthDeny SHARED null_auth_deny.c)
|
||||
target_link_libraries(NullAuthDeny maxscale-common)
|
||||
set_target_properties(NullAuthDeny PROPERTIES VERSION "1.0.0")
|
||||
install_module(NullAuthDeny core)
|
17
server/modules/filter/cache/cachefilter.cc
vendored
17
server/modules/filter/cache/cachefilter.cc
vendored
@ -74,6 +74,7 @@ static int routeQuery(FILTER* pInstance, void* pSessionData, GWBUF* pPacket
|
||||
static int clientReply(FILTER* pInstance, void* pSessionData, GWBUF* pPacket);
|
||||
static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb);
|
||||
static uint64_t getCapabilities(void);
|
||||
static void destroyInstance(FILTER* pInstance);
|
||||
|
||||
static bool process_params(char **pzOptions, FILTER_PARAMETER **ppParams, CACHE_CONFIG& config);
|
||||
|
||||
@ -121,7 +122,7 @@ extern "C" FILTER_OBJECT *GetModuleObject()
|
||||
clientReply,
|
||||
diagnostics,
|
||||
getCapabilities,
|
||||
NULL, // destroyInstance
|
||||
destroyInstance,
|
||||
};
|
||||
|
||||
return &object;
|
||||
@ -301,7 +302,6 @@ static void diagnostics(FILTER* pInstance, void* pSessionData, DCB* pDcb)
|
||||
CPP_GUARD(pSessionCache->diagnostics(pDcb));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Capability routine.
|
||||
*
|
||||
@ -312,6 +312,19 @@ static uint64_t getCapabilities(void)
|
||||
return RCAP_TYPE_TRANSACTION_TRACKING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the filter instance.
|
||||
*
|
||||
* @param pInstance The filter instance.
|
||||
*/
|
||||
static void destroyInstance(FILTER* pInstance)
|
||||
{
|
||||
MXS_NOTICE("Deleting Cache filter instance.");
|
||||
CACHE_FILTER* pFilter = reinterpret_cast<CACHE_FILTER*>(pInstance);
|
||||
|
||||
delete pFilter;
|
||||
}
|
||||
|
||||
//
|
||||
// API Implementation END
|
||||
//
|
||||
|
8
server/modules/filter/cache/cachefilter.h
vendored
8
server/modules/filter/cache/cachefilter.h
vendored
@ -38,6 +38,14 @@ class StorageFactory;
|
||||
#define CACHE_DEBUG_MIN CACHE_DEBUG_NONE
|
||||
#define CACHE_DEBUG_MAX (CACHE_DEBUG_RULES | CACHE_DEBUG_USAGE | CACHE_DEBUG_DECISIONS)
|
||||
|
||||
#if !defined(UINT32_MAX)
|
||||
#define UINT32_MAX (4294967295U)
|
||||
#endif
|
||||
|
||||
#if !defined(UINT64_MAX)
|
||||
#define UINT64_MAX (18446744073709551615UL)
|
||||
#endif
|
||||
|
||||
// Count
|
||||
#define CACHE_DEFAULT_MAX_RESULTSET_ROWS UINT32_MAX
|
||||
// Bytes
|
||||
|
@ -41,14 +41,14 @@ CACHE_STORAGE* createInstance(cache_thread_model_t model,
|
||||
|
||||
if (max_count != 0)
|
||||
{
|
||||
MXS_WARNING("A maximum item count of %" PRIu32 " specified, although 'storage_inMemory' "
|
||||
"does not enforce such a limit.", max_count);
|
||||
MXS_WARNING("A maximum item count of %u specified, although 'storage_inMemory' "
|
||||
"does not enforce such a limit.", (unsigned int)max_count);
|
||||
}
|
||||
|
||||
if (max_size != 0)
|
||||
{
|
||||
MXS_WARNING("A maximum size of %" PRIu64 " specified, although 'storage_inMemory' "
|
||||
"does not enforce such a limit.", max_size);
|
||||
MXS_WARNING("A maximum size of %lu specified, although 'storage_inMemory' "
|
||||
"does not enforce such a limit.", (unsigned long)max_size);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -40,14 +40,14 @@ CACHE_STORAGE* createInstance(cache_thread_model_t, // Ignored, RocksDB always M
|
||||
|
||||
if (maxCount != 0)
|
||||
{
|
||||
MXS_WARNING("A maximum item count of %" PRIu32 " specifed, although 'storage_rocksdb' "
|
||||
"does not enforce such a limit.", maxCount);
|
||||
MXS_WARNING("A maximum item count of %u specifed, although 'storage_rocksdb' "
|
||||
"does not enforce such a limit.", (unsigned int)maxCount);
|
||||
}
|
||||
|
||||
if (maxSize != 0)
|
||||
{
|
||||
MXS_WARNING("A maximum size of %" PRIu64 " specified, although 'storage_rocksdb' "
|
||||
"does not enforce such a limit.", maxSize);
|
||||
MXS_WARNING("A maximum size of %lu specified, although 'storage_rocksdb' "
|
||||
"does not enforce such a limit.", (unsigned long)maxSize);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -70,7 +70,7 @@ version()
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* The module initialization routine, called when the module
|
||||
* is first loaded.
|
||||
* @see function load_module in load_utils.c for explanation of lint
|
||||
*/
|
||||
@ -205,7 +205,7 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
|
||||
/**
|
||||
* The routeQuery entry point. This is passed the query buffer
|
||||
* to which the filter should be applied. Once applied the
|
||||
* query shoudl normally be passed to the downstream component
|
||||
* query should normally be passed to the downstream component
|
||||
* (filter or router) in the filter chain.
|
||||
*
|
||||
* @param instance The filter instance data
|
||||
@ -216,51 +216,18 @@ static int
|
||||
routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
||||
{
|
||||
HINT_SESSION *my_session = (HINT_SESSION *)session;
|
||||
char *ptr;
|
||||
int rval, len, residual;
|
||||
HINT *hint;
|
||||
|
||||
if (my_session->request == NULL)
|
||||
if (modutil_is_SQL(queue))
|
||||
{
|
||||
/*
|
||||
* No stored buffer, so this must be the first
|
||||
* buffer of a new request.
|
||||
*/
|
||||
if (modutil_MySQL_Query(queue, &ptr, &len, &residual) == 0)
|
||||
{
|
||||
return my_session->down.routeQuery(
|
||||
my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
}
|
||||
my_session->request = queue;
|
||||
my_session->query_len = len;
|
||||
my_session->request = NULL;
|
||||
my_session->query_len = 0;
|
||||
HINT *hint = hint_parser(my_session, queue);
|
||||
queue->hint = hint;
|
||||
}
|
||||
else
|
||||
{
|
||||
gwbuf_append(my_session->request, queue);
|
||||
}
|
||||
|
||||
if (gwbuf_length(my_session->request) < my_session->query_len)
|
||||
{
|
||||
/*
|
||||
* We have not got the entire SQL text, buffer and wait for
|
||||
* the remainder.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
/* We have the entire SQL text, parse for hints and attach to the
|
||||
* buffer at the head of the queue.
|
||||
*/
|
||||
queue = my_session->request;
|
||||
my_session->request = NULL;
|
||||
my_session->query_len = 0;
|
||||
hint = hint_parser(my_session, queue);
|
||||
queue->hint = hint;
|
||||
|
||||
/* Now process the request */
|
||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
||||
return my_session->down.routeQuery(my_session->down.instance,
|
||||
my_session->down.session, queue);
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,5 +256,5 @@ diagnostic(FILTER *instance, void *fsession, DCB *dcb)
|
||||
*/
|
||||
static uint64_t getCapabilities(void)
|
||||
{
|
||||
return RCAP_TYPE_STMT_INPUT;
|
||||
return RCAP_TYPE_CONTIGUOUS_INPUT;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include <string.h>
|
||||
#include <maxscale/atomic.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/service.h>
|
||||
|
||||
MODULE_INFO info =
|
||||
{
|
||||
@ -59,13 +60,23 @@ MODULE_INFO info =
|
||||
|
||||
static char *version_str = "V1.1.1";
|
||||
|
||||
/** Formatting buffer size */
|
||||
#define QLA_STRING_BUFFER_SIZE 1024
|
||||
/** Date string buffer size */
|
||||
#define QLA_DATE_BUFFER_SIZE 20
|
||||
|
||||
/** Log file settings flags */
|
||||
/** Log file save mode flags */
|
||||
#define CONFIG_FILE_SESSION (1 << 0) // Default value, session specific files
|
||||
#define CONFIG_FILE_UNIFIED (1 << 1) // One file shared by all sessions
|
||||
|
||||
/* Flags for controlling extra log entry contents. The default items are
|
||||
* always on, for now.
|
||||
*/
|
||||
#define LOG_DATA_SERVICE (1 << 0)
|
||||
#define LOG_DATA_SESSION (1 << 1)
|
||||
#define LOG_DATA_DATE (1 << 2)
|
||||
#define LOG_DATA_USER (1 << 3)
|
||||
#define LOG_DATA_QUERY (1 << 4)
|
||||
#define LOG_DATA_DEFAULT (LOG_DATA_DATE | LOG_DATA_USER | LOG_DATA_QUERY)
|
||||
|
||||
/*
|
||||
* The filter entry points
|
||||
*/
|
||||
@ -105,17 +116,21 @@ static FILTER_OBJECT MyObject =
|
||||
typedef struct
|
||||
{
|
||||
int sessions; /* The count of sessions */
|
||||
char *name; /* Filter definition name */
|
||||
char *filebase; /* The filename base */
|
||||
char *source; /* The source of the client connection */
|
||||
char *userName; /* The user name to filter on */
|
||||
char *source; /* The source of the client connection to filter on */
|
||||
char *user_name; /* The user name to filter on */
|
||||
char *match; /* Optional text to match against */
|
||||
regex_t re; /* Compiled regex text */
|
||||
char *nomatch; /* Optional text to match against for exclusion */
|
||||
regex_t nore; /* Compiled regex nomatch text */
|
||||
uint32_t log_mode_flags; /* Log file mode settings */
|
||||
uint32_t log_file_data_flags; /* What data is saved to the files */
|
||||
FILE *unified_fp; /* Unified log file. The pointer needs to be shared here
|
||||
* to avoid garbled printing. */
|
||||
bool flush_writes; /* Flush log file after every write */
|
||||
bool flush_writes; /* Flush log file after every write? */
|
||||
bool append; /* Open files in append-mode? */
|
||||
bool write_warning_given; /* To make sure some warning are only given once */
|
||||
} QLA_INSTANCE;
|
||||
|
||||
/**
|
||||
@ -128,15 +143,20 @@ typedef struct
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
DOWNSTREAM down;
|
||||
char *filename;
|
||||
FILE *fp;
|
||||
int active;
|
||||
char *user;
|
||||
DOWNSTREAM down;
|
||||
char *filename; /* The session-specific log file name */
|
||||
FILE *fp; /* The session-specific log file */
|
||||
char *remote;
|
||||
size_t ses_id; /* The session this filter serves */
|
||||
char *service; /* The service name this filter is attached to. Not owned. */
|
||||
size_t ses_id; /* The session this filter serves */
|
||||
char *user; /* The client */
|
||||
} QLA_SESSION;
|
||||
|
||||
static FILE* open_log_file(uint32_t, QLA_INSTANCE *, const char *);
|
||||
static int write_log_entry(uint32_t, FILE*, QLA_INSTANCE*, QLA_SESSION*, const char*,
|
||||
const char*, size_t);
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
@ -191,14 +211,18 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
|
||||
if (my_instance)
|
||||
{
|
||||
my_instance->source = NULL;
|
||||
my_instance->userName = NULL;
|
||||
my_instance->match = NULL;
|
||||
my_instance->nomatch = NULL;
|
||||
my_instance->append = false;
|
||||
my_instance->filebase = NULL;
|
||||
my_instance->log_mode_flags = 0;
|
||||
my_instance->unified_fp = NULL;
|
||||
my_instance->flush_writes = false;
|
||||
my_instance->log_file_data_flags = LOG_DATA_DEFAULT;
|
||||
my_instance->log_mode_flags = 0;
|
||||
my_instance->match = NULL;
|
||||
my_instance->name = MXS_STRDUP(name);
|
||||
my_instance->nomatch = NULL;
|
||||
my_instance->source = NULL;
|
||||
my_instance->unified_fp = NULL;
|
||||
my_instance->user_name = NULL;
|
||||
my_instance->write_warning_given = false;
|
||||
bool error = false;
|
||||
|
||||
if (params)
|
||||
@ -219,7 +243,7 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "user"))
|
||||
{
|
||||
my_instance->userName = MXS_STRDUP_A(params[i]->value);
|
||||
my_instance->user_name = MXS_STRDUP_A(params[i]->value);
|
||||
}
|
||||
else if (!strcmp(params[i]->name, "filebase"))
|
||||
{
|
||||
@ -264,6 +288,18 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
{
|
||||
my_instance->flush_writes = true;
|
||||
}
|
||||
else if (!strcasecmp(options[i], "append"))
|
||||
{
|
||||
my_instance->append = true;
|
||||
}
|
||||
else if (!strcasecmp(options[i], "print_service"))
|
||||
{
|
||||
my_instance->log_file_data_flags |= LOG_DATA_SERVICE;
|
||||
}
|
||||
else if (!strcasecmp(options[i], "print_session"))
|
||||
{
|
||||
my_instance->log_file_data_flags |= LOG_DATA_SESSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("qlafilter: Unsupported option '%s'.",
|
||||
@ -305,8 +341,8 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
error = true;
|
||||
}
|
||||
// Try to open the unified log file
|
||||
if (my_instance->log_mode_flags & CONFIG_FILE_UNIFIED &&
|
||||
my_instance->filebase != NULL)
|
||||
if (!error && (my_instance->log_mode_flags & CONFIG_FILE_UNIFIED) &&
|
||||
(my_instance->filebase != NULL))
|
||||
{
|
||||
// First calculate filename length
|
||||
const char UNIFIED[] = ".unified";
|
||||
@ -316,7 +352,9 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
{
|
||||
snprintf(filename, namelen, "%s.unified", my_instance->filebase);
|
||||
// Open the file. It is only closed at program exit
|
||||
my_instance->unified_fp = fopen(filename, "w");
|
||||
my_instance->unified_fp = open_log_file(my_instance->log_file_data_flags,
|
||||
my_instance, filename);
|
||||
|
||||
if (my_instance->unified_fp == NULL)
|
||||
{
|
||||
char errbuf[MXS_STRERROR_BUFLEN];
|
||||
@ -353,7 +391,7 @@ createInstance(const char *name, char **options, FILTER_PARAMETER **params)
|
||||
}
|
||||
MXS_FREE(my_instance->filebase);
|
||||
MXS_FREE(my_instance->source);
|
||||
MXS_FREE(my_instance->userName);
|
||||
MXS_FREE(my_instance->user_name);
|
||||
MXS_FREE(my_instance);
|
||||
my_instance = NULL;
|
||||
}
|
||||
@ -392,8 +430,8 @@ newSession(FILTER *instance, SESSION *session)
|
||||
|
||||
if ((my_instance->source && remote &&
|
||||
strcmp(remote, my_instance->source)) ||
|
||||
(my_instance->userName && userName &&
|
||||
strcmp(userName, my_instance->userName)))
|
||||
(my_instance->user_name && userName &&
|
||||
strcmp(userName, my_instance->user_name)))
|
||||
{
|
||||
my_session->active = 0;
|
||||
}
|
||||
@ -401,6 +439,7 @@ newSession(FILTER *instance, SESSION *session)
|
||||
my_session->user = userName;
|
||||
my_session->remote = remote;
|
||||
my_session->ses_id = session->ses_id;
|
||||
my_session->service = session->service->name;
|
||||
|
||||
sprintf(my_session->filename, "%s.%lu",
|
||||
my_instance->filebase,
|
||||
@ -410,9 +449,11 @@ newSession(FILTER *instance, SESSION *session)
|
||||
atomic_add(&(my_instance->sessions), 1);
|
||||
|
||||
// Only open the session file if the corresponding mode setting is used
|
||||
if (my_session->active && (my_instance->log_mode_flags | CONFIG_FILE_SESSION))
|
||||
if (my_session->active && (my_instance->log_mode_flags & CONFIG_FILE_SESSION))
|
||||
{
|
||||
my_session->fp = fopen(my_session->filename, "w");
|
||||
uint32_t data_flags = (my_instance->log_file_data_flags &
|
||||
~LOG_DATA_SESSION); // No point printing "Session"
|
||||
my_session->fp = open_log_file(data_flags, my_instance, my_session->filename);
|
||||
|
||||
if (my_session->fp == NULL)
|
||||
{
|
||||
@ -496,21 +537,21 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
||||
{
|
||||
QLA_INSTANCE *my_instance = (QLA_INSTANCE *) instance;
|
||||
QLA_SESSION *my_session = (QLA_SESSION *) session;
|
||||
char *ptr;
|
||||
char *ptr = NULL;
|
||||
int length = 0;
|
||||
struct tm t;
|
||||
struct timeval tv;
|
||||
|
||||
if (my_session->active)
|
||||
{
|
||||
if ((ptr = modutil_get_SQL(queue)) != NULL)
|
||||
if (modutil_extract_SQL(queue, &ptr, &length))
|
||||
{
|
||||
if ((my_instance->match == NULL ||
|
||||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
||||
(my_instance->nomatch == NULL ||
|
||||
regexec(&my_instance->nore, ptr, 0, NULL, 0) != 0))
|
||||
{
|
||||
char buffer[QLA_STRING_BUFFER_SIZE];
|
||||
char buffer[QLA_DATE_BUFFER_SIZE];
|
||||
gettimeofday(&tv, NULL);
|
||||
localtime_r(&tv.tv_sec, &t);
|
||||
strftime(buffer, sizeof(buffer), "%F %T", &t);
|
||||
@ -519,28 +560,39 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
||||
* Loop over all the possible log file modes and write to
|
||||
* the enabled files.
|
||||
*/
|
||||
char *sql_string = trim(squeeze_whitespace(ptr));
|
||||
|
||||
char *sql_string = ptr;
|
||||
bool write_error = false;
|
||||
if (my_instance->log_mode_flags & CONFIG_FILE_SESSION)
|
||||
{
|
||||
fprintf(my_session->fp, "%s,%s@%s,%s\n", buffer, my_session->user,
|
||||
my_session->remote, sql_string);
|
||||
if (my_instance->flush_writes)
|
||||
// In this case there is no need to write the session
|
||||
// number into the files.
|
||||
uint32_t data_flags = (my_instance->log_file_data_flags &
|
||||
~LOG_DATA_SESSION);
|
||||
|
||||
if (write_log_entry(data_flags, my_session->fp,
|
||||
my_instance, my_session, buffer, sql_string, length) < 0)
|
||||
{
|
||||
fflush(my_session->fp);
|
||||
write_error = true;
|
||||
}
|
||||
}
|
||||
if (my_instance->log_mode_flags & CONFIG_FILE_UNIFIED)
|
||||
{
|
||||
fprintf(my_instance->unified_fp, "S%zd,%s,%s@%s,%s\n",
|
||||
my_session->ses_id, buffer, my_session->user,
|
||||
my_session->remote, sql_string);
|
||||
if (my_instance->flush_writes)
|
||||
uint32_t data_flags = my_instance->log_file_data_flags;
|
||||
if (write_log_entry(data_flags, my_instance->unified_fp,
|
||||
my_instance, my_session, buffer, sql_string, length) < 0)
|
||||
{
|
||||
fflush(my_instance->unified_fp);
|
||||
write_error = true;
|
||||
}
|
||||
}
|
||||
if (write_error && !my_instance->write_warning_given)
|
||||
{
|
||||
MXS_ERROR("qla-filter '%s': Log file write failed. "
|
||||
"Suppressing further similar warnings.",
|
||||
my_instance->name);
|
||||
my_instance->write_warning_given = true;
|
||||
}
|
||||
}
|
||||
MXS_FREE(ptr);
|
||||
}
|
||||
}
|
||||
/* Pass the query downstream */
|
||||
@ -575,10 +627,10 @@ diagnostic(FILTER *instance, void *fsession, DCB *dcb)
|
||||
dcb_printf(dcb, "\t\tLimit logging to connections from %s\n",
|
||||
my_instance->source);
|
||||
}
|
||||
if (my_instance->userName)
|
||||
if (my_instance->user_name)
|
||||
{
|
||||
dcb_printf(dcb, "\t\tLimit logging to user %s\n",
|
||||
my_instance->userName);
|
||||
my_instance->user_name);
|
||||
}
|
||||
if (my_instance->match)
|
||||
{
|
||||
@ -601,3 +653,244 @@ static uint64_t getCapabilities(void)
|
||||
{
|
||||
return RCAP_TYPE_CONTIGUOUS_INPUT;
|
||||
}
|
||||
/**
|
||||
* Open the log file and print a header if appropriate.
|
||||
* @param data_flags Data save settings flags
|
||||
* @param instance The filter instance
|
||||
* @param filename Target file path
|
||||
* @return A valid file on success, null otherwise.
|
||||
*/
|
||||
static FILE* open_log_file(uint32_t data_flags, QLA_INSTANCE *instance, const char *filename)
|
||||
{
|
||||
bool file_existed = false;
|
||||
FILE *fp = NULL;
|
||||
if (instance->append == false)
|
||||
{
|
||||
// Just open the file (possibly overwriting) and then print header.
|
||||
fp = fopen(filename, "w");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Using fopen() with 'a+' means we will always write to the end but can read
|
||||
// anywhere. Depending on the "append"-setting the file has been
|
||||
// opened in different modes, which should be considered if file handling
|
||||
// changes later (e.g. rewinding).
|
||||
if ((fp = fopen(filename, "a+")) != NULL)
|
||||
{
|
||||
// Check to see if file already has contents
|
||||
fseek(fp, 0, SEEK_END);
|
||||
if (ftell(fp) > 0) // Any errors in ftell cause overwriting
|
||||
{
|
||||
file_existed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fp && !file_existed)
|
||||
{
|
||||
// Print a header. Luckily, we know the header has limited length
|
||||
const char SERVICE[] = "Service,";
|
||||
const char SESSION[] = "Session,";
|
||||
const char DATE[] = "Date,";
|
||||
const char USERHOST[] = "User@Host,";
|
||||
const char QUERY[] = "Query,";
|
||||
|
||||
const int headerlen = sizeof(SERVICE) + sizeof(SERVICE) + sizeof(DATE) +
|
||||
sizeof(USERHOST) + sizeof(QUERY);
|
||||
|
||||
char print_str[headerlen];
|
||||
memset(print_str, '\0', headerlen);
|
||||
|
||||
char *current_pos = print_str;
|
||||
if (instance->log_file_data_flags & LOG_DATA_SERVICE)
|
||||
{
|
||||
strcat(current_pos, SERVICE);
|
||||
current_pos += sizeof(SERVICE) - 1;
|
||||
}
|
||||
if (instance->log_file_data_flags & LOG_DATA_SESSION)
|
||||
{
|
||||
strcat(current_pos, SESSION);
|
||||
current_pos += sizeof(SERVICE) - 1;
|
||||
}
|
||||
if (instance->log_file_data_flags & LOG_DATA_DATE)
|
||||
{
|
||||
strcat(current_pos, DATE);
|
||||
current_pos += sizeof(DATE) - 1;
|
||||
}
|
||||
if (instance->log_file_data_flags & LOG_DATA_USER)
|
||||
{
|
||||
strcat(current_pos, USERHOST);
|
||||
current_pos += sizeof(USERHOST) - 1;
|
||||
}
|
||||
if (instance->log_file_data_flags & LOG_DATA_QUERY)
|
||||
{
|
||||
strcat(current_pos, QUERY);
|
||||
current_pos += sizeof(QUERY) - 1;
|
||||
}
|
||||
if (current_pos > print_str)
|
||||
{
|
||||
// Overwrite the last ','.
|
||||
*(current_pos - 1) = '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing to print
|
||||
return fp;
|
||||
}
|
||||
|
||||
// Finally, write the log header.
|
||||
int written = fprintf(fp, "%s", print_str);
|
||||
|
||||
if ((written <= 0) ||
|
||||
((instance->flush_writes) && (fflush(fp) < 0)))
|
||||
{
|
||||
// Weird error, file opened but a write failed. Best to stop.
|
||||
fclose(fp);
|
||||
MXS_ERROR("qlafilter: Failed to print header to file %s.", filename);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an entry to the log file.
|
||||
* @param data_flags Controls what to write
|
||||
* @param logfile Target file
|
||||
* @param instance Filter instance
|
||||
* @param session Filter session
|
||||
* @param time_string Date entry
|
||||
* @param sql_string SQL-query, not NULL terminated!
|
||||
* @param sql_str_len Length of SQL-string
|
||||
* @return The number of characters written, or a negative value on failure
|
||||
*/
|
||||
static int write_log_entry(uint32_t data_flags, FILE *logfile, QLA_INSTANCE *instance,
|
||||
QLA_SESSION *session, const char *time_string, const char *sql_string,
|
||||
size_t sql_str_len)
|
||||
{
|
||||
ss_dassert(logfile != NULL);
|
||||
size_t print_len = 0;
|
||||
|
||||
// First calculate an upper limit for the total length. The strlen()-calls
|
||||
// could be removed if the values would be saved into the instance or session
|
||||
// or if we had some reasonable max lengths. (Apparently there are max lengths
|
||||
// but they are much higher than what is typically needed)
|
||||
|
||||
// The numbers have some extra for delimiters.
|
||||
if (data_flags & LOG_DATA_SERVICE)
|
||||
{
|
||||
print_len += strlen(session->service) + 1;
|
||||
}
|
||||
if (data_flags & LOG_DATA_SESSION)
|
||||
{
|
||||
print_len += 20; // To print a 64bit integer
|
||||
}
|
||||
if (data_flags & LOG_DATA_DATE)
|
||||
{
|
||||
print_len += QLA_DATE_BUFFER_SIZE + 1;
|
||||
}
|
||||
if (data_flags & LOG_DATA_USER)
|
||||
{
|
||||
print_len += strlen(session->user) + strlen(session->remote) + 2;
|
||||
}
|
||||
if (data_flags & LOG_DATA_QUERY)
|
||||
{
|
||||
print_len += sql_str_len + 1; // Can't use strlen, not null-terminated
|
||||
}
|
||||
|
||||
if (print_len == 0)
|
||||
{
|
||||
return 0; // Nothing to print
|
||||
}
|
||||
|
||||
// Allocate space for a buffer. Printing to the file in parts would likely
|
||||
// cause garbled printing if several threads write simultaneously, so we
|
||||
// have to first print to a string.
|
||||
char *print_str = NULL;
|
||||
if ((print_str = MXS_CALLOC(print_len, sizeof(char))) == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
char *current_pos = print_str;
|
||||
int rval = 0;
|
||||
if (!error && (data_flags & LOG_DATA_SERVICE))
|
||||
{
|
||||
if ((rval = sprintf(current_pos, "%s,", session->service)) < 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_pos += rval;
|
||||
}
|
||||
}
|
||||
if (!error && (data_flags & LOG_DATA_SESSION))
|
||||
{
|
||||
if ((rval = sprintf(current_pos, "%lu,", session->ses_id)) < 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_pos += rval;
|
||||
}
|
||||
}
|
||||
if (!error && (data_flags & LOG_DATA_DATE))
|
||||
{
|
||||
if ((rval = sprintf(current_pos, "%s,", time_string)) < 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_pos += rval;
|
||||
}
|
||||
}
|
||||
if (!error && (data_flags & LOG_DATA_USER))
|
||||
{
|
||||
if ((rval = sprintf(current_pos, "%s@%s,", session->user, session->remote)) < 0)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_pos += rval;
|
||||
}
|
||||
}
|
||||
if (!error && (data_flags & LOG_DATA_QUERY))
|
||||
{
|
||||
strncat(current_pos, sql_string, sql_str_len); // non-null-terminated string
|
||||
current_pos += sql_str_len + 1; // +1 to move to the next char after
|
||||
}
|
||||
if (error || current_pos <= print_str)
|
||||
{
|
||||
MXS_FREE(print_str);
|
||||
MXS_ERROR("qlafilter ('%s'): Failed to format log event.", instance->name);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Overwrite the last ','. The rest is already filled with 0.
|
||||
*(current_pos - 1) = '\n';
|
||||
}
|
||||
|
||||
// Finally, write the log event.
|
||||
int written = fprintf(logfile, "%s", print_str);
|
||||
MXS_FREE(print_str);
|
||||
|
||||
if ((!instance->flush_writes) || (written <= 0))
|
||||
{
|
||||
return written;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try flushing. If successful, still return the characters written.
|
||||
int rval = fflush(logfile);
|
||||
if (rval >= 0)
|
||||
{
|
||||
return written;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
|
@ -596,8 +596,7 @@ newSession(FILTER *instance, SESSION *session)
|
||||
my_session->branch_dcb = dcb;
|
||||
my_session->dummy_filterdef = dummy;
|
||||
|
||||
if ((dummy_upstream = filterUpstream(
|
||||
dummy, my_session, &ses->tail)) == NULL)
|
||||
if ((dummy_upstream = filter_upstream(dummy, my_session, &ses->tail)) == NULL)
|
||||
{
|
||||
filter_free(dummy);
|
||||
closeSession(instance, (void*) my_session);
|
||||
@ -702,7 +701,7 @@ freeSession(FILTER *instance, void *session)
|
||||
|
||||
if (state == SESSION_STATE_ROUTER_READY)
|
||||
{
|
||||
session_free(ses);
|
||||
session_put_ref(ses);
|
||||
}
|
||||
else if (state == SESSION_STATE_TO_BE_FREED)
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
if(BUILD_AVRO)
|
||||
add_subdirectory(avro)
|
||||
add_subdirectory(avrorouter)
|
||||
endif()
|
||||
if(BUILD_BINLOG)
|
||||
add_subdirectory(binlog)
|
||||
add_subdirectory(binlogrouter)
|
||||
endif()
|
||||
|
||||
add_subdirectory(cli)
|
||||
|
@ -1,7 +1,7 @@
|
||||
if(AVRO_FOUND AND JANSSON_FOUND)
|
||||
include_directories(${AVRO_INCLUDE_DIR})
|
||||
include_directories(${JANSSON_INCLUDE_DIR})
|
||||
add_library(avrorouter SHARED avro.c ../binlog/binlog_common.c avro_client.c avro_schema.c avro_rbr.c avro_file.c avro_index.c)
|
||||
add_library(avrorouter SHARED avro.c ../binlogrouter/binlog_common.c avro_client.c avro_schema.c avro_rbr.c avro_file.c avro_index.c)
|
||||
set_target_properties(avrorouter PROPERTIES VERSION "1.0.0")
|
||||
set_target_properties(avrorouter PROPERTIES LINK_FLAGS -Wl,-z,defs)
|
||||
target_link_libraries(avrorouter maxscale-common ${JANSSON_LIBRARIES} ${AVRO_LIBRARIES} maxavro sqlite3 lzma)
|
@ -568,7 +568,6 @@ createInstance(SERVICE *service, char **options)
|
||||
inst->reconnect_pending = 0;
|
||||
inst->handling_threads = 0;
|
||||
inst->rotating = 0;
|
||||
inst->residual = NULL;
|
||||
inst->slaves = NULL;
|
||||
inst->next = NULL;
|
||||
inst->lastEventTimestamp = 0;
|
||||
@ -1847,7 +1846,7 @@ static void rses_end_locked_router_action(ROUTER_SLAVE *rses)
|
||||
|
||||
static uint64_t getCapabilities(void)
|
||||
{
|
||||
return RCAP_TYPE_NO_RSESSION;
|
||||
return RCAP_TYPE_NO_RSESSION | RCAP_TYPE_CONTIGUOUS_OUTPUT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2413,13 +2412,6 @@ destroyInstance(ROUTER *instance)
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard the queued residual data */
|
||||
while (inst->residual)
|
||||
{
|
||||
inst->residual = gwbuf_consume(inst->residual, GWBUF_LENGTH(inst->residual));
|
||||
}
|
||||
inst->residual = NULL;
|
||||
|
||||
MXS_INFO("%s is being stopped by MaxScale shudown. Disconnecting from master %s:%d, "
|
||||
"read up to log %s, pos %lu, transaction safe pos %lu",
|
||||
inst->service->name,
|
@ -251,12 +251,9 @@ static const char BLR_DBUSERS_FILE[] = "dbusers";
|
||||
/** Possible states of an event sent by the master */
|
||||
enum blr_event_state
|
||||
{
|
||||
BLR_EVENT_DONE, /*< No event being processed */
|
||||
BLR_EVENT_STARTED, /*< The first packet of an event which spans multiple packets
|
||||
* has been received */
|
||||
BLR_EVENT_STARTED, /*< The first packet of an event has been received */
|
||||
BLR_EVENT_ONGOING, /*< Other packets of a multi-packet event are being processed */
|
||||
BLR_EVENT_COMPLETE /*< A multi-packet event has been successfully processed
|
||||
* but the router is not yet ready to process another one */
|
||||
BLR_EVENT_DONE, /*< The complete event was received */
|
||||
};
|
||||
|
||||
/* Master Server configuration struct */
|
||||
@ -524,19 +521,14 @@ typedef struct router_instance
|
||||
unsigned int master_state; /*< State of the master FSM */
|
||||
uint8_t lastEventReceived; /*< Last even received */
|
||||
uint32_t lastEventTimestamp; /*< Timestamp from last event */
|
||||
GWBUF *residual; /*< Any residual binlog event */
|
||||
MASTER_RESPONSES saved_master; /*< Saved master responses */
|
||||
char *binlogdir; /*< The directory with the binlog files */
|
||||
SPINLOCK binlog_lock; /*< Lock to control update of the binlog position */
|
||||
int trx_safe; /*< Detect and handle partial transactions */
|
||||
int pending_transaction; /*< Pending transaction */
|
||||
enum blr_event_state master_event_state; /*< Packet read state */
|
||||
uint32_t stored_checksum; /*< The current value of the checksum */
|
||||
uint8_t partial_checksum[MYSQL_CHECKSUM_LEN]; /*< The partial value of the checksum
|
||||
* received from the master */
|
||||
uint8_t partial_checksum_bytes; /*< How many bytes of the checksum we have read */
|
||||
uint64_t checksum_size; /*< Data size for the checksum */
|
||||
REP_HEADER stored_header; /*< Relication header of the event the master is sending */
|
||||
GWBUF *stored_event; /*< Buffer where partial events are stored */
|
||||
uint64_t last_safe_pos; /* last committed transaction */
|
||||
char binlog_name[BINLOG_FNAMELEN + 1];
|
||||
/*< Name of the current binlog file */
|
@ -104,8 +104,6 @@ static void blr_extract_header_semisync(uint8_t *pkt, REP_HEADER *hdr);
|
||||
static int blr_send_semisync_ack (ROUTER_INSTANCE *router, uint64_t pos);
|
||||
static int blr_get_master_semisync(GWBUF *buf);
|
||||
|
||||
int blr_write_data_into_binlog(ROUTER_INSTANCE *router, uint32_t data_len, uint8_t *buf);
|
||||
void extract_checksum(ROUTER_INSTANCE* router, uint8_t *cksumptr, uint8_t len);
|
||||
static void blr_terminate_master_replication(ROUTER_INSTANCE *router, uint8_t* ptr, int len);
|
||||
void blr_notify_all_slaves(ROUTER_INSTANCE *router);
|
||||
extern bool blr_notify_waiting_slave(ROUTER_SLAVE *slave);
|
||||
@ -168,13 +166,6 @@ blr_start_master(void* data)
|
||||
}
|
||||
router->master_state = BLRM_CONNECTING;
|
||||
|
||||
/* Discard the queued residual data */
|
||||
while (router->residual)
|
||||
{
|
||||
router->residual = gwbuf_consume(router->residual, GWBUF_LENGTH(router->residual));
|
||||
}
|
||||
router->residual = NULL;
|
||||
|
||||
spinlock_release(&router->lock);
|
||||
if ((client = dcb_alloc(DCB_ROLE_INTERNAL, NULL)) == NULL)
|
||||
{
|
||||
@ -242,13 +233,6 @@ blr_restart_master(ROUTER_INSTANCE *router)
|
||||
{
|
||||
dcb_close(router->client);
|
||||
|
||||
/* Discard the queued residual data */
|
||||
while (router->residual)
|
||||
{
|
||||
router->residual = gwbuf_consume(router->residual, GWBUF_LENGTH(router->residual));
|
||||
}
|
||||
router->residual = NULL;
|
||||
|
||||
/* Now it is safe to unleash other threads on this router instance */
|
||||
spinlock_acquire(&router->lock);
|
||||
router->reconnect_pending = 0;
|
||||
@ -335,6 +319,8 @@ blr_master_close(ROUTER_INSTANCE *router)
|
||||
dcb_close(router->master);
|
||||
router->master_state = BLRM_UNCONNECTED;
|
||||
router->master_event_state = BLR_EVENT_DONE;
|
||||
gwbuf_free(router->stored_event);
|
||||
router->stored_event = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -987,6 +973,63 @@ encode_value(unsigned char *data, unsigned int value, int len)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the stored event checksum matches the calculated checksum
|
||||
*/
|
||||
static bool verify_checksum(ROUTER_INSTANCE *router, size_t len, uint8_t *ptr)
|
||||
{
|
||||
bool rval = true;
|
||||
uint32_t offset = MYSQL_HEADER_LEN + 1;
|
||||
uint32_t size = len - (offset + MYSQL_CHECKSUM_LEN);
|
||||
|
||||
uint32_t checksum = crc32(0L, ptr + offset, size);
|
||||
uint32_t pktsum = EXTRACT32(ptr + offset + size);
|
||||
|
||||
if (pktsum != checksum)
|
||||
{
|
||||
rval = false;
|
||||
MXS_ERROR("%s: Checksum error in event from master, "
|
||||
"binlog %s @ %lu. Closing master connection.",
|
||||
router->service->name, router->binlog_name,
|
||||
router->current_pos);
|
||||
router->stats.n_badcrc++;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset router errors
|
||||
*
|
||||
* @param router Router instance
|
||||
* @param hdr Replication header
|
||||
*/
|
||||
static void reset_errors(ROUTER_INSTANCE *router, REP_HEADER *hdr)
|
||||
{
|
||||
spinlock_acquire(&router->lock);
|
||||
|
||||
/* set mysql errno to 0 */
|
||||
router->m_errno = 0;
|
||||
|
||||
/* Remove error message */
|
||||
if (router->m_errmsg)
|
||||
{
|
||||
MXS_FREE(router->m_errmsg);
|
||||
}
|
||||
router->m_errmsg = NULL;
|
||||
|
||||
spinlock_release(&router->lock);
|
||||
#ifdef SHOW_EVENTS
|
||||
printf("blr: len %lu, event type 0x%02x, flags 0x%04x, "
|
||||
"event size %d, event timestamp %lu\n",
|
||||
(unsigned long)len - 4,
|
||||
hdr->event_type,
|
||||
hdr->flags,
|
||||
hdr->event_size,
|
||||
(unsigned long)hdr->timestamp);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* blr_handle_binlog_record - we have received binlog records from
|
||||
* the master and we must now work out what to do with them.
|
||||
@ -997,134 +1040,24 @@ encode_value(unsigned char *data, unsigned int value, int len)
|
||||
void
|
||||
blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
{
|
||||
uint8_t *msg = NULL, *ptr, *pdata;
|
||||
uint8_t *msg = NULL, *ptr;
|
||||
REP_HEADER hdr;
|
||||
unsigned int len = 0, reslen;
|
||||
unsigned int pkt_length;
|
||||
int no_residual = 1;
|
||||
int preslen = -1;
|
||||
unsigned int len = 0;
|
||||
int prev_length = -1;
|
||||
int n_bufs = -1, pn_bufs = -1;
|
||||
int check_packet_len;
|
||||
int semisync_bytes;
|
||||
int semi_sync_send_ack = 0;
|
||||
|
||||
/*
|
||||
* Prepend any residual buffer to the buffer chain we have
|
||||
* been called with.
|
||||
*/
|
||||
if (router->residual)
|
||||
{
|
||||
pkt = gwbuf_append(router->residual, pkt);
|
||||
router->residual = NULL;
|
||||
no_residual = 0;
|
||||
}
|
||||
|
||||
pkt_length = gwbuf_length(pkt);
|
||||
/*
|
||||
* Loop over all the packets while we still have some data
|
||||
* and the packet length is enough to hold a replication event
|
||||
* header.
|
||||
*/
|
||||
while (pkt && pkt_length > 24)
|
||||
while (pkt)
|
||||
{
|
||||
reslen = GWBUF_LENGTH(pkt);
|
||||
pdata = GWBUF_DATA(pkt);
|
||||
if (reslen < 3) // Payload length straddles buffers
|
||||
{
|
||||
/* Get the length of the packet from the residual and new packet */
|
||||
if (reslen >= 3)
|
||||
{
|
||||
len = EXTRACT24(pdata);
|
||||
}
|
||||
else if (reslen == 2)
|
||||
{
|
||||
len = EXTRACT16(pdata);
|
||||
len |= (*(uint8_t *)GWBUF_DATA(pkt->next) << 16);
|
||||
}
|
||||
else if (reslen == 1)
|
||||
{
|
||||
len = *pdata;
|
||||
len |= (EXTRACT16(GWBUF_DATA(pkt->next)) << 8);
|
||||
}
|
||||
len += 4; // Allow space for the header
|
||||
}
|
||||
else
|
||||
{
|
||||
len = EXTRACT24(pdata) + 4;
|
||||
}
|
||||
/* len is now the payload length for the packet we are working on */
|
||||
|
||||
if (reslen < len && pkt_length >= len)
|
||||
{
|
||||
/*
|
||||
* The message is contained in more than the current
|
||||
* buffer, however we have the complete messasge in
|
||||
* this buffer and the chain of remaining buffers.
|
||||
*
|
||||
* Allocate a contiguous buffer for the binlog message
|
||||
* and copy the complete message into this buffer.
|
||||
*/
|
||||
int msg_remainder = len;
|
||||
GWBUF *p = pkt;
|
||||
|
||||
if ((msg = MXS_MALLOC(len)) == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
n_bufs = 0;
|
||||
ptr = msg;
|
||||
while (p && msg_remainder > 0)
|
||||
{
|
||||
int plen = GWBUF_LENGTH(p);
|
||||
int n = (msg_remainder > plen ? plen : msg_remainder);
|
||||
memcpy(ptr, GWBUF_DATA(p), n);
|
||||
msg_remainder -= n;
|
||||
ptr += n;
|
||||
if (msg_remainder > 0)
|
||||
{
|
||||
p = p->next;
|
||||
}
|
||||
n_bufs++;
|
||||
}
|
||||
if (msg_remainder)
|
||||
{
|
||||
MXS_ERROR("Expected entire message in buffer "
|
||||
"chain, but failed to create complete "
|
||||
"message as expected. %s @ %lu",
|
||||
router->binlog_name,
|
||||
router->current_pos);
|
||||
MXS_FREE(msg);
|
||||
/* msg = NULL; Not needed unless msg will be referred to again */
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = msg;
|
||||
}
|
||||
else if (reslen < len)
|
||||
{
|
||||
/*
|
||||
* The message is not fully contained in the current
|
||||
* and we do not have the complete message in the
|
||||
* buffer chain. Therefore we must stop processing
|
||||
* until we receive the next buffer.
|
||||
*/
|
||||
router->stats.n_residuals++;
|
||||
MXS_DEBUG("Residual data left after %lu records. %s @ %lu",
|
||||
router->stats.n_binlogs,
|
||||
router->binlog_name, router->current_pos);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* The message is fully contained in the current buffer
|
||||
*/
|
||||
ptr = pdata;
|
||||
n_bufs = 1;
|
||||
}
|
||||
|
||||
ptr = GWBUF_DATA(pkt);
|
||||
len = gw_mysql_get_byte3(ptr);
|
||||
semisync_bytes = 0;
|
||||
|
||||
/*
|
||||
@ -1135,7 +1068,7 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
|
||||
if (len < BINLOG_EVENT_HDR_LEN && router->master_event_state != BLR_EVENT_ONGOING)
|
||||
{
|
||||
char *event_msg = "";
|
||||
char *event_msg = "unknown";
|
||||
|
||||
/* Packet is too small to be a binlog event */
|
||||
if (ptr[4] == 0xfe) /* EOF Packet */
|
||||
@ -1147,11 +1080,13 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
event_msg = "error";
|
||||
}
|
||||
MXS_NOTICE("Non-event message (%s) from master.", event_msg);
|
||||
pkt = gwbuf_consume(pkt, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (router->master_event_state == BLR_EVENT_DONE)
|
||||
{
|
||||
/** This is the start of a new event */
|
||||
spinlock_acquire(&router->lock);
|
||||
router->stats.n_binlogs++;
|
||||
router->stats.n_binlogs_ses++;
|
||||
@ -1187,20 +1122,16 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
/* Sanity check */
|
||||
if (hdr.ok == 0)
|
||||
{
|
||||
if (hdr.event_size != len - check_packet_len &&
|
||||
if (hdr.event_size != len - (check_packet_len - MYSQL_HEADER_LEN) &&
|
||||
(hdr.event_size + (check_packet_len - MYSQL_HEADER_LEN)) < MYSQL_PACKET_LENGTH_MAX)
|
||||
{
|
||||
MXS_ERROR("Packet length is %d, but event size is %d, "
|
||||
"binlog file %s position %lu "
|
||||
"reslen is %d and preslen is %d, "
|
||||
"length of previous event %d. %s",
|
||||
"binlog file %s position %lu, "
|
||||
"length of previous event %d.",
|
||||
len, hdr.event_size,
|
||||
router->binlog_name,
|
||||
router->current_pos,
|
||||
reslen, preslen, prev_length,
|
||||
(prev_length == -1 ?
|
||||
(no_residual ? "No residual data from previous call" :
|
||||
"Residual data from previous call") : ""));
|
||||
prev_length);
|
||||
|
||||
blr_log_packet(LOG_ERR, "Packet:", ptr, len);
|
||||
|
||||
@ -1216,18 +1147,13 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
|
||||
break;
|
||||
}
|
||||
else if (hdr.event_size + (check_packet_len - MYSQL_HEADER_LEN) >= MYSQL_PACKET_LENGTH_MAX)
|
||||
{
|
||||
router->master_event_state = BLR_EVENT_STARTED;
|
||||
|
||||
/** Store the header for later use */
|
||||
memcpy(&router->stored_header, &hdr, sizeof(hdr));
|
||||
}
|
||||
|
||||
/** Prepare the checksum variables for this event */
|
||||
router->stored_checksum = crc32(0L, NULL, 0);
|
||||
router->checksum_size = hdr.event_size - MYSQL_CHECKSUM_LEN;
|
||||
router->partial_checksum_bytes = 0;
|
||||
/** This is the first (and possibly last) packet of a replication
|
||||
* event. We store the header in case the event is large and
|
||||
* it is transmitted over multiple network packets. */
|
||||
router->master_event_state = BLR_EVENT_STARTED;
|
||||
memcpy(&router->stored_header, &hdr, sizeof(hdr));
|
||||
reset_errors(router, &hdr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1236,159 +1162,96 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
|
||||
gwbuf_free(pkt);
|
||||
pkt = NULL;
|
||||
pkt_length = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (hdr.ok == 0)
|
||||
{
|
||||
spinlock_acquire(&router->lock);
|
||||
|
||||
/* set mysql errno to 0 */
|
||||
router->m_errno = 0;
|
||||
|
||||
/* Remove error message */
|
||||
if (router->m_errmsg)
|
||||
{
|
||||
MXS_FREE(router->m_errmsg);
|
||||
}
|
||||
router->m_errmsg = NULL;
|
||||
|
||||
spinlock_release(&router->lock);
|
||||
#ifdef SHOW_EVENTS
|
||||
printf("blr @ %lu: len %lu, event type 0x%02x, flags 0x%04x, "
|
||||
"event size %d, event timestamp %lu, next pos %lu\n",
|
||||
router->current_pos,
|
||||
(unsigned long)len - 4,
|
||||
hdr.event_type,
|
||||
hdr.flags,
|
||||
hdr.event_size,
|
||||
(unsigned long)hdr.timestamp,
|
||||
(unsigned long)hdr.next_pos);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/** We're processing a multi-packet replication event */
|
||||
ss_dassert(router->master_event_state == BLR_EVENT_ONGOING);
|
||||
}
|
||||
|
||||
/* pending large event */
|
||||
if (router->master_event_state != BLR_EVENT_DONE)
|
||||
/** Gather the event into one big buffer */
|
||||
GWBUF *part = gwbuf_split(&pkt, len + MYSQL_HEADER_LEN);
|
||||
|
||||
if (semisync_bytes)
|
||||
{
|
||||
if (len - MYSQL_HEADER_LEN < MYSQL_PACKET_LENGTH_MAX)
|
||||
/** Consume the two semi-sync bytes */
|
||||
part = gwbuf_consume(part, semisync_bytes);
|
||||
}
|
||||
|
||||
ss_dassert(router->master_event_state == BLR_EVENT_STARTED ||
|
||||
router->master_event_state == BLR_EVENT_ONGOING);
|
||||
|
||||
if (router->master_event_state == BLR_EVENT_ONGOING)
|
||||
{
|
||||
/**
|
||||
* Consume the network header so that we can append the raw
|
||||
* event data to the original buffer. This allows both checksum
|
||||
* calculations and encryption to process it as a contiguous event
|
||||
*/
|
||||
part = gwbuf_consume(part, MYSQL_HEADER_LEN);
|
||||
}
|
||||
|
||||
router->stored_event = gwbuf_append(router->stored_event, part);
|
||||
|
||||
if (len < MYSQL_PACKET_LENGTH_MAX)
|
||||
{
|
||||
/**
|
||||
* This is either the only packet for the event or the last
|
||||
* packet in a series for this event. The buffer now contains
|
||||
* the network header of the first packet (4 bytes) and one OK byte.
|
||||
* The semi-sync bytes are always consumed at an earlier stage.
|
||||
*/
|
||||
ss_dassert(router->master_event_state != BLR_EVENT_DONE);
|
||||
|
||||
if (router->master_event_state != BLR_EVENT_STARTED)
|
||||
{
|
||||
/** This is the last packet, we can now proceed to distribute
|
||||
* the event afer it has been written to disk */
|
||||
ss_dassert(router->master_event_state != BLR_EVENT_COMPLETE);
|
||||
router->master_event_state = BLR_EVENT_COMPLETE;
|
||||
/**
|
||||
* This is not the first packet for this event. We must use
|
||||
* the stored header.
|
||||
*/
|
||||
memcpy(&hdr, &router->stored_header, sizeof(hdr));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* current partial event is being written to disk file */
|
||||
uint32_t offset = MYSQL_HEADER_LEN;
|
||||
uint32_t extra_bytes = MYSQL_HEADER_LEN;
|
||||
|
||||
/** Don't write the OK byte into the binlog */
|
||||
if (router->master_event_state == BLR_EVENT_STARTED)
|
||||
{
|
||||
offset = MYSQL_HEADER_LEN + 1;
|
||||
router->master_event_state = BLR_EVENT_ONGOING;
|
||||
extra_bytes = MYSQL_HEADER_LEN + 1;
|
||||
}
|
||||
|
||||
ss_dassert(len - extra_bytes - semisync_bytes > 0);
|
||||
uint32_t bytes_available = len - extra_bytes - semisync_bytes;
|
||||
|
||||
if (router->master_chksum)
|
||||
{
|
||||
uint32_t size = MXS_MIN(len - extra_bytes - semisync_bytes,
|
||||
router->checksum_size);
|
||||
|
||||
router->stored_checksum = crc32(router->stored_checksum,
|
||||
ptr + offset,
|
||||
size);
|
||||
router->checksum_size -= size;
|
||||
|
||||
if (router->checksum_size == 0 && size < bytes_available)
|
||||
{
|
||||
extract_checksum(router, ptr + offset + size,
|
||||
bytes_available - size);
|
||||
}
|
||||
}
|
||||
|
||||
if (blr_write_data_into_binlog(router, bytes_available,
|
||||
ptr + offset) == 0)
|
||||
{
|
||||
/** Failed to write to the binlog file, destroy the buffer
|
||||
* chain and close the connection with the master */
|
||||
while (pkt)
|
||||
{
|
||||
pkt = GWBUF_CONSUME_ALL(pkt);
|
||||
}
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
pkt = gwbuf_consume(pkt, len);
|
||||
pkt_length -= len;
|
||||
continue;
|
||||
}
|
||||
/** The event is now complete */
|
||||
router->master_event_state = BLR_EVENT_DONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/**
|
||||
* This packet is a part of a series of packets that contain an
|
||||
* event larger than MYSQL_PACKET_LENGTH_MAX bytes.
|
||||
*
|
||||
* For each partial event chunk, we remove the network header and
|
||||
* append it to router->stored_event. The first event is an
|
||||
* exception to this and it is appended as-is with the network
|
||||
* header and the extra OK byte.
|
||||
*/
|
||||
ss_dassert(len == MYSQL_PACKET_LENGTH_MAX);
|
||||
router->master_event_state = BLR_EVENT_ONGOING;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* First check that the checksum we calculate matches the
|
||||
* checksum in the packet we received.
|
||||
/** We now have the complete event in one contiguous buffer */
|
||||
router->stored_event = gwbuf_make_contiguous(router->stored_event);
|
||||
ptr = GWBUF_DATA(router->stored_event);
|
||||
|
||||
/** len is now the length of the complete event plus 4 bytes of network
|
||||
* header and one OK byte. Semi-sync bytes are never stored. */
|
||||
len = gwbuf_length(router->stored_event);
|
||||
|
||||
/**
|
||||
* If checksums are enabled, verify that the stored checksum
|
||||
* matches the one we calculated
|
||||
*/
|
||||
if (router->master_chksum)
|
||||
if (router->master_chksum && !verify_checksum(router, len, ptr))
|
||||
{
|
||||
uint32_t offset = MYSQL_HEADER_LEN;
|
||||
uint32_t size = len - MYSQL_HEADER_LEN - MYSQL_CHECKSUM_LEN;
|
||||
|
||||
if (router->master_event_state == BLR_EVENT_DONE)
|
||||
{
|
||||
/** Set the pointer offset to the first byte after
|
||||
* the header and OK byte */
|
||||
offset = MYSQL_HEADER_LEN + 1;
|
||||
size = len - (check_packet_len + MYSQL_CHECKSUM_LEN);
|
||||
}
|
||||
|
||||
size = MXS_MIN(size, router->checksum_size);
|
||||
|
||||
if (router->checksum_size > 0)
|
||||
{
|
||||
router->stored_checksum = crc32(router->stored_checksum,
|
||||
ptr + offset, size);
|
||||
router->checksum_size -= size;
|
||||
}
|
||||
|
||||
if(router->checksum_size == 0 && size < (len - offset - semisync_bytes))
|
||||
{
|
||||
extract_checksum(router, ptr + offset + size,
|
||||
len - offset - size - semisync_bytes);
|
||||
}
|
||||
|
||||
if (router->partial_checksum_bytes == MYSQL_CHECKSUM_LEN)
|
||||
{
|
||||
uint32_t pktsum = EXTRACT32(router->partial_checksum);
|
||||
if (pktsum != router->stored_checksum)
|
||||
{
|
||||
router->stats.n_badcrc++;
|
||||
MXS_FREE(msg);
|
||||
/* msg = NULL; Not needed unless msg will be referred to again */
|
||||
MXS_ERROR("%s: Checksum error in event from master, "
|
||||
"binlog %s @ %lu. Closing master connection.",
|
||||
router->service->name, router->binlog_name,
|
||||
router->current_pos);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pkt = gwbuf_consume(pkt, len);
|
||||
pkt_length -= len;
|
||||
continue;
|
||||
}
|
||||
MXS_FREE(msg);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hdr.ok == 0)
|
||||
@ -1583,16 +1446,6 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
}
|
||||
else if (hdr.flags != LOG_EVENT_ARTIFICIAL_F)
|
||||
{
|
||||
ptr = ptr + 4; // Skip header
|
||||
uint32_t offset = 4;
|
||||
|
||||
if (router->master_event_state == BLR_EVENT_STARTED ||
|
||||
router->master_event_state == BLR_EVENT_DONE)
|
||||
{
|
||||
ptr++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
{
|
||||
spinlock_acquire(&router->binlog_lock);
|
||||
@ -1600,54 +1453,36 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
spinlock_release(&router->binlog_lock);
|
||||
}
|
||||
|
||||
/* Current event is being written to disk file.
|
||||
* It is possible for an empty packet to be sent if an
|
||||
* event is exactly 2^24 bytes long. In this case the
|
||||
* empty packet should be discarded. */
|
||||
if (len > MYSQL_HEADER_LEN &&
|
||||
blr_write_binlog_record(router, &hdr, len - offset - semisync_bytes, ptr) == 0)
|
||||
uint32_t offset = MYSQL_HEADER_LEN + 1; // Skip header and OK byte
|
||||
|
||||
/**
|
||||
* Write the raw event data to disk without the network
|
||||
* header or the OK byte
|
||||
*/
|
||||
if (blr_write_binlog_record(router, &hdr, len - offset, ptr + offset) == 0)
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt, GWBUF_LENGTH(pkt))) != NULL)
|
||||
{
|
||||
;
|
||||
}
|
||||
gwbuf_free(pkt);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for rotete event */
|
||||
/* Check for rotate event */
|
||||
if (hdr.event_type == ROTATE_EVENT)
|
||||
{
|
||||
if (!blr_rotate_event(router, ptr, &hdr))
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt, GWBUF_LENGTH(pkt))) != NULL)
|
||||
{
|
||||
;
|
||||
}
|
||||
gwbuf_free(pkt);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle semi-sync request fom master */
|
||||
/* Handle semi-sync request from master */
|
||||
if (router->master_semi_sync != MASTER_SEMISYNC_NOT_AVAILABLE &&
|
||||
semi_sync_send_ack == BLR_MASTER_SEMI_SYNC_ACK_REQ &&
|
||||
(router->master_event_state == BLR_EVENT_COMPLETE ||
|
||||
router->master_event_state == BLR_EVENT_DONE))
|
||||
(router->master_event_state == BLR_EVENT_DONE))
|
||||
{
|
||||
|
||||
MXS_DEBUG("%s: binlog record in file %s, pos %lu has "
|
||||
@ -1733,16 +1568,7 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
spinlock_release(&router->binlog_lock);
|
||||
if (!blr_rotate_event(router, ptr, &hdr))
|
||||
{
|
||||
/*
|
||||
* Failed to write to the
|
||||
* binlog file, destroy the
|
||||
* buffer chain and close the
|
||||
* connection with the master
|
||||
*/
|
||||
while ((pkt = gwbuf_consume(pkt, GWBUF_LENGTH(pkt))) != NULL)
|
||||
{
|
||||
;
|
||||
}
|
||||
gwbuf_free(pkt);
|
||||
blr_master_close(router);
|
||||
blr_master_delayed_connect(router);
|
||||
return;
|
||||
@ -1750,17 +1576,15 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A large event is now fully received and processed */
|
||||
if (router->master_event_state == BLR_EVENT_COMPLETE)
|
||||
{
|
||||
router->master_event_state = BLR_EVENT_DONE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
blr_terminate_master_replication(router, ptr, len);
|
||||
}
|
||||
|
||||
/** Finished processing the event */
|
||||
gwbuf_free(router->stored_event);
|
||||
router->stored_event = NULL;
|
||||
}
|
||||
|
||||
if (msg)
|
||||
@ -1768,33 +1592,8 @@ blr_handle_binlog_record(ROUTER_INSTANCE *router, GWBUF *pkt)
|
||||
MXS_FREE(msg);
|
||||
msg = NULL;
|
||||
}
|
||||
prev_length = len;
|
||||
while (len > 0)
|
||||
{
|
||||
unsigned int n, plen;
|
||||
plen = GWBUF_LENGTH(pkt);
|
||||
n = (plen < len ? plen : len);
|
||||
pkt = gwbuf_consume(pkt, n);
|
||||
len -= n;
|
||||
pkt_length -= n;
|
||||
}
|
||||
preslen = reslen;
|
||||
pn_bufs = n_bufs;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if we have a residual, part binlog message to deal with.
|
||||
* Just simply store the GWBUF for next time
|
||||
*/
|
||||
if (pkt)
|
||||
{
|
||||
router->residual = pkt;
|
||||
ss_dassert(pkt_length != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss_dassert(pkt_length == 0);
|
||||
}
|
||||
blr_file_flush(router);
|
||||
}
|
||||
|
||||
@ -2242,13 +2041,6 @@ blr_stop_start_master(ROUTER_INSTANCE *router)
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard the queued residual data */
|
||||
while (router->residual)
|
||||
{
|
||||
router->residual = gwbuf_consume(router->residual, GWBUF_LENGTH(router->residual));
|
||||
}
|
||||
router->residual = NULL;
|
||||
|
||||
router->master_state = BLRM_UNCONNECTED;
|
||||
spinlock_release(&router->lock);
|
||||
|
||||
@ -2598,26 +2390,6 @@ bool blr_send_event(blr_thread_role_t role,
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the checksum from the binlogs
|
||||
*
|
||||
* This updates the internal state of the router and will allow us to detect
|
||||
* if the checksum is split across two packets.
|
||||
* @param router Router instance
|
||||
* @param cksumptr Pointer to the checksum
|
||||
* @param len How much of the data is readable
|
||||
*/
|
||||
void extract_checksum(ROUTER_INSTANCE* router, uint8_t *cksumptr, uint8_t len)
|
||||
{
|
||||
uint8_t *ptr = cksumptr;
|
||||
while (ptr - cksumptr < len && router->partial_checksum_bytes < MYSQL_CHECKSUM_LEN)
|
||||
{
|
||||
router->partial_checksum[router->partial_checksum_bytes] = *ptr;
|
||||
ptr++;
|
||||
router->partial_checksum_bytes++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the slave connection and log errors
|
||||
*
|
@ -348,6 +348,7 @@ blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
int query_len;
|
||||
char *ptr;
|
||||
extern char *strcasestr();
|
||||
bool unexpected = true;
|
||||
|
||||
qtext = (char*)GWBUF_DATA(queue);
|
||||
query_len = extract_field((uint8_t *)qtext, 24) - 1;
|
||||
@ -534,6 +535,10 @@ blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
|
||||
return blr_slave_send_var_value(router, slave, heading, server_id, BLR_TYPE_INT);
|
||||
}
|
||||
else if (strcasestr(word, "binlog_gtid_pos"))
|
||||
{
|
||||
unexpected = false;
|
||||
}
|
||||
}
|
||||
else if (strcasecmp(word, "SHOW") == 0)
|
||||
{
|
||||
@ -1122,7 +1127,17 @@ blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
|
||||
MXS_FREE(query_text);
|
||||
|
||||
query_text = strndup(qtext, query_len);
|
||||
MXS_ERROR("Unexpected query from '%s'@'%s': %s", slave->dcb->user, slave->dcb->remote, query_text);
|
||||
|
||||
if (unexpected)
|
||||
{
|
||||
MXS_ERROR("Unexpected query from '%s'@'%s': %s", slave->dcb->user, slave->dcb->remote, query_text);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_INFO("Unexpected query from '%s'@'%s', possibly a 10.1 slave: %s",
|
||||
slave->dcb->user, slave->dcb->remote, query_text);
|
||||
}
|
||||
|
||||
MXS_FREE(query_text);
|
||||
blr_slave_send_error(router, slave,
|
||||
"You have an error in your SQL syntax; Check the syntax "
|
||||
@ -3444,13 +3459,6 @@ blr_stop_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard the queued residual data */
|
||||
while (router->residual)
|
||||
{
|
||||
router->residual = gwbuf_consume(router->residual, GWBUF_LENGTH(router->residual));
|
||||
}
|
||||
router->residual = NULL;
|
||||
|
||||
/* Now it is safe to unleash other threads on this router instance */
|
||||
router->reconnect_pending = 0;
|
||||
router->active_logs = 0;
|
@ -290,8 +290,8 @@ bool listfuncs_cb(const MODULECMD *cmd, void *data)
|
||||
{
|
||||
DCB *dcb = (DCB*)data;
|
||||
|
||||
dcb_printf(dcb, "%s::%s(", cmd->domain, cmd->identifier);
|
||||
|
||||
dcb_printf(dcb, "Command: %s %s\n", cmd->domain, cmd->identifier);
|
||||
dcb_printf(dcb, "Parameters: ");
|
||||
|
||||
for (int i = 0; i < cmd->arg_count_max; i++)
|
||||
{
|
||||
@ -309,8 +309,7 @@ bool listfuncs_cb(const MODULECMD *cmd, void *data)
|
||||
}
|
||||
}
|
||||
|
||||
dcb_printf(dcb, ")\n");
|
||||
|
||||
dcb_printf(dcb, "\n\n");
|
||||
|
||||
for (int i = 0; i < cmd->arg_count_max; i++)
|
||||
{
|
||||
@ -334,9 +333,9 @@ bool listfuncs_cb(const MODULECMD *cmd, void *data)
|
||||
return true;
|
||||
}
|
||||
|
||||
void dListFunctions(DCB *dcb)
|
||||
void dListCommands(DCB *dcb, const char *domain, const char *ident)
|
||||
{
|
||||
modulecmd_foreach(NULL, NULL, listfuncs_cb, dcb);
|
||||
modulecmd_foreach(domain, ident, listfuncs_cb, dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -405,10 +404,13 @@ struct subcommand listoptions[] =
|
||||
{0, 0, 0}
|
||||
},
|
||||
{
|
||||
"functions", 0, 0, dListFunctions,
|
||||
"List registered functions",
|
||||
"List all registered functions",
|
||||
{0}
|
||||
"commands", 0, 2, dListCommands,
|
||||
"List registered commands",
|
||||
"Usage list commands [DOMAIN] [COMMAND]\n"
|
||||
"Parameters:\n"
|
||||
"DOMAIN Regular expressions for filtering module domains\n"
|
||||
"COMMAND Regular expressions for filtering module commands\n",
|
||||
{ARG_TYPE_STRING, ARG_TYPE_STRING}
|
||||
},
|
||||
{ EMPTY_OPTION}
|
||||
};
|
||||
@ -1081,6 +1083,22 @@ static void createListener(DCB *dcb, SERVICE *service, char *name, char *address
|
||||
}
|
||||
}
|
||||
|
||||
static void createMonitor(DCB *dcb, const char *name, const char *module)
|
||||
{
|
||||
if (monitor_find(name))
|
||||
{
|
||||
dcb_printf(dcb, "Monitor '%s' already exists\n", name);
|
||||
}
|
||||
else if (runtime_create_monitor(name, module))
|
||||
{
|
||||
dcb_printf(dcb, "Created monitor '%s'\n", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to create monitor '%s', see log for more details\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
struct subcommand createoptions[] =
|
||||
{
|
||||
{
|
||||
@ -1127,6 +1145,16 @@ struct subcommand createoptions[] =
|
||||
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
||||
}
|
||||
},
|
||||
{
|
||||
"monitor", 2, 2, createMonitor,
|
||||
"Create a new monitor",
|
||||
"Usage: create monitor NAME MODULE\n"
|
||||
"NAME Monitor name\n"
|
||||
"MODULE Monitor module\n",
|
||||
{
|
||||
ARG_TYPE_STRING, ARG_TYPE_STRING
|
||||
}
|
||||
},
|
||||
{
|
||||
EMPTY_OPTION
|
||||
}
|
||||
@ -1162,6 +1190,22 @@ static void destroyListener(DCB *dcb, SERVICE *service, const char *name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void destroyMonitor(DCB *dcb, MONITOR *monitor)
|
||||
{
|
||||
char name[strlen(monitor->name) + 1];
|
||||
strcpy(name, monitor->name);
|
||||
|
||||
if (runtime_destroy_monitor(monitor))
|
||||
{
|
||||
dcb_printf(dcb, "Destroyed monitor '%s'\n", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to destroy monitor '%s', see log file for more details\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
struct subcommand destroyoptions[] =
|
||||
{
|
||||
{
|
||||
@ -1176,6 +1220,12 @@ struct subcommand destroyoptions[] =
|
||||
"Usage: destroy listener SERVICE NAME",
|
||||
{ARG_TYPE_SERVICE, ARG_TYPE_STRING}
|
||||
},
|
||||
{
|
||||
"monitor", 1, 1, destroyMonitor,
|
||||
"Destroy a monitor",
|
||||
"Usage: destroy monitor NAME",
|
||||
{ARG_TYPE_MONITOR}
|
||||
},
|
||||
{
|
||||
EMPTY_OPTION
|
||||
}
|
||||
@ -1290,6 +1340,13 @@ static void alterMonitor(DCB *dcb, MONITOR *monitor, char *v1, char *v2, char *v
|
||||
{
|
||||
dcb_printf(dcb, "Error: Bad key-value parameter: %s=%s\n", key, value);
|
||||
}
|
||||
else if (!monitor->created_online)
|
||||
{
|
||||
dcb_printf(dcb, "Warning: Altered monitor '%s' which is in the "
|
||||
"main\nconfiguration file. These changes will not be "
|
||||
"persisted and need\nto be manually added or set again"
|
||||
"after a restart.\n", monitor->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1316,9 +1373,8 @@ struct subcommand alteroptions[] =
|
||||
"monitor", 2, 12, alterMonitor,
|
||||
"Alter monitor parameters",
|
||||
"Usage: alter monitor NAME KEY=VALUE ...\n"
|
||||
"This will alter an existing parameter of a monitor. The accepted values\n"
|
||||
"for KEY are: 'user', 'password', 'monitor_interval',\n"
|
||||
"'backend_connect_timeout', 'backend_write_timeout', 'backend_read_timeout'\n"
|
||||
"This will alter an existing parameter of a monitor. To remove parameters,\n"
|
||||
"pass an empty value for a key e.g. 'maxadmin alter monitor my-monitor my-key='\n"
|
||||
"A maximum of 11 parameters can be changed at one time",
|
||||
{ARG_TYPE_MONITOR, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
||||
ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING, ARG_TYPE_STRING,
|
||||
@ -1335,9 +1391,9 @@ static inline bool requires_output_dcb(const MODULECMD *cmd)
|
||||
return cmd->arg_count_max > 0 && MODULECMD_GET_TYPE(type) == MODULECMD_ARG_OUTPUT;
|
||||
}
|
||||
|
||||
static void callFunction(DCB *dcb, char *domain, char *id, char *v3,
|
||||
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
||||
char *v10, char *v11, char *v12)
|
||||
static void callModuleCommand(DCB *dcb, char *domain, char *id, char *v3,
|
||||
char *v4, char *v5, char *v6, char *v7, char *v8, char *v9,
|
||||
char *v10, char *v11, char *v12)
|
||||
{
|
||||
const void *values[11] = {v3, v4, v5, v6, v7, v8, v9, v10, v11, v12};
|
||||
const int valuelen = sizeof(values) / sizeof(values[0]);
|
||||
@ -1354,7 +1410,7 @@ static void callFunction(DCB *dcb, char *domain, char *id, char *v3,
|
||||
{
|
||||
if (requires_output_dcb(cmd))
|
||||
{
|
||||
/** The function requires a DCB for output, add the client DCB
|
||||
/** The command requires a DCB for output, add the client DCB
|
||||
* as the first argument */
|
||||
for (int i = valuelen - 1; i > 0; i--)
|
||||
{
|
||||
@ -1370,28 +1426,28 @@ static void callFunction(DCB *dcb, char *domain, char *id, char *v3,
|
||||
{
|
||||
if (!modulecmd_call_command(cmd, arg))
|
||||
{
|
||||
dcb_printf(dcb, "Failed to call function: %s\n", modulecmd_get_error());
|
||||
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
||||
}
|
||||
modulecmd_arg_free(arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Failed to parse arguments: %s\n", modulecmd_get_error());
|
||||
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dcb_printf(dcb, "Function not found: %s\n", modulecmd_get_error());
|
||||
dcb_printf(dcb, "Error: %s\n", modulecmd_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
struct subcommand calloptions[] =
|
||||
{
|
||||
{
|
||||
"function", 2, 12, callFunction,
|
||||
"Call module function",
|
||||
"Usage: call function NAMESPACE FUNCTION ARGS...\n"
|
||||
"To list all registered functions, run 'list functions'.\n",
|
||||
"command", 2, 12, callModuleCommand,
|
||||
"Call module command",
|
||||
"Usage: call command NAMESPACE COMMAND ARGS...\n"
|
||||
"To list all registered commands, run 'list commands'.\n",
|
||||
{ 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, ARG_TYPE_STRING}
|
||||
@ -1703,6 +1759,7 @@ execute_cmd(CLI_SESSION *cli)
|
||||
else
|
||||
{
|
||||
unsigned long arg_list[MAXARGS] = {};
|
||||
bool ok = true;
|
||||
|
||||
for (int k = 0; k < cmds[i].options[j].argc_max && k < argc; k++)
|
||||
{
|
||||
@ -1710,72 +1767,75 @@ execute_cmd(CLI_SESSION *cli)
|
||||
if (arg_list[k] == 0)
|
||||
{
|
||||
dcb_printf(dcb, "Invalid argument: %s\n", args[k + 2]);
|
||||
break;
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmds[i].options[j].argc_max)
|
||||
if (ok)
|
||||
{
|
||||
case 0:
|
||||
cmds[i].options[j].fn(dcb);
|
||||
break;
|
||||
case 1:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0]);
|
||||
break;
|
||||
case 2:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1]);
|
||||
break;
|
||||
case 3:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2]);
|
||||
break;
|
||||
case 4:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3]);
|
||||
break;
|
||||
case 5:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4]);
|
||||
break;
|
||||
case 6:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5]);
|
||||
break;
|
||||
case 7:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6]);
|
||||
break;
|
||||
case 8:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7]);
|
||||
break;
|
||||
case 9:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8]);
|
||||
break;
|
||||
case 10:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9]);
|
||||
break;
|
||||
case 11:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9], arg_list[10]);
|
||||
break;
|
||||
case 12:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9], arg_list[10], arg_list[11]);
|
||||
break;
|
||||
default:
|
||||
dcb_printf(dcb, "Error: Maximum argument count is %d.\n", MAXARGS);
|
||||
break;
|
||||
switch (cmds[i].options[j].argc_max)
|
||||
{
|
||||
case 0:
|
||||
cmds[i].options[j].fn(dcb);
|
||||
break;
|
||||
case 1:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0]);
|
||||
break;
|
||||
case 2:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1]);
|
||||
break;
|
||||
case 3:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2]);
|
||||
break;
|
||||
case 4:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3]);
|
||||
break;
|
||||
case 5:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4]);
|
||||
break;
|
||||
case 6:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5]);
|
||||
break;
|
||||
case 7:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6]);
|
||||
break;
|
||||
case 8:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7]);
|
||||
break;
|
||||
case 9:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8]);
|
||||
break;
|
||||
case 10:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9]);
|
||||
break;
|
||||
case 11:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9], arg_list[10]);
|
||||
break;
|
||||
case 12:
|
||||
cmds[i].options[j].fn(dcb, arg_list[0], arg_list[1], arg_list[2],
|
||||
arg_list[3], arg_list[4], arg_list[5],
|
||||
arg_list[6], arg_list[7], arg_list[8],
|
||||
arg_list[9], arg_list[10], arg_list[11]);
|
||||
break;
|
||||
default:
|
||||
dcb_printf(dcb, "Error: Maximum argument count is %d.\n", MAXARGS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user