1/******************************************************
2hot backup tool for InnoDB
3(c) 2009-2015 Percona LLC and/or its affiliates
4Originally Created 3/3/2009 Yasufumi Kinoshita
5Written by Alexey Kopytov, Aleksandr Kuzminsky, Stewart Smith, Vadim Tkachenko,
6Yasufumi Kinoshita, Ignacio Nin and Baron Schwartz.
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; version 2 of the License.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program; if not, write to the Free Software
19Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20
21*******************************************************
22
23This file incorporates work covered by the following copyright and
24permission notice:
25
26Copyright (c) 2000, 2011, MySQL AB & Innobase Oy. All Rights Reserved.
27
28This program is free software; you can redistribute it and/or modify it under
29the terms of the GNU General Public License as published by the Free Software
30Foundation; version 2 of the License.
31
32This program is distributed in the hope that it will be useful, but WITHOUT
33ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
34FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
35
36You should have received a copy of the GNU General Public License along with
37this program; if not, write to the Free Software Foundation, Inc., 59 Temple
38Place, Suite 330, Boston, MA 02111-1307 USA
39
40*******************************************************/
41#define MYSQL_CLIENT
42
43#include <my_global.h>
44#include <mysql.h>
45#include <mysqld.h>
46#include <my_sys.h>
47#include <string.h>
48#include <limits>
49#include "common.h"
50#include "xtrabackup.h"
51#include "srv0srv.h"
52#include "mysql_version.h"
53#include "backup_copy.h"
54#include "backup_mysql.h"
55#include "mysqld.h"
56#include "encryption_plugin.h"
57#include <sstream>
58#include <sql_error.h>
59#include <ut0ut.h>
60
61
62char *tool_name;
63char tool_args[2048];
64
65/* mysql flavor and version */
66mysql_flavor_t server_flavor = FLAVOR_UNKNOWN;
67unsigned long mysql_server_version = 0;
68
69/* server capabilities */
70bool have_changed_page_bitmaps = false;
71bool have_backup_locks = false;
72bool have_backup_safe_binlog_info = false;
73bool have_lock_wait_timeout = false;
74bool have_galera_enabled = false;
75bool have_flush_engine_logs = false;
76bool have_multi_threaded_slave = false;
77bool have_gtid_slave = false;
78
79/* Kill long selects */
80os_thread_id_t kill_query_thread_id;
81os_event_t kill_query_thread_started;
82os_event_t kill_query_thread_stopped;
83os_event_t kill_query_thread_stop;
84
85bool sql_thread_started = false;
86char *mysql_slave_position = NULL;
87char *mysql_binlog_position = NULL;
88char *buffer_pool_filename = NULL;
89
90/* History on server */
91time_t history_start_time;
92time_t history_end_time;
93time_t history_lock_time;
94
95MYSQL *mysql_connection;
96
97extern my_bool opt_ssl_verify_server_cert, opt_use_ssl;
98
99MYSQL *
100xb_mysql_connect()
101{
102 MYSQL *connection = mysql_init(NULL);
103 char mysql_port_str[std::numeric_limits<int>::digits10 + 3];
104
105 sprintf(mysql_port_str, "%d", opt_port);
106
107 if (connection == NULL) {
108 msg("Failed to init MySQL struct: %s.\n",
109 mysql_error(connection));
110 return(NULL);
111 }
112
113 if (!opt_secure_auth) {
114 mysql_options(connection, MYSQL_SECURE_AUTH,
115 (char *) &opt_secure_auth);
116 }
117
118 if (xb_plugin_dir && *xb_plugin_dir){
119 mysql_options(connection, MYSQL_PLUGIN_DIR, xb_plugin_dir);
120 }
121 mysql_options(connection, MYSQL_OPT_PROTOCOL, &opt_protocol);
122 mysql_options(connection,MYSQL_SET_CHARSET_NAME, "utf8");
123
124 msg_ts("Connecting to MySQL server host: %s, user: %s, password: %s, "
125 "port: %s, socket: %s\n", opt_host ? opt_host : "localhost",
126 opt_user ? opt_user : "not set",
127 opt_password ? "set" : "not set",
128 opt_port != 0 ? mysql_port_str : "not set",
129 opt_socket ? opt_socket : "not set");
130
131#ifdef HAVE_OPENSSL
132 if (opt_use_ssl)
133 {
134 mysql_ssl_set(connection, opt_ssl_key, opt_ssl_cert,
135 opt_ssl_ca, opt_ssl_capath,
136 opt_ssl_cipher);
137 mysql_options(connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl);
138 mysql_options(connection, MYSQL_OPT_SSL_CRLPATH,
139 opt_ssl_crlpath);
140 }
141 mysql_options(connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
142 (char*)&opt_ssl_verify_server_cert);
143#endif
144
145 if (!mysql_real_connect(connection,
146 opt_host ? opt_host : "localhost",
147 opt_user,
148 opt_password,
149 "" /*database*/, opt_port,
150 opt_socket, 0)) {
151 msg("Failed to connect to MySQL server: %s.\n",
152 mysql_error(connection));
153 mysql_close(connection);
154 return(NULL);
155 }
156
157 xb_mysql_query(connection, "SET SESSION wait_timeout=2147483",
158 false, true);
159
160 return(connection);
161}
162
163/*********************************************************************//**
164Execute mysql query. */
165MYSQL_RES *
166xb_mysql_query(MYSQL *connection, const char *query, bool use_result,
167 bool die_on_error)
168{
169 MYSQL_RES *mysql_result = NULL;
170
171 if (mysql_query(connection, query)) {
172 msg("Error: failed to execute query %s: %s\n", query,
173 mysql_error(connection));
174 if (die_on_error) {
175 exit(EXIT_FAILURE);
176 }
177 return(NULL);
178 }
179
180 /* store result set on client if there is a result */
181 if (mysql_field_count(connection) > 0) {
182 if ((mysql_result = mysql_store_result(connection)) == NULL) {
183 msg("Error: failed to fetch query result %s: %s\n",
184 query, mysql_error(connection));
185 exit(EXIT_FAILURE);
186 }
187
188 if (!use_result) {
189 mysql_free_result(mysql_result);
190 }
191 }
192
193 return mysql_result;
194}
195
196
197struct mysql_variable {
198 const char *name;
199 char **value;
200};
201
202
203static
204void
205read_mysql_variables(MYSQL *connection, const char *query, mysql_variable *vars,
206 bool vertical_result)
207{
208 MYSQL_RES *mysql_result;
209 MYSQL_ROW row;
210 mysql_variable *var;
211
212 mysql_result = xb_mysql_query(connection, query, true);
213
214 ut_ad(!vertical_result || mysql_num_fields(mysql_result) == 2);
215
216 if (vertical_result) {
217 while ((row = mysql_fetch_row(mysql_result))) {
218 char *name = row[0];
219 char *value = row[1];
220 for (var = vars; var->name; var++) {
221 if (strcmp(var->name, name) == 0
222 && value != NULL) {
223 *(var->value) = strdup(value);
224 }
225 }
226 }
227 } else {
228 MYSQL_FIELD *field;
229
230 if ((row = mysql_fetch_row(mysql_result)) != NULL) {
231 int i = 0;
232 while ((field = mysql_fetch_field(mysql_result))
233 != NULL) {
234 char *name = field->name;
235 char *value = row[i];
236 for (var = vars; var->name; var++) {
237 if (strcmp(var->name, name) == 0
238 && value != NULL) {
239 *(var->value) = strdup(value);
240 }
241 }
242 ++i;
243 }
244 }
245 }
246
247 mysql_free_result(mysql_result);
248}
249
250
251static
252void
253free_mysql_variables(mysql_variable *vars)
254{
255 mysql_variable *var;
256
257 for (var = vars; var->name; var++) {
258 free(*(var->value));
259 }
260}
261
262
263static
264char *
265read_mysql_one_value(MYSQL *connection, const char *query)
266{
267 MYSQL_RES *mysql_result;
268 MYSQL_ROW row;
269 char *result = NULL;
270
271 mysql_result = xb_mysql_query(connection, query, true);
272
273 ut_ad(mysql_num_fields(mysql_result) == 1);
274
275 if ((row = mysql_fetch_row(mysql_result))) {
276 result = strdup(row[0]);
277 }
278
279 mysql_free_result(mysql_result);
280
281 return(result);
282}
283
284static
285bool
286check_server_version(unsigned long version_number,
287 const char *version_string,
288 const char *version_comment,
289 const char *innodb_version)
290{
291 bool version_supported = false;
292 bool mysql51 = false;
293
294 mysql_server_version = version_number;
295
296 server_flavor = FLAVOR_UNKNOWN;
297 if (strstr(version_comment, "Percona") != NULL) {
298 server_flavor = FLAVOR_PERCONA_SERVER;
299 } else if (strstr(version_comment, "MariaDB") != NULL ||
300 strstr(version_string, "MariaDB") != NULL) {
301 server_flavor = FLAVOR_MARIADB;
302 } else if (strstr(version_comment, "MySQL") != NULL) {
303 server_flavor = FLAVOR_MYSQL;
304 }
305
306 mysql51 = version_number > 50100 && version_number < 50500;
307 version_supported = version_supported
308 || (mysql51 && innodb_version != NULL);
309 version_supported = version_supported
310 || (version_number > 50500 && version_number < 50700);
311 version_supported = version_supported
312 || ((version_number > 100000)
313 && server_flavor == FLAVOR_MARIADB);
314
315 if (mysql51 && innodb_version == NULL) {
316 msg("Error: Built-in InnoDB in MySQL 5.1 is not "
317 "supported in this release. You can either use "
318 "Percona XtraBackup 2.0, or upgrade to InnoDB "
319 "plugin.\n");
320 } else if (!version_supported) {
321 msg("Error: Unsupported server version: '%s'. Please "
322 "report a bug at "
323 "https://bugs.launchpad.net/percona-xtrabackup\n",
324 version_string);
325 }
326
327 return(version_supported);
328}
329
330/*********************************************************************//**
331Receive options important for XtraBackup from MySQL server.
332@return true on success. */
333bool
334get_mysql_vars(MYSQL *connection)
335{
336 char *gtid_mode_var = NULL;
337 char *version_var = NULL;
338 char *version_comment_var = NULL;
339 char *innodb_version_var = NULL;
340 char *have_backup_locks_var = NULL;
341 char *have_backup_safe_binlog_info_var = NULL;
342 char *log_bin_var = NULL;
343 char *lock_wait_timeout_var= NULL;
344 char *wsrep_on_var = NULL;
345 char *slave_parallel_workers_var = NULL;
346 char *gtid_slave_pos_var = NULL;
347 char *innodb_buffer_pool_filename_var = NULL;
348 char *datadir_var = NULL;
349 char *innodb_log_group_home_dir_var = NULL;
350 char *innodb_log_file_size_var = NULL;
351 char *innodb_log_files_in_group_var = NULL;
352 char *innodb_data_file_path_var = NULL;
353 char *innodb_data_home_dir_var = NULL;
354 char *innodb_undo_directory_var = NULL;
355 char *innodb_page_size_var = NULL;
356 char *innodb_undo_tablespaces_var = NULL;
357 char *endptr;
358 unsigned long server_version = mysql_get_server_version(connection);
359
360 bool ret = true;
361
362 mysql_variable mysql_vars[] = {
363 {"have_backup_locks", &have_backup_locks_var},
364 {"have_backup_safe_binlog_info",
365 &have_backup_safe_binlog_info_var},
366 {"log_bin", &log_bin_var},
367 {"lock_wait_timeout", &lock_wait_timeout_var},
368 {"gtid_mode", &gtid_mode_var},
369 {"version", &version_var},
370 {"version_comment", &version_comment_var},
371 {"innodb_version", &innodb_version_var},
372 {"wsrep_on", &wsrep_on_var},
373 {"slave_parallel_workers", &slave_parallel_workers_var},
374 {"gtid_slave_pos", &gtid_slave_pos_var},
375 {"innodb_buffer_pool_filename",
376 &innodb_buffer_pool_filename_var},
377 {"datadir", &datadir_var},
378 {"innodb_log_group_home_dir", &innodb_log_group_home_dir_var},
379 {"innodb_log_file_size", &innodb_log_file_size_var},
380 {"innodb_log_files_in_group", &innodb_log_files_in_group_var},
381 {"innodb_data_file_path", &innodb_data_file_path_var},
382 {"innodb_data_home_dir", &innodb_data_home_dir_var},
383 {"innodb_undo_directory", &innodb_undo_directory_var},
384 {"innodb_page_size", &innodb_page_size_var},
385 {"innodb_undo_tablespaces", &innodb_undo_tablespaces_var},
386 {NULL, NULL}
387 };
388
389 read_mysql_variables(connection, "SHOW VARIABLES",
390 mysql_vars, true);
391
392 if (have_backup_locks_var != NULL && !opt_no_backup_locks) {
393 have_backup_locks = true;
394 }
395
396 if (opt_binlog_info == BINLOG_INFO_AUTO) {
397
398 if (have_backup_safe_binlog_info_var != NULL)
399 opt_binlog_info = BINLOG_INFO_LOCKLESS;
400 else if (log_bin_var != NULL && !strcmp(log_bin_var, "ON"))
401 opt_binlog_info = BINLOG_INFO_ON;
402 else
403 opt_binlog_info = BINLOG_INFO_OFF;
404 }
405
406 if (have_backup_safe_binlog_info_var == NULL &&
407 opt_binlog_info == BINLOG_INFO_LOCKLESS) {
408
409 msg("Error: --binlog-info=LOCKLESS is not supported by the "
410 "server\n");
411 return(false);
412 }
413
414 if (lock_wait_timeout_var != NULL) {
415 have_lock_wait_timeout = true;
416 }
417
418 if (wsrep_on_var != NULL) {
419 have_galera_enabled = true;
420 }
421
422 /* Check server version compatibility and detect server flavor */
423
424 if (!(ret = check_server_version(server_version, version_var,
425 version_comment_var,
426 innodb_version_var))) {
427 goto out;
428 }
429
430 if (server_version > 50500) {
431 have_flush_engine_logs = true;
432 }
433
434 if (slave_parallel_workers_var != NULL
435 && atoi(slave_parallel_workers_var) > 0) {
436 have_multi_threaded_slave = true;
437 }
438
439 if (innodb_buffer_pool_filename_var != NULL) {
440 buffer_pool_filename = strdup(innodb_buffer_pool_filename_var);
441 }
442
443 if ((gtid_mode_var && strcmp(gtid_mode_var, "ON") == 0) ||
444 (gtid_slave_pos_var && *gtid_slave_pos_var)) {
445 have_gtid_slave = true;
446 }
447
448 msg("Using server version %s\n", version_var);
449
450 if (!(ret = detect_mysql_capabilities_for_backup())) {
451 goto out;
452 }
453
454 /* make sure datadir value is the same in configuration file */
455 if (check_if_param_set("datadir")) {
456 if (!directory_exists(mysql_data_home, false)) {
457 msg("Warning: option 'datadir' points to "
458 "nonexistent directory '%s'\n", mysql_data_home);
459 }
460 if (!directory_exists(datadir_var, false)) {
461 msg("Warning: MySQL variable 'datadir' points to "
462 "nonexistent directory '%s'\n", datadir_var);
463 }
464 if (!equal_paths(mysql_data_home, datadir_var)) {
465 msg("Warning: option 'datadir' has different "
466 "values:\n"
467 " '%s' in defaults file\n"
468 " '%s' in SHOW VARIABLES\n",
469 mysql_data_home, datadir_var);
470 }
471 }
472
473 /* get some default values is they are missing from my.cnf */
474 if (datadir_var && *datadir_var) {
475 strmake(mysql_real_data_home, datadir_var, FN_REFLEN - 1);
476 mysql_data_home= mysql_real_data_home;
477 }
478
479 if (innodb_data_file_path_var && *innodb_data_file_path_var) {
480 innobase_data_file_path = my_strdup(
481 innodb_data_file_path_var, MYF(MY_FAE));
482 }
483
484 if (innodb_data_home_dir_var) {
485 innobase_data_home_dir = my_strdup(
486 innodb_data_home_dir_var, MYF(MY_FAE));
487 }
488
489 if (innodb_log_group_home_dir_var
490 && *innodb_log_group_home_dir_var) {
491 srv_log_group_home_dir = my_strdup(
492 innodb_log_group_home_dir_var, MYF(MY_FAE));
493 }
494
495 if (innodb_undo_directory_var && *innodb_undo_directory_var) {
496 srv_undo_dir = my_strdup(
497 innodb_undo_directory_var, MYF(MY_FAE));
498 }
499
500 if (innodb_log_files_in_group_var) {
501 srv_n_log_files = strtol(
502 innodb_log_files_in_group_var, &endptr, 10);
503 ut_ad(*endptr == 0);
504 }
505
506 if (innodb_log_file_size_var) {
507 srv_log_file_size = strtoll(
508 innodb_log_file_size_var, &endptr, 10);
509 ut_ad(*endptr == 0);
510 }
511
512 if (innodb_page_size_var) {
513 innobase_page_size = strtoll(
514 innodb_page_size_var, &endptr, 10);
515 ut_ad(*endptr == 0);
516 }
517
518 if (innodb_undo_tablespaces_var) {
519 srv_undo_tablespaces = strtoul(innodb_undo_tablespaces_var, &endptr, 10);
520 ut_ad(*endptr == 0);
521 }
522
523out:
524 free_mysql_variables(mysql_vars);
525
526 return(ret);
527}
528
529/*********************************************************************//**
530Query the server to find out what backup capabilities it supports.
531@return true on success. */
532bool
533detect_mysql_capabilities_for_backup()
534{
535 const char *query = "SELECT 'INNODB_CHANGED_PAGES', COUNT(*) FROM "
536 "INFORMATION_SCHEMA.PLUGINS "
537 "WHERE PLUGIN_NAME LIKE 'INNODB_CHANGED_PAGES'";
538 char *innodb_changed_pages = NULL;
539 mysql_variable vars[] = {
540 {"INNODB_CHANGED_PAGES", &innodb_changed_pages}, {NULL, NULL}};
541
542 if (xtrabackup_incremental) {
543
544 read_mysql_variables(mysql_connection, query, vars, true);
545
546 ut_ad(innodb_changed_pages != NULL);
547
548 have_changed_page_bitmaps = (atoi(innodb_changed_pages) == 1);
549
550 /* INNODB_CHANGED_PAGES are listed in
551 INFORMATION_SCHEMA.PLUGINS in MariaDB, but
552 FLUSH NO_WRITE_TO_BINLOG CHANGED_PAGE_BITMAPS
553 is not supported for versions below 10.1.6
554 (see MDEV-7472) */
555 if (server_flavor == FLAVOR_MARIADB &&
556 mysql_server_version < 100106) {
557 have_changed_page_bitmaps = false;
558 }
559
560 free_mysql_variables(vars);
561 }
562
563 /* do some sanity checks */
564 if (opt_galera_info && !have_galera_enabled) {
565 msg("--galera-info is specified on the command "
566 "line, but the server does not support Galera "
567 "replication. Ignoring the option.\n");
568 opt_galera_info = false;
569 }
570
571 if (opt_slave_info && have_multi_threaded_slave &&
572 !have_gtid_slave) {
573 msg("The --slave-info option requires GTID enabled for a "
574 "multi-threaded slave.\n");
575 return(false);
576 }
577
578 return(true);
579}
580
581static
582bool
583select_incremental_lsn_from_history(lsn_t *incremental_lsn)
584{
585 MYSQL_RES *mysql_result;
586 char query[1000];
587 char buf[100];
588
589 if (opt_incremental_history_name) {
590 mysql_real_escape_string(mysql_connection, buf,
591 opt_incremental_history_name,
592 (unsigned long)strlen(opt_incremental_history_name));
593 snprintf(query, sizeof(query),
594 "SELECT innodb_to_lsn "
595 "FROM PERCONA_SCHEMA.xtrabackup_history "
596 "WHERE name = '%s' "
597 "AND innodb_to_lsn IS NOT NULL "
598 "ORDER BY innodb_to_lsn DESC LIMIT 1",
599 buf);
600 }
601
602 if (opt_incremental_history_uuid) {
603 mysql_real_escape_string(mysql_connection, buf,
604 opt_incremental_history_uuid,
605 (unsigned long)strlen(opt_incremental_history_uuid));
606 snprintf(query, sizeof(query),
607 "SELECT innodb_to_lsn "
608 "FROM PERCONA_SCHEMA.xtrabackup_history "
609 "WHERE uuid = '%s' "
610 "AND innodb_to_lsn IS NOT NULL "
611 "ORDER BY innodb_to_lsn DESC LIMIT 1",
612 buf);
613 }
614
615 mysql_result = xb_mysql_query(mysql_connection, query, true);
616
617 ut_ad(mysql_num_fields(mysql_result) == 1);
618 const MYSQL_ROW row = mysql_fetch_row(mysql_result);
619 if (row) {
620 *incremental_lsn = strtoull(row[0], NULL, 10);
621 msg("Found and using lsn: " LSN_PF " for %s %s\n",
622 *incremental_lsn,
623 opt_incremental_history_uuid ? "uuid" : "name",
624 opt_incremental_history_uuid ?
625 opt_incremental_history_uuid :
626 opt_incremental_history_name);
627 } else {
628 msg("Error while attempting to find history record "
629 "for %s %s\n",
630 opt_incremental_history_uuid ? "uuid" : "name",
631 opt_incremental_history_uuid ?
632 opt_incremental_history_uuid :
633 opt_incremental_history_name);
634 }
635
636 mysql_free_result(mysql_result);
637
638 return(row != NULL);
639}
640
641static
642const char *
643eat_sql_whitespace(const char *query)
644{
645 bool comment = false;
646
647 while (*query) {
648 if (comment) {
649 if (query[0] == '*' && query[1] == '/') {
650 query += 2;
651 comment = false;
652 continue;
653 }
654 ++query;
655 continue;
656 }
657 if (query[0] == '/' && query[1] == '*') {
658 query += 2;
659 comment = true;
660 continue;
661 }
662 if (strchr("\t\n\r (", query[0])) {
663 ++query;
664 continue;
665 }
666 break;
667 }
668
669 return(query);
670}
671
672static
673bool
674is_query_from_list(const char *query, const char **list)
675{
676 const char **item;
677
678 query = eat_sql_whitespace(query);
679
680 item = list;
681 while (*item) {
682 if (strncasecmp(query, *item, strlen(*item)) == 0) {
683 return(true);
684 }
685 ++item;
686 }
687
688 return(false);
689}
690
691static
692bool
693is_query(const char *query)
694{
695 const char *query_list[] = {"insert", "update", "delete", "replace",
696 "alter", "load", "select", "do", "handler", "call", "execute",
697 "begin", NULL};
698
699 return is_query_from_list(query, query_list);
700}
701
702static
703bool
704is_select_query(const char *query)
705{
706 const char *query_list[] = {"select", NULL};
707
708 return is_query_from_list(query, query_list);
709}
710
711static
712bool
713is_update_query(const char *query)
714{
715 const char *query_list[] = {"insert", "update", "delete", "replace",
716 "alter", "load", NULL};
717
718 return is_query_from_list(query, query_list);
719}
720
721static
722bool
723have_queries_to_wait_for(MYSQL *connection, uint threshold)
724{
725 MYSQL_RES *result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST",
726 true);
727 const bool all_queries = (opt_lock_wait_query_type == QUERY_TYPE_ALL);
728 bool have_to_wait = false;
729
730 while (MYSQL_ROW row = mysql_fetch_row(result)) {
731 const char *info = row[7];
732 int duration = row[5] ? atoi(row[5]) : 0;
733 char *id = row[0];
734
735 if (info != NULL
736 && duration >= (int)threshold
737 && ((all_queries && is_query(info))
738 || is_update_query(info))) {
739 msg_ts("Waiting for query %s (duration %d sec): %s",
740 id, duration, info);
741 have_to_wait = true;
742 break;
743 }
744 }
745
746 mysql_free_result(result);
747 return(have_to_wait);
748}
749
750static
751void
752kill_long_queries(MYSQL *connection, time_t timeout)
753{
754 char kill_stmt[100];
755
756 MYSQL_RES *result = xb_mysql_query(connection, "SHOW FULL PROCESSLIST",
757 true);
758 const bool all_queries = (opt_kill_long_query_type == QUERY_TYPE_ALL);
759 while (MYSQL_ROW row = mysql_fetch_row(result)) {
760 const char *info = row[7];
761 long long duration = row[5]? atoll(row[5]) : 0;
762 char *id = row[0];
763
764 if (info != NULL &&
765 (time_t)duration >= timeout &&
766 ((all_queries && is_query(info)) ||
767 is_select_query(info))) {
768 msg_ts("Killing query %s (duration %d sec): %s\n",
769 id, (int)duration, info);
770 snprintf(kill_stmt, sizeof(kill_stmt),
771 "KILL %s", id);
772 xb_mysql_query(connection, kill_stmt, false, false);
773 }
774 }
775
776 mysql_free_result(result);
777}
778
779static
780bool
781wait_for_no_updates(MYSQL *connection, uint timeout, uint threshold)
782{
783 time_t start_time;
784
785 start_time = time(NULL);
786
787 msg_ts("Waiting %u seconds for queries running longer than %u seconds "
788 "to finish\n", timeout, threshold);
789
790 while (time(NULL) <= (time_t)(start_time + timeout)) {
791 if (!have_queries_to_wait_for(connection, threshold)) {
792 return(true);
793 }
794 os_thread_sleep(1000000);
795 }
796
797 msg_ts("Unable to obtain lock. Please try again later.");
798
799 return(false);
800}
801
802static
803os_thread_ret_t
804DECLARE_THREAD(kill_query_thread)(
805/*===============*/
806 void *arg __attribute__((unused)))
807{
808 MYSQL *mysql;
809 time_t start_time;
810
811 start_time = time(NULL);
812
813 os_event_set(kill_query_thread_started);
814
815 msg_ts("Kill query timeout %d seconds.\n",
816 opt_kill_long_queries_timeout);
817
818 while (time(NULL) - start_time <
819 (time_t)opt_kill_long_queries_timeout) {
820 if (os_event_wait_time(kill_query_thread_stop, 1000) !=
821 OS_SYNC_TIME_EXCEEDED) {
822 goto stop_thread;
823 }
824 }
825
826 if ((mysql = xb_mysql_connect()) == NULL) {
827 msg("Error: kill query thread failed\n");
828 goto stop_thread;
829 }
830
831 while (true) {
832 kill_long_queries(mysql, time(NULL) - start_time);
833 if (os_event_wait_time(kill_query_thread_stop, 1000) !=
834 OS_SYNC_TIME_EXCEEDED) {
835 break;
836 }
837 }
838
839 mysql_close(mysql);
840
841stop_thread:
842 msg_ts("Kill query thread stopped\n");
843
844 os_event_set(kill_query_thread_stopped);
845
846 os_thread_exit();
847 OS_THREAD_DUMMY_RETURN;
848}
849
850
851static
852void
853start_query_killer()
854{
855 kill_query_thread_stop = os_event_create(0);
856 kill_query_thread_started = os_event_create(0);
857 kill_query_thread_stopped = os_event_create(0);
858
859 os_thread_create(kill_query_thread, NULL, &kill_query_thread_id);
860
861 os_event_wait(kill_query_thread_started);
862}
863
864static
865void
866stop_query_killer()
867{
868 os_event_set(kill_query_thread_stop);
869 os_event_wait_time(kill_query_thread_stopped, 60000);
870}
871
872
873/*
874Killing connections that wait for MDL lock.
875If lock-ddl-per-table is used, there can be some DDL statements
876
877FLUSH TABLES would hang infinitely, if DDL statements are waiting for
878MDL lock, which mariabackup currently holds. Therefore we start killing
879those statements from a dedicated thread, until FLUSH TABLES WITH READ LOCK
880succeeds.
881*/
882
883static os_event_t mdl_killer_stop_event;
884static os_event_t mdl_killer_finished_event;
885
886static
887os_thread_ret_t
888DECLARE_THREAD(kill_mdl_waiters_thread(void *))
889{
890 MYSQL *mysql;
891 if ((mysql = xb_mysql_connect()) == NULL) {
892 msg("Error: kill mdl waiters thread failed to connect\n");
893 goto stop_thread;
894 }
895
896 for(;;){
897 if (os_event_wait_time(mdl_killer_stop_event, 1000) == 0)
898 break;
899
900 MYSQL_RES *result = xb_mysql_query(mysql,
901 "SELECT ID, COMMAND, INFO FROM INFORMATION_SCHEMA.PROCESSLIST "
902 " WHERE State='Waiting for table metadata lock'",
903 true, true);
904 while (MYSQL_ROW row = mysql_fetch_row(result))
905 {
906 char query[64];
907
908 if (row[1] && !strcmp(row[1], "Killed"))
909 continue;
910
911 msg_ts("Killing MDL waiting %s ('%s') on connection %s\n",
912 row[1], row[2], row[0]);
913 snprintf(query, sizeof(query), "KILL QUERY %s", row[0]);
914 if (mysql_query(mysql, query) && (mysql_errno(mysql) != ER_NO_SUCH_THREAD)) {
915 msg("Error: failed to execute query %s: %s\n", query,mysql_error(mysql));
916 exit(EXIT_FAILURE);
917 }
918 }
919 }
920
921 mysql_close(mysql);
922
923stop_thread:
924 msg_ts("Kill mdl waiters thread stopped\n");
925 os_event_set(mdl_killer_finished_event);
926 os_thread_exit();
927 return os_thread_ret_t(0);
928}
929
930
931static void start_mdl_waiters_killer()
932{
933 mdl_killer_stop_event = os_event_create(0);
934 mdl_killer_finished_event = os_event_create(0);
935 os_thread_create(kill_mdl_waiters_thread, 0, 0);
936}
937
938
939/* Tell MDL killer to stop and finish for its completion*/
940static void stop_mdl_waiters_killer()
941{
942 os_event_set(mdl_killer_stop_event);
943 os_event_wait(mdl_killer_finished_event);
944
945 os_event_destroy(mdl_killer_stop_event);
946 os_event_destroy(mdl_killer_finished_event);
947}
948
949/*********************************************************************//**
950Function acquires either a backup tables lock, if supported
951by the server, or a global read lock (FLUSH TABLES WITH READ LOCK)
952otherwise.
953@returns true if lock acquired */
954bool
955lock_tables(MYSQL *connection)
956{
957 if (have_lock_wait_timeout) {
958 /* Set the maximum supported session value for
959 lock_wait_timeout to prevent unnecessary timeouts when the
960 global value is changed from the default */
961 xb_mysql_query(connection,
962 "SET SESSION lock_wait_timeout=31536000", false);
963 }
964
965 if (have_backup_locks) {
966 msg_ts("Executing LOCK TABLES FOR BACKUP...\n");
967 xb_mysql_query(connection, "LOCK TABLES FOR BACKUP", false);
968 return(true);
969 }
970
971 if (opt_lock_ddl_per_table) {
972 start_mdl_waiters_killer();
973 }
974
975 if (!opt_lock_wait_timeout && !opt_kill_long_queries_timeout) {
976
977 /* We do first a FLUSH TABLES. If a long update is running, the
978 FLUSH TABLES will wait but will not stall the whole mysqld, and
979 when the long update is done the FLUSH TABLES WITH READ LOCK
980 will start and succeed quickly. So, FLUSH TABLES is to lower
981 the probability of a stage where both mysqldump and most client
982 connections are stalled. Of course, if a second long update
983 starts between the two FLUSHes, we have that bad stall.
984
985 Option lock_wait_timeout serve the same purpose and is not
986 compatible with this trick.
987 */
988
989 msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG TABLES...\n");
990
991 xb_mysql_query(connection,
992 "FLUSH NO_WRITE_TO_BINLOG TABLES", false);
993 }
994
995 if (opt_lock_wait_timeout) {
996 if (!wait_for_no_updates(connection, opt_lock_wait_timeout,
997 opt_lock_wait_threshold)) {
998 return(false);
999 }
1000 }
1001
1002 msg_ts("Executing FLUSH TABLES WITH READ LOCK...\n");
1003
1004 if (opt_kill_long_queries_timeout) {
1005 start_query_killer();
1006 }
1007
1008 if (have_galera_enabled) {
1009 xb_mysql_query(connection,
1010 "SET SESSION wsrep_causal_reads=0", false);
1011 }
1012
1013 xb_mysql_query(connection, "FLUSH TABLES WITH READ LOCK", false);
1014
1015 if (opt_lock_ddl_per_table) {
1016 stop_mdl_waiters_killer();
1017 }
1018
1019 if (opt_kill_long_queries_timeout) {
1020 stop_query_killer();
1021 }
1022
1023 return(true);
1024}
1025
1026
1027/*********************************************************************//**
1028If backup locks are used, execute LOCK BINLOG FOR BACKUP provided that we are
1029not in the --no-lock mode and the lock has not been acquired already.
1030@returns true if lock acquired */
1031bool
1032lock_binlog_maybe(MYSQL *connection)
1033{
1034 if (have_backup_locks && !opt_no_lock && !binlog_locked) {
1035 msg_ts("Executing LOCK BINLOG FOR BACKUP...\n");
1036 xb_mysql_query(connection, "LOCK BINLOG FOR BACKUP", false);
1037 binlog_locked = true;
1038
1039 return(true);
1040 }
1041
1042 return(false);
1043}
1044
1045
1046/*********************************************************************//**
1047Releases either global read lock acquired with FTWRL and the binlog
1048lock acquired with LOCK BINLOG FOR BACKUP, depending on
1049the locking strategy being used */
1050void
1051unlock_all(MYSQL *connection)
1052{
1053 if (opt_debug_sleep_before_unlock) {
1054 msg_ts("Debug sleep for %u seconds\n",
1055 opt_debug_sleep_before_unlock);
1056 os_thread_sleep(opt_debug_sleep_before_unlock * 1000);
1057 }
1058
1059 if (binlog_locked) {
1060 msg_ts("Executing UNLOCK BINLOG\n");
1061 xb_mysql_query(connection, "UNLOCK BINLOG", false);
1062 }
1063
1064 msg_ts("Executing UNLOCK TABLES\n");
1065 xb_mysql_query(connection, "UNLOCK TABLES", false);
1066
1067 msg_ts("All tables unlocked\n");
1068}
1069
1070
1071static
1072int
1073get_open_temp_tables(MYSQL *connection)
1074{
1075 char *slave_open_temp_tables = NULL;
1076 mysql_variable status[] = {
1077 {"Slave_open_temp_tables", &slave_open_temp_tables},
1078 {NULL, NULL}
1079 };
1080 int result = false;
1081
1082 read_mysql_variables(connection,
1083 "SHOW STATUS LIKE 'slave_open_temp_tables'", status, true);
1084
1085 result = slave_open_temp_tables ? atoi(slave_open_temp_tables) : 0;
1086
1087 free_mysql_variables(status);
1088
1089 return(result);
1090}
1091
1092/*********************************************************************//**
1093Wait until it's safe to backup a slave. Returns immediately if
1094the host isn't a slave. Currently there's only one check:
1095Slave_open_temp_tables has to be zero. Dies on timeout. */
1096bool
1097wait_for_safe_slave(MYSQL *connection)
1098{
1099 char *read_master_log_pos = NULL;
1100 char *slave_sql_running = NULL;
1101 int n_attempts = 1;
1102 const int sleep_time = 3;
1103 int open_temp_tables = 0;
1104 bool result = true;
1105
1106 mysql_variable status[] = {
1107 {"Read_Master_Log_Pos", &read_master_log_pos},
1108 {"Slave_SQL_Running", &slave_sql_running},
1109 {NULL, NULL}
1110 };
1111
1112 sql_thread_started = false;
1113
1114 read_mysql_variables(connection, "SHOW SLAVE STATUS", status, false);
1115
1116 if (!(read_master_log_pos && slave_sql_running)) {
1117 msg("Not checking slave open temp tables for "
1118 "--safe-slave-backup because host is not a slave\n");
1119 goto cleanup;
1120 }
1121
1122 if (strcmp(slave_sql_running, "Yes") == 0) {
1123 sql_thread_started = true;
1124 xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false);
1125 }
1126
1127 if (opt_safe_slave_backup_timeout > 0) {
1128 n_attempts = opt_safe_slave_backup_timeout / sleep_time;
1129 }
1130
1131 open_temp_tables = get_open_temp_tables(connection);
1132 msg_ts("Slave open temp tables: %d\n", open_temp_tables);
1133
1134 while (open_temp_tables && n_attempts--) {
1135 msg_ts("Starting slave SQL thread, waiting %d seconds, then "
1136 "checking Slave_open_temp_tables again (%d attempts "
1137 "remaining)...\n", sleep_time, n_attempts);
1138
1139 xb_mysql_query(connection, "START SLAVE SQL_THREAD", false);
1140 os_thread_sleep(sleep_time * 1000000);
1141 xb_mysql_query(connection, "STOP SLAVE SQL_THREAD", false);
1142
1143 open_temp_tables = get_open_temp_tables(connection);
1144 msg_ts("Slave open temp tables: %d\n", open_temp_tables);
1145 }
1146
1147 /* Restart the slave if it was running at start */
1148 if (open_temp_tables == 0) {
1149 msg_ts("Slave is safe to backup\n");
1150 goto cleanup;
1151 }
1152
1153 result = false;
1154
1155 if (sql_thread_started) {
1156 msg_ts("Restarting slave SQL thread.\n");
1157 xb_mysql_query(connection, "START SLAVE SQL_THREAD", false);
1158 }
1159
1160 msg_ts("Slave_open_temp_tables did not become zero after "
1161 "%d seconds\n", opt_safe_slave_backup_timeout);
1162
1163cleanup:
1164 free_mysql_variables(status);
1165
1166 return(result);
1167}
1168
1169
1170/*********************************************************************//**
1171Retrieves MySQL binlog position of the master server in a replication
1172setup and saves it in a file. It also saves it in mysql_slave_position
1173variable. */
1174bool
1175write_slave_info(MYSQL *connection)
1176{
1177 char *master = NULL;
1178 char *filename = NULL;
1179 char *gtid_executed = NULL;
1180 char *position = NULL;
1181 char *gtid_slave_pos = NULL;
1182 char *ptr;
1183 bool result = false;
1184
1185 mysql_variable status[] = {
1186 {"Master_Host", &master},
1187 {"Relay_Master_Log_File", &filename},
1188 {"Exec_Master_Log_Pos", &position},
1189 {"Executed_Gtid_Set", &gtid_executed},
1190 {NULL, NULL}
1191 };
1192
1193 mysql_variable variables[] = {
1194 {"gtid_slave_pos", &gtid_slave_pos},
1195 {NULL, NULL}
1196 };
1197
1198 read_mysql_variables(connection, "SHOW SLAVE STATUS", status, false);
1199 read_mysql_variables(connection, "SHOW VARIABLES", variables, true);
1200
1201 if (master == NULL || filename == NULL || position == NULL) {
1202 msg("Failed to get master binlog coordinates "
1203 "from SHOW SLAVE STATUS\n");
1204 msg("This means that the server is not a "
1205 "replication slave. Ignoring the --slave-info "
1206 "option\n");
1207 /* we still want to continue the backup */
1208 result = true;
1209 goto cleanup;
1210 }
1211
1212 /* Print slave status to a file.
1213 If GTID mode is used, construct a CHANGE MASTER statement with
1214 MASTER_AUTO_POSITION and correct a gtid_purged value. */
1215 if (gtid_executed != NULL && *gtid_executed) {
1216 /* MySQL >= 5.6 with GTID enabled */
1217
1218 for (ptr = strchr(gtid_executed, '\n');
1219 ptr;
1220 ptr = strchr(ptr, '\n')) {
1221 *ptr = ' ';
1222 }
1223
1224 result = backup_file_printf(XTRABACKUP_SLAVE_INFO,
1225 "SET GLOBAL gtid_purged='%s';\n"
1226 "CHANGE MASTER TO MASTER_AUTO_POSITION=1\n",
1227 gtid_executed);
1228
1229 ut_a(asprintf(&mysql_slave_position,
1230 "master host '%s', purge list '%s'",
1231 master, gtid_executed) != -1);
1232 } else if (gtid_slave_pos && *gtid_slave_pos) {
1233 /* MariaDB >= 10.0 with GTID enabled */
1234 result = backup_file_printf(XTRABACKUP_SLAVE_INFO,
1235 "SET GLOBAL gtid_slave_pos = '%s';\n"
1236 "CHANGE MASTER TO master_use_gtid = slave_pos\n",
1237 gtid_slave_pos);
1238 ut_a(asprintf(&mysql_slave_position,
1239 "master host '%s', gtid_slave_pos %s",
1240 master, gtid_slave_pos) != -1);
1241 } else {
1242 result = backup_file_printf(XTRABACKUP_SLAVE_INFO,
1243 "CHANGE MASTER TO MASTER_LOG_FILE='%s', "
1244 "MASTER_LOG_POS=%s\n", filename, position);
1245 ut_a(asprintf(&mysql_slave_position,
1246 "master host '%s', filename '%s', position '%s'",
1247 master, filename, position) != -1);
1248 }
1249
1250cleanup:
1251 free_mysql_variables(status);
1252 free_mysql_variables(variables);
1253
1254 return(result);
1255}
1256
1257
1258/*********************************************************************//**
1259Retrieves MySQL Galera and
1260saves it in a file. It also prints it to stdout. */
1261bool
1262write_galera_info(MYSQL *connection)
1263{
1264 char *state_uuid = NULL, *state_uuid55 = NULL;
1265 char *last_committed = NULL, *last_committed55 = NULL;
1266 bool result;
1267
1268 mysql_variable status[] = {
1269 {"Wsrep_local_state_uuid", &state_uuid},
1270 {"wsrep_local_state_uuid", &state_uuid55},
1271 {"Wsrep_last_committed", &last_committed},
1272 {"wsrep_last_committed", &last_committed55},
1273 {NULL, NULL}
1274 };
1275
1276 /* When backup locks are supported by the server, we should skip
1277 creating xtrabackup_galera_info file on the backup stage, because
1278 wsrep_local_state_uuid and wsrep_last_committed will be inconsistent
1279 without blocking commits. The state file will be created on the prepare
1280 stage using the WSREP recovery procedure. */
1281 if (have_backup_locks) {
1282 return(true);
1283 }
1284
1285 read_mysql_variables(connection, "SHOW STATUS", status, true);
1286
1287 if ((state_uuid == NULL && state_uuid55 == NULL)
1288 || (last_committed == NULL && last_committed55 == NULL)) {
1289 msg("Failed to get master wsrep state from SHOW STATUS.\n");
1290 result = false;
1291 goto cleanup;
1292 }
1293
1294 result = backup_file_printf(XTRABACKUP_GALERA_INFO,
1295 "%s:%s\n", state_uuid ? state_uuid : state_uuid55,
1296 last_committed ? last_committed : last_committed55);
1297
1298cleanup:
1299 free_mysql_variables(status);
1300
1301 return(result);
1302}
1303
1304
1305/*********************************************************************//**
1306Flush and copy the current binary log file into the backup,
1307if GTID is enabled */
1308bool
1309write_current_binlog_file(MYSQL *connection)
1310{
1311 char *executed_gtid_set = NULL;
1312 char *gtid_binlog_state = NULL;
1313 char *log_bin_file = NULL;
1314 char *log_bin_dir = NULL;
1315 bool gtid_exists;
1316 bool result = true;
1317 char filepath[FN_REFLEN];
1318
1319 mysql_variable status[] = {
1320 {"Executed_Gtid_Set", &executed_gtid_set},
1321 {NULL, NULL}
1322 };
1323
1324 mysql_variable status_after_flush[] = {
1325 {"File", &log_bin_file},
1326 {NULL, NULL}
1327 };
1328
1329 mysql_variable vars[] = {
1330 {"gtid_binlog_state", &gtid_binlog_state},
1331 {"log_bin_basename", &log_bin_dir},
1332 {NULL, NULL}
1333 };
1334
1335 read_mysql_variables(connection, "SHOW MASTER STATUS", status, false);
1336 read_mysql_variables(connection, "SHOW VARIABLES", vars, true);
1337
1338 gtid_exists = (executed_gtid_set && *executed_gtid_set)
1339 || (gtid_binlog_state && *gtid_binlog_state);
1340
1341 if (gtid_exists) {
1342 size_t log_bin_dir_length;
1343
1344 lock_binlog_maybe(connection);
1345
1346 xb_mysql_query(connection, "FLUSH BINARY LOGS", false);
1347
1348 read_mysql_variables(connection, "SHOW MASTER STATUS",
1349 status_after_flush, false);
1350
1351 if (opt_log_bin != NULL && strchr(opt_log_bin, FN_LIBCHAR)) {
1352 /* If log_bin is set, it has priority */
1353 if (log_bin_dir) {
1354 free(log_bin_dir);
1355 }
1356 log_bin_dir = strdup(opt_log_bin);
1357 } else if (log_bin_dir == NULL) {
1358 /* Default location is MySQL datadir */
1359 log_bin_dir = strdup("./");
1360 }
1361
1362 dirname_part(log_bin_dir, log_bin_dir, &log_bin_dir_length);
1363
1364 /* strip final slash if it is not the only path component */
1365 if (log_bin_dir_length > 1 &&
1366 log_bin_dir[log_bin_dir_length - 1] == FN_LIBCHAR) {
1367 log_bin_dir[log_bin_dir_length - 1] = 0;
1368 }
1369
1370 if (log_bin_dir == NULL || log_bin_file == NULL) {
1371 msg("Failed to get master binlog coordinates from "
1372 "SHOW MASTER STATUS");
1373 result = false;
1374 goto cleanup;
1375 }
1376
1377 snprintf(filepath, sizeof(filepath), "%s%c%s",
1378 log_bin_dir, FN_LIBCHAR, log_bin_file);
1379 result = copy_file(ds_data, filepath, log_bin_file, 0);
1380 }
1381
1382cleanup:
1383 free_mysql_variables(status_after_flush);
1384 free_mysql_variables(status);
1385 free_mysql_variables(vars);
1386
1387 return(result);
1388}
1389
1390
1391/*********************************************************************//**
1392Retrieves MySQL binlog position and
1393saves it in a file. It also prints it to stdout. */
1394bool
1395write_binlog_info(MYSQL *connection)
1396{
1397 char *filename = NULL;
1398 char *position = NULL;
1399 char *gtid_mode = NULL;
1400 char *gtid_current_pos = NULL;
1401 char *gtid_executed = NULL;
1402 char *gtid = NULL;
1403 bool result;
1404 bool mysql_gtid;
1405 bool mariadb_gtid;
1406
1407 mysql_variable status[] = {
1408 {"File", &filename},
1409 {"Position", &position},
1410 {"Executed_Gtid_Set", &gtid_executed},
1411 {NULL, NULL}
1412 };
1413
1414 mysql_variable vars[] = {
1415 {"gtid_mode", &gtid_mode},
1416 {"gtid_current_pos", &gtid_current_pos},
1417 {NULL, NULL}
1418 };
1419
1420 read_mysql_variables(connection, "SHOW MASTER STATUS", status, false);
1421 read_mysql_variables(connection, "SHOW VARIABLES", vars, true);
1422
1423 if (filename == NULL || position == NULL) {
1424 /* Do not create xtrabackup_binlog_info if binary
1425 log is disabled */
1426 result = true;
1427 goto cleanup;
1428 }
1429
1430 mysql_gtid = ((gtid_mode != NULL) && (strcmp(gtid_mode, "ON") == 0));
1431 mariadb_gtid = (gtid_current_pos != NULL);
1432
1433 gtid = (gtid_executed != NULL ? gtid_executed : gtid_current_pos);
1434
1435 if (mariadb_gtid || mysql_gtid) {
1436 ut_a(asprintf(&mysql_binlog_position,
1437 "filename '%s', position '%s', "
1438 "GTID of the last change '%s'",
1439 filename, position, gtid) != -1);
1440 result = backup_file_printf(XTRABACKUP_BINLOG_INFO,
1441 "%s\t%s\t%s\n", filename, position,
1442 gtid);
1443 } else {
1444 ut_a(asprintf(&mysql_binlog_position,
1445 "filename '%s', position '%s'",
1446 filename, position) != -1);
1447 result = backup_file_printf(XTRABACKUP_BINLOG_INFO,
1448 "%s\t%s\n", filename, position);
1449 }
1450
1451cleanup:
1452 free_mysql_variables(status);
1453 free_mysql_variables(vars);
1454
1455 return(result);
1456}
1457
1458struct escape_and_quote
1459{
1460 escape_and_quote(MYSQL *mysql, const char *str)
1461 : mysql(mysql), str(str) {}
1462 MYSQL * const mysql;
1463 const char * const str;
1464};
1465
1466static
1467std::ostream&
1468operator<<(std::ostream& s, const escape_and_quote& eq)
1469{
1470 if (!eq.str)
1471 return s << "NULL";
1472 s << '\'';
1473 size_t len = strlen(eq.str);
1474 char* escaped = (char *)alloca(2 * len + 1);
1475 len = mysql_real_escape_string(eq.mysql, escaped, eq.str, (ulong)len);
1476 s << std::string(escaped, len);
1477 s << '\'';
1478 return s;
1479}
1480
1481/*********************************************************************//**
1482Writes xtrabackup_info file and if backup_history is enable creates
1483PERCONA_SCHEMA.xtrabackup_history and writes a new history record to the
1484table containing all the history info particular to the just completed
1485backup. */
1486bool
1487write_xtrabackup_info(MYSQL *connection, const char * filename, bool history)
1488{
1489
1490 char *uuid = NULL;
1491 char *server_version = NULL;
1492 char buf_start_time[100];
1493 char buf_end_time[100];
1494 tm tm;
1495 std::ostringstream oss;
1496 const char *xb_stream_name[] = {"file", "tar", "xbstream"};
1497
1498 uuid = read_mysql_one_value(connection, "SELECT UUID()");
1499 server_version = read_mysql_one_value(connection, "SELECT VERSION()");
1500 localtime_r(&history_start_time, &tm);
1501 strftime(buf_start_time, sizeof(buf_start_time),
1502 "%Y-%m-%d %H:%M:%S", &tm);
1503 history_end_time = time(NULL);
1504 localtime_r(&history_end_time, &tm);
1505 strftime(buf_end_time, sizeof(buf_end_time),
1506 "%Y-%m-%d %H:%M:%S", &tm);
1507 bool is_partial = (xtrabackup_tables
1508 || xtrabackup_tables_file
1509 || xtrabackup_databases
1510 || xtrabackup_databases_file
1511 || xtrabackup_tables_exclude
1512 || xtrabackup_databases_exclude
1513 );
1514
1515 backup_file_printf(filename,
1516 "uuid = %s\n"
1517 "name = %s\n"
1518 "tool_name = %s\n"
1519 "tool_command = %s\n"
1520 "tool_version = %s\n"
1521 "ibbackup_version = %s\n"
1522 "server_version = %s\n"
1523 "start_time = %s\n"
1524 "end_time = %s\n"
1525 "lock_time = %d\n"
1526 "binlog_pos = %s\n"
1527 "innodb_from_lsn = %llu\n"
1528 "innodb_to_lsn = %llu\n"
1529 "partial = %s\n"
1530 "incremental = %s\n"
1531 "format = %s\n"
1532 "compressed = %s\n",
1533 uuid, /* uuid */
1534 opt_history ? opt_history : "", /* name */
1535 tool_name, /* tool_name */
1536 tool_args, /* tool_command */
1537 MYSQL_SERVER_VERSION, /* tool_version */
1538 MYSQL_SERVER_VERSION, /* ibbackup_version */
1539 server_version, /* server_version */
1540 buf_start_time, /* start_time */
1541 buf_end_time, /* end_time */
1542 (int)history_lock_time, /* lock_time */
1543 mysql_binlog_position ?
1544 mysql_binlog_position : "", /* binlog_pos */
1545 incremental_lsn, /* innodb_from_lsn */
1546 metadata_to_lsn, /* innodb_to_lsn */
1547 is_partial? "Y" : "N",
1548 xtrabackup_incremental ? "Y" : "N", /* incremental */
1549 xb_stream_name[xtrabackup_stream_fmt], /* format */
1550 xtrabackup_compress ? "compressed" : "N"); /* compressed */
1551
1552 if (!history) {
1553 goto cleanup;
1554 }
1555
1556 xb_mysql_query(connection,
1557 "CREATE DATABASE IF NOT EXISTS PERCONA_SCHEMA", false);
1558 xb_mysql_query(connection,
1559 "CREATE TABLE IF NOT EXISTS PERCONA_SCHEMA.xtrabackup_history("
1560 "uuid VARCHAR(40) NOT NULL PRIMARY KEY,"
1561 "name VARCHAR(255) DEFAULT NULL,"
1562 "tool_name VARCHAR(255) DEFAULT NULL,"
1563 "tool_command TEXT DEFAULT NULL,"
1564 "tool_version VARCHAR(255) DEFAULT NULL,"
1565 "ibbackup_version VARCHAR(255) DEFAULT NULL,"
1566 "server_version VARCHAR(255) DEFAULT NULL,"
1567 "start_time TIMESTAMP NULL DEFAULT NULL,"
1568 "end_time TIMESTAMP NULL DEFAULT NULL,"
1569 "lock_time BIGINT UNSIGNED DEFAULT NULL,"
1570 "binlog_pos VARCHAR(128) DEFAULT NULL,"
1571 "innodb_from_lsn BIGINT UNSIGNED DEFAULT NULL,"
1572 "innodb_to_lsn BIGINT UNSIGNED DEFAULT NULL,"
1573 "partial ENUM('Y', 'N') DEFAULT NULL,"
1574 "incremental ENUM('Y', 'N') DEFAULT NULL,"
1575 "format ENUM('file', 'tar', 'xbstream') DEFAULT NULL,"
1576 "compressed ENUM('Y', 'N') DEFAULT NULL"
1577 ") CHARACTER SET utf8 ENGINE=innodb", false);
1578
1579
1580#define ESCAPE_BOOL(expr) ((expr)?"'Y'":"'N'")
1581
1582 oss << "insert into PERCONA_SCHEMA.xtrabackup_history("
1583 << "uuid, name, tool_name, tool_command, tool_version,"
1584 << "ibbackup_version, server_version, start_time, end_time,"
1585 << "lock_time, binlog_pos, innodb_from_lsn, innodb_to_lsn,"
1586 << "partial, incremental, format, compressed) "
1587 << "values("
1588 << escape_and_quote(connection, uuid) << ","
1589 << escape_and_quote(connection, opt_history) << ","
1590 << escape_and_quote(connection, tool_name) << ","
1591 << escape_and_quote(connection, tool_args) << ","
1592 << escape_and_quote(connection, MYSQL_SERVER_VERSION) << ","
1593 << escape_and_quote(connection, MYSQL_SERVER_VERSION) << ","
1594 << escape_and_quote(connection, server_version) << ","
1595 << "from_unixtime(" << history_start_time << "),"
1596 << "from_unixtime(" << history_end_time << "),"
1597 << history_lock_time << ","
1598 << escape_and_quote(connection, mysql_binlog_position) << ","
1599 << incremental_lsn << ","
1600 << metadata_to_lsn << ","
1601 << ESCAPE_BOOL(is_partial) << ","
1602 << ESCAPE_BOOL(xtrabackup_incremental)<< ","
1603 << escape_and_quote(connection,xb_stream_name[xtrabackup_stream_fmt]) <<","
1604 << ESCAPE_BOOL(xtrabackup_compress) << ")";
1605
1606 xb_mysql_query(mysql_connection, oss.str().c_str(), false);
1607
1608cleanup:
1609
1610 free(uuid);
1611 free(server_version);
1612
1613 return(true);
1614}
1615
1616extern const char *innodb_checksum_algorithm_names[];
1617
1618#ifdef _WIN32
1619#include <algorithm>
1620#endif
1621
1622static std::string make_local_paths(const char *data_file_path)
1623{
1624 if (strchr(data_file_path, '/') == 0
1625#ifdef _WIN32
1626 && strchr(data_file_path, '\\') == 0
1627#endif
1628 ){
1629 return std::string(data_file_path);
1630 }
1631
1632 std::ostringstream buf;
1633
1634 char *dup = strdup(innobase_data_file_path);
1635 ut_a(dup);
1636 char *p;
1637 char * token = strtok_r(dup, ";", &p);
1638 while (token) {
1639 if (buf.tellp())
1640 buf << ";";
1641
1642 char *fname = strrchr(token, '/');
1643#ifdef _WIN32
1644 fname = std::max(fname,strrchr(token, '\\'));
1645#endif
1646 if (fname)
1647 buf << fname + 1;
1648 else
1649 buf << token;
1650 token = strtok_r(NULL, ";", &p);
1651 }
1652 free(dup);
1653 return buf.str();
1654}
1655
1656bool write_backup_config_file()
1657{
1658 int rc= backup_file_printf("backup-my.cnf",
1659 "# This MySQL options file was generated by innobackupex.\n\n"
1660 "# The MySQL server\n"
1661 "[mysqld]\n"
1662 "innodb_checksum_algorithm=%s\n"
1663 "innodb_data_file_path=%s\n"
1664 "innodb_log_files_in_group=%lu\n"
1665 "innodb_log_file_size=%llu\n"
1666 "innodb_page_size=%lu\n"
1667 "innodb_undo_directory=%s\n"
1668 "innodb_undo_tablespaces=%lu\n"
1669 "%s%s\n"
1670 "%s\n",
1671 innodb_checksum_algorithm_names[srv_checksum_algorithm],
1672 make_local_paths(innobase_data_file_path).c_str(),
1673 srv_n_log_files,
1674 srv_log_file_size,
1675 srv_page_size,
1676 srv_undo_dir,
1677 srv_undo_tablespaces,
1678 innobase_buffer_pool_filename ?
1679 "innodb_buffer_pool_filename=" : "",
1680 innobase_buffer_pool_filename ?
1681 innobase_buffer_pool_filename : "",
1682 encryption_plugin_get_config());
1683 return rc;
1684}
1685
1686
1687static
1688char *make_argv(char *buf, size_t len, int argc, char **argv)
1689{
1690 size_t left= len;
1691 const char *arg;
1692
1693 buf[0]= 0;
1694 ++argv; --argc;
1695 while (argc > 0 && left > 0)
1696 {
1697 arg = *argv;
1698 if (strncmp(*argv, "--password", strlen("--password")) == 0) {
1699 arg = "--password=...";
1700 }
1701 left-= snprintf(buf + len - left, left,
1702 "%s%c", arg, argc > 1 ? ' ' : 0);
1703 ++argv; --argc;
1704 }
1705
1706 return buf;
1707}
1708
1709void
1710capture_tool_command(int argc, char **argv)
1711{
1712 /* capture tool name tool args */
1713 tool_name = strrchr(argv[0], '/');
1714 tool_name = tool_name ? tool_name + 1 : argv[0];
1715
1716 make_argv(tool_args, sizeof(tool_args), argc, argv);
1717}
1718
1719
1720bool
1721select_history()
1722{
1723 if (opt_incremental_history_name || opt_incremental_history_uuid) {
1724 if (!select_incremental_lsn_from_history(
1725 &incremental_lsn)) {
1726 return(false);
1727 }
1728 }
1729 return(true);
1730}
1731
1732bool
1733flush_changed_page_bitmaps()
1734{
1735 if (xtrabackup_incremental && have_changed_page_bitmaps &&
1736 !xtrabackup_incremental_force_scan) {
1737 xb_mysql_query(mysql_connection,
1738 "FLUSH NO_WRITE_TO_BINLOG CHANGED_PAGE_BITMAPS", false);
1739 }
1740 return(true);
1741}
1742
1743
1744/*********************************************************************//**
1745Deallocate memory, disconnect from MySQL server, etc.
1746@return true on success. */
1747void
1748backup_cleanup()
1749{
1750 free(mysql_slave_position);
1751 free(mysql_binlog_position);
1752 free(buffer_pool_filename);
1753
1754 if (mysql_connection) {
1755 mysql_close(mysql_connection);
1756 }
1757}
1758
1759
1760static pthread_mutex_t mdl_lock_con_mutex;
1761static MYSQL *mdl_con = NULL;
1762
1763void
1764mdl_lock_init()
1765{
1766 pthread_mutex_init(&mdl_lock_con_mutex, NULL);
1767 mdl_con = xb_mysql_connect();
1768 if (mdl_con)
1769 {
1770 xb_mysql_query(mdl_con, "BEGIN", false, true);
1771 }
1772}
1773
1774void
1775mdl_lock_table(ulint space_id)
1776{
1777 std::ostringstream oss;
1778 oss << "SELECT NAME "
1779 "FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES "
1780 "WHERE SPACE = " << space_id << " AND NAME LIKE '%%/%%'";
1781
1782 pthread_mutex_lock(&mdl_lock_con_mutex);
1783
1784 MYSQL_RES *mysql_result = xb_mysql_query(mdl_con, oss.str().c_str(), true, true);
1785
1786 while (MYSQL_ROW row = mysql_fetch_row(mysql_result)) {
1787 std::string full_table_name = ut_get_name(0,row[0]);
1788 std::ostringstream lock_query;
1789 lock_query << "SELECT 1 FROM " << full_table_name << " LIMIT 0";
1790
1791 msg_ts("Locking MDL for %s\n", full_table_name.c_str());
1792 xb_mysql_query(mdl_con, lock_query.str().c_str(), false, false);
1793 }
1794
1795 pthread_mutex_unlock(&mdl_lock_con_mutex);
1796 mysql_free_result(mysql_result);
1797}
1798
1799
1800void
1801mdl_unlock_all()
1802{
1803 msg_ts("Unlocking MDL for all tables\n");
1804 xb_mysql_query(mdl_con, "COMMIT", false, true);
1805 mysql_close(mdl_con);
1806 pthread_mutex_destroy(&mdl_lock_con_mutex);
1807}
1808
1809