diff options
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/CMakeLists.txt | 62 | ||||
-rw-r--r-- | scripts/comp_sql.c | 6 | ||||
-rwxr-xr-x | scripts/galera_new_cluster.sh | 2 | ||||
-rw-r--r-- | scripts/galera_recovery.sh | 3 | ||||
-rw-r--r-- | scripts/mysql_install_db.sh | 39 | ||||
-rw-r--r-- | scripts/mysql_setpermission.sh | 8 | ||||
-rw-r--r-- | scripts/mysql_system_tables_fix.sql | 45 | ||||
-rw-r--r-- | scripts/mysql_to_mariadb.sql | 22 | ||||
-rw-r--r-- | scripts/mysqld_multi.sh | 110 | ||||
-rw-r--r-- | scripts/mysqld_safe.sh | 89 | ||||
-rw-r--r-- | scripts/mytop.sh | 2 | ||||
-rw-r--r-- | scripts/wsrep_sst_backup.sh | 109 | ||||
-rw-r--r-- | scripts/wsrep_sst_common.sh | 1495 | ||||
-rw-r--r-- | scripts/wsrep_sst_mariabackup.sh | 1804 | ||||
-rw-r--r-- | scripts/wsrep_sst_mysqldump.sh | 142 | ||||
-rw-r--r-- | scripts/wsrep_sst_rsync.sh | 1033 |
16 files changed, 3540 insertions, 1431 deletions
diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 0b01c7b97e8..bd8aac00012 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -99,7 +99,6 @@ INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mysql_test_db.sql ${CMAKE_CURRENT_SOURCE_DIR}/fill_help_tables.sql ${CMAKE_CURRENT_SOURCE_DIR}/mysql_test_data_timezone.sql - ${CMAKE_CURRENT_SOURCE_DIR}/mysql_to_mariadb.sql ${CMAKE_CURRENT_BINARY_DIR}/maria_add_gis_sp.sql ${CMAKE_CURRENT_BINARY_DIR}/maria_add_gis_sp_bootstrap.sql ${FIX_PRIVILEGES_SQL} @@ -164,6 +163,7 @@ ENDIF() SET(HOSTNAME "hostname") SET(MYSQLD_USER "mysql") +SET(MYSQLD_GROUP "mysql") ENDIF(UNIX) # Really ugly, one script, "mysql_install_db", needs prefix set to ".", @@ -275,12 +275,43 @@ ELSE() wsrep_sst_mysqldump wsrep_sst_rsync wsrep_sst_mariabackup + wsrep_sst_backup ) # The following script is sourced from other SST scripts, so it should # not be made executable. SET(WSREP_SOURCE wsrep_sst_common ) + + SET (wsrep_sst_rsync_wan ${CMAKE_CURRENT_BINARY_DIR}/wsrep_sst_rsync_wan) + ADD_CUSTOM_COMMAND( + OUTPUT ${wsrep_sst_rsync_wan} + COMMAND ${CMAKE_COMMAND} ARGS -E create_symlink + wsrep_sst_rsync + wsrep_sst_rsync_wan + ) + ADD_CUSTOM_TARGET(symlink_wsrep_sst_rsync + ALL + DEPENDS ${wsrep_sst_rsync_wan} + ) + INSTALL( + FILES ${wsrep_sst_rsync_wan} + DESTINATION ${INSTALL_BINDIR} + COMPONENT Server + ) + + FOREACH(file ${WSREP_SOURCE}) + CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/${file}.sh + ${CMAKE_CURRENT_BINARY_DIR}/${file} ESCAPE_QUOTES @ONLY) + IF(NOT ${file}_COMPONENT) + SET(${file}_COMPONENT Server) + ENDIF() + INSTALL(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${file} + DESTINATION ${INSTALL_BINDIR} + COMPONENT ${${file}_COMPONENT} + ) + ENDFOREACH() ENDIF() IF (NOT WITHOUT_SERVER) SET(SERVER_SCRIPTS @@ -332,35 +363,6 @@ ELSE() COMPONENT ${${file}_COMPONENT} ) ENDFOREACH() - SET (wsrep_sst_rsync_wan ${CMAKE_CURRENT_BINARY_DIR}/wsrep_sst_rsync_wan) - ADD_CUSTOM_COMMAND( - OUTPUT ${wsrep_sst_rsync_wan} - COMMAND ${CMAKE_COMMAND} ARGS -E create_symlink - wsrep_sst_rsync - wsrep_sst_rsync_wan - ) - ADD_CUSTOM_TARGET(symlink_wsrep_sst_rsync - ALL - DEPENDS ${wsrep_sst_rsync_wan} - ) - INSTALL( - FILES ${wsrep_sst_rsync_wan} - DESTINATION ${INSTALL_BINDIR} - COMPONENT Server - ) - - FOREACH(file ${WSREP_SOURCE}) - CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/${file}.sh - ${CMAKE_CURRENT_BINARY_DIR}/${file} ESCAPE_QUOTES @ONLY) - IF(NOT ${file}_COMPONENT) - SET(${file}_COMPONENT Server) - ENDIF() - INSTALL(FILES - ${CMAKE_CURRENT_BINARY_DIR}/${file} - DESTINATION ${INSTALL_BINDIR} - COMPONENT ${${file}_COMPONENT} - ) - ENDFOREACH() ENDIF() # Install libgcc as mylibgcc.a diff --git a/scripts/comp_sql.c b/scripts/comp_sql.c index 748b2320f2a..abd59e85bb5 100644 --- a/scripts/comp_sql.c +++ b/scripts/comp_sql.c @@ -1,5 +1,5 @@ /* Copyright (c) 2004, 2010, Oracle and/or its affiliates. - Copyright (c) 2012, 2014, Monty Program Ab + Copyright (c) 2012, 2022, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -77,7 +77,6 @@ char *fgets_fn(char *buffer, size_t size, fgets_input_t input, int *error) static void print_query(FILE *out, const char *query) { const char *ptr= query; - int column= 0; fprintf(out, "\""); while (*ptr) @@ -90,21 +89,18 @@ static void print_query(FILE *out, const char *query) and wrap to the next line, tabulated. */ fprintf(out, "\\n\"\n \""); - column= 2; break; case '\r': /* Skipped */ break; case '\"': fprintf(out, "\\\""); - column++; break; case '\\': fprintf(out, "\\\\"); break; default: putc(*ptr, out); - column++; break; } ptr++; diff --git a/scripts/galera_new_cluster.sh b/scripts/galera_new_cluster.sh index e0763ed516a..ac9dcf42102 100755 --- a/scripts/galera_new_cluster.sh +++ b/scripts/galera_new_cluster.sh @@ -22,7 +22,7 @@ EOF fi systemctl set-environment _WSREP_NEW_CLUSTER='--wsrep-new-cluster' && \ - systemctl start ${1:-mariadb} + systemctl restart ${1:-mariadb} extcode=$? diff --git a/scripts/galera_recovery.sh b/scripts/galera_recovery.sh index 709c4b0eed5..8df2abc3fd5 100644 --- a/scripts/galera_recovery.sh +++ b/scripts/galera_recovery.sh @@ -101,8 +101,7 @@ wsrep_recover_position() { # Safety checks if [ -n "$log_file" -a -f "$log_file" ]; then - [ "$euid" = "0" ] && chown $user $log_file - chmod 600 $log_file + chmod 600 $log_file else log "WSREP: mktemp failed" fi diff --git a/scripts/mysql_install_db.sh b/scripts/mysql_install_db.sh index 3b4ed381e47..4c6376142cc 100644 --- a/scripts/mysql_install_db.sh +++ b/scripts/mysql_install_db.sh @@ -31,6 +31,7 @@ defaults="" defaults_group_suffix="" mysqld_opt="" user="" +group="" silent_startup="--silent-startup" force=0 @@ -67,6 +68,7 @@ Usage: $0 [OPTIONS] --cross-bootstrap For internal use. Used when building the MariaDB system tables on a different host than the target. --datadir=path The path to the MariaDB data directory. + --no-defaults Don't read default options from any option file. --defaults-extra-file=name Read this file after the global files are read. --defaults-file=name Only read default options from the given file name. @@ -79,8 +81,6 @@ Usage: $0 [OPTIONS] --help Display this help and exit. --ldata=path The path to the MariaDB data directory. Same as --datadir. - --no-defaults Don't read default options from any option file. - --defaults-file=path Read only this configuration file. --rpm For internal use. This option is used by RPM files during the MariaDB installation process. --skip-name-resolve Use IP addresses rather than hostnames when creating @@ -96,6 +96,11 @@ Usage: $0 [OPTIONS] user. You must be root to use this option. By default mysqld runs using your current login name and files and directories that it creates will be owned by you. + --group=group_name The login group to use for running mysqld. Files and + directories created by mysqld will be owned by this + group. You must be root to use this option. By default + mysqld runs using your current group and files and + directories that it creates will be owned by you. All other options are passed to the mysqld program @@ -145,11 +150,11 @@ parse_arguments() --ldata=*|--datadir=*|--data=*) ldata=`parse_arg "$arg"` ;; --log-error=*) log_error=`parse_arg "$arg"` ;; - --user=*) # Note that the user will be passed to mysqld so that it runs # as 'user' (crucial e.g. if log-bin=/some_other_path/ # where a chown of datadir won't help) - user=`parse_arg "$arg"` ;; + --user=*) user=`parse_arg "$arg"` ;; + --group=*) group=`parse_arg "$arg"` ;; --skip-name-resolve) ip_only=1 ;; --verbose) verbose=1 ; silent_startup="" ;; --rpm) in_rpm=1 ;; @@ -458,7 +463,12 @@ do fi if test -n "$user" then - chown $user "$dir" + if test -z "$group" + then + chown $user $dir + else + chown $user:$group $dir + fi if test $? -ne 0 then echo "Cannot change ownership of the database directories to the '$user'" @@ -473,6 +483,12 @@ then args="$args --user=$user" fi +#To be enabled if/when we enable --group as an option to mysqld +#if test -n "$group" +#then +# args="$args --group=$group" +#fi + # When doing a "cross bootstrap" install, no reference to the current # host should be added to the system tables. So we filter out any # lines which contain the current host name. @@ -521,6 +537,7 @@ cat_sql() s_echo "Installing MariaDB/MySQL system tables in '$ldata' ..." if cat_sql | eval "$filter_cmd_line" | mysqld_install_cmd_line > /dev/null then + printf "@VERSION@-MariaDB" > "$ldata/mysql_upgrade_info" s_echo "OK" else log_file_place=$ldata @@ -573,12 +590,8 @@ then echo echo echo "PLEASE REMEMBER TO SET A PASSWORD FOR THE MariaDB root USER !" - echo "To do so, start the server, then issue the following commands:" + echo "To do so, start the server, then issue the following command:" echo - echo "'$bindir/mysqladmin' -u root password 'new-password'" - echo "'$bindir/mysqladmin' -u root -h $hostname password 'new-password'" - echo - echo "Alternatively you can run:" echo "'$bindir/mysql_secure_installation'" echo echo "which will also give you the option of removing the test" @@ -587,8 +600,7 @@ then fi echo - echo "See the MariaDB Knowledgebase at http://mariadb.com/kb or the" - echo "MySQL manual for more instructions." + echo "See the MariaDB Knowledgebase at http://mariadb.com/kb" if test "$in_rpm" -eq 0 then @@ -604,8 +616,7 @@ then echo "Please report any problems at http://mariadb.org/jira" echo echo "The latest information about MariaDB is available at http://mariadb.org/." - echo "You can find additional information about the MySQL part at:" - echo "http://dev.mysql.com" + echo echo "Consider joining MariaDB's strong and vibrant community:" echo "https://mariadb.org/get-involved/" echo diff --git a/scripts/mysql_setpermission.sh b/scripts/mysql_setpermission.sh index 71462d28622..7c0ef575db7 100644 --- a/scripts/mysql_setpermission.sh +++ b/scripts/mysql_setpermission.sh @@ -52,6 +52,7 @@ use strict; use vars qw($dbh $sth $hostname $opt_user $opt_password $opt_help $opt_host $opt_socket $opt_port $host $version); +my $sqlport = ""; my $sqlhost = ""; my $user = ""; @@ -84,9 +85,13 @@ if ($opt_password eq '') print "\n"; } +# Using port argument with 'localhost' will cause an error +if ($sqlhost ne "localhost") { + $sqlport = ":port=$opt_port"; +} # make the connection to MariaDB -$dbh= DBI->connect("DBI:mysql:mysql:host=$sqlhost:port=$opt_port:mysql_socket=$opt_socket",$opt_user,$opt_password, {PrintError => 0}) || +$dbh= DBI->connect("DBI:mysql:mysql:host=$sqlhost$sqlport:mysql_socket=$opt_socket",$opt_user,$opt_password, {PrintError => 0}) || die("Can't make a connection to the mysql server.\n The error: $DBI::errstr"); # the start of the program @@ -278,7 +283,6 @@ sub addall { $sth = $dbh->do("REVOKE ALL ON $db.* FROM \'$user\'\@\'$host\'") || die $dbh->errstr; } } - $dbh->do("FLUSH PRIVILEGES") || print STDERR "Can't flush privileges\n"; print "Everything is inserted and mysql privileges have been reloaded.\n\n"; } diff --git a/scripts/mysql_system_tables_fix.sql b/scripts/mysql_system_tables_fix.sql index 3949cd8f072..79866d149bb 100644 --- a/scripts/mysql_system_tables_fix.sql +++ b/scripts/mysql_system_tables_fix.sql @@ -27,6 +27,7 @@ set sql_mode=''; set storage_engine=MyISAM; set enforce_storage_engine=NULL; +set alter_algorithm=DEFAULT; ALTER TABLE user add File_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL; @@ -679,18 +680,33 @@ ALTER TABLE db modify Delete_history_priv enum('N','Y') COLLATE utf8_general_ci UPDATE user SET Delete_history_priv = Super_priv WHERE @had_user_delete_history_priv = 0; -ALTER TABLE user ADD plugin char(64) CHARACTER SET latin1 DEFAULT '' NOT NULL, - ADD authentication_string TEXT NOT NULL; -ALTER TABLE user MODIFY plugin char(64) CHARACTER SET latin1 DEFAULT '' NOT NULL, - MODIFY authentication_string TEXT NOT NULL; -ALTER TABLE user ADD password_expired ENUM('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL; -ALTER TABLE user ADD is_role enum('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL; -ALTER TABLE user ADD default_role char(80) binary DEFAULT '' NOT NULL; -ALTER TABLE user ADD max_statement_time decimal(12,6) DEFAULT 0 NOT NULL; +ALTER TABLE user ADD plugin char(64) CHARACTER SET latin1 DEFAULT '' NOT NULL AFTER max_user_connections, + ADD authentication_string TEXT NOT NULL AFTER plugin; +ALTER TABLE user CHANGE auth_string authentication_string TEXT NOT NULL; + +ALTER TABLE user ADD password_expired ENUM('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER authentication_string; +ALTER TABLE user ADD is_role enum('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER password_expired; +ALTER TABLE user ADD default_role char(80) binary DEFAULT '' NOT NULL AFTER is_role; +ALTER TABLE user ADD max_statement_time decimal(12,6) DEFAULT 0 NOT NULL AFTER default_role; + -- Somewhere above, we ran ALTER TABLE user .... CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin. --- we want password_expired column to have collation utf8_general_ci. -ALTER TABLE user MODIFY password_expired ENUM('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL; -ALTER TABLE user MODIFY is_role enum('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL; +-- we want password_expired column to have collation utf8_general_ci. +-- Order columns correctly that were not ordered until MDEV-23201 (ff8ffef3e1915d7a9caa07d9461cd8d47c4baf98) + +ALTER TABLE user MODIFY plugin char(64) CHARACTER SET latin1 DEFAULT '' NOT NULL AFTER max_user_connections, + MODIFY authentication_string TEXT NOT NULL AFTER plugin, + MODIFY password_expired ENUM('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER authentication_string, + MODIFY is_role enum('N', 'Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER password_expired, + MODIFY default_role char(80) binary DEFAULT '' NOT NULL AFTER is_role, + MODIFY max_statement_time decimal(12,6) DEFAULT 0 NOT NULL AFTER default_role, +-- MDEV-24122 formerly mysql5.7 users may have the following columns password_last_changed, +-- password_lifetime and account_locked. Ensure they are beyond the end of the user columns +-- used by MariaDB. MariaDB-10.4 will use these in the creation of mysql.global_priv. +-- password_last_changed has a DEFAULT/ON UPDATE of CURRENT_TIMESTAMP to keep track of +-- time until 10.4 added. + MODIFY IF EXISTS password_last_changed timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER max_statement_time, + MODIFY IF EXISTS password_lifetime smallint unsigned DEFAULT NULL AFTER password_last_changed, + MODIFY IF EXISTS account_locked enum('N', 'Y') CHARACTER SET utf8 DEFAULT 'N' NOT NULL after password_lifetime; -- Need to pre-fill mysql.proxies_priv with access for root even when upgrading from -- older versions @@ -780,3 +796,10 @@ ALTER TABLE help_topic MODIFY url TEXT NOT NULL; # MDEV-7383 - varbinary on mix/max of column_stats alter table column_stats modify min_value varbinary(255) DEFAULT NULL, modify max_value varbinary(255) DEFAULT NULL; + +# MDEV-21873: 10.2 to 10.3 upgrade doesn't remove semi-sync reference from +# mysql.plugin table. +# As per suggested fix, check INFORMATION_SCHEMA.PLUGINS +# and if semisync plugins aren't there, delete them from mysql.plugin. +DELETE FROM mysql.plugin WHERE name="rpl_semi_sync_master" AND NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME="rpl_semi_sync_master"); +DELETE FROM mysql.plugin WHERE name="rpl_semi_sync_slave" AND NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME="rpl_semi_sync_slave"); diff --git a/scripts/mysql_to_mariadb.sql b/scripts/mysql_to_mariadb.sql deleted file mode 100644 index 4ee3f3a4214..00000000000 --- a/scripts/mysql_to_mariadb.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Script that changes MySQL 5.7 privilege tables to MariaDB 10.x --- This should be run first with --- mysql --force mysql < mysql_to_mariadb.sql --- It's ok to ignore any errors, as these usually means that the tables are --- already fixed. - --- After this script s run, one should run at least: --- mysql_upgrade --upgrade-system-tables --- to get the other tables in the mysql database fixed. - --- Drop not existing columnms -alter table mysql.user drop column `password_last_changed`, drop column `password_lifetime`, drop column `account_locked`; - --- Change existing columns -alter table mysql.user change column `authentication_string` `auth_string` text COLLATE utf8_bin NOT NULL; - --- Add new columns -alter table mysql.user add column `Password` char(41) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '' after `user`, add column `is_role` enum('N','Y') CHARACTER SET utf8 NOT NULL DEFAULT 'N' after `auth_string`; -alter table mysql.user add column `default_role` char(80) COLLATE utf8_bin NOT NULL DEFAULT '', add column `max_statement_time` decimal(12,6) NOT NULL DEFAULT '0.000000'; - --- Fix passwords -update mysql.user set `password`=`auth_string`, plugin='' where plugin="mysql_native_password"; diff --git a/scripts/mysqld_multi.sh b/scripts/mysqld_multi.sh index 2c3578834eb..17b8a135fc1 100644 --- a/scripts/mysqld_multi.sh +++ b/scripts/mysqld_multi.sh @@ -20,9 +20,10 @@ use Getopt::Long; use POSIX qw(strftime getcwd); +use File::Path qw(mkpath); $|=1; -$VER="2.20"; +$VER="3.0"; my @defaults_options; # Leading --no-defaults, --defaults-file, etc. @@ -147,6 +148,7 @@ sub main usage() if (!defined($ARGV[0]) || (!($ARGV[0] =~ m/^start$/i) && !($ARGV[0] =~ m/^stop$/i) && + !($ARGV[0] =~ m/^reload$/i) && !($ARGV[0] =~ m/^report$/i))); if (!$opt_no_log) @@ -160,7 +162,7 @@ sub main print strftime "%a %b %e %H:%M:%S %Y", localtime; print "\n"; } - if ($ARGV[0] =~ m/^start$/i) + if (($ARGV[0] =~ m/^start$/i) || ($ARGV[0] =~ m/^reload$/i)) { if (!defined(($mysqld= my_which($opt_mysqld))) && $opt_verbose) { @@ -169,7 +171,11 @@ sub main print "This is OK, if you are using option \"mysqld=...\" in "; print "groups [mysqldN] separately for each.\n\n"; } - start_mysqlds(); + if ($ARGV[0] =~ m/^start$/i) { + start_mysqlds(); + } elsif ($ARGV[0] =~ m/^reload$/i) { + reload_mysqlds(); + } } else { @@ -329,8 +335,42 @@ sub start_mysqlds() $mysqld_found= 1; # The default $mysqld_found= 0 if (!length($mysqld)); $com= "$mysqld"; + for ($j = 0, $tmp= ""; defined($options[$j]); $j++) { + if ("--datadir=" eq substr($options[$j], 0, 10)) { + $datadir = $options[$j]; + $datadir =~ s/\-\-datadir\=//; + eval { mkpath($datadir) }; + if ($@) { + print "FATAL ERROR: Cannot create data directory $datadir: $!\n"; + exit(1); + } + if (! -d $datadir."/mysql") { + if (-w $datadir) { + print "\n\nInstalling new database in $datadir\n\n"; + $install_cmd="@bindir@/mysql_install_db "; + $install_cmd.="--user=mysql "; + $install_cmd.="--datadir=$datadir"; + system($install_cmd); + } else { + print "\n"; + print "FATAL ERROR: Tried to create mysqld under group [$groups[$i]],\n"; + print "but the data directory is not writable.\n"; + print "data directory used: $datadir\n"; + exit(1); + } + } + + if (! -d $datadir."/mysql") { + print "\n"; + print "FATAL ERROR: Tried to start mysqld under group [$groups[$i]],\n"; + print "but no data directory was found or could be created.\n"; + print "data directory used: $datadir\n"; + exit(1); + } + } + if ("--mysqladmin=" eq substr($options[$j], 0, 13)) { # catch this and ignore @@ -411,6 +451,58 @@ sub start_mysqlds() } #### +#### reload multiple servers +#### + +sub reload_mysqlds() +{ + my (@groups, $com, $tmp, $i, @options, $j); + + if (!$opt_no_log) + { + w2log("\nReloading MySQL servers\n","$opt_log",0,0); + } + else + { + print "\nReloading MySQL servers\n"; + } + @groups = &find_groups($groupids); + for ($i = 0; defined($groups[$i]); $i++) + { + $mysqld_server = $mysqld; + @options = defaults_for_group($groups[$i]); + + for ($j = 0, $tmp= ""; defined($options[$j]); $j++) + { + if ("--mysqladmin=" eq substr($options[$j], 0, 13)) + { + # catch this and ignore + } + elsif ("--mysqld=" eq substr($options[$j], 0, 9)) + { + $options[$j] =~ s/\-\-mysqld\=//; + $mysqld_server = $options[$j]; + } + elsif ("--pid-file=" eq substr($options[$j], 0, 11)) + { + $options[$j] =~ s/\-\-pid-file\=//; + $pid_file = $options[$j]; + } + } + $com = "killproc -p $pid_file -HUP $mysqld_server"; + system($com); + + $com = "touch $pid_file"; + system($com); + } + if (!$i && !$opt_no_log) + { + w2log("No MySQL servers to be reloaded (check your GNRs)", + "$opt_log", 0, 0); + } +} + +### #### stop multiple servers #### @@ -792,7 +884,7 @@ sub usage $my_progname version $VER by Jani Tolonen Description: -$my_progname can be used to start, or stop any number of separate +$my_progname can be used to start, reload, or stop any number of separate mysqld processes running in different TCP/IP ports and UNIX sockets. $my_progname can read group [mysqld_multi] from my.cnf file. You may @@ -810,16 +902,16 @@ integer starting from 1. These groups should be the same as the regular [mysqld] group, but with those port, socket and any other options that are to be used with each separate mysqld process. The number in the group name has another function; it can be used for starting, -stopping, or reporting any specific mysqld server. +reloading, stopping, or reporting any specific mysqld server. -Usage: $my_progname [OPTIONS] {start|stop|report} [GNR,GNR,GNR...] -or $my_progname [OPTIONS] {start|stop|report} [GNR-GNR,GNR,GNR-GNR,...] +Usage: $my_progname [OPTIONS] {start|reload|stop|report} [GNR,GNR,GNR...] +or $my_progname [OPTIONS] {start|reload|stop|report} [GNR-GNR,GNR,GNR-GNR,...] -The GNR means the group number. You can start, stop or report any GNR, +The GNR means the group number. You can start, reload, stop or report any GNR, or several of them at the same time. (See --example) The GNRs list can be comma separated or a dash combined. The latter means that all the GNRs between GNR1-GNR2 will be affected. Without GNR argument all the -groups found will either be started, stopped, or reported. Note that +groups found will either be started, reloaded, stopped, or reported. Note that syntax for specifying GNRs must appear without spaces. Options: diff --git a/scripts/mysqld_safe.sh b/scripts/mysqld_safe.sh index 8969b01023b..f92a31851ff 100644 --- a/scripts/mysqld_safe.sh +++ b/scripts/mysqld_safe.sh @@ -29,6 +29,7 @@ logging=init want_syslog=0 syslog_tag= user='@MYSQLD_USER@' +group='@MYSQLD_GROUP@' pid_file= err_log= err_log_base= @@ -73,12 +74,14 @@ usage () { cat <<EOF Usage: $0 [OPTIONS] --no-defaults Don't read the system defaults file - --core-file-size=LIMIT Limit core files to the specified size --defaults-file=FILE Use the specified defaults file --defaults-extra-file=FILE Also use defaults from the specified file + --defaults-group-suffix=X Additionally read default groups with X appended + as a suffix --ledir=DIRECTORY Look for mysqld in the specified directory --open-files-limit=LIMIT Limit the number of open files --crash-script=FILE Script to call when mysqld crashes + --core-file-size=LIMIT Limit core files to the specified size --timezone=TZ Set the system timezone --malloc-lib=LIB Preload shared library LIB if available --mysqld=FILE Use the specified file as mysqld @@ -126,7 +129,7 @@ log_generic () { case $logging in init) ;; # Just echo the message, don't save it anywhere file) - if [ -n "$helper" ]; then + if [ "$helper_exist" -eq "0" ]; then echo "$msg" | "$helper" "$user" log "$err_log" fi ;; @@ -150,7 +153,7 @@ eval_log_error () { local cmd="$1" case $logging in file) - if [ -n "$helper" ]; then + if [ "$helper_exist" -eq "0" ]; then cmd="$cmd 2>&1 | "`shell_quote_string "$helper"`" $user log "`shell_quote_string "$err_log"` fi ;; @@ -235,7 +238,9 @@ wsrep_recover_position() { fi if [ -f $wr_logfile ]; then - [ "$euid" = "0" ] && chown $user $wr_logfile + # NOTE! Do not change ownership of the temporary file, as on newer kernel + # versions fs.protected_regular is set to '2' and redirecting output with > + # as root to a file not owned by root will fail with "Permission denied" chmod 600 $wr_logfile else log_error "WSREP: mktemp failed" @@ -250,6 +255,11 @@ wsrep_recover_position() { eval "$mysqld_cmd --wsrep_recover $wr_options 2> $wr_logfile" + if [ ! -s "$wr_logfile" ]; then + log_error "Log file $wr_logfile was empty, cannot proceed. Is system running fs.protected_regular?" + exit 1 + fi + local rp="$(grep 'WSREP: Recovered position:' $wr_logfile)" if [ -z "$rp" ]; then local skipped="$(grep WSREP $wr_logfile | grep 'skipping position recovery')" @@ -281,6 +291,7 @@ parse_arguments() { --pid[-_]file=*) pid_file="$val" ;; --plugin[-_]dir=*) PLUGIN_DIR="$val" ;; --user=*) user="$val"; SET_USER=1 ;; + --group=*) group="$val"; SET_USER=1 ;; --log[-_]basename=*|--hostname=*|--loose[-_]log[-_]basename=*) pid_file="$val.pid"; err_log_base="$val"; @@ -421,25 +432,10 @@ mysqld_ld_preload_text() { echo "$text" } - -mysql_config= -get_mysql_config() { - if [ -z "$mysql_config" ]; then - mysql_config=`echo "$0" | sed 's,/[^/][^/]*$,/mysql_config,'` - if [ ! -x "$mysql_config" ]; then - log_error "Can not run mysql_config $@ from '$mysql_config'" - exit 1 - fi - fi - - "$mysql_config" "$@" -} - - # set_malloc_lib LIB # - If LIB is empty, do nothing and return -# - If LIB starts with 'tcmalloc' or 'jemalloc', look for the shared library in -# /usr/lib, /usr/lib64 and then pkglibdir. +# - If LIB starts with 'tcmalloc' or 'jemalloc', look for the shared library +# using `ldconfig`. # tcmalloc is part of the Google perftools project. # - If LIB is an absolute path, assume it is a malloc shared library # @@ -447,28 +443,29 @@ get_mysql_config() { # running mysqld. See ld.so for details. set_malloc_lib() { malloc_lib="$1" - if expr "$malloc_lib" : "\(tcmalloc\|jemalloc\)" > /dev/null ; then - pkglibdir=`get_mysql_config --variable=pkglibdir` - where='' - # This list is kept intentionally simple. Simply set --malloc-lib - # to a full path if another location is desired. - for libdir in /usr/lib /usr/lib64 "$pkglibdir" "$pkglibdir/mysql"; do - tmp=`echo "$libdir/lib$malloc_lib.so".[0-9]` - where="$where $libdir" - # log_notice "DEBUG: Checking for malloc lib '$tmp'" - [ -r "$tmp" ] || continue - malloc_lib="$tmp" - where='' - break - done + export PATH=$PATH:/sbin + if ! command -v ldconfig > /dev/null 2>&1 + then + log_error "ldconfig command not found, required for ldconfig -p" + exit 1 + fi + # format from ldconfig: + # "libjemalloc.so.1 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libjemalloc.so.1" + libmalloc_path="$(ldconfig -p | sed -n "/lib${malloc_lib}/p" | cut -d '>' -f2)" - if [ -n "$where" ]; then - log_error "no shared library for lib$malloc_lib.so.[0-9] found in$where" + if [ -z "$libmalloc_path" ]; then + log_error "no shared library for lib$malloc_lib.so.[0-9] found." exit 1 fi - fi + for f in $libmalloc_path; do + if [ -f "$f" ]; then + malloc_lib=$f # get the first path if many + break + fi + done + fi # Allow --malloc-lib='' to override other settings [ -z "$malloc_lib" ] && return @@ -485,7 +482,6 @@ set_malloc_lib() { exit 1 ;; esac - add_mysqld_ld_preload "$malloc_lib" } @@ -532,10 +528,9 @@ fi helper=`find_in_bin mysqld_safe_helper` print_defaults=`find_in_bin my_print_defaults` - # Check if helper exists -$helper --help >/dev/null 2>&1 || helper="" - +command -v $helper --help >/dev/null 2>&1 +helper_exist=$? # # Second, try to find the data directory # @@ -699,6 +694,8 @@ then if test "$user" != "root" -o $SET_USER = 1 then USER_OPTION="--user=$user" + # To be used if/when we enable --system-group as an option to mysqld + GROUP_OPTION="--group=$group" fi if test -n "$open_files" then @@ -721,7 +718,12 @@ then log_error "Fatal error Can't create database directory '$mysql_unix_port'" exit 1 fi - chown $user $mysql_unix_port_dir + if [ "$user" -a "$group" ]; then + chown $user:$group $mysql_unix_port_dir + else + [ "$user" ] && chown $user $mysql_unix_port_dir + [ "$group" ] && chgrp $group $mysql_unix_port_dir + fi chmod 755 $mysql_unix_port_dir fi @@ -942,7 +944,6 @@ fi # Avoid 'nohup: ignoring input' warning test -n "$NOHUP_NICENESS" && cmd="$cmd < /dev/null" - log_notice "Starting $MYSQLD daemon with databases from $DATADIR" # variable to track the current number of "fast" (a.k.a. subsecond) restarts diff --git a/scripts/mytop.sh b/scripts/mytop.sh index 3ef0a59f27f..e7b9040c3a8 100644 --- a/scripts/mytop.sh +++ b/scripts/mytop.sh @@ -1,4 +1,4 @@ -#!/usr/bin/perl -w +#!/usr/bin/env perl # # $Id: mytop,v 1.91 2012/01/18 16:49:12 mgrennan Exp $ diff --git a/scripts/wsrep_sst_backup.sh b/scripts/wsrep_sst_backup.sh new file mode 100644 index 00000000000..6f8c658135a --- /dev/null +++ b/scripts/wsrep_sst_backup.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +set -ue + +# Copyright (C) 2017-2021 MariaDB +# Copyright (C) 2010-2014 Codership Oy +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston +# MA 02110-1335 USA. + +# This is a reference script for rsync-based state snapshot transfer + +RSYNC_REAL_PID=0 # rsync process id +STUNNEL_REAL_PID=0 # stunnel process id + +OS="$(uname)" +[ "$OS" = 'Darwin' ] && export -n LD_LIBRARY_PATH + +# Setting the path for lsof on CentOS +export PATH="/usr/sbin:/sbin:$PATH" + +. $(dirname "$0")/wsrep_sst_common + +MAGIC_FILE="$WSREP_SST_OPT_DATA/backup_sst_complete" +rm -rf "$MAGIC_FILE" + +WSREP_LOG_DIR=${WSREP_LOG_DIR:-""} +# if WSREP_LOG_DIR env. variable is not set, try to get it from my.cnf +if [ -z "$WSREP_LOG_DIR" ]; then + WSREP_LOG_DIR=$(parse_cnf mysqld innodb-log-group-home-dir '') +fi + +if [ -n "$WSREP_LOG_DIR" ]; then + # handle both relative and absolute paths + WSREP_LOG_DIR=$(cd $WSREP_SST_OPT_DATA; mkdir -p "$WSREP_LOG_DIR"; cd $WSREP_LOG_DIR; pwd -P) +else + # default to datadir + WSREP_LOG_DIR=$(cd $WSREP_SST_OPT_DATA; pwd -P) +fi + +if [ "$WSREP_SST_OPT_ROLE" = 'donor' ] +then + + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" + + RC=0 + + if [ $WSREP_SST_OPT_BYPASS -eq 0 ]; then + + FLUSHED="$WSREP_SST_OPT_DATA/tables_flushed" + ERROR="$WSREP_SST_OPT_DATA/sst_error" + + [ -f "$FLUSHED" ] && rm -f "$FLUSHED" + [ -f "$ERROR" ] && rm -f "$ERROR" + + echo "flush tables" + + # Wait for : + # (a) Tables to be flushed, AND + # (b) Cluster state ID & wsrep_gtid_domain_id to be written to the file, OR + # (c) ERROR file, in case flush tables operation failed. + + while [ ! -r "$FLUSHED" ] || \ + ! grep -q -F ':' -- "$FLUSHED" + do + # Check whether ERROR file exists. + if [ -f "$ERROR" ]; then + # Flush tables operation failed. + rm -f "$ERROR" + exit 255 + fi + sleep 0.2 + done + + STATE=$(cat "$FLUSHED") + rm -f "$FLUSHED" + + + else # BYPASS + + wsrep_log_info "Bypassing state dump." + fi + + echo 'continue' # now server can resume updating data + + echo "$STATE" > "$MAGIC_FILE" + + echo "done $STATE" + +else # joiner + + wsrep_log_error "Unsupported role: '$WSREP_SST_OPT_ROLE'" + exit 22 # EINVAL + +fi + +wsrep_log_info "$WSREP_METHOD $WSREP_TRANSFER_TYPE completed on $WSREP_SST_OPT_ROLE" +exit 0 diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index 16607470f2c..6a94cb0f706 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,3 +1,4 @@ +# Copyright (C) 2017-2022 MariaDB # Copyright (C) 2012-2015 Codership Oy # # This program is free software; you can redistribute it and/or modify @@ -16,58 +17,166 @@ # This is a common command line parser to be sourced by other SST scripts -set -u +trap 'exit 32' HUP PIPE +trap 'exit 3' INT QUIT TERM + +# Setting the path for some utilities on CentOS +export PATH="$PATH:/usr/sbin:/usr/bin:/sbin:/bin" + +trim_string() +{ + if [ -n "$BASH_VERSION" ]; then + local pattern="[![:space:]${2:-}]" + local x="${1#*$pattern}" + local z=${#1} + x=${#x} + if [ $x -ne $z ]; then + local y="${1%$pattern*}" + y=${#y} + x=$(( z-x-1 )) + y=$(( y-x+1 )) + printf '%s' "${1:$x:$y}" + else + printf '' + fi + else + local pattern="[[:space:]${2:-}]" + echo "$1" | sed -E "s/^$pattern+|$pattern+\$//g" + fi +} + +trim_dir() +{ + local t=$(trim_string "$1") + if [ "$t" != '/' ]; then + if [ "${t%/}" != "$t" ]; then + t=$(trim_string "${t%/}") + fi + else + t='.' + fi + if [ -n "$BASH_VERSION" ]; then + printf '%s' "$t" + else + echo "$t" + fi +} + +to_minuses() +{ + local x="$1" + local t="${1#*_}" + local r="" + while [ "$t" != "$x" ]; do + r="$r${x%%_*}-" + x="$t" + t="${t#*_}" + done + if [ -n "$BASH_VERSION" ]; then + printf '%s' "$r$x" + else + echo "$r$x" + fi +} WSREP_SST_OPT_BYPASS=0 +WSREP_SST_OPT_PROGRESS=0 WSREP_SST_OPT_BINLOG="" WSREP_SST_OPT_BINLOG_INDEX="" +WSREP_SST_OPT_LOG_BASENAME="" WSREP_SST_OPT_DATA="" -WSREP_SST_OPT_AUTH=${WSREP_SST_OPT_AUTH:-} -WSREP_SST_OPT_USER=${WSREP_SST_OPT_USER:-} -WSREP_SST_OPT_PSWD=${WSREP_SST_OPT_PSWD:-} +WSREP_SST_OPT_AUTH="${WSREP_SST_OPT_AUTH:-}" +WSREP_SST_OPT_USER="${WSREP_SST_OPT_USER:-}" +WSREP_SST_OPT_PSWD="${WSREP_SST_OPT_PSWD:-}" +WSREP_SST_OPT_REMOTE_AUTH="${WSREP_SST_OPT_REMOTE_AUTH:-}" WSREP_SST_OPT_DEFAULT="" +WSREP_SST_OPT_DEFAULTS="" WSREP_SST_OPT_EXTRA_DEFAULT="" +WSREP_SST_OPT_EXTRA_DEFAULTS="" WSREP_SST_OPT_SUFFIX_DEFAULT="" WSREP_SST_OPT_SUFFIX_VALUE="" WSREP_SST_OPT_MYSQLD="" -INNODB_DATA_HOME_DIR_ARG="" -INNODB_LOG_GROUP_HOME_ARG="" -INNODB_UNDO_DIR_ARG="" -LOG_BIN_ARG="" +WSREP_SST_OPT_PORT="" +WSREP_SST_OPT_ADDR="" +WSREP_SST_OPT_ADDR_PORT="" +WSREP_SST_OPT_HOST="" +WSREP_SST_OPT_HOST_UNESCAPED="" +INNODB_DATA_HOME_DIR=$(trim_dir "${INNODB_DATA_HOME_DIR:-}") +INNODB_LOG_GROUP_HOME=$(trim_dir "${INNODB_LOG_GROUP_HOME:-}") +INNODB_UNDO_DIR=$(trim_dir "${INNODB_UNDO_DIR:-}") +INNODB_BUFFER_POOL="" +INNODB_FORCE_RECOVERY="" +INNOEXTRA="" while [ $# -gt 0 ]; do case "$1" in '--address') - readonly WSREP_SST_OPT_ADDR="$2" + WSREP_SST_OPT_ADDR="$2" # # Break address string into host:port/path parts # - case "${WSREP_SST_OPT_ADDR}" in + case "$WSREP_SST_OPT_ADDR" in \[*) # IPv6 - addr_no_bracket=${WSREP_SST_OPT_ADDR#\[} - readonly WSREP_SST_OPT_HOST_UNESCAPED=${addr_no_bracket%%\]*} - readonly WSREP_SST_OPT_HOST="[${WSREP_SST_OPT_HOST_UNESCAPED}]" - readonly WSREP_SST_OPT_HOST_ESCAPED="\\[${WSREP_SST_OPT_HOST_UNESCAPED}\\]" + # Remove the starting and ending square brackets, if present: + addr_no_bracket="${WSREP_SST_OPT_ADDR#\[}" + # Some utilities and subsequent code require an address + # without square brackets: + readonly WSREP_SST_OPT_HOST_UNESCAPED="${addr_no_bracket%%\]*}" + # Square brackets are needed in most cases: + readonly WSREP_SST_OPT_HOST="[$WSREP_SST_OPT_HOST_UNESCAPED]" + # Mark this address as IPv6: + readonly WSREP_SST_OPT_HOST_IPv6=1 + # Let's remove the leading part that contains the host address: + remain="${WSREP_SST_OPT_ADDR#*\]}" ;; *) - readonly WSREP_SST_OPT_HOST=${WSREP_SST_OPT_ADDR%%[:/]*} - readonly WSREP_SST_OPT_HOST_UNESCAPED=$WSREP_SST_OPT_HOST - readonly WSREP_SST_OPT_HOST_ESCAPED=$WSREP_SST_OPT_HOST + readonly WSREP_SST_OPT_HOST="${WSREP_SST_OPT_ADDR%%[:/]*}" + readonly WSREP_SST_OPT_HOST_UNESCAPED="$WSREP_SST_OPT_HOST" + readonly WSREP_SST_OPT_HOST_IPv6=0 + # Let's remove the leading part that contains the host address: + remain="${WSREP_SST_OPT_ADDR#*[:/]}" ;; esac - remain=${WSREP_SST_OPT_ADDR#${WSREP_SST_OPT_HOST_ESCAPED}} - remain=${remain#:} - readonly WSREP_SST_OPT_ADDR_PORT=${remain%%/*} - remain=${remain#*/} - readonly WSREP_SST_OPT_MODULE=${remain%%/*} - readonly WSREP_SST_OPT_PATH=${WSREP_SST_OPT_ADDR#*/} - remain=${WSREP_SST_OPT_PATH#*/} - if [ "$remain" != "${WSREP_SST_OPT_PATH}" ]; then - readonly WSREP_SST_OPT_LSN=${remain%%/*} - remain=${remain#*/} - if [ "$remain" != "${WSREP_SST_OPT_LSN}" ]; then - readonly WSREP_SST_OPT_SST_VER=${remain%%/*} + # If there is nothing but the address, then the remainder is empty: + [ "$remain" = "$WSREP_SST_OPT_ADDR" ] && remain="" + # Let's remove the ":" character that separates the port number + # from the hostname: + remain="${remain#:}" + # Extract the port number from the address - all characters + # up to "/" (if present): + WSREP_SST_OPT_ADDR_PORT="${remain%%/*}" + # If the "/" character is present, then the path is not empty: + if [ "$WSREP_SST_OPT_ADDR_PORT" != "$remain" ]; then + # This operation removes everything up to the "/" character, + # effectively removing the port number from the string: + readonly WSREP_SST_OPT_PATH="${remain#*/}" + else + readonly WSREP_SST_OPT_PATH="" + fi + # Remove the module name part from the string, which ends with "/": + remain="${WSREP_SST_OPT_PATH#*/}" + # This operation removes the tail after the very first occurrence + # of the "/" character, inclusively: + readonly WSREP_SST_OPT_MODULE="${WSREP_SST_OPT_PATH%%/*}" + # If there is one more "/" in the string, then everything before + # it will be the LSN, otherwise the LSN is empty: + if [ "$remain" != "$WSREP_SST_OPT_PATH" ]; then + # Extract the part that matches the LSN by removing all + # characters starting from the very first "/": + readonly WSREP_SST_OPT_LSN="${remain%%/*}" + # Exctract everything after the first occurrence of + # the "/" character in the string: + source="$remain" + remain="${remain#*/}" + # If the remainder does not match the original string, + # then there is something else (the version number in + # our case): + if [ "$remain" != "$source" ]; then + # Let's extract the version number by removing the tail + # after the very first occurence of the "/" character + # (inclusively): + readonly WSREP_SST_OPT_SST_VER="${remain%%/*}" else readonly WSREP_SST_OPT_SST_VER="" fi @@ -78,34 +187,44 @@ case "$1" in shift ;; '--bypass') - WSREP_SST_OPT_BYPASS=1 + readonly WSREP_SST_OPT_BYPASS=1 + ;; + '--progress') + readonly WSREP_SST_OPT_PROGRESS=$(( $2 )) + shift ;; '--datadir') - readonly WSREP_SST_OPT_DATA="$2" + # Let's remove the trailing slash: + readonly WSREP_SST_OPT_DATA=$(trim_dir "$2") shift ;; '--innodb-data-home-dir') - readonly INNODB_DATA_HOME_DIR_ARG="$2" + # Let's remove the trailing slash: + readonly INNODB_DATA_HOME_DIR=$(trim_dir "$2") shift ;; '--innodb-log-group-home-dir') - readonly INNODB_LOG_GROUP_HOME_ARG="$2" + # Let's remove the trailing slash: + readonly INNODB_LOG_GROUP_HOME=$(trim_dir "$2") shift ;; '--innodb-undo-directory') - readonly INNODB_UNDO_DIR_ARG="$2" + # Let's remove the trailing slash: + readonly INNODB_UNDO_DIR=$(trim_dir "$2") shift ;; - '--log-bin') - readonly LOG_BIN_ARG="$2" + '--innodb-buffer-pool-filename') + readonly INNODB_BUFFER_POOL=$(trim_string "$2") shift ;; '--defaults-file') readonly WSREP_SST_OPT_DEFAULT="$1=$2" + readonly WSREP_SST_OPT_DEFAULTS="$1='$2'" shift ;; '--defaults-extra-file') readonly WSREP_SST_OPT_EXTRA_DEFAULT="$1=$2" + readonly WSREP_SST_OPT_EXTRA_DEFAULTS="$1='$2'" shift ;; '--defaults-group-suffix') @@ -114,15 +233,34 @@ case "$1" in shift ;; '--host') - readonly WSREP_SST_OPT_HOST="$2" + case "$2" in + \[*) + # IPv6 + # Remove the starting and ending square brackets, if present: + addr_no_bracket="${2#\[}" + # Some utilities and subsequent code require an address + # without square brackets: + readonly WSREP_SST_OPT_HOST_UNESCAPED="${addr_no_bracket%%\]*}" + # Square brackets are needed in most cases: + readonly WSREP_SST_OPT_HOST="[${WSREP_SST_OPT_HOST_UNESCAPED}]" + # Mark this address as IPv6: + readonly WSREP_SST_OPT_HOST_IPv6=1 + ;; + *) + readonly WSREP_SST_OPT_HOST="$2" + readonly WSREP_SST_OPT_HOST_UNESCAPED="$2" + readonly WSREP_SST_OPT_HOST_IPv6=0 + ;; + esac + WSREP_SST_OPT_ADDR="$WSREP_SST_OPT_HOST" shift ;; '--local-port') - readonly WSREP_SST_OPT_LPORT="$2" + readonly WSREP_SST_OPT_LPORT=$(( $2 )) shift ;; '--parent') - readonly WSREP_SST_OPT_PARENT="$2" + readonly WSREP_SST_OPT_PARENT=$(( $2 )) shift ;; '--password') @@ -130,7 +268,7 @@ case "$1" in shift ;; '--port') - readonly WSREP_SST_OPT_PORT="$2" + readonly WSREP_SST_OPT_PORT=$(( $2 )) shift ;; '--role') @@ -149,14 +287,18 @@ case "$1" in readonly WSREP_SST_OPT_GTID="$2" shift ;; - '--binlog') - WSREP_SST_OPT_BINLOG="$2" + '--binlog'|'--log-bin') + readonly WSREP_SST_OPT_BINLOG="$2" + shift + ;; + '--binlog-index'|'--log-bin-index') + WSREP_SST_OPT_BINLOG_INDEX="$2" + shift + ;; + '--log-basename') + readonly WSREP_SST_OPT_LOG_BASENAME="$2" shift ;; - '--binlog-index') - WSREP_SST_OPT_BINLOG_INDEX="$2" - shift - ;; '--gtid-domain-id') readonly WSREP_SST_OPT_GTID_DOMAIN_ID="$2" shift @@ -164,208 +306,1221 @@ case "$1" in '--mysqld-args') original_cmd="" shift + cmd_tail=0 while [ $# -gt 0 ]; do - option=${1%%=*} - if [[ "$option" != "--defaults-file" && \ - "$option" != "--defaults-extra-file" && \ - "$option" != "--defaults-group-suffix" && \ - "$option" != "--port" && \ - "$option" != "--socket" ]]; then - value=${1#*=} - case "$option" in - '--innodb-data-home-dir') - if [ -z "$INNODB_DATA_HOME_DIR_ARG" ]; then - readonly INNODB_DATA_HOME_DIR_ARG="$value" - fi - ;; - '--innodb-log-group-home-dir') - if [ -z "$INNODB_LOG_GROUP_HOME_ARG" ]; then - readonly INNODB_LOG_GROUP_HOME_ARG="$value" - fi - ;; - '--innodb-undo-directory') - if [ -z "$INNODB_UNDO_DIR_ARG" ]; then - readonly INNODB_UNDO_DIR_ARG="$value" - fi - ;; - '--log-bin') - if [ -z "$LOG_BIN_ARG" ]; then - readonly LOG_BIN_ARG="$value" - fi - ;; - esac - if [ -z "$original_cmd" ]; then - original_cmd="$1" - else - original_cmd="$original_cmd $1" - fi + lname="${1#--}" + # "--" is interpreted as the end of the list of options: + if [ -z "$lname" ]; then + shift + if [ $# -gt 0 ]; then + # copy "--" to the output string: + original_cmd="$original_cmd --" + # All other arguments must be copied unchanged: + while [ $# -gt 0 ]; do + original_cmd="$original_cmd '$1'" + shift + done + fi + break + fi + # Make sure the argument does not start with "--", otherwise it + # is a long option, which is processed after this "if": + if [ "$lname" = "$1" ]; then + # Check if the argument is the short option or the short + # options list, starting with "-": + options="${1#-}" + if [ "$options" != "$1" -a -n "$options" ]; then + slist="" + while [ -n "$options" ]; do + # Let's separate the first character as the current + # option name: + if [ -n "$BASH_VERSION" ]; then + option="${options:0:1}" + else + # If it's not bash, then we need to use slow + # external utilities: + option=$(echo "$options" | cut -c1) + fi + # And the subsequent characters consider option value: + value="" + if [ ${#options} -gt 0 ]; then + value="${options#?}" + fi + # Check for options without argument: + if [ "$option" != '?' -a \ + "$option" != 'a' -a \ + "$option" != 's' -a \ + "$option" != 'v' ] + then + # If the option value is absent, then check + # the following argument: + if [ -z "$value" -a $# -gt 1 ]; then + # if the next argument does not start with + # the "-" character, then next argument is + # the current option value: + if [ "${2#-}" = "$2" ]; then + shift + value="$1" + elif [ "$2" = '--' ]; then + shift + if [ $# -gt 1 ]; then + cmd_tail=1 + shift + value="$1" + fi + fi + fi + if [ "$option" = 'h' ]; then + if [ -z "$WSREP_SST_OPT_DATA" ]; then + MYSQLD_OPT_DATADIR=$(trim_dir "$value") + fi + elif [ "$option" != 'u' -a \ + "$option" != 'P' ] + then + if [ $cmd_tail -ne 0 ]; then + option="$option --" + fi + if [ -z "$value" ]; then + slist="$slist$option" + elif [ -z "$slist" ]; then + slist="$option '$value'" + else + slist="$slist -$option '$value'" + fi + break + fi + if [ $cmd_tail -ne 0 ]; then + if [ -n "$slist" ]; then + slist="$slist --" + else + slist='-' + fi + fi + break + else + slist="$slist$option" + fi + options="$value" + done + if [ -n "$slist" ]; then + original_cmd="$original_cmd -$slist" + fi + elif [ -z "$options" ]; then + # We found an minus sign without any characters after it: + original_cmd="$original_cmd -" + else + # We found a value that does not start with a minus - + # it is a positional argument or the value of previous + # option. Copy it to output string (as is): + original_cmd="$original_cmd '$1'" + fi + shift + if [ $cmd_tail -ne 0 ]; then + # All other arguments must be copied unchanged: + while [ $# -gt 0 ]; do + original_cmd="$original_cmd '$1'" + shift + done + break + fi + continue + fi + # Now we are sure that we are working with an option + # that has a "long" name, so remove all characters after + # the first equal sign: + option="${1%%=*}" + # If the option name contains underscores, then replace + # them to minuses: + if [ "${option#*_}" != "$option" ]; then + option=$(to_minuses "$option") + fi + # The "--loose-" prefix should not affect the recognition + # of the option name: + if [ "${option#--loose-}" != "$option" ]; then + option="--${option#--loose-}" + fi + # Some options just need to be removed from the list: + if [ "$option" != '--defaults-file' -a \ + "$option" != '--defaults-extra-file' -a \ + "$option" != '--defaults-group-suffix' -a \ + "$option" != '--user' -a \ + "$option" != '--port' -a \ + "$option" != '--socket' ]; then + value="${1#*=}" + if [ "$value" = "$1" ]; then + value="" + fi + # Let's fill in the variables containing important paths + # that might not have been passed through explicit parameters + # (+ removing the trailing slash in these paths). Many of these + # options are processed internally within scripts or passed + # explicitly to other programs, so we need to remove them + # from mysqld's argument list: + skip_mysqld_arg=0 + case "$option" in + '--innodb-data-home-dir') + if [ -z "$INNODB_DATA_HOME_DIR" ]; then + MYSQLD_OPT_INNODB_DATA_HOME_DIR=$(trim_dir "$value") + fi + skip_mysqld_arg=1 + ;; + '--innodb-log-group-home-dir') + if [ -z "$INNODB_LOG_GROUP_HOME" ]; then + MYSQLD_OPT_INNODB_LOG_GROUP_HOME=$(trim_dir "$value") + fi + skip_mysqld_arg=1 + ;; + '--innodb-undo-directory') + if [ -z "$INNODB_UNDO_DIR" ]; then + MYSQLD_OPT_INNODB_UNDO_DIR=$(trim_dir "$value") + fi + skip_mysqld_arg=1 + ;; + '--innodb-buffer-pool-filename') + if [ -z "$INNODB_BUFFER_POOL" ]; then + MYSQLD_OPT_INNODB_BUFFER_POOL=$(trim_string "$value") + fi + skip_mysqld_arg=1 + ;; + '--innodb-force-recovery') + if [ -n "$value" -a "$value" != "0" ]; then + INNODB_FORCE_RECOVERY=$(trim_string "$value") + fi + skip_mysqld_arg=1 + ;; + '--log-bin') + if [ -z "$WSREP_SST_OPT_BINLOG" ]; then + MYSQLD_OPT_LOG_BIN=$(trim_string "$value") + fi + skip_mysqld_arg=1 + ;; + '--log-bin-index') + if [ -z "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + MYSQLD_OPT_LOG_BIN_INDEX=$(trim_string "$value") + fi + skip_mysqld_arg=1 + ;; + '--log-basename') + if [ -z "$WSREP_SST_OPT_LOG_BASENAME" ]; then + MYSQLD_OPT_LOG_BASENAME=$(trim_string "$value") + fi + skip_mysqld_arg=1 + ;; + '--datadir') + if [ -z "$WSREP_SST_OPT_DATA" ]; then + MYSQLD_OPT_DATADIR=$(trim_dir "$value") + fi + skip_mysqld_arg=1 + ;; + esac + if [ $skip_mysqld_arg -eq 0 ]; then + original_cmd="$original_cmd '$1'" + fi fi shift done - readonly WSREP_SST_OPT_MYSQLD="$original_cmd" + WSREP_SST_OPT_MYSQLD="${original_cmd# *}" break ;; - *) # must be command - # usage - # exit 1 - ;; + *) # Must be command usage + # exit 1 + ;; esac shift done -readonly WSREP_SST_OPT_BYPASS -readonly WSREP_SST_OPT_BINLOG -readonly WSREP_SST_OPT_BINLOG_INDEX - -if [ -n "${WSREP_SST_OPT_ADDR_PORT:-}" ]; then - if [ -n "${WSREP_SST_OPT_PORT:-}" ]; then - if [ "$WSREP_SST_OPT_PORT" != "$WSREP_SST_OPT_ADDR_PORT" ]; then - echo "WSREP_SST: [ERROR] port in --port=$WSREP_SST_OPT_PORT differs from port in --address=$WSREP_SST_OPT_ADDR" >&2 - exit 2 - fi - else - readonly WSREP_SST_OPT_PORT="$WSREP_SST_OPT_ADDR_PORT" - fi + +WSREP_TRANSFER_TYPE='SST' +[ $WSREP_SST_OPT_BYPASS -ne 0 ] && readonly WSREP_TRANSFER_TYPE='IST' +# Let's take the name of the current script as a base, +# removing the directory, extension and "wsrep_sst_" prefix: +WSREP_METHOD="${0##*/}" +WSREP_METHOD="${WSREP_METHOD%.*}" +readonly WSREP_METHOD="${WSREP_METHOD#wsrep_sst_}" +if [ -n "${WSREP_SST_OPT_ROLE+x}" ]; then + if [ "$WSREP_SST_OPT_ROLE" != 'donor' -a \ + "$WSREP_SST_OPT_ROLE" != 'joiner' ] + then + wsrep_log_error "Unrecognized role: '$WSREP_SST_OPT_ROLE'" + exit 22 # EINVAL + fi +else + readonly WSREP_SST_OPT_ROLE='donor' +fi + +readonly WSREP_SST_OPT_PROGRESS + +# The same argument can be present on the command line several +# times, in this case we must take its last value: +if [ -n "${MYSQLD_OPT_INNODB_DATA_HOME_DIR:-}" -a \ + -z "$INNODB_DATA_HOME_DIR" ]; then + readonly INNODB_DATA_HOME_DIR="$MYSQLD_OPT_INNODB_DATA_HOME_DIR" +fi +if [ -n "${MYSQLD_OPT_INNODB_LOG_GROUP_HOME:-}" -a \ + -z "$INNODB_LOG_GROUP_HOME" ]; then + readonly INNODB_LOG_GROUP_HOME="$MYSQLD_OPT_INNODB_LOG_GROUP_HOME" +fi +if [ -n "${MYSQLD_OPT_INNODB_UNDO_DIR:-}" -a \ + -z "$INNODB_UNDO_DIR" ]; then + readonly INNODB_UNDO_DIR="$MYSQLD_OPT_INNODB_UNDO_DIR" +fi +if [ -n "${MYSQLD_OPT_INNODB_BUFFER_POOL:-}" -a \ + -z "$INNODB_BUFFER_POOL" ]; then + readonly INNODB_BUFFER_POOL="$MYSQLD_OPT_INNODB_BUFFER_POOL" +fi +if [ -n "${MYSQLD_OPT_LOG_BIN:-}" -a \ + -z "$WSREP_SST_OPT_BINLOG" ]; then + readonly WSREP_SST_OPT_BINLOG="$MYSQLD_OPT_LOG_BIN" +fi +if [ -n "${MYSQLD_OPT_LOG_BIN_INDEX:-}" -a \ + -z "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + WSREP_SST_OPT_BINLOG_INDEX="$MYSQLD_OPT_LOG_BIN_INDEX" +fi +if [ -n "${MYSQLD_OPT_DATADIR:-}" -a \ + -z "$WSREP_SST_OPT_DATA" ]; then + readonly WSREP_SST_OPT_DATA="$MYSQLD_OPT_DATADIR" +fi +if [ -n "${MYSQLD_OPT_LOG_BASENAME:-}" -a \ + -z "$WSREP_SST_OPT_LOG_BASENAME" ]; then + readonly WSREP_SST_OPT_LOG_BASENAME="$MYSQLD_OPT_LOG_BASENAME" +fi + +# If the --log-bin option is present without a value, then +# set WSREP_SST_OPT_BINLOG value using other arguments: +if [ -z "$WSREP_SST_OPT_BINLOG" -a -n "${MYSQLD_OPT_LOG_BIN+x}" ]; then + if [ -n "$WSREP_SST_OPT_LOG_BASENAME" ]; then + # If the WSREP_SST_OPT_BINLOG variable is not set, but + # --log-basename is present among the arguments to mysqld, + # then set WSREP_SST_OPT_BINLOG equal to the base name + # with the "-bin" suffix: + readonly WSREP_SST_OPT_BINLOG="$WSREP_SST_OPT_LOG_BASENAME-bin" + else + # Take the default name: + readonly WSREP_SST_OPT_BINLOG='mysql-bin' + fi +fi + +# Reconstructing the command line arguments that control the innodb +# and binlog options: +if [ -n "$WSREP_SST_OPT_LOG_BASENAME" ]; then + if [ -n "$WSREP_SST_OPT_MYSQLD" ]; then + WSREP_SST_OPT_MYSQLD="--log-basename='$WSREP_SST_OPT_LOG_BASENAME' $WSREP_SST_OPT_MYSQLD" + else + WSREP_SST_OPT_MYSQLD="--log-basename='$WSREP_SST_OPT_LOG_BASENAME'" + fi +fi +if [ -n "$INNODB_DATA_HOME_DIR" ]; then + INNOEXTRA="$INNOEXTRA --innodb-data-home-dir='$INNODB_DATA_HOME_DIR'" +fi +if [ -n "$INNODB_LOG_GROUP_HOME" ]; then + INNOEXTRA="$INNOEXTRA --innodb-log-group-home-dir='$INNODB_LOG_GROUP_HOME'" +fi +if [ -n "$INNODB_UNDO_DIR" ]; then + INNOEXTRA="$INNOEXTRA --innodb-undo-directory='$INNODB_UNDO_DIR'" +fi +if [ -n "$INNODB_BUFFER_POOL" ]; then + INNOEXTRA="$INNOEXTRA --innodb-buffer-pool-filename='$INNODB_BUFFER_POOL'" +fi +if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + INNOEXTRA="$INNOEXTRA --log-bin='$WSREP_SST_OPT_BINLOG'" + if [ -n "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + if [ -n "$WSREP_SST_OPT_MYSQLD" ]; then + WSREP_SST_OPT_MYSQLD="--log-bin-index='$WSREP_SST_OPT_BINLOG_INDEX' $WSREP_SST_OPT_MYSQLD" + else + WSREP_SST_OPT_MYSQLD="--log-bin-index='$WSREP_SST_OPT_BINLOG_INDEX'" + fi + fi +fi + +readonly INNODB_FORCE_RECOVERY +readonly WSREP_SST_OPT_MYSQLD + +get_binlog() +{ + # if no command line argument and WSREP_SST_OPT_BINLOG is not set, + # try to get it from my.cnf: + if [ -z "$WSREP_SST_OPT_BINLOG" ]; then + WSREP_SST_OPT_BINLOG=$(parse_cnf '--mysqld' 'log-bin') + fi + # if no command line argument and WSREP_SST_OPT_BINLOG_INDEX is not set, + # try to get it from my.cnf: + if [ -z "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + WSREP_SST_OPT_BINLOG_INDEX=$(parse_cnf '--mysqld' 'log-bin-index') + fi + # if no command line argument and WSREP_SST_OPT_LOG_BASENAME is not set, + # then try to get it from my.cnf: + if [ -z "$WSREP_SST_OPT_LOG_BASENAME" ]; then + WSREP_SST_OPT_LOG_BASENAME=$(parse_cnf '--mysqld' 'log-basename') + fi + if [ -z "$WSREP_SST_OPT_BINLOG" ]; then + # If the log-bin option is specified without a parameter, + # then we need to build the name of the index file according + # to the rules described in the server documentation: + if [ $(in_config '--mysqld' 'log-bin') -ne 0 ]; then + if [ -n "$WSREP_SST_OPT_LOG_BASENAME" ]; then + # If the WSREP_SST_OPT_BINLOG variable is not set, but + # --log-basename is present among the arguments of mysqld, + # then set WSREP_SST_OPT_BINLOG equal to the base name + # with the "-bin" suffix: + readonly WSREP_SST_OPT_BINLOG="$WSREP_SST_OPT_LOG_BASENAME-bin" + else + # Take the default name: + readonly WSREP_SST_OPT_BINLOG='mysql-bin' + fi + fi + fi + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + # If the name of the index file is not specified, then we will build + # it according to the specifications for the server: + if [ -z "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + if [ -n "$WSREP_SST_OPT_LOG_BASENAME" ]; then + # If the WSREP_SST_OPT_BINLOG_INDEX variable is not set, but + # --log-basename is present among the arguments of mysqld, + # then set WSREP_SST_OPT_BINLOG_INDEX equal to the base name + # with the "-bin" suffix: + readonly WSREP_SST_OPT_BINLOG_INDEX="$WSREP_SST_OPT_LOG_BASENAME-bin.index" + else + # Use the default name (note that base of this name + # is already defined above): + readonly WSREP_SST_OPT_BINLOG_INDEX="$WSREP_SST_OPT_BINLOG.index" + fi + else + # Remove all directories from the index file path: + local filename="${WSREP_SST_OPT_BINLOG_INDEX##*/}" + # Check if the index file name contains the extension: + if [ "${filename%.*}" = "$filename" ]; then + # Let's add the default extension (".index"): + readonly WSREP_SST_OPT_BINLOG_INDEX="$WSREP_SST_OPT_BINLOG_INDEX.index" + else + readonly WSREP_SST_OPT_BINLOG_INDEX + fi + fi + fi +} + +# Check the presence of the port value and, if necessary, transfer +# the port number from the address to the WSREP_SST_OPT_PORT variable +# or vice versa, and also, if necessary, substitute the missing port +# value into the address value: +if [ -n "$WSREP_SST_OPT_ADDR_PORT" ]; then + if [ -n "$WSREP_SST_OPT_PORT" ]; then + if [ "$WSREP_SST_OPT_PORT" != "$WSREP_SST_OPT_ADDR_PORT" ]; then + echo "WSREP_SST: [ERROR] port in --port=$WSREP_SST_OPT_PORT" \ + "differs from port in --address=$WSREP_SST_OPT_ADDR" >&2 + exit 2 + fi + else + # If the address contains a port number, assign it to + # the corresponding variable: + readonly WSREP_SST_OPT_PORT="$WSREP_SST_OPT_ADDR_PORT" + fi +else + # If the port is missing, take the default port: + if [ -z "$WSREP_SST_OPT_PORT" ]; then + readonly WSREP_SST_OPT_PORT=4444 + fi + WSREP_SST_OPT_ADDR_PORT="$WSREP_SST_OPT_PORT" fi -# try to use my_print_defaults, mysql and mysqldump that come with the sources -# (for MTR suite) -SCRIPTS_DIR="$(cd $(dirname "$0"); pwd -P)" +# Let's construct a new value for the address with the port: +sst_path="${WSREP_SST_OPT_PATH:+/}$WSREP_SST_OPT_PATH" +WSREP_SST_OPT_ADDR="$WSREP_SST_OPT_HOST:$WSREP_SST_OPT_PORT$sst_path" + +readonly WSREP_SST_OPT_ADDR +readonly WSREP_SST_OPT_ADDR_PORT + +commandex() +{ + if [ -n "$BASH_VERSION" ]; then + command -v "$1" || : + elif [ -x "$1" ]; then + echo "$1" + else + which "$1" || : + fi +} + +# try to use my_print_defaults, mysql and mysqldump that come +# with the sources (for MTR suite): +script_binary=$(dirname "$0") +SCRIPTS_DIR=$(cd "$script_binary"; pwd) EXTRA_DIR="$SCRIPTS_DIR/../extra" CLIENT_DIR="$SCRIPTS_DIR/../client" if [ -x "$CLIENT_DIR/mysql" ]; then MYSQL_CLIENT="$CLIENT_DIR/mysql" else - MYSQL_CLIENT=mysql + MYSQL_CLIENT=$(commandex 'mysql') fi if [ -x "$CLIENT_DIR/mysqldump" ]; then MYSQLDUMP="$CLIENT_DIR/mysqldump" else - MYSQLDUMP=mysqldump + MYSQLDUMP=$(commandex 'mysqldump') fi +wsrep_log() +{ + # echo everything to stderr so that it gets into common error log + # deliberately made to look different from the rest of the log + local readonly tst=$(date "+%Y%m%d %H:%M:%S.%N" | cut -b -21) + echo "WSREP_SST: $* ($tst)" >&2 +} + +wsrep_log_error() +{ + wsrep_log "[ERROR] $*" +} + +wsrep_log_warning() +{ + wsrep_log "[WARNING] $*" +} + +wsrep_log_info() +{ + wsrep_log "[INFO] $*" +} + if [ -x "$SCRIPTS_DIR/my_print_defaults" ]; then MY_PRINT_DEFAULTS="$SCRIPTS_DIR/my_print_defaults" elif [ -x "$EXTRA_DIR/my_print_defaults" ]; then MY_PRINT_DEFAULTS="$EXTRA_DIR/my_print_defaults" else - MY_PRINT_DEFAULTS=my_print_defaults + MY_PRINT_DEFAULTS=$(commandex 'my_print_defaults') + if [ -z "$MY_PRINT_DEFAULTS" ]; then + wsrep_log_error "my_print_defaults not found in path" + exit 2 + fi fi +readonly MY_PRINT_DEFAULTS + +wsrep_defaults="$WSREP_SST_OPT_DEFAULTS" +wsrep_defaults="$wsrep_defaults${WSREP_SST_OPT_EXTRA_DEFAULTS:+ }$WSREP_SST_OPT_EXTRA_DEFAULTS" +wsrep_defaults="$wsrep_defaults${WSREP_SST_OPT_SUFFIX_DEFAULT:+ }$WSREP_SST_OPT_SUFFIX_DEFAULT" + +readonly WSREP_SST_OPT_CONF="${wsrep_defaults:+ }$wsrep_defaults" + wsrep_defaults="$WSREP_SST_OPT_DEFAULT" -if [ -n "$wsrep_defaults" ]; then - wsrep_defaults="$wsrep_defaults " -fi -wsrep_defaults="$wsrep_defaults$WSREP_SST_OPT_EXTRA_DEFAULT" -if [ -n "$wsrep_defaults" ]; then - wsrep_defaults="$wsrep_defaults " -fi -readonly WSREP_SST_OPT_CONF="$wsrep_defaults$WSREP_SST_OPT_SUFFIX_DEFAULT" -readonly MY_PRINT_DEFAULTS="$MY_PRINT_DEFAULTS $WSREP_SST_OPT_CONF" +wsrep_defaults="$wsrep_defaults${WSREP_SST_OPT_EXTRA_DEFAULT:+ }$WSREP_SST_OPT_EXTRA_DEFAULT" +wsrep_defaults="$wsrep_defaults${WSREP_SST_OPT_SUFFIX_DEFAULT:+ }$WSREP_SST_OPT_SUFFIX_DEFAULT" -wsrep_auth_not_set() +readonly WSREP_SST_OPT_CONF_UNQUOTED="${wsrep_defaults:+ }$wsrep_defaults" + +# +# User can specify mariabackup specific settings that will be used during sst +# process like encryption, etc. Parse such configuration option. +# +# 1st parameter: group (config file section like sst) or +# my_print_defaults argument (like --mysqld) +# 2nd parameter: var : name of the variable in the section, e.g. server-id +# 3rd parameter: default value for the parameter +# +parse_cnf() { - [ -z "$WSREP_SST_OPT_AUTH" -o "$WSREP_SST_OPT_AUTH" = "(null)" ] + local groups="$1" + local var="$2" + local reval="" + + # normalize the variable names specified in the .cnf file + # (user can use '_' or '-', for example, log-bin or log_bin), + # then search for the last instance of the desired variable + # and finally get the value of that variable (if the variable + # was specified several times - we use only its last instance): + + local pattern='BEGIN {OFS=FS="="} {sub(/^--loose/,"-",$0); gsub(/_/,"-",$1); if ($1=="--'"$var"'") lastval=substr($0,length($1)+2)} END {print lastval}' + + while [ -n "$groups" ]; do + # Remove the largest suffix starting with the '|' character: + local group="${groups%%\|*}" + # Remove the remainder (the group name) from the rest + # of the groups list (as if it were a prefix): + if [ "$group" != "$groups" ]; then + groups="${groups#*\|}" + else + groups="" + fi + # If the group name is the same as the "mysqld" without "--" prefix, + # then try to use it together with the group suffix: + if [ "$group" = 'mysqld' -a -n "$WSREP_SST_OPT_SUFFIX_VALUE" ]; then + reval=$("$MY_PRINT_DEFAULTS" \ + ${WSREP_SST_OPT_DEFAULT:+"$WSREP_SST_OPT_DEFAULT"} \ + ${WSREP_SST_OPT_EXTRA_DEFAULT:+"$WSREP_SST_OPT_EXTRA_DEFAULT"} \ + ${WSREP_SST_OPT_SUFFIX_DEFAULT:+"$WSREP_SST_OPT_SUFFIX_DEFAULT"} \ + "mysqld$WSREP_SST_OPT_SUFFIX_VALUE" | awk "$pattern") + if [ -n "$reval" ]; then + break + fi + fi + # Let's try to use the group name as it is: + reval=$("$MY_PRINT_DEFAULTS" \ + ${WSREP_SST_OPT_DEFAULT:+"$WSREP_SST_OPT_DEFAULT"} \ + ${WSREP_SST_OPT_EXTRA_DEFAULT:+"$WSREP_SST_OPT_EXTRA_DEFAULT"} \ + ${WSREP_SST_OPT_SUFFIX_DEFAULT:+"$WSREP_SST_OPT_SUFFIX_DEFAULT"} \ + "$group" | awk "$pattern") + if [ -n "$reval" ]; then + break + fi + done + + # Use default if we haven't found a value: + [ -z "$reval" ] && reval="${3:-}" + + # Truncate spaces: + [ -n "$reval" ] && reval=$(trim_string "$reval") + + if [ -n "$BASH_VERSION" ]; then + printf '%s' "$reval" + else + echo "$reval" + fi } -# State Snapshot Transfer authentication password was displayed in the ps output. Bug fixed #1200727. -if $MY_PRINT_DEFAULTS sst | grep -q "wsrep_sst_auth"; then - if wsrep_auth_not_set; then - WSREP_SST_OPT_AUTH=$($MY_PRINT_DEFAULTS sst | grep -- "--wsrep_sst_auth" | cut -d= -f2) +# +# This function simply checks for the presence of the parameter +# in the config file, but does not return its value. It returns "1" +# (true) even if the parameter is present in the configuration file +# without a value: +# +in_config() +{ + local groups="$1" + local var="$2" + local found=0 + + # normalize the variable names specified in the .cnf file + # (user can use '_' or '-', for example, log-bin or log_bin), + # then search for the last instance(s) of the desired variable: + + local pattern='BEGIN {OFS=FS="="; found=0} {sub(/^--loose/,"-",$0); gsub(/_/,"-",$1); if ($1=="--'"$var"'") found=1} END {print found}' + + while [ -n "$groups" ]; do + # Remove the largest suffix starting with the '|' character: + local group="${groups%%\|*}" + # Remove the remainder (the group name) from the rest + # of the groups list (as if it were a prefix): + if [ "$group" != "$groups" ]; then + groups="${groups#*\|}" + else + groups="" + fi + # If the group name is the same as the "mysqld" without "--" prefix, + # then try to use it together with the group suffix: + if [ "$group" = 'mysqld' -a -n "$WSREP_SST_OPT_SUFFIX_VALUE" ]; then + found=$("$MY_PRINT_DEFAULTS" \ + ${WSREP_SST_OPT_DEFAULT:+"$WSREP_SST_OPT_DEFAULT"} \ + ${WSREP_SST_OPT_EXTRA_DEFAULT:+"$WSREP_SST_OPT_EXTRA_DEFAULT"} \ + ${WSREP_SST_OPT_SUFFIX_DEFAULT:+"$WSREP_SST_OPT_SUFFIX_DEFAULT"} \ + "mysqld$WSREP_SST_OPT_SUFFIX_VALUE" | awk "$pattern") + if [ $found -ne 0 ]; then + break + fi + fi + # Let's try to use the group name as it is: + found=$($MY_PRINT_DEFAULTS \ + ${WSREP_SST_OPT_DEFAULT:+"$WSREP_SST_OPT_DEFAULT"} \ + ${WSREP_SST_OPT_EXTRA_DEFAULT:+"$WSREP_SST_OPT_EXTRA_DEFAULT"} \ + ${WSREP_SST_OPT_SUFFIX_DEFAULT:+"$WSREP_SST_OPT_SUFFIX_DEFAULT"} \ + "$group" | awk "$pattern") + if [ $found -ne 0 ]; then + break + fi + done + if [ -n "$BASH_VERSION" ]; then + printf '%s' $found + else + echo $found fi +} + +wsrep_auth_not_set() +{ + [ -z "$WSREP_SST_OPT_AUTH" ] +} + +# Get rid of incorrect values resulting from substitution +# in programs external to the script: +if [ "$WSREP_SST_OPT_USER" = '(null)' ]; then + WSREP_SST_OPT_USER="" +fi +if [ "$WSREP_SST_OPT_PSWD" = '(null)' ]; then + WSREP_SST_OPT_PSWD="" +fi +if [ "$WSREP_SST_OPT_AUTH" = '(null)' ]; then + WSREP_SST_OPT_AUTH="" fi -readonly WSREP_SST_OPT_AUTH -# Splitting AUTH into potential user:password pair -if ! wsrep_auth_not_set -then - WSREP_SST_OPT_USER="${WSREP_SST_OPT_AUTH%%:*}" - WSREP_SST_OPT_PSWD="${WSREP_SST_OPT_AUTH##*:}" +# Let's read the value of the authentication string from the +# configuration file so that it does not go to the command line +# and does not appear in the ps output: +if wsrep_auth_not_set; then + WSREP_SST_OPT_AUTH=$(parse_cnf 'sst' 'wsrep-sst-auth') fi + +# Splitting WSREP_SST_OPT_AUTH as "user:password" pair: +if ! wsrep_auth_not_set; then + # Extract username as shortest prefix up to first ':' character: + WSREP_SST_OPT_AUTH_USER="${WSREP_SST_OPT_AUTH%%:*}" + if [ -z "$WSREP_SST_OPT_USER" ]; then + # if the username is not in the command line arguments, + # set the username and password using WSREP_SST_OPT_AUTH + # from the environment: + WSREP_SST_OPT_USER="$WSREP_SST_OPT_AUTH_USER" + WSREP_SST_OPT_PSWD="${WSREP_SST_OPT_AUTH#*:}" + elif [ "$WSREP_SST_OPT_USER" = "$WSREP_SST_OPT_AUTH_USER" ]; then + # If the username in the command line arguments and in + # the environment variable are the same, set the password + # if it was not specified in the command line: + if [ -z "$WSREP_SST_OPT_PSWD" ]; then + WSREP_SST_OPT_PSWD="${WSREP_SST_OPT_AUTH#*:}" + fi + else + # The username is passed through the command line and does + # not match the username in the environment variable - ignore + # the environment and rebuild the authentication parameters: + WSREP_SST_OPT_AUTH="$WSREP_SST_OPT_USER:$WSREP_SST_OPT_PSWD" + fi +fi + readonly WSREP_SST_OPT_USER readonly WSREP_SST_OPT_PSWD +readonly WSREP_SST_OPT_AUTH + +if [ -n "$WSREP_SST_OPT_REMOTE_AUTH" ]; then + # Split auth string at the last ':' + readonly WSREP_SST_OPT_REMOTE_USER="${WSREP_SST_OPT_REMOTE_AUTH%%:*}" + readonly WSREP_SST_OPT_REMOTE_PSWD="${WSREP_SST_OPT_REMOTE_AUTH#*:}" +else + readonly WSREP_SST_OPT_REMOTE_USER= + readonly WSREP_SST_OPT_REMOTE_PSWD= +fi + +readonly WSREP_SST_OPT_REMOTE_AUTH -if [ -n "${WSREP_SST_OPT_DATA:-}" ] -then +if [ -n "$WSREP_SST_OPT_DATA" ]; then SST_PROGRESS_FILE="$WSREP_SST_OPT_DATA/sst_in_progress" else SST_PROGRESS_FILE="" fi -wsrep_log() +wsrep_cleanup_progress_file() { - # echo everything to stderr so that it gets into common error log - # deliberately made to look different from the rest of the log - local readonly tst="$(date +%Y%m%d\ %H:%M:%S.%N | cut -b -21)" - echo "WSREP_SST: $* ($tst)" >&2 + if [ -n "$SST_PROGRESS_FILE" -a -f "$SST_PROGRESS_FILE" ]; then + rm -f "$SST_PROGRESS_FILE" 2>/dev/null || : + fi } -wsrep_log_error() +wsrep_check_program() { - wsrep_log "[ERROR] $*" + local prog="$1" + local cmd=$(commandex "$prog") + if [ -z "$cmd" ]; then + echo "'$prog' not found in PATH" + return 2 # no such file or directory + fi } -wsrep_log_warning() +wsrep_check_programs() { - wsrep_log "[WARNING] $*" + local ret=0 + while [ $# -gt 0 ]; do + wsrep_check_program "$1" || ret=$? + shift + done + return $ret } -wsrep_log_info() +wsrep_check_datadir() { - wsrep_log "[INFO] $*" + if [ -z "$WSREP_SST_OPT_DATA" ]; then + wsrep_log_error \ + "The '--datadir' parameter must be passed to the SST script" + exit 2 + fi } -wsrep_cleanup_progress_file() +get_openssl() { - [ -n "${SST_PROGRESS_FILE:-}" ] && rm -f "$SST_PROGRESS_FILE" 2>/dev/null || true + # If the OPENSSL_BINARY variable is already defined, just return: + if [ -n "${OPENSSL_BINARY+x}" ]; then + return + fi + # Let's look for openssl: + OPENSSL_BINARY=$(commandex 'openssl') + if [ -z "$OPENSSL_BINARY" ]; then + OPENSSL_BINARY='/usr/bin/openssl' + if [ ! -x "$OPENSSL_BINARY" ]; then + OPENSSL_BINARY="" + fi + fi + readonly OPENSSL_BINARY } -wsrep_check_program() +# +# Generate a string equivalent to 16 random bytes +# +wsrep_gen_secret() +{ + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + "$OPENSSL_BINARY" rand -hex 16 + elif [ -n "$BASH_VERSION" ]; then + printf '%04x%04x%04x%04x%04x%04x%04x%04x' \ + $RANDOM $RANDOM $RANDOM $RANDOM \ + $RANDOM $RANDOM $RANDOM $RANDOM + elif [ -n "$(commandex cksum)" -a \ + -n "$(commandex printf)" ] + then + printf '%08x%08x%08x%08x' \ + $(head -8 /dev/urandom | cksum | cut -d ' ' -f1) \ + $(head -8 /dev/urandom | cksum | cut -d ' ' -f1) \ + $(head -8 /dev/urandom | cksum | cut -d ' ' -f1) \ + $(head -8 /dev/urandom | cksum | cut -d ' ' -f1) + else + wsrep_log_error "Unable to generate 16-byte secret" + exit 22 + fi +} + +# +# Checking if the address passed to us is local. +# If the second parameter is nonzero, then this function +# does not check for matches with local domain names: +# +is_local_ip() +{ + # Rapid recognition of the most common cases: + [ "$1" = '127.0.0.1' -o \ + "$1" = '127.0.0.2' -o \ + "$1" = 'localhost' -o \ + "$1" = '::1' ] && return 0 + # If the address starts with "127." this is probably a local + # address, but we need to clarify what follows this prefix: + if [ "${1#127.}" != "$1" ]; then + # All 127.0.0.0/8 addresses are local: + if echo "$1" | grep -q -E '^127\.[0-9]+\.[0-9]+\.[0-9]+$'; then + return 0 + fi + fi + # If the second parameter is nonzero, then we will skip + # the domain name check: + if [ "${2:-0}" -eq 0 ]; then + # We consider all the names of a given host to be local addresses: + [ "$1" = "$(hostname -s)" -o \ + "$1" = "$(hostname -f)" -o \ + "$1" = "$(hostname -d)" ] && return 0 + fi + # If the address contains anything other than digits + # and separators, it is not a local address: + [ "${1#*[!0-9.]}" != "$1" ] && \ + [ "${1#*[!0-9A-Fa-f:\[\]]}" != "$1" ] && return 1 + # Now let's check if the given address is assigned to + # one of the network cards: + local ip_util=$(commandex 'ip') + if [ -n "$ip_util" ]; then + # ip address show ouput format is " inet[6] <address>/<mask>": + "$ip_util" address show \ + | grep -o -E '^[[:space:]]*inet.?[[:space:]]+[^[:space:]]+/' \ + | grep -qw -F -- "$1/" && return 0 + else + local ifconfig_util=$(commandex 'ifconfig') + if [ -n "$ifconfig_util" ]; then + # ifconfig output format is " inet[6] <address> ...": + "$ifconfig_util" \ + | grep -o -E '^[[:space:]]*inet.?[[:space:]]+[^[:space:]]+' \ + | grep -qw -F -- "$1" && return 0 + fi + fi + return 1 +} + +check_sockets_utils() { - local prog=$1 + lsof_available=0 + sockstat_available=0 + ss_available=0 + + [ -n "$(commandex lsof)" ] && lsof_available=1 + [ -n "$(commandex sockstat)" ] && sockstat_available=1 + [ -n "$(commandex ss)" ] && ss_available=1 - if ! command -v $prog >/dev/null + if [ $lsof_available -eq 0 -a \ + $sockstat_available -eq 0 -a \ + $ss_available -eq 0 ] then - echo "'$prog' not found in PATH" - exit 2 # ENOENT no such file or directory + wsrep_log_error "Neither lsof, nor sockstat or ss tool was found in" \ + "the PATH. Make sure you have it installed." + exit 2 # ENOENT fi } -wsrep_check_programs() +# +# Check if the port is in the "listen" state. +# The first parameter is the PID of the process that should +# listen on the port - if it is not known, you can specify +# an empty string or zero. +# The second parameter is the port number. +# The third parameter is a list of the names of utilities +# (via "|") that can listen on this port during the state +# transfer. +# +check_port() { - local ret=0 + local pid="${1:-0}" + local port="$2" + local utils="$3" - while [ $# -gt 0 ] - do - wsrep_check_program $1 - shift - done + [ $pid -le 0 ] && pid='[0-9]+' + + local rc=1 + + if [ $lsof_available -ne 0 ]; then + lsof -Pnl -i ":$port" 2>/dev/null | \ + grep -q -E "^($utils)[^[:space:]]*[[:space:]]+$pid[[:space:]].*\\(LISTEN\\)" && rc=0 + elif [ $sockstat_available -ne 0 ]; then + local opts='-p' + if [ "$OS" = 'FreeBSD' ]; then + # sockstat on FreeBSD requires the "-s" option + # to display the connection state: + opts='-sp' + fi + sockstat "$opts" "$port" 2>/dev/null | \ + grep -q -E "[[:space:]]+($utils)[^[:space:]]*[[:space:]]+$pid[[:space:]].*[[:space:]]LISTEN" && rc=0 + elif [ $ss_available -ne 0 ]; then + ss -nlpH "( sport = :$port )" 2>/dev/null | \ + grep -q -E "users:\\(.*\\(\"($utils)[^[:space:]]*\"[^)]*,pid=$pid(,[^)]*)?\\)" && rc=0 + else + wsrep_log_error "Unknown sockets utility" + exit 2 # ENOENT + fi + + return $rc } # -# user can specify mariabackup specific settings that will be used during sst -# process like encryption, etc..... -# parse such configuration option. (group for xb settings is [sst] in my.cnf +# If the ssl_dhparams variable is already set, uses that as a source +# of dh parameters for OpenSSL. Otherwise, looks for dhparams.pem in +# the datadir, and creates it there if it can't find the file. # -# 1st param: group (config file section like sst) or my_print_defaults argument (like --mysqld) -# 2nd param: var : name of the variable in the section, e.g. server-id -# 3rd param: - : default value for the param -parse_cnf() +check_for_dhparams() { - local group=$1 - local var=$2 - local reval="" + ssl_dhparams="$DATA/dhparams.pem" + get_openssl + if [ -n "$OPENSSL_BINARY" ]; then + wsrep_log_info \ + "Could not find dhparams file, creating $ssl_dhparams" + local bug=0 + local errmsg + errmsg=$("$OPENSSL_BINARY" \ + dhparam -out "$ssl_dhparams" -dsaparam 2048 2>&1) || bug=1 + if [ $bug -ne 0 ]; then + wsrep_log_info "run: \"$OPENSSL_BINARY\" dhparam"\ + "-out \"$ssl_dhparams\" -dsaparam 2048" + wsrep_log_info "output: $errmsg" + wsrep_log_error "******** ERROR *****************************************" + wsrep_log_error "* Could not create the dhparams.pem file with OpenSSL. *" + wsrep_log_error "********************************************************" + ssl_dhparams="" + fi + else + # Rollback: if openssl is not installed, then use + # the default parameters: + ssl_dhparams="" + fi +} + +# +# Verifies that the CA file verifies the certificate. +# Doing this here lets us generate better error messages. +# +# 1st param: path to the CA file. +# 2nd param: path to the certificate. +# +verify_ca_matches_cert() +{ + local cert="$1" + local ca="$2" + local cap="$3" + + local readable=1; [ ! -r "$cert" ] && readable=0 + [ -n "$ca" -a ! -r "$ca" ] && readable=0 + [ -n "$cap" -a ! -r "$cap" ] && readable=0 + + if [ $readable -eq 0 ]; then + wsrep_log_error \ + "Both PEM file and CA file (or path) must be readable" + exit 22 + fi + + # If the openssl utility is not installed, then + # we will not do this certificate check: + get_openssl + if [ -z "$OPENSSL_BINARY" ]; then + wsrep_log_info "openssl utility not found" + return + fi + + local not_match=0 + local errmsg + errmsg=$("$OPENSSL_BINARY" verify -verbose \ + ${ca:+ -CAfile} ${ca:+ "$ca"} \ + ${cap:+ -CApath} ${cap:+ "$cap"} \ + "$cert" 2>&1) || not_match=1 - # normalize the variable names specified in cnf file (user can use _ or - for example log-bin or log_bin) - # then search for needed variable - # finally get the variable value (if variables has been specified multiple time use the last value only) + if [ $not_match -eq 1 ]; then + wsrep_log_info "run: \"$OPENSSL_BINARY\" verify -verbose${ca:+ -CAfile \"$ca\"}${cap:+ -CApath \"$cap\"} \"$cert\"" + wsrep_log_info "output: $errmsg" + wsrep_log_error "******** FATAL ERROR ********************************************" + wsrep_log_error "* The certifcate and CA (certificate authority) do not match. *" + wsrep_log_error "* It does not appear that the certificate was issued by the CA. *" + wsrep_log_error "* Please check your certificate and CA files. *" + wsrep_log_error "*****************************************************************" + exit 22 + fi +} + +# +# Verifies that the certificate matches the private key. +# Doing this will save us having to wait for a timeout that would +# otherwise occur. +# +# 1st param: path to the certificate. +# 2nd param: path to the private key. +# +verify_cert_matches_key() +{ + local cert="$1" + local key="$2" - reval=$($MY_PRINT_DEFAULTS "${group}" | awk -v var="${var}" 'BEGIN { OFS=FS="=" } { gsub(/_/,"-",$1); if ( $1=="--"var) lastval=substr($0,length($1)+2) } END { print lastval}') + if [ ! -r "$key" -o ! -r "$cert" ]; then + wsrep_log_error "Both the certificate file and the key file" \ + "must be readable" + exit 22 + fi + + # If the openssl utility is not installed, then + # we will not do this certificate check: + get_openssl + if [ -z "$OPENSSL_BINARY" ]; then + wsrep_log_info "openssl utility not found" + return + fi - # use default if we haven't found a value - if [ -z "$reval" ]; then - [ -n "$3" ] && reval=$3 + # Generate the public key from the cert and the key. + # They should match (otherwise we can't create an SSL connection). + local pk1=$("$OPENSSL_BINARY" x509 -in "$cert" -pubkey -noout 2>/dev/null || :) + local pk2=$("$OPENSSL_BINARY" pkey -in "$key" -pubout 2>/dev/null || :) + if [ "$pk1" != "$pk2" ]; then + wsrep_log_error "******************* FATAL ERROR *****************" + wsrep_log_error "* The certificate and private key do not match. *" + wsrep_log_error "* Please check your certificate and key files. *" + wsrep_log_error "*************************************************" + exit 22 fi - echo $reval } + +# +# Compares two version strings. +# The first parameter is the version to be checked; +# The second parameter is the minimum version required; +# Returns 1 (failure) if $1 >= $2, 0 (success) otherwise. +# +check_for_version() +{ + local y1="${1#*.}" + [ "$y1" = "$1" ] && y1="" + local z1="${y1#*.}" + [ "$z1" = "$y1" ] && z1="" + local w1="${z1#*.}" + [ "$w1" = "$z1" ] && w1="" + local x1="${1%%.*}" + y1="${y1%%.*}" + z1="${z1%%.*}" + w1="${w1%%.*}" + [ -z "$y1" ] && y1=0 + [ -z "$z1" ] && z1=0 + [ -z "$w1" ] && w1=0 + local y2="${2#*.}" + [ "$y2" = "$2" ] && y2="" + local z2="${y2#*.}" + [ "$z2" = "$y2" ] && z2="" + local w2="${z2#*.}" + [ "$w2" = "$z2" ] && w2="" + local x2="${2%%.*}" + y2="${y2%%.*}" + z2="${z2%%.*}" + w2="${w2%%.*}" + [ -z "$y2" ] && y2=0 + [ -z "$z2" ] && z2=0 + [ -z "$w2" ] && w2=0 + [ $x1 -lt $x2 ] && return 1 + [ $x1 -gt $x2 ] && return 0 + [ $y1 -lt $y2 ] && return 1 + [ $y1 -gt $y2 ] && return 0 + [ $z1 -lt $z2 ] && return 1 + [ $z1 -gt $z2 ] && return 0 + [ $w1 -lt $w2 ] && return 1 + return 0 +} + +# +# Check whether process is still running. +# The first parameter contains the name of the PID file. +# The second parameter is the flag of the need to delete +# the PID file. +# If the second parameter is not zero and not empty, +# then if the process terminates, the corresponding +# PID file will be deleted. +# This function also sets the CHECK_PID variable to zero +# if the process has already exited, or writes the PID +# of the process there if it is still running. +# +check_pid() +{ + local pid_file="$1" + if [ -r "$pid_file" ]; then + local pid=$(cat "$pid_file" 2>/dev/null || :) + if [ -n "$pid" ]; then + if [ $pid -gt 0 ]; then + if ps -p $pid >/dev/null 2>&1; then + CHECK_PID=$pid + return 0 + fi + fi + fi + local remove=${2:-0} + if [ $remove -ne 0 ]; then + rm -f "$pid_file" || : + fi + fi + local config="${3:-}" + if [ -n "$config" -a -f "$config" ]; then + rm -f "$config" || : + fi + CHECK_PID=0 + return 1 +} + +# +# Checking that the process with the specified PID is still +# running and killing it in this case by sending SIGTERM +# (using the "kill" operation). +# The first parameter contains PID of the process. +# The second and third parameters (both optional) are the names +# of the PID and the configuration files, which should be removed +# after the process ends. +# If the first parameter (PID of the process) is zero, then +# the function immediately deletes the PID and the configuration +# files (if specified), without any additional checks. +# +cleanup_pid() +{ + local pid=$1 + local pid_file="${2:-}" + local config="${3:-}" + + if [ $pid -gt 0 ]; then + if ps -p $pid >/dev/null 2>&1; then + if kill $pid >/dev/null 2>&1; then + sleep 0.5 + local round=0 + local force=0 + while ps -p $pid >/dev/null 2>&1; do + sleep 1 + round=$(( round+1 )) + if [ $round -eq 16 ]; then + if [ $force -eq 0 ]; then + round=8 + force=1 + kill -9 $pid >/dev/null 2>&1 || : + sleep 0.5 + else + return 1 + fi + fi + done + elif ps -p $pid >/dev/null 2>&1; then + wsrep_log_warning "Unable to kill PID=$pid ($pid_file)" + return 1 + fi + fi + fi + + [ -n "$pid_file" -a -f "$pid_file" ] && rm -f "$pid_file" || : + [ -n "$config" -a -f "$config" ] && rm -f "$config" || : + + return 0 +} + +nproc="" + +get_proc() +{ + if [ -z "$nproc" ]; then + set +e + if [ "$OS" = 'Linux' ]; then + nproc=$(grep -cw -E '^processor' /proc/cpuinfo 2>/dev/null) + elif [ "$OS" = 'Darwin' -o "$OS" = 'FreeBSD' ]; then + nproc=$(sysctl -n hw.ncpu) + fi + set -e + if [ -z "$nproc" ] || [ $nproc -eq 0 ]; then + nproc=1 + fi + fi +} + +check_server_ssl_config() +{ + # backward-compatible behavior: + tcert=$(parse_cnf 'sst' 'tca') + tcap=$(parse_cnf 'sst' 'tcapath') + tpem=$(parse_cnf 'sst' 'tcert') + tkey=$(parse_cnf 'sst' 'tkey') + # reading new ssl configuration options: + local tcert2=$(parse_cnf "$encgroups" 'ssl-ca') + local tcap2=$(parse_cnf "$encgroups" 'ssl-capath') + local tpem2=$(parse_cnf "$encgroups" 'ssl-cert') + local tkey2=$(parse_cnf "$encgroups" 'ssl-key') + # if there are no old options, then we take new ones: + if [ -z "$tcert" -a -z "$tcap" -a -z "$tpem" -a -z "$tkey" ]; then + tcert="$tcert2" + tcap="$tcap2" + tpem="$tpem2" + tkey="$tkey2" + # checking for presence of the new-style SSL configuration: + elif [ -n "$tcert2" -o -n "$tcap2" -o -n "$tpem2" -o -n "$tkey2" ]; then + if [ "$tcert" != "$tcert2" -o \ + "$tcap" != "$tcap2" -o \ + "$tpem" != "$tpem2" -o \ + "$tkey" != "$tkey2" ] + then + wsrep_log_info \ + "new ssl configuration options (ssl-ca[path], ssl-cert" \ + "and ssl-key) are ignored by SST due to presence" \ + "of the tca[path], tcert and/or tkey in the [sst] section" + fi + fi + if [ -n "$tcert" ]; then + if [ "${tcert%/}" != "$tcert" -o -d "$tcert" ]; then + tcap="$tcert" + tcert="" + fi + fi +} + +simple_cleanup() +{ + # Since this is invoked just after exit NNN + local estatus=$? + if [ $estatus -ne 0 ]; then + wsrep_log_error "Cleanup after exit with status: $estatus" + fi + if [ -n "${SST_PID:-}" ]; then + [ "$(pwd)" != "$OLD_PWD" ] && cd "$OLD_PWD" + [ -f "$SST_PID" ] && rm -f "$SST_PID" || : + fi + exit $estatus +} + +wsrep_log_info "$WSREP_METHOD $WSREP_TRANSFER_TYPE started on $WSREP_SST_OPT_ROLE" diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index 799276258f2..63ef8be8690 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -1,6 +1,9 @@ -#!/bin/bash -ue +#!/usr/bin/env bash + +set -ue + +# Copyright (C) 2017-2022 MariaDB # Copyright (C) 2013 Percona Inc -# Copyright (C) 2017-2019 MariaDB # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,26 +20,27 @@ # MA 02110-1335 USA. # Documentation: -# http://www.percona.com/doc/percona-xtradb-cluster/manual/xtrabackup_sst.html +# https://mariadb.com/kb/en/mariabackup-overview/ # Make sure to read that before proceeding! -. $(dirname $0)/wsrep_sst_common +OS="$(uname)" + +. $(dirname "$0")/wsrep_sst_common +wsrep_check_datadir -OS=$(uname) ealgo="" +eformat="" ekey="" ekeyfile="" encrypt=0 -nproc=1 -ecode=0 ssyslog="" ssystag="" -XTRABACKUP_PID="" -SST_PORT="" -REMOTEIP="" +BACKUP_PID="" tcert="" +tcap="" tpem="" tkey="" +tmode="" sockopt="" progress="" ttime=0 @@ -45,22 +49,20 @@ lsn="" ecmd="" rlimit="" # Initially -stagemsg="${WSREP_SST_OPT_ROLE}" +stagemsg="$WSREP_SST_OPT_ROLE" cpat="" speciald=1 ib_home_dir="" ib_log_dir="" ib_undo_dir="" -sfmt="tar" +sfmt="" strmcmd="" tfmt="" tcmd="" -rebuild=0 -rebuildcmd="" payload=0 -pvformat="-F '%N => Rate:%r Avg:%a Elapsed:%t %e Bytes: %b %p' " -pvopts="-f -i 10 -N $WSREP_SST_OPT_ROLE " +pvformat="-F '%N => Rate:%r Avg:%a Elapsed:%t %e Bytes: %b %p'" +pvopts="-f -i 10 -N $WSREP_SST_OPT_ROLE" STATDIR="" uextra=0 disver="" @@ -72,857 +74,1110 @@ xtmpdir="" scomp="" sdecomp="" +ssl_dhparams="" + +compress='none' +compress_chunk="" +compress_threads="" + +backup_threads="" + +encrypt_threads="" +encrypt_chunk="" + +readonly SECRET_TAG='secret' +readonly TOTAL_TAG='total' + # Required for backup locks # For backup locks it is 1 sent by joiner -# 5.6.21 PXC and later can't donate to an older joiner sst_ver=1 -if pv --help 2>/dev/null | grep -q FORMAT;then - pvopts+=$pvformat -fi -pcmd="pv $pvopts" declare -a RC -set +e -INNOBACKUPEX_BIN=$(which mariabackup) -if test -z $INNOBACKUPEX_BIN -then - wsrep_log_error 'mariabackup binary not found in $PATH' - exit 42 +BACKUP_BIN=$(commandex 'mariabackup') +if [ -z "$BACKUP_BIN" ]; then + wsrep_log_error 'mariabackup binary not found in path' + exit 42 fi -set -e -XBSTREAM_BIN=mbstream -XBCRYPT_BIN=xbcrypt # Not available in MariaBackup - -DATA="${WSREP_SST_OPT_DATA}" -INFO_FILE="xtrabackup_galera_info" -IST_FILE="xtrabackup_ist" -MAGIC_FILE="${DATA}/${INFO_FILE}" -INNOAPPLYLOG="${DATA}/mariabackup.prepare.log" -INNOMOVELOG="${DATA}/mariabackup.move.log" -INNOBACKUPLOG="${DATA}/mariabackup.backup.log" - -# Setting the path for ss and ip -export PATH="/usr/sbin:/sbin:$PATH" - -timeit(){ - local stage=$1 + +DATA="$WSREP_SST_OPT_DATA" +INFO_FILE='xtrabackup_galera_info' +IST_FILE='xtrabackup_ist' +MAGIC_FILE="$DATA/$INFO_FILE" + +INNOAPPLYLOG="$DATA/mariabackup.prepare.log" +INNOMOVELOG="$DATA/mariabackup.move.log" +INNOBACKUPLOG="$DATA/mariabackup.backup.log" + +timeit() +{ + local stage="$1" shift local cmd="$@" local x1 x2 took extcode - if [[ $ttime -eq 1 ]];then + if [ $ttime -eq 1 ]; then x1=$(date +%s) - wsrep_log_info "Evaluating $cmd" - eval "$cmd" - extcode=$? + fi + + wsrep_log_info "Evaluating $cmd" + eval $cmd + extcode=$? + + if [ $ttime -eq 1 ]; then x2=$(date +%s) took=$(( x2-x1 )) wsrep_log_info "NOTE: $stage took $took seconds" totime=$(( totime+took )) - else - wsrep_log_info "Evaluating $cmd" - eval "$cmd" - extcode=$? fi + return $extcode } get_keys() { # $encrypt -eq 1 is for internal purposes only - if [[ $encrypt -ge 2 || $encrypt -eq -1 ]];then - return + if [ $encrypt -ge 2 -o $encrypt -eq -1 ]; then + return fi - if [[ $encrypt -eq 0 ]];then - if $MY_PRINT_DEFAULTS xtrabackup | grep -q encrypt;then - wsrep_log_error "Unexpected option combination. SST may fail. Refer to http://www.percona.com/doc/percona-xtradb-cluster/manual/xtrabackup_sst.html" + if [ $encrypt -eq 0 ]; then + if [ -n "$ealgo" -o -n "$ekey" -o -n "$ekeyfile" ]; then + wsrep_log_error "Options for encryption are specified," \ + "but encryption itself is disabled. SST may fail." fi return fi - if [[ $sfmt == 'tar' ]];then - wsrep_log_info "NOTE: Xtrabackup-based encryption - encrypt=1 - cannot be enabled with tar format" + if [ "$sfmt" = 'tar' ]; then + wsrep_log_info "NOTE: key-based encryption (encrypt=1)" \ + "cannot be enabled with tar format" encrypt=-1 return fi - wsrep_log_info "Xtrabackup based encryption enabled in my.cnf - Supported only from Xtrabackup 2.1.4" + wsrep_log_info "Key based encryption enabled in my.cnf" - if [[ -z $ealgo ]];then + if [ -z "$ealgo" ]; then wsrep_log_error "FATAL: Encryption algorithm empty from my.cnf, bailing out" exit 3 fi - if [[ -z $ekey && ! -r $ekeyfile ]];then - wsrep_log_error "FATAL: Either key or keyfile must be readable" + if [ -z "$ekey" -a ! -r "$ekeyfile" ]; then + wsrep_log_error "FATAL: Either key must be specified" \ + "or keyfile must be readable" exit 3 fi - if [[ -z $ekey ]];then - ecmd="${XBCRYPT_BIN} --encrypt-algo=$ealgo --encrypt-key-file=$ekeyfile" + if [ "$eformat" = 'openssl' ]; then + get_openssl + if [ -z "$OPENSSL_BINARY" ]; then + wsrep_log_error "If encryption using the openssl is enabled," \ + "then you need to install openssl" + exit 2 + fi + ecmd="'$OPENSSL_BINARY' enc -$ealgo" + if "$OPENSSL_BINARY" enc -help 2>&1 | grep -qw -F -- '-pbkdf2'; then + ecmd="$ecmd -pbkdf2" + elif "$OPENSSL_BINARY" enc -help 2>&1 | grep -qw -F -- '-iter'; then + ecmd="$ecmd -iter 1" + elif "$OPENSSL_BINARY" enc -help 2>&1 | grep -qw -F -- '-md'; then + ecmd="$ecmd -md sha256" + fi + if [ -z "$ekey" ]; then + ecmd="$ecmd -kfile '$ekeyfile'" + else + ecmd="$ecmd -k '$ekey'" + fi + elif [ "$eformat" = 'xbcrypt' ]; then + if [ -z "$(commandex xbcrypt)" ]; then + wsrep_log_error "If encryption using the xbcrypt is enabled," \ + "then you need to install xbcrypt" + exit 2 + fi + wsrep_log_info "NOTE: xbcrypt-based encryption," \ + "supported only from Xtrabackup 2.1.4" + if [ -z "$ekey" ]; then + ecmd="xbcrypt --encrypt-algo='$ealgo' --encrypt-key-file='$ekeyfile'" + else + ecmd="xbcrypt --encrypt-algo='$ealgo' --encrypt-key='$ekey'" + fi + if [ -n "$encrypt_threads" ]; then + ecmd="$ecmd --encrypt-threads=$encrypt_threads" + fi + if [ -n "$encrypt_chunk" ]; then + ecmd="$ecmd --encrypt-chunk-size=$encrypt_chunk" + fi else - ecmd="${XBCRYPT_BIN} --encrypt-algo=$ealgo --encrypt-key=$ekey" + wsrep_log_error "Unknown encryption format='$eformat'" + exit 2 fi - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - ecmd+=" -d" - fi + [ "$WSREP_SST_OPT_ROLE" = 'joiner' ] && ecmd="$ecmd -d" - stagemsg+="-XB-Encrypted" + stagemsg="$stagemsg-XB-Encrypted" } -get_transfer() +get_socat_ver() { - if [[ -z $SST_PORT ]];then - TSST_PORT=4444 - else - TSST_PORT=$SST_PORT + [ -n "${SOCAT_VERSION+x}" ] && return + # Determine the socat version + SOCAT_VERSION=$(socat -V 2>&1 | \ + grep -m1 -owE '[0-9]+(\.[0-9]+)+' | \ + head -n1 || :) + if [ -z "$SOCAT_VERSION" ]; then + wsrep_log_error "******** FATAL ERROR ******************" + wsrep_log_error "* Cannot determine the socat version. *" + wsrep_log_error "***************************************" + exit 2 fi +} - if [[ $tfmt == 'nc' ]];then - wsrep_check_programs nc +get_transfer() +{ + if [ "$tfmt" = 'nc' ]; then wsrep_log_info "Using netcat as streamer" - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - if nc -h 2>&1 | grep -q ncat;then - # Ncat - tcmd="nc -l ${TSST_PORT}" - elif nc -h 2>&1 | grep -q -- '-d\>';then - # Debian netcat - tcmd="nc -dl ${TSST_PORT}" + wsrep_check_programs nc + tcmd='nc' + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + if nc -h 2>&1 | grep -q -F 'ncat'; then + wsrep_log_info "Using Ncat as streamer" + tcmd="$tcmd -l" + elif nc -h 2>&1 | grep -qw -F -- '-d'; then + wsrep_log_info "Using Debian netcat as streamer" + tcmd="$tcmd -dl" + if [ $WSREP_SST_OPT_HOST_IPv6 -eq 1 ]; then + # When host is not explicitly specified (when only the port + # is specified) netcat can only bind to an IPv4 address if + # the "-6" option is not explicitly specified: + tcmd="$tcmd -6" + fi else - # traditional netcat - tcmd="nc -l -p ${TSST_PORT}" + wsrep_log_info "Using traditional netcat as streamer" + tcmd="$tcmd -l -p" fi + tcmd="$tcmd $SST_PORT" else - if nc -h 2>&1 | grep -q ncat;then - # Ncat - tcmd="nc ${REMOTEIP} ${TSST_PORT}" - elif nc -h 2>&1 | grep -q -- '-d\>';then - # Debian netcat - tcmd="nc ${REMOTEIP} ${TSST_PORT}" + # Check to see if netcat supports the '-N' flag. + # -N Shutdown the network socket after EOF on stdin + # If it supports the '-N' flag, then we need to use the '-N' + # flag, otherwise the transfer will stay open after the file + # transfer and cause the command to timeout. + # Older versions of netcat did not need this flag and will + # return an error if the flag is used. + if nc -h 2>&1 | grep -qw -F -- '-N'; then + tcmd="$tcmd -N" + wsrep_log_info "Using nc -N" + fi + # netcat doesn't understand [] around IPv6 address + if nc -h 2>&1 | grep -q -F 'ncat'; then + wsrep_log_info "Using Ncat as streamer" + elif nc -h 2>&1 | grep -qw -F -- '-d'; then + wsrep_log_info "Using Debian netcat as streamer" else - # traditional netcat - tcmd="nc -q0 ${REMOTEIP} ${TSST_PORT}" + wsrep_log_info "Using traditional netcat as streamer" + tcmd="$tcmd -q0" fi + tcmd="$tcmd $WSREP_SST_OPT_HOST_UNESCAPED $SST_PORT" fi else tfmt='socat' - wsrep_check_programs socat + wsrep_log_info "Using socat as streamer" + wsrep_check_programs socat + + if [ -n "$sockopt" ]; then + sockopt=$(trim_string "$sockopt" ',') + if [ -n "$sockopt" ]; then + sockopt=",$sockopt" + fi + fi - if [[ $encrypt -eq 2 || $encrypt -eq 3 ]] && ! socat -V | grep -q "WITH_OPENSSL 1";then - wsrep_log_error "Encryption requested, but socat is not OpenSSL enabled (encrypt=$encrypt)" + # Add an option for ipv6 if needed: + if [ $WSREP_SST_OPT_HOST_IPv6 -eq 1 ]; then + # If sockopt contains 'pf=ip6' somewhere in the middle, + # this will not interfere with socat, but exclude the trivial + # cases when sockopt contains 'pf=ip6' as prefix or suffix: + if [ "$sockopt" = "${sockopt#,pf=ip6,}" -a \ + "$sockopt" = "${sockopt%,pf=ip6}" ] + then + sockopt=",pf=ip6$sockopt" + fi + fi + + if [ $encrypt -lt 2 ]; then + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + tcmd="socat -u TCP-LISTEN:$SST_PORT,reuseaddr$sockopt stdio" + else + tcmd="socat -u stdio TCP:$REMOTEIP:$SST_PORT$sockopt" + fi + return + fi + + if ! socat -V | grep -q -F 'WITH_OPENSSL 1'; then + wsrep_log_error "******** FATAL ERROR ************************************************ " + wsrep_log_error "* Encryption requested, but socat is not OpenSSL enabled (encrypt=$encrypt) *" + wsrep_log_error "********************************************************************* " exit 2 fi - if [[ $encrypt -eq 2 ]];then - wsrep_log_info "Using openssl based encryption with socat: with crt and pem" - if [[ -z $tpem || -z $tcert ]];then - wsrep_log_error "Both PEM and CRT files required" + local action='Decrypting' + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + tcmd="socat -u openssl-listen:$SST_PORT,reuseaddr" + else + local addr="$REMOTEIP:$SST_PORT" + tcmd="socat -u stdio openssl-connect:$addr" + action='Encrypting' + get_socat_ver + if ! check_for_version "$SOCAT_VERSION" '1.7.4.1'; then + if check_for_version "$SOCAT_VERSION" '1.7.3.3'; then + # Workaround for a bug known as 'Red Hat issue 1870279' + # (connection reset by peer) in socat versions 1.7.3.3 + # to 1.7.4.0: + tcmd="socat stdio openssl-connect:$addr,linger=10" + wsrep_log_info \ + "Use workaround for socat $SOCAT_VERSION bug" + fi + fi + fi + + if [ "${sockopt#*,dhparam=}" = "$sockopt" ]; then + if [ -z "$ssl_dhparams" ]; then + get_socat_ver + if ! check_for_version "$SOCAT_VERSION" '1.7.3'; then + # socat versions < 1.7.3 will have 512-bit dhparams (too small) + # so create 2048-bit dhparams and send that as a parameter: + check_for_dhparams + fi + fi + if [ -n "$ssl_dhparams" ]; then + tcmd="$tcmd,dhparam='$ssl_dhparams'" + fi + fi + + CN_option=",commonname=''" + + if [ $encrypt -eq 2 ]; then + wsrep_log_info \ + "Using openssl based encryption with socat: with crt and pem" + if [ -z "$tpem" -o -z "$tcert$tcap" ]; then + wsrep_log_error \ + "Both PEM file and CRT file (or path) are required" exit 22 fi - stagemsg+="-OpenSSL-Encrypted-2" - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - wsrep_log_info "Decrypting with cert=${tpem}, cafile=${tcert}" - tcmd="socat -u openssl-listen:${TSST_PORT},reuseaddr,cert=${tpem},cafile=${tcert}${sockopt} stdio" - else - wsrep_log_info "Encrypting with cert=${tpem}, cafile=${tcert}" - tcmd="socat -u stdio openssl-connect:${REMOTEIP}:${TSST_PORT},cert=${tpem},cafile=${tcert}${sockopt}" + verify_ca_matches_cert "$tpem" "$tcert" "$tcap" + tcmd="$tcmd,cert='$tpem'" + if [ -n "$tcert" ]; then + tcmd="$tcmd,cafile='$tcert'" + fi + if [ -n "$tcap" ]; then + tcmd="$tcmd,capath='$tcap'" fi - elif [[ $encrypt -eq 3 ]];then - wsrep_log_info "Using openssl based encryption with socat: with key and crt" - if [[ -z $tpem || -z $tkey ]];then - wsrep_log_error "Both certificate and key files required" + stagemsg="$stagemsg-OpenSSL-Encrypted-2" + wsrep_log_info "$action with cert='$tpem', ca='$tcert', capath='$tcap'" + elif [ $encrypt -eq 3 -o $encrypt -eq 4 ]; then + wsrep_log_info \ + "Using openssl based encryption with socat: with key and crt" + if [ -z "$tpem" -o -z "$tkey" ]; then + wsrep_log_error "Both the certificate file (or path) and" \ + "the key file are required" exit 22 fi - stagemsg+="-OpenSSL-Encrypted-3" - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - if [[ -z $tcert ]];then - wsrep_log_info "Decrypting with cert=${tpem}, key=${tkey}, verify=0" - tcmd="socat -u openssl-listen:${TSST_PORT},reuseaddr,cert=${tpem},key=${tkey},verify=0${sockopt} stdio" - else - wsrep_log_info "Decrypting with cert=${tpem}, key=${tkey}, cafile=${tcert}" - tcmd="socat -u openssl-listen:${TSST_PORT},reuseaddr,cert=${tpem},key=${tkey},cafile=${tcert}${sockopt} stdio" + verify_cert_matches_key "$tpem" "$tkey" + stagemsg="$stagemsg-OpenSSL-Encrypted-3" + if [ -z "$tcert$tcap" ]; then + if [ $encrypt -eq 4 ]; then + wsrep_log_error \ + "Peer certificate file (or path) required if encrypt=4" + exit 22 fi + # no verification + CN_option="" + tcmd="$tcmd,cert='$tpem',key='$tkey',verify=0" + wsrep_log_info \ + "$action with cert='$tpem', key='$tkey', verify=0" else - if [[ -z $tcert ]];then - wsrep_log_info "Encrypting with cert=${tpem}, key=${tkey}, verify=0" - tcmd="socat -u stdio openssl-connect:${REMOTEIP}:${TSST_PORT},cert=${tpem},key=${tkey},verify=0${sockopt}" + # CA verification + verify_ca_matches_cert "$tpem" "$tcert" "$tcap" + if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then + CN_option=",commonname='$WSREP_SST_OPT_REMOTE_USER'" + elif [ "$WSREP_SST_OPT_ROLE" = 'joiner' -o $encrypt -eq 4 ] + then + CN_option=",commonname=''" + elif is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CN_option=',commonname=localhost' else - wsrep_log_info "Encrypting with cert=${tpem}, key=${tkey}, cafile=${tcert}" - tcmd="socat -u stdio openssl-connect:${REMOTEIP}:${TSST_PORT},cert=${tpem},key=${tkey},cafile=${tcert}${sockopt}" + CN_option=",commonname='$WSREP_SST_OPT_HOST_UNESCAPED'" fi + tcmd="$tcmd,cert='$tpem',key='$tkey'" + if [ -n "$tcert" ]; then + tcmd="$tcmd,cafile='$tcert'" + fi + if [ -n "$tcap" ]; then + tcmd="$tcmd,capath='$tcap'" + fi + wsrep_log_info "$action with cert='$tpem', key='$tkey'," \ + "ca='$tcert', capath='$tcap'" fi - - else - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - tcmd="socat -u TCP-LISTEN:${TSST_PORT},reuseaddr${sockopt} stdio" - else - tcmd="socat -u stdio TCP:${REMOTEIP}:${TSST_PORT}${sockopt}" - fi + else + wsrep_log_info "Unknown encryption mode: encrypt=$encrypt" + exit 22 fi - fi -} + tcmd="$tcmd$CN_option$sockopt" -parse_cnf() -{ - local group=$1 - local var=$2 - # print the default settings for given group using my_print_default. - # normalize the variable names specified in cnf file (user can use _ or - for example log-bin or log_bin) - # then grep for needed variable - # finally get the variable value (if variables has been specified multiple time use the last value only) - reval=$($MY_PRINT_DEFAULTS $group | awk -F= '{if ($1 ~ /_/) { gsub(/_/,"-",$1); print $1"="$2 } else { print $0 }}' | grep -- "--$var=" | cut -d= -f2- | tail -1) - if [[ -z $reval ]];then - [[ -n $3 ]] && reval=$3 + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + tcmd="$tcmd stdio" + fi fi - echo $reval } get_footprint() { - pushd $WSREP_SST_OPT_DATA 1>/dev/null - payload=$(find . -regex '.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' -type f -print0 | du --files0-from=- --block-size=1 -c | awk 'END { print $1 }') - if $MY_PRINT_DEFAULTS xtrabackup | grep -q -- "--compress";then + cd "$DATA_DIR" + local payload_data=$(find . \ + -regex '.*undo[0-9]+$\|.*\.ibd$\|.*\.MYI$\|.*\.MYD$\|.*ibdata1$' \ + -type f -print0 | du --files0-from=- --block-size=1 -c -s | \ + awk 'END { print $1 }') + + local payload_undo=0 + if [ -n "$ib_undo_dir" -a -d "$ib_undo_dir" ]; then + cd "$ib_undo_dir" + payload_undo=$(find . -regex '.*undo[0-9]+$' -type f -print0 | \ + du --files0-from=- --block-size=1 -c -s | awk 'END { print $1 }') + fi + cd "$OLD_PWD" + + wsrep_log_info \ + "SST footprint estimate: data: $payload_data, undo: $payload_undo" + + payload=$(( payload_data + payload_undo )) + + if [ "$compress" != 'none' ]; then # QuickLZ has around 50% compression ratio # When compression/compaction used, the progress is only an approximate. payload=$(( payload*1/2 )) fi - popd 1>/dev/null - pcmd+=" -s $payload" + + if [ $WSREP_SST_OPT_PROGRESS -eq 1 ]; then + # report to parent the total footprint of the SST + echo "$TOTAL_TAG $payload" + fi + adjust_progress } adjust_progress() { + pcmd="" + rcmd="" - if ! command -v pv >/dev/null;then - wsrep_log_error "pv not found in path: $PATH" - wsrep_log_error "Disabling all progress/rate-limiting" - pcmd="" - rlimit="" - progress="" - return + [ "$progress" = 'none' ] && return + + rlimitopts="" + if [ -n "$rlimit" -a "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + wsrep_log_info "Rate-limiting SST to $rlimit" + rlimitopts=" -L $rlimit" fi - if [[ -n $progress && $progress != '1' ]];then - if [[ -e $progress ]];then - pcmd+=" 2>>$progress" - else - pcmd+=" 2>$progress" + if [ -n "$progress" ]; then + + # Backward compatibility: user-configured progress output + pcmd="pv $pvopts$rlimitopts" + + if [ -z "${PV_FORMAT+x}" ]; then + PV_FORMAT=0 + pv --help | grep -qw -F -- '-F' && PV_FORMAT=1 + fi + if [ $PV_FORMAT -eq 1 ]; then + pcmd="$pcmd $pvformat" + fi + + if [ $payload -ne 0 ]; then + pcmd="$pcmd -s $payload" fi - elif [[ -z $progress && -n $rlimit ]];then - # When rlimit is non-zero - pcmd="pv -q" - fi - if [[ -n $rlimit && "$WSREP_SST_OPT_ROLE" == "donor" ]];then - wsrep_log_info "Rate-limiting SST to $rlimit" - pcmd+=" -L \$rlimit" + if [ "$progress" != '1' ]; then + if [ -e "$progress" ]; then + pcmd="$pcmd 2>>'$progress'" + else + pcmd="$pcmd 2>'$progress'" + fi + fi + + elif [ $WSREP_SST_OPT_PROGRESS -eq 1 ]; then + + # Default progress output parseable by parent + pcmd="pv -f -i 1 -n -b$rlimitopts" + + # read progress data, add tag and post to stdout + # for the parent + rcmd="stdbuf -oL tr '\r' '\n' | xargs -n1 echo complete" + + elif [ -n "$rlimitopts" ]; then + + # Rate-limiting only, when rlimit is non-zero + pcmd="pv -q$rlimitopts" + fi } +encgroups='--mysqld|sst|xtrabackup' + read_cnf() { - sfmt=$(parse_cnf sst streamfmt "xbstream") - tfmt=$(parse_cnf sst transferfmt "socat") - tcert=$(parse_cnf sst tca "") - tpem=$(parse_cnf sst tcert "") - tkey=$(parse_cnf sst tkey "") - encrypt=$(parse_cnf sst encrypt 0) + sfmt=$(parse_cnf sst streamfmt 'mbstream') + tfmt=$(parse_cnf sst transferfmt 'socat') + + encrypt=$(parse_cnf "$encgroups" 'encrypt' 0) + tmode=$(parse_cnf "$encgroups" 'ssl-mode' 'DISABLED' | \ + tr '[[:lower:]]' '[[:upper:]]') + + case "$tmode" in + 'VERIFY_IDENTITY'|'VERIFY_CA'|'REQUIRED'|'DISABLED') + ;; + *) + wsrep_log_error "Unrecognized ssl-mode option: '$tmode'" + exit 22 # EINVAL + ;; + esac + + if [ $encrypt -eq 0 -o $encrypt -ge 2 ]; then + if [ "$tmode" != 'DISABLED' -o $encrypt -ge 2 ]; then + check_server_ssl_config + fi + if [ "$tmode" != 'DISABLED' ]; then + if [ 0 -eq $encrypt -a -n "$tpem" -a -n "$tkey" ] + then + encrypt=3 # enable cert/key SSL encyption + # avoid CA verification if not set explicitly: + # nodes may happen to have different CA if self-generated, + # zeroing up tcert and tcap does the trick: + if [ "${tmode#VERIFY}" = "$tmode" ]; then + tcert="" + tcap="" + fi + fi + fi + elif [ $encrypt -eq 1 ]; then + ealgo=$(parse_cnf "$encgroups" 'encrypt-algo') + eformat=$(parse_cnf "$encgroups" 'encrypt-format' 'openssl') + ekey=$(parse_cnf "$encgroups" 'encrypt-key') + # The keyfile should be read only when the key + # is not specified or empty: + if [ -z "$ekey" ]; then + ekeyfile=$(parse_cnf "$encgroups" 'encrypt-key-file') + fi + fi + + wsrep_log_info "SSL configuration: CA='$tcert', CAPATH='$tcap'," \ + "CERT='$tpem', KEY='$tkey', MODE='$tmode'," \ + "encrypt='$encrypt'" + + if [ $encrypt -ge 2 ]; then + ssl_dhparams=$(parse_cnf "$encgroups" 'ssl-dhparams') + fi + sockopt=$(parse_cnf sst sockopt "") progress=$(parse_cnf sst progress "") - rebuild=$(parse_cnf sst rebuild 0) ttime=$(parse_cnf sst time 0) - cpat=$(parse_cnf sst cpat '.*galera\.cache$\|.*sst_in_progress$\|.*\.sst$\|.*gvwstate\.dat$\|.*grastate\.dat$\|.*\.err$\|.*\.log$\|.*RPM_UPGRADE_MARKER$\|.*RPM_UPGRADE_HISTORY$') - [[ $OS == "FreeBSD" ]] && cpat=$(parse_cnf sst cpat '.*galera\.cache$|.*sst_in_progress$|.*\.sst$|.*gvwstate\.dat$|.*grastate\.dat$|.*\.err$|.*\.log$|.*RPM_UPGRADE_MARKER$|.*RPM_UPGRADE_HISTORY$') - ealgo=$(parse_cnf xtrabackup encrypt "") - ekey=$(parse_cnf xtrabackup encrypt-key "") - ekeyfile=$(parse_cnf xtrabackup encrypt-key-file "") + cpat='.*\.pem$\|.*galera\.cache$\|.*sst_in_progress$\|.*\.sst$\|.*gvwstate\.dat$\|.*grastate\.dat$\|.*\.err$\|.*\.log$\|.*RPM_UPGRADE_MARKER$\|.*RPM_UPGRADE_HISTORY$' + [ "$OS" = 'FreeBSD' ] && cpat=$(echo "$cpat" | sed 's/\\|/|/g') + cpat=$(parse_cnf sst cpat "$cpat") scomp=$(parse_cnf sst compressor "") sdecomp=$(parse_cnf sst decompressor "") - # Refer to http://www.percona.com/doc/percona-xtradb-cluster/manual/xtrabackup_sst.html - if [[ -z $ealgo ]];then - ealgo=$(parse_cnf sst encrypt-algo "") - ekey=$(parse_cnf sst encrypt-key "") - ekeyfile=$(parse_cnf sst encrypt-key-file "") - fi - rlimit=$(parse_cnf sst rlimit "") uextra=$(parse_cnf sst use-extra 0) speciald=$(parse_cnf sst sst-special-dirs 1) iopts=$(parse_cnf sst inno-backup-opts "") iapts=$(parse_cnf sst inno-apply-opts "") impts=$(parse_cnf sst inno-move-opts "") - stimeout=$(parse_cnf sst sst-initial-timeout 100) + stimeout=$(parse_cnf sst sst-initial-timeout 300) ssyslog=$(parse_cnf sst sst-syslog 0) ssystag=$(parse_cnf mysqld_safe syslog-tag "${SST_SYSLOG_TAG:-}") - ssystag+="-" + ssystag="$ssystag-" sstlogarchive=$(parse_cnf sst sst-log-archive 1) - sstlogarchivedir=$(parse_cnf sst sst-log-archive-dir "/tmp/sst_log_archive") + sstlogarchivedir="" + if [ $sstlogarchive -ne 0 ]; then + sstlogarchivedir=$(parse_cnf sst sst-log-archive-dir \ + '/tmp/sst_log_archive') + if [ -n "$sstlogarchivedir" ]; then + sstlogarchivedir=$(trim_dir "$sstlogarchivedir") + fi + fi - if [[ $speciald -eq 0 ]];then - wsrep_log_error "sst-special-dirs equal to 0 is not supported, falling back to 1" + if [ $speciald -eq 0 ]; then + wsrep_log_error \ + "sst-special-dirs equal to 0 is not supported, falling back to 1" speciald=1 - fi + fi + + if [ $ssyslog -ne -1 ]; then + ssyslog=$(in_config 'mysqld_safe' 'syslog') + fi - if [[ $ssyslog -ne -1 ]];then - if $MY_PRINT_DEFAULTS mysqld_safe | tr '_' '-' | grep -q -- "--syslog";then - ssyslog=1 + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + compress=$(parse_cnf "$encgroups" 'compress' 'none') + if [ "$compress" != 'none' ]; then + compress_chunk=$(parse_cnf "$encgroups" 'compress-chunk-size') + compress_threads=$(parse_cnf "$encgroups" 'compress-threads') fi fi - if [[ $encrypt -eq 1 ]]; then - wsrep_log_error "Xtrabackup-based encryption is currently not" \ - "supported with MariaBackup" - exit 2 + backup_threads=$(parse_cnf "$encgroups" 'backup-threads') + + if [ "$eformat" = 'xbcrypt' ]; then + encrypt_threads=$(parse_cnf "$encgroups" 'encrypt-threads') + encrypt_chunk=$(parse_cnf "$encgroups" 'encrypt-chunk-size') fi } get_stream() { - if [[ $sfmt == 'mbstream' || $sfmt == 'xbstream' ]];then - wsrep_log_info "Streaming with ${sfmt}" - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - strmcmd="${XBSTREAM_BIN} -x" + if [ "$sfmt" = 'mbstream' -o "$sfmt" = 'xbstream' ]; then + sfmt='mbstream' + local STREAM_BIN=$(commandex "$sfmt") + if [ -z "$STREAM_BIN" ]; then + wsrep_log_error "Streaming with $sfmt, but $sfmt not found in path" + exit 42 + fi + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + strmcmd="'$STREAM_BIN' -x" else - strmcmd="${XBSTREAM_BIN} -c \${INFO_FILE}" + strmcmd="'$STREAM_BIN' -c '$INFO_FILE'" fi else - sfmt="tar" - wsrep_log_info "Streaming with tar" - if [[ "$WSREP_SST_OPT_ROLE" == "joiner" ]];then - strmcmd="tar xfi - " + sfmt='tar' + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + strmcmd='tar xfi -' else - strmcmd="tar cf - \${INFO_FILE} " + strmcmd="tar cf - '$INFO_FILE'" fi - fi + wsrep_log_info "Streaming with $sfmt" } -get_proc() -{ - set +e - nproc=$(grep -c processor /proc/cpuinfo) - [[ -z $nproc || $nproc -eq 0 ]] && nproc=1 - set -e -} - -sig_joiner_cleanup() -{ - wsrep_log_error "Removing $MAGIC_FILE file due to signal" - rm -f "$MAGIC_FILE" -} - -cleanup_joiner() +cleanup_at_exit() { # Since this is invoked just after exit NNN local estatus=$? - if [[ $estatus -ne 0 ]];then - wsrep_log_error "Cleanup after exit with status:$estatus" - elif [ "${WSREP_SST_OPT_ROLE}" = "joiner" ];then - wsrep_log_info "Removing the sst_in_progress file" - wsrep_cleanup_progress_file - fi - if [[ -n $progress && -p $progress ]];then - wsrep_log_info "Cleaning up fifo file $progress" - rm $progress - fi - if [[ -n ${STATDIR:-} ]];then - [[ -d $STATDIR ]] && rm -rf $STATDIR + if [ $estatus -ne 0 ]; then + wsrep_log_error "Cleanup after exit with status: $estatus" + elif [ -z "${coords:-}" -a "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + estatus=32 + wsrep_log_error "Failed to get current position" fi - # Final cleanup - pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') - - # This means no setsid done in mysqld. - # We don't want to kill mysqld here otherwise. - if [[ $$ -eq $pgid ]];then - - # This means a signal was delivered to the process. - # So, more cleanup. - if [[ $estatus -ge 128 ]];then - kill -KILL -$$ || true - fi + [ "$(pwd)" != "$OLD_PWD" ] && cd "$OLD_PWD" + if [ $estatus -ne 0 ]; then + wsrep_log_error "Removing $MAGIC_FILE file due to signal" + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" || : fi - exit $estatus -} - -check_pid() -{ - local pid_file="$1" - [ -r "$pid_file" ] && ps -p $(cat "$pid_file") >/dev/null 2>&1 -} - -cleanup_donor() -{ - # Since this is invoked just after exit NNN - local estatus=$? - if [[ $estatus -ne 0 ]];then - wsrep_log_error "Cleanup after exit with status:$estatus" - fi - - if [[ -n ${XTRABACKUP_PID:-} ]];then - if check_pid $XTRABACKUP_PID - then - wsrep_log_error "xtrabackup process is still running. Killing..." - kill_xtrabackup + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + wsrep_log_info "Removing the sst_in_progress file" + wsrep_cleanup_progress_file + else + if [ -n "$BACKUP_PID" ]; then + if check_pid "$BACKUP_PID" 1; then + wsrep_log_error \ + "mariabackup process is still running. Killing..." + cleanup_pid $CHECK_PID "$BACKUP_PID" + fi fi - + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" || : fi - rm -f ${DATA}/${IST_FILE} || true - if [[ -n $progress && -p $progress ]];then - wsrep_log_info "Cleaning up fifo file $progress" - rm -f $progress || true + if [ -n "$progress" -a -p "$progress" ]; then + wsrep_log_info "Cleaning up fifo file: $progress" + rm -f "$progress" || : fi wsrep_log_info "Cleaning up temporary directories" - if [[ -n $xtmpdir ]];then - [[ -d $xtmpdir ]] && rm -rf $xtmpdir || true - fi - - if [[ -n $itmpdir ]];then - [[ -d $itmpdir ]] && rm -rf $itmpdir || true + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then + [ -n "$STATDIR" -a -d "$STATDIR" ] && rm -rf "$STATDIR" || : + else + [ -n "$xtmpdir" -a -d "$xtmpdir" ] && rm -rf "$xtmpdir" || : + [ -n "$itmpdir" -a -d "$itmpdir" ] && rm -rf "$itmpdir" || : fi - # Final cleanup - pgid=$(ps -o pgid= $$ | grep -o '[0-9]*') + # Final cleanup + pgid=$(ps -o pgid= $$ 2>/dev/null | grep -o -E '[0-9]+' || :) # This means no setsid done in mysqld. # We don't want to kill mysqld here otherwise. - if [[ $$ -eq $pgid ]];then - - # This means a signal was delivered to the process. - # So, more cleanup. - if [[ $estatus -ge 128 ]];then - kill -KILL -$$ || true + if [ -n "$pgid" ]; then + if [ $$ -eq $pgid ]; then + # This means a signal was delivered to the process. + # So, more cleanup. + if [ $estatus -ge 128 ]; then + kill -KILL -- -$$ || : + fi fi + fi + if [ -n "${SST_PID:-}" ]; then + [ -f "$SST_PID" ] && rm -f "$SST_PID" || : fi exit $estatus - -} - -kill_xtrabackup() -{ - local PID=$(cat $XTRABACKUP_PID) - [ -n "$PID" -a "0" != "$PID" ] && kill $PID && (kill $PID && kill -9 $PID) || : - wsrep_log_info "Removing xtrabackup pid file $XTRABACKUP_PID" - rm -f "$XTRABACKUP_PID" || true } setup_ports() { - if [[ "$WSREP_SST_OPT_ROLE" == "donor" ]];then - if [ "${WSREP_SST_OPT_ADDR#\[}" != "$WSREP_SST_OPT_ADDR" ]; then - remain=$(echo $WSREP_SST_OPT_ADDR | awk -F '\\][:/]' '{ print $2 }') - REMOTEIP=$(echo $WSREP_SST_OPT_ADDR | awk -F '\\]:' '{ print $1 }')"]" - SST_PORT=$(echo $remain | awk -F '[:/]' '{ print $1 }') - lsn=$(echo $remain | awk -F '[:/]' '{ print $3 }') - sst_ver=$(echo $remain | awk -F '[:/]' '{ print $4 }') - else - SST_PORT=$(echo $WSREP_SST_OPT_ADDR | awk -F '[:/]' '{ print $2 }') - REMOTEIP=$(echo $WSREP_SST_OPT_ADDR | awk -F ':' '{ print $1 }') - lsn=$(echo $WSREP_SST_OPT_ADDR | awk -F '[:/]' '{ print $4 }') - sst_ver=$(echo $WSREP_SST_OPT_ADDR | awk -F '[:/]' '{ print $5 }') - fi - else - if [ "${WSREP_SST_OPT_ADDR#\[}" != "$WSREP_SST_OPT_ADDR" ]; then - SST_PORT=$(echo ${WSREP_SST_OPT_ADDR} | awk -F '\\]:' '{ print $2 }') - else - SST_PORT=$(echo ${WSREP_SST_OPT_ADDR} | awk -F ':' '{ print $2 }') - fi + SST_PORT="$WSREP_SST_OPT_PORT" + if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + REMOTEIP="$WSREP_SST_OPT_HOST" + lsn="$WSREP_SST_OPT_LSN" + sst_ver="$WSREP_SST_OPT_SST_VER" fi } -# waits ~10 seconds for nc to open the port and then reports ready -# (regardless of timeout) +# +# Waits ~30 seconds for socat or nc to open the port and +# then reports ready, regardless of timeout. +# wait_for_listen() { - local PORT=$1 - local ADDR=$2 - local MODULE=$3 - for i in {1..50} - do - if [ "$OS" = "FreeBSD" ];then - sockstat -46lp $PORT | grep -qE "^[^ ]* *(socat|nc) *[^ ]* *[^ ]* *[^ ]* *[^ ]*:$PORT" && break - else - ss -p state listening "( sport = :$PORT )" | grep -qE 'socat|nc' && break + for i in {1..150}; do + if check_port "" "$SST_PORT" 'socat|nc'; then + break fi sleep 0.2 done - echo "ready ${ADDR}/${MODULE}//$sst_ver" + echo "ready $ADDR:$SST_PORT/$MODULE/$lsn/$sst_ver" } check_extra() { local use_socket=1 - if [[ $uextra -eq 1 ]];then - if $MY_PRINT_DEFAULTS --mysqld | tr '_' '-' | grep -- "--thread-handling=" | grep -q 'pool-of-threads';then - local eport=$($MY_PRINT_DEFAULTS --mysqld | tr '_' '-' | grep -- "--extra-port=" | cut -d= -f2) - if [[ -n $eport ]];then - # Xtrabackup works only locally. - # Hence, setting host to 127.0.0.1 unconditionally. + if [ $uextra -eq 1 ]; then + local thread_handling=$(parse_cnf '--mysqld' 'thread-handling') + if [ "$thread_handling" = 'pool-of-threads' ]; then + local eport=$(parse_cnf '--mysqld' 'extra-port') + if [ -n "$eport" ]; then + # mariabackup works only locally. + # Hence, setting host to 127.0.0.1 unconditionally: wsrep_log_info "SST through extra_port $eport" - INNOEXTRA+=" --host=127.0.0.1 --port=$eport" + INNOEXTRA="$INNOEXTRA --host=127.0.0.1 --port=$eport" use_socket=0 - else + else wsrep_log_error "Extra port $eport null, failing" exit 1 fi - else + else wsrep_log_info "Thread pool not set, ignore the option use_extra" fi fi - if [[ $use_socket -eq 1 ]] && [[ -n "$WSREP_SST_OPT_SOCKET" ]];then - INNOEXTRA+=" --socket=$WSREP_SST_OPT_SOCKET" + if [ $use_socket -eq 1 -a -n "$WSREP_SST_OPT_SOCKET" ]; then + INNOEXTRA="$INNOEXTRA --socket='$WSREP_SST_OPT_SOCKET'" fi } recv_joiner() { - local dir=$1 - local msg=$2 + local dir="$1" + local msg="$2" local tmt=$3 local checkf=$4 - local ltcmd + local wait=$5 - if [[ ! -d ${dir} ]];then + if [ ! -d "$dir" ]; then # This indicates that IST is in progress return fi - pushd ${dir} 1>/dev/null - set +e - - if [[ $tmt -gt 0 ]] && command -v timeout >/dev/null;then - if timeout --help | grep -q -- '-k';then - ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" - else - ltcmd="timeout -s9 $tmt $tcmd" + local ltcmd="$tcmd" + if [ $tmt -gt 0 ]; then + if [ -n "$(commandex timeout)" ]; then + if timeout --help | grep -qw -F -- '-k'; then + ltcmd="timeout -k $(( tmt+10 )) $tmt $tcmd" + else + ltcmd="timeout -s9 $tmt $tcmd" + fi fi - timeit "$msg" "$ltcmd | $strmcmd; RC=( "\${PIPESTATUS[@]}" )" - else - timeit "$msg" "$tcmd | $strmcmd; RC=( "\${PIPESTATUS[@]}" )" fi + if [ $wait -ne 0 ]; then + wait_for_listen & + fi + + cd "$dir" + set +e + timeit "$msg" "$ltcmd | $strmcmd; RC=( "\${PIPESTATUS[@]}" )" set -e - popd 1>/dev/null + cd "$OLD_PWD" - if [[ ${RC[0]} -eq 124 ]];then - wsrep_log_error "Possible timeout in receving first data from donor in gtid stage" + if [ ${RC[0]} -eq 124 ]; then + wsrep_log_error "Possible timeout in receiving first data from" \ + "donor in gtid stage: exit codes: ${RC[@]}" exit 32 fi - for ecode in "${RC[@]}";do - if [[ $ecode -ne 0 ]];then - wsrep_log_error "Error while getting data from donor node: " \ + for ecode in "${RC[@]}"; do + if [ $ecode -ne 0 ]; then + wsrep_log_error "Error while getting data from donor node:" \ "exit codes: ${RC[@]}" exit 32 fi done - if [[ $checkf -eq 1 && ! -r "${MAGIC_FILE}" ]];then - # this message should cause joiner to abort - wsrep_log_error "xtrabackup process ended without creating '${MAGIC_FILE}'" - wsrep_log_info "Contents of datadir" - wsrep_log_info "$(ls -l ${dir}/*)" - exit 32 + if [ $checkf -eq 1 ]; then + if [ ! -r "$MAGIC_FILE" ]; then + # this message should cause joiner to abort: + wsrep_log_error "receiving process ended without creating" \ + "magic file ($MAGIC_FILE)" + wsrep_log_info "Contents of datadir:" + wsrep_log_info $(ls -l "$dir/"*) + exit 32 + fi + + if [ -n "$MY_SECRET" ]; then + # Check donor supplied secret: + SECRET=$(grep -m1 -E "^$SECRET_TAG[[:space:]]" "$MAGIC_FILE" || :) + SECRET=$(trim_string "${SECRET#$SECRET_TAG}") + if [ "$SECRET" != "$MY_SECRET" ]; then + wsrep_log_error "Donor does not know my secret!" + wsrep_log_info "Donor: '$SECRET', my: '$MY_SECRET'" + exit 32 + fi + fi + + if [ $WSREP_SST_OPT_PROGRESS -eq 1 ]; then + # check total SST footprint + payload=$(grep -m1 -E "^$TOTAL_TAG[[:space:]]" "$MAGIC_FILE" || :) + if [ -n "$payload" ]; then + payload=$(trim_string "${payload#$TOTAL_TAG}") + if [ $payload -ge 0 ]; then + # report to parent + echo "$TOTAL_TAG $payload" + fi + fi + fi fi } - send_donor() { - local dir=$1 - local msg=$2 + local dir="$1" + local msg="$2" - pushd ${dir} 1>/dev/null + cd "$dir" set +e timeit "$msg" "$strmcmd | $tcmd; RC=( "\${PIPESTATUS[@]}" )" set -e - popd 1>/dev/null + cd "$OLD_PWD" - - for ecode in "${RC[@]}";do - if [[ $ecode -ne 0 ]];then - wsrep_log_error "Error while getting data from donor node: " \ + for ecode in "${RC[@]}"; do + if [ $ecode -ne 0 ]; then + wsrep_log_error "Error while sending data to joiner node:" \ "exit codes: ${RC[@]}" exit 32 fi done - } monitor_process() { local sst_stream_pid=$1 - while true ; do - - if ! ps -p "${WSREP_SST_OPT_PARENT}" &>/dev/null; then - wsrep_log_error "Parent mysqld process (PID:${WSREP_SST_OPT_PARENT}) terminated unexpectedly." + while :; do + if ! ps -p "$WSREP_SST_OPT_PARENT" >/dev/null 2>&1; then + wsrep_log_error \ + "Parent mysqld process (PID: $WSREP_SST_OPT_PARENT)" \ + "terminated unexpectedly." + kill -- -"$WSREP_SST_OPT_PARENT" exit 32 fi - - if ! ps -p "${sst_stream_pid}" &>/dev/null; then + if ! ps -p "$sst_stream_pid" >/dev/null 2>&1; then break fi - sleep 0.1 - done } -wsrep_check_programs "$INNOBACKUPEX_BIN" - -rm -f "${MAGIC_FILE}" - -if [[ ! ${WSREP_SST_OPT_ROLE} == 'joiner' && ! ${WSREP_SST_OPT_ROLE} == 'donor' ]];then - wsrep_log_error "Invalid role ${WSREP_SST_OPT_ROLE}" - exit 22 -fi +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" read_cnf setup_ports -if ${INNOBACKUPEX_BIN} /tmp --help 2>/dev/null | grep -q -- '--version-check'; then - disver="--no-version-check" +if [ "$progress" = 'none' ]; then + wsrep_log_info "All progress/rate-limiting disabled by configuration" +elif [ -z "$(commandex pv)" ]; then + wsrep_log_info "Progress reporting tool pv not found in path: $PATH" + wsrep_log_info "Disabling all progress/rate-limiting" + progress='none' fi -if [[ ${FORCE_FTWRL:-0} -eq 1 ]];then - wsrep_log_info "Forcing FTWRL due to environment variable FORCE_FTWRL equal to $FORCE_FTWRL" - iopts+=" --no-backup-locks " +if "$BACKUP_BIN" --help 2>/dev/null | grep -qw -F -- '--version-check'; then + disver=' --no-version-check' fi -INNOEXTRA=$WSREP_SST_OPT_MYSQLD - -INNODB_DATA_HOME_DIR=${INNODB_DATA_HOME_DIR:-""} -# Try to set INNODB_DATA_HOME_DIR from the command line: -if [ ! -z "$INNODB_DATA_HOME_DIR_ARG" ]; then - INNODB_DATA_HOME_DIR=$INNODB_DATA_HOME_DIR_ARG -fi -# if INNODB_DATA_HOME_DIR env. variable is not set, try to get it from my.cnf -if [ -z "$INNODB_DATA_HOME_DIR" ]; then - INNODB_DATA_HOME_DIR=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE innodb-data-home-dir '') -fi -if [ -z "$INNODB_DATA_HOME_DIR" ]; then - INNODB_DATA_HOME_DIR=$(parse_cnf --mysqld innodb-data-home-dir '') -fi -if [ ! -z "$INNODB_DATA_HOME_DIR" ]; then - INNOEXTRA+=" --innodb-data-home-dir=$INNODB_DATA_HOME_DIR" -fi +OLD_PWD="$(pwd)" -if [ -n "$INNODB_DATA_HOME_DIR" ]; then - # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd $DATA; mkdir -p "$INNODB_DATA_HOME_DIR"; cd $INNODB_DATA_HOME_DIR; pwd -P) -else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd $DATA; pwd -P) +if [ -n "$DATA" -a "$DATA" != '.' ]; then + [ ! -d "$DATA" ] && mkdir -p "$DATA" + cd "$DATA" fi +DATA_DIR="$(pwd)" -if [[ $ssyslog -eq 1 ]];then - - if ! command -v logger >/dev/null;then - wsrep_log_error "logger not in path: $PATH. Ignoring" - else +cd "$OLD_PWD" - wsrep_log_info "Logging all stderr of SST/Innobackupex to syslog" +if [ $ssyslog -eq 1 ]; then + if [ -n "$(commandex logger)" ]; then + wsrep_log_info "Logging all stderr of SST/mariabackup to syslog" exec 2> >(logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE) wsrep_log_error() { - logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE "$@" + logger -p daemon.err -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE "$@" } - wsrep_log_info() + wsrep_log_warning() { - logger -p daemon.info -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE "$@" + logger -p daemon.warning -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE "$@" } - INNOAPPLY="${INNOBACKUPEX_BIN} --innobackupex $disver $iapts \$INNOEXTRA --apply-log \$rebuildcmd \${DATA} 2>&1 | logger -p daemon.err -t ${ssystag}innobackupex-apply" - INNOMOVE="${INNOBACKUPEX_BIN} --innobackupex ${WSREP_SST_OPT_CONF} $disver $impts --move-back --force-non-empty-directories \${DATA} 2>&1 | logger -p daemon.err -t ${ssystag}innobackupex-move" - INNOBACKUP="${INNOBACKUPEX_BIN} --innobackupex ${WSREP_SST_OPT_CONF} $disver $iopts \$tmpopts \$INNOEXTRA --galera-info --stream=\$sfmt \$itmpdir 2> >(logger -p daemon.err -t ${ssystag}innobackupex-backup)" + wsrep_log_info() + { + logger -p daemon.info -t ${ssystag}wsrep-sst-$WSREP_SST_OPT_ROLE "$@" + } + else + wsrep_log_error "logger not in path: $PATH. Ignoring" fi - + INNOAPPLY="2>&1 | logger -p daemon.err -t ${ssystag}innobackupex-apply" + INNOMOVE="2>&1 | logger -p daemon.err -t ${ssystag}innobackupex-move" + INNOBACKUP="2> >(logger -p daemon.err -t ${ssystag}innobackupex-backup)" else - -if [[ "$sstlogarchive" -eq 1 ]] -then - ARCHIVETIMESTAMP=$(date "+%Y.%m.%d-%H.%M.%S.%N") - newfile="" - - if [[ ! -z "$sstlogarchivedir" ]] + if [ $sstlogarchive -eq 1 ] then - if [[ ! -d "$sstlogarchivedir" ]] - then - mkdir -p "$sstlogarchivedir" + ARCHIVETIMESTAMP=$(date '+%Y.%m.%d-%H.%M.%S.%N') + + if [ -n "$sstlogarchivedir" ]; then + if [ ! -d "$sstlogarchivedir" ]; then + if ! mkdir -p "$sstlogarchivedir"; then + sstlogarchivedir="" + wsrep_log_warning \ + "Unable to create '$sstlogarchivedir' directory" + fi + elif [ ! -w "$sstlogarchivedir" ]; then + sstlogarchivedir="" + wsrep_log_warning \ + "The '$sstlogarchivedir' directory is not writtable" + fi fi - fi - if [ -e "${INNOAPPLYLOG}" ] - then - if [[ ! -z "$sstlogarchivedir" ]] - then - newfile=$sstlogarchivedir/$(basename "${INNOAPPLYLOG}").${ARCHIVETIMESTAMP} - else - newfile=${INNOAPPLYLOG}.${ARCHIVETIMESTAMP} + if [ -e "$INNOAPPLYLOG" ]; then + if [ -n "$sstlogarchivedir" ]; then + newfile=$(basename "$INNOAPPLYLOG") + newfile="$sstlogarchivedir/$newfile.$ARCHIVETIMESTAMP" + else + newfile="$INNOAPPLYLOG.$ARCHIVETIMESTAMP" + fi + wsrep_log_info "Moving '$INNOAPPLYLOG' to '$newfile'" + mv "$INNOAPPLYLOG" "$newfile" && gzip "$newfile" || \ + wsrep_log_warning "Failed to archive log file ('$newfile')" fi - - wsrep_log_info "Moving ${INNOAPPLYLOG} to ${newfile}" - mv "${INNOAPPLYLOG}" "${newfile}" - gzip "${newfile}" - fi - if [ -e "${INNOMOVELOG}" ] - then - if [[ ! -z "$sstlogarchivedir" ]] - then - newfile=$sstlogarchivedir/$(basename "${INNOMOVELOG}").${ARCHIVETIMESTAMP} - else - newfile=${INNOMOVELOG}.${ARCHIVETIMESTAMP} + if [ -e "$INNOMOVELOG" ]; then + if [ -n "$sstlogarchivedir" ]; then + newfile=$(basename "$INNOMOVELOG") + newfile="$sstlogarchivedir/$newfile.$ARCHIVETIMESTAMP" + else + newfile="$INNOMOVELOG.$ARCHIVETIMESTAMP" + fi + wsrep_log_info "Moving '$INNOMOVELOG' to '$newfile'" + mv "$INNOMOVELOG" "$newfile" && gzip "$newfile" || \ + wsrep_log_warning "Failed to archive log file ('$newfile')" fi - wsrep_log_info "Moving ${INNOMOVELOG} to ${newfile}" - mv "${INNOMOVELOG}" "${newfile}" - gzip "${newfile}" - fi - - if [ -e "${INNOBACKUPLOG}" ] - then - if [[ ! -z "$sstlogarchivedir" ]] - then - newfile=$sstlogarchivedir/$(basename "${INNOBACKUPLOG}").${ARCHIVETIMESTAMP} - else - newfile=${INNOBACKUPLOG}.${ARCHIVETIMESTAMP} + if [ -e "$INNOBACKUPLOG" ]; then + if [ -n "$sstlogarchivedir" ]; then + newfile=$(basename "$INNOBACKUPLOG") + newfile="$sstlogarchivedir/$newfile.$ARCHIVETIMESTAMP" + else + newfile="$INNOBACKUPLOG.$ARCHIVETIMESTAMP" + fi + wsrep_log_info "Moving '$INNOBACKUPLOG' to '$newfile'" + mv "$INNOBACKUPLOG" "$newfile" && gzip "$newfile" || \ + wsrep_log_warning "Failed to archive log file ('$newfile')" fi - - wsrep_log_info "Moving ${INNOBACKUPLOG} to ${newfile}" - mv "${INNOBACKUPLOG}" "${newfile}" - gzip "${newfile}" fi - -fi - - INNOAPPLY="${INNOBACKUPEX_BIN} --innobackupex $disver $iapts \$INNOEXTRA --apply-log \$rebuildcmd \${DATA} &> ${INNOAPPLYLOG}" - INNOMOVE="${INNOBACKUPEX_BIN} --innobackupex ${WSREP_SST_OPT_CONF} $disver $impts --move-back --force-non-empty-directories \${DATA} &> ${INNOMOVELOG}" - INNOBACKUP="${INNOBACKUPEX_BIN} --innobackupex ${WSREP_SST_OPT_CONF} $disver $iopts \$tmpopts \$INNOEXTRA --galera-info --stream=\$sfmt \$itmpdir 2> ${INNOBACKUPLOG}" + INNOAPPLY="> '$INNOAPPLYLOG' 2>&1" + INNOMOVE="> '$INNOMOVELOG' 2>&1" + INNOBACKUP="2> '$INNOBACKUPLOG'" fi +setup_commands() +{ + local mysqld_args="" + if [ -n "$WSREP_SST_OPT_MYSQLD" ]; then + mysqld_args=" --mysqld-args $WSREP_SST_OPT_MYSQLD" + fi + local recovery="" + if [ -n "$INNODB_FORCE_RECOVERY" ]; then + recovery=" --innodb-force-recovery=$INNODB_FORCE_RECOVERY" + fi + INNOAPPLY="$BACKUP_BIN --prepare$disver$recovery${iapts:+ }$iapts$INNOEXTRA --target-dir='$DATA' --datadir='$DATA'$mysqld_args $INNOAPPLY" + INNOMOVE="$BACKUP_BIN$WSREP_SST_OPT_CONF --move-back$disver${impts:+ }$impts$INNOEXTRA --galera-info --force-non-empty-directories --target-dir='$DATA' --datadir='${TDATA:-$DATA}' $INNOMOVE" + INNOBACKUP="$BACKUP_BIN$WSREP_SST_OPT_CONF --backup$disver${iopts:+ }$iopts$tmpopts$INNOEXTRA --galera-info --stream=$sfmt --target-dir='$itmpdir' --datadir='$DATA'$mysqld_args $INNOBACKUP" +} + get_stream get_transfer -if [ "$WSREP_SST_OPT_ROLE" = "donor" ] -then - trap cleanup_donor EXIT +if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + + trap cleanup_at_exit EXIT if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - usrst=0 - if [[ -z $sst_ver ]];then + if [ -z "$sst_ver" ]; then wsrep_log_error "Upgrade joiner to 5.6.21 or higher for backup locks support" wsrep_log_error "The joiner is not supported for this version of donor" exit 93 fi - if [[ -z $(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE tmpdir "") && \ - -z $(parse_cnf --mysqld tmpdir "") && \ - -z $(parse_cnf xtrabackup tmpdir "") ]]; then - xtmpdir=$(mktemp -d) - tmpopts=" --tmpdir=$xtmpdir" - wsrep_log_info "Using $xtmpdir as xtrabackup temporary directory" + tmpdir=$(parse_cnf "$encgroups" 'tmpdir') + if [ -z "$tmpdir" ]; then + xtmpdir="$(mktemp -d)" + elif [ "$OS" = 'Linux' ]; then + xtmpdir=$(mktemp '-d' "--tmpdir=$tmpdir") + else + xtmpdir=$(TMPDIR="$tmpdir"; mktemp '-d') fi - itmpdir=$(mktemp -d) - wsrep_log_info "Using $itmpdir as innobackupex temporary directory" + wsrep_log_info "Using '$xtmpdir' as mariabackup temporary directory" + tmpopts=" --tmpdir='$xtmpdir'" + + itmpdir="$(mktemp -d)" + wsrep_log_info "Using '$itmpdir' as mariabackup working directory" - if [[ -n "${WSREP_SST_OPT_USER:-}" && "$WSREP_SST_OPT_USER" != "(null)" ]]; then - INNOEXTRA+=" --user=$WSREP_SST_OPT_USER" + usrst=0 + if [ -n "$WSREP_SST_OPT_USER" ]; then + INNOEXTRA="$INNOEXTRA --user='$WSREP_SST_OPT_USER'" usrst=1 fi - if [ -n "${WSREP_SST_OPT_PSWD:-}" ]; then - INNOEXTRA+=" --password=$WSREP_SST_OPT_PSWD" - elif [[ $usrst -eq 1 ]];then - # Empty password, used for testing, debugging etc. - INNOEXTRA+=" --password=" - fi - - get_keys - if [[ $encrypt -eq 1 ]];then - if [[ -n $ekey ]];then - INNOEXTRA+=" --encrypt=$ealgo --encrypt-key=$ekey" - else - INNOEXTRA+=" --encrypt=$ealgo --encrypt-key-file=$ekeyfile" - fi + if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" + elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD fi check_extra + if [ -n "$progress" -o $WSREP_SST_OPT_PROGRESS -eq 1 ]; then + wsrep_log_info "Estimating total transfer size" + get_footprint + wsrep_log_info "To transfer: $payload" + else + adjust_progress + fi + wsrep_log_info "Streaming GTID file before SST" # Store donor's wsrep GTID (state ID) and wsrep_gtid_domain_id # (separated by a space). - echo "${WSREP_SST_OPT_GTID} ${WSREP_SST_OPT_GTID_DOMAIN_ID}" > "${MAGIC_FILE}" + echo "$WSREP_SST_OPT_GTID $WSREP_SST_OPT_GTID_DOMAIN_ID" > "$MAGIC_FILE" + + if [ -n "$WSREP_SST_OPT_REMOTE_PSWD" ]; then + # Let joiner know that we know its secret + echo "$SECRET_TAG $WSREP_SST_OPT_REMOTE_PSWD" >> "$MAGIC_FILE" + fi + + if [ $WSREP_SST_OPT_PROGRESS -eq 1 ]; then + # Tell joiner what to expect: + echo "$TOTAL_TAG $payload" >> "$MAGIC_FILE" + fi ttcmd="$tcmd" - if [[ $encrypt -eq 1 ]];then - if [[ -n $scomp ]];then - tcmd=" $ecmd | $scomp | $tcmd " - else - tcmd=" $ecmd | $tcmd " - fi - elif [[ -n $scomp ]];then - tcmd=" $scomp | $tcmd " + if [ -n "$scomp" ]; then + tcmd="$scomp | $tcmd" + fi + + get_keys + if [ $encrypt -eq 1 ]; then + tcmd="$ecmd | $tcmd" fi - send_donor $DATA "${stagemsg}-gtid" + send_donor "$DATA" "$stagemsg-gtid" + # Restore the transport commmand to its original state tcmd="$ttcmd" - if [[ -n $progress ]];then - get_footprint - tcmd="$pcmd | $tcmd" - elif [[ -n $rlimit ]];then - adjust_progress - tcmd="$pcmd | $tcmd" + + if [ -n "$pcmd" ]; then + if [ -n "$rcmd" ]; then + # redirect pv stderr to rcmd for tagging and output to parent + tcmd="{ $pcmd 2>&3 | $tcmd; } 3>&1 | $rcmd" + else + # use user-configured pv output + tcmd="$pcmd | $tcmd" + fi fi wsrep_log_info "Sleeping before data transfer for SST" sleep 10 - wsrep_log_info "Streaming the backup to joiner at ${REMOTEIP} ${SST_PORT:-4444}" + wsrep_log_info "Streaming the backup to joiner at $REMOTEIP:$SST_PORT" - if [[ -n $scomp ]];then + # Add compression to the head of the stream (if specified) + if [ -n "$scomp" ]; then tcmd="$scomp | $tcmd" fi + # Add encryption to the head of the stream (if specified) + if [ $encrypt -eq 1 ]; then + tcmd="$ecmd | $tcmd" + fi + + iopts="--databases-exclude='lost+found'${iopts:+ }$iopts" + + if [ ${FORCE_FTWRL:-0} -eq 1 ]; then + wsrep_log_info "Forcing FTWRL due to environment variable" \ + "FORCE_FTWRL equal to $FORCE_FTWRL" + iopts="--no-backup-locks${iopts:+ }$iopts" + fi + + # if compression is enabled for backup files, then add the + # appropriate options to the mariabackup command line: + if [ "$compress" != 'none' ]; then + iopts="--compress${compress:+=$compress}${iopts:+ }$iopts" + if [ -n "$compress_threads" ]; then + iopts="--compress-threads=$compress_threads${iopts:+ }$iopts" + fi + if [ -n "$compress_chunk" ]; then + iopts="--compress-chunk-size=$compress_chunk${iopts:+ }$iopts" + fi + fi + + if [ -n "$backup_threads" ]; then + iopts="--parallel=$backup_threads${iopts:+ }$iopts" + fi + + setup_commands + set +e - timeit "${stagemsg}-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" + timeit "$stagemsg-SST" "$INNOBACKUP | $tcmd; RC=( "\${PIPESTATUS[@]}" )" set -e if [ ${RC[0]} -ne 0 ]; then - wsrep_log_error "${INNOBACKUPEX_BIN} finished with error: ${RC[0]}. " \ - "Check syslog or ${INNOBACKUPLOG} for details" - exit 22 - elif [[ ${RC[$(( ${#RC[@]}-1 ))]} -eq 1 ]];then - wsrep_log_error "$tcmd finished with error: ${RC[1]}" - exit 22 + wsrep_log_error "mariabackup finished with error: ${RC[0]}." \ + "Check syslog or '$INNOBACKUPLOG' for details" + exit 22 + elif [ ${RC[$(( ${#RC[@]}-1 ))]} -eq 1 ]; then + wsrep_log_error "$tcmd finished with error: ${RC[1]}" + exit 22 fi - # innobackupex implicitly writes PID to fixed location in $xtmpdir - XTRABACKUP_PID="$xtmpdir/xtrabackup_pid" - + # mariabackup implicitly writes PID to fixed location in $xtmpdir + BACKUP_PID="$xtmpdir/xtrabackup_pid" else # BYPASS FOR IST @@ -931,209 +1186,289 @@ then # Store donor's wsrep GTID (state ID) and wsrep_gtid_domain_id # (separated by a space). - echo "${WSREP_SST_OPT_GTID} ${WSREP_SST_OPT_GTID_DOMAIN_ID}" > "${MAGIC_FILE}" - echo "1" > "${DATA}/${IST_FILE}" + echo "$WSREP_SST_OPT_GTID $WSREP_SST_OPT_GTID_DOMAIN_ID" > "$MAGIC_FILE" + echo "1" > "$DATA/$IST_FILE" + + if [ -n "$scomp" ]; then + tcmd="$scomp | $tcmd" + fi + get_keys - if [[ $encrypt -eq 1 ]];then - if [[ -n $scomp ]];then - tcmd=" $ecmd | $scomp | $tcmd " - else - tcmd=" $ecmd | $tcmd " - fi - elif [[ -n $scomp ]];then - tcmd=" $scomp | $tcmd " + if [ $encrypt -eq 1 ]; then + tcmd="$ecmd | $tcmd" fi - strmcmd+=" \${IST_FILE}" - send_donor $DATA "${stagemsg}-IST" + strmcmd="$strmcmd '$IST_FILE'" + + send_donor "$DATA" "$stagemsg-IST" fi - echo "done ${WSREP_SST_OPT_GTID}" + echo "done $WSREP_SST_OPT_GTID" wsrep_log_info "Total time on donor: $totime seconds" -elif [ "${WSREP_SST_OPT_ROLE}" = "joiner" ] -then - [[ -e $SST_PROGRESS_FILE ]] && wsrep_log_info "Stale sst_in_progress file: $SST_PROGRESS_FILE" - [[ -n $SST_PROGRESS_FILE ]] && touch $SST_PROGRESS_FILE +else # joiner - ib_home_dir=$INNODB_DATA_HOME_DIR + [ -e "$SST_PROGRESS_FILE" ] && \ + wsrep_log_info "Stale sst_in_progress file: $SST_PROGRESS_FILE" + [ -n "$SST_PROGRESS_FILE" ] && touch "$SST_PROGRESS_FILE" - # Try to set ib_log_dir from the command line: - ib_log_dir=$INNODB_LOG_GROUP_HOME_ARG - if [ -z "$ib_log_dir" ]; then - ib_log_dir=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE innodb-log-group-home-dir "") + # if no command line argument and INNODB_DATA_HOME_DIR environment + # variable is not set, try to get it from the my.cnf: + if [ -z "$INNODB_DATA_HOME_DIR" ]; then + INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') + INNODB_DATA_HOME_DIR=$(trim_dir "$INNODB_DATA_HOME_DIR") fi - if [ -z "$ib_log_dir" ]; then - ib_log_dir=$(parse_cnf --mysqld innodb-log-group-home-dir "") + + if [ -n "$INNODB_DATA_HOME_DIR" -a "$INNODB_DATA_HOME_DIR" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" + ib_home_dir="$(pwd)" + cd "$OLD_PWD" fi - # Try to set ib_undo_dir from the command line: - ib_undo_dir=$INNODB_UNDO_DIR_ARG - if [ -z "$ib_undo_dir" ]; then - ib_undo_dir=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE innodb-undo-directory "") + # if no command line argument and INNODB_LOG_GROUP_HOME is not set, + # then try to get it from the my.cnf: + if [ -z "$INNODB_LOG_GROUP_HOME" ]; then + INNODB_LOG_GROUP_HOME=$(parse_cnf '--mysqld' 'innodb-log-group-home-dir') + INNODB_LOG_GROUP_HOME=$(trim_dir "$INNODB_LOG_GROUP_HOME") fi - if [ -z "$ib_undo_dir" ]; then - ib_undo_dir=$(parse_cnf --mysqld innodb-undo-directory "") + + if [ -n "$INNODB_LOG_GROUP_HOME" -a "$INNODB_LOG_GROUP_HOME" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_LOG_GROUP_HOME" ] && mkdir -p "$INNODB_LOG_GROUP_HOME" + cd "$INNODB_LOG_GROUP_HOME" + ib_log_dir="$(pwd)" + cd "$OLD_PWD" fi - stagemsg="Joiner-Recv" + # if no command line argument and INNODB_UNDO_DIR is not set, + # then try to get it from the my.cnf: + if [ -z "$INNODB_UNDO_DIR" ]; then + INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') + INNODB_UNDO_DIR=$(trim_dir "$INNODB_UNDO_DIR") + fi + if [ -n "$INNODB_UNDO_DIR" -a "$INNODB_UNDO_DIR" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" + ib_undo_dir="$(pwd)" + cd "$OLD_PWD" + fi - sencrypted=1 - nthreads=1 + if [ -n "$backup_threads" ]; then + impts="--parallel=$backup_threads${impts:+ }$impts" + fi - MODULE="xtrabackup_sst" + SST_PID="$WSREP_SST_OPT_DATA/wsrep_sst.pid" - rm -f "${DATA}/${IST_FILE}" + # give some time for previous SST to complete: + check_round=0 + while check_pid "$SST_PID" 0; do + wsrep_log_info "previous SST is not completed, waiting for it to exit" + check_round=$(( check_round + 1 )) + if [ $check_round -eq 10 ]; then + wsrep_log_error "previous SST script still running." + exit 114 # EALREADY + fi + sleep 1 + done - # May need xtrabackup_checkpoints later on - rm -f ${DATA}/xtrabackup_binary ${DATA}/xtrabackup_galera_info ${DATA}/ib_logfile0 + trap simple_cleanup EXIT + echo $$ > "$SST_PID" - ADDR=${WSREP_SST_OPT_ADDR} - if [ -z "${SST_PORT}" ] - then - SST_PORT=4444 - if [ "${ADDR#\[}" != "$ADDR" ]; then - ADDR="$(echo ${WSREP_SST_OPT_ADDR} | awk -F '\\]:' '{ print $1 }')]:${SST_PORT}" - else - ADDR="$(echo ${WSREP_SST_OPT_ADDR} | awk -F ':' '{ print $1 }'):${SST_PORT}" - fi - fi + stagemsg='Joiner-Recv' - wait_for_listen ${SST_PORT} ${ADDR} ${MODULE} & + MODULE="${WSREP_SST_OPT_MODULE:-xtrabackup_sst}" - trap sig_joiner_cleanup HUP PIPE INT TERM - trap cleanup_joiner EXIT + [ -f "$DATA/$IST_FILE" ] && rm -f "$DATA/$IST_FILE" - if [[ -n $progress ]];then - adjust_progress - tcmd+=" | $pcmd" + # May need xtrabackup_checkpoints later on + [ -f "$DATA/xtrabackup_binary" ] && rm -f "$DATA/xtrabackup_binary" + [ -f "$DATA/xtrabackup_galera_info" ] && rm -f "$DATA/xtrabackup_galera_info" + + ADDR="$WSREP_SST_OPT_HOST" + + if [ "${tmode#VERIFY}" != "$tmode" ]; then + # backward-incompatible behavior: + CN="" + if [ -n "$tpem" ]; then + # find out my Common Name + get_openssl + if [ -z "$OPENSSL_BINARY" ]; then + wsrep_log_error \ + 'openssl not found but it is required for authentication' + exit 42 + fi + CN=$("$OPENSSL_BINARY" x509 -noout -subject -in "$tpem" | \ + tr ',' '\n' | grep -F 'CN =' | cut -d '=' -f2 | sed s/^\ // | \ + sed s/\ %//) + fi + MY_SECRET="$(wsrep_gen_secret)" + # Add authentication data to address + ADDR="$CN:$MY_SECRET@$ADDR" + else + MY_SECRET="" # for check down in recv_joiner() fi get_keys - if [[ $encrypt -eq 1 && $sencrypted -eq 1 ]];then - if [[ -n $sdecomp ]];then - strmcmd=" $sdecomp | $ecmd | $strmcmd" - else - strmcmd=" $ecmd | $strmcmd" - fi - elif [[ -n $sdecomp ]];then - strmcmd=" $sdecomp | $strmcmd" + if [ $encrypt -eq 1 ]; then + strmcmd="$ecmd | $strmcmd" fi - STATDIR=$(mktemp -d) - MAGIC_FILE="${STATDIR}/${INFO_FILE}" - recv_joiner $STATDIR "${stagemsg}-gtid" $stimeout 1 + if [ -n "$sdecomp" ]; then + strmcmd="$sdecomp | $strmcmd" + fi + check_sockets_utils - if ! ps -p ${WSREP_SST_OPT_PARENT} &>/dev/null - then - wsrep_log_error "Parent mysqld process (PID:${WSREP_SST_OPT_PARENT}) terminated unexpectedly." + trap cleanup_at_exit EXIT + + STATDIR="$(mktemp -d)" + MAGIC_FILE="$STATDIR/$INFO_FILE" + + recv_joiner "$STATDIR" "$stagemsg-gtid" $stimeout 1 1 + + if ! ps -p "$WSREP_SST_OPT_PARENT" >/dev/null 2>&1; then + wsrep_log_error "Parent mysqld process (PID: $WSREP_SST_OPT_PARENT)" \ + "terminated unexpectedly." exit 32 fi - if [ ! -r "${STATDIR}/${IST_FILE}" ] - then + if [ ! -r "$STATDIR/$IST_FILE" ]; then + + adjust_progress + if [ -n "$pcmd" ]; then + if [ -n "$rcmd" ]; then + # redirect pv stderr to rcmd for tagging and output to parent + strmcmd="{ $pcmd 2>&3 | $strmcmd; } 3>&1 | $rcmd" + else + # use user-configured pv output + strmcmd="$pcmd | $strmcmd" + fi + fi - if [[ -d ${DATA}/.sst ]];then - wsrep_log_info "WARNING: Stale temporary SST directory: ${DATA}/.sst from previous state transfer. Removing" - rm -rf ${DATA}/.sst + if [ -d "$DATA/.sst" ]; then + wsrep_log_info \ + "WARNING: Stale temporary SST directory:" \ + "'$DATA/.sst' from previous state transfer, removing..." + rm -rf "$DATA/.sst" fi - mkdir -p ${DATA}/.sst - (recv_joiner $DATA/.sst "${stagemsg}-SST" 0 0) & + mkdir -p "$DATA/.sst" + (recv_joiner "$DATA/.sst" "$stagemsg-SST" 0 0 0) & jpid=$! wsrep_log_info "Proceeding with SST" - wsrep_log_info "Cleaning the existing datadir and innodb-data/log directories" - if [ "${OS}" = "FreeBSD" ]; then - find -E $ib_home_dir $ib_log_dir $ib_undo_dir $DATA -mindepth 1 -prune -regex $cpat -o -exec rm -rfv {} 1>&2 \+ - else - find $ib_home_dir $ib_log_dir $ib_undo_dir $DATA -mindepth 1 -prune -regex $cpat -o -exec rm -rfv {} 1>&2 \+ - fi - - tempdir=$LOG_BIN_ARG - if [ -z "$tempdir" ]; then - tempdir=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE log-bin "") - fi - if [ -z "$tempdir" ]; then - tempdir=$(parse_cnf --mysqld log-bin "") - fi - if [[ -n ${tempdir:-} ]];then - binlog_dir=$(dirname $tempdir) - binlog_file=$(basename $tempdir) - if [[ -n ${binlog_dir:-} && $binlog_dir != '.' && $binlog_dir != $DATA ]];then - pattern="$binlog_dir/$binlog_file\.[0-9]+$" - wsrep_log_info "Cleaning the binlog directory $binlog_dir as well" - find $binlog_dir -maxdepth 1 -type f -regex $pattern -exec rm -fv {} 1>&2 \+ || true - rm $binlog_dir/*.index || true + get_binlog + + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + binlog_dir=$(dirname "$WSREP_SST_OPT_BINLOG") + binlog_base=$(basename "$WSREP_SST_OPT_BINLOG") + binlog_index="$WSREP_SST_OPT_BINLOG_INDEX" + cd "$DATA" + wsrep_log_info "Cleaning the old binary logs" + # If there is a file with binlogs state, delete it: + [ -f "$binlog_base.state" ] && rm "$binlog_base.state" >&2 + # Clean up the old binlog files and index: + if [ -f "$binlog_index" ]; then + while read bin_file || [ -n "$bin_file" ]; do + rm -f "$bin_file" >&2 || : + done < "$binlog_index" + rm "$binlog_index" >&2 + fi + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ + -d "$binlog_dir" ] + then + cd "$binlog_dir" + if [ "$(pwd)" != "$DATA_DIR" ]; then + wsrep_log_info \ + "Cleaning the binlog directory '$binlog_dir' as well" + fi fi + rm -f "$binlog_base".[0-9]* >&2 || : + cd "$OLD_PWD" fi + wsrep_log_info \ + "Cleaning the existing datadir and innodb-data/log directories" + if [ "$OS" = 'FreeBSD' ]; then + find -E ${ib_home_dir:+"$ib_home_dir"} \ + ${ib_undo_dir:+"$ib_undo_dir"} \ + ${ib_log_dir:+"$ib_log_dir"} \ + "$DATA" -mindepth 1 -prune -regex "$cpat" \ + -o -exec rm -rf {} >&2 \+ + else + find ${ib_home_dir:+"$ib_home_dir"} \ + ${ib_undo_dir:+"$ib_undo_dir"} \ + ${ib_log_dir:+"$ib_log_dir"} \ + "$DATA" -mindepth 1 -prune -regex "$cpat" \ + -o -exec rm -rf {} >&2 \+ + fi + TDATA="$DATA" + DATA="$DATA/.sst" - TDATA=${DATA} - DATA="${DATA}/.sst" - - - MAGIC_FILE="${DATA}/${INFO_FILE}" + MAGIC_FILE="$DATA/$INFO_FILE" wsrep_log_info "Waiting for SST streaming to complete!" monitor_process $jpid - get_proc - - if [[ ! -s ${DATA}/xtrabackup_checkpoints ]];then - wsrep_log_error "xtrabackup_checkpoints missing, failed innobackupex/SST on donor" + if [ ! -s "$DATA/xtrabackup_checkpoints" ]; then + wsrep_log_error "xtrabackup_checkpoints missing," \ + "failed mariabackup/SST on donor" exit 2 fi - # Rebuild indexes for compact backups - if grep -q 'compact = 1' ${DATA}/xtrabackup_checkpoints;then + # Compact backups are not supported by mariabackup + if grep -qw -F 'compact = 1' "$DATA/xtrabackup_checkpoints"; then wsrep_log_info "Index compaction detected" - rebuild=1 - fi - - if [[ $rebuild -eq 1 ]];then - nthreads=$(parse_cnf xtrabackup rebuild-threads $nproc) - wsrep_log_info "Rebuilding during prepare with $nthreads threads" - rebuildcmd="--rebuild-indexes --rebuild-threads=$nthreads" + wsrel_log_error "Compact backups are not supported by mariabackup" + exit 2 fi - if test -n "$(find ${DATA} -maxdepth 1 -type f -name '*.qp' -print -quit)";then - + qpfiles=$(find "$DATA" -maxdepth 1 -type f -name '*.qp' -print -quit) + if [ -n "$qpfiles" ]; then wsrep_log_info "Compressed qpress files found" - if ! command -v qpress >/dev/null;then - wsrep_log_error "qpress not found in path: $PATH" + if [ -z "$(commandex qpress)" ]; then + wsrep_log_error "qpress utility not found in the path" exit 22 fi - if [[ -n $progress ]] && pv --help | grep -q 'line-mode';then - count=$(find ${DATA} -type f -name '*.qp' | wc -l) + get_proc + + dcmd="xargs -n 2 qpress -dT$nproc" + + if [ -n "$progress" -a "$progress" != 'none' ] && \ + pv --help | grep -qw -F -- '--line-mode' + then + count=$(find "$DATA" -maxdepth 1 -type f -name '*.qp' | wc -l) count=$(( count*2 )) - if pv --help | grep -q FORMAT;then - pvopts="-f -s $count -l -N Decompression -F '%N => Rate:%r Elapsed:%t %e Progress: [%b/$count]'" - else - pvopts="-f -s $count -l -N Decompression" - fi - pcmd="pv $pvopts" + pvopts='-f -l -N Decompression' + pvformat="-F '%N => Rate:%r Elapsed:%t %e Progress: [%b/$count]'" + payload=$count adjust_progress - dcmd="$pcmd | xargs -n 2 qpress -T${nproc}d" - else - dcmd="xargs -n 2 qpress -T${nproc}d" + dcmd="$pcmd | $dcmd" fi - - # Decompress the qpress files + # Decompress the qpress files wsrep_log_info "Decompression with $nproc threads" - timeit "Joiner-Decompression" "find ${DATA} -type f -name '*.qp' -printf '%p\n%h\n' | $dcmd" + timeit 'Joiner-Decompression' \ + "find '$DATA' -type f -name '*.qp' -printf '%p\n%h\n' | \ + $dcmd" extcode=$? - if [[ $extcode -eq 0 ]];then + if [ $extcode -eq 0 ]; then wsrep_log_info "Removing qpress files after decompression" - find ${DATA} -type f -name '*.qp' -delete - if [[ $? -ne 0 ]];then - wsrep_log_error "Something went wrong with deletion of qpress files. Investigate" + find "$DATA" -type f -name '*.qp' -delete + if [ $? -ne 0 ]; then + wsrep_log_error \ + "Something went wrong with deletion of qpress files." \ + "Investigate" fi else wsrep_log_error "Decompression failed. Exit code: $extcode" @@ -1141,59 +1476,84 @@ then fi fi + wsrep_log_info "Preparing the backup at $DATA" + setup_commands + timeit 'mariabackup prepare stage' "$INNOAPPLY" + if [ $? -ne 0 ]; then + wsrep_log_error "mariabackup apply finished with errors." \ + "Check syslog or '$INNOAPPLYLOG' for details." + exit 22 + fi - if [[ ! -z $WSREP_SST_OPT_BINLOG ]];then - - BINLOG_DIRNAME=$(dirname $WSREP_SST_OPT_BINLOG) - BINLOG_FILENAME=$(basename $WSREP_SST_OPT_BINLOG) - - # To avoid comparing data directory and BINLOG_DIRNAME - mv $DATA/${BINLOG_FILENAME}.* $BINLOG_DIRNAME/ 2>/dev/null || true - - pushd $BINLOG_DIRNAME &>/dev/null - for bfiles in $(ls -1 ${BINLOG_FILENAME}.[0-9]*);do - echo ${BINLOG_DIRNAME}/${bfiles} >> ${BINLOG_FILENAME}.index - done - popd &> /dev/null - + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + cd "$DATA" + binlogs="" + if [ -f 'xtrabackup_binlog_info' ]; then + NL=$'\n' + while read bin_string || [ -n "$bin_string" ]; do + bin_file=$(echo "$bin_string" | cut -f1) + if [ -f "$bin_file" ]; then + binlogs="$binlogs${binlogs:+$NL}$bin_file" + fi + done < 'xtrabackup_binlog_info' + else + binlogs=$(ls -d -1 "$binlog_base".[0-9]* 2>/dev/null || :) + fi + cd "$DATA_DIR" + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' ]; then + [ ! -d "$binlog_dir" ] && mkdir -p "$binlog_dir" + fi + index_dir=$(dirname "$binlog_index"); + if [ -n "$index_dir" -a "$index_dir" != '.' ]; then + [ ! -d "$index_dir" ] && mkdir -p "$index_dir" + fi + if [ -n "$binlogs" ]; then + wsrep_log_info "Moving binary logs to $binlog_dir" + echo "$binlogs" | \ + while read bin_file || [ -n "$bin_file" ]; do + mv "$DATA/$bin_file" "$binlog_dir" + echo "$binlog_dir${binlog_dir:+/}$bin_file" >> "$binlog_index" + done + fi + cd "$OLD_PWD" fi - wsrep_log_info "Preparing the backup at ${DATA}" - timeit "Xtrabackup prepare stage" "$INNOAPPLY" + MAGIC_FILE="$TDATA/$INFO_FILE" - if [ $? -ne 0 ]; - then - wsrep_log_error "${INNOBACKUPEX_BIN} apply finished with errors. Check syslog or ${INNOAPPLYLOG} for details" + wsrep_log_info "Moving the backup to $TDATA" + timeit 'mariabackup move stage' "$INNOMOVE" + if [ $? -eq 0 ]; then + wsrep_log_info "Move successful, removing $DATA" + rm -rf "$DATA" + DATA="$TDATA" + else + wsrep_log_error "Move failed, keeping '$DATA' for further diagnosis" + wsrep_log_error "Check syslog or '$INNOMOVELOG' for details" exit 22 fi - MAGIC_FILE="${TDATA}/${INFO_FILE}" - set +e - set -e - wsrep_log_info "Moving the backup to ${TDATA}" - timeit "Xtrabackup move stage" "$INNOMOVE" - if [[ $? -eq 0 ]];then - wsrep_log_info "Move successful, removing ${DATA}" - rm -rf $DATA - DATA=${TDATA} - else - wsrep_log_error "Move failed, keeping ${DATA} for further diagnosis" - wsrep_log_error "Check syslog or ${INNOMOVELOG} for details" - exit 22 - fi + else + wsrep_log_info "'$IST_FILE' received from donor: Running IST" + if [ $WSREP_SST_OPT_BYPASS -eq 0 ]; then + readonly WSREP_SST_OPT_BYPASS=1 + readonly WSREP_TRANSFER_TYPE='IST' + fi - else - wsrep_log_info "${IST_FILE} received from donor: Running IST" fi - if [[ ! -r ${MAGIC_FILE} ]];then - wsrep_log_error "SST magic file ${MAGIC_FILE} not found/readable" + if [ ! -r "$MAGIC_FILE" ]; then + wsrep_log_error "SST magic file '$MAGIC_FILE' not found/readable" exit 2 fi - wsrep_log_info "Galera co-ords from recovery: $(cat ${MAGIC_FILE})" - cat "${MAGIC_FILE}" # Output : UUID:seqno wsrep_gtid_domain_id + + # Remove special tags from the magic file, and from the output: + coords=$(head -n1 "$MAGIC_FILE") + wsrep_log_info "Galera co-ords from recovery: $coords" + echo "$coords" # Output : UUID:seqno wsrep_gtid_domain_id + wsrep_log_info "Total time on joiner: $totime seconds" fi +wsrep_log_info "$WSREP_METHOD $WSREP_TRANSFER_TYPE completed on $WSREP_SST_OPT_ROLE" exit 0 diff --git a/scripts/wsrep_sst_mysqldump.sh b/scripts/wsrep_sst_mysqldump.sh index 664fd2c04f9..e1efcbf11ad 100644 --- a/scripts/wsrep_sst_mysqldump.sh +++ b/scripts/wsrep_sst_mysqldump.sh @@ -1,5 +1,9 @@ -#!/bin/bash -ue +#!/usr/bin/env bash + +set -ue + # Copyright (C) 2009-2015 Codership Oy +# Copyright (C) 2017-2022 MariaDB # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,34 +21,17 @@ # This is a reference script for mysqldump-based state snapshot tansfer -. $(dirname $0)/wsrep_sst_common -PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin +. $(dirname "$0")/wsrep_sst_common EINVAL=22 -local_ip() -{ - [ "$1" = "127.0.0.1" ] && return 0 - [ "$1" = "localhost" ] && return 0 - [ "$1" = "[::1]" ] && return 0 - [ "$1" = "$(hostname -s)" ] && return 0 - [ "$1" = "$(hostname -f)" ] && return 0 - [ "$1" = "$(hostname -d)" ] && return 0 - - # Now if ip program is not found in the path, we can't return 0 since - # it would block any address. Thankfully grep should fail in this case - ip route get "$1" | grep local >/dev/null && return 0 - - return 1 -} - if test -z "$WSREP_SST_OPT_HOST"; then wsrep_log_error "HOST cannot be nil"; exit $EINVAL; fi if test -z "$WSREP_SST_OPT_PORT"; then wsrep_log_error "PORT cannot be nil"; exit $EINVAL; fi if test -z "$WSREP_SST_OPT_LPORT"; then wsrep_log_error "LPORT cannot be nil"; exit $EINVAL; fi if test -z "$WSREP_SST_OPT_SOCKET";then wsrep_log_error "SOCKET cannot be nil";exit $EINVAL; fi if test -z "$WSREP_SST_OPT_GTID"; then wsrep_log_error "GTID cannot be nil"; exit $EINVAL; fi -if local_ip $WSREP_SST_OPT_HOST && \ +if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED" && \ [ "$WSREP_SST_OPT_PORT" = "$WSREP_SST_OPT_LPORT" ] then wsrep_log_error \ @@ -53,14 +40,18 @@ then fi # Check client version -if ! $MYSQL_CLIENT --version | grep 'Distrib 10\.[1-9]' >/dev/null -then +if ! $MYSQL_CLIENT --version | grep -q -E 'Distrib 10\.[1-9]'; then $MYSQL_CLIENT --version >&2 wsrep_log_error "this operation requires MySQL client version 10.1 or newer" exit $EINVAL fi -[ -n "$WSREP_SST_OPT_USER" ] && AUTH="-u$WSREP_SST_OPT_USER" || AUTH= +AUTH="" +usrst=0 +if [ -n "$WSREP_SST_OPT_USER" ]; then + AUTH="-u$WSREP_SST_OPT_USER" + usrst=1 +fi # Refs https://github.com/codership/mysql-wsrep/issues/141 # Passing password in MYSQL_PWD environment variable is considered @@ -71,9 +62,14 @@ fi # whereas (at least on Linux) unprivileged user can't see process environment # that he does not own. So while it may be not secure in the NSA sense of the # word, it is arguably more secure than passing password on the command line. -[ -n "$WSREP_SST_OPT_PSWD" ] && export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +if [ -n "$WSREP_SST_OPT_PSWD" ]; then + export MYSQL_PWD="$WSREP_SST_OPT_PSWD" +elif [ $usrst -eq 1 ]; then + # Empty password, used for testing, debugging etc. + unset MYSQL_PWD +fi -STOP_WSREP="SET wsrep_on=OFF;" +STOP_WSREP='SET wsrep_on=OFF;' # mysqldump cannot restore CSV tables, fix this issue CSV_TABLES_FIX=" @@ -83,13 +79,13 @@ USE mysql; SET @cond = (SELECT (SUPPORT = 'YES' or SUPPORT = 'DEFAULT') FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'csv'); -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"General log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS general_log ( event_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL, server_id int(10) unsigned NOT NULL, command_type varchar(64) NOT NULL, argument mediumtext NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"General log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; DROP PREPARE stmt; -SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT=\"Slow log\"', 'SET @dummy = 0'); +SET @stmt = IF (@cond = '1', 'CREATE TABLE IF NOT EXISTS slow_log ( start_time timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host mediumtext NOT NULL, query_time time(6) NOT NULL, lock_time time(6) NOT NULL, rows_sent int(11) NOT NULL, rows_examined int(11) NOT NULL, db varchar(512) NOT NULL, last_insert_id int(11) NOT NULL, insert_id int(11) NOT NULL, server_id int(10) unsigned NOT NULL, sql_text mediumtext NOT NULL, thread_id bigint(21) unsigned NOT NULL) ENGINE=CSV DEFAULT CHARSET=utf8mb3 COMMENT=\"Slow log\"', 'SET @dummy = 0'); PREPARE stmt FROM @stmt; EXECUTE stmt; @@ -98,31 +94,30 @@ DROP PREPARE stmt;" SET_START_POSITION="SET GLOBAL wsrep_start_position='$WSREP_SST_OPT_GTID';" SET_WSREP_GTID_DOMAIN_ID="" -if [ -n $WSREP_SST_OPT_GTID_DOMAIN_ID ] -then - SET_WSREP_GTID_DOMAIN_ID=" - SET @val = (SELECT GLOBAL_VALUE FROM INFORMATION_SCHEMA.SYSTEM_VARIABLES WHERE VARIABLE_NAME = 'WSREP_GTID_STRICT_MODE' AND GLOBAL_VALUE > 0); - SET @stmt = IF (@val IS NOT NULL, 'SET GLOBAL WSREP_GTID_DOMAIN_ID=$WSREP_SST_OPT_GTID_DOMAIN_ID', 'SET @dummy = 0'); - PREPARE stmt FROM @stmt; - EXECUTE stmt; - DROP PREPARE stmt;" +if [ -n "$WSREP_SST_OPT_GTID_DOMAIN_ID" ]; then + SET_WSREP_GTID_DOMAIN_ID=" + SET @val = (SELECT GLOBAL_VALUE FROM INFORMATION_SCHEMA.SYSTEM_VARIABLES WHERE VARIABLE_NAME = 'WSREP_GTID_STRICT_MODE' AND GLOBAL_VALUE > 0); + SET @stmt = IF (@val IS NOT NULL, 'SET GLOBAL WSREP_GTID_DOMAIN_ID=$WSREP_SST_OPT_GTID_DOMAIN_ID', 'SET @dummy = 0'); + PREPARE stmt FROM @stmt; + EXECUTE stmt; + DROP PREPARE stmt;" fi -MYSQL="$MYSQL_CLIENT $WSREP_SST_OPT_CONF "\ -"$AUTH -h${WSREP_SST_OPT_HOST_UNESCAPED} "\ +MYSQL="$MYSQL_CLIENT$WSREP_SST_OPT_CONF_UNQUOTED "\ +"$AUTH -h$WSREP_SST_OPT_HOST_UNESCAPED "\ "-P$WSREP_SST_OPT_PORT --disable-reconnect --connect_timeout=10" # Check if binary logging is enabled on the joiner node. # Note: SELECT cannot be used at this point. -LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL |\ +LOG_BIN=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'log_bin'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Check the joiner node's server version. -SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL |\ +SERVER_VERSION=$(echo "set statement wsrep_sync_wait=0 for SHOW VARIABLES LIKE 'version'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') # Retrieve the donor's @@global.gtid_binlog_state. -GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL |\ +GTID_BINLOG_STATE=$(echo "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_state'" | $MYSQL | \ tail -1 | awk -F ' ' '{ print $2 }') RESET_MASTER="" @@ -130,50 +125,49 @@ SET_GTID_BINLOG_STATE="" SQL_LOG_BIN_OFF="" # Safety check -if [ "${SERVER_VERSION%%.*}" != '5' ] -then - # If binary logging is enabled on the joiner node, we need to copy donor's - # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be - # executed to erase binary logs (if any). Binary logging should also be - # turned off for the session so that gtid state does not get altered while - # the dump gets replayed on joiner. - if [[ "$LOG_BIN" == 'ON' ]]; then - RESET_MASTER="RESET MASTER;" - SET_GTID_BINLOG_STATE="SET @@global.gtid_binlog_state='$GTID_BINLOG_STATE';" - SQL_LOG_BIN_OFF="SET @@session.sql_log_bin=OFF;" - fi +if [ ${SERVER_VERSION%%.*} -gt 5 ]; then + # If binary logging is enabled on the joiner node, we need to copy donor's + # gtid_binlog_state to joiner. In order to do that, a RESET MASTER must be + # executed to erase binary logs (if any). Binary logging should also be + # turned off for the session so that gtid state does not get altered while + # the dump gets replayed on joiner. + if [ "$LOG_BIN" = 'ON' ]; then + RESET_MASTER="SET GLOBAL wsrep_on=OFF; RESET MASTER; SET GLOBAL wsrep_on=ON;" + SET_GTID_BINLOG_STATE="SET GLOBAL wsrep_on=OFF; SET @@global.gtid_binlog_state='$GTID_BINLOG_STATE'; SET GLOBAL wsrep_on=ON;" + SQL_LOG_BIN_OFF="SET @@session.sql_log_bin=OFF;" + fi fi # NOTE: we don't use --routines here because we're dumping mysql.proc table -MYSQLDUMP="$MYSQLDUMP $WSREP_SST_OPT_CONF $AUTH -S$WSREP_SST_OPT_SOCKET \ +MYSQLDUMP="$MYSQLDUMP$WSREP_SST_OPT_CONF_UNQUOTED $AUTH -S$WSREP_SST_OPT_SOCKET \ --add-drop-database --add-drop-table --skip-add-locks --create-options \ --disable-keys --extended-insert --skip-lock-tables --quick --set-charset \ --skip-comments --flush-privileges --all-databases --events" -# need to disable logging when loading the dump -# reason is that dump contains ALTER TABLE for log tables, and -# this causes an error if logging is enabled -GENERAL_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG"` -SLOW_LOG_OPT=`$MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG"` -$MYSQL -e "$STOP_WSREP SET GLOBAL GENERAL_LOG=OFF" -$MYSQL -e "$STOP_WSREP SET GLOBAL SLOW_QUERY_LOG=OFF" - -# commands to restore log settings -RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" -RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" - - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] then - (echo $STOP_WSREP && echo $RESET_MASTER && \ - echo $SET_GTID_BINLOG_STATE && echo $SQL_LOG_BIN_OFF && \ - echo $STOP_WSREP && $MYSQLDUMP && echo $CSV_TABLES_FIX && \ - echo $RESTORE_GENERAL_LOG && echo $RESTORE_SLOW_QUERY_LOG && \ - echo $SET_START_POSITION && echo $SET_WSREP_GTID_DOMAIN_ID \ - || echo "SST failed to complete;") | $MYSQL + # need to disable logging when loading the dump + # reason is that dump contains ALTER TABLE for log tables, and + # this causes an error if logging is enabled + GENERAL_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@GENERAL_LOG") + SLOW_LOG_OPT=$($MYSQL --skip-column-names -e "$STOP_WSREP SELECT @@SLOW_QUERY_LOG") + + LOG_OFF="SET GLOBAL GENERAL_LOG=OFF; SET GLOBAL SLOW_QUERY_LOG=OFF;" + + # commands to restore log settings + RESTORE_GENERAL_LOG="SET GLOBAL GENERAL_LOG=$GENERAL_LOG_OPT;" + RESTORE_SLOW_QUERY_LOG="SET GLOBAL SLOW_QUERY_LOG=$SLOW_LOG_OPT;" + + (echo "$STOP_WSREP" && echo "$LOG_OFF" && echo "$RESET_MASTER" && \ + echo "$SET_GTID_BINLOG_STATE" && echo "$SQL_LOG_BIN_OFF" && \ + echo "$STOP_WSREP" && $MYSQLDUMP && echo "$CSV_TABLES_FIX" && \ + echo "$RESTORE_GENERAL_LOG" && echo "$RESTORE_SLOW_QUERY_LOG" && \ + echo "$SET_START_POSITION" && echo "$SET_WSREP_GTID_DOMAIN_ID" \ + || echo "SST failed to complete;") | $MYSQL || exit $? else wsrep_log_info "Bypassing state dump." - echo $SET_START_POSITION | $MYSQL + echo "$SET_START_POSITION" | $MYSQL || exit $? fi -# +wsrep_log_info "$WSREP_METHOD $WSREP_TRANSFER_TYPE completed on $WSREP_SST_OPT_ROLE" +exit 0 diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 3bd2af1ad5c..ddf41559c29 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -1,6 +1,9 @@ -#!/bin/bash -ue +#!/usr/bin/env bash -# Copyright (C) 2010-2014 Codership Oy +set -ue + +# Copyright (C) 2017-2022 MariaDB +# Copyright (C) 2010-2022 Codership Oy # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,303 +19,548 @@ # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston # MA 02110-1335 USA. -# This is a reference script for rsync-based state snapshot tansfer - -RSYNC_PID= # rsync pid file -RSYNC_CONF= # rsync configuration file -RSYNC_REAL_PID= # rsync process id +# This is a reference script for rsync-based state snapshot transfer -OS=$(uname) -[ "$OS" = "Darwin" ] && export -n LD_LIBRARY_PATH +RSYNC_REAL_PID=0 # rsync process id +STUNNEL_REAL_PID=0 # stunnel process id -# Setting the path for lsof on CentOS -export PATH="/usr/sbin:/sbin:$PATH" +OS="$(uname)" +[ "$OS" = 'Darwin' ] && export -n LD_LIBRARY_PATH -. $(dirname $0)/wsrep_sst_common +. $(dirname "$0")/wsrep_sst_common +wsrep_check_datadir wsrep_check_programs rsync cleanup_joiner() { - wsrep_log_info "Joiner cleanup. rsync PID: $RSYNC_REAL_PID" - [ "0" != "$RSYNC_REAL_PID" ] && \ - kill $RSYNC_REAL_PID && \ - sleep 0.5 && \ - kill -9 $RSYNC_REAL_PID >/dev/null 2>&1 || \ - : - rm -rf "$RSYNC_CONF" - rm -f "$STUNNEL_CONF" - rm -f "$STUNNEL_PID" - rm -rf "$MAGIC_FILE" - rm -rf "$RSYNC_PID" - wsrep_log_info "Joiner cleanup done." - if [ "${WSREP_SST_OPT_ROLE}" = "joiner" ];then + # Since this is invoked just after exit NNN + local estatus=$? + if [ $estatus -ne 0 ]; then + wsrep_log_error "Cleanup after exit with status: $estatus" + elif [ -z "${coords:-}" ]; then + estatus=32 + wsrep_log_error "Failed to get current position" + fi + + local failure=0 + + [ "$(pwd)" != "$OLD_PWD" ] && cd "$OLD_PWD" + + wsrep_log_info "Joiner cleanup: rsync PID=$RSYNC_REAL_PID," \ + "stunnel PID=$STUNNEL_REAL_PID" + + if [ -n "$STUNNEL" ]; then + if cleanup_pid $STUNNEL_REAL_PID "$STUNNEL_PID" "$STUNNEL_CONF"; then + if [ $RSYNC_REAL_PID -eq 0 ]; then + if [ -r "$RSYNC_PID" ]; then + RSYNC_REAL_PID=$(cat "$RSYNC_PID" 2>/dev/null || :) + if [ -z "$RSYNC_REAL_PID" ]; then + RSYNC_REAL_PID=0 + fi + fi + fi + else + wsrep_log_warning "stunnel cleanup failed." + failure=1 + fi + fi + + if [ $failure -eq 0 ]; then + if cleanup_pid $RSYNC_REAL_PID "$RSYNC_PID" "$RSYNC_CONF"; then + [ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" || : + [ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" || : + else + wsrep_log_warning "rsync cleanup failed." + fi + fi + + if [ "$WSREP_SST_OPT_ROLE" = 'joiner' ]; then wsrep_cleanup_progress_file fi -} -# Check whether rsync process is still running. -check_pid() -{ - local pid_file=$1 - [ -r "$pid_file" ] && ps -p $(cat $pid_file) >/dev/null 2>&1 + [ -f "$SST_PID" ] && rm -f "$SST_PID" || : + + wsrep_log_info "Joiner cleanup done." + + exit $estatus } check_pid_and_port() { - local pid_file=$1 - local rsync_pid=$2 - local rsync_addr=$3 - local rsync_port=$4 - - case $OS in - FreeBSD) - local port_info="$(sockstat -46lp ${rsync_port} 2>/dev/null | \ - grep ":${rsync_port}")" - local is_rsync="$(echo $port_info | \ - grep -E '[[:space:]]+(rsync|stunnel)[[:space:]]+'"$rsync_pid" 2>/dev/null)" - ;; - *) - if ! command -v lsof > /dev/null; then - wsrep_log_error "lsof tool not found in PATH! Make sure you have it installed." - exit 2 # ENOENT + local pid_file="$1" + local pid=$2 + local addr="$3" + local port="$4" + + local utils='rsync|stunnel' + + if ! check_port $pid "$port" "$utils"; then + local port_info + local busy=0 + + if [ $lsof_available -ne 0 ]; then + port_info=$(lsof -Pnl -i ":$port" 2>/dev/null | \ + grep -F '(LISTEN)') + echo "$port_info" | \ + grep -q -E "[[:space:]](\\*|\\[?::\\]?):$port[[:space:]]" && busy=1 + else + local filter='([^[:space:]]+[[:space:]]+){4}[^[:space:]]+' + if [ $sockstat_available -ne 0 ]; then + local opts='-p' + if [ "$OS" = 'FreeBSD' ]; then + # sockstat on FreeBSD requires the "-s" option + # to display the connection state: + opts='-sp' + # in addition, sockstat produces an additional column: + filter='([^[:space:]]+[[:space:]]+){5}[^[:space:]]+' + fi + port_info=$(sockstat "$opts" "$port" 2>/dev/null | \ + grep -E '[[:space:]]LISTEN' | grep -o -E "$filter") + else + port_info=$(ss -nlpH "( sport = :$port )" 2>/dev/null | \ + grep -F 'users:(' | grep -o -E "$filter") + fi + echo "$port_info" | \ + grep -q -E "[[:space:]](\\*|\\[?::\\]?):$port\$" && busy=1 fi - local port_info="$(lsof -i :$rsync_port -Pn 2>/dev/null | \ - grep "(LISTEN)")" - local is_rsync="$(echo $port_info | \ - grep -E '^(rsync|stunnel)[[:space:]]+'"$rsync_pid" 2>/dev/null)" - ;; - esac - - local is_listening_all="$(echo $port_info | \ - grep "*:$rsync_port" 2>/dev/null)" - local is_listening_addr="$(echo $port_info | \ - grep -F "$rsync_addr:$rsync_port" 2>/dev/null)" + if [ $busy -eq 0 ]; then + if ! echo "$port_info" | grep -qw -F "[$addr]:$port" && \ + ! echo "$port_info" | grep -qw -F -- "$addr:$port" + then + if ! ps -p $pid >/dev/null 2>&1; then + wsrep_log_error \ + "rsync or stunnel daemon (PID: $pid)" \ + "terminated unexpectedly." + exit 16 # EBUSY + fi + return 1 + fi + fi - if [ ! -z "$is_listening_all" -o ! -z "$is_listening_addr" ]; then - if [ -z "$is_rsync" ]; then - wsrep_log_error "rsync daemon port '$rsync_port' has been taken" + if ! check_port $pid "$port" "$utils"; then + wsrep_log_error "rsync or stunnel daemon port '$port'" \ + "has been taken by another program" exit 16 # EBUSY fi fi - check_pid $pid_file && \ - [ -n "$port_info" ] && [ -n "$is_rsync" ] && \ - [ $(cat $pid_file) -eq $rsync_pid ] -} -is_local_ip() -{ - local address="$1" - local get_addr_bin - if ! command -v ifconfig > /dev/null - then - get_addr_bin=ip - get_addr_bin="$get_addr_bin address show" - # Add an slash at the end, so we don't get false positive : 172.18.0.4 matches 172.18.0.41 - # ip output format is "X.X.X.X/mask" - address="${address}/" - else - # Add an space at the end, so we don't get false positive : 172.18.0.4 matches 172.18.0.41 - # ifconfig output format is "X.X.X.X " - get_addr_bin=ifconfig - address="$address " - fi - - $get_addr_bin | grep -F "$address" > /dev/null + check_pid "$pid_file" && [ $CHECK_PID -eq $pid ] } STUNNEL_CONF="$WSREP_SST_OPT_DATA/stunnel.conf" -rm -f "$STUNNEL_CONF" - STUNNEL_PID="$WSREP_SST_OPT_DATA/stunnel.pid" -rm -f "$STUNNEL_PID" MAGIC_FILE="$WSREP_SST_OPT_DATA/rsync_sst_complete" -rm -rf "$MAGIC_FILE" - -BINLOG_TAR_FILE="$WSREP_SST_OPT_DATA/wsrep_sst_binlog.tar" -BINLOG_N_FILES=1 -rm -f "$BINLOG_TAR_FILE" || : - -if ! [ -z $WSREP_SST_OPT_BINLOG ] -then - BINLOG_DIRNAME=$(dirname $WSREP_SST_OPT_BINLOG) - BINLOG_FILENAME=$(basename $WSREP_SST_OPT_BINLOG) - BINLOG_INDEX_DIRNAME=$(dirname $WSREP_SST_OPT_BINLOG) - BINLOG_INDEX_FILENAME=$(basename $WSREP_SST_OPT_BINLOG) -fi -if ! [ -z $WSREP_SST_OPT_BINLOG_INDEX ] -then - BINLOG_INDEX_DIRNAME=$(dirname $WSREP_SST_OPT_BINLOG_INDEX) - BINLOG_INDEX_FILENAME=$(basename $WSREP_SST_OPT_BINLOG_INDEX) +get_binlog + +if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + binlog_dir=$(dirname "$WSREP_SST_OPT_BINLOG") + binlog_base=$(basename "$WSREP_SST_OPT_BINLOG") fi -WSREP_LOG_DIR=${WSREP_LOG_DIR:-""} -# Try to set WSREP_LOG_DIR from the command line: -if [ -z "$WSREP_LOG_DIR" ]; then - WSREP_LOG_DIR=$INNODB_LOG_GROUP_HOME_ARG +OLD_PWD="$(pwd)" + +DATA="$WSREP_SST_OPT_DATA" +if [ -n "$DATA" -a "$DATA" != '.' ]; then + [ ! -d "$DATA" ] && mkdir -p "$DATA" + cd "$DATA" fi -# if WSREP_LOG_DIR env. variable is not set, try to get it from my.cnf -if [ -z "$WSREP_LOG_DIR" ]; then - WSREP_LOG_DIR=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE innodb-log-group-home-dir '') +DATA_DIR="$(pwd)" + +cd "$OLD_PWD" + +BINLOG_TAR_FILE="$DATA_DIR/wsrep_sst_binlog.tar" + +ib_log_dir="$DATA_DIR" +ib_home_dir="$DATA_DIR" +ib_undo_dir="$DATA_DIR" + +# if no command line argument and INNODB_LOG_GROUP_HOME is not set, +# then try to get it from the my.cnf: +if [ -z "$INNODB_LOG_GROUP_HOME" ]; then + INNODB_LOG_GROUP_HOME=$(parse_cnf '--mysqld' 'innodb-log-group-home-dir') + INNODB_LOG_GROUP_HOME=$(trim_dir "$INNODB_LOG_GROUP_HOME") fi -if [ -z "$WSREP_LOG_DIR" ]; then - WSREP_LOG_DIR=$(parse_cnf --mysqld innodb-log-group-home-dir '') + +if [ -n "$INNODB_LOG_GROUP_HOME" -a "$INNODB_LOG_GROUP_HOME" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_LOG_GROUP_HOME" ] && mkdir -p "$INNODB_LOG_GROUP_HOME" + cd "$INNODB_LOG_GROUP_HOME" + ib_log_dir="$(pwd)" + cd "$OLD_PWD" fi -if [ -n "$WSREP_LOG_DIR" ]; then - # handle both relative and absolute paths - WSREP_LOG_DIR=$(cd $WSREP_SST_OPT_DATA; mkdir -p "$WSREP_LOG_DIR"; cd $WSREP_LOG_DIR; pwd -P) -else - # default to datadir - WSREP_LOG_DIR=$(cd $WSREP_SST_OPT_DATA; pwd -P) +# if no command line argument and INNODB_DATA_HOME_DIR environment +# variable is not set, try to get it from the my.cnf: +if [ -z "$INNODB_DATA_HOME_DIR" ]; then + INNODB_DATA_HOME_DIR=$(parse_cnf '--mysqld' 'innodb-data-home-dir') + INNODB_DATA_HOME_DIR=$(trim_dir "$INNODB_DATA_HOME_DIR") fi -INNODB_DATA_HOME_DIR=${INNODB_DATA_HOME_DIR:-""} -# Try to set INNODB_DATA_HOME_DIR from the command line: -if [ ! -z "$INNODB_DATA_HOME_DIR_ARG" ]; then - INNODB_DATA_HOME_DIR=$INNODB_DATA_HOME_DIR_ARG +if [ -n "$INNODB_DATA_HOME_DIR" -a "$INNODB_DATA_HOME_DIR" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_DATA_HOME_DIR" ] && mkdir -p "$INNODB_DATA_HOME_DIR" + cd "$INNODB_DATA_HOME_DIR" + ib_home_dir="$(pwd)" + cd "$OLD_PWD" fi -# if INNODB_DATA_HOME_DIR env. variable is not set, try to get it from my.cnf -if [ -z "$INNODB_DATA_HOME_DIR" ]; then - INNODB_DATA_HOME_DIR=$(parse_cnf mysqld$WSREP_SST_OPT_SUFFIX_VALUE innodb-data-home-dir '') + +# if no command line argument and INNODB_UNDO_DIR is not set, +# then try to get it from the my.cnf: +if [ -z "$INNODB_UNDO_DIR" ]; then + INNODB_UNDO_DIR=$(parse_cnf '--mysqld' 'innodb-undo-directory') + INNODB_UNDO_DIR=$(trim_dir "$INNODB_UNDO_DIR") fi -if [ -z "$INNODB_DATA_HOME_DIR" ]; then - INNODB_DATA_HOME_DIR=$(parse_cnf --mysqld innodb-data-home-dir '') + +if [ -n "$INNODB_UNDO_DIR" -a "$INNODB_UNDO_DIR" != '.' ]; then + # handle both relative and absolute paths: + cd "$DATA" + [ ! -d "$INNODB_UNDO_DIR" ] && mkdir -p "$INNODB_UNDO_DIR" + cd "$INNODB_UNDO_DIR" + ib_undo_dir="$(pwd)" + cd "$OLD_PWD" fi -if [ -n "$INNODB_DATA_HOME_DIR" ]; then - # handle both relative and absolute paths - INNODB_DATA_HOME_DIR=$(cd $WSREP_SST_OPT_DATA; mkdir -p "$INNODB_DATA_HOME_DIR"; cd $INNODB_DATA_HOME_DIR; pwd -P) +encgroups='--mysqld|sst' + +check_server_ssl_config + +SSTKEY="$tkey" +SSTCERT="$tpem" +SSTCA="$tcert" +SSTCAP="$tcap" + +SSLMODE=$(parse_cnf "$encgroups" 'ssl-mode' | tr '[[:lower:]]' '[[:upper:]]') + +if [ -z "$SSLMODE" ]; then + # Implicit verification if CA is set and the SSL mode + # is not specified by user: + if [ -n "$SSTCA$SSTCAP" ]; then + STUNNEL_BIN=$(commandex 'stunnel') + if [ -n "$STUNNEL_BIN" ]; then + SSLMODE='VERIFY_CA' + fi + # Require SSL by default if SSL key and cert are present: + elif [ -n "$SSTKEY" -a -n "$SSTCERT" ]; then + SSLMODE='REQUIRED' + fi else - # default to datadir - INNODB_DATA_HOME_DIR=$(cd $WSREP_SST_OPT_DATA; pwd -P) + case "$SSLMODE" in + 'VERIFY_IDENTITY'|'VERIFY_CA'|'REQUIRED'|'DISABLED') + ;; + *) + wsrep_log_error "Unrecognized ssl-mode option: '$SSLMODE'" + exit 22 # EINVAL + ;; + esac fi -# Old filter - include everything except selected -# FILTER=(--exclude '*.err' --exclude '*.pid' --exclude '*.sock' \ -# --exclude '*.conf' --exclude core --exclude 'galera.*' \ -# --exclude grastate.txt --exclude '*.pem' \ -# --exclude '*.[0-9][0-9][0-9][0-9][0-9][0-9]' --exclude '*.index') +if [ -n "$SSTKEY" -a -n "$SSTCERT" ]; then + verify_cert_matches_key "$SSTCERT" "$SSTKEY" +fi -# New filter - exclude everything except dirs (schemas) and innodb files -FILTER="-f '- /lost+found' - -f '- /.fseventsd' - -f '- /.Trashes' - -f '+ /wsrep_sst_binlog.tar' - -f '- $INNODB_DATA_HOME_DIR/ib_lru_dump' - -f '- $INNODB_DATA_HOME_DIR/ibdata*' - -f '+ /undo*' - -f '+ /*/' - -f '- /*'" +CAFILE_OPT="" +CAPATH_OPT="" +if [ -n "$SSTCA$SSTCAP" ]; then + if [ -n "$SSTCA" ]; then + CAFILE_OPT="CAfile = $SSTCA" + fi + if [ -n "$SSTCAP" ]; then + CAPATH_OPT="CApath = $SSTCAP" + fi + if [ -n "$SSTCERT" ]; then + verify_ca_matches_cert "$SSTCERT" "$SSTCA" "$SSTCAP" + fi +fi + +VERIFY_OPT="" +CHECK_OPT="" +CHECK_OPT_LOCAL="" +if [ "${SSLMODE#VERIFY}" != "$SSLMODE" ]; then + if [ "$SSLMODE" = 'VERIFY_IDENTITY' ]; then + VERIFY_OPT='verifyPeer = yes' + else + VERIFY_OPT='verifyChain = yes' + fi + if [ -z "$SSTCA$SSTCAP" ]; then + wsrep_log_error "Can't have ssl-mode='$SSLMODE' without CA file or path" + exit 22 # EINVAL + fi + if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then + CHECK_OPT="checkHost = $WSREP_SST_OPT_REMOTE_USER" + elif [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + # check if the address is an ip-address (v4 or v6): + if echo "$WSREP_SST_OPT_HOST_UNESCAPED" | \ + grep -q -E '^([0-9]+(\.[0-9]+){3}|[0-9a-fA-F]*(\:[0-9a-fA-F]*)+)$' + then + CHECK_OPT="checkIP = $WSREP_SST_OPT_HOST_UNESCAPED" + else + CHECK_OPT="checkHost = $WSREP_SST_OPT_HOST" + fi + if is_local_ip "$WSREP_SST_OPT_HOST_UNESCAPED"; then + CHECK_OPT_LOCAL='checkHost = localhost' + fi + fi +fi -SSTKEY=$(parse_cnf sst tkey "") -SSTCERT=$(parse_cnf sst tcert "") STUNNEL="" -if [ -f "$SSTKEY" ] && [ -f "$SSTCERT" ] && wsrep_check_programs stunnel -then - STUNNEL="stunnel ${STUNNEL_CONF}" +if [ -n "$SSLMODE" -a "$SSLMODE" != 'DISABLED' ]; then + if [ -z "${STUNNEL_BIN+x}" ]; then + STUNNEL_BIN=$(commandex 'stunnel') + fi + if [ -n "$STUNNEL_BIN" ]; then + wsrep_log_info "Using stunnel for SSL encryption: CA: '$SSTCA'," \ + "CAPATH='$SSTCAP', ssl-mode='$SSLMODE'" + STUNNEL="$STUNNEL_BIN $STUNNEL_CONF" + fi fi -if [ "$WSREP_SST_OPT_ROLE" = "donor" ] -then +readonly SECRET_TAG='secret' +readonly BYPASS_TAG='bypass' -cat << EOF > "$STUNNEL_CONF" -CApath = ${SSTCERT%/*} +SST_PID="$WSREP_SST_OPT_DATA/wsrep_sst.pid" + +# give some time for previous SST to complete: +check_round=0 +while check_pid "$SST_PID" 0; do + wsrep_log_info "Previous SST is not completed, waiting for it to exit" + check_round=$(( check_round + 1 )) + if [ $check_round -eq 20 ]; then + wsrep_log_error "previous SST script still running." + exit 114 # EALREADY + fi + sleep 1 +done + +trap simple_cleanup EXIT +echo $$ > "$SST_PID" + +# give some time for stunnel from the previous SST to complete: +check_round=0 +while check_pid "$STUNNEL_PID" 1 "$STUNNEL_CONF"; do + wsrep_log_info "Lingering stunnel daemon found at startup," \ + "waiting for it to exit" + check_round=$(( check_round + 1 )) + if [ $check_round -eq 10 ]; then + wsrep_log_error "stunnel daemon still running." + exit 114 # EALREADY + fi + sleep 1 +done + +MODULE="${WSREP_SST_OPT_MODULE:-rsync_sst}" + +RSYNC_PID="$WSREP_SST_OPT_DATA/$MODULE.pid" +RSYNC_CONF="$WSREP_SST_OPT_DATA/$MODULE.conf" + +# give some time for rsync from the previous SST to complete: +check_round=0 +while check_pid "$RSYNC_PID" 1 "$RSYNC_CONF"; do + wsrep_log_info "Lingering rsync daemon found at startup," \ + "waiting for it to exit" + check_round=$(( check_round + 1 )) + if [ $check_round -eq 10 ]; then + wsrep_log_error "rsync daemon still running." + exit 114 # EALREADY + fi + sleep 1 +done + +[ -f "$MAGIC_FILE" ] && rm -f "$MAGIC_FILE" +[ -f "$BINLOG_TAR_FILE" ] && rm -f "$BINLOG_TAR_FILE" + +RC=0 + +if [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then + + if [ -n "$STUNNEL" ]; then + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +${CAPATH_OPT} foreground = yes pid = $STUNNEL_PID debug = warning client = yes -connect = ${WSREP_SST_OPT_ADDR%/*} +connect = $WSREP_SST_OPT_HOST_UNESCAPED:$WSREP_SST_OPT_PORT TIMEOUTclose = 0 -verifyPeer = yes +${VERIFY_OPT} +${CHECK_OPT} +${CHECK_OPT_LOCAL} EOF + fi - if [ $WSREP_SST_OPT_BYPASS -eq 0 ] - then + if [ $WSREP_SST_OPT_BYPASS -eq 0 ]; then FLUSHED="$WSREP_SST_OPT_DATA/tables_flushed" ERROR="$WSREP_SST_OPT_DATA/sst_error" - rm -rf "$FLUSHED" - rm -rf "$ERROR" + [ -f "$FLUSHED" ] && rm -f "$FLUSHED" + [ -f "$ERROR" ] && rm -f "$ERROR" - # Use deltaxfer only for WAN - inv=$(basename $0) - [ "$inv" = "wsrep_sst_rsync_wan" ] && WHOLE_FILE_OPT="" \ - || WHOLE_FILE_OPT="--whole-file" - - echo "flush tables" + echo 'flush tables' # Wait for : # (a) Tables to be flushed, AND # (b) Cluster state ID & wsrep_gtid_domain_id to be written to the file, OR # (c) ERROR file, in case flush tables operation failed. - while [ ! -r "$FLUSHED" ] && ! grep -q ':' "$FLUSHED" >/dev/null 2>&1 + while [ ! -r "$FLUSHED" ] || \ + ! grep -q -F ':' -- "$FLUSHED" do # Check whether ERROR file exists. - if [ -f "$ERROR" ] - then + if [ -f "$ERROR" ]; then # Flush tables operation failed. - rm -rf "$ERROR" + rm "$ERROR" exit 255 fi - sleep 0.2 done - STATE="$(cat $FLUSHED)" - rm -rf "$FLUSHED" + STATE=$(cat "$FLUSHED") + rm "$FLUSHED" sync - if ! [ -z $WSREP_SST_OPT_BINLOG ] - then - # Prepare binlog files - OLD_PWD="$(pwd)" - cd $BINLOG_DIRNAME - - if ! [ -z $WSREP_SST_OPT_BINLOG_INDEX ] - binlog_files_full=$(tail -n $BINLOG_N_FILES ${BINLOG_FILENAME}.index) - then - cd $BINLOG_INDEX_DIRNAME - binlog_files_full=$(tail -n $BINLOG_N_FILES ${BINLOG_INDEX_FILENAME}.index) - fi - - cd $BINLOG_DIRNAME - binlog_files="" - for ii in $binlog_files_full - do - binlog_files="$binlog_files $(basename $ii)" - done - - if ! [ -z "$binlog_files" ] - then - wsrep_log_info "Preparing binlog files for transfer:" - tar -cvf $BINLOG_TAR_FILE $binlog_files >&2 + wsrep_log_info "Tables flushed" + + if [ -n "$WSREP_SST_OPT_BINLOG" ]; then + # Change the directory to binlog base (if possible): + cd "$DATA" + # Let's check the existence of the file with the index: + if [ -f "$WSREP_SST_OPT_BINLOG_INDEX" ]; then + # Let's read the binlog index: + max_binlogs=$(parse_cnf "$encgroups" 'sst-max-binlogs' 1) + if [ $max_binlogs -ge 0 ]; then + binlog_files="" + if [ $max_binlogs -gt 0 ]; then + binlog_files=$(tail -n $max_binlogs \ + "$WSREP_SST_OPT_BINLOG_INDEX") + fi + else + binlog_files=$(cat "$WSREP_SST_OPT_BINLOG_INDEX") + fi + if [ -n "$binlog_files" ]; then + # Preparing binlog files for transfer: + wsrep_log_info "Preparing binlog files for transfer:" + tar_type=0 + if tar --help | grep -qw -F -- '--transform'; then + tar_type=1 + elif tar --version | grep -qw -E '^bsdtar'; then + tar_type=2 + fi + if [ $tar_type -eq 2 ]; then + if [ -n "$BASH_VERSION" ]; then + printf '%s' "$binlog_files" >&2 + else + echo "$binlog_files" >&2 + fi + fi + if [ $tar_type -ne 0 ]; then + # Preparing list of the binlog file names: + echo "$binlog_files" | { + binlogs="" + while read bin_file || [ -n "$bin_file" ]; do + [ ! -f "$bin_file" ] && continue + if [ -n "$BASH_VERSION" ]; then + first="${bin_file:0:1}" + else + first=$(echo "$bin_file" | cut -c1) + fi + if [ "$first" = '-' -o "$first" = '@' ]; then + bin_file="./$bin_file" + fi + binlogs="$binlogs${binlogs:+ }'$bin_file'" + done + if [ -n "$binlogs" ]; then + if [ $tar_type -eq 1 ]; then + tar_options="--transform='s/^.*\///g'" + else + # bsdtar handles backslash incorrectly: + tar_options="-s '?^.*/??g'" + fi + eval tar -P $tar_options \ + -cvf "'$BINLOG_TAR_FILE'" $binlogs >&2 + fi + } + else + tar_options='-cvf' + echo "$binlog_files" | \ + while read bin_file || [ -n "$bin_file" ]; do + [ ! -f "$bin_file" ] && continue + bin_dir=$(dirname "$bin_file") + bin_base=$(basename "$bin_file") + if [ -n "$BASH_VERSION" ]; then + first="${bin_base:0:1}" + else + first=$(echo "$bin_base" | cut -c1) + fi + if [ "$first" = '-' -o "$first" = '@' ]; then + bin_base="./$bin_base" + fi + if [ -n "$bin_dir" -a "$bin_dir" != '.' ]; then + tar $tar_options "$BINLOG_TAR_FILE" \ + -C "$bin_dir" "$bin_base" >&2 + else + tar $tar_options "$BINLOG_TAR_FILE" \ + "$bin_base" >&2 + fi + tar_options='-rvf' + done + fi + fi fi cd "$OLD_PWD" fi - # first, the normal directories, so that we can detect incompatible protocol - RC=0 - eval rsync ${STUNNEL:+--rsh="$STUNNEL"} \ + # Use deltaxfer only for WAN: + WHOLE_FILE_OPT="" + if [ "${WSREP_METHOD%_wan}" = "$WSREP_METHOD" ]; then + WHOLE_FILE_OPT='--whole-file' + fi + +# Old filter - include everything except selected +# FILTER=(--exclude '*.err' --exclude '*.pid' --exclude '*.sock' \ +# --exclude '*.conf' --exclude core --exclude 'galera.*' \ +# --exclude grastate.txt --exclude '*.pem' \ +# --exclude '*.[0-9][0-9][0-9][0-9][0-9][0-9]' --exclude '*.index') + +# New filter - exclude everything except dirs (schemas) and innodb files +FILTER="-f '- /lost+found' + -f '- /.zfs' + -f '- /.fseventsd' + -f '- /.Trashes' + -f '- /.pid' + -f '- /.conf' + -f '+ /wsrep_sst_binlog.tar' + -f '- $ib_home_dir/ib_lru_dump' + -f '- $ib_home_dir/ibdata*' + -f '+ $ib_undo_dir/undo*' + -f '+ /*/' + -f '- /*'" + + # first, the normal directories, so that we can detect + # incompatible protocol: + eval rsync ${STUNNEL:+"--rsh='$STUNNEL'"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ - $WHOLE_FILE_OPT ${FILTER} "$WSREP_SST_OPT_DATA/" \ - rsync://$WSREP_SST_OPT_ADDR >&2 || RC=$? + $WHOLE_FILE_OPT $FILTER "'$WSREP_SST_OPT_DATA/'" \ + "'rsync://$WSREP_SST_OPT_ADDR'" >&2 || RC=$? - if [ "$RC" -ne 0 ]; then + if [ $RC -ne 0 ]; then wsrep_log_error "rsync returned code $RC:" - case $RC in 12) RC=71 # EPROTO wsrep_log_error \ - "rsync server on the other end has incompatible protocol. " \ - "Make sure you have the same version of rsync on all nodes." + "rsync server on the other end has incompatible" \ + "protocol. Make sure you have the same version of" \ + "rsync on all nodes." ;; 22) RC=12 # ENOMEM ;; @@ -322,46 +570,57 @@ EOF exit $RC fi + wsrep_log_info "Transfer of normal directories done" + # Transfer InnoDB data files rsync ${STUNNEL:+--rsh="$STUNNEL"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ $WHOLE_FILE_OPT -f '+ /ibdata*' -f '+ /ib_lru_dump' \ - -f '- **' "$INNODB_DATA_HOME_DIR/" \ - rsync://$WSREP_SST_OPT_ADDR-data_dir >&2 || RC=$? + -f '- **' "$ib_home_dir/" \ + "rsync://$WSREP_SST_OPT_ADDR-data_dir" >&2 || RC=$? if [ $RC -ne 0 ]; then wsrep_log_error "rsync innodb_data_home_dir returned code $RC:" exit 255 # unknown error fi - # second, we transfer InnoDB log files + wsrep_log_info "Transfer of InnoDB data files done" + + # second, we transfer InnoDB and Aria log files rsync ${STUNNEL:+--rsh="$STUNNEL"} \ --owner --group --perms --links --specials \ --ignore-times --inplace --dirs --delete --quiet \ - $WHOLE_FILE_OPT -f '+ /ib_logfile[0-9]*' -f '- **' "$WSREP_LOG_DIR/" \ - rsync://$WSREP_SST_OPT_ADDR-log_dir >&2 || RC=$? + $WHOLE_FILE_OPT -f '+ /ib_logfile[0-9]*' -f '+ /aria_log.*' \ + -f '+ /aria_log_control' -f '- **' "$ib_log_dir/" \ + "rsync://$WSREP_SST_OPT_ADDR-log_dir" >&2 || RC=$? if [ $RC -ne 0 ]; then wsrep_log_error "rsync innodb_log_group_home_dir returned code $RC:" exit 255 # unknown error fi - # then, we parallelize the transfer of database directories, use . so that pathconcatenation works - OLD_PWD="$(pwd)" - cd $WSREP_SST_OPT_DATA + wsrep_log_info "Transfer of InnoDB and Aria log files done" + + # then, we parallelize the transfer of database directories, + # use '.' so that path concatenation works: - count=1 - [ "$OS" = "Linux" ] && count=$(grep -c processor /proc/cpuinfo) - [ "$OS" = "Darwin" -o "$OS" = "FreeBSD" ] && count=$(sysctl -n hw.ncpu) + cd "$DATA" - find . -maxdepth 1 -mindepth 1 -type d -not -name "lost+found" \ - -print0 | xargs -I{} -0 -P $count \ + backup_threads=$(parse_cnf '--mysqld|sst' 'backup-threads') + if [ -z "$backup_threads" ]; then + get_proc + backup_threads=$nproc + fi + + find . -maxdepth 1 -mindepth 1 -type d -not -name 'lost+found' \ + -not -name '.zfs' -print0 | xargs -I{} -0 -P $backup_threads \ rsync ${STUNNEL:+--rsh="$STUNNEL"} \ - --owner --group --perms --links --specials \ - --ignore-times --inplace --recursive --delete --quiet \ - $WHOLE_FILE_OPT --exclude '*/ib_logfile*' "$WSREP_SST_OPT_DATA"/{}/ \ - rsync://$WSREP_SST_OPT_ADDR/{} >&2 || RC=$? + --owner --group --perms --links --specials --ignore-times \ + --inplace --recursive --delete --quiet $WHOLE_FILE_OPT \ + --exclude '*/ib_logfile*' --exclude '*/aria_log.*' \ + --exclude '*/aria_log_control' "$WSREP_SST_OPT_DATA/{}/" \ + "rsync://$WSREP_SST_OPT_ADDR/{}" >&2 || RC=$? cd "$OLD_PWD" @@ -370,61 +629,66 @@ EOF exit 255 # unknown error fi + wsrep_log_info "Transfer of data done" + + [ -f "$BINLOG_TAR_FILE" ] && rm "$BINLOG_TAR_FILE" + else # BYPASS + wsrep_log_info "Bypassing state dump." # Store donor's wsrep GTID (state ID) and wsrep_gtid_domain_id # (separated by a space). STATE="$WSREP_SST_OPT_GTID $WSREP_SST_OPT_GTID_DOMAIN_ID" + fi - echo "continue" # now server can resume updating data + wsrep_log_info "Sending continue to donor" + echo 'continue' # now server can resume updating data echo "$STATE" > "$MAGIC_FILE" - rsync ${STUNNEL:+--rsh="$STUNNEL"} \ - --archive --quiet --checksum "$MAGIC_FILE" rsync://$WSREP_SST_OPT_ADDR - - echo "done $STATE" -elif [ "$WSREP_SST_OPT_ROLE" = "joiner" ] -then - wsrep_check_programs lsof + if [ -n "$WSREP_SST_OPT_REMOTE_PSWD" ]; then + # Let joiner know that we know its secret + echo "$SECRET_TAG $WSREP_SST_OPT_REMOTE_PSWD" >> "$MAGIC_FILE" + fi - touch $SST_PROGRESS_FILE - MYSQLD_PID=$WSREP_SST_OPT_PARENT + if [ $WSREP_SST_OPT_BYPASS -ne 0 ]; then + echo "$BYPASS_TAG" >> "$MAGIC_FILE" + fi - MODULE="rsync_sst" + rsync ${STUNNEL:+--rsh="$STUNNEL"} \ + --archive --quiet --checksum "$MAGIC_FILE" \ + "rsync://$WSREP_SST_OPT_ADDR" >&2 || RC=$? - RSYNC_PID="$WSREP_SST_OPT_DATA/$MODULE.pid" + rm "$MAGIC_FILE" - if check_pid $RSYNC_PID - then - wsrep_log_error "rsync daemon already running." - exit 114 # EALREADY + if [ $RC -ne 0 ]; then + wsrep_log_error "rsync $MAGIC_FILE returned code $RC:" + exit 255 # unknown error fi - rm -rf "$RSYNC_PID" - ADDR=$WSREP_SST_OPT_ADDR - if [ "${ADDR#\[}" != "$ADDR" ]; then - RSYNC_PORT=$(echo $ADDR | awk -F '\\]:' '{ print $2 }') - RSYNC_ADDR=$(echo $ADDR | awk -F '\\]:' '{ print $1 }')"]" - else - RSYNC_PORT=$(echo $ADDR | awk -F ':' '{ print $2 }') - RSYNC_ADDR=$(echo $ADDR | awk -F ':' '{ print $1 }') - fi - if [ -z "$RSYNC_PORT" ] - then - RSYNC_PORT=4444 - ADDR="$RSYNC_ADDR:$RSYNC_PORT" + echo "done $STATE" + + if [ -n "$STUNNEL" ]; then + rm "$STUNNEL_CONF" + [ -f "$STUNNEL_PID" ] && rm "$STUNNEL_PID" fi - trap "exit 32" HUP PIPE - trap "exit 3" INT TERM ABRT +else # joiner + + check_sockets_utils + + ADDR="$WSREP_SST_OPT_HOST" + RSYNC_PORT="$WSREP_SST_OPT_PORT" + RSYNC_ADDR="$WSREP_SST_OPT_HOST" + RSYNC_ADDR_UNESCAPED="$WSREP_SST_OPT_HOST_UNESCAPED" + trap cleanup_joiner EXIT - RSYNC_CONF="$WSREP_SST_OPT_DATA/$MODULE.conf" + touch "$SST_PROGRESS_FILE" - if [ -n "${MYSQL_TMP_DIR:-}" ] ; then + if [ -n "${MYSQL_TMP_DIR:-}" ]; then SILENT="log file = $MYSQL_TMP_DIR/rsyncd.log" else SILENT="" @@ -438,112 +702,233 @@ timeout = 300 $SILENT [$MODULE] path = $WSREP_SST_OPT_DATA + exclude = .zfs [$MODULE-log_dir] - path = $WSREP_LOG_DIR + path = $ib_log_dir [$MODULE-data_dir] - path = $INNODB_DATA_HOME_DIR + path = $ib_home_dir EOF -# rm -rf "$DATA"/ib_logfile* # we don't want old logs around - - readonly RSYNC_PORT=${WSREP_SST_OPT_PORT:-4444} - # If the IP is local listen only in it - if is_local_ip "$RSYNC_ADDR" - then - RSYNC_EXTRA_ARGS="--address $RSYNC_ADDR" - STUNNEL_ACCEPT="$RSYNC_ADDR:$RSYNC_PORT" + # If the IP is local, listen only on it: + if is_local_ip "$RSYNC_ADDR_UNESCAPED"; then + RSYNC_EXTRA_ARGS="--address $RSYNC_ADDR_UNESCAPED" + STUNNEL_ACCEPT="$RSYNC_ADDR_UNESCAPED:$RSYNC_PORT" else - # Not local, possibly a NAT, listen on all interfaces - RSYNC_EXTRA_ARGS="" - STUNNEL_ACCEPT="$RSYNC_PORT" - # Overwrite address with all - RSYNC_ADDR="*" + # Not local, possibly a NAT, listen on all interfaces: + RSYNC_EXTRA_ARGS="" + STUNNEL_ACCEPT="$RSYNC_PORT" + # Overwrite address with all: + RSYNC_ADDR='*' fi - if [ -z "$STUNNEL" ] - then - rsync --daemon --no-detach --port "$RSYNC_PORT" --config "$RSYNC_CONF" ${RSYNC_EXTRA_ARGS} & - RSYNC_REAL_PID=$! + if [ -z "$STUNNEL" ]; then + rsync --daemon --no-detach --port "$RSYNC_PORT" \ + --config "$RSYNC_CONF" $RSYNC_EXTRA_ARGS & + RSYNC_REAL_PID=$! + TRANSFER_REAL_PID=$RSYNC_REAL_PID + TRANSFER_PID="$RSYNC_PID" else - cat << EOF > "$STUNNEL_CONF" + # Let's check if the path to the config file contains a space? + RSYNC_BIN=$(commandex 'rsync') + if [ "${RSYNC_CONF#* }" = "$RSYNC_CONF" ]; then + cat << EOF > "$STUNNEL_CONF" key = $SSTKEY cert = $SSTCERT +${CAFILE_OPT} +${CAPATH_OPT} foreground = yes pid = $STUNNEL_PID debug = warning client = no +${VERIFY_OPT} +${CHECK_OPT} +${CHECK_OPT_LOCAL} [rsync] accept = $STUNNEL_ACCEPT -exec = $(which rsync) +exec = $RSYNC_BIN execargs = rsync --server --daemon --config=$RSYNC_CONF . EOF - stunnel "$STUNNEL_CONF" & - RSYNC_REAL_PID=$! - RSYNC_PID=$STUNNEL_PID + else + # The path contains a space, so we will run it via + # shell with "eval" command: + export RSYNC_CMD="eval '$RSYNC_BIN' --server --daemon --config='$RSYNC_CONF' ." + cat << EOF > "$STUNNEL_CONF" +key = $SSTKEY +cert = $SSTCERT +${CAFILE_OPT} +${CAPATH_OPT} +foreground = yes +pid = $STUNNEL_PID +debug = warning +client = no +${VERIFY_OPT} +${CHECK_OPT} +${CHECK_OPT_LOCAL} +[rsync] +accept = $STUNNEL_ACCEPT +exec = $SHELL +execargs = $SHELL -c \$RSYNC_CMD +EOF + fi + stunnel "$STUNNEL_CONF" & + STUNNEL_REAL_PID=$! + TRANSFER_REAL_PID=$STUNNEL_REAL_PID + TRANSFER_PID="$STUNNEL_PID" + fi + + if [ "${SSLMODE#VERIFY}" != "$SSLMODE" ]; then + # backward-incompatible behavior: + CN="" + if [ -n "$SSTCERT" ]; then + # find out my Common Name + get_openssl + if [ -z "$OPENSSL_BINARY" ]; then + wsrep_log_error \ + 'openssl not found but it is required for authentication' + exit 42 + fi + CN=$("$OPENSSL_BINARY" x509 -noout -subject -in "$SSTCERT" | \ + tr ',' '\n' | grep -F 'CN =' | cut -d '=' -f2 | sed s/^\ // | \ + sed s/\ %//) + fi + MY_SECRET="$(wsrep_gen_secret)" + # Add authentication data to address + ADDR="$CN:$MY_SECRET@$WSREP_SST_OPT_HOST" + else + MY_SECRET="" # for check down in recv_joiner() fi - until check_pid_and_port "$RSYNC_PID" "$RSYNC_REAL_PID" "$RSYNC_ADDR" "$RSYNC_PORT" + until check_pid_and_port "$TRANSFER_PID" $TRANSFER_REAL_PID \ + "$RSYNC_ADDR_UNESCAPED" "$RSYNC_PORT" do sleep 0.2 done - echo "ready $WSREP_SST_OPT_HOST:$RSYNC_PORT/$MODULE" + echo "ready $ADDR:$RSYNC_PORT/$MODULE" + + MYSQLD_PID="$WSREP_SST_OPT_PARENT" # wait for SST to complete by monitoring magic file - while [ ! -r "$MAGIC_FILE" ] && check_pid "$RSYNC_PID" && \ - ps -p $MYSQLD_PID >/dev/null + while [ ! -r "$MAGIC_FILE" ] && check_pid "$TRANSFER_PID" && \ + ps -p $MYSQLD_PID >/dev/null 2>&1 do sleep 1 done - if ! ps -p $MYSQLD_PID >/dev/null - then + if ! ps -p $MYSQLD_PID >/dev/null 2>&1; then wsrep_log_error \ - "Parent mysqld process (PID:$MYSQLD_PID) terminated unexpectedly." - kill -- -"${MYSQLD_PID}" + "Parent mysqld process (PID: $MYSQLD_PID) terminated unexpectedly." + kill -- -$MYSQLD_PID sleep 1 exit 32 fi - if ! [ -z $WSREP_SST_OPT_BINLOG ] - then + if [ ! -r "$MAGIC_FILE" ]; then + # This message should cause joiner to abort: + wsrep_log_info "rsync process ended without creating" \ + "magic file ($MAGIC_FILE)" + exit 32 + fi - OLD_PWD="$(pwd)" - cd $BINLOG_DIRNAME + if [ -n "$MY_SECRET" ]; then + # Check donor supplied secret: + SECRET=$(grep -m1 -E "^$SECRET_TAG[[:space:]]" "$MAGIC_FILE" || :) + SECRET=$(trim_string "${SECRET#$SECRET_TAG}") + if [ "$SECRET" != "$MY_SECRET" ]; then + wsrep_log_error "Donor does not know my secret!" + wsrep_log_info "Donor: '$SECRET', my: '$MY_SECRET'" + exit 32 + fi + fi - if [ -f $BINLOG_TAR_FILE ] - then - # Clean up old binlog files first - rm -f ${BINLOG_FILENAME}.* - wsrep_log_info "Extracting binlog files:" - tar -xvf $BINLOG_TAR_FILE >&2 - for ii in $(ls -1 ${BINLOG_FILENAME}.*) - do - if ! [ -z $WSREP_SST_OPT_BINLOG_INDEX ] - echo ${BINLOG_DIRNAME}/${ii} >> ${BINLOG_FILENAME}.index - then - echo ${BINLOG_DIRNAME}/${ii} >> ${BINLOG_INDEX_DIRNAME}/${BINLOG_INDEX_FILENAME}.index - fi - done + if [ $WSREP_SST_OPT_BYPASS -eq 0 ]; then + if grep -m1 -qE "^$BYPASS_TAG([[:space:]]+.*)?\$" "$MAGIC_FILE"; then + readonly WSREP_SST_OPT_BYPASS=1 + readonly WSREP_TRANSFER_TYPE='IST' fi - cd "$OLD_PWD" + fi + binlog_tar_present=0 + if [ -f "$BINLOG_TAR_FILE" ]; then + binlog_tar_present=1 + if [ $WSREP_SST_OPT_BYPASS -ne 0 ]; then + wsrep_log_warning "tar with binlogs transferred in the IST mode" + fi fi - if [ -r "$MAGIC_FILE" ] - then - # UUID:seqno & wsrep_gtid_domain_id is received here. - cat "$MAGIC_FILE" # Output : UUID:seqno wsrep_gtid_domain_id - else - # this message should cause joiner to abort - echo "rsync process ended without creating '$MAGIC_FILE'" + + if [ $WSREP_SST_OPT_BYPASS -eq 0 -a -n "$WSREP_SST_OPT_BINLOG" ]; then + # If it is SST (not an IST) or tar with binlogs is present + # among the transferred files, then we need to remove the + # old binlogs: + cd "$DATA" + # Clean up the old binlog files and index: + binlog_index="$WSREP_SST_OPT_BINLOG_INDEX" + if [ -f "$binlog_index" ]; then + while read bin_file || [ -n "$bin_file" ]; do + rm -f "$bin_file" || : + done < "$binlog_index" + rm "$binlog_index" + fi + binlog_cd=0 + # Change the directory to binlog base (if possible): + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' -a \ + -d "$binlog_dir" ] + then + binlog_cd=1 + cd "$binlog_dir" + fi + # Clean up unindexed binlog files: + rm -f "$binlog_base".[0-9]* || : + [ $binlog_cd -ne 0 ] && cd "$DATA_DIR" + if [ $binlog_tar_present -ne 0 ]; then + # Create a temporary file: + tmpdir=$(parse_cnf '--mysqld|sst' 'tmpdir') + if [ -z "$tmpdir" ]; then + tmpfile="$(mktemp)" + elif [ "$OS" = 'Linux' ]; then + tmpfile=$(mktemp "--tmpdir=$tmpdir") + else + tmpfile=$(TMPDIR="$tmpdir"; mktemp) + fi + index_dir=$(dirname "$binlog_index"); + if [ -n "$index_dir" -a "$index_dir" != '.' ]; then + [ ! -d "$index_dir" ] && mkdir -p "$index_dir" + fi + binlog_cd=0 + if [ -n "$binlog_dir" -a "$binlog_dir" != '.' ]; then + [ ! -d "$binlog_dir" ] && mkdir -p "$binlog_dir" + binlog_cd=1 + cd "$binlog_dir" + fi + # Extracting binlog files: + wsrep_log_info "Extracting binlog files:" + if tar --version | grep -qw -E '^bsdtar'; then + tar -tf "$BINLOG_TAR_FILE" > "$tmpfile" && \ + tar -xvf "$BINLOG_TAR_FILE" > /dev/null || RC=$? + else + tar -xvf "$BINLOG_TAR_FILE" > "$tmpfile" && \ + cat "$tmpfile" >&2 || RC=$? + fi + if [ $RC -ne 0 ]; then + wsrep_log_error "Error unpacking tar file with binlog files" + rm "$tmpfile" + exit 32 + fi + # Rebuild binlog index: + [ $binlog_cd -ne 0 ] && cd "$DATA_DIR" + while read bin_file || [ -n "$bin_file" ]; do + echo "$binlog_dir${binlog_dir:+/}$bin_file" >> "$binlog_index" + done < "$tmpfile" + rm "$tmpfile" + cd "$OLD_PWD" + fi fi - wsrep_cleanup_progress_file -# cleanup_joiner -else - wsrep_log_error "Unrecognized role: '$WSREP_SST_OPT_ROLE'" - exit 22 # EINVAL -fi -rm -f $BINLOG_TAR_FILE || : + # Remove special tags from the magic file, and from the output: + coords=$(head -n1 "$MAGIC_FILE") + wsrep_log_info "Galera co-ords from recovery: $coords" + echo "$coords" # Output : UUID:seqno wsrep_gtid_domain_id +fi +wsrep_log_info "$WSREP_METHOD $WSREP_TRANSFER_TYPE completed on $WSREP_SST_OPT_ROLE" exit 0 |