Files
MaxScale/server/modules/routing/binlogrouter/blr_slave.c
Markus Mäkelä 96b98845a2 Allow filters to be used with binlogrouter
Filters can now be used with the binlogrouter to modify the contents of
the binlog stream now that the correct function call is used.
2017-07-07 10:37:30 +03:00

8473 lines
263 KiB
C

/*
* 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: 2020-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 blr_slave.c - contains code for the router to slave communication
*
* The binlog router is designed to be used in replication environments to
* increase the replication fanout of a master server. It provides a transparant
* mechanism to read the binlog entries for multiple slaves while requiring
* only a single connection to the actual master to support the slaves.
*
* The current prototype implement is designed to support MySQL 5.6 and has
* a number of limitations. This prototype is merely a proof of concept and
* should not be considered production ready.
*
* @verbatim
* Revision History
*
* Date Who Description
* 14/04/2014 Mark Riddoch Initial implementation
* 18/02/2015 Massimiliano Pinto Addition of DISCONNECT ALL and DISCONNECT SERVER server_id
* 18/03/2015 Markus Makela Better detection of CRC32 | NONE checksum
* 19/03/2015 Massimiliano Pinto Addition of basic MariaDB 10 compatibility support
* 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility
* 11/05/2015 Massimiliano Pinto Only MariaDB 10 Slaves can register to binlog router
* with a MariaDB 10 Master
* 25/05/2015 Massimiliano Pinto Addition of BLRM_SLAVE_STOPPED state and blr_start/stop_slave.
* New commands STOP SLAVE, START SLAVE added.
* 29/05/2015 Massimiliano Pinto Addition of CHANGE MASTER TO ...
* 05/06/2015 Massimiliano Pinto router->service->dbref->sever->name instead of master->remote
* in blr_slave_send_slave_status()
* 08/06/2015 Massimiliano Pinto blr_slave_send_slave_status() shows mysql_errno and error_msg
* 15/06/2015 Massimiliano Pinto Added constraints to CHANGE MASTER TO MASTER_LOG_FILE/POS
* 23/06/2015 Massimiliano Pinto Added utility routines for blr_handle_change_master
* Call create/use binlog in blr_start_slave() (START SLAVE)
* 29/06/2015 Massimiliano Pinto Successfully CHANGE MASTER results in updating master.ini
* in blr_handle_change_master()
* 20/08/2015 Massimiliano Pinto Added parsing and validation for CHANGE MASTER TO
* 21/08/2015 Massimiliano Pinto Added support for new config options:
* master_uuid, master_hostname, master_version
* If set those values are sent to slaves instead of
* saved master responses
* 03/09/2015 Massimiliano Pinto Added support for SHOW [GLOBAL] VARIABLES LIKE
* 04/09/2015 Massimiliano Pinto Added support for SHOW WARNINGS
* 15/09/2015 Massimiliano Pinto Added support for SHOW [GLOBAL] STATUS LIKE 'Uptime'
* 25/09/2015 Massimiliano Pinto Addition of slave heartbeat:
* the period set during registration is checked
* and heartbeat event might be sent to the affected slave.
* 25/09/2015 Martin Brampton Block callback processing when no router session in the DCB
* 23/10/2015 Markus Makela Added current_safe_event
* 09/05/2016 Massimiliano Pinto Added SELECT USER()
* 11/07/2016 Massimiliano Pinto Added SSL backend support
* 24/08/2016 Massimiliano Pinto Added slave notification via CS_WAIT_DATA
* 16/09/2016 Massimiliano Pinto Special events created by MaxScale are not sent to slaves:
* MARIADB10_START_ENCRYPTION_EVENT or IGNORABLE_EVENT
* Events with LOG_EVENT_IGNORABLE_F are skipped as well.
*
* @endverbatim
*/
#include "blr.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <maxscale/maxscale.h>
#include <maxscale/service.h>
#include <maxscale/server.h>
#include <maxscale/router.h>
#include <maxscale/atomic.h>
#include <maxscale/spinlock.h>
#include <maxscale/dcb.h>
#include <maxscale/spinlock.h>
#include <maxscale/housekeeper.h>
#include <sys/stat.h>
#include <maxscale/log_manager.h>
#include <maxscale/version.h>
#include <zlib.h>
#include <maxscale/alloc.h>
#include <inttypes.h>
/**
* This struct is used by sqlite3_exec callback routine
* for SHOW BINARY LOGS.
*
* It stores the next row sequence number,
* the last binlog file name read from gtid_maps storage
* and the connected client DCB.
*/
typedef struct
{
int seq_no; /* Output sequence in result test */
char *last_file; /* Last binlog file found in GTID repo */
const char *binlogdir; /* Binlog files cache dir */
bool extra_info; /* Add extra ouput info */
DCB *client; /* Connected client DCB */
} BINARY_LOG_DATA_RESULT;
extern void poll_fake_write_event(DCB *dcb);
static char* get_next_token(char *str, const char* delim, char **saveptr);
extern int load_mysql_users(SERV_LISTENER *listener);
extern void blr_master_close(ROUTER_INSTANCE* router);
extern int blr_file_new_binlog(ROUTER_INSTANCE *router, char *file);
extern int blr_file_write_master_config(ROUTER_INSTANCE *router, char *error);
extern char *blr_extract_column(GWBUF *buf, int col);
extern uint32_t extract_field(uint8_t *src, int bits);
void blr_extract_header(register uint8_t *ptr, register REP_HEADER *hdr);
int blr_file_get_next_binlogname(ROUTER_INSTANCE *router);
static void encode_value(unsigned char *data, unsigned int value, int len);
static int blr_slave_query(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
GWBUF *queue);
static int blr_slave_replay(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
GWBUF *master);
static void blr_slave_send_error(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *msg);
static int blr_slave_send_timestamp(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_register(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
GWBUF *queue);
static int blr_slave_binlog_dump(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
GWBUF *queue);
int blr_slave_catchup(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
bool large);
uint8_t *blr_build_header(GWBUF *pkt, REP_HEADER *hdr);
int blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data);
static int blr_slave_fake_rotate(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
BLFILE** filep,
const char *new_file);
static uint32_t blr_slave_send_fde(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
GWBUF *fde);
static int blr_slave_send_maxscale_version(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_send_server_id(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_send_maxscale_variables(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_send_master_status(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_send_slave_status(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
bool all_slaves);
static int blr_slave_send_slave_hosts(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_send_fieldcount(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int count);
static int blr_slave_send_columndef(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *name,
int type,
int len,
uint8_t seqno);
static int blr_slave_send_eof(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int seqno);
static int blr_slave_send_disconnected_server(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int server_id,
int found);
static int blr_slave_disconnect_all(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_slave_disconnect_server(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int server_id);
static int blr_slave_send_ok(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave);
static int blr_stop_slave(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave);
static int blr_start_slave(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave);
static void blr_slave_send_error_packet(ROUTER_SLAVE *slave,
char *msg,
unsigned int err_num,
char *status);
static int blr_handle_change_master(ROUTER_INSTANCE* router,
char *command,
char *error);
static int blr_set_master_hostname(ROUTER_INSTANCE *router,
char *hostname);
static int blr_set_master_port(ROUTER_INSTANCE *router,
char *command);
static char *blr_set_master_logfile(ROUTER_INSTANCE *router,
char *filename,
char *error);
static void blr_master_get_config(ROUTER_INSTANCE *router,
MASTER_SERVER_CFG *current_master);
static void blr_master_free_config(MASTER_SERVER_CFG *current_master);
static void blr_master_restore_config(ROUTER_INSTANCE *router,
MASTER_SERVER_CFG *current_master);
static void blr_master_set_empty_config(ROUTER_INSTANCE *router);
static void blr_master_apply_config(ROUTER_INSTANCE *router,
MASTER_SERVER_CFG *prev_master);
static int blr_slave_send_ok_message(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave,
char *message);
static char *blr_get_parsed_command_value(char *input);
static char **blr_validate_change_master_option(char *option,
CHANGE_MASTER_OPTIONS *config);
static int blr_set_master_user(ROUTER_INSTANCE *router, char *user);
static int blr_set_master_password(ROUTER_INSTANCE *router, char *password);
static int blr_parse_change_master_command(char *input,
char *error_string,
CHANGE_MASTER_OPTIONS *config);
static int blr_handle_change_master_token(char *input,
char *error,
CHANGE_MASTER_OPTIONS *config);
static void blr_master_free_parsed_options(CHANGE_MASTER_OPTIONS *options);
static int blr_slave_send_var_value(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type);
static int blr_slave_send_variable(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type);
static int blr_slave_send_columndef_with_info_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno);
int blr_test_parse_change_master_command(char *input,
char *error_string,
CHANGE_MASTER_OPTIONS *config);
char *blr_test_set_master_logfile(ROUTER_INSTANCE *router,
char *filename,
char *error);
static int blr_slave_handle_variables(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *stmt);
static int blr_slave_send_warning_message(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave,
char *message);
static int blr_slave_show_warnings(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave);
static int blr_slave_send_status_variable(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type);
static int blr_slave_handle_status_variables(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *stmt);
static int blr_slave_send_columndef_with_status_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno);
static void blr_send_slave_heartbeat(void *inst);
static int blr_slave_send_heartbeat(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static int blr_set_master_ssl(ROUTER_INSTANCE *router,
CHANGE_MASTER_OPTIONS config,
char *error_message);
static int blr_slave_read_ste(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
uint32_t fde_end_pos);
static GWBUF *blr_slave_read_fde(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static bool blr_handle_simple_select_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *select_stmt);
static GWBUF *blr_build_fake_rotate_event(ROUTER_SLAVE *slave,
unsigned long pos,
const char *filename,
unsigned long serverid);
static int blr_send_connect_fake_rotate(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static bool blr_slave_gtid_request(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
bool req_file,
unsigned long req_pos);
static int blr_send_fake_gtid_list(ROUTER_SLAVE *slave,
const char *gtid,
uint32_t serverid);
static bool blr_handle_maxwell_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *maxwell_stmt);
static bool blr_handle_show_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *show_stmt);
static bool blr_handle_set_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *set_stmt);
static bool blr_handle_admin_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *admin_stmt,
char *admin_options);
extern unsigned int blr_file_get_next_seqno(const char *filename);
extern uint32_t blr_slave_get_file_size(const char *filename);
static void blr_slave_skip_empty_files(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static inline void blr_get_file_fullpath(const char *binlog_file,
const char *root_dir,
char *full_path,
const char *f_prefix);
static int blr_show_binary_logs(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *extra_data);
extern bool blr_parse_gtid(const char *gtid, MARIADB_GTID_ELEMS *info);
static int binary_logs_select_cb(void *data,
int cols,
char** values,
char** names);
static GWBUF *blr_create_result_row(const char *name,
const char *value,
int seq_no);
static int blr_slave_send_id_ro(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
static bool blr_handle_complex_select(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *col1,
const char *coln);
extern bool blr_is_current_binlog(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave);
extern bool blr_compare_binlogs(ROUTER_INSTANCE *router,
MARIADB_GTID_INFO *slave,
const char *r_file,
const char *s_file);
/**
* Process a request packet from the slave server.
*
* The router can handle a limited subset of requests from the slave, these
* include a subset of general SQL queries, a slave registeration command and
* the binlog dump command.
*
* The strategy for responding to these commands is to use caches responses
* for the the same commands that have previously been made to the real master
* if this is possible, if it is not then the router itself will synthesize a
* response.
*
* @param router The router instance: the master for this replication chain
* @param slave The slave specific data
* @param queue The incoming request packet
*/
int
blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
int rv = 0;
if (slave->state < 0 || slave->state > BLRS_MAXSTATE)
{
MXS_ERROR("Invalid slave state machine state (%d) for binlog router.",
slave->state);
gwbuf_consume(queue, gwbuf_length(queue));
return 0;
}
slave->stats.n_requests++;
switch (MYSQL_COMMAND(queue))
{
case COM_QUERY:
slave->stats.n_queries++;
rv = blr_slave_query(router, slave, queue);
break;
case COM_REGISTER_SLAVE:
if (router->master_state == BLRM_UNCONFIGURED)
{
char *err_msg = "Binlog router is not yet configured"
" for replication.";
slave->state = BLRS_ERRORED;
blr_slave_send_error_packet(slave,
err_msg,
1597,
NULL);
MXS_ERROR("%s: Slave %s: %s",
router->service->name,
slave->dcb->remote,
err_msg);
dcb_close(slave->dcb);
rv = 1;
}
else if (router->mariadb10_compat && !slave->mariadb10_compat)
{
char *err_msg = "MariaDB 10 Slave is required"
" for Slave registration.";
/**
* If Master is MariaDB10 don't allow registration from
* MariaDB/Mysql 5 Slaves
*/
slave->state = BLRS_ERRORED;
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb,
++slave->seqno,
0,
err_msg,
"42000",
1064);
MXS_ERROR("%s: Slave %s: %s",
router->service->name,
slave->dcb->remote,
err_msg);
dcb_close(slave->dcb);
rv = 1;
}
else if (router->mariadb10_master_gtid && !slave->mariadb_gtid)
{
/**
* If GTID master replication is set
* only GTID slaves can continue the registration.
*/
const char *err_msg = "MariaDB 10 Slave GTID is required"
" for Slave registration.";
slave->state = BLRS_ERRORED;
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb,
++slave->seqno,
0,
err_msg,
"HY000",
1597);
MXS_ERROR("%s: Slave %s: %s"
" Please use: CHANGE MASTER TO master_use_gtid=slave_pos.",
router->service->name,
slave->dcb->remote,
err_msg);
dcb_close(slave->dcb);
rv = 1;
}
else
{
/* Master and Slave version OK: continue with slave registration */
rv = blr_slave_register(router, slave, queue);
}
break;
case COM_BINLOG_DUMP:
rv = blr_slave_binlog_dump(router, slave, queue);
if (rv && router->send_slave_heartbeat && slave->heartbeat > 0)
{
char task_name[BLRM_TASK_NAME_LEN + 1] = "";
snprintf(task_name,
BLRM_TASK_NAME_LEN,
"%s slaves heartbeat send",
router->service->name);
/* Add slave heartbeat check task with 1 second frequency */
hktask_add(task_name, blr_send_slave_heartbeat, router, 1);
}
break;
case COM_STATISTICS:
rv = blr_statistics(router, slave, queue);
break;
case COM_PING:
rv = blr_ping(router, slave, queue);
break;
case COM_QUIT:
MXS_DEBUG("COM_QUIT received from slave with server_id %d",
slave->serverid);
rv = 1;
break;
default:
blr_send_custom_error(slave->dcb,
1,
0,
"You have an error in your SQL syntax; Check the "
"syntax the MaxScale binlog router accepts.",
"42000",
1064);
MXS_ERROR("Unexpected MySQL Command (%d) received from slave",
MYSQL_COMMAND(queue));
break;
}
return rv;
}
/*
* Return a pointer to where the actual SQL query starts, skipping initial
* comments and whitespace characters, if there are any.
*/
const char *
blr_skip_leading_sql_comments(const char *sql_query)
{
const char *p = sql_query;
while (*p) {
if (*p == '/' && p[1] == '*')
{
++p; // skip '/'
++p; // skip '*'
while (*p)
{
if (*p == '*' && p[1] == '/')
{
++p; // skip '*'
++p; // skip '/'
break;
}
else
{
++p;
}
}
}
else if (isspace(*p))
{
++p;
}
else
{
return p;
}
}
return p;
}
/**
* Handle a query from the slave. This is expected to be one of the "standard"
* queries we expect as part of the registraton process. Most of these can
* be dealt with by replying the stored responses we got from the master
* when MaxScale registered as a slave. The exception to the rule is the
* request to obtain the current timestamp value of the server.
*
* The original set added for the registration process has been enhanced in
* order to support some commands that are useful for monitoring the binlog
* router.
*
* 16 select statements are currently supported:
* SELECT UNIX_TIMESTAMP();
* SELECT @master_binlog_checksum
* SELECT @@GLOBAL.GTID_MODE
* SELECT VERSION()
* SELECT 1
* SELECT @@version_comment limit 1
* SELECT @@hostname
* SELECT @@max_allowed_packet
* SELECT @@maxscale_version
* SELECT @@[GLOBAL.]server_id
* SELECT @@version
* SELECT @@[GLOBAL.]server_uuid
* SELECT USER()
* SELECT @@GLOBAL.gtid_domain_id
* SELECT @@[GLOBAL].gtid_current_pos
* SELECT @@[global.]server_id, @@[global.]read_only
*
* 9 show commands are supported:
* SHOW [GLOBAL] VARIABLES LIKE 'SERVER_ID'
* SHOW [GLOBAL] VARIABLES LIKE 'SERVER_UUID'
* SHOW [GLOBAL] VARIABLES LIKE 'MAXSCALE%'
* SHOW SLAVE STATUS
* SHOW MASTER STATUS
* SHOW SLAVE HOSTS
* SHOW WARNINGS
* SHOW [GLOBAL] STATUS LIKE 'Uptime'
* SHOW [FULL] BINARY LOGS
*
* 12 set commands are supported:
* SET @master_binlog_checksum = @@global.binlog_checksum
* SET @master_heartbeat_period=...
* SET @slave_slave_uuid=...
* SET NAMES latin1
* SET NAMES utf8
* SET NAMES XXX
* SET mariadb_slave_capability=...
* SET autocommit=
* SET @@session.autocommit=
* SET @slave_connect_state=
* SET @slave_gtid_strict_mode=
* SET @slave_gtid_ignore_duplicates=
*
* 4 administrative commands are supported:
* STOP SLAVE
* START SLAVE
* CHANGE MASTER TO
* RESET SLAVE
*
* @param router The router instance
* @param slave The slave specific data
* @param queue The incoming request packet
* @return Non-zero if data has been sent
*/
static int
blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
char *qtext, *query_text;
char *sep = " ,=";
char *word, *brkb;
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;
qtext += MYSQL_HEADER_LEN + 1; // Skip header and first byte of the payload
query_text = strndup(qtext, query_len);
/* Don't log the full statement containg 'password', just trucate it */
ptr = strcasestr(query_text, "password");
if (ptr != NULL)
{
char *new_text = MXS_STRDUP_A(query_text);
int trucate_at = (ptr - query_text);
if (trucate_at > 0)
{
if ( (trucate_at + 3) <= (int)strlen(new_text))
{
int i;
for (i = 0; i < 3; i++)
{
new_text[trucate_at + i] = '.';
}
new_text[trucate_at + 3] = '\0';
}
else
{
new_text[trucate_at] = '\0';
}
}
MXS_INFO("Execute statement (truncated, it contains password)"
" from the slave '%s'",
new_text);
MXS_FREE(new_text);
}
else
{
MXS_INFO("Execute statement from the slave '%s'", query_text);
}
/*
* Implement a very rudimental "parsing" of the query text by extarcting the
* words from the statement and matchng them against the subset of queries we
* are expecting from the slave. We already have responses to these commands,
* except for the select of UNIX_TIMESTAMP(), that we have saved from MaxScale's
* own interaction with the real master. We simply replay these saved responses
* to the slave.
*/
/* - 1 - Check and handle possible Maxwell input statement */
if (blr_handle_maxwell_stmt(router,
slave,
query_text))
{
MXS_FREE(query_text);
return 1;
} /* - 2 - Handle SELECT, SET, SHOW and Admin commands */
else if ((word = strtok_r(query_text, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete query.", router->service->name);
}
else if (strcasecmp(word, "SELECT") == 0)
{
/* Handle SELECT */
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete select query.", router->service->name);
}
else
{
if (brkb && strlen(brkb) &&
blr_handle_complex_select(router,
slave,
word,
brkb))
{
MXS_FREE(query_text);
return 1;
}
if (blr_handle_simple_select_stmt(router,
slave,
word))
{
MXS_FREE(query_text);
return 1;
}
else
{
/* Handle a special case */
unexpected = strcasestr(word, "binlog_gtid_pos") == NULL;
}
}
}
else if (strcasecmp(word, "SHOW") == 0)
{
/* Handle SHOW */
if (blr_handle_show_stmt(router,
slave,
brkb))
{
MXS_FREE(query_text);
return 1;
}
}
else if (strcasecmp(query_text, "SET") == 0)
{
/* Handle SET */
if (blr_handle_set_stmt(router,
slave,
brkb))
{
MXS_FREE(query_text);
return 1;
}
}
else
{ /* Handle ADMIN commands */
if (blr_handle_admin_stmt(router,
slave,
word,
brkb))
{
MXS_FREE(query_text);
return 1;
}
}
/* - 3 - Handle unsuppored statements from client */
MXS_FREE(query_text);
query_text = strndup(qtext, query_len);
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 "
"the MaxScale binlog router accepts.");
return 1;
}
/**
* Send a reply to a command we have received from the slave. The reply itself
* is merely a copy of a previous message we received from the master when we
* registered as a slave. Hence we just replay this saved reply.
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param master The saved master response
* @return Non-zero if data was sent
*/
static int
blr_slave_replay(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *master)
{
GWBUF *clone;
if (router->master_state == BLRM_UNCONFIGURED)
{
return blr_slave_send_ok(router, slave);
}
if (!master)
{
return 0;
}
if ((clone = gwbuf_clone(master)) != NULL)
{
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, clone);
}
else
{
MXS_ERROR("Failed to clone server response to send to slave.");
return 0;
}
}
/**
* Construct an error response
*
* @param router The router instance
* @param slave The slave server instance
* @param msg The error message to send
*/
static void
blr_slave_send_error(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *msg)
{
GWBUF *pkt;
unsigned char *data;
int len;
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
{
return;
}
data = GWBUF_DATA(pkt);
len = strlen(msg) + 9;
encode_value(&data[0], len, 24); // Payload length
data[3] = 1; // Sequence id
// Payload
data[4] = 0xff; // Error indicator
encode_value(&data[5], 1064, 16);// Error Code
memcpy((char *)&data[7], "#42000", 6);
memcpy(&data[13], msg, strlen(msg)); // Error Message
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/*
* Some standard packets that have been captured from a network trace of server
* interactions. These packets are the schema definition sent in response to
* a SELECT UNIX_TIMESTAMP() statement and the EOF packet that marks the end
* of transmission of the result set.
*/
static uint8_t timestamp_def[] =
{
0x01, 0x00, 0x00, 0x01, 0x01, 0x26, 0x00, 0x00, 0x02, 0x03, 0x64, 0x65,
0x66, 0x00, 0x00, 0x00, 0x10, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x54, 0x49,
0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x28, 0x29, 0x00, 0x0c, 0x3f,
0x00, 0x0b, 0x00, 0x00, 0x00, 0x08, 0x81, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x02, 0x00
};
static uint8_t timestamp_eof[] = { 0x05, 0x00, 0x00, 0x05,
0xfe, 0x00, 0x00, 0x02, 0x00 };
/**
* Send a response to a "SELECT UNIX_TIMESTAMP()" request.
* This differs from the other
* requests since we do not save a copy of the original interaction
* with the master and simply replay it.
* We want to always send the current time. We have stored a typcial
* response, which gives us the schema information normally returned.
* This is sent to the * client and then we add a dynamic part that will
* insert the current timestamp data.
* Finally we send a preprepaed EOF packet to end the response stream.
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_timestamp(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char timestamp[20];
uint8_t *ptr;
int len, ts_len;
sprintf(timestamp, "%ld", time(0));
ts_len = strlen(timestamp);
len = sizeof(timestamp_def) + sizeof(timestamp_eof) +
MYSQL_HEADER_LEN + 1 + ts_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
memcpy(ptr, timestamp_def, sizeof(timestamp_def)); // Fixed preamble
ptr += sizeof(timestamp_def);
encode_value(ptr, ts_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = ts_len; // Length of result string
memcpy((char *)ptr, timestamp, ts_len); // Result string
ptr += ts_len;
// EOF packet to terminate result
memcpy(ptr, timestamp_eof, sizeof(timestamp_eof));
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send a response the the SQL command SELECT @@MAXSCALE_VERSION
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_maxscale_version(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char version[80] = "";
uint8_t *ptr;
int len, vers_len;
sprintf(version, "%s", MAXSCALE_VERSION);
vers_len = strlen(version);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router,
slave,
"MAXSCALE_VERSION",
BLR_TYPE_STRING,
vers_len,
2);
blr_slave_send_eof(router, slave, 3);
len = MYSQL_HEADER_LEN + 1 + vers_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, version, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is to be added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send a response the the SQL command SELECT @@server_id
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_server_id(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char server_id[40];
uint8_t *ptr;
int len, id_len;
sprintf(server_id, "%d", router->masterid);
id_len = strlen(server_id);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router,
slave,
"SERVER_ID",
BLR_TYPE_INT,
id_len,
2);
blr_slave_send_eof(router, slave, 3);
len = MYSQL_HEADER_LEN + 1 + id_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, id_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = id_len; // Length of result string
memcpy((char *)ptr, server_id, id_len); // Result string
/* ptr += id_len; Not required unless more data is to be added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send the response to the SQL command "SHOW VARIABLES LIKE 'MAXSCALE%'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_maxscale_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char name[40];
char version[80];
uint8_t *ptr;
int len, vers_len, seqno = 2;
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router,
slave,
"Variable_name",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_columndef(router,
slave,
"Value",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_eof(router, slave, seqno++);
sprintf(version, "%s", MAXSCALE_VERSION);
vers_len = strlen(version);
strcpy(name, "MAXSCALE_VERSION");
len = MYSQL_HEADER_LEN + 1 + vers_len + strlen(name) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, vers_len + 2 + strlen(name), 24);
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(name); // Length of result string
memcpy((char *)ptr, name, strlen(name)); // Result string
ptr += strlen(name);
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, version, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is to be added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "SHOW MASTER STATUS"
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_master_status(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char file[40];
char position[40];
uint8_t *ptr;
int len, file_len;
blr_slave_send_fieldcount(router, slave, 5);
blr_slave_send_columndef(router, slave, "File",
BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Position",
BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Binlog_Do_DB",
BLR_TYPE_STRING, 40, 4);
blr_slave_send_columndef(router, slave, "Binlog_Ignore_DB",
BLR_TYPE_STRING, 40, 5);
blr_slave_send_columndef(router, slave, "Execute_Gtid_Set",
BLR_TYPE_STRING, 40, 6);
blr_slave_send_eof(router, slave, 7);
sprintf(file, "%s", router->binlog_name);
file_len = strlen(file);
sprintf(position, "%lu", router->binlog_position);
len = MYSQL_HEADER_LEN + 1 + file_len + strlen(position) + 1 + 3;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
ptr += 3;
*ptr++ = 0x08; // Sequence number in response
*ptr++ = strlen(file); // Length of result string
memcpy((char *)ptr, file, strlen(file)); // Result string
ptr += strlen(file);
*ptr++ = strlen(position); // Length of result string
// Result string
memcpy((char *)ptr, position, strlen(position));
ptr += strlen(position);
*ptr++ = 0; // Send 3 empty values
*ptr++ = 0;
*ptr++ = 0;
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, 9);
}
/*
* Columns to send for GTID in "SHOW SLAVE STATUS" command
*/
static const char *slave_status_columns[] =
{
"Slave_IO_State",
"Master_Host",
"Master_User",
"Master_Port",
"Connect_Retry",
"Master_Log_File",
"Read_Master_Log_Pos",
"Relay_Log_File",
"Relay_Log_Pos",
"Relay_Master_Log_File",
"Slave_IO_Running",
"Slave_SQL_Running",
"Replicate_Do_DB",
"Replicate_Ignore_DB",
"Replicate_Do_Table",
"Replicate_Ignore_Table",
"Replicate_Wild_Do_Table",
"Replicate_Wild_Ignore_Table",
"Last_Errno",
"Last_Error",
"Skip_Counter",
"Exec_Master_Log_Pos",
"Relay_Log_Space",
"Until_Condition",
"Until_Log_File",
"Until_Log_Pos",
"Master_SSL_Allowed",
"Master_SSL_CA_File",
"Master_SSL_CA_Path",
"Master_SSL_Cert",
"Master_SSL_Cipher",
"Master_SSL_Key",
"Seconds_Behind_Master",
"Master_SSL_Verify_Server_Cert",
"Last_IO_Errno",
"Last_IO_Error",
"Last_SQL_Errno",
"Last_SQL_Error",
"Replicate_Ignore_Server_Ids",
"Master_Server_Id",
"Master_UUID",
"Master_Info_File",
"SQL_Delay",
"SQL_Remaining_Delay",
"Slave_SQL_Running_State",
"Master_Retry_Count",
"Master_Bind",
"Last_IO_Error_TimeStamp",
"Last_SQL_Error_Timestamp",
"Master_SSL_Crl",
"Master_SSL_Crlpath",
NULL
};
/*
* New columns to send for GTID in "SHOW ALL SLAVES STATUS" command
*/
static const char *all_slaves_status_columns[] =
{
"Connection_name",
"Slave_SQL_State",
NULL
};
/*
* Columns to send for GTID in "SHOW SLAVE STATUS" MySQL 5.6/7 command
*/
static const char *mysql_gtid_status_columns[] =
{
"Retrieved_Gtid_Set",
"Executed_Gtid_Set",
"Auto_Position",
NULL
};
/*
* Columns to send for GTID in "SHOW SLAVE STATUS" MariaDB 10 command
* and SHOW ALL SLAVES STATUS as well
*/
static const char *mariadb10_gtid_status_columns[] =
{
"Using_Gtid",
"Gtid_IO_Pos",
NULL
};
/**
* Send the response to the SQL command "SHOW SLAVE STATUS" or
* SHOW ALL SLAVES STATUS
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param all_slaves Whether to use SHOW ALL SLAVES STATUS
* @return Non-zero if data was sent
*/
static int
blr_slave_send_slave_status(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
bool all_slaves)
{
GWBUF *pkt;
char column[251] = "";
uint8_t *ptr;
int len, actual_len, col_len, seqno, i;
char *dyn_column = NULL;
int max_column_size = sizeof(column);
int ncols = 0;
int gtid_cols = 0;
/* Count SHOW SLAVE STATUS the columns */
while (slave_status_columns[ncols])
{
ncols++;
}
/* Add the new SHOW ALL SLAVES STATUS columns */
if (all_slaves)
{
int k = 0;
while (all_slaves_status_columns[k++])
{
ncols++;
}
}
/* Get the right GTID columns array */
const char **gtid_status_columns = router->mariadb10_gtid ?
mariadb10_gtid_status_columns :
mysql_gtid_status_columns;
/* Increment ncols with the right GTID columns */
while (gtid_status_columns[gtid_cols++])
{
ncols++;
}
/* Send number of columns */
blr_slave_send_fieldcount(router, slave, ncols);
seqno = 2;
if (all_slaves)
{
/* Send first the column definitions for the all_slaves */
for (i = 0; all_slaves_status_columns[i]; i++)
{
blr_slave_send_columndef(router,
slave,
all_slaves_status_columns[i],
BLR_TYPE_STRING,
40,
seqno++);
}
}
/* Now send column definitions for slave status */
for (i = 0; slave_status_columns[i]; i++)
{
blr_slave_send_columndef(router,
slave,
slave_status_columns[i],
BLR_TYPE_STRING,
40,
seqno++);
}
/* Send MariaDB 10 or MySQL 5.6/7 GTID columns */
for (i = 0; gtid_status_columns[i]; i++)
{
blr_slave_send_columndef(router,
slave,
gtid_status_columns[i],
BLR_TYPE_STRING,
40,
seqno++);
}
/* Send EOF for columns def */
blr_slave_send_eof(router, slave, seqno++);
// Max length + 250 bytes error message
len = MYSQL_HEADER_LEN + 1 + ncols * max_column_size + 250;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
ptr += 3;
// Sequence number in response
*ptr++ = seqno++;
if (all_slaves)
{
for (i = 0; all_slaves_status_columns[i]; i++)
{
*ptr++ = 0; // Empty value
}
}
// Slave_IO_State
snprintf(column, max_column_size, "%s",
blrm_states[router->master_state]);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
// Master_Host
snprintf(column,
max_column_size,
"%s",
router->service->dbref->server->name ?
router->service->dbref->server->name :
"");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
// Master_User
snprintf(column, max_column_size, "%s",
router->user ? router->user : "");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
// Master_Port
sprintf(column, "%d", router->service->dbref->server->port);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%d", 60); // Connect retry
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* if router->trx_safe report current_pos*/
if (router->trx_safe)
{
sprintf(column, "%lu", router->current_pos);
}
else
{
sprintf(column, "%lu", router->binlog_position);
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* We have no relay log, we relay the binlog, so we will send the same data */
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* We have no relay log, we relay the binlog, so we will send the same data */
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
if (router->master_state != BLRM_SLAVE_STOPPED)
{
if (router->master_state < BLRM_BINLOGDUMP)
{
strcpy(column, "Connecting");
}
else
{
strcpy(column, "Yes");
}
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
if (router->master_state != BLRM_SLAVE_STOPPED)
{
strcpy(column, "Yes");
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Send 6 empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
/* Last error information */
sprintf(column, "%lu", router->m_errno);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Last error message */
if (router->m_errmsg == NULL)
{
*ptr++ = 0;
}
else
{
dyn_column = (char*)router->m_errmsg;
col_len = strlen(dyn_column);
if (col_len > 250)
{
col_len = 250;
}
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, dyn_column, col_len); // Result string
ptr += col_len;
}
/* Skip_Counter */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
strcpy(column, "None");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
/* Until_Log_Pos */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_SSL_Allowed */
if (router->ssl_enabled)
{
strcpy(column, "Yes");
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Check whether to report SSL master connection details */
if (router->ssl_ca && router->ssl_cert && router->ssl_key)
{
char big_column[250 + 1] = "";
// set Master_SSL_Cert
strncpy(big_column, router->ssl_ca, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Empty Master_SSL_CA_Path column
// set Master_SSL_Cert
strncpy(big_column, router->ssl_cert, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Empty Master_SSL_Cipher column
// set Master_SSL_Key
strncpy(big_column, router->ssl_key, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
}
else
{
*ptr++ = 0; // Empty SSL columns
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
}
/* Seconds_Behind_Master */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_SSL_Verify_Server_Cert */
strcpy(column, "No");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Last_IO_Error */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
/* Last_SQL_Error */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
*ptr++ = 0;
/* Master_Server_Id */
sprintf(column, "%d", router->masterid);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_server_UUID */
snprintf(column, max_column_size, "%s", router->master_uuid ?
router->master_uuid : router->uuid);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_info_file */
snprintf(column, max_column_size, "%s/master.ini", router->binlogdir);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* SQL_Delay*/
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0xfb; // NULL value
/* Slave_Running_State */
if (router->master_state == BLRM_SLAVE_STOPPED)
{
strcpy(column, "Slave stopped");
}
else if (!router->m_errno)
{
strcpy(column, "Slave running");
}
else
{
if (router->master_state < BLRM_BINLOGDUMP)
{
strcpy(column, "Registering");
}
else
{
strcpy(column, "Error");
}
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_Retry_Count */
sprintf(column, "%d", 1000);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Send 5 empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
if (!router->mariadb10_gtid)
{
// No GTID support send empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
}
else
{
// MariaDB 10 GTID
sprintf(column, "%s", "Slave_pos");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy(ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%s", router->last_mariadb_gtid);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy(ptr, column, col_len); // Result string
ptr += col_len;
}
*ptr++ = 0;
actual_len = ptr - (uint8_t *)GWBUF_DATA(pkt);
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, actual_len - MYSQL_HEADER_LEN, 24);
// Trim the buffer to the actual size
pkt = gwbuf_rtrim(pkt, len - actual_len);
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "SHOW SLAVE HOSTS"
*
* @param router The binlog router instance
* @param slave The connected slave server
* @return Non-zero if data was sent
*/
static int
blr_slave_send_slave_hosts(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char server_id[40];
char host[40];
char port[40];
char master_id[40];
char slave_uuid[40];
uint8_t *ptr;
int len, seqno;
ROUTER_SLAVE *sptr;
blr_slave_send_fieldcount(router, slave, 5);
blr_slave_send_columndef(router, slave, "Server_id", BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Host", BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Port", BLR_TYPE_STRING, 40, 4);
blr_slave_send_columndef(router, slave, "Master_id", BLR_TYPE_STRING, 40, 5);
blr_slave_send_columndef(router, slave, "Slave_UUID", BLR_TYPE_STRING, 40, 6);
blr_slave_send_eof(router, slave, 7);
seqno = 8;
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
if (sptr->state == BLRS_DUMPING || sptr->state == BLRS_REGISTERED)
{
sprintf(server_id, "%d", sptr->serverid);
sprintf(host, "%s", sptr->hostname ? sptr->hostname : "");
sprintf(port, "%d", sptr->port);
sprintf(master_id, "%d", router->serverid);
sprintf(slave_uuid, "%s", sptr->uuid ? sptr->uuid : "");
len = MYSQL_HEADER_LEN + strlen(server_id) + strlen(host) + strlen(port)
+ strlen(master_id) + strlen(slave_uuid) + 5;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - MYSQL_HEADER_LEN, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(server_id); // Length of result string
memcpy((char *)ptr, server_id, strlen(server_id)); // Result string
ptr += strlen(server_id);
*ptr++ = strlen(host); // Length of result string
memcpy((char *)ptr, host, strlen(host)); // Result string
ptr += strlen(host);
*ptr++ = strlen(port); // Length of result string
memcpy((char *)ptr, port, strlen(port)); // Result string
ptr += strlen(port);
*ptr++ = strlen(master_id); // Length of result string
memcpy((char *)ptr, master_id, strlen(master_id)); // Result string
ptr += strlen(master_id);
*ptr++ = strlen(slave_uuid); // Length of result string
memcpy((char *)ptr, slave_uuid, strlen(slave_uuid)); // Result string
ptr += strlen(slave_uuid);
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
return blr_slave_send_eof(router, slave, seqno);
}
/**
* Process a slave replication registration message.
*
* We store the various bits of information the slave gives us and generate
* a reply message: OK packet.
*
* @param router The router instance
* @param slave The slave server
* @param queue The BINLOG_DUMP packet
* @return Non-zero if data was sent
*/
static int
blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
uint8_t *ptr;
int slen;
ptr = GWBUF_DATA(queue);
ptr += 4; // Skip length and sequence number
if (*ptr++ != COM_REGISTER_SLAVE)
{
return 0;
}
slave->serverid = extract_field(ptr, 32);
ptr += 4;
slen = *ptr++;
if (slen != 0)
{
slave->hostname = strndup((char *)ptr, slen);
ptr += slen;
}
else
{
slave->hostname = NULL;
}
slen = *ptr++;
if (slen != 0)
{
ptr += slen;
slave->user = strndup((char *)ptr, slen);
}
else
{
slave->user = NULL;
}
slen = *ptr++;
if (slen != 0)
{
slave->passwd = strndup((char *)ptr, slen);
ptr += slen;
}
else
{
slave->passwd = NULL;
}
slave->port = extract_field(ptr, 16);
ptr += 2;
slave->rank = extract_field(ptr, 32);
slave->state = BLRS_REGISTERED;
/*
* Send OK response
*/
return blr_slave_send_ok(router, slave);
}
/**
* Process a COM_BINLOG_DUMP message from the slave. This is the
* final step in the process of registration. The new master, MaxScale
* must send a response packet and generate a fake BINLOG_ROTATE event
* with the binlog file requested by the slave. And then send a
* FORMAT_DESCRIPTION_EVENT that has been saved from the real master.
*
* Once send MaxScale must continue to send binlog events to the slave.
*
* @param router The router instance
* @param slave The slave server
* @param queue The BINLOG_DUMP packet
* @return The number of bytes written to the slave
*/
static int
blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
GWBUF *resp;
uint8_t *ptr;
int len, binlognamelen;
REP_HEADER hdr;
uint32_t chksum;
uint32_t fde_end_pos;
uint32_t requested_pos;
ptr = GWBUF_DATA(queue);
len = extract_field(ptr, 24);
binlognamelen = len - 11;
ptr += 4; // Skip length and sequence number
if (*ptr++ != COM_BINLOG_DUMP)
{
MXS_ERROR("blr_slave_binlog_dump expected a COM_BINLOG_DUMP but received %d",
*(ptr - 1));
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
/* Get the current router binlog file */
spinlock_acquire(&router->binlog_lock);
strcpy(slave->binlogfile, router->binlog_name);
spinlock_release(&router->binlog_lock);
/* Set the safe pos */
slave->binlog_pos = 4;
/* Get the requested pos from packet */
requested_pos = extract_field(ptr, 32);
/* Go ahead: after 4 bytes pos, 2 bytes flag and 4 bytes serverid */
ptr += 4;
ptr += 2;
ptr += 4;
/* ptr now points to requested filename, if present */
if (binlognamelen)
{
if (binlognamelen > BINLOG_FNAMELEN)
{
char req_file[binlognamelen + 1];
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
memcpy(req_file, (char *)ptr, binlognamelen);
MXS_ERROR("Slave %lu requests COM_BINLOG_DUMP with a filename %s"
" longer than max %d chars. Aborting.",
(unsigned long)slave->serverid,
req_file,
BINLOG_FNAMELEN);
snprintf(errmsg, BINLOG_ERROR_MSG_LEN,
"Connecting slave requested binlog"
" file name %s longer than max %d chars.",
req_file,
BINLOG_FNAMELEN);
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
blr_send_custom_error(slave->dcb,
slave->seqno + 1,
0,
errmsg,
"HY000",
BINLOG_FATAL_ERROR_READING);
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
/* Set the received filename from packet: it could be changed later */
memcpy(slave->binlogfile, (char *)ptr, binlognamelen);
slave->binlogfile[binlognamelen] = 0;
}
/**
* Check MariaDB GTID request
*/
if (slave->mariadb10_compat &&
slave->mariadb_gtid)
{
/* Set file and pos accordingly to GTID lookup */
if (!blr_slave_gtid_request(router,
slave,
binlognamelen > 0,
requested_pos))
{
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
}
else
{
/**
* Binlog file has been set from packet data.
* Now just set the position from packet as well.
*/
slave->binlog_pos = requested_pos;
}
/**
* Check for a pending transaction and possible unsafe position.
* Force slave disconnection if requested position is unsafe.
*/
if (router->trx_safe)
{
bool force_disconnect = false;
spinlock_acquire(&router->binlog_lock);
if (router->pending_transaction.state > BLRM_NO_TRANSACTION &&
blr_is_current_binlog(router, slave) &&
(slave->binlog_pos > router->binlog_position))
{
force_disconnect = true;
}
spinlock_release(&router->binlog_lock);
if (force_disconnect)
{
MXS_ERROR("%s: Slave %s:%i, server-id %d, binlog '%s', blr_slave_binlog_dump failure: "
"Requested binlog position %lu. Position is unsafe so disconnecting. "
"Latest safe position %lu, end of binlog file %lu",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
(unsigned long)slave->binlog_pos,
router->binlog_position,
router->current_pos);
slave->state = BLRS_ERRORED;
/*
* Close the slave session and socket
* The slave will try to reconnect
*/
dcb_close(slave->dcb);
return 1;
}
}
MXS_DEBUG("%s: Slave %s:%i, COM_BINLOG_DUMP: binlog name '%s', length %lu, "
"from position %lu.",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->binlogfile,
strlen(slave->binlogfile),
(unsigned long)slave->binlog_pos);
/* First reply starts from seq = 1 */
slave->seqno = 1;
/**
* Check whether the request file is empty
* and try using next file in sequence or next one
* based on GTID mpas.
* If one or more files have been skipped then
* the slave->binlog_pos is set to 4 and
* slave->binlogname set to new filename.
*/
blr_slave_skip_empty_files(router, slave);
/* Build and send Fake Rotate Event */
if (!blr_send_connect_fake_rotate(router, slave))
{
// ERROR
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
/* set lastEventReceived */
slave->lastEventReceived = ROTATE_EVENT;
/* set lastReply for slave heartbeat check */
if (router->send_slave_heartbeat)
{
slave->lastReply = time(0);
}
/* Read Format Description Event */
GWBUF *fde = blr_slave_read_fde(router, slave);
if (fde == NULL)
{
// ERROR
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
/* FDE ends at pos 4 + FDE size */
fde_end_pos = 4 + GWBUF_LENGTH(fde);
/* Send a Fake FORMAT_DESCRIPTION_EVENT */
if (slave->binlog_pos != 4)
{
if (!blr_slave_send_fde(router, slave, fde))
{
// ERROR
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
}
/* set lastEventReceived */
slave->lastEventReceived = FORMAT_DESCRIPTION_EVENT;
/**
* Check for START_ENCRYPTION_EVENT (after FDE) if
* client request pos is greater than 4
*
* TODO: If router has binlog encryption take it
* otherwise error
* If no encryption and event found return error
*
* If event is found the contest is set into slave struct
*/
if (slave->binlog_pos != 4)
{
blr_slave_read_ste(router, slave, fde_end_pos);
}
/**
* Add GTID_LIST Fake Event before sending any new event
* Note: slave->binlog_pos must not be 4
*/
if (slave->binlog_pos != 4 &&
slave->mariadb10_compat &&
slave->mariadb_gtid)
{
if (!blr_send_fake_gtid_list(slave,
slave->mariadb_gtid,
router->masterid))
{
// ERROR
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 1;
}
slave->lastEventReceived = MARIADB10_GTID_GTID_LIST_EVENT;
}
/* Set dcb_callback for the events reading routine */
dcb_add_callback(slave->dcb, DCB_REASON_DRAINED, blr_slave_callback, slave);
slave->state = BLRS_DUMPING;
MXS_NOTICE("%s: Slave [%s]:%d, server id %d requested binlog file %s from position %lu",
router->service->name, slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
(unsigned long)slave->binlog_pos);
/* Force the slave to call catchup routine */
poll_fake_write_event(slave->dcb);
return 1;
}
/**
* Encode a value into a number of bits in a MySQL packet
*
* @param data Pointer to location in target packet
* @param value The value to encode into the buffer
* @param len Number of bits to encode value into
*/
static void
encode_value(unsigned char *data, unsigned int value, int len)
{
while (len > 0)
{
*data++ = value & 0xff;
value >>= 8;
len -= 8;
}
}
/**
* Populate a header structure for a replication message from a GWBUF.
*
* @param pkt The incoming packet in a GWBUF chain
* @param hdr The packet header to populate
* @return A pointer to the first byte following the event header
*/
uint8_t *
blr_build_header(GWBUF *pkt, REP_HEADER *hdr)
{
uint8_t *ptr;
ptr = GWBUF_DATA(pkt);
encode_value(ptr, hdr->payload_len, 24);
ptr += 3;
*ptr++ = hdr->seqno;
*ptr++ = hdr->ok;
encode_value(ptr, hdr->timestamp, 32);
ptr += 4;
*ptr++ = hdr->event_type;
encode_value(ptr, hdr->serverid, 32);
ptr += 4;
encode_value(ptr, hdr->event_size, 32);
ptr += 4;
encode_value(ptr, hdr->next_pos, 32);
ptr += 4;
encode_value(ptr, hdr->flags, 16);
ptr += 2;
return ptr;
}
/**
* We have a registered slave that is behind the current leading edge of the
* binlog. We must replay the log entries to bring this node up to speed.
*
* There may be a large number of records to send to the slave, the process
* is triggered by the slave COM_BINLOG_DUMP message and all the events must
* be sent without receiving any new event. This measn there is no trigger into
* MaxScale other than this initial message. However, if we simply send all the
* events we end up with an extremely long write queue on the DCB and risk
* running the server out of resources.
*
* The slave catchup routine will send a burst of replication events per single
* call. The paramter "long" control the number of events in the burst. The
* short burst is intended to be used when the master receive an event and
* needs to put the slave into catchup mode. This prevents the slave taking
* too much time away from the thread that is processing the master events.
*
* At the end of the burst a fake EPOLLOUT event is added to the poll event
* queue. This ensures that the slave callback for processing DCB write drain
* will be called and future catchup requests will be handled on another thread.
*
* @param router The binlog router
* @param slave The slave that is behind
* @param large Send a long or short burst of events
* @return The number of bytes written
*/
int
blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
{
GWBUF *record;
REP_HEADER hdr;
int rval = 1, burst;
int rotating = 0;
long burst_size;
char read_errmsg[BINLOG_ERROR_MSG_LEN + 1];
MARIADB_GTID_INFO *f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
&slave->f_info :
NULL;
read_errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
if (large)
{
burst = router->long_burst;
}
else
{
burst = router->short_burst;
}
burst_size = router->burst_size;
int do_return;
spinlock_acquire(&router->binlog_lock);
do_return = 0;
/* check for a pending transaction and safe position */
if (router->pending_transaction.state > BLRM_NO_TRANSACTION &&
blr_is_current_binlog(router, slave) &&
(slave->binlog_pos > router->binlog_position))
{
do_return = 1;
}
spinlock_release(&router->binlog_lock);
if (do_return)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate &= ~CS_BUSY;
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return 0;
}
BLFILE *file;
#ifdef BLFILE_IN_SLAVE
file = slave->file;
slave->file = NULL;
#else
file = NULL;
#endif
if (file == NULL)
{
rotating = router->rotating;
if ((file = blr_open_binlog(router,
slave->binlogfile,
f_tree)) == NULL)
{
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
if (rotating)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return rval;
}
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
"failed to open binlog file",
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile);
slave->cstate &= ~CS_BUSY;
slave->state = BLRS_ERRORED;
snprintf(err_msg,
BINLOG_ERROR_MSG_LEN,
"Failed to open binlog '%s'",
slave->binlogfile);
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb,
slave->seqno++,
0,
err_msg,
"HY000",
BINLOG_FATAL_ERROR_READING);
dcb_close(slave->dcb);
return 0;
}
}
slave->stats.n_bursts++;
#ifdef BLSLAVE_IN_FILE
slave->file = file;
#endif
int events_before = slave->stats.n_events;
while (burst-- && burst_size > 0 &&
(record = blr_read_binlog(router,
file,
slave->binlog_pos,
&hdr,
read_errmsg,
slave->encryption_ctx)) != NULL)
{
char binlog_name[BINLOG_FNAMELEN + 1];
uint32_t binlog_pos;
uint32_t event_size;
strcpy(binlog_name, slave->binlogfile);
binlog_pos = slave->binlog_pos;
/* Don't sent special events generated by MaxScale */
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT ||
hdr.event_type == IGNORABLE_EVENT ||
(hdr.flags & LOG_EVENT_IGNORABLE_F))
{
/* In case of file rotation or pos = 4 the events
* are sent from position 4 and the new FDE at pos 4 is read.
* We need to check whether the first event after FDE
* is the MARIADB10_START_ENCRYPTION_EVENT of the new file.
*
* Read it if slave->encryption_ctx is NULL and
* set the slave->encryption_ctx accordingly
*/
spinlock_acquire(&slave->catch_lock);
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT &&
!slave->encryption_ctx)
{
/* read it, set slave & file context */
uint8_t *record_ptr = GWBUF_DATA(record);
SLAVE_ENCRYPTION_CTX *encryption_ctx;
encryption_ctx = MXS_CALLOC(1, sizeof(SLAVE_ENCRYPTION_CTX));
MXS_ABORT_IF_NULL(encryption_ctx);
record_ptr += BINLOG_EVENT_HDR_LEN;
encryption_ctx->binlog_crypto_scheme = record_ptr[0];
memcpy(&encryption_ctx->binlog_key_version,
record_ptr + 1,
BLRM_KEY_VERSION_LENGTH);
memcpy(encryption_ctx->nonce,
record_ptr + 1 + BLRM_KEY_VERSION_LENGTH,
BLRM_NONCE_LENGTH);
/* Save current first_enc_event_pos */
encryption_ctx->first_enc_event_pos = hdr.next_pos;
/* set the encryption ctx into slave */
slave->encryption_ctx = encryption_ctx;
MXS_INFO("Start Encryption event found while reading. "
"Binlog %s is encrypted. First event at %lu",
slave->binlogfile,
(unsigned long)hdr.next_pos);
}
else
{
MXS_INFO("Found ignorable event [%s] of size %lu while "
"reading binlog %s at %lu",
blr_get_event_description(router, hdr.event_type),
(unsigned long)hdr.event_size,
slave->binlogfile,
(unsigned long)slave->binlog_pos);
}
/* set next pos */
slave->binlog_pos = hdr.next_pos;
spinlock_release(&slave->catch_lock);
gwbuf_free(record);
record = NULL;
break;
}
if (hdr.event_type == ROTATE_EVENT)
{
unsigned long beat1 = hkheartbeat;
blr_close_binlog(router, file);
if (hkheartbeat - beat1 > 1)
{
MXS_ERROR("blr_close_binlog took %lu maxscale beats",
hkheartbeat - beat1);
}
blr_slave_rotate(router, slave, GWBUF_DATA(record));
/* reset the encryption context */
MXS_FREE(slave->encryption_ctx);
slave->encryption_ctx = NULL;
beat1 = hkheartbeat;
#ifdef BLFILE_IN_SLAVE
if ((slave->file = blr_open_binlog(router,
slave->binlogfile,
f_tree)) == NULL)
#else
if ((file = blr_open_binlog(router,
slave->binlogfile,
f_tree)) == NULL)
#endif
{
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
if (rotating)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return rval;
}
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
"failed to open binlog file in rotate event",
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile);
slave->state = BLRS_ERRORED;
snprintf(err_msg,
BINLOG_ERROR_MSG_LEN,
"Failed to open binlog '%s' in rotate event",
slave->binlogfile);
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb,
slave->seqno,
0,
err_msg,
"HY000",
BINLOG_FATAL_ERROR_READING);
gwbuf_free(record);
record = NULL;
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 0;
}
#ifdef BLFILE_IN_SLAVE
file = slave->file;
#endif
if (hkheartbeat - beat1 > 1)
{
MXS_ERROR("blr_open_binlog took %lu beats",
hkheartbeat - beat1);
}
}
if (blr_send_event(BLR_THREAD_ROLE_SLAVE,
binlog_name,
binlog_pos,
slave,
&hdr,
(uint8_t*)record->start))
{
if (hdr.event_type != ROTATE_EVENT)
{
slave->binlog_pos = hdr.next_pos;
}
slave->stats.n_events++;
burst_size -= hdr.event_size;
}
else
{
MXS_WARNING("Slave %s:%i, server-id %d, binlog '%s, position %u: "
"Slave-thread could not send event to slave, "
"closing connection.",
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
binlog_name,
binlog_pos);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 0;
}
gwbuf_free(record);
record = NULL;
/* set lastReply for slave heartbeat check */
if (router->send_slave_heartbeat)
{
slave->lastReply = time(0);
}
}
/**
* End of while reading
* Checking last buffer first
*/
if (record == NULL)
{
slave->stats.n_failed_read++;
if (hdr.ok == SLAVE_POS_BAD_FD)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
read_errmsg);
}
if (hdr.ok == SLAVE_POS_BEYOND_EOF)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
read_errmsg);
/*
* Close the slave session and socket
* The slave will try to reconnect
*/
dcb_close(slave->dcb);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
return 0;
}
if (hdr.ok == SLAVE_POS_READ_ERR)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
read_errmsg);
spinlock_acquire(&slave->catch_lock);
slave->state = BLRS_ERRORED;
spinlock_release(&slave->catch_lock);
/*
* Send an error that will stop slave replication
*/
blr_send_custom_error(slave->dcb,
slave->seqno++,
0,
read_errmsg,
"HY000",
BINLOG_FATAL_ERROR_READING);
dcb_close(slave->dcb);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
return 0;
}
if (hdr.ok == SLAVE_POS_READ_UNSAFE)
{
MXS_NOTICE("%s: Slave %s:%i, server-id %d, binlog '%s', read %d events, "
"current committed transaction event being sent: %lu, %s",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
slave->stats.n_events - events_before,
router->current_safe_event,
read_errmsg);
}
}
spinlock_acquire(&slave->catch_lock);
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
if (record)
{
slave->stats.n_flows++;
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
/* force slave to read events via catchup routine */
poll_fake_write_event(slave->dcb);
}
else if (slave->binlog_pos == router->binlog_position &&
blr_is_current_binlog(router, slave))
{
spinlock_acquire(&router->binlog_lock);
spinlock_acquire(&slave->catch_lock);
/*
* Now check again since we hold the router->binlog_lock
* and slave->catch_lock.
*/
if (slave->binlog_pos != router->binlog_position ||
!blr_is_current_binlog(router, slave))
{
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
spinlock_release(&router->binlog_lock);
/* force slave to read events via catchup routine */
poll_fake_write_event(slave->dcb);
}
else
{
/* set the CS_WAIT_DATA that allows notification
* when new events are received form master server
* call back routine will be called later.
*/
slave->cstate |= CS_WAIT_DATA;
spinlock_release(&slave->catch_lock);
spinlock_release(&router->binlog_lock);
}
}
else
{
char next_file[BINLOG_FNAMELEN + 1] = "";
if (slave->binlog_pos >= blr_file_size(file) &&
router->rotating == 0 &&
(!blr_is_current_binlog(router, slave) &&
blr_file_next_exists(router, slave, next_file)))
{
/* We may have reached the end of file of a non-current
* binlog file.
*
* Note if the master is rotating there is a window during
* which the rotate event has been written to the old binlog
* but the new binlog file has not yet been created. Therefore
* we ignore these issues during the rotate processing.
*/
MXS_ERROR("%s: Slave [%s]:%d, server-id %d reached end of file for binlog file %s "
"at %lu which is not the file currently being downloaded. "
"Master binlog is %s, %lu. This may be caused by a "
"previous failure of the master.",
router->service->name,
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
(unsigned long)slave->binlog_pos,
router->binlog_name,
router->binlog_position);
/* Reset encryption context */
MXS_FREE(slave->encryption_ctx);
slave->encryption_ctx = NULL;
/* Now pass the next_file to blr_slave_fake_rotate() */
#ifdef BLFILE_IN_SLAVE
if (blr_slave_fake_rotate(router,
slave,
&slave->file,
next_file))
#else
if (blr_slave_fake_rotate(router,
slave,
&file,
next_file))
#endif
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
else
{
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
return 0;
}
}
else
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
}
#ifndef BLFILE_IN_SLAVE
if (file)
{
blr_close_binlog(router, file);
}
#endif
return rval;
}
/**
* The DCB callback used by the slave to obtain DCB_REASON_LOW_WATER callbacks
* when the server sends all the the queue data for a DCB. This is the mechanism
* that is used to implement the flow control mechanism for the sending of
* large quantities of binlog records during the catchup process.
*
* @param dcb The DCB of the slave connection
* @param reason The reason the callback was called
* @param data The user data, in this case the server structure
*/
int
blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data)
{
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)data;
ROUTER_INSTANCE *router = slave->router;
if (NULL == dcb->session->router_session)
{
/*
* The following processing will fail if there is no router session,
* because the "data" parameter will not contain meaningful data,
* so we have no choice but to stop here.
*/
return 0;
}
if (reason == DCB_REASON_DRAINED)
{
if (slave->state == BLRS_DUMPING)
{
spinlock_acquire(&slave->catch_lock);
if (slave->cstate & CS_BUSY)
{
spinlock_release(&slave->catch_lock);
return 0;
}
slave->cstate &= ~(CS_EXPECTCB);
slave->cstate |= CS_BUSY;
spinlock_release(&slave->catch_lock);
slave->stats.n_dcb++;
blr_slave_catchup(router, slave, true);
}
else
{
MXS_DEBUG("Ignored callback due to slave state %s",
blrs_states[slave->state]);
}
}
if (reason == DCB_REASON_LOW_WATER)
{
if (slave->state == BLRS_DUMPING)
{
slave->stats.n_cb++;
blr_slave_catchup(router, slave, true);
}
else
{
slave->stats.n_cbna++;
}
}
return 0;
}
/**
* Rotate the slave to the new binlog file
*
* @param router The router instance
* @param slave The slave instance
* @param ptr The rotate event (minus header and OK byte)
*/
void
blr_slave_rotate(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, uint8_t *ptr)
{
int len = EXTRACT24(ptr + 9); // Extract the event length
// Remove length of header and position
len = len - (BINLOG_EVENT_HDR_LEN + 8);
if (router->master_chksum)
{
len -= MYSQL_HEADER_LEN;
}
if (len > BINLOG_FNAMELEN)
{
len = BINLOG_FNAMELEN;
}
ptr += BINLOG_EVENT_HDR_LEN; // Skip header
slave->binlog_pos = extract_field(ptr, 32);
slave->binlog_pos += (((uint64_t)extract_field(ptr + 4, 32)) << 32);
memcpy(slave->binlogfile, ptr + 8, len);
slave->binlogfile[len] = 0;
}
/**
* Generate an internal rotate event that we can use to cause
* the slave to move beyond a binlog file
* that is missisng the rotate event at the end.
*
* @param router The router instance
* @param slave The slave to rotate
* @return Non-zero if the rotate took place
*/
static int
blr_slave_fake_rotate(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
BLFILE** filep,
const char *new_file)
{
char *sptr;
int filenum;
GWBUF *r_event;
MARIADB_GTID_INFO *f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
&slave->f_info :
NULL;
if ((sptr = strrchr(new_file, '.')) == NULL)
{
return 0;
}
blr_close_binlog(router, *filep);
/* Set Pos = 4 */
slave->binlog_pos = 4;
/* Set Filename */
strcpy(slave->binlogfile, new_file);
if ((*filep = blr_open_binlog(router,
new_file,
f_tree)) == NULL)
{
return 0;
}
/* Build Fake Rotate Event */
r_event = blr_build_fake_rotate_event(slave,
slave->binlog_pos,
new_file,
router->masterid);
return r_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, r_event) : 0;
}
/**
* Read the format description event FDE from current slave logfile
*
* @param router The router instance
* @param slave The slave to send the event to
* @return The read FDE event on success or NULL on error
*/
static GWBUF *
blr_slave_read_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
BLFILE *file;
REP_HEADER hdr;
GWBUF *record, *head;
uint8_t *ptr;
uint32_t chksum;
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
MARIADB_GTID_INFO *f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
&slave->f_info :
NULL;
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
memset(&hdr, 0, BINLOG_EVENT_HDR_LEN);
if ((file = blr_open_binlog(router,
slave->binlogfile,
f_tree)) == NULL)
{
return NULL;
}
/* FDE, at pos 4, is not encrypted, pass NULL to last parameter */
if ((record = blr_read_binlog(router,
file,
4,
&hdr,
err_msg,
NULL)) == NULL)
{
if (hdr.ok != SLAVE_POS_READ_OK)
{
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', "
"blr_read_binlog failure: %s",
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
err_msg);
}
blr_close_binlog(router, file);
return NULL;
}
blr_close_binlog(router, file);
return record;
}
/**
* Send a "fake" format description event to the newly connected slave
*
* @param router The router instance
* @param slave The slave to send the event to
* @return The FDE event size on success or 0 on error
*/
static uint32_t
blr_slave_send_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *fde)
{
GWBUF *head;
uint8_t *ptr;
uint32_t chksum;
uint32_t event_size;
uint8_t *event_ptr;
if (fde == NULL)
{
return 0;
}
event_ptr = GWBUF_DATA(fde);
if ((head = gwbuf_alloc(MYSQL_HEADER_LEN + 1)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(head);
event_size = GWBUF_LENGTH(fde);
/* Set payload to event_size + 1 (the ok/err byte) */
encode_value(ptr, event_size + 1, 32);
ptr += 3;
*ptr++ = slave->seqno++;
*ptr++ = 0; // OK/ERR byte
head = gwbuf_append(head, fde);
event_ptr = GWBUF_DATA(fde);
encode_value(event_ptr, time(0), 32); // Overwrite timestamp
event_ptr += 13; // 4 time + 1 type + 4 server_id + 4 event_size
/* event_ptr points to position of the next event */
encode_value(event_ptr, 0, 32); // Set next position to 0
/*
* Since we have changed the timestamp we must recalculate the CRC
*
* Position ptr to the start of the event header,
* calculate a new checksum
* and write it into the header
*/
ptr = GWBUF_DATA(fde) + event_size - BINLOG_EVENT_CRC_SIZE;
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum,
GWBUF_DATA(fde),
event_size - BINLOG_EVENT_CRC_SIZE);
encode_value(ptr, chksum, 32);
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, head);
}
/**
* Send the field count packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param count Number of columns in the result set
* @return Non-zero on success
*/
static int
blr_slave_send_fieldcount(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int count)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(MYSQL_HEADER_LEN + 1)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x01; // Sequence number in response
*ptr++ = count; // Number of columns
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send the column definition packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *name,
int type,
int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 22 + strlen(name), 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = 0; // Schema name length
*ptr++ = 0; // virtual table name length
*ptr++ = 0; // Table name length
*ptr++ = strlen(name); // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = 0; // Orginal column name
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send an EOF packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param seqno The sequence number of the EOF packet
* @return Non-zero on success
*/
static int
blr_slave_send_eof(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(9)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 5, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 0xfe; // Length of result string
encode_value(ptr, 0, 16); // No errors
ptr += 2;
encode_value(ptr, 2, 16); // Autocommit enabled
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send the reply only to the SQL command "DISCONNECT SERVER $server_id'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_disconnected_server(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int server_id,
int found)
{
GWBUF *pkt;
char state[40];
char serverid[40];
uint8_t *ptr;
int len, id_len, seqno = 2;
sprintf(serverid, "%d", server_id);
if (found)
{
strcpy(state, "disconnected");
}
else
{
strcpy(state, "not found");
}
id_len = strlen(serverid);
len = MYSQL_HEADER_LEN + (1 + id_len) + (1 + strlen(state));
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router,
slave,
"server_id",
BLR_TYPE_INT,
40,
seqno++);
blr_slave_send_columndef(router,
slave,
"state",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_eof(router, slave, seqno++);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - MYSQL_HEADER_LEN, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = id_len; // Length of result string
memcpy((char *)ptr, serverid, id_len); // Result string
ptr += id_len;
*ptr++ = strlen(state); // Length of result string
memcpy((char *)ptr, state, strlen(state)); // Result string
/* ptr += strlen(state); Not required unless more data is to be added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "DISCONNECT SERVER $server_id'
* and close the connection to that server
*
* @param router The binlog router instance
* @param slave The connected slave server
* @param server_id The slave server_id to disconnect
* @return Non-zero if data was sent to the client
*/
static int
blr_slave_disconnect_server(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
int server_id)
{
ROUTER_SLAVE *sptr;
int n;
int server_found = 0;
spinlock_acquire(&router->lock);
sptr = router->slaves;
/* look for server_id among all registered slaves */
while (sptr)
{
/* don't examine slaves with state = 0 */
if ((sptr->state == BLRS_REGISTERED ||
sptr->state == BLRS_DUMPING) &&
sptr->serverid == server_id)
{
/* server_id found */
server_found = 1;
MXS_NOTICE("%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote,
server_id,
slave->dcb->user,
slave->dcb->remote);
/* send server_id with disconnect state to client */
n = blr_slave_send_disconnected_server(router,
slave,
server_id,
1);
sptr->state = BLRS_UNREGISTERED;
dcb_close(sptr->dcb);
break;
}
else
{
sptr = sptr->next;
}
}
spinlock_release(&router->lock);
/**
* Server id was not found
* send server_id with not found state to the client
*/
if (!server_found)
{
n = blr_slave_send_disconnected_server(router,
slave,
server_id,
0);
}
if (n == 0)
{
MXS_ERROR("gwbuf memory allocation in "
"DISCONNECT SERVER server_id [%d]",
sptr->serverid);
blr_slave_send_error(router,
slave,
"Memory allocation error for DISCONNECT SERVER");
}
return 1;
}
/**
* Send the response to the SQL command "DISCONNECT ALL'
* and close the connection to all slave servers
*
* @param router The binlog router instance
* @param slave The connected slave server
* @return Non-zero if data was sent to the client
*/
static int
blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
ROUTER_SLAVE *sptr;
char server_id[40];
char state[40];
uint8_t *ptr;
int len, seqno = 2;
GWBUF *pkt;
/* preparing output result */
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router,
slave,
"server_id",
BLR_TYPE_INT,
40,
seqno++);
blr_slave_send_columndef(router,
slave,
"state",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_eof(router, slave, seqno++);
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
/* skip servers with state = 0 */
if (sptr->state == BLRS_REGISTERED || sptr->state == BLRS_DUMPING)
{
sprintf(server_id, "%d", sptr->serverid);
sprintf(state, "disconnected");
len = MYSQL_HEADER_LEN + 1 + strlen(server_id) + strlen(state) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
MXS_ERROR("gwbuf memory allocation in "
"DISCONNECT ALL for [%s], server_id [%d]",
sptr->dcb->remote, sptr->serverid);
spinlock_release(&router->lock);
blr_slave_send_error(router,
slave,
"Memory allocation error for DISCONNECT ALL");
return 1;
}
MXS_NOTICE("%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote,
sptr->serverid,
slave->dcb->user,
slave->dcb->remote);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - MYSQL_HEADER_LEN, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(server_id); // Length of result string
memcpy((char *)ptr, server_id, strlen(server_id)); // Result string
ptr += strlen(server_id);
*ptr++ = strlen(state); // Length of result string
memcpy((char *)ptr, state, strlen(state)); // Result string
ptr += strlen(state);
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
sptr->state = BLRS_UNREGISTERED;
dcb_close(sptr->dcb);
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
blr_slave_send_eof(router, slave, seqno);
return 1;
}
/**
* Send a MySQL OK packet to the connected client
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending data
*
* @return Result of a write call, non-zero if successful
*/
static int
blr_slave_send_ok(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
GWBUF *pkt;
uint8_t ok_packet[] =
{
7, 0, 0, // Payload length
1, // Seqno,
0, // OK,
0, 0, 2, 0, 0, 0
};
if ((pkt = gwbuf_alloc(sizeof(ok_packet))) == NULL)
{
return 0;
}
memcpy(GWBUF_DATA(pkt), ok_packet, sizeof(ok_packet));
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send a MySQL OK packet with a message to the client
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending data
*
* @return The write call result: non-zero on success
*/
static int
blr_slave_send_ok_message(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave,
char *message)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(11 + strlen(message) + 1)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 7 + strlen(message) + 1; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
if (strlen(message) == 0)
{
*ptr++ = 0;
*ptr++ = 0;
}
else
{
*ptr++ = 1;
*ptr++ = 0;
*ptr++ = strlen(message);
strcpy((char *)ptr, message);
}
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Stop current replication from master
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Always 1 for error, for send_ok the bytes sent
*
*/
static int
blr_stop_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
/* if unconfigured return an error */
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_warning_message(router,
slave,
"1255:Slave already has been stopped");
return 1;
}
/* if already stopped return an error */
if (router->master_state == BLRM_SLAVE_STOPPED)
{
blr_slave_send_warning_message(router,
slave,
"1255:Slave already has been stopped");
return 1;
}
if (router->master)
{
if (router->master->fd != -1 &&
router->master->state == DCB_STATE_POLLING)
{
blr_master_close(router);
}
}
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
/* set last_safe_pos */
router->last_safe_pos = router->binlog_position;
/**
* Set router->prevbinlog to router->binlog_name
* The FDE event with current filename may arrive
* after STOP SLAVE is received
*/
if (strcmp(router->binlog_name, router->prevbinlog) != 0)
{
strcpy(router->prevbinlog, router->binlog_name); // Same size
}
if (router->client)
{
if (router->client->fd != -1 &&
router->client->state == DCB_STATE_POLLING)
{
dcb_close(router->client);
router->client = NULL;
}
}
/* Now it is safe to unleash other threads on this router instance */
router->reconnect_pending = 0;
router->active_logs = 0;
spinlock_release(&router->lock);
MXS_NOTICE("%s: STOP SLAVE executed by %s@%s. Disconnecting from master [%s]:%d, "
"read up to log %s, pos %lu, transaction safe pos %lu",
router->service->name,
slave->dcb->user,
slave->dcb->remote,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name,
router->current_pos,
router->binlog_position);
if (router->trx_safe &&
router->pending_transaction.state > BLRM_NO_TRANSACTION)
{
char message[BINLOG_ERROR_MSG_LEN + 1] = "";
snprintf(message, BINLOG_ERROR_MSG_LEN,
"1105:Stopped slave mid-transaction in binlog file %s, "
"pos %lu, incomplete transaction starts at pos %lu",
router->binlog_name,
router->current_pos,
router->binlog_position);
return blr_slave_send_warning_message(router, slave, message);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
/**
* Start replication from current configured master
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Always 1 for error, for send_ok the bytes sent
*
*/
static int
blr_start_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
/* if unconfigured return an error */
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_error_packet(slave,
"The server is not configured as slave; "
"fix in config file or with CHANGE MASTER TO",
(unsigned int)1200,
NULL);
return 1;
}
/* if running return an error */
if (router->master_state != BLRM_UNCONNECTED &&
router->master_state != BLRM_SLAVE_STOPPED)
{
blr_slave_send_warning_message(router,
slave,
"1254:Slave is already running");
return 1;
}
spinlock_acquire(&router->lock);
router->master_state = BLRM_UNCONNECTED;
spinlock_release(&router->lock);
/**
* Check whether to create the new binlog (router->binlog_name)
*
* File handling happens only if mariadb10_master_gtid is off:
* with Master GTID the first file will be created/opened
* by the fake Rotate Event.
*/
/* Check first for incomplete transaction */
if (strlen(router->prevbinlog) &&
strcmp(router->prevbinlog, router->binlog_name) != 0)
{
if (router->trx_safe &&
router->pending_transaction.state > BLRM_NO_TRANSACTION)
{
char msg[BINLOG_ERROR_MSG_LEN + 1] = "";
char file[PATH_MAX + 1] = "";
struct stat statb;
unsigned long filelen = 0;
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
// Add file prefix
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
{
sprintf(t_prefix,
"%" PRIu32 "/%" PRIu32 "/",
router->mariadb10_gtid_domain,
router->orig_masterid);
}
// Router current file
snprintf(file, PATH_MAX, "%s/%s%s",
router->binlogdir,
t_prefix,
router->prevbinlog);
/* Get file size */
if (stat(file, &statb) == 0)
{
filelen = statb.st_size;
}
/* Prepare warning message */
snprintf(msg, BINLOG_ERROR_MSG_LEN,
"1105:Truncated partial transaction in file %s%s, "
"starting at pos %lu, "
"ending at pos %lu. File %s now has length %lu.",
t_prefix,
router->prevbinlog,
router->last_safe_pos,
filelen,
router->prevbinlog,
router->last_safe_pos);
/* Truncate previous binlog file to last_safe pos */
if (truncate(file, router->last_safe_pos) == -1)
{
MXS_ERROR("Failed to truncate file: %d, %s",
errno, mxs_strerror(errno));
}
/* Log it */
MXS_WARNING("A transaction is still opened at pos %lu"
" File %s%s will be truncated. "
"Next binlog file is %s at pos %d, "
"START SLAVE is required again.",
router->last_safe_pos,
t_prefix,
router->prevbinlog,
router->binlog_name,
4);
spinlock_acquire(&router->lock);
router->pending_transaction.state = BLRM_NO_TRANSACTION;
router->last_safe_pos = 0;
router->master_state = BLRM_UNCONNECTED;
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
spinlock_release(&router->lock);
/* Send warning message to mysql command */
blr_slave_send_warning_message(router, slave, msg);
return 1;
}
/* No pending transaction */
else
{
/**
* If router->mariadb10_master_gtid is Off then
* handle file create/append.
* This means the domain_id and server_id
* are not taken into account for filename prefix.
*/
if (!router->mariadb10_master_gtid)
{
/* If the router file is not open, create a new binlog file */
if (router->binlog_fd == -1)
{
blr_file_new_binlog(router, router->binlog_name);
}
else
{
/* A new binlog file has been created and opened
* by CHANGE MASTER TO: use it
*/
blr_file_append(router, router->binlog_name);
}
}
}
}
/** Initialise SSL: exit on error */
if (router->ssl_enabled && router->service->dbref->server->server_ssl)
{
if (listener_init_SSL(router->service->dbref->server->server_ssl) != 0)
{
MXS_ERROR("%s: Unable to initialise SSL with backend server",
router->service->name);
blr_slave_send_error_packet(slave,
"Unable to initialise SSL with backend server",
1210,
"HY000");
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
spinlock_release(&router->lock);
return 1;
}
}
/** Start replication from master */
blr_start_master(router);
MXS_NOTICE("%s: START SLAVE executed by %s@%s. Trying connection to master [%s]:%d, "
"binlog %s, pos %lu, transaction safe pos %lu",
router->service->name,
slave->dcb->user,
slave->dcb->remote,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name,
router->current_pos,
router->binlog_position);
/* Try reloading new users and update cached credentials */
service_refresh_users(router->service);
return blr_slave_send_ok(router, slave);
}
/**
* Construct an error packet reply with specified code and status
*
* @param slave The slave server instance
* @param msg The error message to send
* @param err_num The error number to send
* @param status The error status
*/
static void
blr_slave_send_error_packet(ROUTER_SLAVE *slave,
char *msg,
unsigned int err_num,
char *status)
{
GWBUF *pkt;
unsigned char *data;
int len;
unsigned int mysql_errno = 0;
char *mysql_state;
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
{
return;
}
if (status != NULL)
{
mysql_state = status;
}
else
{
mysql_state = "HY000";
}
if (err_num > 0)
{
mysql_errno = err_num;
}
else
{
mysql_errno = (unsigned int)2003;
}
data = GWBUF_DATA(pkt);
len = strlen(msg) + 9;
encode_value(&data[0], len, 24); // Payload length
data[3] = 1; // Sequence id
data[4] = 0xff; // Error indicator
encode_value(&data[5], mysql_errno, 16); // Error Code
data[7] = '#'; // Status message first char
memcpy((char *)&data[8], mysql_state, 5); // Status message
memcpy(&data[13], msg, strlen(msg)); // Error Message
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* handle a 'change master' operation
*
* @param router The router instance
* @param command The change master SQL command
* @param error The error message, preallocated
* BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 0 on success,
* 1 on success with new binlog, -1 on failure
*/
static
int blr_handle_change_master(ROUTER_INSTANCE* router,
char *command,
char *error)
{
char *master_logfile = NULL;
char *master_log_pos = NULL;
int change_binlog = 0;
long long pos = 0;
MASTER_SERVER_CFG *current_master = NULL;
CHANGE_MASTER_OPTIONS change_master;
int parse_ret;
char *cmd_ptr;
char *cmd_string;
if ((cmd_ptr = strcasestr(command, "TO")) == NULL)
{
static const char MESSAGE[] = "statement doesn't have the CHANGE MASTER TO syntax";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
return -1;
}
if ((cmd_string = MXS_STRDUP(cmd_ptr + 2)) == NULL)
{
static const char MESSAGE[] = "error allocating memory for statement parsing";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
MXS_ERROR("%s: %s", router->service->name, error);
return -1;
}
/* Parse SQL command and populate the change_master struct */
memset(&change_master, 0, sizeof(change_master));
parse_ret = blr_parse_change_master_command(cmd_string,
error,
&change_master);
MXS_FREE(cmd_string);
if (parse_ret)
{
MXS_ERROR("%s CHANGE MASTER TO parse error: %s",
router->service->name,
error);
blr_master_free_parsed_options(&change_master);
return -1;
}
/* allocate struct for current replication parameters */
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
if (!current_master)
{
static const char MESSAGE[] = "error allocating memory for blr_master_get_config";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
MXS_ERROR("%s: %s", router->service->name, error);
blr_master_free_parsed_options(&change_master);
return -1;
}
spinlock_acquire(&router->lock);
/* save current config option data */
blr_master_get_config(router, current_master);
/*
* Change values in the router->service->dbref->server structure
* Change filename and position in the router structure
*/
/* Set new binlog position from parsed SQL command */
master_log_pos = change_master.binlog_pos;
if (master_log_pos == NULL)
{
pos = 0;
}
else
{
pos = atoll(master_log_pos);
}
/* Change the replication user */
blr_set_master_user(router, change_master.user);
/* Change the replication password */
blr_set_master_password(router, change_master.password);
/* Change the master name/address */
blr_set_master_hostname(router, change_master.host);
/* Change the master port */
blr_set_master_port(router, change_master.port);
/* Handle SSL options */
int ssl_error;
ssl_error = blr_set_master_ssl(router, change_master, error);
if (ssl_error != -1 &&
(!change_master.ssl_cert ||
!change_master.ssl_ca ||
!change_master.ssl_key))
{
if (change_master.ssl_enabled &&
atoi(change_master.ssl_enabled))
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"MASTER_SSL=1 but some required options are missing: "
"check MASTER_SSL_CERT, MASTER_SSL_KEY, MASTER_SSL_CA");
ssl_error = -1;
}
}
if (ssl_error == -1)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
spinlock_release(&router->lock);
return -1;
}
/**
* Change the binlog filename as from MASTER_LOG_FILE
* New binlog file could be the next one or current one
*/
master_logfile = blr_set_master_logfile(router,
change_master.binlog_file,
error);
/**
* If MASTER_LOG_FILE is not set
* and master connection is configured
* set master_logfile to current binlog_name.
*
* 'router->use_mariadb10_gtid' value is checked before
* returning an error
*/
if (master_logfile == NULL)
{
bool change_binlog_error = true;
const char *err_prefix = "Router is not configured "
"for master connection,";
/* Replication is not configured yet */
if (router->master_state == BLRM_UNCONFIGURED)
{
/* Check MASTER_USE_GTID option */
if (router->mariadb10_master_gtid &&
!change_master.use_mariadb10_gtid)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"%s MASTER_USE_GTID=Slave_pos is required",
err_prefix);
}
else
{
/* If there is another error message keep it */
if (!strlen(error) &&
!change_master.use_mariadb10_gtid)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"%s MASTER_LOG_FILE is required",
err_prefix);
}
}
change_binlog_error = strlen(error) ? true : false;
}
else
{
/* If errors returned set error */
if (strlen(error) &&
(router->mariadb10_master_gtid &&
!change_master.use_mariadb10_gtid))
{
/* MASTER_USE_GTID option not set */
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"%s MASTER_USE_GTID=Slave_pos is required",
err_prefix);
}
else
{
/* Use current binlog file */
master_logfile = MXS_STRDUP_A(router->binlog_name);
change_binlog_error = false;
}
}
if (change_binlog_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
spinlock_release(&router->lock);
return -1;
}
}
else
/* master_log_file is not NULL */
{
/* Check for MASTER_USE_GTID option */
const char *err_prefix = "Router is not configured "
"for master connection,";
if (router->mariadb10_master_gtid &&
!change_master.use_mariadb10_gtid)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"%s MASTER_USE_GTID=Slave_pos is required",
err_prefix);
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
spinlock_release(&router->lock);
return -1;
}
}
/**
* If master connection is configured check new binlog name:
* If binlog name has changed to next one
* then only position 4 is allowed
*/
/**
* Check whether MASTER_USE_GTID option was set
*/
if ((router->mariadb10_master_gtid &&
!change_master.use_mariadb10_gtid) &&
strcmp(master_logfile, router->binlog_name) != 0 &&
router->master_state != BLRM_UNCONFIGURED)
{
int return_error = 0;
if (master_log_pos == NULL)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Please provide an explicit MASTER_LOG_POS "
"for new MASTER_LOG_FILE %s: "
"Permitted binlog pos is %d. "
"Current master_log_file=%s, master_log_pos=%lu",
master_logfile,
4,
router->binlog_name,
router->current_pos);
return_error = 1;
}
else
{
if (pos != 4)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_POS to %s for "
"MASTER_LOG_FILE %s: "
"Permitted binlog pos is %d. "
"Current master_log_file=%s, master_log_pos=%lu",
master_log_pos,
master_logfile,
4,
router->binlog_name,
router->current_pos);
return_error = 1;
}
}
/* Return an error or set new binlog name at pos 4 */
if (return_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* Restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
spinlock_release(&router->lock);
return -1;
}
else
{
/* Set new filename at pos 4 */
strcpy(router->binlog_name, master_logfile);
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
/**
* Close current file binlog file,
* next start slave will create the new one
*/
fsync(router->binlog_fd);
close(router->binlog_fd);
router->binlog_fd = -1;
MXS_INFO("%s: New MASTER_LOG_FILE is [%s]",
router->service->name,
router->binlog_name);
}
}
/* MariaDB 10 GTID request */
else if (router->mariadb10_master_gtid &&
change_master.use_mariadb10_gtid)
{
/* Set empty filename at pos 4 */
strcpy(router->binlog_name, "");
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
MXS_INFO("%s: MASTER_USE_GTID is [%s], value [%s]",
router->service->name,
change_master.use_mariadb10_gtid,
router->last_mariadb_gtid);
}
else
{
/**
* Same binlog or master connection not configured
* Position cannot be different from
* current pos or 4 (if BLRM_UNCONFIGURED)
*/
int return_error = 0;
if (router->master_state == BLRM_UNCONFIGURED)
{
if (master_log_pos != NULL && pos != 4)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_POS to %s: "
"Permitted binlog pos is 4. Specified master_log_file=%s",
master_log_pos,
master_logfile);
return_error = 1;
}
}
else
{
if (master_log_pos != NULL && pos != router->current_pos)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN, "Can not set MASTER_LOG_POS to %s: "
"Permitted binlog pos is %lu. "
"Current master_log_file=%s, master_log_pos=%lu",
master_log_pos,
router->current_pos,
router->binlog_name,
router->current_pos);
return_error = 1;
}
}
/* log error and return */
if (return_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
spinlock_release(&router->lock);
return -1;
}
else
{
/**
* no pos change, set it to 4 if BLRM_UNCONFIGURED
* Also set binlog name if UNCONFIGURED
*/
if (router->master_state == BLRM_UNCONFIGURED)
{
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
strcpy(router->binlog_name, master_logfile);
MXS_INFO("%s: New MASTER_LOG_FILE is [%s]",
router->service->name,
router->binlog_name);
}
MXS_INFO("%s: New MASTER_LOG_POS is [%lu]",
router->service->name,
router->current_pos);
}
}
/* Log config changes (without passwords) */
MXS_NOTICE("%s: 'CHANGE MASTER TO executed'. Previous state "
"MASTER_HOST='%s', MASTER_PORT=%i, MASTER_LOG_FILE='%s', "
"MASTER_LOG_POS=%lu, MASTER_USER='%s'. New state is MASTER_HOST='%s', "
"MASTER_PORT=%i, MASTER_LOG_FILE='%s', MASTER_LOG_POS=%lu, MASTER_USER='%s'%s",
router->service->name,
current_master->host,
current_master->port,
current_master->logfile,
current_master->pos,
current_master->user,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name,
router->current_pos,
router->user,
change_master.use_mariadb10_gtid ?
", MASTER_USE_GTID=Slave_pos" :
"");
blr_master_free_config(current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
if (router->master_state == BLRM_UNCONFIGURED)
{
change_binlog = 1;
}
spinlock_release(&router->lock);
return change_binlog;
}
/*
* Set new master hostname
*
* @param router Current router instance
* @param hostname The hostname to set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_hostname(ROUTER_INSTANCE *router, char *hostname)
{
if (hostname)
{
char *ptr;
char *end;
ptr = strchr(hostname, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = hostname;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
server_update_address(router->service->dbref->server, ptr);
MXS_INFO("%s: New MASTER_HOST is [%s]",
router->service->name,
router->service->dbref->server->name);
return 1;
}
return 0;
}
/**
* Set new master port
*
* @param router Current router instance
* @param port The server TCP port
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_port(ROUTER_INSTANCE *router, char *port)
{
unsigned short new_port;
if (port != NULL)
{
new_port = atoi(port);
if (new_port)
{
server_update_port(router->service->dbref->server, new_port);
MXS_INFO("%s: New MASTER_PORT is [%i]",
router->service->name,
router->service->dbref->server->port);
return 1;
}
}
return 0;
}
/*
* Set new master binlog file
*
* The routing must be called holding router->lock
*
* @param router Current router instance
* @param filename Binlog file name
* @param error The error msg for command,
* pre-allocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return New binlog file or NULL on error
*/
static char *
blr_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error)
{
char *new_binlog_file = NULL;
if (filename)
{
long next_binlog_seqname;
char *file_ptr;
char *end;
file_ptr = strchr(filename, '\'');
if (file_ptr)
{
file_ptr++;
}
else
{
file_ptr = filename;
}
end = strchr(file_ptr, '\'');
if (end)
{
*end = '\0';
}
/* check binlog filename format */
end = strchr(file_ptr, '.');
if (!end)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Selected binlog [%s] is not in the format"
" '%s.yyyyyy'",
file_ptr,
router->fileroot);
return NULL;
}
end++;
if (router->master_state == BLRM_UNCONFIGURED)
{
char *stem_end;
stem_end = strrchr(file_ptr, '.');
/* set filestem */
if (stem_end)
{
if (router->fileroot)
{
MXS_FREE(router->fileroot);
}
router->fileroot = strndup(file_ptr, stem_end - file_ptr);
}
}
else
{
/* get next binlog file name, assuming filestem is the same */
next_binlog_seqname = blr_file_get_next_binlogname(router);
if (!next_binlog_seqname)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Cannot get the next MASTER_LOG_FILE name "
"from current binlog [%s]",
router->binlog_name);
return NULL;
}
/* Compare binlog file name with current one */
if (strcmp(router->binlog_name, file_ptr) == 0)
{
/**
* No binlog name change,
* a new position will be checked later
*/
}
else
{
/**
* This is a new binlog file request
* If file is not the next one return an error
*/
if (atoi(end) != next_binlog_seqname)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_FILE to %s: "
"Permitted binlog file names are "
"%s or %s.%06li. Current master_log_file=%s, "
"master_log_pos=%lu",
file_ptr,
router->binlog_name,
router->fileroot,
next_binlog_seqname,
router->binlog_name,
router->current_pos);
return NULL;
}
/* Binlog file name succesfully changed */
}
}
if (strlen(file_ptr) <= BINLOG_FNAMELEN)
{
new_binlog_file = MXS_STRDUP(file_ptr);
}
else
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_FILE to %s: Maximum length is %d.",
file_ptr,
BINLOG_FNAMELEN);
}
}
return new_binlog_file;
}
/**
* Get master configuration store it
*
* @param router Current router instance
* @param curr_master Preallocated struct to fill
*/
static void
blr_master_get_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *curr_master)
{
SSL_LISTENER *server_ssl;
curr_master->port = router->service->dbref->server->port;
curr_master->host = MXS_STRDUP_A(router->service->dbref->server->name);
curr_master->pos = router->current_pos;
curr_master->safe_pos = router->binlog_position;
strcpy(curr_master->logfile, router->binlog_name); // Same size
curr_master->user = MXS_STRDUP_A(router->user);
curr_master->password = MXS_STRDUP_A(router->password);
curr_master->filestem = MXS_STRDUP_A(router->fileroot);
/* SSL options */
if (router->service->dbref->server->server_ssl)
{
server_ssl = router->service->dbref->server->server_ssl;
curr_master->ssl_enabled = router->ssl_enabled;
if (router->ssl_version)
{
curr_master->ssl_version = MXS_STRDUP_A(router->ssl_version);
}
if (server_ssl->ssl_key)
{
curr_master->ssl_key = MXS_STRDUP_A(server_ssl->ssl_key);
}
if (server_ssl->ssl_cert)
{
curr_master->ssl_cert = MXS_STRDUP_A(server_ssl->ssl_cert);
}
if (server_ssl->ssl_ca_cert)
{
curr_master->ssl_ca = MXS_STRDUP_A(server_ssl->ssl_ca_cert);
}
}
}
/**
* Free a master configuration struct
*
* @param master_cfg Saved master configuration to free
*/
static void
blr_master_free_config(MASTER_SERVER_CFG *master_cfg)
{
MXS_FREE(master_cfg->host);
MXS_FREE(master_cfg->user);
MXS_FREE(master_cfg->password);
MXS_FREE(master_cfg->filestem);
/* SSL options */
MXS_FREE(master_cfg->ssl_key);
MXS_FREE(master_cfg->ssl_cert);
MXS_FREE(master_cfg->ssl_ca);
MXS_FREE(master_cfg->ssl_version);
/* MariaDB 10 GTID */
MXS_FREE(master_cfg->use_mariadb10_gtid);
MXS_FREE(master_cfg);
}
/**
* Restore master configuration values for host and port
*
* @param router Current router instance
* @param prev_master Previous saved master configuration
*/
static void
blr_master_restore_config(ROUTER_INSTANCE *router,
MASTER_SERVER_CFG *prev_master)
{
server_update_address(router->service->dbref->server, prev_master->host);
server_update_port(router->service->dbref->server, prev_master->port);
router->ssl_enabled = prev_master->ssl_enabled;
if (prev_master->ssl_version)
{
MXS_FREE(router->ssl_version);
router->ssl_version = MXS_STRDUP_A(prev_master->ssl_version);
}
blr_master_free_config(prev_master);
}
/**
* Set all the master configuration fields to empty values
*
* @param router Current router instance
*/
static void
blr_master_set_empty_config(ROUTER_INSTANCE *router)
{
server_update_address(router->service->dbref->server, "none");
server_update_port(router->service->dbref->server, (unsigned short)3306);
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
strcpy(router->binlog_name, "");
strcpy(router->prevbinlog, "");
/* Set Empty master id */
router->orig_masterid = 0;
/* Set Default GTID domain */
router->mariadb10_gtid_domain = BLR_DEFAULT_GTID_DOMAIN_ID;
}
/**
* Restore all master configuration values
*
* @param router Current router instance
* @param prev_master Previous saved master configuration
*/
static void
blr_master_apply_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *prev_master)
{
server_update_address(router->service->dbref->server, prev_master->host);
server_update_port(router->service->dbref->server, prev_master->port);
router->current_pos = prev_master->pos;
router->binlog_position = prev_master->safe_pos;
router->current_safe_event = prev_master->safe_pos;
strcpy(router->binlog_name, prev_master->logfile);
if (router->user)
{
MXS_FREE(router->user);
router->user = MXS_STRDUP_A(prev_master->user);
}
if (router->password)
{
MXS_FREE(router->password);
router->password = MXS_STRDUP_A(prev_master->password);
}
if (router->fileroot)
{
MXS_FREE(router->fileroot);
router->fileroot = MXS_STRDUP_A(prev_master->filestem);
}
}
/**
* Change the replication user
*
* @param router Current router instance
* @param user The userto set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_user(ROUTER_INSTANCE *router, char *user)
{
if (user != NULL)
{
char *ptr;
char *end;
ptr = strchr(user, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = user;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
if (router->user)
{
MXS_FREE(router->user);
}
router->user = MXS_STRDUP_A(ptr);
MXS_INFO("%s: New MASTER_USER is [%s]",
router->service->name,
router->user);
return 1;
}
return 0;
}
/**
* Change the replication password
*
* @param router Current router instance
* @param password The password to set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_password(ROUTER_INSTANCE *router, char *password)
{
if (password != NULL)
{
char *ptr;
char *end;
ptr = strchr(password, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = password;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
if (router->password)
{
MXS_FREE(router->password);
}
router->password = MXS_STRDUP_A(ptr);
/* don't log new password */
return 1;
}
return 0;
}
/**
* Get next token
*
* Works exactly like strtok_t except that a delim character which appears
* anywhere within quotes is ignored. For instance, if delim is "," then
* a string like "MASTER_USER='maxscale_repl_user',MASTER_PASSWORD='a,a'"
* will be tokenized into the following two tokens:
*
* MASTER_USER='maxscale_repl_user'
* MASTER_PASSWORD='a,a'
*
* @see strtok_r
*/
static char* get_next_token(char *str, const char* delim, char **saveptr)
{
if (str)
{
*saveptr = str;
}
if (!*saveptr)
{
return NULL;
}
bool delim_found = true;
// Skip any delims in the beginning.
while (**saveptr && delim_found)
{
const char* d = delim;
while (*d)
{
if (*d == **saveptr)
{
break;
}
++d;
}
if (*d == 0)
{
delim_found = false;
}
else
{
++*saveptr;
}
}
if (!**saveptr)
{
return NULL;
}
delim_found = false;
char *token = *saveptr;
char *p = *saveptr;
char quote = 0;
while (*p && !delim_found)
{
switch (*p)
{
case '\'':
case '"':
case '`':
if (!quote)
{
quote = *p;
}
else if (quote == *p)
{
quote = 0;
}
break;
default:
if (!quote)
{
const char *d = delim;
while (*d && !delim_found)
{
if (*p == *d)
{
delim_found = true;
*p = 0;
}
else
{
++d;
}
}
}
}
++p;
}
if (*p == 0)
{
*saveptr = NULL;
}
else if (delim_found)
{
*saveptr = p;
delim_found = true;
while (**saveptr && delim_found)
{
const char *d = delim;
while (*d)
{
if (**saveptr == *d)
{
break;
}
else
{
++d;
}
}
if (*d == 0)
{
delim_found = false;
}
else
{
++*saveptr;
}
}
}
return token;
}
/**
* Parse a CHANGE MASTER TO SQL command
*
* @param input The command to be parsed
* @param error_string Pre-allocated string for error message,
* BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on failure
*/
static int
blr_parse_change_master_command(char *input,
char *error_string,
CHANGE_MASTER_OPTIONS *config)
{
char *sep = ",";
char *word, *brkb;
if ((word = get_next_token(input, sep, &brkb)) == NULL)
{
snprintf(error_string,
BINLOG_ERROR_MSG_LEN,
"Unable to parse query [%s]",
input);
return 1;
}
else
{
/* parse options key=val */
if (blr_handle_change_master_token(word,
error_string,
config))
{
return 1;
}
}
while ((word = get_next_token(NULL, sep, &brkb)) != NULL)
{
/* parse options key=val */
if (blr_handle_change_master_token(word,
error_string,
config))
{
return 1;
}
}
return 0;
}
/**
* Validate option and set the value for a change master option
*
* @param input Current option with value
* @param error pre-allocted string for error message,
* BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on error
*/
static int
blr_handle_change_master_token(char *input,
char *error,
CHANGE_MASTER_OPTIONS *config)
{
/* space+TAB+= */
char *sep = " \t=";
char *word, *brkb;
char *value = NULL;
char **option_field = NULL;
if ((word = get_next_token(input, sep, &brkb)) == NULL)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"error parsing %s",
brkb);
return 1;
}
else
{
if ((option_field = blr_validate_change_master_option(word,
config)) == NULL)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"option '%s' is not supported",
word);
return 1;
}
/* value must be freed after usage */
if ((value = blr_get_parsed_command_value(brkb)) == NULL)
{
snprintf(error,
BINLOG_ERROR_MSG_LEN,
"missing value for '%s'",
word);
return 1;
}
else
{
*option_field = value;
}
}
return 0;
}
/**
* Get value of a change master option
*
* @param input Current option with value
* @return The new allocated option value or NULL
*/
static char *
blr_get_parsed_command_value(char *input)
{
char *ret = NULL;
if (input && *input)
{
char value[strlen(input) + 1];
strcpy(value, input);
/* space+TAB+= */
char *sep = " \t=";
char *word;
if ((word = get_next_token(NULL, sep, &input)) != NULL)
{
/* remove trailing spaces */
char *ptr = value + strlen(value) - 1;
while (ptr > value && isspace(*ptr))
{
*ptr-- = 0;
}
ret = MXS_STRDUP_A(strstr(value, word));
}
}
return ret;
}
/**
* Validate a change master option
*
* @param option The option to check
* @param config The option structure
* @return A pointer to the field in the option strucure or NULL
*/
static char
**blr_validate_change_master_option(char *option,
CHANGE_MASTER_OPTIONS *config)
{
if (strcasecmp(option, "master_host") == 0)
{
return &config->host;
}
else if (strcasecmp(option, "master_port") == 0)
{
return &config->port;
}
else if (strcasecmp(option, "master_log_file") == 0)
{
return &config->binlog_file;
}
else if (strcasecmp(option, "master_log_pos") == 0)
{
return &config->binlog_pos;
}
else if (strcasecmp(option, "master_user") == 0)
{
return &config->user;
}
else if (strcasecmp(option, "master_password") == 0)
{
return &config->password;
}
else if (strcasecmp(option, "master_ssl") == 0)
{
return &config->ssl_enabled;
}
else if (strcasecmp(option, "master_ssl_key") == 0)
{
return &config->ssl_key;
}
else if (strcasecmp(option, "master_ssl_cert") == 0)
{
return &config->ssl_cert;
}
else if (strcasecmp(option, "master_ssl_ca") == 0)
{
return &config->ssl_ca;
}
else if (strcasecmp(option, "master_ssl_version") == 0 ||
strcasecmp(option, "master_tls_version") == 0)
{
return &config->ssl_version;
}
else if (strcasecmp(option, "master_use_gtid") == 0)
{
return &config->use_mariadb10_gtid;
}
else
{
return NULL;
}
}
/**
* Free parsed master options struct pointers
*
* @param options Parsed option struct
*/
static void
blr_master_free_parsed_options(CHANGE_MASTER_OPTIONS *options)
{
MXS_FREE(options->host);
options->host = NULL;
MXS_FREE(options->port);
options->port = NULL;
MXS_FREE(options->user);
options->user = NULL;
MXS_FREE(options->password);
options->password = NULL;
MXS_FREE(options->binlog_file);
options->binlog_file = NULL;
MXS_FREE(options->binlog_pos);
options->binlog_pos = NULL;
/* SSL options */
MXS_FREE(options->ssl_enabled);
options->ssl_enabled = NULL;
MXS_FREE(options->ssl_key);
options->ssl_key = NULL;
MXS_FREE(options->ssl_ca);
options->ssl_ca = NULL;
MXS_FREE(options->ssl_cert);
options->ssl_cert = NULL;
MXS_FREE(options->ssl_version);
options->ssl_version = NULL;
}
/**
* Send a MySQL protocol response for selected variable
*
* @param router The binlog router instance
* @param slave The connected slave server
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_var_value(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len;
if (value == NULL)
{
return blr_slave_send_ok(router, slave);
}
vers_len = strlen(value);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router,
slave,
variable,
column_type,
vers_len,
2);
blr_slave_send_eof(router, slave, 3);
len = MYSQL_HEADER_LEN + (1 + vers_len);
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, value, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send the response to the SQL command "SHOW VARIABLES LIKE 'xxx'
*
* @param router The binlog router instance
* @param slave The connected slave server
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_variable(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len, seqno = 2;
char *p = MXS_STRDUP_A(variable);
int var_len;
char *old_ptr = p;
if (value == NULL)
{
return 0;
}
/* Remove heading and trailing "'" */
if (*p == '\'')
{
p++;
}
if (p[strlen(p) - 1] == '\'')
{
p[strlen(p) - 1] = '\0';
}
var_len = strlen(p);
/* force lowercase */
for (int i = 0; i < var_len; i++)
{
p[i] = tolower(p[i]);
}
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef_with_info_schema(router,
slave,
"Variable_name",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_columndef_with_info_schema(router,
slave,
"Value",
column_type,
40,
seqno++);
blr_slave_send_eof(router, slave, seqno++);
vers_len = strlen(value);
len = MYSQL_HEADER_LEN + (1 + vers_len) + (1 + var_len);
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 2 + var_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = var_len; // Length of result string
memcpy((char *)ptr, p, var_len); // Result string with var name
ptr += var_len;
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, value, vers_len); // Result string with var value
/* ptr += vers_len; Not required unless more data is added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
MXS_FREE(old_ptr);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the column definition packet for a variable
* in a response packet sequence.
*
* It adds information_schema and variables and variable_name
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef_with_info_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
int info_len = strlen("information_schema");
int virtual_table_name_len = strlen("VARIABLES");
int table_name_len = strlen("VARIABLES");
int column_name_len = strlen(name);
int orig_column_name_len = strlen("VARIABLE_NAME");
int packet_data_len = 22 + strlen(name) + info_len +
virtual_table_name_len + table_name_len +
orig_column_name_len;
if ((pkt = gwbuf_alloc(MYSQL_HEADER_LEN + packet_data_len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, packet_data_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = info_len; // Schema name length
strcpy((char *)ptr, "information_schema");
ptr += info_len;
*ptr++ = virtual_table_name_len; // virtual table name length
strcpy((char *)ptr, "VARIABLES");
ptr += virtual_table_name_len;
*ptr++ = table_name_len; // Table name length
strcpy((char *)ptr, "VARIABLES");
ptr += table_name_len;
*ptr++ = column_name_len; // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = orig_column_name_len; // Orginal column name
strcpy((char *)ptr, "VARIABLE_NAME");
ptr += orig_column_name_len;
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Interface for testing blr_parse_change_master_command()
*
* @param input The command to be parsed
* @param error_string Pre-allocated string for error message,
* BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on failure
*/
int
blr_test_parse_change_master_command(char *input,
char *error_string,
CHANGE_MASTER_OPTIONS *config)
{
return blr_parse_change_master_command(input,
error_string,
config);
}
/*
* Interface for testing set new master binlog file
*
*
* @param router Current router instance
* @param filename Binlog file name
* @param error The error msg for command,
* pre-allocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return New binlog file or NULL on error
*/
char *
blr_test_set_master_logfile(ROUTER_INSTANCE *router,
char *filename,
char *error)
{
return blr_set_master_logfile(router, filename, error);
}
/**
* Interface for testing a 'change master' operation
*
* @param router The router instance
* @param command The change master SQL command
* @param error The error message,
* preallocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 0 on success, 1 on success with new binlog, -1 on failure
*/
int
blr_test_handle_change_master(ROUTER_INSTANCE* router,
char *command,
char *error)
{
return blr_handle_change_master(router, command, error);
}
/**
* Handle the response to the SQL command
* "SHOW GLOBAL VARIABLES LIKE or SHOW VARIABLES LIKE
*
* @param router The binlog router instance
* @param slave The connected slave server
* @param stmt The SQL statement
* @return Non-zero if the variable is handled,
* 0 if variable is unknown, -1 for syntax error
*/
static int
blr_slave_handle_variables(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *stmt)
{
char *brkb;
char *word;
/* SPACE,TAB,= */
char *sep = " ,=";
if ((word = strtok_r(stmt, sep, &brkb)) == NULL)
{
return -1;
}
else if (strcasecmp(word, "LIKE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Missing LIKE clause in SHOW [GLOBAL] VARIABLES.",
router->service->name);
return -1;
}
else if (strcasecmp(word, "'SERVER_ID'") == 0)
{
if (router->set_master_server_id)
{
char server_id[40];
sprintf(server_id, "%d", router->masterid);
return blr_slave_send_variable(router,
slave,
"'SERVER_ID'",
server_id,
BLR_TYPE_INT);
}
else
{
return blr_slave_replay(router,
slave,
router->saved_master.server_id);
}
}
else if (strcasecmp(word, "'SERVER_UUID'") == 0)
{
if (router->set_master_uuid)
{
return blr_slave_send_variable(router,
slave,
"'SERVER_UUID'",
router->master_uuid,
BLR_TYPE_STRING);
}
else
{
return blr_slave_replay(router,
slave,
router->saved_master.uuid);
}
}
else if (strcasecmp(word, "'MAXSCALE%'") == 0)
{
return blr_slave_send_maxscale_variables(router, slave);
}
else
{
return 0;
}
}
else
{
return -1;
}
}
/**
* Send a MySQL OK packet with a warning flag to the slave backend
* and set the warning message in slave structure
* The message should be retrieved by SHOW WARNINGS command
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending the response
*
* @return The write call result: non-zero if write was successful
*/
static int
blr_slave_send_warning_message(ROUTER_INSTANCE* router,
ROUTER_SLAVE* slave,
char *message)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(11)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 7; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
if (strlen(message) == 0)
{
*ptr++ = 0;
*ptr++ = 0;
}
else
{
*ptr++ = 1; /* warning byte set to 1 */
*ptr++ = 0;
}
/* set the new warning in this slave connection */
if (slave->warning_msg)
{
MXS_FREE(slave->warning_msg);
}
slave->warning_msg = MXS_STRDUP_A(message);
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* Send a MySQL SHOW WARNINGS packet with a message
* that has been stored in slave struct.
*
* If there is no warning message an OK packet is sent
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending the response
*
* @return The write call result: non-zero if write was successful
*/
static int
blr_slave_show_warnings(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
GWBUF *pkt;
uint8_t *ptr;
int len;
int msg_len = 0;
int code_len = 0;
int level_len = 0;
/* check whether a warning message is available */
if (slave->warning_msg)
{
char *level = "Warning";
char *msg_ptr;
char err_code[16 + 1] = "";
msg_ptr = strchr(slave->warning_msg, ':');
if (msg_ptr)
{
size_t len = (msg_ptr - slave->warning_msg > 16) ?
16 :
(msg_ptr - slave->warning_msg);
memcpy(err_code, slave->warning_msg, len);
err_code[len] = 0;
code_len = strlen(err_code);
msg_ptr++;
}
else
{
msg_ptr = slave->warning_msg;
}
msg_len = strlen(msg_ptr);
level_len = strlen(level);
blr_slave_send_fieldcount(router, slave, 3); // 3 columns
blr_slave_send_columndef(router, slave, "Level", BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Code", BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Message", BLR_TYPE_STRING, 80, 4);
blr_slave_send_eof(router, slave, 5);
len = MYSQL_HEADER_LEN + (1 + level_len) + (1 + code_len) + (1 + msg_len);
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return blr_slave_send_ok(router, slave);
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - MYSQL_HEADER_LEN, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x06; // Sequence number in response
*ptr++ = level_len; // Length of result string
memcpy((char *)ptr, level, level_len); // Result string
ptr += level_len;
*ptr++ = code_len; // Length of result string
if (code_len)
{
memcpy((char *)ptr, err_code, code_len); // Result string
ptr += code_len;
}
*ptr++ = msg_len; // Length of result string
if (msg_len)
{
memcpy((char *)ptr, msg_ptr, msg_len); // Result string
/* ptr += msg_len; Not required unless more data is added */
}
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
return blr_slave_send_eof(router, slave, 7);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
/**
* Handle the response to the SQL command
* "SHOW [GLOBAL] STATUS LIKE or SHOW STATUS LIKE
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param stmt The SQL statement
* @return Non-zero if the variable is handled,
* 0 if variable is unknown, -1 for syntax errors.
*/
static int
blr_slave_handle_status_variables(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *stmt)
{
char *brkb = NULL;
char *word = NULL;
/* SPACE,TAB,= */
char *sep = " ,=";
if ((word = strtok_r(stmt, sep, &brkb)) == NULL)
{
return -1;
}
else if (strcasecmp(word, "LIKE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Missing LIKE clause in SHOW [GLOBAL] STATUS.",
router->service->name);
return -1;
}
else if (strcasecmp(word, "'Uptime'") == 0)
{
char uptime[41] = "";
snprintf(uptime, 40, "%d", maxscale_uptime());
return blr_slave_send_status_variable(router,
slave,
"Uptime",
uptime,
BLR_TYPE_INT);
}
else
{
return 0;
}
}
else
{
return -1;
}
}
/**
* Send the response to the SQL command "SHOW [GLOBAL] STATUS LIKE 'xxx'
*
* @param router The binlog router instance
* @param slave The connected slave server
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_status_variable(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len, seqno = 2;
char *p = MXS_STRDUP_A(variable);
int var_len;
char *old_ptr = p;
/* Remove heading and trailing "'" */
if (*p == '\'')
{
p++;
}
if (p[strlen(p) - 1] == '\'')
{
p[strlen(p) - 1] = '\0';
}
var_len = strlen(p);
/* force lowercase */
for (int i = 0; i < var_len; i++)
{
p[i] = tolower(p[i]);
}
/* First char is uppercase */
p[0] = toupper(p[0]);
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef_with_status_schema(router,
slave,
"Variable_name",
BLR_TYPE_STRING,
40,
seqno++);
blr_slave_send_columndef_with_status_schema(router,
slave,
"Value",
column_type,
40,
seqno++);
blr_slave_send_eof(router, slave, seqno++);
vers_len = strlen(value);
len = MYSQL_HEADER_LEN + (1 + vers_len) + (1 + var_len);
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, vers_len + 2 + var_len, 24);
ptr += 3;
// Sequence number in response
*ptr++ = seqno++;
// Length of result string
*ptr++ = var_len;
// Result string with var name
memcpy((char *)ptr, p, var_len);
ptr += var_len;
// Length of result string
*ptr++ = vers_len;
// Result string with var value
memcpy((char *)ptr, value, vers_len);
/* ptr += vers_len; Not required unless more data is added */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
MXS_FREE(old_ptr);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the column definition packet for a STATUS variable
* in a response packet sequence.
*
* It adds information_schema.STATUS and variables and variable_name
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef_with_status_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
int info_len = strlen("information_schema");
int virtual_table_name_len = strlen("STATUS");
int table_name_len = strlen("STATUS");
int column_name_len = strlen(name);
int orig_column_name_len = strlen("VARIABLE_NAME");
int packet_data_len = 0;
char *ptr_name_start = name;
if (strcasecmp(ptr_name_start, "value") == 0)
{
orig_column_name_len = strlen("VARIABLE_VALUE");
}
packet_data_len = 22 + strlen(name) + info_len + virtual_table_name_len +
table_name_len + orig_column_name_len;
if ((pkt = gwbuf_alloc(MYSQL_HEADER_LEN + packet_data_len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, packet_data_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = info_len; // Schema name length
strcpy((char *)ptr, "information_schema");
ptr += info_len;
*ptr++ = virtual_table_name_len; // virtual table name length
strcpy((char *)ptr, "STATUS");
ptr += virtual_table_name_len;
*ptr++ = table_name_len; // Table name length
strcpy((char *)ptr, "STATUS");
ptr += table_name_len;
*ptr++ = column_name_len; // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = orig_column_name_len; // Orginal column name
if (strcasecmp(ptr_name_start, "value") == 0)
{
strcpy((char *)ptr, "VARIABLE_VALUE");
}
else
{
strcpy((char *)ptr, "VARIABLE_NAME");
}
ptr += orig_column_name_len;
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/**
* The heartbeat check function called
* from the housekeeper for registered slaves.
*
* @param router Current router instance
*/
static void
blr_send_slave_heartbeat(void *inst)
{
ROUTER_SLAVE *sptr = NULL;
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *) inst;
time_t t_now = time(0);
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
/* skip servers with state = 0 */
if ( (sptr->state == BLRS_DUMPING) && (sptr->heartbeat > 0) &&
((t_now + 1 - sptr->lastReply) >= sptr->heartbeat) )
{
MXS_NOTICE("Sending Heartbeat to slave server-id %d. "
"Heartbeat interval is %d, last event time is %lu",
sptr->serverid, sptr->heartbeat,
(unsigned long)sptr->lastReply);
blr_slave_send_heartbeat(router, sptr);
sptr->lastReply = t_now;
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
}
/**
* Create and send an hearbeat packet to be sent to a registered slave server
*
* @param router The current route rinstance
* @param slave The current slave connection
* @return Number of bytes sent or 0 in case of failure
*/
static int
blr_slave_send_heartbeat(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
REP_HEADER hdr;
GWBUF *h_event;
uint8_t *ptr;
int len = BINLOG_EVENT_HDR_LEN;
uint32_t chksum;
int filename_len = strlen(slave->binlogfile);
/* Add CRC32 4 bytes */
if (!slave->nocrc)
{
len += BINLOG_EVENT_CRC_SIZE;
}
/* add binlogname to data content len */
len += filename_len;
/**
* Alloc buffer for network binlog stream:
*
* 4 bytes header (3 for pkt len + 1 seq.no)
* 1 byte for Ok / ERR
* n bytes data content
*
* Total = 5 bytes + len
*/
if ((h_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL)
{
return 0;
}
/* The OK/Err byte is part of payload */
hdr.payload_len = len + 1;
/* Add sequence no */
hdr.seqno = slave->seqno++;
/* Add OK */
hdr.ok = 0;
/* Add timestamp: 0 */
hdr.timestamp = 0L;
/* Set Event Type */
hdr.event_type = HEARTBEAT_EVENT;
/* Add master server id */
hdr.serverid = router->masterid;
/* Add event size */
hdr.event_size = len;
/* Add Next Pos */
hdr.next_pos = slave->binlog_pos;
/* Add Artificial flags */
hdr.flags = 0x20;
/* point just after the header */
ptr = blr_build_header(h_event, &hdr);
/* Copy binlog name */
memcpy(ptr, slave->binlogfile, filename_len);
ptr += filename_len;
/* Add the CRC32 */
if (!slave->nocrc)
{
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum,
GWBUF_DATA(h_event) + MYSQL_HEADER_LEN + 1,
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
encode_value(ptr, chksum, 32);
}
/* Write the packet */
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event);
}
/**
* Skip the ' char and return pointer to new start position.
* The last ' char is removed.
*
* @param input The input string
* @return Position after first '
*/
char *
blr_escape_config_string(char *input)
{
char *ptr;
char *end;
ptr = strchr(input, '\'');
if (!ptr)
{
return input;
}
else
{
if (ptr + 1)
{
ptr++;
}
else
{
*ptr = '\0';
}
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
return ptr;
}
/**
* Change the replication SSL options
*
* @param router Current router instance
* @param config The current config
* @param error_message Pre-allocated string for error message is
* BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 1 for applied change,
* 0 no changes and -1 for errors
*/
static int
blr_set_master_ssl(ROUTER_INSTANCE *router,
CHANGE_MASTER_OPTIONS config,
char *error_message)
{
SSL_LISTENER *server_ssl = NULL;
int updated = 0;
if (config.ssl_enabled)
{
router->ssl_enabled = atoi(config.ssl_enabled);
updated++;
}
if (router->ssl_enabled == false)
{
/* Free SSL struct */
blr_free_ssl_data(router);
}
else
{
/* Check for existing SSL struct */
if (router->service->dbref->server->server_ssl)
{
server_ssl = router->service->dbref->server->server_ssl;
server_ssl->ssl_init_done = false;
}
else
{
/* Allocate SSL struct for backend connection */
if ((server_ssl = MXS_CALLOC(1, sizeof(SSL_LISTENER))) == NULL)
{
router->ssl_enabled = false;
/* Report back the error */
snprintf(error_message, BINLOG_ERROR_MSG_LEN,
"CHANGE MASTER TO: Error allocating memory for SSL struct"
" in blr_set_master_ssl");
return -1;
}
/* Set some SSL defaults */
server_ssl->ssl_init_done = false;
server_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX;
server_ssl->ssl_cert_verify_depth = 9;
/* Set the pointer */
router->service->dbref->server->server_ssl = server_ssl;
}
}
/* Update options in router fields and in server_ssl struct, if present */
if (config.ssl_key)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_key);
server_ssl->ssl_key = MXS_STRDUP_A(blr_escape_config_string(config.ssl_key));
}
MXS_FREE(router->ssl_key);
router->ssl_key = MXS_STRDUP_A(blr_escape_config_string(config.ssl_key));
updated++;
}
if (config.ssl_ca)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_ca_cert);
server_ssl->ssl_ca_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_ca));
}
MXS_FREE(router->ssl_ca);
router->ssl_ca = MXS_STRDUP_A(blr_escape_config_string(config.ssl_ca));
updated++;
}
if (config.ssl_cert)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_cert);
server_ssl->ssl_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_cert));
}
MXS_FREE(router->ssl_cert);
router->ssl_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_cert));
updated++;
}
if (config.ssl_version && server_ssl)
{
char *ssl_version = blr_escape_config_string(config.ssl_version);
if (ssl_version && strlen(ssl_version))
{
if (listener_set_ssl_version(server_ssl, ssl_version) != 0)
{
/* Report back the error */
snprintf(error_message, BINLOG_ERROR_MSG_LEN,
"Unknown parameter value for 'ssl_version': %s",
ssl_version);
return -1;
}
/* Set provided ssl_version in router SSL cfg anyway */
MXS_FREE(router->ssl_version);
router->ssl_version = MXS_STRDUP_A(ssl_version);
updated++;
}
}
if (updated)
{
return 1;
}
else
{
return 0;
}
}
/**
* Notify a waiting slave that new events are stored in binlog file
*
* @param slave The current connected slave
* @return True if slave has been notified
*
*/
bool blr_notify_waiting_slave(ROUTER_SLAVE *slave)
{
bool ret = false;
spinlock_acquire(&slave->catch_lock);
if (slave->cstate & CS_WAIT_DATA)
{
ret = true;
/* Add fake event that will call the blr_slave_callback routine */
poll_fake_write_event(slave->dcb);
slave->cstate &= ~CS_WAIT_DATA;
}
spinlock_release(&slave->catch_lock);
return ret;
}
/**
* Read START_ENCRYPTION_EVENT, after FDE
*
* @param router The router instance
* @param slave The connected slave server
* @param fde_end_pos The position of START_ENCRYPTION_EVENT, after FDE
* @return Non zero on success
*/
static int
blr_slave_read_ste(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
uint32_t fde_end_pos)
{
REP_HEADER hdr;
GWBUF *record, *head;
uint8_t *ptr;
uint32_t chksum;
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
MARIADB_GTID_INFO *f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
&slave->f_info :
NULL;
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
memset(&hdr, 0, BINLOG_EVENT_HDR_LEN);
BLFILE *file;
if ((file = blr_open_binlog(router,
slave->binlogfile,
f_tree)) == NULL)
{
return 0;
}
/* Start Encryption Event is not encrypted, we pass NULL to last param */
if ((record = blr_read_binlog(router,
file,
fde_end_pos,
&hdr,
err_msg,
NULL)) == NULL)
{
if (hdr.ok != SLAVE_POS_READ_OK)
{
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', "
"blr_read_binlog failure: %s",
slave->dcb->remote,
dcb_get_port(slave->dcb),
slave->serverid,
slave->binlogfile,
err_msg);
}
blr_close_binlog(router, file);
return 0;
}
blr_close_binlog(router, file);
/* check for START_ENCRYPTION_EVENT */
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT)
{
uint8_t *record_ptr = GWBUF_DATA(record);
SLAVE_ENCRYPTION_CTX *new_encryption_ctx;
new_encryption_ctx = MXS_CALLOC(1, sizeof(SLAVE_ENCRYPTION_CTX));
if (!new_encryption_ctx)
{
return 0;
}
record_ptr += BINLOG_EVENT_HDR_LEN;
// Set schema, 1 Byte
new_encryption_ctx->binlog_crypto_scheme = record_ptr[0];
// Set key version
memcpy(&new_encryption_ctx->binlog_key_version,
record_ptr + 1,
BLRM_KEY_VERSION_LENGTH);
// Set nonce
memcpy(new_encryption_ctx->nonce,
record_ptr + 1 + BLRM_KEY_VERSION_LENGTH,
BLRM_NONCE_LENGTH);
/* Set the pos of first encrypted event */
new_encryption_ctx->first_enc_event_pos = fde_end_pos + hdr.event_size;
spinlock_acquire(&slave->catch_lock);
SLAVE_ENCRYPTION_CTX *old_encryption_ctx = slave->encryption_ctx;
/* Set the new encryption ctx into slave */
slave->encryption_ctx = new_encryption_ctx;
spinlock_release(&slave->catch_lock);
/* Free previous encryption ctx */
MXS_FREE(old_encryption_ctx);
MXS_INFO("Start Encryption event found. Binlog %s is encrypted. "
"First event at %lu",
slave->binlogfile,
(unsigned long)fde_end_pos + hdr.event_size);
/**
* Note: if the requested pos is equal to START_ENCRYPTION_EVENT pos
* the event will be skipped by blr_read_binlog() routine
*/
return 1;
}
return 0;
}
/**
* Handle received SELECT statements from clients
*
* if a SELECT statement is one of suported one
* a proper reply to the connected client is done
*
* @param router Router instance
* @param slave Connected client/slave server
* @param select_stmt The SELECT statement
* @return True for handled queries, False otherwise
*/
static bool blr_handle_simple_select_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *select_stmt)
{
char *word;
char *brkb;
char *sep = " \t,=";
if ((word = strtok_r(select_stmt, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete select query.", router->service->name);
return false;
}
else if (strcasecmp(word, "UNIX_TIMESTAMP()") == 0)
{
blr_slave_send_timestamp(router, slave);
return true;
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0 ||
strcasecmp(word, "@@global.binlog_checksum") == 0)
{
blr_slave_replay(router, slave, router->saved_master.chksum2);
return true;
}
else if (strcasecmp(word, "@@GLOBAL.GTID_MODE") == 0)
{
blr_slave_replay(router, slave, router->saved_master.gtid_mode);
return true;
}
else if (strcasecmp(word, "1") == 0)
{
blr_slave_replay(router, slave, router->saved_master.select1);
return true;
}
else if (strcasecmp(word, "VERSION()") == 0)
{
if (router->set_master_version)
{
blr_slave_send_var_value(router,
slave,
"VERSION()",
router->set_master_version,
BLR_TYPE_STRING);
return true;
}
else
{
blr_slave_replay(router, slave, router->saved_master.selectver);
return true;
}
}
else if (strcasecmp(word, "USER()") == 0)
{
/* Return user@host */
char user_host[MYSQL_USER_MAXLEN + 1 + MYSQL_HOST_MAXLEN + 1] = "";
snprintf(user_host, sizeof(user_host),
"%s@%s", slave->dcb->user, slave->dcb->remote);
blr_slave_send_var_value(router,
slave,
"USER()",
user_host,
BLR_TYPE_STRING);
return true;
}
else if (strcasecmp(word, "@@version") == 0)
{
if (router->set_master_version)
{
blr_slave_send_var_value(router,
slave,
"@@version",
router->set_master_version,
BLR_TYPE_STRING);
return true;
}
else
{
char *version = blr_extract_column(router->saved_master.selectver,
1);
blr_slave_send_var_value(router,
slave,
"@@version",
version == NULL ? "" : version,
BLR_TYPE_STRING);
return true;
}
}
else if (strcasecmp(word, "@@version_comment") == 0)
{
if (!router->saved_master.selectvercom)
/**
* This allows mysql client to get in when
* @@version_comment is not available
*/
{
blr_slave_send_ok(router, slave);
return true;
}
else
{
blr_slave_replay(router,
slave,
router->saved_master.selectvercom);
return true;
}
}
else if (strcasecmp(word, "@@hostname") == 0)
{
if (router->set_master_hostname)
{
blr_slave_send_var_value(router,
slave,
"@@hostname",
router->set_master_hostname,
BLR_TYPE_STRING);
return true;
}
else
{
blr_slave_replay(router,
slave,
router->saved_master.selecthostname);
return true;
}
}
else if ((strcasecmp(word, "@@server_uuid") == 0) ||
(strcasecmp(word, "@@global.server_uuid") == 0))
{
/* to ensure we match the case in query and response */
char heading[40];
strcpy(heading, word);
if (router->set_master_uuid)
{
blr_slave_send_var_value(router,
slave,
heading,
router->master_uuid,
BLR_TYPE_STRING);
return true;
}
else
{
char *master_uuid = blr_extract_column(router->saved_master.uuid,
2);
blr_slave_send_var_value(router,
slave,
heading,
master_uuid == NULL ? "" : master_uuid,
BLR_TYPE_STRING);
MXS_FREE(master_uuid);
return true;
}
}
else if (strcasecmp(word, "@@max_allowed_packet") == 0)
{
blr_slave_replay(router, slave, router->saved_master.map);
return true;
}
else if (strcasecmp(word, "@@maxscale_version") == 0)
{
blr_slave_send_maxscale_version(router, slave);
return true;
}
else if ((strcasecmp(word, "@@server_id") == 0) ||
(strcasecmp(word, "@@global.server_id") == 0))
{
char server_id[40];
/* to ensure we match the case in query and response */
char heading[40];
sprintf(server_id, "%d", router->masterid);
strcpy(heading, word);
if (strcasecmp(brkb, "@@read_only") == 0 ||
strcasecmp(brkb, "@@global.read_only") == 0)
{
blr_slave_send_id_ro(router, slave);
return true;
}
blr_slave_send_var_value(router,
slave,
heading,
server_id,
BLR_TYPE_INT);
return true;
}
else if ((strcasecmp(word, "@@gtid_current_pos") == 0) ||
(strcasecmp(word, "@@global.gtid_current_pos") == 0))
{
char heading[40];
char mariadb_gtid[GTID_MAX_LEN + 1];
mariadb_gtid[0] = '\0';
strcpy(heading, word);
if (router->mariadb10_compat &&
router->mariadb10_gtid)
{
spinlock_acquire(&router->binlog_lock);
strcpy(mariadb_gtid, router->last_mariadb_gtid);
spinlock_release(&router->binlog_lock);
}
blr_slave_send_var_value(router,
slave,
heading,
mariadb_gtid,
BLR_TYPE_INT);
return true;
}
else if (strcasecmp(word, "@@GLOBAL.gtid_domain_id") == 0)
{
/* If not mariadb10 mastergtid an error message will be returned */
if (slave->mariadb10_compat &&
router->mariadb10_gtid)
{
char heading[40];
char gtid_domain[40];
sprintf(gtid_domain,
"%" PRIu32 "",
router->mariadb10_gtid_domain);
strcpy(heading, word);
blr_slave_send_var_value(router,
slave,
heading,
gtid_domain,
BLR_TYPE_INT);
return true;
}
}
return false;
}
/**
* Build and send a Fake Rotate event to the new client
*
* @param router The router instance
* @param slave The new connected client
* @return Non-zero if the rotate was sent
*/
static int blr_send_connect_fake_rotate(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave)
{
/* Build Fake Rotate Event */
GWBUF *r_event = blr_build_fake_rotate_event(slave,
slave->binlog_pos,
slave->binlogfile,
router->masterid);
/* Send Fake Rotate Event or return 0*/
return r_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, r_event) : 0;
}
/**
* Build a fake rotate event
*
* @param slave The current connected client
* @param pos The position to set in the event
* @param filename The filename to set in the event
* @param serverid The serverid to set in the event
* @return A GWBUF with the binlog event or NULL
*/
static GWBUF *blr_build_fake_rotate_event(ROUTER_SLAVE *slave,
unsigned long pos,
const char *filename,
unsigned long serverid)
{
GWBUF *r_event;
uint8_t *ptr;
int len;
int flen;
REP_HEADER hdr;
uint32_t chksum;
flen = strlen(filename);
/* Event size: header + 8 bytes pos + filename */
len = BINLOG_EVENT_HDR_LEN + 8 + flen;
/* Add CRC32 bytes if needed */
len += slave->nocrc ? 0 : BINLOG_EVENT_CRC_SIZE;
/* Allocate space for packet header, status and data */
if ((r_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL)
{
return NULL;
}
/* Add 1 byte to paylod for status indicator */
hdr.payload_len = len + 1;
/* Add sequence and increment it */
hdr.seqno = slave->seqno++;
/* Set status indicator byte to OK */
hdr.ok = 0;
/* No timestamp is required */
hdr.timestamp = 0L;
/* Rotate Event Type */
hdr.event_type = ROTATE_EVENT;
hdr.serverid = serverid;
hdr.event_size = len;
/* Next pos is not needed */
hdr.next_pos = 0;
/* Artificial Event Flag */
hdr.flags = 0x20;
/* Add replication hdr to resp */
ptr = blr_build_header(r_event, &hdr);
/* Add 8 bytes pos */
encode_value(ptr, pos, 64);
ptr += 8;
/* Add binlog filename, no trailing 0 */
memcpy(ptr, filename, flen);
ptr += flen;
/* Now add the CRC to the fake binlog rotate event */
if (!slave->nocrc)
{
/*
* First checksum of an empty buffer
* then the checksum of the event portion of the message:
* we do not include the len, seq number and ok byte that are part of
* first 5 bytes of the message.
* We also do not include the 4 byte checksum itself.
*/
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum,
GWBUF_DATA(r_event) + MYSQL_HEADER_LEN + 1,
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
encode_value(ptr, chksum, 32);
}
return r_event;
}
/**
* Look for a MariaDB GTID in the gtid maps database
*
* The caller specifies the position from COM_BINLOG_DUMP
* packet and if a filename is present or not in the request.
*
* Default position is 4, default file is router->binlog_file.
*
* If req_file is false then the file to read data from
* could be either router->binlog_file or the file the GTID
* belongs to.
*
* Note: rmpty GTID means send data from router->binlog_file pos 4.
*
* @param router The router instance
* @param slave The current slave server connected
* @param req_file Using binlog filename or not
* @param req_pos The requested file pos
* @return False if GTID is not found and slave
* is connectig with gtid_strict_mode=1,
* other errors.
* True otherwise.
*/
static bool blr_slave_gtid_request(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
bool req_file,
unsigned long req_pos)
{
MARIADB_GTID_INFO f_gtid = {};
uint32_t router_pos;
char router_curr_file[BINLOG_FNAMELEN + 1];
char last_gtid[GTID_MAX_LEN + 1];
spinlock_acquire(&router->binlog_lock);
// Set gtid as current router gtid
strcpy(last_gtid, router->last_mariadb_gtid);
// Set file as router current file
strcpy(router_curr_file, router->binlog_name);
// Set safe postion of current ruter file
router_pos = router->binlog_position;
// Set domain_id, server_id in case of empty/not found GTID
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
{
f_gtid.gtid_elms.domain_id = router->mariadb10_gtid_domain;
f_gtid.gtid_elms.server_id = router->orig_masterid;
}
spinlock_release(&router->binlog_lock);
MXS_INFO("Slave %lu is registering with MariaDB GTID '%s'",
(unsigned long)slave->serverid,
slave->mariadb_gtid);
if (!slave->mariadb_gtid[0])
{
/**
* Empty GTID:
* Sending data from the router current file and pos 4
*/
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
// Add file prefix
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
{
sprintf(t_prefix,
"%" PRIu32 "/%" PRIu32 "/",
f_gtid.gtid_elms.domain_id,
f_gtid.gtid_elms.server_id);
}
strcpy(slave->binlogfile, router_curr_file);
slave->binlog_pos = 4;
// TODO: Add prefix
MXS_INFO("Slave %d is registering with empty GTID:"
" sending events from current binlog file %s%s,"
" pos %" PRIu32 "",
slave->serverid,
t_prefix,
slave->binlogfile,
slave->binlog_pos);
/* Add GTID details to slave struct */
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
return true;
}
else
{
char dbpath[PATH_MAX + 1];
snprintf(dbpath, sizeof(dbpath), "/%s/%s",
router->binlogdir, GTID_MAPS_DB);
/* Result set init */
f_gtid.gtid = NULL;
/* Open GTID maps read-only database */
if (sqlite3_open_v2(dbpath,
&slave->gtid_maps,
SQLITE_OPEN_READONLY,
NULL) != SQLITE_OK)
{
MXS_ERROR("Slave %lu: failed to open GTID maps db '%s': %s",
(unsigned long)slave->serverid,
dbpath,
sqlite3_errmsg(slave->gtid_maps));
slave->gtid_maps = NULL;
return false;
}
else
{
/* Fetch the GTID from the maps storage */
blr_fetch_mariadb_gtid(slave, slave->mariadb_gtid, &f_gtid);
/* Close GTID maps database */
sqlite3_close_v2(slave->gtid_maps);
slave->gtid_maps = NULL;
}
/* Requested GTID Not Found */
if (!f_gtid.gtid)
{
MXS_WARNING("Requested MariaDB GTID '%s' by server %lu"
" has not been found",
slave->mariadb_gtid,
(unsigned long)slave->serverid);
/* Check strict mode */
if (slave->gtid_strict_mode)
{
strcpy(slave->binlogfile, "");
slave->binlog_pos = 0;
blr_send_custom_error(slave->dcb,
slave->seqno + 1,
0,
"connecting slave requested to start"
" from non existent GTID.",
"HY000",
BINLOG_FATAL_ERROR_READING);
return false;
}
else
{
/* No strict mode: */
// - 1 -Set request GTID as current master one
MXS_FREE(slave->mariadb_gtid);
slave->mariadb_gtid = MXS_STRDUP_A(last_gtid);
// - 2 - Use current router file and position
strcpy(slave->binlogfile, router_curr_file);
slave->binlog_pos = router_pos;
// - 3 Set GTID details for filename
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
{
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
}
}
}
else
{
/* GTID has been found */
MXS_INFO("Found GTID '%s' for slave %" PRIu32 ""
" at %" PRIu32 "/%" PRIu32 "/%s:%" PRIu64 ""
". Next event at %" PRIu64 "",
slave->mariadb_gtid,
slave->serverid,
f_gtid.gtid_elms.domain_id,
f_gtid.gtid_elms.server_id,
f_gtid.file,
f_gtid.start,
f_gtid.end);
/**
* Checks:
* a) GTID request has no binlog file at all:
* use GTID info file
* b) binlog file & position:
* if the requested binlog file is equal to GTID info file use it.
*/
if (!req_file ||
(strcmp(slave->binlogfile, f_gtid.file) == 0))
{
/* Set binlog file to the GTID one */
strcpy(slave->binlogfile, f_gtid.file);
/* Set pos to GTID next event pos */
slave->binlog_pos = f_gtid.end;
}
else
{
/**
* The requested binlog file is not the GTID info file.
* The binlog file could be different due to:
* a rotate event or other non GTID events written
* after that GTID.
* If file exists events will be sent from requested file@pos
* otherwise file & pos = GTID info file.
*/
// Add tree prefix
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
char file_path[PATH_MAX + 1] = "";
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
{
sprintf(t_prefix,
"%" PRIu32 "/%" PRIu32 "/",
f_gtid.gtid_elms.domain_id,
f_gtid.gtid_elms.server_id);
}
// Get binlog filename full-path
blr_get_file_fullpath(slave->binlogfile,
router->binlogdir,
file_path,
t_prefix[0] ? t_prefix: NULL);
if (blr_slave_get_file_size(file_path) != 0)
{
slave->binlog_pos = req_pos;
}
else
{
/* Set binlog file to the GTID one */
strcpy(slave->binlogfile, f_gtid.file);
/* Set pos to GTID next event pos */
slave->binlog_pos = f_gtid.end;
}
}
/* Set GTID details in f_info*/
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
/* Free gtid and file from result */
MXS_FREE(f_gtid.gtid);
MXS_FREE(f_gtid.file);
}
}
return true;
}
/**
* Create a Fake GTID_LIST event
*
* The routine creates a Fake GTID_LIST event
*
* @param slave The connected client
* @param gtid The requested GTID from client
* @param serverid The router server_id to add
* in the replication event header
* @return Fake GTID_LIST event on success or NULL
*
*/
static GWBUF *blr_build_fake_gtid_list_event(ROUTER_SLAVE *slave,
const char *gtid,
uint32_t serverid)
{
int len;
GWBUF *gl_event;
uint8_t *ptr;
REP_HEADER hdr;
uint32_t chksum;
MARIADB_GTID_ELEMS req_gtid = {};
if (!blr_parse_gtid(gtid, &req_gtid))
{
return NULL;
}
/**
* We only support one GTID in the GTID_LIST event
*
* Paylod is:
* BINLOG_EVENT_HDR_LEN + 4 bytes GTID count + 1 GTID
*/
len = BINLOG_EVENT_HDR_LEN + 4 + 1 * (4 + 4 + 8);
/* Add CRC32 bytes if needed */
len += slave->nocrc ? 0 : BINLOG_EVENT_CRC_SIZE;
/* Allocate space for packet header, status and data */
if ((gl_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL)
{
return NULL;
}
/* Add 1 byte to paylod for status indicator */
hdr.payload_len = len + 1;
/* Add sequence and increment it */
hdr.seqno = slave->seqno++;
/* Set status indicator byte to OK */
hdr.ok = 0;
/* No timestamp is required */
hdr.timestamp = 0L;
/* GTID Event Type */
hdr.event_type = MARIADB10_GTID_GTID_LIST_EVENT;
hdr.serverid = serverid;
hdr.event_size = len;
/* Next pos is set */
hdr.next_pos = slave->binlog_pos;
/* Artificial Event Flag */
hdr.flags = 0x20;
/* Add replication hdr to resp */
ptr = blr_build_header(gl_event, &hdr);
/* Add 4 bytes count */
/* Note: We set only 1 GTID in GTID_LIST Event */
encode_value(ptr, 1, 32);
ptr += 4;
/* Add 4 bytes domain id */
encode_value(ptr, req_gtid.domain_id, 32);
ptr += 4;
/* Add 4 bytes server id*/
encode_value(ptr, req_gtid.server_id, 32);
ptr += 4;
/* Add 8 bytes sequence */
encode_value(ptr, req_gtid.seq_no, 64);
ptr += 8;
/* Now add the CRC to the fake binlog rotate event */
if (!slave->nocrc)
{
/*
* First checksum of an empty buffer
* then the checksum of the event portion of the message:
* we do not include the len, seq number and ok byte that are part of
* first 5 bytes of the message.
* We also do not include the 4 byte checksum itself.
*/
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum,
GWBUF_DATA(gl_event) + MYSQL_HEADER_LEN + 1,
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
encode_value(ptr, chksum, 32);
}
return gl_event;
}
/**
* Create and send a Fake GTID_LIST event
*
* @param slave Current slave server
* @param gtid The requested GTID from client
* @oaram serverid The server_id to use in replication header
* @return Non-zero if data has been sent
*/
static int blr_send_fake_gtid_list(ROUTER_SLAVE *slave,
const char *gtid,
uint32_t serverid)
{
/* Build Fake GTID_LIST Event */
GWBUF *gl_event = blr_build_fake_gtid_list_event(slave,
gtid,
serverid);
/* Send Fake GTID_LIST Event or return 0*/
return gl_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, gl_event) : 0;
}
/**
* Handle received Maxwell statements from clients
*
* if a Maxwell statement is suported
* a proper reply to the connected client is done
*
* @param router Router instance
* @param slave Connected client/slave server
* @param maxwell_stmt The admin command options
* @return True for handled queries, False otherwise
*/
static bool blr_handle_maxwell_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *maxwell_stmt)
{
static const char mysql_connector_results_charset_query[] = "SET character_set_results = NULL";
static const char maxwell_server_id_query[] = "SELECT @@server_id as server_id";
static const char maxwell_log_bin_query[] = "SHOW VARIABLES LIKE 'log_bin'";
static const char maxwell_binlog_format_query[] = "SHOW VARIABLES LIKE 'binlog_format'";
static const char maxwell_binlog_row_image_query[] = "SHOW VARIABLES LIKE 'binlog_row_image'";
static const char maxwell_lower_case_tables_query[] = "select @@lower_case_table_names";
if (strcmp(blr_skip_leading_sql_comments(maxwell_stmt),
MYSQL_CONNECTOR_SERVER_VARS_QUERY) == 0)
{
int rc = blr_slave_replay(router,
slave,
router->saved_master.server_vars);
if (!rc)
{
MXS_ERROR("Error sending mysql-connector-j server variables");
}
return true;
}
else if (router->maxwell_compat &&
strcmp(maxwell_stmt, mysql_connector_results_charset_query) == 0)
{
blr_slave_send_ok(router, slave);
return true;
}
else if (router->maxwell_compat &&
strcmp(maxwell_stmt, MYSQL_CONNECTOR_SQL_MODE_QUERY) == 0)
{
blr_slave_send_ok(router, slave);
return true;
}
else if (strcmp(maxwell_stmt, maxwell_server_id_query) == 0)
{
char server_id[40];
sprintf(server_id, "%d", router->masterid);
blr_slave_send_var_value(router,
slave,
"server_id",
server_id,
BLR_TYPE_STRING);
return true;
}
else if (strcmp(maxwell_stmt, maxwell_log_bin_query) == 0)
{
char *log_bin = blr_extract_column(router->saved_master.binlog_vars, 1);
blr_slave_send_var_value(router,
slave,
"Value",
log_bin == NULL ? "" : log_bin,
BLR_TYPE_STRING);
MXS_FREE(log_bin);
return true;
}
else if (strcmp(maxwell_stmt, maxwell_binlog_format_query) == 0)
{
char *binlog_format = blr_extract_column(router->saved_master.binlog_vars, 2);
blr_slave_send_var_value(router,
slave,
"Value",
binlog_format == NULL ? "" : binlog_format,
BLR_TYPE_STRING);
MXS_FREE(binlog_format);
return true;
}
else if (strcmp(maxwell_stmt, maxwell_binlog_row_image_query) == 0)
{
char *binlog_row_image = blr_extract_column(router->saved_master.binlog_vars, 3);
blr_slave_send_var_value(router,
slave,
"Value",
binlog_row_image == NULL ? "" : binlog_row_image,
BLR_TYPE_STRING);
MXS_FREE(binlog_row_image);
return true;
}
else if (strcmp(maxwell_stmt, maxwell_lower_case_tables_query) == 0)
{
int rc = blr_slave_replay(router,
slave,
router->saved_master.lower_case_tables);
if (!rc)
{
MXS_ERROR("Error sending lower_case_tables query response");
}
return true;
}
return false;
}
/**
* Handle received SHOW statements from clients
*
* if a SHOW statement is one of suported one
* a proper reply to the connected client is done
*
* @param router Router instance
* @param slave Connected client/slave server
* @param show_stmt The SHOW statement
* @return True for handled queries, False otherwise
*/
static bool blr_handle_show_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *show_stmt)
{
char *word;
char *brkb;
char *sep = " \t,=";
if ((word = strtok_r(show_stmt, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete show query.", router->service->name);
return false;
}
else if (strcasecmp(word, "WARNINGS") == 0)
{
blr_slave_show_warnings(router, slave);
return true;
}
else if (strcasecmp(word, "BINARY") == 0 ||
(strcasecmp(word, "FULL") == 0 &&
strcasecmp(word, "BINARY")))
{
if (router->mariadb10_gtid)
{
blr_show_binary_logs(router, slave, word);
}
else
{
char *errmsg = "SHOW [FULL] BINARY LOGS needs the"
" 'mariadb10_slave_gtid' option to be set.";
MXS_ERROR("%s: %s",
errmsg,
router->service->name);
blr_slave_send_error_packet(slave,
errmsg,
1198,
NULL);
}
return true;
}
else if (strcasecmp(word, "GLOBAL") == 0)
{
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_ok(router, slave);
return true;
}
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected VARIABLES in SHOW GLOBAL",
router->service->name);
return false;
}
else if (strcasecmp(word, "VARIABLES") == 0)
{
int rc = blr_slave_handle_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
return true;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL VARIABLES.",
router->service->name);
return false;
}
}
else if (strcasecmp(word, "STATUS") == 0)
{
int rc = blr_slave_handle_status_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
return true;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL STATUS.",
router->service->name);
return false;
}
}
}
else if (strcasecmp(word, "VARIABLES") == 0)
{
int rc;
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_ok(router, slave);
return true;
}
rc = blr_slave_handle_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
return true;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW VARIABLES.",
router->service->name);
return false;
}
}
else if (strcasecmp(word, "MASTER") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected SHOW MASTER STATUS command",
router->service->name);
return false;
}
else if (strcasecmp(word, "STATUS") == 0)
{
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
blr_slave_send_master_status(router, slave);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
}
/* Added support for SHOW ALL SLAVES STATUS */
else if (strcasecmp(word, "SLAVE") == 0 ||
(strcasecmp(word, "ALL") == 0))
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected SHOW SLAVE STATUS command",
router->service->name);
return false;
}
else if (strcasecmp(word, "STATUS") == 0 ||
(strcasecmp(word, "SLAVES") == 0 &&
strcasecmp(brkb, "STATUS") == 0))
{
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
bool s_all = strcasecmp(word, "SLAVES") == 0 ? true : false;
blr_slave_send_slave_status(router, slave, s_all);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
else if (strcasecmp(word, "HOSTS") == 0)
{
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
blr_slave_send_slave_hosts(router, slave);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
}
else if (strcasecmp(word, "STATUS") == 0)
{
int rc = blr_slave_handle_status_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
return true;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW STATUS.",
router->service->name);
return false;
}
}
return false;
}
/**
* Handle received SET statements from clients
*
* if a SHOW statement is one of suported one
* a proper reply to the connected client is done
*
* @param router Router instance
* @param slave Connected client/slave server
* @param set_stmt The SET statement
* @return True for handled queries, False otherwise
*/
static bool blr_handle_set_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *set_stmt)
{
char *word;
char *brkb;
char *sep = " \t,=";
if ((word = strtok_r(set_stmt, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete set command.", router->service->name);
return false;
}
else if ((strcasecmp(word, "autocommit") == 0) ||
(strcasecmp(word, "@@session.autocommit") == 0))
{
blr_slave_send_ok(router, slave);
return true;
}
else if (strcasecmp(word, "@master_heartbeat_period") == 0)
{
int v_len = 0;
word = strtok_r(NULL, sep, &brkb);
if (word)
{
char *new_val;
v_len = strlen(word);
if (v_len > 6)
{
new_val = mxs_strndup_a(word, v_len - 6);
slave->heartbeat = atoi(new_val) / 1000;
}
else
{
new_val = mxs_strndup_a(word, v_len);
slave->heartbeat = atoi(new_val) / 1000000;
}
MXS_FREE(new_val);
}
blr_slave_replay(router, slave, router->saved_master.heartbeat);
return true;
}
else if (strcasecmp(word, "@mariadb_slave_capability") == 0)
{
/* mariadb10 compatibility is set for the slave */
slave->mariadb10_compat = true;
if (router->mariadb10_compat)
{
blr_slave_replay(router, slave, router->saved_master.mariadb10);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
word = strtok_r(NULL, sep, &brkb);
if (word && (strcasecmp(word, "'none'") == 0))
{
slave->nocrc = 1;
}
else if (word && (strcasecmp(word, "@@global.binlog_checksum") == 0))
{
slave->nocrc = !router->master_chksum;
}
else
{
slave->nocrc = 0;
}
blr_slave_replay(router, slave, router->saved_master.chksum1);
return true;
}
else if (strcasecmp(word, "@slave_uuid") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
{
int len = strlen(word);
char *word_ptr = word;
if (len)
{
if (word[len - 1] == '\'')
{
word[len - 1] = '\0';
}
if (word[0] == '\'')
{
word[0] = '\0';
word_ptr++;
}
}
/* Free previous value */
MXS_FREE(slave->uuid);
slave->uuid = MXS_STRDUP_A(word_ptr);
}
if (router->saved_master.setslaveuuid)
{
blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
else if (strcasecmp(word, "@@global.gtid_slave_pos") == 0)
{
if (slave->serverid != 0)
{
MXS_ERROR("Master GTID registration can be sent only"
" via administration connection");
blr_slave_send_error_packet(slave,
"Master GTID registration cannot be"
" issued by a registrating slave.",
1198, NULL);
return false;
}
if (router->master_state != BLRM_SLAVE_STOPPED &&
router->master_state != BLRM_UNCONFIGURED)
{
const char *err_msg_u = "configured replication: Issue CHANGE MASTER TO first.";
const char *err_msg_s = "stopped replication: issue STOP SLAVE first.";
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
MXS_ERROR("GTID registration without %s",
router->master_state == BLRM_SLAVE_STOPPED ?
err_msg_s : err_msg_u);
snprintf(error_string,
BINLOG_ERROR_MSG_LEN,
"Cannot use Master GTID registration without %s",
router->master_state == BLRM_SLAVE_STOPPED ?
err_msg_s :
err_msg_u);
blr_slave_send_error_packet(slave,
error_string,
1198,
NULL);
return true;
}
/* If not mariadb GTID an error message will be returned */
if (router->mariadb10_master_gtid)
{
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
{
char heading[GTID_MAX_LEN + 1];
MARIADB_GTID_ELEMS gtid_elms = {};
// TODO: gtid_strip_chars routine for this
strcpy(heading, word + 1);
heading[strlen(heading) - 1] = '\0';
MXS_INFO("Requesting GTID (%s) from Master server.",
!heading[0] ? "empty value" : heading);
/* Parse the non empty GTID value */
if (heading[0] && !blr_parse_gtid(heading, &gtid_elms))
{
static const char *err_fmt = "Invalid format for GTID ('%s')"
" set request; use 'X-Y-Z'";
char err_msg[sizeof(err_fmt) + GTID_MAX_LEN + 1];
sprintf(err_msg, err_fmt, heading);
MXS_ERROR("%s", err_msg);
/* Stop Master registration */
blr_slave_send_error_packet(slave,
err_msg,
1198,
NULL);
}
else
{
strcpy(router->last_mariadb_gtid, heading);
blr_slave_send_ok(router, slave);
}
return true;
}
}
else
{
MXS_ERROR("Master GTID registration needs 'mariadb10_master_gtid'"
" option to be set.");
blr_slave_send_error_packet(slave,
"Master GTID registration needs"
" 'mariadb10_master_gtid' option"
" to be set first.",
1198,
NULL);
return true;
}
}
else if (strstr(word, "@slave_connect_state") != NULL)
{
/* If not mariadb an error message will be returned */
if (slave->mariadb10_compat &&
router->mariadb10_gtid &&
(word = strtok_r(NULL, sep, &brkb)) != NULL)
{
char heading[GTID_MAX_LEN + 1];
MXS_DEBUG("Received GTID request '%s' from slave %u",
word,
slave->serverid);
strcpy(heading, word + 1);
heading[strlen(heading) - 1] = '\0';
/**
* Set the GTID string, it could be an empty
* in case of a fresh new setup.
*/
MXS_FREE(slave->mariadb_gtid);
slave->mariadb_gtid = MXS_STRDUP_A(heading);
blr_slave_send_ok(router, slave);
return true;
}
else
{
MXS_ERROR("GTID Master registration is not enabled");
return false;
}
}
else if (strcasecmp(word, "@slave_gtid_strict_mode") == 0)
{
/* If not mariadb an error message will be returned */
if (slave->mariadb10_compat &&
router->mariadb10_gtid &&
(word = strtok_r(NULL, sep, &brkb)) != NULL)
{
/* Set strict mode */
slave->gtid_strict_mode = atoi(word);
blr_slave_send_ok(router, slave);
return true;
}
}
else if (strcasecmp(word, "@slave_gtid_ignore_duplicates") == 0)
{
/* If not mariadb an error message will be returned */
if (slave->mariadb10_compat &&
router->mariadb10_gtid)
{
blr_slave_send_ok(router, slave);
return true;
}
}
else if (strcasecmp(word, "NAMES") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Truncated SET NAMES command.",
router->service->name);
return false;
}
else if (strcasecmp(word, "latin1") == 0)
{
blr_slave_replay(router, slave, router->saved_master.setnames);
return true;
}
else if (strcasecmp(word, "utf8") == 0)
{
blr_slave_replay(router, slave, router->saved_master.utf8);
return true;
}
else
{
blr_slave_send_ok(router, slave);
return true;
}
}
return false;
}
/**
* Handle received admin statements from clients
*
* if an admin statement is one of suported one
* a proper reply to the connected client is done
*
* @param router Router instance
* @param slave Connected client/slave server
* @param admin_stmt The admin statement
* @param admin_opts The admin command options
* @return True for handled queries, False otherwise
*/
static bool blr_handle_admin_stmt(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *admin_stmt,
char *admin_opts)
{
char *word;
char *brkb;
char *sep = " \t,=";
if (admin_opts == NULL || !admin_opts[0])
{
MXS_ERROR("%s: Incomplete admin command.", router->service->name);
return false;
}
/* Handle RESET command */
else if (strcasecmp(admin_stmt, "RESET") == 0)
{
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete RESET command.", router->service->name);
return false;
}
/* RESET the current configured master cfg */
else if (strcasecmp(word, "SLAVE") == 0)
{
if (router->master_state == BLRM_SLAVE_STOPPED)
{
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
MASTER_SERVER_CFG *current_master = NULL;
int removed_cfg = 0;
/* save current replication parameters */
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
MXS_ABORT_IF_NULL(current_master);
if (!current_master)
{
snprintf(error_string,
BINLOG_ERROR_MSG_LEN,
"error allocating memory for blr_master_get_config");
MXS_ERROR("%s: %s", router->service->name, error_string);
blr_slave_send_error_packet(slave,
error_string,
1201,
NULL);
return true;
}
/* get current data */
blr_master_get_config(router, current_master);
MXS_NOTICE("%s: 'RESET SLAVE executed'. Previous state MASTER_HOST='%s', "
"MASTER_PORT=%i, MASTER_LOG_FILE='%s', MASTER_LOG_POS=%lu, "
"MASTER_USER='%s'",
router->service->name,
current_master->host,
current_master->port,
current_master->logfile,
current_master->pos,
current_master->user);
/* remove master.ini */
static const char MASTER_INI[] = "/master.ini";
char path[strlen(router->binlogdir) + sizeof(MASTER_INI)];
strcpy(path, router->binlogdir);
strcat(path, MASTER_INI);
/* remove master.ini */
removed_cfg = unlink(path);
if (removed_cfg == -1)
{
snprintf(error_string, BINLOG_ERROR_MSG_LEN,
"Error removing %s, %s, errno %u", path,
mxs_strerror(errno), errno);
MXS_ERROR("%s: %s", router->service->name, error_string);
}
spinlock_acquire(&router->lock);
/* Set the BLRM_UNCONFIGURED state */
router->master_state = BLRM_UNCONFIGURED;
blr_master_set_empty_config(router);
blr_master_free_config(current_master);
/* Remove any error message and errno */
free(router->m_errmsg);
router->m_errmsg = NULL;
router->m_errno = 0;
spinlock_release(&router->lock);
if (removed_cfg == -1)
{
blr_slave_send_error_packet(slave,
error_string,
1201,
NULL);
}
else
{
blr_slave_send_ok(router, slave);
}
return true;
}
else
{
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_ok(router, slave);
}
else
{
blr_slave_send_error_packet(slave,
"This operation cannot be performed "
"with a running slave; run STOP SLAVE first",
1198,
NULL);
}
return true;
}
}
}
/* Start replication from the current configured master */
else if (strcasecmp(admin_stmt, "START") == 0)
{
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete START command.",
router->service->name);
return false;
}
else if (strcasecmp(word, "SLAVE") == 0)
{
blr_start_slave(router, slave);
return true;
}
}
/* Stop replication from the current master*/
else if (strcasecmp(admin_stmt, "STOP") == 0)
{
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete STOP command.", router->service->name);
return false;
}
else if (strcasecmp(word, "SLAVE") == 0)
{
blr_stop_slave(router, slave);
return true;
}
}
/* Change the server to replicate from */
else if (strcasecmp(admin_stmt, "CHANGE") == 0)
{
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete CHANGE command.", router->service->name);
return false;
}
else if (strcasecmp(word, "MASTER") == 0)
{
if (router->master_state != BLRM_SLAVE_STOPPED &&
router->master_state != BLRM_UNCONFIGURED)
{
blr_slave_send_error_packet(slave,
"Cannot change master with a running slave; "
"run STOP SLAVE first",
(unsigned int)1198,
NULL);
return true;
}
else
{
int rc;
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
MASTER_SERVER_CFG *current_master = NULL;
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
if (!current_master)
{
blr_slave_send_error_packet(slave,
error_string,
1201,
NULL);
return true;
}
blr_master_get_config(router, current_master);
rc = blr_handle_change_master(router, brkb, error_string);
if (rc < 0)
{
/* CHANGE MASTER TO has failed */
blr_slave_send_error_packet(slave,
error_string,
1234,
"42000");
blr_master_free_config(current_master);
return true;
}
else
{
int ret;
char error[BINLOG_ERROR_MSG_LEN + 1];
/* Write/Update master config into master.ini file */
ret = blr_file_write_master_config(router, error);
if (ret)
{
/* file operation failure: restore config */
spinlock_acquire(&router->lock);
blr_master_apply_config(router, current_master);
blr_master_free_config(current_master);
spinlock_release(&router->lock);
snprintf(error_string, BINLOG_ERROR_MSG_LEN,
"Error writing into %s/master.ini: %s",
router->binlogdir,
error);
MXS_ERROR("%s: %s",
router->service->name, error_string);
blr_slave_send_error_packet(slave,
error_string,
1201,
NULL);
return true;
}
/**
* check if router is BLRM_UNCONFIGURED
* and change state to BLRM_SLAVE_STOPPED
*/
if (rc == 1 || router->master_state == BLRM_UNCONFIGURED)
{
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
spinlock_release(&router->lock);
/*
* The binlog server has just been configured
* master.ini file written in router->binlogdir.
*
* Create the binlogfile specified in MASTER_LOG_FILE
* only if MariaDB GTID 'mariadb10_master_gtid' is Off
*/
if (!router->mariadb10_master_gtid &&
blr_file_new_binlog(router, router->binlog_name))
{
MXS_INFO("%s: 'master.ini' created, binlog file '%s' created",
router->service->name, router->binlog_name);
}
blr_master_free_config(current_master);
blr_slave_send_ok(router, slave);
return true;
}
if (router->trx_safe &&
router->pending_transaction.state > BLRM_NO_TRANSACTION)
{
if (strcmp(router->binlog_name, router->prevbinlog) != 0)
{
char message[BINLOG_ERROR_MSG_LEN + 1] = "";
snprintf(message, BINLOG_ERROR_MSG_LEN,
"1105:Partial transaction in file %s starting at pos %lu, "
"ending at pos %lu will be lost with next START SLAVE command",
current_master->logfile,
current_master->safe_pos,
current_master->pos);
blr_master_free_config(current_master);
blr_slave_send_warning_message(router, slave, message);
return true;
}
}
blr_master_free_config(current_master);
/*
* The CHAMGE MASTER command might specify a new binlog file.
* Let's create the binlogfile specified in MASTER_LOG_FILE
* only if MariaDB GTID 'mariadb10_master_gtid' is Off
*/
if (!router->mariadb10_master_gtid &&
(strlen(router->prevbinlog) &&
strcmp(router->prevbinlog, router->binlog_name) != 0))
{
if (blr_file_new_binlog(router, router->binlog_name))
{
MXS_INFO("%s: created new binlog file '%s' by "
"'CHANGE MASTER TO' command",
router->service->name,
router->binlog_name);
}
}
blr_slave_send_ok(router, slave);
return true;
}
}
}
}
/* Discnnect conneted client(s) */
else if (strcasecmp(admin_stmt, "DISCONNECT") == 0)
{
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete DISCONNECT command.",
router->service->name);
return false;
}
else if (strcasecmp(word, "ALL") == 0)
{
blr_slave_disconnect_all(router, slave);
return true;
}
else if (strcasecmp(word, "SERVER") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected DISCONNECT SERVER $server_id",
router->service->name);
return false;
}
else
{
int serverid = atoi(word);
blr_slave_disconnect_server(router, slave, serverid);
return true;
}
}
}
return false;
}
/**
* Skip reading empty binlog files (4 bytes only)
*
* @param router Current router instance
* @param slave Current connected slave
*/
static void blr_slave_skip_empty_files(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave)
{
char binlog_file[BINLOG_FNAMELEN + 1];
char router_curr_file[BINLOG_FNAMELEN + 1];
char file_path[PATH_MAX + 1] = "";
unsigned int seqno;
bool skipped_files = false;
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
MARIADB_GTID_INFO *f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
&slave->f_info :
NULL;
char next_file[BINLOG_FNAMELEN + 1] = "";
// Save the current router binlog filename
spinlock_acquire(&router->binlog_lock);
strcpy(router_curr_file, router->binlog_name);
spinlock_release(&router->binlog_lock);
// Set the starting filename
strcpy(binlog_file, slave->binlogfile);
//Add tree prefix
if (f_tree)
{
sprintf(t_prefix,
"%" PRIu32 "/%" PRIu32 "/",
f_tree->gtid_elms.domain_id,
f_tree->gtid_elms.server_id);
}
// Get binlog filename full-path
blr_get_file_fullpath(binlog_file,
router->binlogdir,
file_path,
t_prefix[0] ? t_prefix: NULL);
/**
* Get the next file in sequence or next by GTID maps
* if current file has 4 bytes size or it doesn't exist at all.
* Stop if the new file is the current binlog file.
*/
while (!blr_compare_binlogs(router,
f_tree,
router_curr_file,
binlog_file) &&
blr_slave_get_file_size(file_path) <= 4 &&
blr_file_next_exists(router, slave, next_file))
{
// Log skipped file
MXS_INFO("Slave %s:%i, skip reading empty file '%s' (4 bytes size).",
slave->dcb->remote,
dcb_get_port(slave->dcb),
binlog_file);
// Update binlog_file name
strcpy(binlog_file, next_file);
// Get binlog file full-path
blr_get_file_fullpath(binlog_file,
router->binlogdir,
file_path,
t_prefix[0] ? t_prefix: NULL);
skipped_files = true;
}
// One or more files skipped: set last found filename and pos = 4
if (skipped_files)
{
strcpy(slave->binlogfile, binlog_file);
slave->binlog_pos = 4;
}
}
/**
* Get the full path of a binlog filename.
*
* @param binlog_file The binlog filename
* @param root_dir The binlog storage directory
* @param full_path The output fullpahth name:
* the memory area must be preallocated.
* @param t_prefix The file_tree prefix with rep_domain
* and server_id.
*/
static inline void blr_get_file_fullpath(const char *binlog_file,
const char *root_dir,
char *full_path,
const char *t_prefix)
{
strcpy(full_path, root_dir);
strcat(full_path, "/");
if (t_prefix)
{
strcat(full_path, t_prefix);
}
strcat(full_path, binlog_file);
}
/**
* Returns the list of binlog files
* saved in GTID repo.
*
* It's called olny if mariadb10_slave_gtid option is set
*
* @param router The router instance
* @param slave The connected client
* @param extra_data Whether to dispay path file
* info before filename
* @retun Sent bytes
*/
static int
blr_show_binary_logs(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *extra_data)
{
char current_file[BINLOG_FNAMELEN];
uint64_t current_pos = 0;
static const char select_query[] = "SELECT binlog_file, "
"MAX(end_pos) AS size, "
"rep_domain, "
"server_id "
"FROM gtid_maps "
"GROUP BY binlog_file "
"ORDER BY id ASC;";
int seqno;
char *errmsg = NULL;
BINARY_LOG_DATA_RESULT result = {};
bool extra_info = !strcasecmp(extra_data, "FULL");
/* Get current binlog finename and position */
spinlock_acquire(&router->binlog_lock);
strcpy(current_file, router->binlog_name);
current_pos = router->current_pos;
spinlock_release(&router->binlog_lock);
/**
* First part of result set:
* send 2 columns and their defintions.
*/
/* This call sets seq to 1 in the packet */
blr_slave_send_fieldcount(router, slave, 2);
/* Set 'seqno' counter to next value: 2 */
seqno = 2;
/* Col 1 def */
blr_slave_send_columndef(router,
slave,
"Log_name",
BLR_TYPE_STRING,
40,
seqno++);
/* Col 2 def */
blr_slave_send_columndef(router,
slave,
"File_size",
BLR_TYPE_INT,
40,
seqno++);
/* Cols EOF */
blr_slave_send_eof(router, slave, seqno);
/* Increment sequence */
seqno++;
/* Initialise the result data struct */
result.seq_no = seqno;
result.client = slave->dcb;
result.last_file = NULL;
result.binlogdir = router->binlogdir;
result.extra_info = extra_info;
/**
* Second part of result set:
*
* add rows for select binlog files.
*
* Note:
* - result.last_file is freed and updated by binary_logs_select_cb()
* - result.seq_no is increased
*/
if (sqlite3_exec(router->gtid_maps,
select_query,
binary_logs_select_cb,
&result,
&errmsg) != SQLITE_OK)
{
MXS_ERROR("Failed to exec 'SELECT binlog_file FROM gtid_maps': "
"%s", errmsg ? errmsg : "database is not available");
sqlite3_free(errmsg);
/* Free last_file */
MXS_FREE(result.last_file);
result.last_file = NULL;
/* Add EOF for empty result set */
return blr_slave_send_eof(router, slave, result.seq_no);
}
/* Use seqno ofof last sent packet */
seqno = result.seq_no;
/**
* Check whether the last file is the current binlog file.
* If not then add the new row.
*/
// TODO: check whether to FIX with prefix
if (strcmp(current_file, result.last_file) != 0)
{
char pos[40];
GWBUF *pkt;
/* Free last file */
MXS_FREE(result.last_file);
/* Create the string value for pos */
sprintf(pos, "%" PRIu64, current_pos);
/* Create & write the new row */
if ((pkt = blr_create_result_row(current_file,
pos,
seqno)) != NULL)
{
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
/* Increment sequence */
seqno++;
}
}
/* Add the result set EOF */
return blr_slave_send_eof(router, slave, seqno);
}
/**
* Creates a Result Set row with two STRING columns
*
* @param val1 First column value
* @param val2 Second column value
* @param seq_no Sequence number for this row
* @return An allocated GWBUF or NULL
*/
GWBUF *blr_create_result_row(const char *val1,
const char *val2,
int seq_no)
{
int val1_len = strlen(val1);
int val2_len = strlen(val2);
GWBUF *pkt;
uint8_t *ptr;
int len = MYSQL_HEADER_LEN + (1 + val1_len + (1 + val2_len));
// Allocate a new GWBUF buffer
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return NULL;
}
ptr = GWBUF_DATA(pkt);
// Add length of data packet
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
ptr += 3;
// Sequence number in response
*ptr++ = seq_no;
// Length of result string "val1"
*ptr++ = val1_len;
memcpy((char *)ptr, val1, val1_len);
ptr += val1_len;
// Length of result string "val2"
*ptr++ = val2_len;
memcpy((char *)ptr, val2, val2_len);
return pkt;
}
/**
* Binary logs select callback for sqlite3 database
*
* @param data Data pointer from caller
* @param cols Number of columns
* @param values The values
* @param names The column names
*
* @return 0 on success, 1 otherwise
*/
static int binary_logs_select_cb(void *data,
int cols,
char** values,
char** names)
{
BINARY_LOG_DATA_RESULT *data_set = (BINARY_LOG_DATA_RESULT *)data;
DCB *dcb = data_set->client;
int ret = 1; // Failure
uint32_t fsize;
char file_size[40];
ss_dassert(cols >= 4 && dcb);
if (values[0] && // File Name
values[1] && // File Size
values[2] && // Domain ID
values[3]) // Server ID
{
GWBUF *pkt;
char file_path[PATH_MAX + 1];
char filename[1 +
strlen(values[0]) +
BINLOG_FILE_EXTRA_INFO];
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
sprintf(t_prefix,
"%s/%s/",
values[2], // domain ID
values[3]); // server ID
fsize = atoll(values[1]);
/* File size != 0 && server ID != 0 */
ss_dassert(fsize && atoll(values[3]));
/**
* In GTID repo binlog file last pos is last GTID.
* In case of rotate_event or any event the "file_size"
* it's not correct.
* In case of binlog files with no transactions at all
* the saved size is 4.
*
* Let's get the real size by calling blr_slave_get_file_size()
*/
//Get binlog filename full-path
blr_get_file_fullpath(values[0],
data_set->binlogdir,
file_path,
t_prefix);
//Get the file size
fsize = blr_slave_get_file_size(file_path);
sprintf(file_size, "%" PRIu32 "", fsize);
// Include extra output
if (data_set->extra_info)
{
sprintf(filename,
"%s%s",
t_prefix,
values[0]); // filename
}
else
{
sprintf(filename, "%s", values[0]); // filename only
}
/* Create the MySQL Result Set row */
if ((pkt = blr_create_result_row(filename, // File name
file_size, // File size
data_set->seq_no)) != NULL)
{
/* Increase sequence for next row */
data_set->seq_no++;
/* Free last file name */
MXS_FREE(data_set->last_file);
/* Set last file name */
data_set->last_file = MXS_STRDUP_A(values[0]);
/* Write packet to client */
ret = MXS_SESSION_ROUTE_REPLY(dcb->session, pkt);
}
return ret; /* Return success or fallure */
}
else
{
return 0; /* Success: no data from db or end of result set */
}
}
/**
* Handle SELECT @@server_id, @@read_only
* that MaxScale MySQL monitor sends to monitored servers
*
* @param router The router instance
* @param slave The connected client
* @return Number of bytes written
*/
static int blr_slave_send_id_ro(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave)
{
int seqno;
GWBUF *pkt;
/**
* First part of result set:
* send 2 columns and their defintions.
*/
/* This call sets seq to 1 in the packet */
blr_slave_send_fieldcount(router, slave, 2);
/* Set 'seqno' counter to next value: 2 */
seqno = 2;
/* Col 1 def */
blr_slave_send_columndef(router,
slave,
"@@server_id",
BLR_TYPE_INT,
40,
seqno++);
/* Col 2 def */
blr_slave_send_columndef(router,
slave,
"@@read_only",
BLR_TYPE_INT,
40,
seqno++);
/* Cols EOF */
blr_slave_send_eof(router, slave, seqno++);
/* Create the MySQL Result Set row */
char server_id[40] = "";
sprintf(server_id, "%d", router->serverid);
if ((pkt = blr_create_result_row(server_id, // File name
"0", // o = OFF
seqno++)) != NULL)
{
/* Write packet to client */
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
}
/* Add the result set EOF and return */
return blr_slave_send_eof(router, slave, seqno);
}
/**
* Handle a SELECT with more than one column.
*
* Only SELECT @@server_id, @@read_only is supported.
* That query is sent by MaxScale MySQL monitor.
*
* @param router The router instance
* @param slave The connected client
* @param col1 The first column
* @param coln Whatever is after first column
* @return True is handled, false otherwise
*/
static bool blr_handle_complex_select(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
const char *col1,
const char *coln)
{
/* Strip leading spaces */
while(isspace(*coln))
{
coln++;
}
if ((strcasecmp(col1, "@@server_id") == 0 ||
strcasecmp(col1, "@@global.server_id") == 0) &&
(strcasecmp(coln, "@@read_only") == 0 ||
strcasecmp(coln, "@@global.read_only") == 0))
{
blr_slave_send_id_ro(router, slave);
return true;
}
else
{
return false;
}
}