/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2022-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ /** * @file maxinfo_parse.c - Parse the limited set of SQL that the MaxScale * information schema can use * * @verbatim * Revision History * * Date Who Description * 17/02/15 Mark Riddoch Initial implementation * * @endverbatim */ #include "maxinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../../core/internal/maxscale.h" #include "../../../core/internal/modules.hh" #include "../../../core/internal/monitor.h" #include "../../../core/internal/monitor.hh" #include "../../../core/internal/poll.hh" #include "../../../core/internal/session.hh" #include "../../../core/internal/server.hh" #include "../../../core/internal/service.hh" static void exec_show(DCB *dcb, MAXINFO_TREE *tree); static void exec_select(DCB *dcb, MAXINFO_TREE *tree); static void exec_show_variables(DCB *dcb, MAXINFO_TREE *filter); static void exec_show_status(DCB *dcb, MAXINFO_TREE *filter); static int maxinfo_pattern_match(const char *pattern, const char *str); static void exec_flush(DCB *dcb, MAXINFO_TREE *tree); static void exec_set(DCB *dcb, MAXINFO_TREE *tree); static void exec_clear(DCB *dcb, MAXINFO_TREE *tree); static void exec_shutdown(DCB *dcb, MAXINFO_TREE *tree); static void exec_restart(DCB *dcb, MAXINFO_TREE *tree); void maxinfo_send_ok(DCB *dcb); /** * Execute a parse tree and return the result set or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ void maxinfo_execute(DCB *dcb, MAXINFO_TREE *tree) { switch (tree->op) { case MAXOP_SHOW: exec_show(dcb, tree); break; case MAXOP_SELECT: exec_select(dcb, tree); break; case MAXOP_FLUSH: exec_flush(dcb, tree); break; case MAXOP_SET: exec_set(dcb, tree); break; case MAXOP_CLEAR: exec_clear(dcb, tree); break; case MAXOP_SHUTDOWN: exec_shutdown(dcb, tree); break; case MAXOP_RESTART: exec_restart(dcb, tree); break; case MAXOP_TABLE: case MAXOP_COLUMNS: case MAXOP_LITERAL: case MAXOP_PREDICATE: case MAXOP_LIKE: case MAXOP_EQUAL: default: maxinfo_send_error(dcb, 0, "Unexpected operator in parse tree"); } } /** * Fetch the list of services and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_services(DCB *dcb, MAXINFO_TREE *tree) { serviceGetList()->write(dcb); } /** * Fetch the list of listeners and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_listeners(DCB *dcb, MAXINFO_TREE *tree) { serviceGetListenerList()->write(dcb); } /** * Fetch the list of sessions and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_sessions(DCB *dcb, MAXINFO_TREE *tree) { sessionGetList()->write(dcb); } /** * Fetch the list of client sessions and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_clients(DCB *dcb, MAXINFO_TREE *tree) { sessionGetList()->write(dcb); } /** * Fetch the list of servers and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_servers(DCB *dcb, MAXINFO_TREE *tree) { serverGetList()->write(dcb); } /** * Fetch the list of modules and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_modules(DCB *dcb, MAXINFO_TREE *tree) { moduleGetList()->write(dcb); } /** * Fetch the list of monitors and stream as a result set * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_monitors(DCB *dcb, MAXINFO_TREE *tree) { monitor_get_list()->write(dcb); } /** * Fetch the event times data * * @param dcb DCB to which to stream result set * @param tree Potential like clause (currently unused) */ static void exec_show_eventTimes(DCB *dcb, MAXINFO_TREE *tree) { eventTimesGetList()->write(dcb); } /** * The table of show commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } show_commands[] = { { "variables", exec_show_variables }, { "status", exec_show_status }, { "services", exec_show_services }, { "listeners", exec_show_listeners }, { "sessions", exec_show_sessions }, { "clients", exec_show_clients }, { "servers", exec_show_servers }, { "modules", exec_show_modules }, { "monitors", exec_show_monitors }, { "eventTimes", exec_show_eventTimes }, { NULL, NULL } }; /** * Execute a show command parse tree and return the result set or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_show(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; for (i = 0; show_commands[i].name; i++) { if (strcasecmp(show_commands[i].name, tree->value) == 0) { (*show_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Unsupported show command '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); MXS_NOTICE("%s", errmsg); } /** * Flush all logs to disk and rotate them. * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ void exec_flush_logs(DCB *dcb, MAXINFO_TREE *tree) { mxs_log_rotate(); maxinfo_send_ok(dcb); } /** * The table of flush commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } flush_commands[] = { { "logs", exec_flush_logs}, { NULL, NULL} }; /** * Execute a flush command parse tree and return the result set or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_flush(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; sprintf(errmsg, "Unsupported flush command '%s'", tree->value); if(!tree) { maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); return; } for (i = 0; flush_commands[i].name; i++) { if (strcasecmp(flush_commands[i].name, tree->value) == 0) { (*flush_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); } /** * Set the server status. * @param dcb Client DCB * @param tree Parse tree */ void exec_set_server(DCB *dcb, MAXINFO_TREE *tree) { SERVER* server = server_find_by_unique_name(tree->value); char errmsg[120]; if (server) { int status = server_map_status(tree->right->value); if (status != 0) { std::string errmsgs; if (mxs::server_set_status(server, status, &errmsgs)) { maxinfo_send_ok(dcb); } else { maxinfo_send_error(dcb, 0, errmsgs.c_str()); } } else { if (strlen(tree->right->value) > 80) // Prevent buffer overrun { tree->right->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->right->value); maxinfo_send_error(dcb, 0, errmsg); } } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } /** * The table of set commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } set_commands[] = { { "server", exec_set_server}, { NULL, NULL} }; /** * Execute a set command parse tree and return the result set or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_set(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; for (i = 0; set_commands[i].name; i++) { if (strcasecmp(set_commands[i].name, tree->value) == 0) { (*set_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Unsupported set command '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); } /** * Clear the server status. * @param dcb Client DCB * @param tree Parse tree */ void exec_clear_server(DCB *dcb, MAXINFO_TREE *tree) { SERVER* server = server_find_by_unique_name(tree->value); char errmsg[120]; if (server) { int status = server_map_status(tree->right->value); if (status != 0) { std::string errmsgs; if (mxs::server_clear_status(server, status, &errmsgs)) { maxinfo_send_ok(dcb); } else { maxinfo_send_error(dcb, 0, errmsgs.c_str()); } } else { if (strlen(tree->right->value) > 80) // Prevent buffer overrun { tree->right->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->right->value); maxinfo_send_error(dcb, 0, errmsg); } } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } /** * The table of clear commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } clear_commands[] = { { "server", exec_clear_server}, { NULL, NULL} }; /** * Execute a clear command parse tree and return the result set or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_clear(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; for (i = 0; clear_commands[i].name; i++) { if (strcasecmp(clear_commands[i].name, tree->value) == 0) { (*clear_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Unsupported clear command '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); } /** * MaxScale shutdown * @param dcb Client DCB * @param tree Parse tree */ void exec_shutdown_maxscale(DCB *dcb, MAXINFO_TREE *tree) { maxscale_shutdown(); maxinfo_send_ok(dcb); } /** * Stop a monitor * @param dcb Client DCB * @param tree Parse tree */ void exec_shutdown_monitor(DCB *dcb, MAXINFO_TREE *tree) { char errmsg[120]; if (tree && tree->value) { MXS_MONITOR* monitor = monitor_find(tree->value); if (monitor) { monitor_stop(monitor); maxinfo_send_ok(dcb); } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } else { sprintf(errmsg, "Missing argument for 'SHUTDOWN MONITOR'"); maxinfo_send_error(dcb, 0, errmsg); } } /** * Stop a service * @param dcb Client DCB * @param tree Parse tree */ void exec_shutdown_service(DCB *dcb, MAXINFO_TREE *tree) { char errmsg[120]; if (tree && tree->value) { SERVICE* service = service_find(tree->value); if (service) { serviceStop(service); maxinfo_send_ok(dcb); } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } else { sprintf(errmsg, "Missing argument for 'SHUTDOWN SERVICE'"); maxinfo_send_error(dcb, 0, errmsg); } } /** * The table of shutdown commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } shutdown_commands[] = { { "maxscale", exec_shutdown_maxscale}, { "monitor", exec_shutdown_monitor}, { "service", exec_shutdown_service}, { NULL, NULL} }; /** * Execute a shutdown command parse tree and return OK or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_shutdown(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; for (i = 0; shutdown_commands[i].name; i++) { if (strcasecmp(shutdown_commands[i].name, tree->value) == 0) { (*shutdown_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Unsupported shutdown command '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); } /** * Restart a monitor * @param dcb Client DCB * @param tree Parse tree */ void exec_restart_monitor(DCB *dcb, MAXINFO_TREE *tree) { char errmsg[120]; if (tree && tree->value) { MXS_MONITOR* monitor = monitor_find(tree->value); if (monitor) { monitor_start(monitor, monitor->parameters); maxinfo_send_ok(dcb); } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } else { sprintf(errmsg, "Missing argument for 'RESTART MONITOR'"); maxinfo_send_error(dcb, 0, errmsg); } } /** * Restart a service * @param dcb Client DCB * @param tree Parse tree */ void exec_restart_service(DCB *dcb, MAXINFO_TREE *tree) { char errmsg[120]; if (tree && tree->value) { SERVICE* service = service_find(tree->value); if (service) { serviceStart(service); maxinfo_send_ok(dcb); } else { if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Invalid argument '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); } } else { sprintf(errmsg, "Missing argument for 'RESTART SERVICE'"); maxinfo_send_error(dcb, 0, errmsg); } } /** * The table of restart commands that are supported */ static struct { const char *name; void (*func)(DCB *, MAXINFO_TREE *); } restart_commands[] = { { "monitor", exec_restart_monitor}, { "service", exec_restart_service}, { NULL, NULL} }; /** * Execute a restart command parse tree and return OK or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_restart(DCB *dcb, MAXINFO_TREE *tree) { int i; char errmsg[120]; for (i = 0; restart_commands[i].name; i++) { if (strcasecmp(restart_commands[i].name, tree->value) == 0) { (*restart_commands[i].func)(dcb, tree->right); return; } } if (strlen(tree->value) > 80) // Prevent buffer overrun { tree->value[80] = 0; } sprintf(errmsg, "Unsupported restart command '%s'", tree->value); maxinfo_send_error(dcb, 0, errmsg); MXS_ERROR("%s", errmsg); } /** * Return the current MaxScale version * * @return The version string for MaxScale */ static char * getVersion() { return const_cast(MAXSCALE_VERSION); } static const char *versionComment = "MariaDB MaxScale"; /** * Return the current MaxScale version * * @return The version string for MaxScale */ static char * getVersionComment() { return const_cast(versionComment); } /** * Return the current MaxScale Home Directory * * @return The version string for MaxScale */ static char * getMaxScaleHome() { return getenv("MAXSCALE_HOME"); } /* The various methods to fetch the variables */ #define VT_STRING 1 #define VT_INT 2 typedef void *(*STATSFUNC)(); /** * Variables that may be sent in a show variables */ static struct { const char *name; int type; STATSFUNC func; } variables[] = { { "version", VT_STRING, (STATSFUNC)getVersion }, { "version_comment", VT_STRING, (STATSFUNC)getVersionComment }, { "basedir", VT_STRING, (STATSFUNC)getMaxScaleHome}, { "MAXSCALE_VERSION", VT_STRING, (STATSFUNC)getVersion }, { "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount }, { "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls }, { "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep }, { "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)maxscale_uptime }, { "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll }, { NULL, 0, NULL } }; std::string value_to_string(int type, STATSFUNC func) { std::string value; if (type == VT_STRING) { if (char* str = (char*)func()) { value = str; } } else { value = std::to_string((int64_t)func()); } return value; } /** * Callback function to populate rows of the show variable * command * * @param data The context point * @return The next row or NULL if end of rows */ static void variable_row(std::unique_ptr& set, const char* like) { for (int i = 0; variables[i].name; i++) { if (!like || !maxinfo_pattern_match(like, variables[i].name)) { set->add_row({variables[i].name, value_to_string(variables[i].type, variables[i].func)}); } } } /** * Execute a show variables command applying an optional filter * * @param dcb The DCB connected to the client * @param filter A potential like clause or NULL */ static void exec_show_variables(DCB *dcb, MAXINFO_TREE *filter) { std::unique_ptr set = ResultSet::create({"Variable_name", "Value"}); variable_row(set, filter ? filter->value : NULL); set->write(dcb); } /** * Return the show variables output a a result set * * @return Variables as a result set */ std::unique_ptr maxinfo_variables() { std::unique_ptr set = ResultSet::create({"Variable_name", "Value"}); variable_row(set, NULL); return set; } /** * Interface to dcb_count_by_usage for all dcbs */ static int maxinfo_all_dcbs() { return dcb_count_by_usage(DCB_USAGE_ALL); } /** * Interface to dcb_count_by_usage for client dcbs */ static int maxinfo_client_dcbs() { return dcb_count_by_usage(DCB_USAGE_CLIENT); } /** * Interface to dcb_count_by_usage for listener dcbs */ static int maxinfo_listener_dcbs() { return dcb_count_by_usage(DCB_USAGE_LISTENER); } /** * Interface to dcb_count_by_usage for backend dcbs */ static int maxinfo_backend_dcbs() { return dcb_count_by_usage(DCB_USAGE_BACKEND); } /** * Interface to dcb_count_by_usage for internal dcbs */ static int maxinfo_internal_dcbs() { return dcb_count_by_usage(DCB_USAGE_INTERNAL); } /** * Interface to poll stats for reads */ static int64_t maxinfo_read_events() { return poll_get_stat(POLL_STAT_READ); } /** * Interface to poll stats for writes */ static int64_t maxinfo_write_events() { return poll_get_stat(POLL_STAT_WRITE); } /** * Interface to poll stats for errors */ static int64_t maxinfo_error_events() { return poll_get_stat(POLL_STAT_ERROR); } /** * Interface to poll stats for hangup */ static int64_t maxinfo_hangup_events() { return poll_get_stat(POLL_STAT_HANGUP); } /** * Interface to poll stats for accepts */ static int64_t maxinfo_accept_events() { return poll_get_stat(POLL_STAT_ACCEPT); } /** * Interface to poll stats for event queue length */ static int64_t maxinfo_avg_event_queue_length() { return poll_get_stat(POLL_STAT_EVQ_AVG); } /** * Interface to poll stats for max event queue length */ static int64_t maxinfo_max_event_queue_length() { return poll_get_stat(POLL_STAT_EVQ_MAX); } /** * Interface to poll stats for max queue time */ static int64_t maxinfo_max_event_queue_time() { return poll_get_stat(POLL_STAT_MAX_QTIME); } /** * Interface to poll stats for max event execution time */ static int64_t maxinfo_max_event_exec_time() { return poll_get_stat(POLL_STAT_MAX_EXECTIME); } /** * Variables that may be sent in a show status */ static struct { const char *name; int type; STATSFUNC func; } status[] = { { "Uptime", VT_INT, (STATSFUNC)maxscale_uptime }, { "Uptime_since_flush_status", VT_INT, (STATSFUNC)maxscale_uptime }, { "Threads_created", VT_INT, (STATSFUNC)config_threadcount }, { "Threads_running", VT_INT, (STATSFUNC)config_threadcount }, { "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount }, { "Threads_connected", VT_INT, (STATSFUNC)serviceSessionCountAll }, { "Connections", VT_INT, (STATSFUNC)maxinfo_all_dcbs }, { "Client_connections", VT_INT, (STATSFUNC)maxinfo_client_dcbs }, { "Backend_connections", VT_INT, (STATSFUNC)maxinfo_backend_dcbs }, { "Listeners", VT_INT, (STATSFUNC)maxinfo_listener_dcbs }, { "Internal_descriptors", VT_INT, (STATSFUNC)maxinfo_internal_dcbs }, { "Read_events", VT_INT, (STATSFUNC)maxinfo_read_events }, { "Write_events", VT_INT, (STATSFUNC)maxinfo_write_events }, { "Hangup_events", VT_INT, (STATSFUNC)maxinfo_hangup_events }, { "Error_events", VT_INT, (STATSFUNC)maxinfo_error_events }, { "Accept_events", VT_INT, (STATSFUNC)maxinfo_accept_events }, { "Avg_event_queue_length", VT_INT, (STATSFUNC)maxinfo_avg_event_queue_length }, { "Max_event_queue_length", VT_INT, (STATSFUNC)maxinfo_max_event_queue_length }, { "Max_event_queue_time", VT_INT, (STATSFUNC)maxinfo_max_event_queue_time }, { "Max_event_execution_time", VT_INT, (STATSFUNC)maxinfo_max_event_exec_time }, { NULL, 0, NULL } }; /** * Callback function to populate rows of the show variable * command * * @param data The context point * @return The next row or NULL if end of rows */ static void status_row(std::unique_ptr& set, const char* like) { for (int i = 0; status[i].name; i++) { if (!like || !maxinfo_pattern_match(like, status[i].name)) { set->add_row({status[i].name, value_to_string(status[i].type, status[i].func)}); } } } /** * Execute a show status command applying an optional filter * * @param dcb The DCB connected to the client * @param filter A potential like clause or NULL */ static void exec_show_status(DCB *dcb, MAXINFO_TREE *filter) { std::unique_ptr set = ResultSet::create({"Variable_name", "Value"}); status_row(set, filter ? filter->value : NULL); set->write(dcb); } /** * Return the show status data as a result set * * @return The show status data as a result set */ std::unique_ptr maxinfo_status() { std::unique_ptr set = ResultSet::create({"Variable_name", "Value"}); status_row(set, NULL); return set; } /** * Execute a select command parse tree and return the result set * or runtime error * * @param dcb The DCB that connects to the client * @param tree The parse tree for the query */ static void exec_select(DCB *dcb, MAXINFO_TREE *tree) { maxinfo_send_error(dcb, 0, "Select not yet implemented"); } /** * Perform a "like" pattern match. Only works for leading and trailing % * * @param pattern Pattern to match * @param str String to match against pattern * @return Zero on match */ static int maxinfo_pattern_match(const char *pattern, const char *str) { int anchor = 0, len, trailing; const char *fixed; if (*pattern != '%') { fixed = pattern; anchor = 1; } else { fixed = &pattern[1]; } len = strlen(fixed); if (fixed[len - 1] == '%') { trailing = 1; } else { trailing = 0; } if (anchor == 1 && trailing == 0) // No wildcard { return strcasecmp(pattern, str); } else if (anchor == 1) { return strncasecmp(str, pattern, len - trailing); } else { char *portion = static_cast(MXS_MALLOC(len + 1)); MXS_ABORT_IF_NULL(portion); int rval; strncpy(portion, fixed, len - trailing); portion[len - trailing] = 0; rval = (strcasestr(str, portion) != NULL ? 0 : 1); MXS_FREE(portion); return rval; } } /** * Send an OK packet to the client. * @param dcb The DCB that connects to the client */ void maxinfo_send_ok(DCB *dcb) { static const char ok_packet[] = { 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; GWBUF* buffer = gwbuf_alloc(sizeof(ok_packet)); if (buffer) { memcpy(buffer->start, ok_packet, sizeof(ok_packet)); dcb->func.write(dcb, buffer); } }