diff --git a/Documentation/Authenticators/MySQL-Authenticator.md b/Documentation/Authenticators/MySQL-Authenticator.md index 82db5d52d..f2397d018 100644 --- a/Documentation/Authenticators/MySQL-Authenticator.md +++ b/Documentation/Authenticators/MySQL-Authenticator.md @@ -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 diff --git a/Documentation/Filters/Query-Log-All-Filter.md b/Documentation/Filters/Query-Log-All-Filter.md index db987c84a..fdff8d093 100644 --- a/Documentation/Filters/Query-Log-All-Filter.md +++ b/Documentation/Filters/Query-Log-All-Filter.md @@ -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. ``` diff --git a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md index 00db1d6cd..c539b49d7 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.0-Release-Notes.md @@ -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) diff --git a/include/maxscale/config.h b/include/maxscale/config.h index 6f02ec613..da440953a 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -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 diff --git a/include/maxscale/filter.h b/include/maxscale/filter.h index 27a1ef8f6..3b25b4483 100644 --- a/include/maxscale/filter.h +++ b/include/maxscale/filter.h @@ -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 *); /** diff --git a/include/maxscale/modulecmd.h b/include/maxscale/modulecmd.h index 10f772bb8..8d9a11aef 100644 --- a/include/maxscale/modulecmd.h +++ b/include/maxscale/modulecmd.h @@ -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 diff --git a/include/maxscale/monitor.h b/include/maxscale/monitor.h index 70dcd7b63..cbaf45917 100644 --- a/include/maxscale/monitor.h +++ b/include/maxscale/monitor.h @@ -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(); diff --git a/include/maxscale/service.h b/include/maxscale/service.h index fb0d29c78..37670a5ad 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -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); diff --git a/include/maxscale/session.h b/include/maxscale/session.h index 9dda114cf..3553cc0ab 100644 --- a/include/maxscale/session.h +++ b/include/maxscale/session.h @@ -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 diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index c1d336d3e..fa900b4ec 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -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 */ diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index a6f351c0d..910bd323c 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -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 diff --git a/query_classifier/test/expected.sql b/query_classifier/test/expected.sql index fd67bcc91..0aa0a9739 100644 --- a/query_classifier/test/expected.sql +++ b/query_classifier/test/expected.sql @@ -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 diff --git a/query_classifier/test/input.sql b/query_classifier/test/input.sql index 450bf81c8..bfa445297 100644 --- a/query_classifier/test/input.sql +++ b/query_classifier/test/input.sql @@ -12,3 +12,6 @@ BEGIN; ROLLBACK; COMMIT; use X; +select last_insert_id(); +select @@last_insert_id; +select @@identity; diff --git a/server/core/config.c b/server/core/config.c index 51e1a82ec..84434519b 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -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; } diff --git a/server/core/config_runtime.c b/server/core/config_runtime.c index a5f5dcb8f..bc02b1dac 100644 --- a/server/core/config_runtime.c +++ b/server/core/config_runtime.c @@ -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); diff --git a/server/core/dcb.c b/server/core/dcb.c index 8589315fb..8ac2fd1f7 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -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); diff --git a/server/core/filter.c b/server/core/filter.c index c3f8812b1..9536d4c2c 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -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; diff --git a/server/core/gateway.cc b/server/core/gateway.cc index f2808c328..1d9f34d44 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -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. */ diff --git a/server/core/maxscale/service.h b/server/core/maxscale/service.h index aa17a1e22..ada5c7c7c 100644 --- a/server/core/maxscale/service.h +++ b/server/core/maxscale/service.h @@ -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 diff --git a/server/core/modulecmd.c b/server/core/modulecmd.c index 018f7bb97..d3cdd38b3 100644 --- a/server/core/modulecmd.c +++ b/server/core/modulecmd.c @@ -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; diff --git a/server/core/monitor.c b/server/core/monitor.c index 9542b912b..e451b5cd3 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -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); diff --git a/server/core/service.c b/server/core/service.c index 0c7165e64..452b597ec 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -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; diff --git a/server/core/session.c b/server/core/session.c index 7648fe45e..54ab0fe81 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -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); + } + } +} diff --git a/server/core/test/testfilter.c b/server/core/test/testfilter.c index 377f5a7af..2fb88b68a 100644 --- a/server/core/test/testfilter.c +++ b/server/core/test/testfilter.c @@ -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; } diff --git a/server/core/test/testmodulecmd.c b/server/core/test/testmodulecmd.c index 6b6628e2f..5b341faa8 100644 --- a/server/core/test/testmodulecmd.c +++ b/server/core/test/testmodulecmd.c @@ -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"); diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index 22eac3876..c9aa88d9d 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -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; diff --git a/server/modules/authenticator/CDCPlainAuth/CMakeLists.txt b/server/modules/authenticator/CDCPlainAuth/CMakeLists.txt new file mode 100644 index 000000000..af8e187cf --- /dev/null +++ b/server/modules/authenticator/CDCPlainAuth/CMakeLists.txt @@ -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() diff --git a/server/modules/authenticator/cdc_plain_auth.c b/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c similarity index 100% rename from server/modules/authenticator/cdc_plain_auth.c rename to server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c diff --git a/server/modules/authenticator/CMakeLists.txt b/server/modules/authenticator/CMakeLists.txt index b8294d01a..f18e73a0d 100644 --- a/server/modules/authenticator/CMakeLists.txt +++ b/server/modules/authenticator/CMakeLists.txt @@ -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() diff --git a/server/modules/authenticator/GSSAPI/CMakeLists.txt b/server/modules/authenticator/GSSAPI/CMakeLists.txt new file mode 100644 index 000000000..01588c2f7 --- /dev/null +++ b/server/modules/authenticator/GSSAPI/CMakeLists.txt @@ -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() diff --git a/server/modules/authenticator/GSSAPI/GSSAPIAuth/CMakeLists.txt b/server/modules/authenticator/GSSAPI/GSSAPIAuth/CMakeLists.txt new file mode 100644 index 000000000..7dd3034a9 --- /dev/null +++ b/server/modules/authenticator/GSSAPI/GSSAPIAuth/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/gssapi_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c similarity index 99% rename from server/modules/authenticator/gssapi_auth.c rename to server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c index 3d1f8b095..5909282f4 100644 --- a/server/modules/authenticator/gssapi_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c @@ -19,7 +19,7 @@ #include #include #include -#include "gssapi_auth.h" +#include "../gssapi_auth.h" /** Default timeout is one minute */ #define MXS_SQLITE_BUSY_TIMEOUT 60000 diff --git a/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/CMakeLists.txt b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/CMakeLists.txt new file mode 100644 index 000000000..fc61e8e9d --- /dev/null +++ b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/gssapi_backend_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c similarity index 99% rename from server/modules/authenticator/gssapi_backend_auth.c rename to server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c index 44325881a..be4b27c33 100644 --- a/server/modules/authenticator/gssapi_backend_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c @@ -16,7 +16,7 @@ #include #include #include -#include "gssapi_auth.h" +#include "../gssapi_auth.h" /** * @file gssapi_backend_auth.c - GSSAPI backend authenticator diff --git a/server/modules/authenticator/gssapi_auth.h b/server/modules/authenticator/GSSAPI/gssapi_auth.h similarity index 100% rename from server/modules/authenticator/gssapi_auth.h rename to server/modules/authenticator/GSSAPI/gssapi_auth.h diff --git a/server/modules/authenticator/gssapi_auth_common.c b/server/modules/authenticator/GSSAPI/gssapi_auth_common.c similarity index 100% rename from server/modules/authenticator/gssapi_auth_common.c rename to server/modules/authenticator/GSSAPI/gssapi_auth_common.c diff --git a/server/modules/authenticator/HTTPAuth/CMakeLists.txt b/server/modules/authenticator/HTTPAuth/CMakeLists.txt new file mode 100644 index 000000000..31e72afac --- /dev/null +++ b/server/modules/authenticator/HTTPAuth/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/http_auth.c b/server/modules/authenticator/HTTPAuth/http_auth.c similarity index 100% rename from server/modules/authenticator/http_auth.c rename to server/modules/authenticator/HTTPAuth/http_auth.c diff --git a/server/modules/authenticator/MaxAdminAuth/CMakeLists.txt b/server/modules/authenticator/MaxAdminAuth/CMakeLists.txt new file mode 100644 index 000000000..7de926fb2 --- /dev/null +++ b/server/modules/authenticator/MaxAdminAuth/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/max_admin_auth.c b/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c similarity index 100% rename from server/modules/authenticator/max_admin_auth.c rename to server/modules/authenticator/MaxAdminAuth/max_admin_auth.c diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 17ade6b30..ee011fd7c 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -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 */ } diff --git a/server/modules/authenticator/MySQLBackendAuth/CMakeLists.txt b/server/modules/authenticator/MySQLBackendAuth/CMakeLists.txt new file mode 100644 index 000000000..65582f6ca --- /dev/null +++ b/server/modules/authenticator/MySQLBackendAuth/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/mysql_backend_auth.c b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c similarity index 100% rename from server/modules/authenticator/mysql_backend_auth.c rename to server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c diff --git a/server/modules/authenticator/NullAuthAllow/CMakeLists.txt b/server/modules/authenticator/NullAuthAllow/CMakeLists.txt new file mode 100644 index 000000000..2f11fb0f2 --- /dev/null +++ b/server/modules/authenticator/NullAuthAllow/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/null_auth_allow.c b/server/modules/authenticator/NullAuthAllow/null_auth_allow.c similarity index 100% rename from server/modules/authenticator/null_auth_allow.c rename to server/modules/authenticator/NullAuthAllow/null_auth_allow.c diff --git a/server/modules/authenticator/NullAuthDeny/CMakeLists.txt b/server/modules/authenticator/NullAuthDeny/CMakeLists.txt new file mode 100644 index 000000000..bedb90cee --- /dev/null +++ b/server/modules/authenticator/NullAuthDeny/CMakeLists.txt @@ -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) diff --git a/server/modules/authenticator/null_auth_deny.c b/server/modules/authenticator/NullAuthDeny/null_auth_deny.c similarity index 100% rename from server/modules/authenticator/null_auth_deny.c rename to server/modules/authenticator/NullAuthDeny/null_auth_deny.c diff --git a/server/modules/filter/cache/cachefilter.cc b/server/modules/filter/cache/cachefilter.cc index 984be83be..4fb85c1d5 100644 --- a/server/modules/filter/cache/cachefilter.cc +++ b/server/modules/filter/cache/cachefilter.cc @@ -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(pInstance); + + delete pFilter; +} + // // API Implementation END // diff --git a/server/modules/filter/cache/cachefilter.h b/server/modules/filter/cache/cachefilter.h index 2da6bcfaa..1accd1222 100644 --- a/server/modules/filter/cache/cachefilter.h +++ b/server/modules/filter/cache/cachefilter.h @@ -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 diff --git a/server/modules/filter/cache/storage/storage_inmemory/storage_inmemory.cc b/server/modules/filter/cache/storage/storage_inmemory/storage_inmemory.cc index b9cb1cd69..34a0a10cc 100644 --- a/server/modules/filter/cache/storage/storage_inmemory/storage_inmemory.cc +++ b/server/modules/filter/cache/storage/storage_inmemory/storage_inmemory.cc @@ -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 diff --git a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc index 8f68de876..792fa04f6 100644 --- a/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc +++ b/server/modules/filter/cache/storage/storage_rocksdb/storage_rocksdb.cc @@ -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 diff --git a/server/modules/filter/hintfilter/hintfilter.c b/server/modules/filter/hintfilter/hintfilter.c index fe35db089..9fb5017c0 100644 --- a/server/modules/filter/hintfilter/hintfilter.c +++ b/server/modules/filter/hintfilter/hintfilter.c @@ -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; } diff --git a/server/modules/filter/qlafilter/qlafilter.c b/server/modules/filter/qlafilter/qlafilter.c index 7997b6ed9..a3563fcc7 100644 --- a/server/modules/filter/qlafilter/qlafilter.c +++ b/server/modules/filter/qlafilter/qlafilter.c @@ -48,6 +48,7 @@ #include #include #include +#include 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; + } +} diff --git a/server/modules/filter/tee/tee.c b/server/modules/filter/tee/tee.c index ab91bfd3a..0e8707b77 100644 --- a/server/modules/filter/tee/tee.c +++ b/server/modules/filter/tee/tee.c @@ -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) { diff --git a/server/modules/routing/CMakeLists.txt b/server/modules/routing/CMakeLists.txt index 6fee3cb20..5f989da22 100644 --- a/server/modules/routing/CMakeLists.txt +++ b/server/modules/routing/CMakeLists.txt @@ -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) diff --git a/server/modules/routing/avro/CMakeLists.txt b/server/modules/routing/avrorouter/CMakeLists.txt similarity index 78% rename from server/modules/routing/avro/CMakeLists.txt rename to server/modules/routing/avrorouter/CMakeLists.txt index 0fed40b78..d852862bb 100644 --- a/server/modules/routing/avro/CMakeLists.txt +++ b/server/modules/routing/avrorouter/CMakeLists.txt @@ -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) diff --git a/server/modules/routing/avro/avro.c b/server/modules/routing/avrorouter/avro.c similarity index 100% rename from server/modules/routing/avro/avro.c rename to server/modules/routing/avrorouter/avro.c diff --git a/server/modules/routing/avro/avro_client.c b/server/modules/routing/avrorouter/avro_client.c similarity index 100% rename from server/modules/routing/avro/avro_client.c rename to server/modules/routing/avrorouter/avro_client.c diff --git a/server/modules/routing/avro/avro_file.c b/server/modules/routing/avrorouter/avro_file.c similarity index 100% rename from server/modules/routing/avro/avro_file.c rename to server/modules/routing/avrorouter/avro_file.c diff --git a/server/modules/routing/avro/avro_index.c b/server/modules/routing/avrorouter/avro_index.c similarity index 100% rename from server/modules/routing/avro/avro_index.c rename to server/modules/routing/avrorouter/avro_index.c diff --git a/server/modules/routing/avro/avro_rbr.c b/server/modules/routing/avrorouter/avro_rbr.c similarity index 100% rename from server/modules/routing/avro/avro_rbr.c rename to server/modules/routing/avrorouter/avro_rbr.c diff --git a/server/modules/routing/avro/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c similarity index 100% rename from server/modules/routing/avro/avro_schema.c rename to server/modules/routing/avrorouter/avro_schema.c diff --git a/server/modules/routing/avro/avrorouter.h b/server/modules/routing/avrorouter/avrorouter.h similarity index 100% rename from server/modules/routing/avro/avrorouter.h rename to server/modules/routing/avrorouter/avrorouter.h diff --git a/server/modules/routing/binlog/CMakeLists.txt b/server/modules/routing/binlogrouter/CMakeLists.txt similarity index 100% rename from server/modules/routing/binlog/CMakeLists.txt rename to server/modules/routing/binlogrouter/CMakeLists.txt diff --git a/server/modules/routing/binlog/README b/server/modules/routing/binlogrouter/README similarity index 100% rename from server/modules/routing/binlog/README rename to server/modules/routing/binlogrouter/README diff --git a/server/modules/routing/binlog/STATUS b/server/modules/routing/binlogrouter/STATUS similarity index 100% rename from server/modules/routing/binlog/STATUS rename to server/modules/routing/binlogrouter/STATUS diff --git a/server/modules/routing/binlog/binlog_common.c b/server/modules/routing/binlogrouter/binlog_common.c similarity index 100% rename from server/modules/routing/binlog/binlog_common.c rename to server/modules/routing/binlogrouter/binlog_common.c diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlogrouter/blr.c similarity index 99% rename from server/modules/routing/binlog/blr.c rename to server/modules/routing/binlogrouter/blr.c index 3d8a3a8aa..9908b2701 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlogrouter/blr.c @@ -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, diff --git a/server/modules/routing/binlog/blr.h b/server/modules/routing/binlogrouter/blr.h similarity index 97% rename from server/modules/routing/binlog/blr.h rename to server/modules/routing/binlogrouter/blr.h index 01b5bdf65..4f7da22b8 100644 --- a/server/modules/routing/binlog/blr.h +++ b/server/modules/routing/binlogrouter/blr.h @@ -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 */ diff --git a/server/modules/routing/binlog/blr_cache.c b/server/modules/routing/binlogrouter/blr_cache.c similarity index 100% rename from server/modules/routing/binlog/blr_cache.c rename to server/modules/routing/binlogrouter/blr_cache.c diff --git a/server/modules/routing/binlog/blr_file.c b/server/modules/routing/binlogrouter/blr_file.c similarity index 100% rename from server/modules/routing/binlog/blr_file.c rename to server/modules/routing/binlogrouter/blr_file.c diff --git a/server/modules/routing/binlog/blr_master.c b/server/modules/routing/binlogrouter/blr_master.c similarity index 83% rename from server/modules/routing/binlog/blr_master.c rename to server/modules/routing/binlogrouter/blr_master.c index d9ed3d500..96c382f0c 100644 --- a/server/modules/routing/binlog/blr_master.c +++ b/server/modules/routing/binlogrouter/blr_master.c @@ -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 * diff --git a/server/modules/routing/binlog/blr_slave.c b/server/modules/routing/binlogrouter/blr_slave.c similarity index 99% rename from server/modules/routing/binlog/blr_slave.c rename to server/modules/routing/binlogrouter/blr_slave.c index e294ebc16..bb535b4bd 100644 --- a/server/modules/routing/binlog/blr_slave.c +++ b/server/modules/routing/binlogrouter/blr_slave.c @@ -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; diff --git a/server/modules/routing/binlog/maxbinlogcheck.c b/server/modules/routing/binlogrouter/maxbinlogcheck.c similarity index 100% rename from server/modules/routing/binlog/maxbinlogcheck.c rename to server/modules/routing/binlogrouter/maxbinlogcheck.c diff --git a/server/modules/routing/binlog/test/CMakeLists.txt b/server/modules/routing/binlogrouter/test/CMakeLists.txt similarity index 100% rename from server/modules/routing/binlog/test/CMakeLists.txt rename to server/modules/routing/binlogrouter/test/CMakeLists.txt diff --git a/server/modules/routing/binlog/test/testbinlog.c b/server/modules/routing/binlogrouter/test/testbinlog.c similarity index 100% rename from server/modules/routing/binlog/test/testbinlog.c rename to server/modules/routing/binlogrouter/test/testbinlog.c diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index cad322c67..215658151 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -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; + } } } }