 82add11e86
			
		
	
	82add11e86
	
	
	
		
			
			SSLContext is now used everywhere except the binlogrouter which still allocates the contexts itself. Fixing the binlogrouter's misuse of internal structures is a rather large undertaking and for this reason the SSLContext will be taken into use there in a separate commit.
		
			
				
	
	
		
			4923 lines
		
	
	
		
			156 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4923 lines
		
	
	
		
			156 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: 2022-01-01
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file blr_file.c - contains code for the router binlog file management
 | |
|  */
 | |
| 
 | |
| #include "blr.hh"
 | |
| 
 | |
| #include <dirent.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <inttypes.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/types.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <ini.h>
 | |
| 
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxbase/atomic.h>
 | |
| #include <maxscale/dcb.hh>
 | |
| #include <maxscale/encryption.h>
 | |
| #include <maxscale/paths.h>
 | |
| #include <maxscale/router.hh>
 | |
| #include <maxscale/secrets.h>
 | |
| #include <maxscale/server.hh>
 | |
| #include <maxscale/service.hh>
 | |
| #include <maxscale/utils.h>
 | |
| 
 | |
| using std::string;
 | |
| using std::vector;
 | |
| 
 | |
| /**
 | |
|  * AES_CTR handling
 | |
|  *
 | |
|  * @param klen    The AES Key len
 | |
|  * @return        The EVP_AES_CTR routine for key len
 | |
|  */
 | |
| 
 | |
| #if OPENSSL_VERSION_NUMBER > 0x10000000L
 | |
| static inline const EVP_CIPHER* aes_ctr(unsigned int klen)
 | |
| {
 | |
|     switch (klen)
 | |
|     {
 | |
|     case 16:
 | |
|         return EVP_aes_128_ctr();
 | |
| 
 | |
|     case 24:
 | |
|         return EVP_aes_192_ctr();
 | |
| 
 | |
|     case 32:
 | |
|         return EVP_aes_256_ctr();
 | |
| 
 | |
|     default:
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * AES_CBC handling
 | |
|  *
 | |
|  * @param klen    The AES Key len
 | |
|  * @return        The EVP_AES_CBC routine for key len
 | |
|  */
 | |
| static inline const EVP_CIPHER* aes_cbc(uint klen)
 | |
| {
 | |
|     switch (klen)
 | |
|     {
 | |
|     case 16:
 | |
|         return EVP_aes_128_cbc();
 | |
| 
 | |
|     case 24:
 | |
|         return EVP_aes_192_cbc();
 | |
| 
 | |
|     case 32:
 | |
|         return EVP_aes_256_cbc();
 | |
| 
 | |
|     default:
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * AES_ECB handling
 | |
|  *
 | |
|  * @param klen    The AES Key len
 | |
|  * @return        The EVP_AES_ECB routine for key len
 | |
|  */
 | |
| static inline const EVP_CIPHER* aes_ecb(uint klen)
 | |
| {
 | |
|     switch (klen)
 | |
|     {
 | |
|     case 16:
 | |
|         return EVP_aes_128_ecb();
 | |
| 
 | |
|     case 24:
 | |
|         return EVP_aes_192_ecb();
 | |
| 
 | |
|     case 32:
 | |
|         return EVP_aes_256_ecb();
 | |
| 
 | |
|     default:
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Array of functions for supported algorithms
 | |
|  */
 | |
| const EVP_CIPHER* (* ciphers[])(unsigned int) =
 | |
| {
 | |
|     aes_cbc,
 | |
| #if OPENSSL_VERSION_NUMBER > 0x10000000L
 | |
|     aes_ctr,
 | |
| #else
 | |
|     NULL,
 | |
| #endif
 | |
|     aes_ecb
 | |
| };
 | |
| 
 | |
| #if OPENSSL_VERSION_NUMBER > 0x10000000L
 | |
| static const char* blr_encryption_algorithm_names[BINLOG_MAX_CRYPTO_SCHEME] = {"aes_cbc", "aes_ctr"};
 | |
| static const char blr_encryption_algorithm_list_names[] = "aes_cbc, aes_ctr";
 | |
| #else
 | |
| static const char* blr_encryption_algorithm_names[BINLOG_MAX_CRYPTO_SCHEME] = {"aes_cbc"};
 | |
| static const char blr_encryption_algorithm_list_names[] = "aes_cbc";
 | |
| #endif
 | |
| 
 | |
| static int      blr_file_create(ROUTER_INSTANCE* router, char* file);
 | |
| static void     blr_log_header(int priority, const char* msg, uint8_t* ptr);
 | |
| void            blr_cache_read_master_data(ROUTER_INSTANCE* router);
 | |
| int             blr_file_get_next_binlogname(ROUTER_INSTANCE* router);
 | |
| int             blr_file_new_binlog(ROUTER_INSTANCE* router, char* file);
 | |
| extern uint32_t extract_field(uint8_t* src, int bits);
 | |
| static void     blr_format_event_size(double* event_size, char* label);
 | |
| extern int      MaxScaleUptime();
 | |
| extern void     encode_value(unsigned char* data, unsigned int value, int len);
 | |
| extern void     blr_extract_header(register uint8_t* ptr, register REP_HEADER* hdr);
 | |
| bool            blr_parse_gtid(const char* gtid, MARIADB_GTID_ELEMS* info);
 | |
| 
 | |
| typedef struct binlog_event_desc
 | |
| {
 | |
|     uint64_t event_pos;
 | |
|     uint8_t  event_type;
 | |
|     time_t   event_time;
 | |
| } BINLOG_EVENT_DESC;
 | |
| 
 | |
| static void blr_print_binlog_details(ROUTER_INSTANCE* router,
 | |
|                                      BINLOG_EVENT_DESC first_event_time,
 | |
|                                      BINLOG_EVENT_DESC last_event_time);
 | |
| static uint8_t* blr_create_ignorable_event(uint32_t event_size,
 | |
|                                            REP_HEADER* hdr,
 | |
|                                            uint32_t event_pos,
 | |
|                                            bool do_checksum);
 | |
| int blr_write_special_event(ROUTER_INSTANCE* router,
 | |
|                             uint32_t file_offset,
 | |
|                             uint32_t hole_size,
 | |
|                             REP_HEADER* hdr,
 | |
|                             int type);
 | |
| static uint8_t* blr_create_start_encryption_event(ROUTER_INSTANCE* router,
 | |
|                                                   uint32_t event_pos,
 | |
|                                                   bool do_checksum);
 | |
| static GWBUF* blr_prepare_encrypted_event(ROUTER_INSTANCE* router,
 | |
|                                           uint8_t* event,
 | |
|                                           uint32_t event_size,
 | |
|                                           uint32_t pos,
 | |
|                                           const uint8_t* nonce,
 | |
|                                           int action);
 | |
| static GWBUF* blr_aes_crypt(ROUTER_INSTANCE* router,
 | |
|                             uint8_t* event,
 | |
|                             uint32_t event_size,
 | |
|                             uint8_t* iv,
 | |
|                             int action);
 | |
| static int blr_aes_create_tail_for_cbc(uint8_t* output,
 | |
|                                        uint8_t* input,
 | |
|                                        uint32_t in_size,
 | |
|                                        uint8_t* iv,
 | |
|                                        uint8_t* key,
 | |
|                                        unsigned int key_len);
 | |
| static int blr_binlog_event_check(ROUTER_INSTANCE* router,
 | |
|                                   unsigned long pos,
 | |
|                                   REP_HEADER* hdr,
 | |
|                                   char* binlogname,
 | |
|                                   char* errmsg);
 | |
| 
 | |
| static void blr_report_checksum(REP_HEADER hdr,
 | |
|                                 const uint8_t* buffer,
 | |
|                                 char* output);
 | |
| 
 | |
| bool blr_load_last_mariadb_gtid(ROUTER_INSTANCE* router,
 | |
|                                 MARIADB_GTID_INFO* result);
 | |
| bool blr_get_last_file(ROUTER_INSTANCE* router,
 | |
|                        MARIADB_GTID_INFO* result);
 | |
| static int gtid_file_select_cb(void* data,
 | |
|                                int cols,
 | |
|                                char** values,
 | |
|                                char** names);
 | |
| bool blr_compare_binlogs(const ROUTER_INSTANCE* router,
 | |
|                          const MARIADB_GTID_ELEMS* info,
 | |
|                          const char* r_file,
 | |
|                          const char* s_file);
 | |
| 
 | |
| void blr_file_update_gtid(ROUTER_INSTANCE* router);
 | |
| 
 | |
| /**
 | |
|  * MariaDB 10.1.7 Start Encryption event content
 | |
|  *
 | |
|  * Event header:    19 bytes
 | |
|  * Content size:    17 bytes
 | |
|  *     crypto scheme 1 byte
 | |
|  *     key_version   4 bytes
 | |
|  *     nonce random 12 bytes
 | |
|  *
 | |
|  * Event size is 19 + 17 = 36 bytes
 | |
|  */
 | |
| typedef struct start_encryption_event
 | |
| {
 | |
|     uint8_t  header[BINLOG_EVENT_HDR_LEN];  /**< Replication event header */
 | |
|     uint8_t  binlog_crypto_scheme;          /**< Encryption scheme */
 | |
|     uint32_t binlog_key_version;            /**< Encryption key version */
 | |
|     uint8_t  nonce[BLRM_NONCE_LENGTH];      /**< nonce (random bytes) of current binlog.
 | |
|                                              * These bytes + the binlog event current pos
 | |
|                                              * form the encrryption IV for the event */
 | |
| } MXS_START_ENCRYPTION_EVENT;
 | |
| 
 | |
| /**
 | |
|  * Initialise the binlog file for this instance. MaxScale will look
 | |
|  * for all the binlogs that it has on local disk, determine the next
 | |
|  * binlog to use and initialise it for writing, determining the
 | |
|  * next record to be fetched from the real master.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @return          1 on success, 0 otherwise
 | |
|  */
 | |
| int blr_file_init(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     char path[PATH_MAX + 1 - BINLOG_FILE_EXTRA_INFO - BINLOG_FNAMELEN - 2] = "";
 | |
|     char filename[PATH_MAX + 1] = "";
 | |
|     int file_found, n = 1;
 | |
|     int root_len, i;
 | |
|     DIR* dirp;
 | |
|     struct dirent* dp;
 | |
|     int ret = 0;
 | |
| 
 | |
|     if (router->binlogdir == NULL)
 | |
|     {
 | |
|         const char* datadir = get_datadir();
 | |
|         size_t len = strlen(datadir) + sizeof('/') + strlen(router->service->name());
 | |
| 
 | |
|         if (len > PATH_MAX)
 | |
|         {
 | |
|             MXS_ERROR("The length of %s/%s is more than the maximum length %d.",
 | |
|                       datadir,
 | |
|                       router->service->name(),
 | |
|                       PATH_MAX);
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         snprintf(path, sizeof(path), "%s/%s", datadir, router->service->name());
 | |
| 
 | |
|         if (access(path, R_OK) == -1)
 | |
|         {
 | |
|             // TODO: Check what kind of error, ENOENT or something else.
 | |
|             mkdir(path, 0700);
 | |
|             // TODO: Check the result of mkdir.
 | |
|         }
 | |
| 
 | |
|         router->binlogdir = MXS_STRDUP_A(path);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         strcpy(path, router->binlogdir);
 | |
|     }
 | |
| 
 | |
|     if (access(path, R_OK) == -1)
 | |
|     {
 | |
|         MXS_ERROR("%s: Unable to read the binlog directory %s.",
 | |
|                   router->service->name(),
 | |
|                   router->binlogdir);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Find last written binary log in the binlog cache
 | |
|      * OR
 | |
|      * get it from GTID maps repo
 | |
|      */
 | |
| 
 | |
|     /* - 1 - try to find a binlog file number by reading the directory */
 | |
|     if (!router->mariadb10_master_gtid)
 | |
|     {
 | |
|         root_len = strlen(router->fileroot);
 | |
|         if ((dirp = opendir(path)) == NULL)
 | |
|         {
 | |
|             MXS_ERROR("%s: Unable to read the binlog directory %s, %s.",
 | |
|                       router->service->name(),
 | |
|                       router->binlogdir,
 | |
|                       mxs_strerror(errno));
 | |
|             return 0;
 | |
|         }
 | |
|         while ((dp = readdir(dirp)) != NULL)
 | |
|         {
 | |
|             if (strncmp(dp->d_name, router->fileroot, root_len) == 0)
 | |
|             {
 | |
|                 i = atoi(dp->d_name + root_len + 1);
 | |
|                 if (i > n)
 | |
|                 {
 | |
|                     n = i;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         closedir(dirp);
 | |
| 
 | |
|         file_found = 0;
 | |
|         do
 | |
|         {
 | |
|             snprintf(filename,
 | |
|                      PATH_MAX,
 | |
|                      "%s/" BINLOG_NAMEFMT,
 | |
|                      path,
 | |
|                      router->fileroot,
 | |
|                      n);
 | |
|             if (access(filename, R_OK) != -1)
 | |
|             {
 | |
|                 file_found = 1;
 | |
|                 n++;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 file_found = 0;
 | |
|             }
 | |
|         }
 | |
|         while (file_found);
 | |
|         n--;
 | |
| 
 | |
|         if (n == 0)     // No binlog files found
 | |
|         {
 | |
|             snprintf(filename,
 | |
|                      PATH_MAX,
 | |
|                      BINLOG_NAMEFMT,
 | |
|                      router->fileroot,
 | |
|                      router->initbinlog);
 | |
| 
 | |
|             if (!blr_file_create(router, filename))
 | |
|             {
 | |
|                 return 0;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             snprintf(filename,
 | |
|                      PATH_MAX,
 | |
|                      BINLOG_NAMEFMT,
 | |
|                      router->fileroot,
 | |
|                      n);
 | |
|             blr_file_append(router, filename);
 | |
|         }
 | |
|         return 1;
 | |
|     }
 | |
|     /* - 2 - Get last file in GTID maps repo */
 | |
|     else
 | |
|     {
 | |
|         char f_prefix[BINLOG_FILE_EXTRA_INFO] = "";
 | |
|         MARIADB_GTID_INFO last_gtid;
 | |
|         memset(&last_gtid, 0, sizeof(last_gtid));
 | |
| 
 | |
|         // SELECT LAST FILE
 | |
|         if (!blr_get_last_file(router, &last_gtid)
 | |
|             || !last_gtid.gtid[0])
 | |
|         {
 | |
|             MXS_INFO("%s: cannot find any GTID in GTID maps repo",
 | |
|                      router->service->name());
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         /** We don't care about sequence being 0
 | |
|          * as this is a placeholder for new file in the repo.
 | |
|          * In case of file without transactions
 | |
|          * there is no real GTID with sequence:
 | |
|          * there should be a GTID_LIST event which holds
 | |
|          * the last GTID.
 | |
|          * The event is parsed while reading the binlog.
 | |
|          * Right now we need only the domain_id, server_id
 | |
|          * and filename.
 | |
|          */
 | |
|         sprintf(f_prefix,
 | |
|                 "%" PRIu32 "/%" PRIu32 "",
 | |
|                 last_gtid.gtid_elms.domain_id,
 | |
|                 last_gtid.gtid_elms.server_id);
 | |
| 
 | |
|         router->mariadb10_gtid_domain = last_gtid.gtid_elms.domain_id;
 | |
|         router->orig_masterid = last_gtid.gtid_elms.server_id;
 | |
| 
 | |
|         snprintf(filename,
 | |
|                  PATH_MAX,
 | |
|                  "%s/%s/%s",
 | |
|                  path,
 | |
|                  f_prefix,
 | |
|                  last_gtid.binlog_name);
 | |
|         if (access(filename, R_OK) != -1)
 | |
|         {
 | |
|             blr_file_append(router, last_gtid.binlog_name);
 | |
|             ret = 1;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             ret = blr_file_create(router, last_gtid.binlog_name);
 | |
|         }
 | |
| 
 | |
|         return ret;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Rotate the current log file with new one
 | |
|  *
 | |
|  * @param    router    The router instance
 | |
|  * @param    file      The new file to create
 | |
|  * @param    pos       The binlog position (not used)
 | |
|  * @return             1 on succes, 0 on failure
 | |
|  */
 | |
| int blr_file_rotate(ROUTER_INSTANCE* router, char* file, uint64_t pos)
 | |
| {
 | |
|     return blr_file_create(router, file);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * binlog files need an initial 4 magic bytes at the start. blr_file_add_magic()
 | |
|  * adds them.
 | |
|  *
 | |
|  * @param fd file descriptor to the open binlog file
 | |
|  * @return   True if the magic string could be written to the file.
 | |
|  */
 | |
| static bool blr_file_add_magic(int fd)
 | |
| {
 | |
|     static const unsigned char magic[] = BINLOG_MAGIC;
 | |
| 
 | |
|     ssize_t written = write(fd, magic, BINLOG_MAGIC_SIZE);
 | |
| 
 | |
|     return written == BINLOG_MAGIC_SIZE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new binlog file for the router to use.
 | |
|  *
 | |
|  * @param router        The router instance
 | |
|  * @param file          The binlog file name
 | |
|  * @return              Non-zero if the fie creation succeeded
 | |
|  */
 | |
| static int blr_file_create(ROUTER_INSTANCE* router, char* orig_file)
 | |
| {
 | |
|     if (strlen(orig_file) > BINLOG_FNAMELEN)
 | |
|     {
 | |
|         MXS_ERROR("The binlog filename %s is longer than "
 | |
|                   "the maximum allowed length %d.",
 | |
|                   orig_file,
 | |
|                   BINLOG_FNAMELEN);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     char file[strlen(orig_file) + 1];
 | |
|     strcpy(file, orig_file);
 | |
| 
 | |
|     int created = 0;
 | |
|     char err_msg[MXS_STRERROR_BUFLEN];
 | |
| 
 | |
|     char path[PATH_MAX + 1] = "";
 | |
| 
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, "/");
 | |
| 
 | |
|     /**
 | |
|      * Create file using domain and server_id prefix
 | |
|      */
 | |
|     if (router->mariadb10_compat
 | |
|         && router->mariadb10_master_gtid
 | |
|         && router->storage_type == BLR_BINLOG_STORAGE_TREE)
 | |
|     {
 | |
|         char prefix[BINLOG_FILE_EXTRA_INFO];
 | |
|         // Add prefix
 | |
|         sprintf(prefix,
 | |
|                 "%" PRIu32 "/%" PRIu32 "/",
 | |
|                 router->mariadb10_gtid_domain,
 | |
|                 router->orig_masterid);
 | |
|         strcat(path, prefix);
 | |
| 
 | |
|         /**
 | |
|          * Check and create $domain_id/$server_id dir
 | |
|          */
 | |
|         if (!mxs_mkdir_all(path, 0700))
 | |
|         {
 | |
|             MXS_ERROR("Service %s, Failed to create binlog"
 | |
|                       " directory tree '%s': [%d] %s",
 | |
|                       router->service->name(),
 | |
|                       path,
 | |
|                       errno,
 | |
|                       mxs_strerror(errno));
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Set final file name full path
 | |
|     strcat(path, file);
 | |
| 
 | |
|     int fd = open(path, O_RDWR | O_CREAT, 0660);
 | |
| 
 | |
|     if (fd != -1)
 | |
|     {
 | |
|         if (blr_file_add_magic(fd))
 | |
|         {
 | |
|             close(router->binlog_fd);
 | |
|             pthread_mutex_lock(&router->binlog_lock);
 | |
| 
 | |
|             /// Use an intermediate buffer in case the source and destination overlap
 | |
|             char new_binlog[strlen(file) + 1];
 | |
|             strcpy(new_binlog, file);
 | |
|             strcpy(router->binlog_name, new_binlog);
 | |
| 
 | |
|             router->binlog_fd = fd;
 | |
|             /* Initial position after the magic number */
 | |
|             router->current_pos = BINLOG_MAGIC_SIZE;
 | |
|             router->binlog_position = BINLOG_MAGIC_SIZE;
 | |
|             router->current_safe_event = BINLOG_MAGIC_SIZE;
 | |
|             router->last_written = BINLOG_MAGIC_SIZE;
 | |
|             pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|             created = 1;
 | |
| 
 | |
|             /**
 | |
|              * Add an entry in GTID repo with size 4
 | |
|              * and router->orig_masterid.
 | |
|              * This allows SHOW BINARY LOGS to list
 | |
|              * new created files.
 | |
|              */
 | |
|             if (router->mariadb10_compat
 | |
|                 && router->mariadb10_gtid)
 | |
|             {
 | |
|                 blr_file_update_gtid(router);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("%s: Failed to write magic string to "
 | |
|                       "created binlog file %s, %s.",
 | |
|                       router->service->name(),
 | |
|                       path,
 | |
|                       mxs_strerror(errno));
 | |
|             close(fd);
 | |
| 
 | |
|             if (!unlink(path))
 | |
|             {
 | |
|                 MXS_ERROR("%s: Failed to delete file %s, %s.",
 | |
|                           router->service->name(),
 | |
|                           path,
 | |
|                           mxs_strerror(errno));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("%s: Failed to create binlog file %s, %s.",
 | |
|                   router->service->name(),
 | |
|                   path,
 | |
|                   mxs_strerror(errno));
 | |
|     }
 | |
| 
 | |
|     return created;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Prepare an existing binlog file to be appended to.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param file      The binlog file name
 | |
|  */
 | |
| void blr_file_append(ROUTER_INSTANCE* router, char* file)
 | |
| {
 | |
|     char path[PATH_MAX + 1] = "";
 | |
|     int fd;
 | |
|     int flags = O_RDWR;
 | |
| 
 | |
|     /* If Master GTID registration is not set, then use append */
 | |
|     if (!router->mariadb10_master_gtid)
 | |
|     {
 | |
|         flags |= O_APPEND;
 | |
|     }
 | |
| 
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, "/");
 | |
| 
 | |
|     // Add file prefix
 | |
|     if (router->mariadb10_compat
 | |
|         && router->mariadb10_master_gtid
 | |
|         && router->storage_type == BLR_BINLOG_STORAGE_TREE)
 | |
|     {
 | |
|         char prefix[BINLOG_FILE_EXTRA_INFO];
 | |
|         sprintf(prefix,
 | |
|                 "%" PRIu32 "/%" PRIu32 "/",
 | |
|                 router->mariadb10_gtid_domain,
 | |
|                 router->orig_masterid);
 | |
|         strcat(path, prefix);
 | |
|     }
 | |
| 
 | |
|     // Add filename
 | |
|     strcat(path, file);
 | |
| 
 | |
|     if ((fd = open(path, flags, 0660)) == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to open binlog file %s for append.",
 | |
|                   path);
 | |
|         return;
 | |
|     }
 | |
|     fsync(fd);
 | |
|     close(router->binlog_fd);
 | |
|     pthread_mutex_lock(&router->binlog_lock);
 | |
|     memmove(router->binlog_name, file, BINLOG_FNAMELEN);
 | |
|     router->current_pos = lseek(fd, 0L, SEEK_END);
 | |
|     if (router->current_pos < 4)
 | |
|     {
 | |
|         if (router->current_pos == 0)
 | |
|         {
 | |
|             if (blr_file_add_magic(fd))
 | |
|             {
 | |
|                 router->current_pos = BINLOG_MAGIC_SIZE;
 | |
|                 router->binlog_position = BINLOG_MAGIC_SIZE;
 | |
|                 router->current_safe_event = BINLOG_MAGIC_SIZE;
 | |
|                 router->last_written = BINLOG_MAGIC_SIZE;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("%s: Could not write magic to binlog file.",
 | |
|                           router->service->name());
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* If for any reason the file's length is between 1 and 3 bytes
 | |
|              * then report an error. */
 | |
|             MXS_ERROR("%s: binlog file %s has an invalid length %lu.",
 | |
|                       router->service->name(),
 | |
|                       path,
 | |
|                       router->current_pos);
 | |
|             close(fd);
 | |
|             pthread_mutex_unlock(&router->binlog_lock);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     router->binlog_fd = fd;
 | |
|     pthread_mutex_unlock(&router->binlog_lock);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Write a binlog entry to disk.
 | |
|  *
 | |
|  * @param router The router instance
 | |
|  * @param buf    The binlog record
 | |
|  * @param len    The length of the binlog record
 | |
|  * @return       Return the number of bytes written
 | |
|  */
 | |
| int blr_write_binlog_record(ROUTER_INSTANCE* router,
 | |
|                             REP_HEADER* hdr,
 | |
|                             uint32_t size,
 | |
|                             uint8_t* buf)
 | |
| {
 | |
|     int n = 0;
 | |
|     bool write_start_encryption_event = false;
 | |
|     uint64_t file_offset = router->current_pos;
 | |
|     uint32_t event_size[4];
 | |
| 
 | |
|     /* Track whether FORMAT_DESCRIPTION_EVENT has been received */
 | |
|     if (hdr->event_type == FORMAT_DESCRIPTION_EVENT)
 | |
|     {
 | |
|         write_start_encryption_event = true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check first for possible hole looking at current pos and next pos
 | |
|      * Fill the gap with a self generated ignorable event
 | |
|      * Binlog file position is incremented by blr_write_special_event()
 | |
|      */
 | |
|     if (hdr->next_pos && (hdr->next_pos > (file_offset + size)))
 | |
|     {
 | |
|         uint64_t hole_size = hdr->next_pos - file_offset - size;
 | |
|         if (!blr_write_special_event(router,
 | |
|                                      file_offset,
 | |
|                                      hole_size,
 | |
|                                      hdr,
 | |
|                                      BLRM_IGNORABLE))
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
|         n = hole_size;
 | |
|     }
 | |
| 
 | |
|     if (router->encryption.enabled && router->encryption_ctx != NULL)
 | |
|     {
 | |
|         GWBUF* encrypted;
 | |
|         uint8_t* encr_ptr;
 | |
|         if ((encrypted = blr_prepare_encrypted_event(router,
 | |
|                                                      buf,
 | |
|                                                      size,
 | |
|                                                      router->current_pos,
 | |
|                                                      NULL,
 | |
|                                                      BINLOG_FLAG_ENCRYPT)) == NULL)
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         encr_ptr = GWBUF_DATA(encrypted);
 | |
| 
 | |
|         n = pwrite(router->binlog_fd,
 | |
|                    encr_ptr,
 | |
|                    size,
 | |
|                    router->last_written);
 | |
| 
 | |
|         gwbuf_free(encrypted);
 | |
|         encrypted = NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* Write current received event form master */
 | |
|         n = pwrite(router->binlog_fd,
 | |
|                    buf,
 | |
|                    size,
 | |
|                    router->last_written);
 | |
|     }
 | |
| 
 | |
|     /* Check write operation result*/
 | |
|     if (n != static_cast<int>(size))
 | |
|     {
 | |
|         MXS_ERROR("%s: Failed to write binlog record at %lu of %s, %s. "
 | |
|                   "Truncating to previous record.",
 | |
|                   router->service->name(),
 | |
|                   router->binlog_position,
 | |
|                   router->binlog_name,
 | |
|                   mxs_strerror(errno));
 | |
|         /* Remove any partial event that was written */
 | |
|         if (ftruncate(router->binlog_fd, router->binlog_position))
 | |
|         {
 | |
|             MXS_ERROR("%s: Failed to truncate binlog record at %lu of %s, %s. ",
 | |
|                       router->service->name(),
 | |
|                       router->binlog_position,
 | |
|                       router->binlog_name,
 | |
|                       mxs_strerror(errno));
 | |
|         }
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /* Increment offsets */
 | |
|     pthread_mutex_lock(&router->binlog_lock);
 | |
|     router->current_pos = hdr->next_pos;
 | |
|     router->last_written += size;
 | |
|     router->last_event_pos = hdr->next_pos - hdr->event_size;
 | |
|     pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|     /* Check whether adding the Start Encryption event into current binlog */
 | |
|     if (router->encryption.enabled && write_start_encryption_event)
 | |
|     {
 | |
|         uint64_t event_size = sizeof(MXS_START_ENCRYPTION_EVENT);
 | |
|         uint64_t file_offset = router->current_pos;
 | |
|         if (router->master_chksum)
 | |
|         {
 | |
|             event_size += BINLOG_EVENT_CRC_SIZE;
 | |
|         }
 | |
|         if (!blr_write_special_event(router,
 | |
|                                      file_offset,
 | |
|                                      event_size,
 | |
|                                      hdr,
 | |
|                                      BLRM_START_ENCRYPTION))
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
|         /* At this point the router->encryption_ctx is set:
 | |
|          * Encryption of new events can start
 | |
|          */
 | |
|         write_start_encryption_event = false;
 | |
| 
 | |
|         n = event_size;
 | |
|     }
 | |
| 
 | |
|     return n;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Flush the content of the binlog file to disk.
 | |
|  *
 | |
|  * @param   router  The binlog router
 | |
|  */
 | |
| void blr_file_flush(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     fsync(router->binlog_fd);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Checks if the BLFILE file pointer has same informations
 | |
|  * as in MARIADB_GTID_INFO pointer
 | |
|  * and if binlog files are the same.
 | |
|  *
 | |
|  * This routine is used in blr_open_binlog()
 | |
|  * in order to use an already opened BLFILE file
 | |
|  * or open a new one.
 | |
|  *
 | |
|  * @param file       Pointer to a BLFILE opened file
 | |
|  * @param binlog     The slave binlog name
 | |
|  * @param info       The MARIADB_GTID_INFO GTID info of
 | |
|  *                   current slave file
 | |
|  * @param s_tree     Whether to use MARIADB_GTID_INFO info
 | |
|  * @return           True if BLFILE can be reused, false otherwise
 | |
|  */
 | |
| static bool inline blr_is_same_slave_file(const BLFILE* file,
 | |
|                                           const char* binlog,
 | |
|                                           const MARIADB_GTID_INFO* info,
 | |
|                                           bool s_tree)
 | |
| {
 | |
|     if (s_tree)
 | |
|     {
 | |
|         return (file->gtid_elms.domain_id == info->gtid_elms.domain_id)
 | |
|                && (file->gtid_elms.server_id == info->gtid_elms.server_id)
 | |
|                && (strcmp(file->binlog_name, binlog) == 0);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return strcmp(file->binlog_name, binlog) == 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Open a binlog file for reading binlog records
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param binlog    The binlog filename
 | |
|  * @param info      MariaDB GTID info
 | |
|  *                  with rep_domain and
 | |
|  *                  server_id.
 | |
|  *                  These two fileds are
 | |
|  *                  used as file prefix
 | |
|  *                  for binlog file name.
 | |
|  *
 | |
|  * @return a binlog file record
 | |
|  */
 | |
| BLFILE* blr_open_binlog(ROUTER_INSTANCE* router,
 | |
|                         const char* binlog,
 | |
|                         const MARIADB_GTID_INFO* info)
 | |
| {
 | |
|     size_t len = strlen(binlog);
 | |
|     if (len > BINLOG_FNAMELEN)
 | |
|     {
 | |
|         MXS_ERROR("The binlog filename %s is longer than "
 | |
|                   "the maximum allowed length %d.",
 | |
|                   binlog,
 | |
|                   BINLOG_FNAMELEN);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     len += (strlen(router->binlogdir) + 1);     // +1 for the '.'
 | |
|     if (len > PATH_MAX)
 | |
|     {
 | |
|         MXS_ERROR("The length of %s/%s is longer than the "
 | |
|                   "maximum allowed length %d.",
 | |
|                   router->binlogdir,
 | |
|                   binlog,
 | |
|                   PATH_MAX);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     char path[PATH_MAX + 1] = "";
 | |
|     BLFILE* file;
 | |
| 
 | |
|     pthread_mutex_lock(&router->fileslock);
 | |
|     file = router->files;
 | |
| 
 | |
|     while (file
 | |
|            &&   /* Check whether 'file' can be reused */
 | |
|            !blr_is_same_slave_file(file,
 | |
|                                    binlog,
 | |
|                                    info,
 | |
|                                    router->storage_type == BLR_BINLOG_STORAGE_TREE))
 | |
|     {
 | |
|         file = file->next;
 | |
|     }
 | |
| 
 | |
|     if (file)
 | |
|     {
 | |
|         /* Reuse 'file' */
 | |
|         file->refcnt++;
 | |
|         pthread_mutex_unlock(&router->fileslock);
 | |
|         return file;
 | |
|     }
 | |
| 
 | |
|     if ((file = (BLFILE*)MXS_CALLOC(1, sizeof(BLFILE))) == NULL)
 | |
|     {
 | |
|         pthread_mutex_unlock(&router->fileslock);
 | |
|         return NULL;
 | |
|     }
 | |
|     strcpy(file->binlog_name, binlog);
 | |
|     file->refcnt = 1;
 | |
|     file->cache = 0;
 | |
| 
 | |
|     /* Store additional file informations */
 | |
|     if (info)
 | |
|     {
 | |
|         memcpy(&file->gtid_elms,
 | |
|                &info->gtid_elms,
 | |
|                sizeof(MARIADB_GTID_ELEMS));
 | |
|     }
 | |
| 
 | |
|     pthread_mutex_init(&file->lock, NULL);
 | |
| 
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, "/");
 | |
| 
 | |
|     /* Add tree prefix: "domain_id/server_id" */
 | |
|     if (info)
 | |
|     {
 | |
|         char t_prefix[BINLOG_FILE_EXTRA_INFO];
 | |
|         sprintf(t_prefix,
 | |
|                 "%" PRIu32 "/%" PRIu32 "/",
 | |
|                 info->gtid_elms.domain_id,
 | |
|                 info->gtid_elms.server_id);
 | |
|         strcat(path, t_prefix);
 | |
|     }
 | |
| 
 | |
|     /* Add file name */
 | |
|     strcat(path, binlog);
 | |
| 
 | |
|     if ((file->fd = open(path, O_RDONLY, 0660)) == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to open binlog file %s", path);
 | |
|         MXS_FREE(file);
 | |
|         pthread_mutex_unlock(&router->fileslock);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     file->next = router->files;
 | |
|     router->files = file;
 | |
|     pthread_mutex_unlock(&router->fileslock);
 | |
| 
 | |
|     return file;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Read a replication event into a GWBUF structure.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param file      File record
 | |
|  * @param pos       Position of binlog record to read
 | |
|  * @param hdr       Binlog header to populate
 | |
|  * @param errmsg    Allocated BINLOG_ERROR_MSG_LEN bytes message error buffer
 | |
|  * @param enc_ctx   Encryption context for binlog file being read
 | |
|  * @return          The binlog record wrapped in a GWBUF structure
 | |
|  */
 | |
| GWBUF* blr_read_binlog(ROUTER_INSTANCE* router,
 | |
|                        BLFILE* file,
 | |
|                        unsigned long pos,
 | |
|                        REP_HEADER* hdr,
 | |
|                        char* errmsg,
 | |
|                        const SLAVE_ENCRYPTION_CTX* enc_ctx)
 | |
| {
 | |
|     uint8_t hdbuf[BINLOG_EVENT_HDR_LEN];
 | |
|     GWBUF* result;
 | |
|     unsigned char* data;
 | |
|     int n;
 | |
|     unsigned long filelen = 0;
 | |
|     struct stat statb;
 | |
| 
 | |
|     memset(hdbuf, '\0', BINLOG_EVENT_HDR_LEN);
 | |
| 
 | |
|     /* set error indicator */
 | |
|     hdr->ok = SLAVE_POS_READ_ERR;
 | |
| 
 | |
|     if (!file)
 | |
|     {
 | |
|         snprintf(errmsg,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "Invalid file pointer for requested binlog at position %lu",
 | |
|                  pos);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     pthread_mutex_lock(&file->lock);
 | |
|     if (fstat(file->fd, &statb) == 0)
 | |
|     {
 | |
|         filelen = statb.st_size;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (file->fd == -1)
 | |
|         {
 | |
|             hdr->ok = SLAVE_POS_BAD_FD;
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "blr_read_binlog called with invalid file->fd, pos %lu",
 | |
|                      pos);
 | |
|             pthread_mutex_unlock(&file->lock);
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
|     pthread_mutex_unlock(&file->lock);
 | |
| 
 | |
|     if (pos > filelen)
 | |
|     {
 | |
|         pthread_mutex_lock(&router->binlog_lock);
 | |
|         pthread_mutex_lock(&file->lock);
 | |
| 
 | |
|         /* Check whether is current router file */
 | |
|         if (!blr_compare_binlogs(router,
 | |
|                                  &file->gtid_elms,
 | |
|                                  router->binlog_name,
 | |
|                                  file->binlog_name))
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Requested position %lu is beyond "
 | |
|                      "'closed' binlog file '%s', size %lu. Generating Error '1236'",
 | |
|                      pos,
 | |
|                      file->binlog_name,
 | |
|                      filelen);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Requested position %lu is beyond "
 | |
|                      "end of the latest binlog file '%s', size %lu. Disconnecting",
 | |
|                      pos,
 | |
|                      file->binlog_name,
 | |
|                      filelen);
 | |
| 
 | |
|             /* Slave will be disconnected by the calling routine */
 | |
|             hdr->ok = SLAVE_POS_BEYOND_EOF;
 | |
|         }
 | |
| 
 | |
|         pthread_mutex_unlock(&file->lock);
 | |
|         pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     pthread_mutex_lock(&router->binlog_lock);
 | |
|     pthread_mutex_lock(&file->lock);
 | |
| 
 | |
|     /* Check current router file and router position */
 | |
|     if (blr_compare_binlogs(router,
 | |
|                             &file->gtid_elms,
 | |
|                             router->binlog_name,
 | |
|                             file->binlog_name)
 | |
|         && pos >= router->binlog_position)
 | |
|     {
 | |
|         if (pos > router->binlog_position)
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Requested binlog position %lu is unsafe. "
 | |
|                      "Latest safe position %lu, end of binlog file %lu",
 | |
|                      pos,
 | |
|                      router->binlog_position,
 | |
|                      router->current_pos);
 | |
| 
 | |
|             hdr->ok = SLAVE_POS_READ_UNSAFE;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* accessing last position is ok */
 | |
|             hdr->ok = SLAVE_POS_READ_OK;
 | |
|         }
 | |
| 
 | |
|         pthread_mutex_unlock(&file->lock);
 | |
|         pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     pthread_mutex_unlock(&file->lock);
 | |
|     pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|     /* Read the header information from the file */
 | |
|     if ((n = pread(file->fd,
 | |
|                    hdbuf,
 | |
|                    BINLOG_EVENT_HDR_LEN,
 | |
|                    pos)) != BINLOG_EVENT_HDR_LEN)
 | |
|     {
 | |
|         switch (n)
 | |
|         {
 | |
|         case 0:
 | |
|             /* Just set ok indicator: nothing to log*/
 | |
|             hdr->ok = SLAVE_POS_READ_OK;
 | |
|             break;
 | |
| 
 | |
|         case -1:
 | |
|             {
 | |
|                 snprintf(errmsg,
 | |
|                          BINLOG_ERROR_MSG_LEN,
 | |
|                          "Failed to read binlog file '%s'; (%s), event at %lu",
 | |
|                          file->binlog_name,
 | |
|                          mxs_strerror(errno),
 | |
|                          pos);
 | |
| 
 | |
|                 if (errno == EBADF)
 | |
|                 {
 | |
|                     snprintf(errmsg,
 | |
|                              BINLOG_ERROR_MSG_LEN,
 | |
|                              "Bad file descriptor for binlog file '%s', "
 | |
|                              "refcount %d, descriptor %d, event at %lu",
 | |
|                              file->binlog_name,
 | |
|                              file->refcnt,
 | |
|                              file->fd,
 | |
|                              pos);
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Bogus data in log event header; "
 | |
|                      "expected %d bytes but read %d, position %lu, binlog file '%s'",
 | |
|                      BINLOG_EVENT_HDR_LEN,
 | |
|                      n,
 | |
|                      pos,
 | |
|                      file->binlog_name);
 | |
|             break;
 | |
|         }
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /* If enc_ctx is NULL check position */
 | |
|     if (enc_ctx == NULL)
 | |
|     {
 | |
|         hdr->timestamp = EXTRACT32(hdbuf);
 | |
|         hdr->event_type = hdbuf[4];
 | |
|         hdr->serverid = EXTRACT32(&hdbuf[5]);
 | |
|         hdr->event_size = extract_field(&hdbuf[9], 32);
 | |
|         hdr->next_pos = EXTRACT32(&hdbuf[13]);
 | |
|         hdr->flags = EXTRACT16(&hdbuf[17]);
 | |
| 
 | |
|         /**
 | |
|          * Binlog event check based on Replication Header content and pos
 | |
|          */
 | |
|         if (!blr_binlog_event_check(router,
 | |
|                                     pos,
 | |
|                                     hdr,
 | |
|                                     file->binlog_name,
 | |
|                                     errmsg))
 | |
|         {
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         /* Try to read again the binlog event */
 | |
|         if (hdr->next_pos < pos && hdr->event_type != ROTATE_EVENT)
 | |
|         {
 | |
|             MXS_ERROR("Next position in header appears to be incorrect "
 | |
|                       "rereading event header at pos %lu in file %s, "
 | |
|                       "file size is %lu. Master will write %lu in %s next.",
 | |
|                       pos,
 | |
|                       file->binlog_name,
 | |
|                       filelen,
 | |
|                       router->binlog_position,
 | |
|                       router->binlog_name);
 | |
| 
 | |
|             if ((n = pread(file->fd,
 | |
|                            hdbuf,
 | |
|                            BINLOG_EVENT_HDR_LEN,
 | |
|                            pos)) != BINLOG_EVENT_HDR_LEN)
 | |
|             {
 | |
|                 switch (n)
 | |
|                 {
 | |
|                 case 0:
 | |
|                     MXS_INFO("Reached end of binlog file at %lu.",
 | |
|                              pos);
 | |
| 
 | |
|                     /* set ok indicator */
 | |
|                     hdr->ok = SLAVE_POS_READ_OK;
 | |
|                     break;
 | |
| 
 | |
|                 case -1:
 | |
|                     {
 | |
|                         snprintf(errmsg,
 | |
|                                  BINLOG_ERROR_MSG_LEN,
 | |
|                                  "Failed to reread header in binlog file '%s'; (%s), event at %lu",
 | |
|                                  file->binlog_name,
 | |
|                                  mxs_strerror(errno),
 | |
|                                  pos);
 | |
| 
 | |
|                         if (errno == EBADF)
 | |
|                         {
 | |
|                             snprintf(errmsg,
 | |
|                                      BINLOG_ERROR_MSG_LEN,
 | |
|                                      "Bad file descriptor rereading header for binlog file '%s', "
 | |
|                                      "refcount %d, descriptor %d, event at %lu",
 | |
|                                      file->binlog_name,
 | |
|                                      file->refcnt,
 | |
|                                      file->fd,
 | |
|                                      pos);
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                 default:
 | |
|                     snprintf(errmsg,
 | |
|                              BINLOG_ERROR_MSG_LEN,
 | |
|                              "Bogus data rereading log event header; "
 | |
|                              "expected %d bytes but read %d, position %lu in binlog file '%s'",
 | |
|                              BINLOG_EVENT_HDR_LEN,
 | |
|                              n,
 | |
|                              pos,
 | |
|                              file->binlog_name);
 | |
|                     break;
 | |
|                 }
 | |
|                 return NULL;
 | |
|             }
 | |
| 
 | |
|             /* Fill replication header struct */
 | |
|             hdr->timestamp = EXTRACT32(hdbuf);
 | |
|             hdr->event_type = hdbuf[4];
 | |
|             hdr->serverid = EXTRACT32(&hdbuf[5]);
 | |
|             hdr->event_size = extract_field(&hdbuf[9], 32);
 | |
|             hdr->next_pos = EXTRACT32(&hdbuf[13]);
 | |
|             hdr->flags = EXTRACT16(&hdbuf[17]);
 | |
| 
 | |
|             if (hdr->next_pos < pos && hdr->event_type != ROTATE_EVENT)
 | |
|             {
 | |
|                 snprintf(errmsg,
 | |
|                          BINLOG_ERROR_MSG_LEN,
 | |
|                          "Next event position still incorrect after rereading, "
 | |
|                          "event at %lu in binlog file '%s'",
 | |
|                          pos,
 | |
|                          file->binlog_name);
 | |
|                 return NULL;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Next position corrected by "
 | |
|                           "rereading");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /**
 | |
|          * The encryption context is set at this point.
 | |
|          *
 | |
|          * Only the event size is in "clear", use it.
 | |
|          */
 | |
|         hdr->event_size = extract_field(&hdbuf[9], 32);
 | |
|     }
 | |
| 
 | |
|     /* Allocate memory for the binlog event */
 | |
|     if ((result = gwbuf_alloc(hdr->event_size)) == NULL)
 | |
|     {
 | |
|         snprintf(errmsg,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "Failed to allocate memory for binlog entry, "
 | |
|                  "size %d, event at %lu in binlog file '%s'",
 | |
|                  hdr->event_size,
 | |
|                  pos,
 | |
|                  file->binlog_name);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     data = GWBUF_DATA(result);
 | |
| 
 | |
|     memcpy(data, hdbuf, BINLOG_EVENT_HDR_LEN);      // Copy the header in the buffer
 | |
| 
 | |
|     if ((n = pread(file->fd,
 | |
|                    &data[BINLOG_EVENT_HDR_LEN],
 | |
|                    hdr->event_size - BINLOG_EVENT_HDR_LEN,
 | |
|                    pos + BINLOG_EVENT_HDR_LEN))
 | |
|         != static_cast<ssize_t>(hdr->event_size - BINLOG_EVENT_HDR_LEN))    // Read the balance
 | |
|     {
 | |
|         if (n == 0)
 | |
|         {
 | |
|             MXS_INFO("Reached end of binlog file at %lu while reading remaining bytes.",
 | |
|                      pos);
 | |
| 
 | |
|             /* set ok indicator */
 | |
|             hdr->ok = SLAVE_POS_READ_OK;
 | |
| 
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         if (n == -1)
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Error reading the binlog event at %lu in binlog file '%s'; "
 | |
|                      "(%s), expected %d bytes.",
 | |
|                      pos,
 | |
|                      file->binlog_name,
 | |
|                      mxs_strerror(errno),
 | |
|                      hdr->event_size - BINLOG_EVENT_HDR_LEN);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Bogus data in log event entry; "
 | |
|                      "expected %d bytes but got %d, position %lu in binlog file '%s'",
 | |
|                      hdr->event_size - BINLOG_EVENT_HDR_LEN,
 | |
|                      n,
 | |
|                      pos,
 | |
|                      file->binlog_name);
 | |
| 
 | |
|             if (filelen != 0 && filelen - pos < hdr->event_size)
 | |
|             {
 | |
|                 snprintf(errmsg,
 | |
|                          BINLOG_ERROR_MSG_LEN,
 | |
|                          "Binlog event is close to the end of the binlog file; "
 | |
|                          "current file size is %lu, event at %lu in binlog file '%s'",
 | |
|                          filelen,
 | |
|                          pos,
 | |
|                          file->binlog_name);
 | |
|             }
 | |
|             blr_log_header(LOG_ERR,
 | |
|                            "Possible malformed event header",
 | |
|                            hdbuf);
 | |
|         }
 | |
| 
 | |
|         gwbuf_free(result);
 | |
| 
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check whether we need to decrypt the current event.
 | |
|      * Note: if event is before the first_enc_event_pos don't decrypt it
 | |
|      */
 | |
|     if (enc_ctx && pos >= enc_ctx->first_enc_event_pos)
 | |
|     {
 | |
|         GWBUF* decrypted_event;
 | |
|         uint8_t* decrypt_ptr;
 | |
|         /* prepare and decrypt the event */
 | |
|         if ((decrypted_event = blr_prepare_encrypted_event(router,
 | |
|                                                            data,
 | |
|                                                            hdr->event_size,
 | |
|                                                            pos,
 | |
|                                                            enc_ctx->nonce,
 | |
|                                                            BINLOG_FLAG_DECRYPT)) == NULL)
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Binlog event decryption error: "
 | |
|                      "file size is %lu, event at %lu in binlog file '%s'",
 | |
|                      filelen,
 | |
|                      pos,
 | |
|                      file->binlog_name);
 | |
|             gwbuf_free(result);
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         decrypt_ptr = GWBUF_DATA(decrypted_event);
 | |
| 
 | |
|         /* Fill replication header struct */
 | |
|         hdr->timestamp = EXTRACT32(decrypt_ptr);
 | |
|         hdr->event_type = decrypt_ptr[4];
 | |
|         hdr->serverid = EXTRACT32(&decrypt_ptr[5]);
 | |
|         hdr->event_size = extract_field(&decrypt_ptr[9], 32);
 | |
|         hdr->next_pos = EXTRACT32(&decrypt_ptr[13]);
 | |
|         hdr->flags = EXTRACT16(&decrypt_ptr[17]);
 | |
| 
 | |
|         /* Free data read from disk */
 | |
|         gwbuf_free(result);
 | |
| 
 | |
|         /**
 | |
|          * Binlog event check based on Rep Header content and pos
 | |
|          */
 | |
|         if (!blr_binlog_event_check(router,
 | |
|                                     pos,
 | |
|                                     hdr,
 | |
|                                     file->binlog_name,
 | |
|                                     errmsg))
 | |
|         {
 | |
|             gwbuf_free(decrypted_event);
 | |
|             return NULL;
 | |
|         }
 | |
| 
 | |
|         /* Set the decrypted event as result */
 | |
|         result = decrypted_event;
 | |
|     }
 | |
| 
 | |
|     /* set OK indicator */
 | |
|     hdr->ok = SLAVE_POS_READ_OK;
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Close a binlog file that has been opened to read binlog records
 | |
|  *
 | |
|  * The open binlog files are shared between multiple slaves that are
 | |
|  * reading the same binlog file.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param file      The file to close
 | |
|  */
 | |
| void blr_close_binlog(ROUTER_INSTANCE* router, BLFILE* file)
 | |
| {
 | |
|     mxb_assert(file);
 | |
|     pthread_mutex_lock(&router->fileslock);
 | |
|     file->refcnt--;
 | |
|     if (file->refcnt == 0)
 | |
|     {
 | |
|         if (router->files == file)
 | |
|         {
 | |
|             router->files = file->next;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             BLFILE* ptr = router->files;
 | |
|             while (ptr && ptr->next != file)
 | |
|             {
 | |
|                 ptr = ptr->next;
 | |
|             }
 | |
|             if (ptr)
 | |
|             {
 | |
|                 ptr->next = file->next;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         file = NULL;
 | |
|     }
 | |
|     pthread_mutex_unlock(&router->fileslock);
 | |
| 
 | |
|     if (file)
 | |
|     {
 | |
|         close(file->fd);
 | |
|         file->fd = -1;
 | |
|         MXS_FREE(file);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Log the event header of  binlog event
 | |
|  *
 | |
|  * @param   priority The syslog priority (LOG_ERR, LOG_WARNING, etc.)
 | |
|  * @param   msg  A message strign to preceed the header with
 | |
|  * @param   ptr  The event header raw data
 | |
|  */
 | |
| static void blr_log_header(int priority, const char* msg, uint8_t* ptr)
 | |
| {
 | |
|     char buf[400], * bufp;
 | |
|     int i;
 | |
| 
 | |
|     bufp = buf;
 | |
|     bufp += sprintf(bufp, "%s: ", msg);
 | |
|     for (i = 0; i < BINLOG_EVENT_HDR_LEN; i++)
 | |
|     {
 | |
|         bufp += sprintf(bufp, "0x%02x ", ptr[i]);
 | |
|     }
 | |
|     MXS_LOG_MESSAGE(priority, "%s", buf);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the size of the current binlog file
 | |
|  *
 | |
|  * @param file  The binlog file
 | |
|  * @return  The current size of the binlog file
 | |
|  */
 | |
| unsigned long blr_file_size(BLFILE* file)
 | |
| {
 | |
|     struct stat statb;
 | |
| 
 | |
|     if (fstat(file->fd, &statb) == 0)
 | |
|     {
 | |
|         return statb.st_size;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Write the response packet to a cache file so that MaxScale can respond
 | |
|  * even if there is no master running when MaxScale starts.
 | |
|  *
 | |
|  * cache dir is 'cache' under router->binlogdir
 | |
|  *
 | |
|  * @param router    The instance of the router
 | |
|  * @param response  The name of the response, used to name the cached file
 | |
|  * @param buf       The buffer to written to the cache
 | |
|  */
 | |
| void blr_cache_response(ROUTER_INSTANCE* router, char* response, GWBUF* buf)
 | |
| {
 | |
|     static const char CACHE[] = "/cache";
 | |
|     size_t len = strlen(router->binlogdir)
 | |
|         + (sizeof(CACHE) - 1)
 | |
|         + sizeof('/')
 | |
|         + strlen(response);
 | |
|     if (len > PATH_MAX)
 | |
|     {
 | |
|         MXS_ERROR("The cache path %s%s/%s is longer than "
 | |
|                   "the maximum allowed length %d.",
 | |
|                   router->binlogdir,
 | |
|                   CACHE,
 | |
|                   response,
 | |
|                   PATH_MAX);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     char path[PATH_MAX + 1] = "";
 | |
|     int fd;
 | |
| 
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, CACHE);
 | |
| 
 | |
|     if (access(path, R_OK) == -1)
 | |
|     {
 | |
|         // TODO: Check error, ENOENT or something else.
 | |
|         mkdir(path, 0700);
 | |
|         // TODO: Check return value.
 | |
|     }
 | |
| 
 | |
|     strcat(path, "/");
 | |
|     strcat(path, response);
 | |
| 
 | |
|     if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0660)) == -1)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
|     if (write(fd, GWBUF_DATA(buf), GWBUF_LENGTH(buf)) == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to write cached response: %d, %s",
 | |
|                   errno,
 | |
|                   mxs_strerror(errno));
 | |
|     }
 | |
| 
 | |
|     close(fd);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Read a cached copy of a master response message. This allows
 | |
|  * the router to start and serve any binlogs it already has on disk
 | |
|  * if the master is not available.
 | |
|  *
 | |
|  * cache dir is 'cache' under router->binlogdir
 | |
|  *
 | |
|  * @param router    The router instance structure
 | |
|  * @param response  The name of the response
 | |
|  * @return A pointer to a GWBUF structure
 | |
|  */
 | |
| GWBUF* blr_cache_read_response(ROUTER_INSTANCE* router, const char* response)
 | |
| {
 | |
|     static const char CACHE[] = "/cache";
 | |
|     size_t len = strlen(router->binlogdir)
 | |
|         + (sizeof(CACHE) - 1)
 | |
|         + sizeof('/')
 | |
|         + strlen(response);
 | |
|     if (len > PATH_MAX)
 | |
|     {
 | |
|         MXS_ERROR("The cache path %s%s/%s is longer than "
 | |
|                   "the maximum allowed length %d.",
 | |
|                   router->binlogdir,
 | |
|                   CACHE,
 | |
|                   response,
 | |
|                   PATH_MAX);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     struct stat statb;
 | |
|     char path[PATH_MAX + 1] = "";
 | |
|     int fd;
 | |
|     GWBUF* buf;
 | |
| 
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, CACHE);
 | |
|     strcat(path, "/");
 | |
|     strcat(path, response);
 | |
| 
 | |
|     if ((fd = open(path, O_RDONLY)) == -1)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     if (fstat(fd, &statb) != 0)
 | |
|     {
 | |
|         close(fd);
 | |
|         return NULL;
 | |
|     }
 | |
|     if ((buf = gwbuf_alloc(statb.st_size)) == NULL)
 | |
|     {
 | |
|         close(fd);
 | |
|         return NULL;
 | |
|     }
 | |
|     if (read(fd, GWBUF_DATA(buf), statb.st_size) == -1)
 | |
|     {
 | |
|         MXS_ERROR("Failed to read cached response: %d, %s",
 | |
|                   errno,
 | |
|                   mxs_strerror(errno));
 | |
|     }
 | |
|     close(fd);
 | |
|     return buf;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Does the next binlog file in the sequence for the slave exist.
 | |
|  *
 | |
|  * If the next file exists in the GTID maps_repo,
 | |
|  * no matter if it's not readable in the filesystem,
 | |
|  * the GTID elems in the slave->f_info struct will be overwritten.
 | |
|  * - file
 | |
|  * - domain_id
 | |
|  * - server_id
 | |
|  *
 | |
|  * Note: slave->binlogfile is always untouched
 | |
|  *
 | |
|  * @param router       The router instance
 | |
|  * @param slave        The slave in question
 | |
|  * @param next_file    The next_file buffer
 | |
|  * @return             0 if the next file does not exist
 | |
|  */
 | |
| int blr_file_next_exists(ROUTER_INSTANCE* router,
 | |
|                          ROUTER_SLAVE* slave,
 | |
|                          char* next_file)
 | |
| {
 | |
|     char* errmsg = NULL;
 | |
|     char* sptr;
 | |
|     char bigbuf[PATH_MAX + 1];
 | |
|     char select_query[GTID_SQL_BUFFER_SIZE];
 | |
|     const char select_tpl[] = "SELECT "
 | |
|                               "(rep_domain || '/' || server_id || '/' || binlog_file) AS file, "
 | |
|                               "rep_domain, "
 | |
|                               "server_id, "
 | |
|                               "binlog_file "
 | |
|                               "FROM gtid_maps "
 | |
|                               "WHERE id = "
 | |
|                               "(SELECT MAX(id) "
 | |
|                               "FROM gtid_maps "
 | |
|                               "WHERE (binlog_file='%s' AND "
 | |
|                               "rep_domain = %" PRIu32 " AND "
 | |
|                                                       "server_id = %" PRIu32 ")) + 1;";
 | |
| 
 | |
|     MARIADB_GTID_ELEMS gtid_elms = {};
 | |
|     MARIADB_GTID_INFO result;
 | |
|     memset(&result, 0, sizeof(result));
 | |
| 
 | |
|     if ((sptr = strrchr(slave->binlog_name, '.')) == NULL)
 | |
|     {
 | |
|         next_file[0] = '\0';
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * The next file to read could be determined in two ways:
 | |
|      * - 1 - file sequence +1
 | |
|      * - 2 - next file form GTID maps repo
 | |
|      */
 | |
|     if (router->storage_type == BLR_BINLOG_STORAGE_FLAT)
 | |
|     {
 | |
|         /**
 | |
|          * Next file is the one with +1 in sequence
 | |
|          */
 | |
|         char buf[BINLOG_FNAMELEN + 1];
 | |
|         int filenum;
 | |
|         filenum = atoi(sptr + 1);
 | |
|         sprintf(buf, BINLOG_NAMEFMT, router->fileroot, filenum + 1);
 | |
|         sprintf(bigbuf, "%s/%s", router->binlogdir, buf);
 | |
|         // Set the new file name in the output
 | |
|         memcpy(next_file, buf, BINLOG_FNAMELEN);
 | |
|         next_file[BINLOG_FNAMELEN] = '\0';
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /**
 | |
|          * Next file is selected in GTID maps repo
 | |
|          */
 | |
|         snprintf(select_query,
 | |
|                  GTID_SQL_BUFFER_SIZE,
 | |
|                  select_tpl,
 | |
|                  slave->binlog_name,
 | |
|                  slave->f_info.gtid_elms.domain_id,
 | |
|                  slave->f_info.gtid_elms.server_id);
 | |
| 
 | |
|         /* Find the GTID */
 | |
|         if (sqlite3_exec(router->gtid_maps,
 | |
|                          select_query,
 | |
|                          gtid_file_select_cb,
 | |
|                          &result,
 | |
|                          &errmsg) != SQLITE_OK)
 | |
|         {
 | |
|             MXS_ERROR("Failed to select next file of %s"
 | |
|                       " from GTID maps DB: %s, select [%s]",
 | |
|                       slave->binlog_name,
 | |
|                       errmsg,
 | |
|                       select_query);
 | |
|             sqlite3_free(errmsg);
 | |
|             next_file[0] = '\0';
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         // Check whether the query has a result
 | |
|         if (result.binlog_name[0])
 | |
|         {
 | |
|             // Full filename path
 | |
|             sprintf(bigbuf,
 | |
|                     "%s/%" PRIu32 "/%" PRIu32 "/%s",
 | |
|                     router->binlogdir,
 | |
|                     result.gtid_elms.domain_id,
 | |
|                     result.gtid_elms.server_id,
 | |
|                     result.binlog_name);
 | |
|             // Set the new file name in the output
 | |
|             memcpy(next_file, result.binlog_name, BINLOG_FNAMELEN);
 | |
|             next_file[BINLOG_FNAMELEN] = '\0';
 | |
| 
 | |
|             MXS_DEBUG("The next Binlog file from GTID maps repo is [%s]",
 | |
|                       bigbuf);
 | |
| 
 | |
|             pthread_mutex_lock(&slave->catch_lock);
 | |
| 
 | |
|             /**
 | |
|              * Update GTID elems in the slave->f_info struct:
 | |
|              * file and domain_id / server_id
 | |
|              * Note: slave->binlog_name is untouched
 | |
|              */
 | |
|             strcpy(slave->f_info.binlog_name, result.binlog_name);
 | |
|             slave->f_info.gtid_elms.domain_id = result.gtid_elms.domain_id;
 | |
|             slave->f_info.gtid_elms.server_id = result.gtid_elms.server_id;
 | |
| 
 | |
|             pthread_mutex_unlock(&slave->catch_lock);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("The next Binlog file from GTID maps repo "
 | |
|                         "of current slave file [%" PRIu32 "/%" PRIu32 "/%s] "
 | |
|                                                                       "has not been found. Router state is [%s]",
 | |
|                         slave->f_info.gtid_elms.domain_id,
 | |
|                         slave->f_info.gtid_elms.server_id,
 | |
|                         slave->binlog_name,
 | |
|                         blrm_states[router->master_state]);
 | |
| 
 | |
|             next_file[0] = '\0';
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Check whether the new file exists
 | |
|     if (access(bigbuf, R_OK) == -1)
 | |
|     {
 | |
|         MXS_ERROR("The next Binlog file [%s] from GTID maps repo "
 | |
|                   "cannot be read or accessed.",
 | |
|                   bigbuf);
 | |
|         return 0;
 | |
|     }
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Read all replication events from a binlog file.
 | |
|  *
 | |
|  * Routine detects errors and pending transactions
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param action    Whether to fix errors or blank events at pos
 | |
|  * @param debug     Whether to enable or not the debug for events
 | |
|  * @return          0 on success, >0 on failure
 | |
|  */
 | |
| int blr_read_events_all_events(ROUTER_INSTANCE* router,
 | |
|                                BINLOG_FILE_FIX* action,
 | |
|                                int debug)
 | |
| {
 | |
|     unsigned long filelen = 0;
 | |
|     struct stat statb;
 | |
|     uint8_t hdbuf[BINLOG_EVENT_HDR_LEN];
 | |
|     uint8_t* data;
 | |
|     GWBUF* result = NULL;
 | |
|     GWBUF* decrypted_event = NULL;
 | |
|     unsigned long long pos = 4;
 | |
|     unsigned long long last_known_commit = 4;
 | |
| 
 | |
|     REP_HEADER hdr;
 | |
|     int pending_transaction = BLRM_NO_TRANSACTION;
 | |
|     int n;
 | |
|     int db_name_len;
 | |
|     uint8_t* ptr;
 | |
|     int var_block_len;
 | |
|     int statement_len;
 | |
|     int found_chksum = 0;
 | |
|     int event_error = 0;
 | |
|     unsigned long transaction_events = 0;
 | |
|     unsigned long total_events = 0;
 | |
|     unsigned long total_bytes = 0;
 | |
|     unsigned long n_transactions = 0;
 | |
|     unsigned long max_events = 0;
 | |
|     unsigned long event_bytes = 0;
 | |
|     unsigned long max_bytes = 0;
 | |
|     double average_events = 0;
 | |
|     double average_bytes = 0;
 | |
|     BINLOG_EVENT_DESC first_event;
 | |
|     BINLOG_EVENT_DESC last_event;
 | |
|     BINLOG_EVENT_DESC fde_event;
 | |
|     int fde_seen = 0;
 | |
|     int start_encryption_seen = 0;
 | |
|     bool fix = action ? action->fix : false;
 | |
|     bool replace_trx_events = false;
 | |
| 
 | |
|     memset(&first_event, '\0', sizeof(first_event));
 | |
|     memset(&last_event, '\0', sizeof(last_event));
 | |
|     memset(&fde_event, '\0', sizeof(fde_event));
 | |
| 
 | |
|     if (router->binlog_fd == -1)
 | |
|     {
 | |
|         MXS_ERROR("Current binlog file %s is not open",
 | |
|                   router->binlog_name);
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     if (fstat(router->binlog_fd, &statb) == 0)
 | |
|     {
 | |
|         filelen = statb.st_size;
 | |
|     }
 | |
| 
 | |
|     router->current_pos = 4;
 | |
|     router->binlog_position = 4;
 | |
|     router->current_safe_event = 4;
 | |
| 
 | |
|     while (1)
 | |
|     {
 | |
| 
 | |
|         /* Read the header information from the file */
 | |
|         if ((n = pread(router->binlog_fd,
 | |
|                        hdbuf,
 | |
|                        BINLOG_EVENT_HDR_LEN,
 | |
|                        pos)) != BINLOG_EVENT_HDR_LEN)
 | |
|         {
 | |
|             switch (n)
 | |
|             {
 | |
|             case 0:
 | |
|                 if (!(debug & BLR_CHECK_ONLY))
 | |
|                 {
 | |
|                     MXS_DEBUG("End of binlog file [%s] at %llu.",
 | |
|                               router->binlog_name,
 | |
|                               pos);
 | |
|                 }
 | |
| 
 | |
|                 if (n_transactions)
 | |
|                 {
 | |
|                     average_events = (double)((double)total_events / (double)n_transactions) * (1.0);
 | |
|                 }
 | |
|                 if (n_transactions)
 | |
|                 {
 | |
|                     average_bytes = (double)((double)total_bytes / (double)n_transactions) * (1.0);
 | |
|                 }
 | |
| 
 | |
|                 /* Report Binlog First and Last event */
 | |
|                 if (pos > 4 && !(debug & BLR_CHECK_ONLY))
 | |
|                 {
 | |
|                     if (first_event.event_type == 0)
 | |
|                     {
 | |
|                         blr_print_binlog_details(router, fde_event, last_event);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         blr_print_binlog_details(router, first_event, last_event);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 /* Report Transaction Summary */
 | |
|                 if (!(debug & BLR_CHECK_ONLY) && n_transactions != 0)
 | |
|                 {
 | |
|                     char total_label[2] = "";
 | |
|                     char average_label[2] = "";
 | |
|                     char max_label[2] = "";
 | |
|                     double format_total_bytes = total_bytes;
 | |
|                     double format_max_bytes = max_bytes;
 | |
| 
 | |
|                     blr_format_event_size(&format_total_bytes, total_label);
 | |
|                     blr_format_event_size(&average_bytes, average_label);
 | |
|                     blr_format_event_size(&format_max_bytes, max_label);
 | |
| 
 | |
|                     MXS_NOTICE("Transaction Summary for binlog '%s'\n"
 | |
|                                "\t\t\tDescription        %17s%17s%17s\n\t\t\t"
 | |
|                                "No. of Transactions %16lu\n\t\t\t"
 | |
|                                "No. of Events       %16lu %16.1f %16lu\n\t\t\t"
 | |
|                                "No. of Bytes       %16.1f%s%16.1f%s%16.1f%s",
 | |
|                                router->binlog_name,
 | |
|                                "Total",
 | |
|                                "Average",
 | |
|                                "Max",
 | |
|                                n_transactions,
 | |
|                                total_events,
 | |
|                                average_events,
 | |
|                                max_events,
 | |
|                                format_total_bytes,
 | |
|                                total_label,
 | |
|                                average_bytes,
 | |
|                                average_label,
 | |
|                                format_max_bytes,
 | |
|                                max_label);
 | |
|                 }
 | |
| 
 | |
|                 if (pending_transaction)
 | |
|                 {
 | |
|                     MXS_WARNING("Binlog file %s contains a previous Opened "
 | |
|                                 "Transaction @ %llu. This pos is safe for slaves",
 | |
|                                 router->binlog_name,
 | |
|                                 last_known_commit);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case -1:
 | |
|                 {
 | |
|                     MXS_ERROR("Failed to read binlog file %s at position %llu"
 | |
|                               " (%s).",
 | |
|                               router->binlog_name,
 | |
|                               pos,
 | |
|                               mxs_strerror(errno));
 | |
| 
 | |
|                     if (errno == EBADF)
 | |
|                     {
 | |
|                         MXS_ERROR("Bad file descriptor in read binlog for file %s"
 | |
|                                   ", descriptor %d.",
 | |
|                                   router->binlog_name,
 | |
|                                   router->binlog_fd);
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 MXS_ERROR("Short read when reading the header. "
 | |
|                           "Expected 19 bytes but got %d bytes. "
 | |
|                           "Binlog file is %s, position %llu",
 | |
|                           n,
 | |
|                           router->binlog_name,
 | |
|                           pos);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             /**
 | |
|              * Check for errors and force last_known_commit position
 | |
|              * and current pos
 | |
|              */
 | |
| 
 | |
|             if (pending_transaction)
 | |
|             {
 | |
|                 router->binlog_position = last_known_commit;
 | |
|                 router->current_safe_event = last_known_commit;
 | |
|                 router->current_pos = pos;
 | |
|                 router->pending_transaction.state = BLRM_TRANSACTION_START;
 | |
| 
 | |
|                 MXS_ERROR("Binlog '%s' ends at position %lu "
 | |
|                           "and has an incomplete transaction at %lu. ",
 | |
|                           router->binlog_name,
 | |
|                           router->current_pos,
 | |
|                           router->binlog_position);
 | |
| 
 | |
|                 return 0;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /* any error */
 | |
|                 if (n != 0)
 | |
|                 {
 | |
|                     router->binlog_position = last_known_commit;
 | |
|                     router->current_safe_event = last_known_commit;
 | |
|                     router->current_pos = pos;
 | |
| 
 | |
|                     MXS_WARNING("an error has been found. "
 | |
|                                 "Setting safe pos to %lu, current pos %lu",
 | |
|                                 router->binlog_position,
 | |
|                                 router->current_pos);
 | |
|                     if (fix)
 | |
|                     {
 | |
|                         if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                         {
 | |
|                             MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                        router->binlog_name,
 | |
|                                        router->binlog_position);
 | |
|                             fsync(router->binlog_fd);
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     return 1;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     router->binlog_position = pos;
 | |
|                     router->current_safe_event = pos;
 | |
|                     router->current_pos = pos;
 | |
| 
 | |
|                     return 0;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (start_encryption_seen)
 | |
|         {
 | |
|             uint8_t iv[AES_BLOCK_SIZE + 1] = "";
 | |
|             char iv_hex[AES_BLOCK_SIZE * 2 + 1] = "";
 | |
|             /* The event size, 4 bytes, is written in clear: use it */
 | |
|             uint32_t event_size = EXTRACT32(hdbuf + BINLOG_EVENT_LEN_OFFSET);
 | |
| 
 | |
|             /**
 | |
|              * Events are encrypted.
 | |
|              *
 | |
|              * Print the IV for the current encrypted event.
 | |
|              */
 | |
| 
 | |
|             if (debug & BLR_REPORT_REP_HEADER)
 | |
|             {
 | |
|                 /* Get binlog file "nonce" and other data from router encryption_ctx */
 | |
|                 BINLOG_ENCRYPTION_CTX* enc_ctx = router->encryption_ctx;
 | |
| 
 | |
|                 /* Encryption IV is 12 bytes nonce + 4 bytes event position */
 | |
|                 memcpy(iv, enc_ctx->nonce, BLRM_NONCE_LENGTH);
 | |
|                 gw_mysql_set_byte4(iv + BLRM_NONCE_LENGTH, (unsigned long)pos);
 | |
| 
 | |
|                 /* Human readable version */
 | |
|                 gw_bin2hex(iv_hex, iv, BLRM_IV_LENGTH);
 | |
| 
 | |
|                 MXS_DEBUG("** Encrypted Event @ %lu: the IV is %s, size is %lu, next pos is %lu\n",
 | |
|                           (unsigned long)pos,
 | |
|                           iv_hex,
 | |
|                           (unsigned long)event_size,
 | |
|                           (unsigned long)(pos + event_size));
 | |
|             }
 | |
| 
 | |
|             /* Set event size only in hdr struct, before decryption */
 | |
|             hdr.event_size = event_size;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             char errmsg[BINLOG_ERROR_MSG_LEN + 1] = "";
 | |
|             /* fill replication header struct */
 | |
|             hdr.timestamp = EXTRACT32(hdbuf);
 | |
|             hdr.event_type = hdbuf[4];
 | |
|             hdr.serverid = EXTRACT32(&hdbuf[5]);
 | |
|             hdr.event_size = extract_field(&hdbuf[9], 32);
 | |
|             hdr.next_pos = EXTRACT32(&hdbuf[13]);
 | |
|             hdr.flags = EXTRACT16(&hdbuf[17]);
 | |
| 
 | |
|             /* Check event */
 | |
|             if (!blr_binlog_event_check(router,
 | |
|                                         pos,
 | |
|                                         &hdr,
 | |
|                                         router->binlog_name,
 | |
|                                         errmsg))
 | |
|             {
 | |
|                 router->binlog_position = last_known_commit;
 | |
|                 router->current_safe_event = last_known_commit;
 | |
|                 router->current_pos = pos;
 | |
| 
 | |
|                 MXS_WARNING("an error has been found in %s. "
 | |
|                             "Setting safe pos to %lu, current pos %lu. "
 | |
|                             "ErrMsg [%s]",
 | |
|                             router->binlog_name,
 | |
|                             router->binlog_position,
 | |
|                             router->current_pos,
 | |
|                             errmsg);
 | |
| 
 | |
|                 if (fix)
 | |
|                 {
 | |
|                     if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                     {
 | |
|                         MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                    router->binlog_name,
 | |
|                                    router->binlog_position);
 | |
|                         fsync(router->binlog_fd);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             if (hdr.event_size <= 0)
 | |
|             {
 | |
|                 MXS_ERROR("Event size error: "
 | |
|                           "size %d at %llu.",
 | |
|                           hdr.event_size,
 | |
|                           pos);
 | |
| 
 | |
|                 router->binlog_position = last_known_commit;
 | |
|                 router->current_safe_event = last_known_commit;
 | |
|                 router->current_pos = pos;
 | |
| 
 | |
|                 MXS_WARNING("an error has been found. "
 | |
|                             "Setting safe pos to %lu, current pos %lu",
 | |
|                             router->binlog_position,
 | |
|                             router->current_pos);
 | |
|                 if (fix)
 | |
|                 {
 | |
|                     if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                     {
 | |
|                         MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                    router->binlog_name,
 | |
|                                    router->binlog_position);
 | |
|                         fsync(router->binlog_fd);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Allocate a GWBUF for the event */
 | |
|         if ((result = gwbuf_alloc(hdr.event_size)) == NULL)
 | |
|         {
 | |
|             MXS_ERROR("Failed to allocate memory for binlog entry, "
 | |
|                       "size %d at %llu.",
 | |
|                       hdr.event_size,
 | |
|                       pos);
 | |
| 
 | |
|             router->binlog_position = last_known_commit;
 | |
|             router->current_safe_event = last_known_commit;
 | |
|             router->current_pos = pos;
 | |
| 
 | |
|             MXS_WARNING("an error has been found. "
 | |
|                         "Setting safe pos to %lu, current pos %lu",
 | |
|                         router->binlog_position,
 | |
|                         router->current_pos);
 | |
| 
 | |
|             if (fix)
 | |
|             {
 | |
|                 if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                 {
 | |
|                     MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                router->binlog_name,
 | |
|                                router->binlog_position);
 | |
|                     fsync(router->binlog_fd);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return 1;
 | |
|         }
 | |
| 
 | |
|         /* Copy the header in the buffer */
 | |
|         data = GWBUF_DATA(result);
 | |
|         memcpy(data, hdbuf, BINLOG_EVENT_HDR_LEN);      // Copy the header in
 | |
| 
 | |
|         /* Read event data */
 | |
|         n = pread(router->binlog_fd,
 | |
|                   &data[BINLOG_EVENT_HDR_LEN],
 | |
|                   hdr.event_size - BINLOG_EVENT_HDR_LEN,
 | |
|                   pos + BINLOG_EVENT_HDR_LEN);
 | |
|         if (n != static_cast<ssize_t>(hdr.event_size - BINLOG_EVENT_HDR_LEN))
 | |
|         {
 | |
|             if (n == -1)
 | |
|             {
 | |
|                 MXS_ERROR("Error reading the event at %llu in %s. "
 | |
|                           "%s, expected %d bytes.",
 | |
|                           pos,
 | |
|                           router->binlog_name,
 | |
|                           mxs_strerror(errno),
 | |
|                           hdr.event_size - BINLOG_EVENT_HDR_LEN);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Short read when reading the event at %llu in %s. "
 | |
|                           "Expected %d bytes got %d bytes.",
 | |
|                           pos,
 | |
|                           router->binlog_name,
 | |
|                           hdr.event_size - BINLOG_EVENT_HDR_LEN,
 | |
|                           n);
 | |
| 
 | |
|                 if (filelen > 0 && filelen - pos < hdr.event_size)
 | |
|                 {
 | |
|                     MXS_ERROR("Binlog event is close to the end of the binlog file %s, "
 | |
|                               "size is %lu.",
 | |
|                               router->binlog_name,
 | |
|                               filelen);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             gwbuf_free(result);
 | |
| 
 | |
|             router->binlog_position = last_known_commit;
 | |
|             router->current_safe_event = last_known_commit;
 | |
|             router->current_pos = pos;
 | |
| 
 | |
|             MXS_WARNING("an error has been found. "
 | |
|                         "Setting safe pos to %lu, current pos %lu",
 | |
|                         router->binlog_position,
 | |
|                         router->current_pos);
 | |
|             if (fix)
 | |
|             {
 | |
|                 if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                 {
 | |
|                     MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                router->binlog_name,
 | |
|                                router->binlog_position);
 | |
|                     fsync(router->binlog_fd);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return 1;
 | |
|         }
 | |
| 
 | |
|         /* check for pending transaction */
 | |
|         if (pending_transaction == 0)
 | |
|         {
 | |
|             last_known_commit = pos;
 | |
|         }
 | |
| 
 | |
|         /* get firts event timestamp, after FDE */
 | |
|         if (fde_seen)
 | |
|         {
 | |
|             first_event.event_time = (unsigned long)hdr.timestamp;
 | |
|             first_event.event_type = hdr.event_type;
 | |
|             first_event.event_pos = pos;
 | |
|             fde_seen = 0;
 | |
|         }
 | |
| 
 | |
|         /* decrypt events */
 | |
|         if (start_encryption_seen)
 | |
|         {
 | |
|             uint8_t iv[AES_BLOCK_SIZE + 1] = "";
 | |
|             char iv_hex[AES_BLOCK_SIZE * 2 + 1] = "";
 | |
|             uint32_t event_size = EXTRACT32(hdbuf + BINLOG_EVENT_LEN_OFFSET);
 | |
|             uint8_t* decrypt_ptr;
 | |
|             unsigned long next_pos;
 | |
|             char errmsg[BINLOG_ERROR_MSG_LEN + 1] = "";
 | |
| 
 | |
|             /**
 | |
|              * Events are encrypted.
 | |
|              */
 | |
| 
 | |
|             if ((decrypted_event = blr_prepare_encrypted_event(router,
 | |
|                                                                data,
 | |
|                                                                hdr.event_size,
 | |
|                                                                pos,
 | |
|                                                                NULL,
 | |
|                                                                BINLOG_FLAG_DECRYPT)) == NULL)
 | |
|             {
 | |
|                 MXS_ERROR("Error while decrypting event at pos %lu, size %lu",
 | |
|                           (unsigned long)pos,
 | |
|                           (unsigned long)hdr.event_size);
 | |
|                 router->m_errno = BINLOG_FATAL_ERROR_READING;
 | |
|                 gwbuf_free(result);
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             decrypt_ptr = GWBUF_DATA(decrypted_event);
 | |
| 
 | |
|             /* fill replication header struct */
 | |
|             hdr.timestamp = EXTRACT32(decrypt_ptr);
 | |
|             hdr.event_type = decrypt_ptr[4];
 | |
|             hdr.serverid = EXTRACT32(&decrypt_ptr[5]);
 | |
|             hdr.event_size = extract_field(&decrypt_ptr[9], 32);
 | |
|             hdr.next_pos = EXTRACT32(&decrypt_ptr[13]);
 | |
|             hdr.flags = EXTRACT16(&decrypt_ptr[17]);
 | |
| 
 | |
|             /* Check event */
 | |
|             if (!blr_binlog_event_check(router,
 | |
|                                         pos,
 | |
|                                         &hdr,
 | |
|                                         router->binlog_name,
 | |
|                                         errmsg))
 | |
|             {
 | |
|                 router->m_errno = BINLOG_FATAL_ERROR_READING;
 | |
|                 gwbuf_free(decrypted_event);
 | |
|                 gwbuf_free(result);
 | |
|                 MXS_ERROR("Error while decrypting event: %s", errmsg);
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             /* get event content after event header */
 | |
|             ptr = decrypt_ptr + BINLOG_EVENT_HDR_LEN;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* get event content after event header */
 | |
|             ptr = data + BINLOG_EVENT_HDR_LEN;
 | |
|         }
 | |
| 
 | |
|         /* check for FORMAT DESCRIPTION EVENT */
 | |
|         if (hdr.event_type == FORMAT_DESCRIPTION_EVENT)
 | |
|         {
 | |
|             int event_header_length;
 | |
|             int check_alg;
 | |
|             uint8_t* checksum;
 | |
|             char buf_t[40];
 | |
|             struct  tm tm_t;
 | |
| 
 | |
|             fde_seen = 1;
 | |
|             fde_event.event_time = (unsigned long)hdr.timestamp;
 | |
|             fde_event.event_type = hdr.event_type;
 | |
|             fde_event.event_pos = pos;
 | |
| 
 | |
|             localtime_r(&fde_event.event_time, &tm_t);
 | |
|             asctime_r(&tm_t, buf_t);
 | |
| 
 | |
|             if (buf_t[strlen(buf_t) - 1] == '\n')
 | |
|             {
 | |
|                 buf_t[strlen(buf_t) - 1] = '\0';
 | |
|             }
 | |
| 
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 MXS_DEBUG("- Format Description event FDE @ %llu, size %lu, time %lu (%s)",
 | |
|                           pos,
 | |
|                           (unsigned long)hdr.event_size,
 | |
|                           fde_event.event_time,
 | |
|                           buf_t);
 | |
|             }
 | |
| 
 | |
|             /* FDE is:
 | |
|              *
 | |
|              * 2 bytes          binlog-version
 | |
|              * string[50]       mysql-server version
 | |
|              * 4 bytes          create timestamp
 | |
|              * 1                event header length, 19 is the current length
 | |
|              * string[p]        event type header lengths:
 | |
|              *                  an array indexed by [Binlog Event Type - 1]
 | |
|              */
 | |
| 
 | |
|             /* ptr now points to event_header_length byte.
 | |
|              * This offset is just 1 byte before the number of supported events offset
 | |
|              */
 | |
|             event_header_length = ptr[BLRM_FDE_EVENT_TYPES_OFFSET - 1];
 | |
| 
 | |
|             /* The number of supported events formula:
 | |
|              * number_of_events = event_size - (event_header_len + BLRM_FDE_EVENT_TYPES_OFFSET)
 | |
|              */
 | |
|             int n_events = hdr.event_size - event_header_length - BLRM_FDE_EVENT_TYPES_OFFSET;
 | |
| 
 | |
|             /**
 | |
|              * The FDE event also carries 5 additional bytes:
 | |
|              *
 | |
|              * 1 byte is the checksum_alg_type and 4 bytes are the computed crc32
 | |
|              *
 | |
|              * These 5 bytes are always present even if alg_type is NONE/UNDEF:
 | |
|              * then the 4 crc32 bytes must not be checked, whatever the value is.
 | |
|              *
 | |
|              * In case of CRC32 algo_type the 4 bytes contain the event crc32.
 | |
|              */
 | |
|             int fde_extra_bytes = BINLOG_EVENT_CRC_ALGO_TYPE + BINLOG_EVENT_CRC_SIZE;
 | |
| 
 | |
|             /* Now remove from the calculated number of events the extra 5 bytes */
 | |
|             n_events -= fde_extra_bytes;
 | |
| 
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 MXS_DEBUG("       FDE ServerVersion [%50s]", ptr + 2);
 | |
| 
 | |
|                 MXS_DEBUG("       FDE Header EventLength %i"
 | |
|                           ", N. of supported MySQL/MariaDB events %i",
 | |
|                           event_header_length,
 | |
|                           n_events);
 | |
|             }
 | |
| 
 | |
|             /* Check whether Master is sending events with CRC32 checksum */
 | |
|             checksum = ptr + hdr.event_size - event_header_length - fde_extra_bytes;
 | |
|             check_alg = checksum[0];
 | |
| 
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 MXS_DEBUG("       FDE Checksum alg desc %i, alg type %s",
 | |
|                           check_alg,
 | |
|                           check_alg == 1 ?
 | |
|                           "BINLOG_CHECKSUM_ALG_CRC32" : "NONE or UNDEF");
 | |
|             }
 | |
|             if (check_alg == 1)
 | |
|             {
 | |
|                 /* Set checksum found indicator */
 | |
|                 found_chksum = 1;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 found_chksum = 0;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if ((debug & BLR_REPORT_REP_HEADER))
 | |
|         {
 | |
|             const char* event_desc = blr_get_event_description(router, hdr.event_type);
 | |
|             MXS_DEBUG("%8s==== Event Header ====\n%39sEvent Pos %lu\n%39sEvent time %lu\n%39s"
 | |
|                       "Event size %lu\n%39sEvent Type %u (%s)\n%39s"
 | |
|                       "Server Id %lu\n%39sNextPos %lu\n%39sFlags %u",
 | |
|                       " ",
 | |
|                       " ",
 | |
|                       (unsigned long) pos,
 | |
|                       " ",
 | |
|                       (unsigned long)hdr.timestamp,
 | |
|                       " ",
 | |
|                       (unsigned long)hdr.event_size,
 | |
|                       " ",
 | |
|                       hdr.event_type,
 | |
|                       event_desc ? event_desc : "NULL",
 | |
|                       " ",
 | |
|                       (unsigned long)hdr.serverid,
 | |
|                       " ",
 | |
|                       (unsigned long)hdr.next_pos,
 | |
|                       " ",
 | |
|                       hdr.flags);
 | |
|             if (found_chksum)
 | |
|             {
 | |
|                 char hex_checksum[BINLOG_EVENT_CRC_SIZE * 2 + strlen(BLR_REPORT_CHECKSUM_FORMAT) + 1];
 | |
|                 blr_report_checksum(hdr, ptr, hex_checksum);
 | |
|                 MXS_DEBUG("%8s%s", " ", hex_checksum);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Detect possible Start Encryption Event */
 | |
|         if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT)
 | |
|         {
 | |
|             char nonce_hex[AES_BLOCK_SIZE * 2 + 1] = "";
 | |
|             MXS_START_ENCRYPTION_EVENT ste_event = {};
 | |
|             void* mem = MXS_CALLOC(1, sizeof(BINLOG_ENCRYPTION_CTX));
 | |
|             BINLOG_ENCRYPTION_CTX* new_encryption_ctx =
 | |
|                 static_cast<BINLOG_ENCRYPTION_CTX*>(mem);
 | |
| 
 | |
|             if (new_encryption_ctx == NULL)
 | |
|             {
 | |
|                 router->m_errno = BINLOG_FATAL_ERROR_READING;
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             /* The start encryption event data is 17 bytes long:
 | |
|              * Scheme = 1
 | |
|              * Key Version: 4
 | |
|              * nonce = 12
 | |
|              */
 | |
| 
 | |
|             /* Fill the event content, after the event header */
 | |
|             ste_event.binlog_crypto_scheme = ptr[0];
 | |
|             ste_event.binlog_key_version = extract_field(ptr + 1, 32);
 | |
|             memcpy(ste_event.nonce, ptr + 1 + 4, BLRM_NONCE_LENGTH);
 | |
| 
 | |
|             /* Fill the encryption_ctx */
 | |
|             memcpy(new_encryption_ctx->nonce, ste_event.nonce, BLRM_NONCE_LENGTH);
 | |
|             new_encryption_ctx->binlog_crypto_scheme = ste_event.binlog_crypto_scheme;
 | |
|             memcpy(&new_encryption_ctx->binlog_key_version,
 | |
|                    &ste_event.binlog_key_version,
 | |
|                    BLRM_KEY_VERSION_LENGTH);
 | |
| 
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 /* Hex representation of nonce */
 | |
|                 gw_bin2hex(nonce_hex, ste_event.nonce, BLRM_NONCE_LENGTH);
 | |
| 
 | |
|                 MXS_DEBUG("- START_ENCRYPTION event @ %llu, size %lu, next pos is @ %lu, flags %u",
 | |
|                           pos,
 | |
|                           (unsigned long)hdr.event_size,
 | |
|                           (unsigned long)hdr.next_pos,
 | |
|                           hdr.flags);
 | |
| 
 | |
|                 MXS_DEBUG("        Encryption scheme: %u, key_version: %u,"
 | |
|                           " nonce: %s\n",
 | |
|                           ste_event.binlog_crypto_scheme,
 | |
|                           ste_event.binlog_key_version,
 | |
|                           nonce_hex);
 | |
|             }
 | |
| 
 | |
|             if (router->encryption.key_len == 0)
 | |
|             {
 | |
|                 router->m_errno = BINLOG_FATAL_ERROR_READING;
 | |
|                 MXS_ERROR("*** The binlog is encrypted. No KEY/Algo found for decryption. ***");
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             start_encryption_seen = 1;
 | |
| 
 | |
|             /* Update the router encryption context */
 | |
|             MXS_FREE(router->encryption_ctx);
 | |
|             router->encryption_ctx = new_encryption_ctx;
 | |
|         }
 | |
| 
 | |
|         /* set last event time, pos and type */
 | |
|         last_event.event_time = (unsigned long)hdr.timestamp;
 | |
|         last_event.event_type = hdr.event_type;
 | |
|         last_event.event_pos = pos;
 | |
| 
 | |
|         /* Decode ROTATE EVENT */
 | |
|         if (hdr.event_type == ROTATE_EVENT)
 | |
|         {
 | |
|             int len, slen;
 | |
|             uint64_t new_pos;
 | |
|             char file[BINLOG_FNAMELEN + 1];
 | |
| 
 | |
|             len = hdr.event_size - BINLOG_EVENT_HDR_LEN;
 | |
|             new_pos = extract_field(ptr + 4, 32);
 | |
|             new_pos <<= 32;
 | |
|             new_pos |= extract_field(ptr, 32);
 | |
|             slen = len - (8 + 4);           // Allow for position and CRC
 | |
|             if (found_chksum == 0)
 | |
|             {
 | |
|                 slen += 4;
 | |
|             }
 | |
|             if (slen > BINLOG_FNAMELEN)
 | |
|             {
 | |
|                 slen = BINLOG_FNAMELEN;
 | |
|             }
 | |
|             memcpy(file, ptr + 8, slen);
 | |
|             file[slen] = 0;
 | |
| 
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 MXS_DEBUG("- Rotate event @ %llu, next file is [%s] @ %lu",
 | |
|                           pos,
 | |
|                           file,
 | |
|                           new_pos);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Find and report Transaction start for event replacing only */
 | |
|         if (action
 | |
|             && action->pos > 4
 | |
|             && action->replace_trx
 | |
|             && pos == action->pos
 | |
|             && pending_transaction)
 | |
|         {
 | |
|             MXS_NOTICE(">>> Position %lu belongs to a transaction started at pos %lu.",
 | |
|                        (unsigned long)pos,
 | |
|                        (unsigned long)last_known_commit);
 | |
|             MXS_NOTICE("This position will be used for replacing all related events.");
 | |
| 
 | |
|             /* Set Transaction start as the stating pos for events replacing */
 | |
|             action->pos = last_known_commit;
 | |
| 
 | |
|             /* Free resources */
 | |
|             gwbuf_free(result);
 | |
|             gwbuf_free(decrypted_event);
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Replace one event at pos or transaction events from pos:
 | |
|          * All events will be replaced by IGNORABLE events
 | |
|          */
 | |
|         if (fix
 | |
|             && action->pos > 4
 | |
|             && (pos == action->pos || replace_trx_events))
 | |
|         {
 | |
|             const char* event_desc = blr_get_event_description(router, hdr.event_type);
 | |
| 
 | |
|             if (action->replace_trx && !replace_trx_events)
 | |
|             {
 | |
|                 MXS_NOTICE("=== Replacing all events of Transaction at pos %lu"
 | |
|                            " with IGNORABLE EVENT event type",
 | |
|                            action->pos);
 | |
|             }
 | |
| 
 | |
|             MXS_NOTICE("=== Replace event (%s) at pos %lu with an IGNORABLE EVENT\n",
 | |
|                        event_desc ? event_desc : "unknown",
 | |
|                        (unsigned long)pos);
 | |
| 
 | |
|             router->last_written = pos;
 | |
|             router->master_chksum = found_chksum;
 | |
| 
 | |
|             /* Create and write Ingonrable event into binlog file at action->pos */
 | |
|             blr_write_special_event(router,
 | |
|                                     pos,
 | |
|                                     hdr.event_size,
 | |
|                                     &hdr,
 | |
|                                     BLRM_IGNORABLE);
 | |
| 
 | |
|             /* Set replace indicator: when COMMIT is seen later, it will be set to false */
 | |
|             replace_trx_events = action->replace_trx ? true : false;
 | |
|         }
 | |
| 
 | |
|         /* If MariaDB 10 compatibility:
 | |
|          * check for MARIADB10_GTID_EVENT with flags
 | |
|          * This marks the transaction starts instead of
 | |
|          * QUERY_EVENT with "BEGIN"
 | |
|          */
 | |
| 
 | |
|         if (router->mariadb10_compat)
 | |
|         {
 | |
|             if (hdr.event_type == MARIADB10_GTID_EVENT)
 | |
|             {
 | |
|                 uint64_t n_sequence;    /* 8 bytes */
 | |
|                 uint32_t domainid;      /* 4 bytes */
 | |
|                 unsigned int flags;     /* 1 byte */
 | |
|                 n_sequence = extract_field(ptr, 64);
 | |
|                 domainid = extract_field(ptr + 8, 32);
 | |
|                 flags = *(ptr + 8 + 4);
 | |
| 
 | |
|                 /**
 | |
|                  * Detect whether it's a standalone transaction:
 | |
|                  * there is no terminating COMMIT event.
 | |
|                  * i.e: a DDL or FLUSH TABLES etc
 | |
|                  */
 | |
|                 router->pending_transaction.standalone = flags & MARIADB_FL_STANDALONE;
 | |
| 
 | |
|                 if (pending_transaction > BLRM_NO_TRANSACTION)
 | |
|                 {
 | |
|                     MXS_ERROR("Transaction cannot be @ pos %llu: "
 | |
|                               "Another MariaDB 10 transaction (GTID %u-%u-%lu)"
 | |
|                               " was opened at %llu",
 | |
|                               pos,
 | |
|                               domainid,
 | |
|                               hdr.serverid,
 | |
|                               n_sequence,
 | |
|                               last_known_commit);
 | |
| 
 | |
|                     gwbuf_free(result);
 | |
| 
 | |
|                     break;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     char mariadb_gtid[GTID_MAX_LEN + 1];
 | |
|                     snprintf(mariadb_gtid,
 | |
|                              GTID_MAX_LEN,
 | |
|                              "%u-%u-%lu",
 | |
|                              domainid,
 | |
|                              hdr.serverid,
 | |
|                              n_sequence);
 | |
| 
 | |
|                     pending_transaction = BLRM_TRANSACTION_START;
 | |
| 
 | |
|                     router->pending_transaction.start_pos = pos;
 | |
|                     router->pending_transaction.end_pos = 0;
 | |
| 
 | |
|                     /* Set MariaDB GTID */
 | |
|                     if (router->mariadb10_gtid)
 | |
|                     {
 | |
|                         strcpy(router->pending_transaction.gtid, mariadb_gtid);
 | |
| 
 | |
|                         /* Save the pending GTID components */
 | |
|                         router->pending_transaction.gtid_elms.domain_id = domainid;
 | |
|                         router->pending_transaction.gtid_elms.server_id = hdr.serverid;
 | |
|                         router->pending_transaction.gtid_elms.seq_no = n_sequence;
 | |
|                     }
 | |
| 
 | |
|                     transaction_events = 0;
 | |
|                     event_bytes = 0;
 | |
|                     if (!(debug & BLR_CHECK_ONLY))
 | |
|                     {
 | |
|                         MXS_DEBUG("> MariaDB 10 Transaction (GTID %u-%u-%lu)"
 | |
|                                   " starts @ pos %llu",
 | |
|                                   domainid,
 | |
|                                   hdr.serverid,
 | |
|                                   n_sequence,
 | |
|                                   pos);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check for GTID_LIST_EVENT
 | |
|          */
 | |
|         if (router->mariadb10_compat)
 | |
|         {
 | |
|             char f_prefix[BINLOG_FILE_EXTRA_INFO] = "";
 | |
|             if (hdr.event_type == MARIADB10_GTID_GTID_LIST_EVENT)
 | |
|             {
 | |
|                 unsigned long n_gtids;
 | |
|                 char mariadb_gtid[GTID_MAX_LEN + 1] = "";
 | |
|                 MARIADB_GTID_INFO gtid_info;
 | |
|                 memset(>id_info, 0, sizeof(gtid_info));
 | |
| 
 | |
|                 n_gtids = extract_field(ptr, 32);
 | |
|                 /* The lower 28 bits are the number of GTIDs */
 | |
|                 n_gtids &= 0x01111111;
 | |
| 
 | |
|                 if (n_gtids)
 | |
|                 {
 | |
|                     ptr += 4;
 | |
|                     uint32_t domainid;      /* 4 bytes */
 | |
|                     domainid = extract_field(ptr, 32);
 | |
|                     ptr += 4;
 | |
| 
 | |
|                     uint32_t serverid;      /* 4 bytes */
 | |
|                     serverid = extract_field(ptr, 32);
 | |
|                     ptr += 4;
 | |
| 
 | |
|                     uint64_t n_sequence;    /* 8 bytes */
 | |
|                     n_sequence = extract_field(ptr, 64);
 | |
|                     ptr += 4;
 | |
| 
 | |
|                     snprintf(mariadb_gtid,
 | |
|                              GTID_MAX_LEN,
 | |
|                              "%" PRIu32 "-%" PRIu32 "-%" PRIu64 "",
 | |
|                              domainid,
 | |
|                              serverid,
 | |
|                              n_sequence);
 | |
| 
 | |
|                     MXS_DEBUG("GTID List Event has %lu GTIDs, first one is %s",
 | |
|                               n_gtids,
 | |
|                               mariadb_gtid);
 | |
| 
 | |
|                     if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
 | |
|                     {
 | |
|                         sprintf(f_prefix,
 | |
|                                 "%" PRIu32 "/%" PRIu32 "/",
 | |
|                                 domainid,
 | |
|                                 serverid);
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_DEBUG("GTID List Event has no GTIDs");
 | |
| 
 | |
|                     /* Try loading last found GTID */
 | |
|                     if (router->mariadb10_gtid
 | |
|                         && blr_load_last_mariadb_gtid(router, >id_info)
 | |
|                         && gtid_info.gtid[0])
 | |
|                     {
 | |
|                         snprintf(mariadb_gtid,
 | |
|                                  GTID_MAX_LEN + 1,
 | |
|                                  "%s",
 | |
|                                  gtid_info.gtid);
 | |
| 
 | |
|                         if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
 | |
|                         {
 | |
|                             sprintf(f_prefix,
 | |
|                                     "%" PRIu32 "/%" PRIu32 "/",
 | |
|                                     gtid_info.gtid_elms.domain_id,
 | |
|                                     gtid_info.gtid_elms.server_id);
 | |
|                         }
 | |
| 
 | |
|                         if (router->mariadb10_gtid)
 | |
|                         {
 | |
|                             MXS_INFO("Last found MariaDB 10 GTID"
 | |
|                                      " in GTID maps repo was (%s). File %s%s",
 | |
|                                      mariadb_gtid,
 | |
|                                      f_prefix,
 | |
|                                      gtid_info.binlog_name);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (router->mariadb10_gtid)
 | |
|                 {
 | |
|                     /* Set MariaDB GTID */
 | |
|                     strcpy(router->last_mariadb_gtid, mariadb_gtid);
 | |
| 
 | |
|                     if (router->mariadb10_gtid)
 | |
|                     {
 | |
|                         MXS_INFO("Last MariaDB 10 GTID (GTID_LIST event)"
 | |
|                                  " is (%s). File %s%s",
 | |
|                                  mariadb_gtid,
 | |
|                                  f_prefix,
 | |
|                                  router->binlog_name);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check QUERY_EVENT
 | |
|          *
 | |
|          * Check for BEGIN ( ONLY for mysql 5.6, mariadb 5.5 )
 | |
|          * Check for COMMIT (not transactional engines)
 | |
|          * Check for pending standalone transaction
 | |
|          */
 | |
| 
 | |
|         if (hdr.event_type == QUERY_EVENT)
 | |
|         {
 | |
|             char* statement_sql;
 | |
|             db_name_len = ptr[4 + 4];
 | |
|             var_block_len = ptr[4 + 4 + 1 + 2];
 | |
| 
 | |
|             statement_len =
 | |
|                 hdr.event_size
 | |
|                 - BINLOG_EVENT_HDR_LEN
 | |
|                 - (4 + 4 + 1 + 2 + 2 + var_block_len + 1 + db_name_len);
 | |
| 
 | |
|             statement_sql = static_cast<char*>(MXS_CALLOC(1, statement_len + 1));
 | |
|             if (statement_sql)
 | |
|             {
 | |
|                 memcpy(statement_sql,
 | |
|                        (char*)ptr + 4 + 4 + 1 + 2 + 2 + var_block_len + 1 + db_name_len,
 | |
|                        statement_len);
 | |
| 
 | |
|                 /* A transaction starts with this event */
 | |
|                 if (strncmp(statement_sql, "BEGIN", 5) == 0)
 | |
|                 {
 | |
|                     if (pending_transaction > BLRM_NO_TRANSACTION)
 | |
|                     {
 | |
|                         MXS_ERROR("Transaction cannot be @ pos %llu: "
 | |
|                                   "Another transaction was opened at %llu",
 | |
|                                   pos,
 | |
|                                   last_known_commit);
 | |
| 
 | |
|                         MXS_FREE(statement_sql);
 | |
|                         gwbuf_free(result);
 | |
| 
 | |
|                         break;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         pending_transaction = BLRM_TRANSACTION_START;
 | |
| 
 | |
|                         router->pending_transaction.start_pos = pos;
 | |
|                         router->pending_transaction.end_pos = 0;
 | |
| 
 | |
|                         transaction_events = 0;
 | |
|                         event_bytes = 0;
 | |
|                         if (!(debug & BLR_CHECK_ONLY))
 | |
|                         {
 | |
|                             MXS_DEBUG("> Transaction starts @ pos %llu", pos);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 /* Commit received for non transactional tables, i.e. MyISAM */
 | |
|                 if (strncmp(statement_sql, "COMMIT", 6) == 0)
 | |
|                 {
 | |
|                     if (pending_transaction > BLRM_NO_TRANSACTION)
 | |
|                     {
 | |
|                         pending_transaction = BLRM_COMMIT_SEEN;
 | |
| 
 | |
|                         if (!(debug & BLR_CHECK_ONLY))
 | |
|                         {
 | |
|                             MXS_DEBUG("       Transaction @ pos %llu,"
 | |
|                                       " closing @ %llu",
 | |
|                                       last_known_commit,
 | |
|                                       pos);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 /**
 | |
|                  * If it's a standalone transaction event we're done:
 | |
|                  * This query event, only one, terminates the
 | |
|                  * transaction.
 | |
|                  */
 | |
|                 if (pending_transaction > BLRM_NO_TRANSACTION
 | |
|                     && router->pending_transaction.standalone)
 | |
|                 {
 | |
|                     pending_transaction = BLRM_STANDALONE_SEEN;
 | |
| 
 | |
|                     if (!(debug & BLR_CHECK_ONLY))
 | |
|                     {
 | |
|                         MXS_DEBUG("       Standalone Transaction @ pos %llu,"
 | |
|                                   " closing @ %llu",
 | |
|                                   last_known_commit,
 | |
|                                   pos);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 MXS_FREE(statement_sql);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 MXS_ERROR("Unable to allocate memory for statement SQL in blr_file.c");
 | |
|                 gwbuf_free(result);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (hdr.event_type == XID_EVENT)
 | |
|         {
 | |
|             /* Commit received for a transactional tables, i.e. InnoDB */
 | |
|             if (pending_transaction > BLRM_NO_TRANSACTION)
 | |
|             {
 | |
|                 pending_transaction = BLRM_XID_EVENT_SEEN;
 | |
| 
 | |
|                 if (!(debug & BLR_CHECK_ONLY))
 | |
|                 {
 | |
|                     MXS_DEBUG("       Transaction XID @ pos %llu,"
 | |
|                               " closing @ %llu",
 | |
|                               last_known_commit,
 | |
|                               pos);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (pending_transaction > BLRM_TRANSACTION_START)
 | |
|         {
 | |
|             if (!(debug & BLR_CHECK_ONLY))
 | |
|             {
 | |
|                 MXS_DEBUG("< Transaction @ pos %llu, is now closed @ %llu."
 | |
|                           " %lu events seen",
 | |
|                           last_known_commit,
 | |
|                           pos,
 | |
|                           transaction_events);
 | |
|             }
 | |
| 
 | |
|             pending_transaction = BLRM_NO_TRANSACTION;
 | |
|             router->pending_transaction.standalone = false;
 | |
| 
 | |
|             router->pending_transaction.end_pos = hdr.next_pos;
 | |
| 
 | |
|             last_known_commit = pos;
 | |
| 
 | |
|             /* Reset the event replacing indicator */
 | |
|             replace_trx_events = false;
 | |
| 
 | |
|             if (router->mariadb10_compat
 | |
|                 && router->mariadb10_gtid)
 | |
|             {
 | |
|                 /* Update Last Seen MariaDB GTID */
 | |
|                 strcpy(router->last_mariadb_gtid,
 | |
|                        router->pending_transaction.gtid);
 | |
|             }
 | |
| 
 | |
|             total_events += transaction_events;
 | |
| 
 | |
|             if (transaction_events > max_events)
 | |
|             {
 | |
|                 max_events = transaction_events;
 | |
|             }
 | |
| 
 | |
|             n_transactions++;
 | |
|         }
 | |
| 
 | |
|         gwbuf_free(result);
 | |
|         gwbuf_free(decrypted_event);
 | |
| 
 | |
|         /* pos and next_pos sanity checks */
 | |
|         if (hdr.next_pos > 0 && hdr.next_pos < pos)
 | |
|         {
 | |
|             MXS_INFO("Binlog %s: next pos %u < pos %llu, truncating to %llu",
 | |
|                      router->binlog_name,
 | |
|                      hdr.next_pos,
 | |
|                      pos,
 | |
|                      pos);
 | |
| 
 | |
|             router->binlog_position = last_known_commit;
 | |
|             router->current_safe_event = last_known_commit;
 | |
|             router->current_pos = pos;
 | |
| 
 | |
|             MXS_WARNING("an error has been found. "
 | |
|                         "Setting safe pos to %lu, current pos %lu",
 | |
|                         router->binlog_position,
 | |
|                         router->current_pos);
 | |
|             if (fix)
 | |
|             {
 | |
|                 if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                 {
 | |
|                     MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                router->binlog_name,
 | |
|                                router->binlog_position);
 | |
|                     fsync(router->binlog_fd);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return 2;
 | |
|         }
 | |
| 
 | |
|         if (hdr.next_pos > 0 && hdr.next_pos != (pos + hdr.event_size))
 | |
|         {
 | |
|             MXS_INFO("Binlog %s: next pos %u != (pos %llu + event_size %u), truncating to %llu",
 | |
|                      router->binlog_name,
 | |
|                      hdr.next_pos,
 | |
|                      pos,
 | |
|                      hdr.event_size,
 | |
|                      pos);
 | |
| 
 | |
|             router->binlog_position = last_known_commit;
 | |
|             router->current_safe_event = last_known_commit;
 | |
|             router->current_pos = pos;
 | |
| 
 | |
|             MXS_WARNING("an error has been found. "
 | |
|                         "Setting safe pos to %lu, current pos %lu",
 | |
|                         router->binlog_position,
 | |
|                         router->current_pos);
 | |
| 
 | |
|             if (fix)
 | |
|             {
 | |
|                 if (ftruncate(router->binlog_fd, router->binlog_position) == 0)
 | |
|                 {
 | |
|                     MXS_NOTICE("Binlog file %s has been truncated at %lu",
 | |
|                                router->binlog_name,
 | |
|                                router->binlog_position);
 | |
|                     fsync(router->binlog_fd);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return 2;
 | |
|         }
 | |
| 
 | |
|         /* set pos to new value */
 | |
|         if (hdr.next_pos > 0)
 | |
|         {
 | |
|             if (pending_transaction)
 | |
|             {
 | |
|                 total_bytes += hdr.event_size;
 | |
|                 event_bytes += hdr.event_size;
 | |
| 
 | |
|                 if (event_bytes > max_bytes)
 | |
|                 {
 | |
|                     max_bytes = event_bytes;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             pos = hdr.next_pos;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Current event type %d @ %llu has nex pos = %u : exiting",
 | |
|                       hdr.event_type,
 | |
|                       pos,
 | |
|                       hdr.next_pos);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         transaction_events++;
 | |
|     }
 | |
| 
 | |
|     if (pending_transaction)
 | |
|     {
 | |
|         MXS_INFO("Binlog %s contains an Open Transaction, truncating to %llu",
 | |
|                  router->binlog_name,
 | |
|                  last_known_commit);
 | |
| 
 | |
|         router->binlog_position = last_known_commit;
 | |
|         router->current_safe_event = last_known_commit;
 | |
|         router->current_pos = pos;
 | |
|         router->pending_transaction.state = BLRM_TRANSACTION_START;
 | |
| 
 | |
|         MXS_WARNING("an error has been found. "
 | |
|                     "Setting safe pos to %lu, current pos %lu",
 | |
|                     router->binlog_position,
 | |
|                     router->current_pos);
 | |
| 
 | |
|         return 0;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         router->binlog_position = pos;
 | |
|         router->current_safe_event = pos;
 | |
|         router->current_pos = pos;
 | |
| 
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Format a number to G, M, k, or B size
 | |
|  *
 | |
|  * @param event_size    The number to format
 | |
|  * @param label     Label to use for display the formattted number
 | |
|  */
 | |
| static void blr_format_event_size(double* event_size, char* label)
 | |
| {
 | |
|     if (*event_size > (1024 * 1024 * 1024))
 | |
|     {
 | |
|         *event_size = *event_size / (1024 * 1024 * 1024);
 | |
|         label[0] = 'G';
 | |
|     }
 | |
|     else if (*event_size > (1024 * 1024))
 | |
|     {
 | |
|         *event_size = *event_size / (1024 * 1024);
 | |
|         label[0] = 'M';
 | |
|     }
 | |
|     else if (*event_size > 1024)
 | |
|     {
 | |
|         *event_size = *event_size / (1024);
 | |
|         label[0] = 'k';
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         label[0] = 'B';
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Read any previously saved master data
 | |
|  *
 | |
|  * @param       router          The router instance
 | |
|  */
 | |
| void blr_cache_read_master_data(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     router->saved_master.server_id = blr_cache_read_response(router, "serverid");
 | |
|     router->saved_master.heartbeat = blr_cache_read_response(router, "heartbeat");
 | |
|     router->saved_master.chksum1 = blr_cache_read_response(router, "chksum1");
 | |
|     router->saved_master.chksum2 = blr_cache_read_response(router, "chksum2");
 | |
|     router->saved_master.gtid_mode = blr_cache_read_response(router, "gtidmode");
 | |
|     router->saved_master.uuid = blr_cache_read_response(router, "uuid");
 | |
|     router->saved_master.setslaveuuid = blr_cache_read_response(router, "ssuuid");
 | |
|     router->saved_master.setnames = blr_cache_read_response(router, "setnames");
 | |
|     router->saved_master.utf8 = blr_cache_read_response(router, "utf8");
 | |
|     router->saved_master.select1 = blr_cache_read_response(router, "select1");
 | |
|     router->saved_master.selectver = blr_cache_read_response(router, "selectver");
 | |
|     router->saved_master.selectvercom = blr_cache_read_response(router, "selectvercom");
 | |
|     router->saved_master.selecthostname = blr_cache_read_response(router, "selecthostname");
 | |
|     router->saved_master.map = blr_cache_read_response(router, "map");
 | |
|     router->saved_master.mariadb10 = blr_cache_read_response(router, "mariadb10");
 | |
|     router->saved_master.server_vars = blr_cache_read_response(router, "server_vars");
 | |
|     router->saved_master.binlog_vars = blr_cache_read_response(router, "binlog_vars");
 | |
|     router->saved_master.lower_case_tables = blr_cache_read_response(router, "lower_case_tables");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the next binlog file name.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @return      0 on error, >0 as sequence number
 | |
|  */
 | |
| int blr_file_get_next_binlogname(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     char* sptr;
 | |
|     int filenum;
 | |
| 
 | |
|     if ((sptr = strrchr(router->binlog_name, '.')) == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     filenum = atoi(sptr + 1);
 | |
|     if (filenum)
 | |
|     {
 | |
|         filenum++;
 | |
|     }
 | |
| 
 | |
|     return filenum;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create a new binlog file
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param file      The new binlog file
 | |
|  * @return      1 on success, 0 on failure
 | |
|  */
 | |
| int blr_file_new_binlog(ROUTER_INSTANCE* router, char* file)
 | |
| {
 | |
|     return blr_file_create(router, file);
 | |
| }
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 
 | |
| /**
 | |
|  * Configuration handler for items in the [binlog_configuration] section
 | |
|  *
 | |
|  * @param       name    The item name
 | |
|  * @param       value   The item value
 | |
|  * @param       inst    The current router instance
 | |
|  * @param [out] config  The config object to modify.
 | |
|  * @return 0 on error
 | |
|  */
 | |
| int blr_handle_config_item(const char* name,
 | |
|                            const char* value,
 | |
|                            ROUTER_INSTANCE* inst,
 | |
|                            ChangeMasterConfig* config)
 | |
| {
 | |
|     mxb_assert(strcmp(name, "filestem") != 0);
 | |
| 
 | |
|     if (strcmp(name, "master_host") == 0)
 | |
|     {
 | |
|         config->host = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_port") == 0)
 | |
|     {
 | |
|         config->port = atoi(value);
 | |
|     }
 | |
|     else if (strcmp(name, "master_user") == 0)
 | |
|     {
 | |
|         config->user = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_password") == 0)
 | |
|     {
 | |
|         config->password = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_ssl") == 0)
 | |
|     {
 | |
|         config->ssl_enabled = config_truth_value((char*)value);
 | |
|     }
 | |
|     else if (strcmp(name, "master_ssl_ca") == 0)
 | |
|     {
 | |
|         config->ssl_ca = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_ssl_cert") == 0)
 | |
|     {
 | |
|         config->ssl_cert = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_ssl_key") == 0)
 | |
|     {
 | |
|         config->ssl_key = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_ssl_version") == 0 || strcmp(name, "master_tls_version") == 0)
 | |
|     {
 | |
|         config->ssl_version = value;
 | |
|     }
 | |
|     else if (strcmp(name, "master_heartbeat_period") == 0)
 | |
|     {
 | |
|         int heartbeat_period = atol((char*)value);
 | |
|         if (heartbeat_period < 0)
 | |
|         {
 | |
|             MXS_WARNING("Found invalid 'master_heartbeat_period' value"
 | |
|                         " for service '%s': %s, ignoring it.",
 | |
|                         inst->service->name(),
 | |
|                         value);
 | |
|             heartbeat_period = -1;
 | |
|         }
 | |
| 
 | |
|         config->heartbeat_period = heartbeat_period;
 | |
|     }
 | |
|     else if (strcmp(name, "master_connect_retry") == 0)
 | |
|     {
 | |
|         int connect_retry = atol((char*)value);
 | |
|         if (connect_retry <= 0)
 | |
|         {
 | |
|             MXS_WARNING("Found invalid 'master_connect_retry' value"
 | |
|                         " for service '%s': %s, ignoring it.",
 | |
|                         inst->service->name(),
 | |
|                         value);
 | |
|             connect_retry = -1;
 | |
|         }
 | |
| 
 | |
|         config->connect_retry = connect_retry;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Config item handler for the ini file reader
 | |
|  *
 | |
|  * @param userdata      The config context element
 | |
|  * @param section       The config file section
 | |
|  * @param name          The Parameter name
 | |
|  * @param value         The Parameter value
 | |
|  * @return zero on error
 | |
|  */
 | |
| 
 | |
| int blr_handler_config(void* userdata, const char* section, const char* name, const char* value)
 | |
| {
 | |
|     int rc = 1;
 | |
| 
 | |
|     static const char SECTION_NAME[] = "binlog_configuration";
 | |
| 
 | |
|     ROUTER_INSTANCE* inst = (ROUTER_INSTANCE*) userdata;
 | |
| 
 | |
|     bool complain_about_section = false;
 | |
| 
 | |
|     if (strncasecmp(section, SECTION_NAME, sizeof(SECTION_NAME) - 1) == 0)
 | |
|     {
 | |
|         ChangeMasterConfig* config = nullptr;
 | |
| 
 | |
|         const char* tail = section + sizeof(SECTION_NAME) - 1;
 | |
| 
 | |
|         if (*tail == 0)
 | |
|         {
 | |
|             // This is [binlog_configuration]
 | |
| 
 | |
|             if (strcmp(name, "filestem") == 0)
 | |
|             {
 | |
|                 MXS_FREE(inst->fileroot);
 | |
|                 inst->fileroot = MXS_STRDUP_A(value);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (inst->configs.size() == 0)
 | |
|                 {
 | |
|                     inst->configs.emplace_back();
 | |
|                 }
 | |
| 
 | |
|                 config = &inst->configs[0];
 | |
|             }
 | |
|         }
 | |
|         else if (*tail == ':')
 | |
|         {
 | |
|             // This is at least [binlog_configuration:]
 | |
| 
 | |
|             ++tail;
 | |
|             int value = atoi(tail);
 | |
|             if ((value >= 2) && (std::to_string(value) == tail))
 | |
|             {
 | |
|                 // This is [binlog_configuration::N], with N >= 2.
 | |
| 
 | |
|                 unsigned n = value;
 | |
|                 if (inst->configs.size() == n - 1)
 | |
|                 {
 | |
|                     // A "[binlog_configuration:N]" section at a point where (if N > 2)
 | |
|                     // "[binlog_configuration:N-1]" or "[binlog_configuration]" (if N == 2)
 | |
|                     // has been seen.
 | |
|                     inst->configs.emplace_back();
 | |
|                 }
 | |
| 
 | |
|                 if (inst->configs.size() >= n)
 | |
|                 {
 | |
|                     config = &inst->configs[n - 1];     // Numbered from 1, indexed from 0.
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // A "[binlog_configuration:N] where the
 | |
|                     string previous;
 | |
|                     if (n > 2)
 | |
|                     {
 | |
|                         previous += ":";
 | |
|                         previous += std::to_string(n - 1);
 | |
|                     }
 | |
| 
 | |
|                     MXS_ERROR("The configuration [%s:%u] appears in master.ini, before the "
 | |
|                               "configuration [%s%s] does. ",
 | |
|                               SECTION_NAME,
 | |
|                               n,
 | |
|                               SECTION_NAME,
 | |
|                               previous.c_str());
 | |
|                     rc = 0;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 complain_about_section = true;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             complain_about_section = true;
 | |
|         }
 | |
| 
 | |
|         if (config)
 | |
|         {
 | |
|             rc = blr_handle_config_item(name, value, inst, config);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         complain_about_section = true;
 | |
|     }
 | |
| 
 | |
|     if (complain_about_section)
 | |
|     {
 | |
|         MXS_ERROR("master.ini has an invalid section [%s], it should be [%s] or "
 | |
|                   "[%s:N] where the N:s are numbered consecutively from 2."
 | |
|                   "Service %s",
 | |
|                   section,
 | |
|                   SECTION_NAME,
 | |
|                   SECTION_NAME,
 | |
|                   inst->service->name());
 | |
|         rc = 0;
 | |
|     }
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| }
 | |
| 
 | |
| int blr_file_read_master_config(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     mxb_assert(router->configs.size() == 0);
 | |
|     static const char MASTER_INI[] = "/master.ini";
 | |
|     char filename[strlen(router->binlogdir) + sizeof(MASTER_INI)];      // sizeof includes the NULL
 | |
|     sprintf(filename, "%s%s", router->binlogdir, MASTER_INI);
 | |
| 
 | |
|     int rc = ini_parse(filename, blr_handler_config, router);
 | |
| 
 | |
|     if (rc == 0)
 | |
|     {
 | |
|         mxb_assert(router->configs.size() > 0);
 | |
|         blr_master_set_config(router, router->configs[0]);
 | |
|     }
 | |
| 
 | |
|     MXS_INFO("%s: %s parse result is %d", router->service->name(), filename, rc);
 | |
| 
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 
 | |
| void write_master_config(FILE* config_file, const ChangeMasterConfig& config)
 | |
| {
 | |
|     static const char SECTION[] = "binlog_configuration";
 | |
| 
 | |
|     // Section
 | |
|     fprintf(config_file, "[%s%s]\n", SECTION, config.connection_name.c_str());
 | |
| 
 | |
|     // Values
 | |
|     fprintf(config_file, "master_host=%s\n", config.host.c_str());
 | |
|     fprintf(config_file, "master_port=%d\n", config.port);
 | |
|     fprintf(config_file, "master_user=%s\n", config.user.c_str());
 | |
|     fprintf(config_file, "master_password=%s\n", config.password.c_str());
 | |
| 
 | |
|     if (!config.ssl_cert.empty()
 | |
|         && !config.ssl_ca.empty()
 | |
|         && !config.ssl_key.empty())
 | |
|     {
 | |
|         fprintf(config_file, "master_ssl=%d\n", config.ssl_enabled);
 | |
|         fprintf(config_file, "master_ssl_key=%s\n", config.ssl_key.c_str());
 | |
|         fprintf(config_file, "master_ssl_cert=%s\n", config.ssl_cert.c_str());
 | |
|         fprintf(config_file, "master_ssl_ca=%s\n", config.ssl_ca.c_str());
 | |
|     }
 | |
| 
 | |
|     if (!config.ssl_version.empty())
 | |
|     {
 | |
|         fprintf(config_file, "master_tls_version=%s\n", config.ssl_version.c_str());
 | |
|     }
 | |
| 
 | |
|     fprintf(config_file, "master_heartbeat_period=%d\n", config.heartbeat_period);
 | |
|     fprintf(config_file, "master_connect_retry=%d\n", config.connect_retry);
 | |
| }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Write a new ini file with master configuration
 | |
|  *
 | |
|  * File is 'inst->binlogdir/master.ini.tmp'
 | |
|  * When done it's renamed to 'inst->binlogdir/master.ini'
 | |
|  *
 | |
|  * @param router    The current router instance
 | |
|  * @param error     Preallocated error message
 | |
|  * @return      0 on success, >0 on failure
 | |
|  *
 | |
|  */
 | |
| int blr_file_write_master_config(ROUTER_INSTANCE* router, char* error)
 | |
| {
 | |
|     const char* section = "binlog_configuration";
 | |
|     FILE* config_file;
 | |
|     int rc;
 | |
|     static const char MASTER_INI[] = "master.ini";
 | |
|     static const char TMP[] = "tmp";
 | |
|     size_t len = strlen(router->binlogdir);
 | |
| 
 | |
|     char filename[len + sizeof('/') + sizeof(MASTER_INI)];      // sizeof includes NULL
 | |
|     char tmp_file[len + sizeof('/') + sizeof(MASTER_INI) + sizeof('.') + sizeof(TMP)];
 | |
|     char* ssl_ca;
 | |
|     char* ssl_cert;
 | |
|     char* ssl_key;
 | |
|     char* ssl_version;
 | |
| 
 | |
|     sprintf(filename, "%s/%s", router->binlogdir, MASTER_INI);
 | |
|     sprintf(tmp_file, "%s/%s.%s", router->binlogdir, MASTER_INI, TMP);
 | |
| 
 | |
|     /* open file for writing */
 | |
|     config_file = fopen(tmp_file, "wb");
 | |
|     if (config_file == NULL)
 | |
|     {
 | |
|         snprintf(error,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "%s, errno %u",
 | |
|                  mxs_strerror(errno),
 | |
|                  errno);
 | |
|         return 2;
 | |
|     }
 | |
| 
 | |
|     if (chmod(tmp_file, S_IRUSR | S_IWUSR) < 0)
 | |
|     {
 | |
|         fclose(config_file);
 | |
|         snprintf(error,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "%s, errno %u",
 | |
|                  mxs_strerror(errno),
 | |
|                  errno);
 | |
|         return 2;
 | |
|     }
 | |
| 
 | |
|     // Assert that the configurarion as dispersed around blr and
 | |
|     // as stored in the configuration item are identical.
 | |
|     mxb_assert(router->configs.size() > 0);
 | |
|     mxb_assert(router->config_index < static_cast<int>(router->configs.size()));
 | |
| #ifdef SS_DEBUG
 | |
|     const ChangeMasterConfig& current = router->configs[router->config_index];
 | |
| 
 | |
|     mxb_assert(current.host == router->service->dbref->server->address);
 | |
|     mxb_assert(current.port == router->service->dbref->server->port);
 | |
|     mxb_assert(current.user == router->user);
 | |
|     mxb_assert(current.password == router->password);
 | |
| 
 | |
|     mxb_assert(current.heartbeat_period == (int)router->heartbeat);
 | |
|     mxb_assert(current.connect_retry == router->retry_interval);
 | |
| #endif
 | |
| 
 | |
|     ChangeMasterConfig default_config = router->configs[0];     // Copied, so that it can be modified.
 | |
| 
 | |
|     // If not SSL enabled, store old SSL config if there is one.
 | |
|     // TODO: Why?
 | |
|     if (!router->ssl_enabled)
 | |
|     {
 | |
|         default_config.ssl_ca = router->ssl_ca ? router->ssl_ca : "";
 | |
|         default_config.ssl_cert = router->ssl_cert ? router->ssl_cert : "";
 | |
|         default_config.ssl_key = router->ssl_key ? router->ssl_key : "";
 | |
|     }
 | |
| 
 | |
|     // Store the default config.
 | |
|     write_master_config(config_file, default_config);
 | |
| 
 | |
|     /* write filestem only if binlog file is set */
 | |
|     if (*router->binlog_name != 0)
 | |
|     {
 | |
|         fprintf(config_file, "filestem=%s\n", router->fileroot);
 | |
|     }
 | |
| 
 | |
|     // Store all secondary configs.
 | |
|     for (size_t i = 1; i < router->configs.size(); ++i)
 | |
|     {
 | |
|         write_master_config(config_file, router->configs[i]);
 | |
|     }
 | |
| 
 | |
|     fclose(config_file);
 | |
| 
 | |
|     /* rename tmp file to right filename */
 | |
|     rc = rename(tmp_file, filename);
 | |
| 
 | |
|     if (rc == -1)
 | |
|     {
 | |
|         snprintf(error,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "%s, errno %u",
 | |
|                  mxs_strerror(errno),
 | |
|                  errno);
 | |
|         return 3;
 | |
|     }
 | |
| 
 | |
|     if (chmod(filename, S_IRUSR | S_IWUSR) < 0)
 | |
|     {
 | |
|         snprintf(error,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "%s, errno %u",
 | |
|                  mxs_strerror(errno),
 | |
|                  errno);
 | |
|         return 3;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /** Print Binlog Details
 | |
|  *
 | |
|  * @param router        The router instance
 | |
|  * @param first_event   First Event details
 | |
|  * @param last_event    First Event details
 | |
|  */
 | |
| 
 | |
| static void blr_print_binlog_details(ROUTER_INSTANCE* router,
 | |
|                                      BINLOG_EVENT_DESC first_event,
 | |
|                                      BINLOG_EVENT_DESC last_event)
 | |
| {
 | |
|     char buf_t[40];
 | |
|     struct tm tm_t;
 | |
|     const char* event_desc;
 | |
| 
 | |
|     /* First Event */
 | |
|     localtime_r(&first_event.event_time, &tm_t);
 | |
|     asctime_r(&tm_t, buf_t);
 | |
| 
 | |
|     if (buf_t[strlen(buf_t) - 1] == '\n')
 | |
|     {
 | |
|         buf_t[strlen(buf_t) - 1] = '\0';
 | |
|     }
 | |
| 
 | |
|     event_desc = blr_get_event_description(router, first_event.event_type);
 | |
| 
 | |
|     MXS_NOTICE("%lu @ %" PRIu64 ", %s, (%s), First EventTime",
 | |
|                first_event.event_time,
 | |
|                first_event.event_pos,
 | |
|                event_desc != NULL ? event_desc : "unknown",
 | |
|                buf_t);
 | |
| 
 | |
|     /* Last Event */
 | |
|     localtime_r(&last_event.event_time, &tm_t);
 | |
|     asctime_r(&tm_t, buf_t);
 | |
| 
 | |
|     if (buf_t[strlen(buf_t) - 1] == '\n')
 | |
|     {
 | |
|         buf_t[strlen(buf_t) - 1] = '\0';
 | |
|     }
 | |
| 
 | |
|     event_desc = blr_get_event_description(router, last_event.event_type);
 | |
| 
 | |
|     MXS_NOTICE("%lu @ %" PRIu64 ", %s, (%s), Last EventTime",
 | |
|                last_event.event_time,
 | |
|                last_event.event_pos,
 | |
|                event_desc != NULL ? event_desc : "unknown",
 | |
|                buf_t);
 | |
| }
 | |
| 
 | |
| /** Create an ignorable event
 | |
|  *
 | |
|  * @param event_size     The size of the new event being created (crc32 4 bytes could be included)
 | |
|  * @param hdr            Current replication event header, received from master
 | |
|  * @param event_pos      The position in binlog file of the new event
 | |
|  * @param do_checksum    Whether checksum must be calculated and stored
 | |
|  * @return               Returns the pointer of new event
 | |
|  */
 | |
| static uint8_t* blr_create_ignorable_event(uint32_t event_size,
 | |
|                                            REP_HEADER* hdr,
 | |
|                                            uint32_t event_pos,
 | |
|                                            bool do_checksum)
 | |
| {
 | |
|     uint8_t* new_event;
 | |
| 
 | |
|     if (event_size < BINLOG_EVENT_HDR_LEN)
 | |
|     {
 | |
|         MXS_ERROR("blr_create_ignorable_event an event of %lu bytes"
 | |
|                   " is not valid in blr_file.c",
 | |
|                   (unsigned long)event_size);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     // Allocate space for event: size might contain the 4 crc32
 | |
|     new_event = static_cast<uint8_t*>(MXS_CALLOC(1, event_size));
 | |
|     if (new_event == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     // Populate Event header 19 bytes for Ignorable Event
 | |
|     encode_value(&new_event[0], hdr->timestamp, 32);            // same timestamp as in current received event
 | |
|     new_event[4] = IGNORABLE_EVENT;                             // type is IGNORABLE_EVENT
 | |
|     encode_value(&new_event[5], hdr->serverid, 32);             // same serverid as in current received event
 | |
|     encode_value(&new_event[9], event_size, 32);                // event size
 | |
|     encode_value(&new_event[13], event_pos + event_size, 32);   // next_pos
 | |
|     encode_value(&new_event[17], LOG_EVENT_IGNORABLE_F, 16);    // flag is LOG_EVENT_IGNORABLE_F
 | |
| 
 | |
|     /* if checksum is required calculate the crc32 and add it in the last 4 bytes*/
 | |
|     if (do_checksum)
 | |
|     {
 | |
|         /*
 | |
|          * Now add the CRC to the Ignorable binlog event.
 | |
|          *
 | |
|          * The algorithm is first to compute the checksum of an empty buffer
 | |
|          * and then the checksum of the real event: 4 byte less than event_size
 | |
|          */
 | |
|         uint32_t chksum;
 | |
|         chksum = crc32(0L, NULL, 0);
 | |
|         chksum = crc32(chksum, new_event, event_size - BINLOG_EVENT_CRC_SIZE);
 | |
| 
 | |
|         // checksum is stored after current event data using 4 bytes
 | |
|         encode_value(new_event + event_size - BINLOG_EVENT_CRC_SIZE, chksum, 32);
 | |
|     }
 | |
|     return new_event;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create and write a special event (not received from master) into binlog file
 | |
|  *
 | |
|  * @param router        The current router instance
 | |
|  * @param file_offset   Position where event will be written
 | |
|  * @param event_size    The size of new event (it might hold the 4 bytes crc32)
 | |
|  * @param hdr           Replication header of the current reived event (from Master)
 | |
|  * @param type          Type of special event to create and write
 | |
|  * @return              1 on success, 0 on error
 | |
|  */
 | |
| int blr_write_special_event(ROUTER_INSTANCE* router,
 | |
|                             uint32_t file_offset,
 | |
|                             uint32_t event_size,
 | |
|                             REP_HEADER* hdr,
 | |
|                             int type)
 | |
| {
 | |
|     int n;
 | |
|     uint8_t* new_event;
 | |
|     const char* new_event_desc;
 | |
| 
 | |
|     switch (type)
 | |
|     {
 | |
|     case BLRM_IGNORABLE:
 | |
|         new_event_desc = "IGNORABLE";
 | |
|         MXS_INFO("Hole detected while writing in binlog '%s' @ %lu: an %s event "
 | |
|                  "of %lu bytes will be written at pos %lu",
 | |
|                  router->binlog_name,
 | |
|                  router->current_pos,
 | |
|                  new_event_desc,
 | |
|                  (unsigned long)event_size,
 | |
|                  (unsigned long)file_offset);
 | |
| 
 | |
|         /* Create the Ignorable event */
 | |
|         if ((new_event = blr_create_ignorable_event(event_size,
 | |
|                                                     hdr,
 | |
|                                                     file_offset,
 | |
|                                                     router->master_chksum)) == NULL)
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
|         if (router->encryption.enabled && router->encryption_ctx != NULL)
 | |
|         {
 | |
|             GWBUF* encrypted;
 | |
|             uint8_t* encr_ptr;
 | |
|             if ((encrypted = blr_prepare_encrypted_event(router,
 | |
|                                                          new_event,
 | |
|                                                          event_size,
 | |
|                                                          router->current_pos,
 | |
|                                                          NULL,
 | |
|                                                          BINLOG_FLAG_ENCRYPT)) == NULL)
 | |
|             {
 | |
|                 return 0;
 | |
|             }
 | |
| 
 | |
|             memcpy(new_event, GWBUF_DATA(encrypted), event_size);
 | |
|             gwbuf_free(encrypted);
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case BLRM_START_ENCRYPTION:
 | |
|         new_event_desc = "MARIADB10_START_ENCRYPTION";
 | |
|         MXS_INFO("New event %s is being added in binlog '%s' @ %lu: "
 | |
|                  "%lu bytes will be written at pos %lu",
 | |
|                  new_event_desc,
 | |
|                  router->binlog_name,
 | |
|                  router->current_pos,
 | |
|                  (unsigned long)event_size,
 | |
|                  (unsigned long)file_offset);
 | |
| 
 | |
|         /* Create the MARIADB10_START_ENCRYPTION event */
 | |
|         if ((new_event = blr_create_start_encryption_event(router,
 | |
|                                                            file_offset,
 | |
|                                                            router->master_chksum)) == NULL)
 | |
|         {
 | |
|             return 0;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         new_event_desc = "UNKNOWN";
 | |
|         MXS_ERROR("Cannot create special binlog event of %s type and size %lu "
 | |
|                   "in binlog file '%s' @ %lu",
 | |
|                   new_event_desc,
 | |
|                   (unsigned long)event_size,
 | |
|                   router->binlog_name,
 | |
|                   router->current_pos);
 | |
|         return 0;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     /* Write the event */
 | |
|     if ((n = pwrite(router->binlog_fd,
 | |
|                     new_event,
 | |
|                     event_size,
 | |
|                     router->last_written)) != static_cast<ssize_t>(event_size))
 | |
|     {
 | |
|         MXS_ERROR("%s: Failed to write %s special binlog record at %lu of %s, %s. "
 | |
|                   "Truncating to previous record.",
 | |
|                   router->service->name(),
 | |
|                   new_event_desc,
 | |
|                   (unsigned long)file_offset,
 | |
|                   router->binlog_name,
 | |
|                   mxs_strerror(errno));
 | |
| 
 | |
|         /* Remove any partial event that was written */
 | |
|         if (ftruncate(router->binlog_fd, router->binlog_position))
 | |
|         {
 | |
|             MXS_ERROR("%s: Failed to truncate %s special binlog record at %lu of %s, %s. ",
 | |
|                       router->service->name(),
 | |
|                       new_event_desc,
 | |
|                       (unsigned long)file_offset,
 | |
|                       router->binlog_name,
 | |
|                       mxs_strerror(errno));
 | |
|         }
 | |
|         MXS_FREE(new_event);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     MXS_FREE(new_event);
 | |
| 
 | |
|     // Increment offsets, next event will be written after this special one
 | |
|     pthread_mutex_lock(&router->binlog_lock);
 | |
| 
 | |
|     router->last_written += event_size;
 | |
|     router->current_pos = file_offset + event_size;
 | |
|     router->last_event_pos = file_offset;
 | |
| 
 | |
|     pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|     // Force write
 | |
|     fsync(router->binlog_fd);
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /** Create the MXS_START_ENCRYPTION_EVENT
 | |
|  *
 | |
|  * This is a New Event added in MariaDB 10.1.7
 | |
|  * Type is 0xa4 and size 36 (crc32 not included)
 | |
|  *
 | |
|  * @param hdr            Current replication event header, received from master
 | |
|  * @param event_pos      The position in binlog file of the new event
 | |
|  * @param do_checksum    Whether checksum must be calculated and stored
 | |
|  * @return               Returns the pointer of new event
 | |
|  */
 | |
| 
 | |
| uint8_t* blr_create_start_encryption_event(ROUTER_INSTANCE* router,
 | |
|                                            uint32_t event_pos,
 | |
|                                            bool do_checksum)
 | |
| {
 | |
|     uint8_t* new_event;
 | |
|     uint8_t event_size = sizeof(MXS_START_ENCRYPTION_EVENT);
 | |
|     BINLOG_ENCRYPTION_CTX* new_encryption_ctx =
 | |
|         static_cast<BINLOG_ENCRYPTION_CTX*>(MXS_CALLOC(1, sizeof(BINLOG_ENCRYPTION_CTX)));
 | |
| 
 | |
|     if (new_encryption_ctx == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     /* Add 4 bytes to event size with crc32 */
 | |
|     if (do_checksum)
 | |
|     {
 | |
|         event_size += BINLOG_EVENT_CRC_SIZE;
 | |
|     }
 | |
| 
 | |
|     new_event = static_cast<uint8_t*>(MXS_CALLOC(1, event_size));
 | |
|     if (new_event == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     // Populate Event header 19 bytes
 | |
|     encode_value(&new_event[0], time(NULL), 32);    // now
 | |
|     new_event[4] = MARIADB10_START_ENCRYPTION_EVENT;// type is BEGIN_ENCRYPTION_EVENT
 | |
|     /* Set binlog server instance server id */
 | |
|     encode_value(&new_event[5], router->serverid, 32);          // serverid of maxscale
 | |
|     encode_value(&new_event[9], event_size, 32);                // event size
 | |
|     encode_value(&new_event[13], event_pos + event_size, 32);   // next_pos
 | |
|     encode_value(&new_event[17], 0, 16);                        // flag is 0 ?
 | |
| 
 | |
|     /**
 | |
|      *  Now add the event content, after 19 bytes of header
 | |
|      */
 | |
| 
 | |
|     /* Set the encryption schema, 1 byte: set to 1 */
 | |
|     new_event[BINLOG_EVENT_HDR_LEN] = 1;
 | |
|     /* The encryption key version, 4 bytes: set to 1, is added after previous one 1 byte */
 | |
|     encode_value(&new_event[BINLOG_EVENT_HDR_LEN + 1], 1, 32);
 | |
|     /* The nonce (12 random bytes) is added after previous 5 bytes */
 | |
|     gw_generate_random_str((char*)&new_event[BINLOG_EVENT_HDR_LEN + 4 + 1], BLRM_NONCE_LENGTH);
 | |
| 
 | |
|     /* if checksum is requred add the crc32 */
 | |
|     if (do_checksum)
 | |
|     {
 | |
|         /*
 | |
|          * Now add the CRC to the Ignorable binlog event.
 | |
|          *
 | |
|          * The algorithm is first to compute the checksum of an empty buffer
 | |
|          * and then the checksum of the event.
 | |
|          */
 | |
|         uint32_t chksum;
 | |
|         chksum = crc32(0L, NULL, 0);
 | |
|         chksum = crc32(chksum, new_event, event_size - BINLOG_EVENT_CRC_SIZE);
 | |
| 
 | |
|         // checksum is stored at the end of current event data: 4 less bytes than event size
 | |
|         encode_value(new_event + event_size - BINLOG_EVENT_CRC_SIZE, chksum, 32);
 | |
|     }
 | |
| 
 | |
|     /* Update the encryption context */
 | |
|     uint8_t* nonce_ptr = &(new_event[BINLOG_EVENT_HDR_LEN + 4 + 1]);
 | |
| 
 | |
|     pthread_mutex_lock(&router->binlog_lock);
 | |
| 
 | |
|     memcpy(new_encryption_ctx->nonce, nonce_ptr, BLRM_NONCE_LENGTH);
 | |
|     new_encryption_ctx->binlog_crypto_scheme = new_event[BINLOG_EVENT_HDR_LEN];
 | |
|     memcpy(&new_encryption_ctx->binlog_key_version,
 | |
|            &new_event[BINLOG_EVENT_HDR_LEN + 1],
 | |
|            BLRM_KEY_VERSION_LENGTH);
 | |
| 
 | |
|     /* Set the router encryption context for current binlog file */
 | |
|     MXS_FREE(router->encryption_ctx);
 | |
|     router->encryption_ctx = new_encryption_ctx;
 | |
| 
 | |
|     pthread_mutex_unlock(&router->binlog_lock);
 | |
| 
 | |
|     return new_event;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Encrypt/Decrypt an array of bytes
 | |
|  *
 | |
|  * Note: The output buffer is 4 bytes larger than input
 | |
|  * Encrypted bytes start at offset 4
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param buffer    The buffer to encrypt/decrypt
 | |
|  * @param size      The buffer size
 | |
|  * @param iv        The AES initialisation Vector
 | |
|  * @action          Crypt action: 1 encrypt, 1 decrypt
 | |
|  * @return          A new allocated, encrypted, GWBUF buffer
 | |
|  *
 | |
|  */
 | |
| static GWBUF* blr_aes_crypt(ROUTER_INSTANCE* router,
 | |
|                             uint8_t* buffer,
 | |
|                             uint32_t size,
 | |
|                             uint8_t* iv,
 | |
|                             int action)
 | |
| {
 | |
|     uint8_t* key = router->encryption.key_value;
 | |
|     unsigned int key_len = router->encryption.key_len;
 | |
|     int outlen;
 | |
|     int flen;
 | |
|     uint32_t encrypted_size = size + 4;
 | |
|     int total_len;
 | |
|     GWBUF* outbuf;
 | |
|     uint8_t* out_ptr;
 | |
| 
 | |
|     if (key_len == 0)
 | |
|     {
 | |
|         MXS_ERROR("The encrytion key len is 0");
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     if ((outbuf = gwbuf_alloc(encrypted_size)) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     out_ptr = GWBUF_DATA(outbuf);
 | |
| 
 | |
|     EVP_CIPHER_CTX* ctx = mxs_evp_cipher_ctx_alloc();
 | |
| 
 | |
|     /* Set the encryption algorithm accordingly to key_len and encryption mode */
 | |
|     if (!EVP_CipherInit_ex(ctx,
 | |
|                            ciphers[router->encryption.encryption_algorithm](router->encryption.key_len),
 | |
|                            NULL,
 | |
|                            key,
 | |
|                            iv,
 | |
|                            action))
 | |
|     {
 | |
|         MXS_ERROR("Error in EVP_CipherInit_ex for algo %d",
 | |
|                   router->encryption.encryption_algorithm);
 | |
|         mxs_evp_cipher_ctx_free(ctx);
 | |
|         MXS_FREE(outbuf);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     /* Set no padding */
 | |
|     EVP_CIPHER_CTX_set_padding(ctx, 0);
 | |
| 
 | |
|     /* Encryt/Decrypt the input data */
 | |
|     if (!EVP_CipherUpdate(ctx,
 | |
|                           out_ptr + 4,
 | |
|                           &outlen,
 | |
|                           buffer,
 | |
|                           size))
 | |
|     {
 | |
|         MXS_ERROR("Error in EVP_CipherUpdate");
 | |
|         mxs_evp_cipher_ctx_free(ctx);
 | |
|         MXS_FREE(outbuf);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     int finale_ret = 1;
 | |
| 
 | |
|     /* Enc/dec finish is differently handled for AES_CBC */
 | |
|     if (router->encryption.encryption_algorithm != BLR_AES_CBC)
 | |
|     {
 | |
|         /* Call Final_ex */
 | |
|         if (!EVP_CipherFinal_ex(ctx,
 | |
|                                 (out_ptr + 4 + outlen),
 | |
|                                 (int*)&flen))
 | |
|         {
 | |
|             MXS_ERROR("Error in EVP_CipherFinal_ex");
 | |
|             finale_ret = 0;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /**
 | |
|          * If some bytes (ctx.buf_len) are still available in ctx.buf
 | |
|          * handle them with ECB and XOR
 | |
|          */
 | |
|         if (size - outlen > 0)
 | |
|         {
 | |
|             if (!blr_aes_create_tail_for_cbc(out_ptr + 4 + outlen,
 | |
|                                              mxs_evp_cipher_ctx_buf(ctx),
 | |
|                                              size - outlen,
 | |
|                                              mxs_evp_cipher_ctx_oiv(ctx),
 | |
|                                              router->encryption.key_value,
 | |
|                                              router->encryption.key_len))
 | |
|             {
 | |
|                 MXS_ERROR("Error in blr_aes_create_tail_for_cbc");
 | |
|                 finale_ret = 0;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!finale_ret)
 | |
|     {
 | |
|         MXS_FREE(outbuf);
 | |
|         outbuf = NULL;
 | |
|     }
 | |
| 
 | |
|     mxs_evp_cipher_ctx_free(ctx);
 | |
| 
 | |
|     return outbuf;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The routine prepares a binlg event for encryption and ecrypts it
 | |
|  *
 | |
|  * @param router    The ruter instance
 | |
|  * @buf             The binlog event
 | |
|  * @size            The event size (CRC32 four bytes included)
 | |
|  * @pos             The position of the event in binlog file
 | |
|  * @nonce           The binlog nonce 12 bytes as in MXS_START_ENCRYPTION_EVENT
 | |
|  *                  of requested or current binlog file
 | |
|  *                  If nonce is NULL the one from current binlog file is used.
 | |
|  * @action          Encryption action: 1 Encryp, 0 Decryot
 | |
|  * @return          A GWBUF buffer or NULL omn error
 | |
|  */
 | |
| static GWBUF* blr_prepare_encrypted_event(ROUTER_INSTANCE* router,
 | |
|                                           uint8_t* buf,
 | |
|                                           uint32_t size,
 | |
|                                           uint32_t pos,
 | |
|                                           const uint8_t* nonce,
 | |
|                                           int action)
 | |
| {
 | |
|     uint8_t iv[BLRM_IV_LENGTH];
 | |
|     uint32_t file_offset = pos;
 | |
|     uint8_t event_size[4];
 | |
|     const uint8_t* nonce_ptr = nonce;
 | |
|     GWBUF* encrypted;
 | |
|     uint8_t* enc_ptr;
 | |
| 
 | |
|     /* If nonce is NULL use the router current binlog file */
 | |
|     if (nonce_ptr == NULL)
 | |
|     {
 | |
|         BINLOG_ENCRYPTION_CTX* encryption_ctx = (BINLOG_ENCRYPTION_CTX*)(router->encryption_ctx);
 | |
|         nonce_ptr = encryption_ctx->nonce;
 | |
|     }
 | |
| 
 | |
|     /* Encryption IV is 12 bytes nonce + 4 bytes event position */
 | |
|     memcpy(iv, nonce_ptr, BLRM_NONCE_LENGTH);
 | |
|     gw_mysql_set_byte4(iv + BLRM_NONCE_LENGTH, (unsigned long)file_offset);
 | |
| 
 | |
|     /**
 | |
|      * Encrypt binlog event, steps:
 | |
|      *
 | |
|      * 1: Save event size (buf + 9, 4 bytes)
 | |
|      * 2: move first 4 bytes of buf to buf + 9
 | |
|      * 3: encrypt buf starting from buf + 4 (so it will be event_size - 4)
 | |
|      * 4: move encrypted_data + 9 (4 bytes) to  encrypted_data[0]
 | |
|      * 5: Copy saved_event_size 4 bytes into encrypted_data + 9
 | |
|      */
 | |
| 
 | |
|     /* (1): Save event size (buf + 9, 4 bytes) */
 | |
|     memcpy(&event_size, buf + BINLOG_EVENT_LEN_OFFSET, 4);
 | |
|     /* (2): move first 4 bytes of buf to buf + 9 */
 | |
|     memmove(buf + BINLOG_EVENT_LEN_OFFSET, buf, 4);
 | |
| 
 | |
| #ifdef SS_DEBUG
 | |
|     char iv_hex[AES_BLOCK_SIZE * 2 + 1] = "";
 | |
|     char nonce_hex[BLRM_NONCE_LENGTH * 2 + 1] = "";
 | |
| 
 | |
|     /* Human readable debug */
 | |
|     gw_bin2hex(iv_hex, iv, BLRM_IV_LENGTH);
 | |
|     gw_bin2hex(nonce_hex, nonce_ptr, BLRM_NONCE_LENGTH);
 | |
| 
 | |
|     MXS_DEBUG("** Encryption/Decryption of Event @ %lu: the IV is %s, "
 | |
|               "size is %lu, next pos is %lu",
 | |
|               (unsigned long)pos,
 | |
|               iv_hex,
 | |
|               (unsigned long)size,
 | |
|               (unsigned long)(pos + size));
 | |
| #endif
 | |
| 
 | |
|     /**
 | |
|      * (3): encrypt the event stored in buf starting from (buf + 4):
 | |
|      * with len (event_size - 4)
 | |
|      *
 | |
|      * NOTE: the encrypted_data buffer returned by blr_aes_encrypt() contains:
 | |
|      * (size - 4) encrypted bytes + (4) bytes event size in clear
 | |
|      *
 | |
|      * The encrypted buffer has same size of the original event (size variable)
 | |
|      */
 | |
| 
 | |
|     if ((encrypted = blr_aes_crypt(router,
 | |
|                                    buf + 4,
 | |
|                                    size - 4,
 | |
|                                    iv,
 | |
|                                    action)) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     enc_ptr = GWBUF_DATA(encrypted);
 | |
| 
 | |
|     /* (4): move encrypted_data + 9 (4 bytes) to  encrypted_data[0] */
 | |
|     memmove(enc_ptr, enc_ptr + BINLOG_EVENT_LEN_OFFSET, 4);
 | |
| 
 | |
|     /* (5): Copy saved_event_size 4 bytes into encrypted_data + 9 */
 | |
|     memcpy(enc_ptr + BINLOG_EVENT_LEN_OFFSET, &event_size, 4);
 | |
| 
 | |
|     return encrypted;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the encryption algorithm string
 | |
|  *
 | |
|  * @param algo    The algorithm value
 | |
|  * @return        A static string or NULL
 | |
|  */
 | |
| const char* blr_get_encryption_algorithm(int algo)
 | |
| {
 | |
|     if (algo < 0 || algo >= BINLOG_MAX_CRYPTO_SCHEME)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return blr_encryption_algorithm_names[algo];
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the encryption algorithm value
 | |
|  *
 | |
|  * @param name   The algorithm string
 | |
|  * @return       The numeric value or -1 on error
 | |
|  */
 | |
| int blr_check_encryption_algorithm(const char* name)
 | |
| {
 | |
|     if (name)
 | |
|     {
 | |
|         if (strcasecmp(name, "aes_cbc") == 0)
 | |
|         {
 | |
|             return BLR_AES_CBC;
 | |
|         }
 | |
| #if OPENSSL_VERSION_NUMBER > 0x10000000L
 | |
|         if (strcasecmp(name, "aes_ctr") == 0)
 | |
|         {
 | |
|             return BLR_AES_CTR;
 | |
|         }
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return a string with a list of supported algorithms
 | |
|  *
 | |
|  * @return    The algorith list as char *
 | |
|  */
 | |
| const char* blr_encryption_algorithm_list(void)
 | |
| {
 | |
|     return blr_encryption_algorithm_list_names;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates the final buffer for AES_CBC encryption
 | |
|  *
 | |
|  * As the encrypted/decrypted data must have same size of inpu data
 | |
|  * the remaining data from EVP_CipherUpdate with AES_CBC engine
 | |
|  * are handled this way:
 | |
|  *
 | |
|  * 1) The IV in the previous stage is encrypted with AES_ECB
 | |
|  *    using the key and a NULL iv
 | |
|  * 2) the remaing data from previous stage are XORed with thant buffer
 | |
|  *    and the the ouput buffer contains the result
 | |
|  *
 | |
|  * @param output    The outut buffer to fill
 | |
|  * @param input     The input buffere 8remaining bytes from previous stage)
 | |
|  * @param in_size   The inout data size
 | |
|  * @param iv        The IV used in previous stage
 | |
|  * @param key       The encryption key
 | |
|  * @param key_len   The lenght of encrytion key
 | |
|  * @return          Return 1 on success, 0 otherwise
 | |
|  */
 | |
| static int blr_aes_create_tail_for_cbc(uint8_t* output,
 | |
|                                        uint8_t* input,
 | |
|                                        uint32_t in_size,
 | |
|                                        uint8_t* iv,
 | |
|                                        uint8_t* key,
 | |
|                                        unsigned int key_len)
 | |
| {
 | |
|     uint8_t mask[AES_BLOCK_SIZE];
 | |
|     int mlen = 0;
 | |
| 
 | |
|     EVP_CIPHER_CTX* t_ctx = mxs_evp_cipher_ctx_alloc();
 | |
| 
 | |
|     /* Initialise with AES_ECB and NULL iv */
 | |
|     if (!EVP_CipherInit_ex(t_ctx,
 | |
|                            ciphers[BLR_AES_ECB](key_len),
 | |
|                            NULL,
 | |
|                            key,
 | |
|                            NULL,    /* NULL iv */
 | |
|                            BINLOG_FLAG_ENCRYPT))
 | |
|     {
 | |
|         MXS_ERROR("Error in EVP_CipherInit_ex CBC for last block (ECB)");
 | |
|         mxs_evp_cipher_ctx_free(t_ctx);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /* Set no padding */
 | |
|     EVP_CIPHER_CTX_set_padding(t_ctx, 0);
 | |
| 
 | |
|     /* Do the enc/dec of the IV (the one from previous stage) */
 | |
|     if (!EVP_CipherUpdate(t_ctx,
 | |
|                           mask,
 | |
|                           &mlen,
 | |
|                           iv,
 | |
|                           sizeof(mask)))
 | |
|     {
 | |
|         MXS_ERROR("Error in EVP_CipherUpdate ECB");
 | |
|         mxs_evp_cipher_ctx_free(t_ctx);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Now the output buffer contains
 | |
|      * the XORed data of input data and the mask (encryption of IV)
 | |
|      *
 | |
|      * Note: this also works for decryption
 | |
|      */
 | |
|     for (uint32_t i = 0; i < in_size; i++)
 | |
|     {
 | |
|         output[i] = input[i] ^ mask[i];
 | |
|     }
 | |
| 
 | |
|     mxs_evp_cipher_ctx_free(t_ctx);
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Run checks against some fieds in replication header
 | |
|  *
 | |
|  * @param router        The router instance
 | |
|  * @param pos           The current pos in binlog
 | |
|  * @param hdr           The replication header struct
 | |
|  * @param binlogname    The binlogname, for error message
 | |
|  * @param errmsg        The errormessage to fill
 | |
|  * @return              0 on error and 1 on success
 | |
|  *
 | |
|  * 1 ok, 0 err */
 | |
| static int blr_binlog_event_check(ROUTER_INSTANCE* router,
 | |
|                                   unsigned long pos,
 | |
|                                   REP_HEADER* hdr,
 | |
|                                   char* binlogname,
 | |
|                                   char* errmsg)
 | |
| {
 | |
|     /* event pos & size checks */
 | |
|     if (hdr->event_size == 0
 | |
|         || ((hdr->next_pos != (pos + hdr->event_size))
 | |
|             && (hdr->event_type != ROTATE_EVENT)))
 | |
|     {
 | |
|         snprintf(errmsg,
 | |
|                  BINLOG_ERROR_MSG_LEN,
 | |
|                  "Client requested master to start replication from invalid "
 | |
|                  "position %lu in binlog file '%s'",
 | |
|                  pos,
 | |
|                  binlogname);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     /* event type checks */
 | |
|     if (router->mariadb10_compat)
 | |
|     {
 | |
|         if (hdr->event_type > MAX_EVENT_TYPE_MARIADB10)
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Invalid MariaDB 10 event type 0x%x at %lu in binlog file '%s'",
 | |
|                      hdr->event_type,
 | |
|                      pos,
 | |
|                      binlogname);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (hdr->event_type > MAX_EVENT_TYPE)
 | |
|         {
 | |
|             snprintf(errmsg,
 | |
|                      BINLOG_ERROR_MSG_LEN,
 | |
|                      "Invalid event type 0x%x at %lu in binlog file '%s'",
 | |
|                      hdr->event_type,
 | |
|                      pos,
 | |
|                      binlogname);
 | |
|             return 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* check is OK */
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Fill a string buffer with HEX representation of CRC32 (4) bytes
 | |
|  * at the end of binlog event
 | |
|  *
 | |
|  * @param hdr    The replication header struct
 | |
|  * @param buffer The buffer with binlog event
 | |
|  * @output       The output buffer to fill, preallocated by the caller
 | |
|  */
 | |
| static void blr_report_checksum(REP_HEADER hdr,
 | |
|                                 const uint8_t* buffer,
 | |
|                                 char* output)
 | |
| {
 | |
|     uint8_t cksum_data[BINLOG_EVENT_CRC_SIZE];
 | |
|     char* ptr = output + strlen(BLR_REPORT_CHECKSUM_FORMAT);
 | |
|     strcpy(output, BLR_REPORT_CHECKSUM_FORMAT);
 | |
| 
 | |
|     /* Hex representation of checksum */
 | |
|     cksum_data[3] = *(buffer + hdr.event_size - 4 - BINLOG_EVENT_HDR_LEN);
 | |
|     cksum_data[2] = *(buffer + hdr.event_size - 3 - BINLOG_EVENT_HDR_LEN);
 | |
|     cksum_data[1] = *(buffer + hdr.event_size - 2 - BINLOG_EVENT_HDR_LEN);
 | |
|     cksum_data[0] = *(buffer + hdr.event_size - 1 - BINLOG_EVENT_HDR_LEN);
 | |
| 
 | |
|     gw_bin2hex(ptr, cksum_data, BINLOG_EVENT_CRC_SIZE);
 | |
|     for (char* p = ptr; *p; ++p)
 | |
|     {
 | |
|         *p = tolower(*p);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Save MariaDB GTID found in complete transaction
 | |
|  *
 | |
|  * @param    inst The router instance
 | |
|  * @return   true on success, false otherwise
 | |
|  */
 | |
| bool blr_save_mariadb_gtid(ROUTER_INSTANCE* inst)
 | |
| {
 | |
|     int sql_ret;
 | |
|     static const char insert_tpl[] = "INSERT OR FAIL INTO gtid_maps("
 | |
|                                      "rep_domain, "
 | |
|                                      "server_id, "
 | |
|                                      "sequence, "
 | |
|                                      "binlog_file, "
 | |
|                                      "start_pos, "
 | |
|                                      "end_pos) "
 | |
|                                      "VALUES ( "
 | |
|                                      "%" PRIu32 ", "
 | |
|                                                 "%" PRIu32 ", "
 | |
|                                                            "%" PRIu64 ", "
 | |
|                                                                       "\"%s\", "
 | |
|                                                                       "%" PRIu64 ", "
 | |
|                                                                                  "%" PRIu64 ");";
 | |
| 
 | |
|     static const char update_tpl[] = "UPDATE gtid_maps SET "
 | |
|                                      "start_pos = %" PRIu64 ", "
 | |
|                                                             "end_pos = %" PRIu64 " "
 | |
|                                                                                  "WHERE rep_domain = %" PRIu32
 | |
|         " AND "
 | |
|         "server_id = %"
 | |
|         PRIu32 " AND "
 | |
|                "sequence = %"
 | |
|         PRIu64 " AND "
 | |
|                "binlog_file = \"%s\";";
 | |
|     char* errmsg;
 | |
|     char sql_stmt[GTID_SQL_BUFFER_SIZE];
 | |
|     MARIADB_GTID_INFO gtid_info;
 | |
|     MARIADB_GTID_ELEMS gtid_elms;
 | |
| 
 | |
|     strcpy(gtid_info.gtid, inst->pending_transaction.gtid);
 | |
|     strcpy(gtid_info.binlog_name, inst->binlog_name);
 | |
|     gtid_info.start = inst->pending_transaction.start_pos;
 | |
|     gtid_info.end = inst->pending_transaction.end_pos;
 | |
|     memcpy(>id_elms,
 | |
|            &inst->pending_transaction.gtid_elms,
 | |
|            sizeof(MARIADB_GTID_ELEMS));
 | |
| 
 | |
|     /* Prepare INSERT SQL */
 | |
|     snprintf(sql_stmt,
 | |
|              GTID_SQL_BUFFER_SIZE,
 | |
|              insert_tpl,
 | |
|              gtid_elms.domain_id,
 | |
|              gtid_elms.server_id,
 | |
|              gtid_elms.seq_no,
 | |
|              gtid_info.binlog_name,
 | |
|              gtid_info.start,
 | |
|              gtid_info.end);
 | |
| 
 | |
|     /* Save GTID into repo */
 | |
|     if ((sql_ret = sqlite3_exec(inst->gtid_maps,
 | |
|                                 sql_stmt,
 | |
|                                 NULL,
 | |
|                                 NULL,
 | |
|                                 &errmsg)) != SQLITE_OK)
 | |
|     {
 | |
|         if (sql_ret == SQLITE_CONSTRAINT)
 | |
|         {
 | |
|             sqlite3_free(errmsg);
 | |
|             /* Prepare UPDATE SQL */
 | |
|             snprintf(sql_stmt,
 | |
|                      GTID_SQL_BUFFER_SIZE,
 | |
|                      update_tpl,
 | |
|                      gtid_info.start,
 | |
|                      gtid_info.end,
 | |
|                      gtid_elms.domain_id,
 | |
|                      gtid_elms.server_id,
 | |
|                      gtid_elms.seq_no,
 | |
|                      gtid_info.binlog_name);
 | |
| 
 | |
|             /* Update GTID into repo */
 | |
|             if ((sql_ret = sqlite3_exec(inst->gtid_maps,
 | |
|                                         sql_stmt,
 | |
|                                         NULL,
 | |
|                                         NULL,
 | |
|                                         &errmsg)) != SQLITE_OK)
 | |
|             {
 | |
|                 MXS_ERROR("Service %s: failed to update GTID %s for %s:%lu,%lu "
 | |
|                           "into gtid_maps database: %s",
 | |
|                           inst->service->name(),
 | |
|                           gtid_info.gtid,
 | |
|                           gtid_info.binlog_name,
 | |
|                           gtid_info.start,
 | |
|                           gtid_info.end,
 | |
|                           errmsg);
 | |
|                 sqlite3_free(errmsg);
 | |
|                 return false;
 | |
|             }
 | |
|             /* Remove SQLITE_CONSTRAINT error message */
 | |
|             sqlite3_free(errmsg);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_ERROR("Service %s: failed to insert GTID %s for %s:%lu,%lu "
 | |
|                       "into gtid_maps database: %s",
 | |
|                       inst->service->name(),
 | |
|                       gtid_info.gtid,
 | |
|                       gtid_info.binlog_name,
 | |
|                       gtid_info.start,
 | |
|                       gtid_info.end,
 | |
|                       errmsg);
 | |
|             sqlite3_free(errmsg);
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     sqlite3_free(errmsg);
 | |
| 
 | |
|     MXS_DEBUG("Saved/udated MariaDB GTID '%s', %s:%lu,%lu, SQL [%s]",
 | |
|               gtid_info.gtid,
 | |
|               inst->binlog_name,
 | |
|               gtid_info.start,
 | |
|               gtid_info.end,
 | |
|               sql_stmt);
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * GTID 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 gtid_select_cb(void* data,
 | |
|                           int cols,
 | |
|                           char** values,
 | |
|                           char** names)
 | |
| {
 | |
|     MARIADB_GTID_INFO* result = (MARIADB_GTID_INFO*)data;
 | |
| 
 | |
|     mxb_assert(cols >= 4);
 | |
| 
 | |
|     if (values[0]
 | |
|         && values[1]
 | |
|         && values[2]
 | |
|         && values[3])
 | |
|     {
 | |
|         strcpy(result->gtid, values[0]);
 | |
|         strcpy(result->binlog_name, values[1]);
 | |
|         result->start = atoll(values[2]);
 | |
|         result->end = atoll(values[3]);
 | |
| 
 | |
|         if (cols > 4
 | |
|             && (values[4]
 | |
|                 && values[5]
 | |
|                 && values[6]))
 | |
|         {
 | |
|             result->gtid_elms.domain_id = atoll(values[4]);
 | |
|             result->gtid_elms.server_id = atoll(values[5]);
 | |
|             result->gtid_elms.seq_no = atoll(values[6]);
 | |
|         }
 | |
| 
 | |
|         if (result->start > 4)
 | |
|         {
 | |
|             mxb_assert(result->end > result->start);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             mxb_assert(result->start > 0 && result->start > 0);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get MariaDB GTID from repo
 | |
|  *
 | |
|  * @param    slave   The current slave instance
 | |
|  * @param    gtid    The GTID to look for
 | |
|  * @param    result  The (allocated) ouput data to fill
 | |
|  * @return   True if with found GTID or false
 | |
|  */
 | |
| bool blr_fetch_mariadb_gtid(ROUTER_SLAVE* slave,
 | |
|                             const char* gtid,
 | |
|                             MARIADB_GTID_INFO* result)
 | |
| {
 | |
|     char* errmsg = NULL;
 | |
|     char select_query[GTID_SQL_BUFFER_SIZE];
 | |
|     MARIADB_GTID_ELEMS gtid_elms = {};
 | |
|     /* The fields in the WHERE clause belong to
 | |
|      * primary key but binlog_file cannot be part of
 | |
|      * WHERE because GTID is made of X-Y-Z, three elements.
 | |
|      *
 | |
|      * The query has ORDER BY id DESC LIMIT 1 in order
 | |
|      * to get the right GTID, even in case of database
 | |
|      * with old content.
 | |
|      */
 | |
|     static const char select_tpl[] = "SELECT "
 | |
|                                      "(rep_domain ||"
 | |
|                                      " '-' || server_id ||"
 | |
|                                      " '-' || sequence) AS gtid, "
 | |
|                                      "binlog_file, "
 | |
|                                      "start_pos, "
 | |
|                                      "end_pos, "
 | |
|                                      "rep_domain, "
 | |
|                                      "server_id, "
 | |
|                                      "sequence "
 | |
|                                      "FROM gtid_maps "
 | |
|                                      "WHERE (rep_domain = %" PRIu32 " AND "
 | |
|                                                                     "server_id = %" PRIu32 " AND "
 | |
|                                                                                            "sequence = %"
 | |
|         PRIu64 ") "
 | |
|                "ORDER BY id DESC LIMIT 1;";
 | |
|     mxb_assert(gtid != NULL);
 | |
| 
 | |
|     /* Parse GTID value into its components */
 | |
|     if (!blr_parse_gtid(gtid, >id_elms))
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     snprintf(select_query,
 | |
|              GTID_SQL_BUFFER_SIZE,
 | |
|              select_tpl,
 | |
|              gtid_elms.domain_id,
 | |
|              gtid_elms.server_id,
 | |
|              gtid_elms.seq_no);
 | |
| 
 | |
|     /* Find the GTID */
 | |
|     if (sqlite3_exec(slave->gtid_maps,
 | |
|                      select_query,
 | |
|                      gtid_select_cb,
 | |
|                      result,
 | |
|                      &errmsg) != SQLITE_OK)
 | |
|     {
 | |
|         MXS_ERROR("Failed to select GTID %s from GTID maps DB: %s, select [%s]",
 | |
|                   gtid,
 | |
|                   errmsg,
 | |
|                   select_query);
 | |
|         sqlite3_free(errmsg);
 | |
|         return false;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (result->gtid[0])
 | |
|         {
 | |
|             MXS_INFO("Binlog file to read from is %" PRIu32 "/%" PRIu32 "/%s",
 | |
|                      result->gtid_elms.domain_id,
 | |
|                      result->gtid_elms.server_id,
 | |
|                      result->binlog_name);
 | |
|         }
 | |
|     }
 | |
|     return result->gtid[0] ? true : false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get the next binlog file sequence number
 | |
|  *
 | |
|  * @param filename    The current filename
 | |
|  * @return            0 on error, >0 as sequence number
 | |
|  */
 | |
| unsigned int blr_file_get_next_seqno(const char* filename)
 | |
| {
 | |
|     const char* sptr;
 | |
|     int filenum;
 | |
| 
 | |
|     if ((sptr = strrchr(filename, '.')) == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     filenum = atoi(sptr + 1);
 | |
|     if (filenum)
 | |
|     {
 | |
|         filenum++;
 | |
|     }
 | |
| 
 | |
|     return filenum;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the binlog file size.
 | |
|  *
 | |
|  * @param filename    The current filename
 | |
|  * @return            0 on error, >0 size
 | |
|  */
 | |
| uint32_t blr_slave_get_file_size(const char* filename)
 | |
| {
 | |
|     struct stat statb;
 | |
| 
 | |
|     if (stat(filename, &statb) == 0)
 | |
|     {
 | |
|         return statb.st_size;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         MXS_ERROR("Failed to get %s file size: %d %s",
 | |
|                   filename,
 | |
|                   errno,
 | |
|                   mxs_strerror(errno));
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Extract the GTID the client requested
 | |
|  *
 | |
|  * @param gtid   Then input GTID
 | |
|  * @param info   The GTID structure to fil
 | |
|  * @return       True for a parsed GTID string or false
 | |
|  */
 | |
| bool blr_parse_gtid(const char* gtid, MARIADB_GTID_ELEMS* info)
 | |
| {
 | |
|     const char* ptr = gtid;
 | |
|     int read = 0;
 | |
|     int len = strlen(gtid);
 | |
| 
 | |
|     while (ptr < gtid + len)
 | |
|     {
 | |
|         if (!isdigit(*ptr))
 | |
|         {
 | |
|             ptr++;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             char* end;
 | |
|             switch (read)
 | |
|             {
 | |
|             case 0:
 | |
|                 info->domain_id = strtoul(ptr, &end, 10);
 | |
|                 break;
 | |
| 
 | |
|             case 1:
 | |
|                 info->server_id = strtoul(ptr, &end, 10);
 | |
|                 break;
 | |
| 
 | |
|             case 2:
 | |
|                 info->seq_no = strtoul(ptr, &end, 10);
 | |
|                 break;
 | |
|             }
 | |
|             read++;
 | |
|             ptr = end;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return (info->server_id && info->seq_no) ? true : false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get MariaDB GTID from repo
 | |
|  *
 | |
|  * @param    router  The current router instance
 | |
|  * @param    result  The (allocated) ouput data to fill
 | |
|  * @return   False on sqlite errors
 | |
|  *           True even if the gtid_maps is empty
 | |
|  *           The caller must check result->gtid value
 | |
|  */
 | |
| 
 | |
| bool blr_load_last_mariadb_gtid(ROUTER_INSTANCE* router,
 | |
|                                 MARIADB_GTID_INFO* result)
 | |
| {
 | |
|     char* errmsg = NULL;
 | |
|     MARIADB_GTID_ELEMS gtid_elms = {};
 | |
|     static const char last_gtid[] = "SELECT "
 | |
|                                     "(rep_domain ||"
 | |
|                                     " '-' || server_id ||"
 | |
|                                     " '-' || sequence) AS gtid, "
 | |
|                                     "binlog_file, "
 | |
|                                     "start_pos, "
 | |
|                                     "end_pos, "
 | |
|                                     "rep_domain, "
 | |
|                                     "server_id, "
 | |
|                                     "sequence "
 | |
|                                     "FROM gtid_maps "
 | |
|                                     "WHERE id = "
 | |
|                                     "(SELECT MAX(id) "
 | |
|                                     "FROM gtid_maps "
 | |
|                                     "WHERE start_pos > 4);";
 | |
| 
 | |
|     /* Find the last GTID */
 | |
|     if (sqlite3_exec(router->gtid_maps,
 | |
|                      last_gtid,
 | |
|                      gtid_select_cb,
 | |
|                      result,
 | |
|                      &errmsg) != SQLITE_OK)
 | |
|     {
 | |
|         MXS_ERROR("Failed to select last GTID"
 | |
|                   " from GTID maps DB: %s, select [%s]",
 | |
|                   errmsg,
 | |
|                   last_gtid);
 | |
|         sqlite3_free(errmsg);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Select callback for blr_file_next_exists()
 | |
|  *
 | |
|  * @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 gtid_file_select_cb(void* data,
 | |
|                                int cols,
 | |
|                                char** values,
 | |
|                                char** names)
 | |
| {
 | |
|     MARIADB_GTID_INFO* result = (MARIADB_GTID_INFO*)data;
 | |
| 
 | |
|     mxb_assert(cols >= 4);
 | |
| 
 | |
|     if (values[0]
 | |
|         && values[1]
 | |
|         && values[2]
 | |
|         && values[3])
 | |
|     {
 | |
|         strcpy(result->binlog_name, values[3]);
 | |
|         result->gtid_elms.domain_id = atoll(values[1]);
 | |
|         result->gtid_elms.server_id = atoll(values[2]);
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Get Last file from GTID from repo
 | |
|  *
 | |
|  * @param    router  The current router instance
 | |
|  * @param    result  The (allocated) ouput data to fill
 | |
|  * @return   False on sqlite errors
 | |
|  *           True even if the gtid_maps is empty
 | |
|  *           The caller must check result->gtid value
 | |
|  */
 | |
| 
 | |
| bool blr_get_last_file(ROUTER_INSTANCE* router,
 | |
|                        MARIADB_GTID_INFO* result)
 | |
| {
 | |
|     char* errmsg = NULL;
 | |
|     MARIADB_GTID_ELEMS gtid_elms = {};
 | |
|     static const char last_gtid[] = "SELECT "
 | |
|                                     "(rep_domain ||"
 | |
|                                     " '-' || server_id ||"
 | |
|                                     " '-' || sequence) AS gtid, "
 | |
|                                     "binlog_file, "
 | |
|                                     "start_pos, "
 | |
|                                     "end_pos, "
 | |
|                                     "rep_domain, "
 | |
|                                     "server_id, "
 | |
|                                     "sequence "
 | |
|                                     "FROM gtid_maps "
 | |
|                                     "WHERE id = "
 | |
|                                     "(SELECT MAX(id) FROM gtid_maps);";
 | |
| 
 | |
|     /* Find the the last file */
 | |
|     if (sqlite3_exec(router->gtid_maps,
 | |
|                      last_gtid,
 | |
|                      gtid_select_cb,
 | |
|                      result,
 | |
|                      &errmsg) != SQLITE_OK)
 | |
|     {
 | |
|         MXS_ERROR("Failed to select last written binlog file"
 | |
|                   " from GTID maps DB: %s, select [%s]",
 | |
|                   errmsg,
 | |
|                   last_gtid);
 | |
|         sqlite3_free(errmsg);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Compare the specified router file and slave one
 | |
|  *
 | |
|  * @param    router    The router instance
 | |
|  * @param    info      Additional file info
 | |
|  * @param    r_file    The router binlog file
 | |
|  * @param    s_file    The slave file
 | |
|  * @return             True or false
 | |
|  */
 | |
| bool blr_compare_binlogs(const ROUTER_INSTANCE* router,
 | |
|                          const MARIADB_GTID_ELEMS* info,
 | |
|                          const char* r_file,
 | |
|                          const char* s_file)
 | |
| {
 | |
|     // Just strcmp()
 | |
|     if (router->storage_type == BLR_BINLOG_STORAGE_FLAT)
 | |
|     {
 | |
|         return strcmp(r_file, s_file) == 0;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         // domain_id, server_id and strcmp()
 | |
|         return (router->mariadb10_gtid_domain == info->domain_id)
 | |
|                && (router->orig_masterid == static_cast<int>(info->server_id))
 | |
|                && strcmp(r_file, s_file) == 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check whether the current slave file is the current file
 | |
|  * being written by the router
 | |
|  *
 | |
|  * @param    router    The router instance
 | |
|  * @param    slave     The connected slave
 | |
|  * @return             True or false
 | |
|  */
 | |
| bool blr_is_current_binlog(ROUTER_INSTANCE* router,
 | |
|                            ROUTER_SLAVE* slave)
 | |
| {
 | |
|     return blr_compare_binlogs(router,
 | |
|                                &slave->f_info.gtid_elms,
 | |
|                                router->binlog_name,
 | |
|                                slave->binlog_name);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Check whether a binlog file exists.
 | |
|  *
 | |
|  * The file could have been manually removed by mistake.
 | |
|  * The check is done when:
 | |
|  * ROTATE event (real or fake) is seen by blr_rotate_event()
 | |
|  * or
 | |
|  * when a slave connects with a binlog_file name.
 | |
|  *
 | |
|  * If param log_file is NULL, the current router->binlog_name
 | |
|  * is checked.
 | |
|  *
 | |
|  *
 | |
|  * @param router       The router instance
 | |
|  * @param info_file    The GTID info file name to check
 | |
|  * @return             True if file exists, false otherwise.
 | |
|  *
 | |
|  */
 | |
| bool blr_binlog_file_exists(ROUTER_INSTANCE* router,
 | |
|                             const MARIADB_GTID_INFO* info_file)
 | |
| {
 | |
|     bool ret = true;
 | |
|     char path[PATH_MAX + 1] = "";
 | |
|     strcpy(path, router->binlogdir);
 | |
|     strcat(path, "/");
 | |
| 
 | |
|     /**
 | |
|      * Create file prefix using domain and server_id prefix
 | |
|      */
 | |
|     if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
 | |
|     {
 | |
|         char prefix[BINLOG_FILE_EXTRA_INFO];
 | |
|         // Add prefix
 | |
|         sprintf(prefix,
 | |
|                 "%" PRIu32 "/%" PRIu32 "/",
 | |
|                 info_file ?
 | |
|                 info_file->gtid_elms.domain_id :
 | |
|                 router->mariadb10_gtid_domain,
 | |
|                 info_file ?
 | |
|                 info_file->gtid_elms.server_id :
 | |
|                 router->orig_masterid);
 | |
|         strcat(path, prefix);
 | |
|     }
 | |
| 
 | |
|     // Set final file name full path
 | |
|     strcat(path,
 | |
|            info_file == NULL ?
 | |
|            router->binlog_name :
 | |
|            info_file->binlog_name);
 | |
| 
 | |
|     // Check file
 | |
|     if (access(path, F_OK) == -1 && errno == ENOENT)
 | |
|     {
 | |
|         // No file found
 | |
|         MXS_WARNING("%s: %s, missing binlog file '%s'",
 | |
|                     router->service->name(),
 | |
|                     info_file == NULL ?
 | |
|                     "ROTATE_EVENT" :
 | |
|                     "Slave request",
 | |
|                     path);
 | |
| 
 | |
|         ret = false;
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add/Update binlog file details into GTID mapd db:
 | |
|  *
 | |
|  * binlog file name
 | |
|  * pos = 4
 | |
|  * server_id = router->orig_masterid
 | |
|  * sequence = 0
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  */
 | |
| void blr_file_update_gtid(ROUTER_INSTANCE* router)
 | |
| {
 | |
|     MARIADB_GTID_ELEMS gtid_elms;
 | |
|     // Add GTID domain
 | |
|     gtid_elms.domain_id = router->mariadb10_gtid_domain;
 | |
|     // router->orig_masterid keeps the original ID
 | |
|     gtid_elms.server_id = router->orig_masterid;
 | |
|     // Set GTID sequence to 0
 | |
|     gtid_elms.seq_no = 0;
 | |
|     // Pos 4 for start/end_pos
 | |
|     router->pending_transaction.end_pos = 4;
 | |
|     router->pending_transaction.start_pos = 4;
 | |
|     // Update all the gtid_elms
 | |
|     memcpy(&router->pending_transaction.gtid_elms,
 | |
|            >id_elms,
 | |
|            sizeof(MARIADB_GTID_ELEMS));
 | |
| 
 | |
|     /**
 | |
|      * Save GTID into repo
 | |
|      *
 | |
|      * If router->orig_masterid is not set yet
 | |
|      * don't update the db
 | |
|      * This happens when mariadb10_master_gtid id Off
 | |
|      * and a new file has been created by blr_file_init()
 | |
|      */
 | |
|     if (gtid_elms.server_id > 0)
 | |
|     {
 | |
|         blr_save_mariadb_gtid(router);
 | |
|     }
 | |
| }
 |