From 02e1853c894906737fe0ea5f836adb087b1a72ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Lindstr=C3=B6m?= Date: Mon, 8 Dec 2025 15:53:09 +0200 Subject: [PATCH] MDEV-29909 : SST fails when table is defined with DATA DIRECTORY='/path/to' and datafile is larger than datadir space Problem is that default path for SST is datadir and if there is not enough space for all datafiles it fails. In this patch wsrep_sst_tmp_dir configuration parameter is introduced. User can now configure path where SST transfers datafiles in joiner while operation is running. Additionally, if available transfer size is compared to available disk space and if not necessary big error is produced. When transfer is completed MariaBackup will move datafiles to their correct locations. Current rules for wsrep_sst_tmp_dir * can be nullptr or empty and will not be used * must be existing path and directory or not used * may not be same as datadir or not used * if total estimated SST payload available it should have enough available space * similar to current behaviour temporal .sst directory is created on path and this directory is removed after SST has finished --- .../suite/galera/r/galera_defaults.result | 7 +- .../galera/r/galera_sst_tmp_directory.result | 22 ++++ .../suite/galera/t/galera_defaults.test | 4 +- .../galera/t/galera_sst_tmp_directory.cnf | 19 +++ .../t/galera_sst_tmp_directory.combinations | 5 + .../galera/t/galera_sst_tmp_directory.test | 59 ++++++++++ .../suite/sys_vars/r/sysvars_wsrep.result | 17 +++ scripts/wsrep_sst_common.sh | 17 ++- scripts/wsrep_sst_mariabackup.sh | 29 +++-- sql/mysqld.cc | 2 +- sql/sys_vars.cc | 7 +- sql/wsrep_mysqld.cc | 12 ++ sql/wsrep_mysqld.h | 2 + sql/wsrep_sst.cc | 109 +++++++++++++++--- sql/wsrep_sst.h | 4 +- sql/wsrep_var.cc | 60 ++++++++++ sql/wsrep_var.h | 2 + 17 files changed, 347 insertions(+), 30 deletions(-) create mode 100644 mysql-test/suite/galera/r/galera_sst_tmp_directory.result create mode 100644 mysql-test/suite/galera/t/galera_sst_tmp_directory.cnf create mode 100644 mysql-test/suite/galera/t/galera_sst_tmp_directory.combinations create mode 100644 mysql-test/suite/galera/t/galera_sst_tmp_directory.test diff --git a/mysql-test/suite/galera/r/galera_defaults.result b/mysql-test/suite/galera/r/galera_defaults.result index 5d07ac0c44f61..e65cf9a8facca 100644 --- a/mysql-test/suite/galera/r/galera_defaults.result +++ b/mysql-test/suite/galera/r/galera_defaults.result @@ -1,9 +1,9 @@ connection node_2; connection node_1; # Correct Galera library found -SELECT COUNT(*) `expect 50` FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%'; -expect 50 -50 +SELECT COUNT(*) `expect 51` FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%'; +expect 51 +51 SELECT VARIABLE_NAME, VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%' @@ -57,6 +57,7 @@ WSREP_SST_AUTH ******** WSREP_SST_DONOR WSREP_SST_DONOR_REJECTS_QUERIES OFF WSREP_SST_METHOD rsync +WSREP_SST_TMP_DIR WSREP_STATUS_FILE WSREP_SYNC_WAIT 15 WSREP_TRX_FRAGMENT_SIZE 0 diff --git a/mysql-test/suite/galera/r/galera_sst_tmp_directory.result b/mysql-test/suite/galera/r/galera_sst_tmp_directory.result new file mode 100644 index 0000000000000..175eba75d7cac --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_tmp_directory.result @@ -0,0 +1,22 @@ +connection node_2; +connection node_1; +connection node_1; +connection node_2; +create table t1(id int not null primary key, b varchar(32)) engine=InnoDB; +insert into t1 SELECT seq, md5(rand()) from seq_1_to_50000; +connection node_2; +Shutting down server ... +connection node_1; +connection node_2; +Starting server ... +connection node_1; +SELECT COUNT(*) AS EXPECT_5000 FROM t1; +EXPECT_5000 +50000 +connection node_2; +SELECT COUNT(*) AS EXPECT_5000 FROM t1; +EXPECT_5000 +50000 +DROP TABLE t1; +disconnect node_2; +disconnect node_1; diff --git a/mysql-test/suite/galera/t/galera_defaults.test b/mysql-test/suite/galera/t/galera_defaults.test index c69a32db67a1e..673e4ac8c2546 100644 --- a/mysql-test/suite/galera/t/galera_defaults.test +++ b/mysql-test/suite/galera/t/galera_defaults.test @@ -13,12 +13,12 @@ --source include/force_restart.inc # Make sure that the test is operating on the right version of galera library. ---let $galera_version=26.4.23 +--let $galera_version=26.4.26 source ../wsrep/include/check_galera_version.inc; # Global Variables -SELECT COUNT(*) `expect 50` FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%'; +SELECT COUNT(*) `expect 51` FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%'; SELECT VARIABLE_NAME, VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES diff --git a/mysql-test/suite/galera/t/galera_sst_tmp_directory.cnf b/mysql-test/suite/galera/t/galera_sst_tmp_directory.cnf new file mode 100644 index 0000000000000..259b1e49a7d2d --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_tmp_directory.cnf @@ -0,0 +1,19 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=mariabackup +wsrep_sst_auth="root:" +wsrep_debug=1 +innodb_fast_shutdown=0 +innodb_undo_tablespaces=0 + +[mysqld.1] +wsrep_provider_options='pc.ignore_sb=true;base_port=@mysqld.1.#galera_port' + +[mysqld.2] +wsrep_provider_options='pc.ignore_sb=true;base_port=@mysqld.2.#galera_port' +wsrep_sst_tmp_dir=@ENV.MYSQLTEST_VARDIR/tmp + +[sst] +transferfmt=@ENV.MTR_GALERA_TFMT +streamfmt=mbstream diff --git a/mysql-test/suite/galera/t/galera_sst_tmp_directory.combinations b/mysql-test/suite/galera/t/galera_sst_tmp_directory.combinations new file mode 100644 index 0000000000000..cef98e75213f7 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_tmp_directory.combinations @@ -0,0 +1,5 @@ +[binlogon] +log-bin +log-slave-updates + +[binlogoff] diff --git a/mysql-test/suite/galera/t/galera_sst_tmp_directory.test b/mysql-test/suite/galera/t/galera_sst_tmp_directory.test new file mode 100644 index 0000000000000..e86d8880568eb --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_tmp_directory.test @@ -0,0 +1,59 @@ +--source include/galera_cluster.inc +--source include/have_sequence.inc + +# Save original auto_increment_offset values. +--let $node_1=node_1 +--let $node_2=node_2 +--source include/auto_increment_offset_save.inc + +# +# TODO : This would be correct way to test but as CREATE TABLE is TOI +# exactly the same string is executed on node_2 also leading to problem +# where t1.ibd file would be in same location on both node_1 and node_2 +# and this leads to fact that CREATE TABLE fails on node_2 as it +# file already exists. +# +# EVAL create table t1(id int not null primary key, b varchar(32)) engine=innodb DATA DIRECTORY='$MYSQL_TMP_DIR'; +# insert into t1 SELECT seq, md5(rand()) from seq_1_to_50000; +# --list_files $MYSQL_TMP_DIR/test +# --list_files $MYSQLD_DATADIR/test + +create table t1(id int not null primary key, b varchar(32)) engine=InnoDB; +insert into t1 SELECT seq, md5(rand()) from seq_1_to_50000; + +--connection node_2 +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE 'test/t1' +--source include/wait_condition.inc +--let $wait_condition = SELECT COUNT(*) = 50000 FROM t1 +--source include/wait_condition.inc + +--echo Shutting down server ... +--source include/shutdown_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +--connection node_2 +# enforce SST +--exec rm -rf $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat + +--echo Starting server ... +--source include/start_mysqld.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_5000 FROM t1; + +--connection node_2 +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc +SELECT COUNT(*) AS EXPECT_5000 FROM t1; + +# Restore original auto_increment_offset values. +--source include/auto_increment_offset_restore.inc + +DROP TABLE t1; + +--source include/galera_end.inc diff --git a/mysql-test/suite/sys_vars/r/sysvars_wsrep.result b/mysql-test/suite/sys_vars/r/sysvars_wsrep.result index 7d9a0338a4dd7..95d605985f682 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_wsrep.result +++ b/mysql-test/suite/sys_vars/r/sysvars_wsrep.result @@ -783,6 +783,23 @@ COMMAND_LINE_ARGUMENT REQUIRED GLOBAL_VALUE_PATH NULL IS_DEPRECATED NO DEPRECATED_REPLACEMENT NULL +VARIABLE_NAME WSREP_SST_TMP_DIR +SESSION_VALUE NULL +GLOBAL_VALUE +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT Path to SST temp root directory +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY YES +COMMAND_LINE_ARGUMENT REQUIRED +GLOBAL_VALUE_PATH NULL +IS_DEPRECATED NO +DEPRECATED_REPLACEMENT NULL VARIABLE_NAME WSREP_START_POSITION SESSION_VALUE NULL GLOBAL_VALUE 00000000-0000-0000-0000-000000000000:-1 diff --git a/scripts/wsrep_sst_common.sh b/scripts/wsrep_sst_common.sh index e8bedfcce2e06..f99ce81c92982 100644 --- a/scripts/wsrep_sst_common.sh +++ b/scripts/wsrep_sst_common.sh @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2024 MariaDB +# Copyright (C) 2017-2025 MariaDB # Copyright (C) 2012-2015 Codership Oy # # This program is free software; you can redistribute it and/or modify @@ -198,6 +198,7 @@ WSREP_SST_OPT_BINLOG="" WSREP_SST_OPT_BINLOG_INDEX="" WSREP_SST_OPT_LOG_BASENAME="" WSREP_SST_OPT_DATA="" +WSREP_SST_OPT_TMP_DIR="" WSREP_SST_OPT_AUTH="${WSREP_SST_OPT_AUTH:-}" WSREP_SST_OPT_USER="${WSREP_SST_OPT_USER:-}" WSREP_SST_OPT_PSWD="${WSREP_SST_OPT_PSWD:-}" @@ -315,6 +316,10 @@ case "$1" in readonly WSREP_SST_OPT_DATA=$(trim_dir "$2") shift ;; + '--sst-tmp-dir') + readonly WSREP_SST_OPT_TMP_DIR=$(trim_dir "$2") + shift + ;; '--aria-log-dir-path') # Let's remove the trailing slash: readonly ARIA_LOG_DIR=$(trim_dir "$2") @@ -657,6 +662,12 @@ case "$1" in fi skip_mysqld_arg=1 ;; + '--sst-tmp-dir') + if [ -z "$WSREP_SST_OPT_TMP_DIR" ]; then + MYSQLD_OPT_SST_TMP_DIR=$(trim_dir "$value") + fi + skip_mysqld_arg=1 + ;; esac if [ $skip_mysqld_arg -eq 0 ]; then original_cmd="$original_cmd '$1'" @@ -736,6 +747,10 @@ 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 [ -n "${MYSQLD_OPT_SST_TMP_DIR:-}" -a \ + -z "$WSREP_SST_OPT_TMP_DIR" ]; then + readonly WSREP_SST_OPT_TMP_DIR="$MYSQLD_OPT_SST_TMP_DIR" +fi # If the --log-bin option is present without a value, then # set WSREP_SST_OPT_BINLOG value using other arguments: diff --git a/scripts/wsrep_sst_mariabackup.sh b/scripts/wsrep_sst_mariabackup.sh index d56715ef89533..fcf74db8292c1 100644 --- a/scripts/wsrep_sst_mariabackup.sh +++ b/scripts/wsrep_sst_mariabackup.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash set -ue - -# Copyright (C) 2017-2024 MariaDB +# For script debugging +# set -uex +# +# Copyright (C) 2017-2025 MariaDB # Copyright (C) 2013 Percona Inc # # This program is free software; you can redistribute it and/or modify @@ -84,6 +86,7 @@ STATDIR="" tmpopts="" itmpdir="" xtmpdir="" +sstdir="" scomp="" sdecomp="" @@ -1315,14 +1318,23 @@ else # joiner fi fi - if [ -d "$DATA/.sst" ]; then + # If user has configured temporary sst directory use it + if [ -n "$WSREP_SST_OPT_TMP_DIR" ]; then + sstdir="$WSREP_SST_OPT_TMP_DIR/.sst" + wsrep_log_info "Using user configured SST directory $sstdir" + else + sstdir="$DATA/.sst" + wsrep_log_info "Using default SST directory $sstdir" + fi + + if [ -d "$sstdir" ]; then wsrep_log_info \ "WARNING: Stale temporary SST directory:" \ - "'$DATA/.sst' from previous state transfer, removing..." - rm -rf "$DATA/.sst" + "'$sstdir' from previous state transfer, removing..." + rm -rf "$sstdir" fi - mkdir -p "$DATA/.sst" - (recv_joiner "$DATA/.sst" "$stagemsg-SST" 0 0 0) & + mkdir -p "$sstdir" + (recv_joiner "$sstdir" "$stagemsg-SST" 0 0 0) & BACKUP_PID=$! wsrep_log_info "Proceeding with SST" @@ -1383,7 +1395,7 @@ else # joiner [ -f "$DATA/mariadb_backup_binlog_pos_innodb" ] && rm -f "$DATA/mariadb_backup_binlog_pos_innodb" TDATA="$DATA" - DATA="$DATA/.sst" + DATA="$sstdir" MAGIC_FILE="$DATA/$INFO_FILE" wsrep_log_info "Waiting for SST streaming to complete!" @@ -1543,6 +1555,7 @@ else # joiner cd "$OLD_PWD" fi + cd "$TDATA" MAGIC_FILE="$TDATA/$INFO_FILE" DONOR_MAGIC_FILE="$TDATA/$DONOR_INFO_FILE" diff --git a/sql/mysqld.cc b/sql/mysqld.cc index de79500825457..cdbfcc974f507 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2023, MariaDB + Copyright (c) 2008, 2025, MariaDB plc 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 diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 0936776cf0a00..e6e0579dba7b0 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2012, 2022, MariaDB Corporation. + Copyright (c) 2012, 2025, MariaDB plc. 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 @@ -6813,6 +6813,11 @@ static Sys_var_uint Sys_wsrep_applier_retry_count ( VALID_RANGE(0, UINT_MAX), DEFAULT(0), BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG); +static Sys_var_charptr_fscs Sys_wsrep_sst_tmp_dir( + "wsrep_sst_tmp_dir", "Path to SST temp root directory", + READ_ONLY GLOBAL_VAR(wsrep_sst_tmp_dir), CMD_LINE(REQUIRED_ARG), + DEFAULT(0), NO_MUTEX_GUARD, NOT_IN_BINLOG); + #endif /* WITH_WSREP */ static bool fix_host_cache_size(sys_var *, THD *, enum_var_type) diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 2187a5468c439..a8a3f54af7031 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -86,6 +86,8 @@ const char *wsrep_dbug_option; const char *wsrep_notify_cmd; const char *wsrep_status_file; const char *wsrep_allowlist; +const char *wsrep_sst_tmp_dir; +char *wsrep_sst_tmp_dir_real=nullptr; ulong wsrep_debug; // Debug level logging my_bool wsrep_convert_LOCK_to_trx; // Convert locking sessions to trx @@ -911,6 +913,9 @@ int wsrep_init() if (!wsrep_data_home_dir || strlen(wsrep_data_home_dir) == 0) wsrep_data_home_dir= mysql_real_data_home; + /* In case of errors, SST tmp dir is not set */ + wsrep_sst_tmp_dir_check(); + Wsrep_server_state::init_provider_services(); if (Wsrep_server_state::instance().load_provider( wsrep_provider, @@ -947,6 +952,7 @@ int wsrep_init() WSREP_DEBUG("SR storage init for: %s", (wsrep_SR_store_type == WSREP_SR_STORE_TABLE) ? "table" : "void"); + return 0; } @@ -1067,6 +1073,12 @@ void wsrep_deinit(bool free_options) wsrep_inited= 0; + if (wsrep_sst_tmp_dir_real) + { + my_free(wsrep_sst_tmp_dir_real); + wsrep_sst_tmp_dir_real= nullptr; + } + if (wsrep_provider_capabilities != NULL) { char* p= wsrep_provider_capabilities; diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h index 1b381cca4147f..dca2c7b038faf 100644 --- a/sql/wsrep_mysqld.h +++ b/sql/wsrep_mysqld.h @@ -92,6 +92,8 @@ extern uint32 wsrep_gtid_domain_id; extern std::atomic wsrep_thread_create_failed; extern ulonglong wsrep_mode; extern uint wsrep_applier_retry_count; +extern char *wsrep_sst_tmp_dir_real; +extern const char *wsrep_sst_tmp_dir; enum enum_wsrep_reject_types { WSREP_REJECT_NONE, /* nothing rejected */ diff --git a/sql/wsrep_sst.cc b/sql/wsrep_sst.cc index e7de810abd351..665e728cf92cb 100644 --- a/sql/wsrep_sst.cc +++ b/sql/wsrep_sst.cc @@ -37,6 +37,7 @@ #include #include "debug_sync.h" #include "my_rnd.h" +#include #include @@ -609,8 +610,37 @@ static int generate_binlog_index_opt_val(char** ret) return 0; } +/* + Generate sst_opt_tmp_dir string for sst_donate_other() + + Returns zero on success, negative error code otherwise. + + String containing wsrep_sst_tmp_dir is stored in param ret if configured, + otherwise empty string. Returned string should be + freed with my_free(). + */ +static int generate_sst_opt_tmp_dir(char** ret) +{ + DBUG_ASSERT(ret); + *ret= NULL; + if (wsrep_sst_tmp_dir_real && strlen(wsrep_sst_tmp_dir_real)) + { + *ret= generate_name_value(WSREP_SST_OPT_TMP_DIR, + wsrep_sst_tmp_dir_real); + WSREP_INFO("Generate sst_tmp_dir = %s", *ret); + } + else + { + *ret= my_strdup(PSI_INSTRUMENT_ME, "", MYF(0)); + } + if (!*ret) return -ENOMEM; + return 0; +} + + // report progress event -static void sst_report_progress(int const from, +static bool sst_report_progress(const bool joiner, + int const from, long long const total_prev, long long const total, long long const complete) @@ -620,12 +650,42 @@ static void sst_report_progress(int const from, snprintf(buf, buf_len, "{ \"from\": %d, \"to\": %d, \"total\": %lld, \"done\": %lld, " "\"indefinite\": -1 }", - from, WSREP_MEMBER_JOINED, total_prev + total, total_prev +complete); - WSREP_DEBUG("REPORTING SST PROGRESS: '%s'", buf); + from, WSREP_MEMBER_JOINED, total_prev + total, total_prev + complete); + + if (joiner) + { + const char *path = (wsrep_sst_tmp_dir_real && strlen(wsrep_sst_tmp_dir_real) != 0) ? + wsrep_sst_tmp_dir_real : mysql_real_data_home_ptr; + + const std::filesystem::path p(path); + std::error_code ec; + std::filesystem::space_info si = std::filesystem::space(p, ec); + constexpr std::uintmax_t failed(-1); + if (si.available == failed) + si.available= 0; + + const unsigned long long requested= total + total_prev; + + WSREP_INFO("Requested total size %llu space available %llu in path %s", + total, (unsigned long long)si.available, path); + + if (requested >= si.available) + { + WSREP_ERROR("Target path %s does not have enough available space for" + " SST data available: %llu bytes < requested total %llu bytes ", + path, (unsigned long long)si.available, requested); + return true; + } + } + + WSREP_INFO("SST in progress '%s'", buf); + + return false; } // process "complete" event from SST script feedback -static void sst_handle_complete(const char* const input, +static bool sst_handle_complete(const bool joiner, + const char* const input, long long const total_prev, long long* total, long long* complete, @@ -637,12 +697,14 @@ static void sst_handle_complete(const char* const input, { *complete= x; if (*complete > *total) *total= *complete; - sst_report_progress(from, total_prev, *total, *complete); + return sst_report_progress(joiner, from, total_prev, *total, *complete); } + return false; } // process "total" event from SST script feedback -static void sst_handle_total(const char* const input, +static bool sst_handle_total(const bool joiner, + const char* const input, long long* total_prev, long long* total, long long* complete, @@ -656,8 +718,9 @@ static void sst_handle_total(const char* const input, *total_prev+= *total; *total= x; *complete= 0; - sst_report_progress(from, *total_prev, *total, *complete); + return sst_report_progress(joiner, from, *total_prev, *total, *complete); } + return false; } struct sst_thread_init @@ -758,13 +821,21 @@ static void* sst_joiner_thread (void* a) if (!strncasecmp (tmp, magic_complete, complete_len)) { - sst_handle_complete(tmp + complete_len, total_prev, &total, &complete, - from); + if (sst_handle_complete(true, tmp + complete_len, total_prev, + &total, &complete, from)) + { + err = 1; + goto err; + } goto wait_signal; } else if (!strncasecmp (tmp, magic_total, total_len)) { - sst_handle_total(tmp + total_len, &total_prev, &total, &complete, from); + if (sst_handle_total(true, tmp + total_len, &total_prev, &total, &complete, from)) + { + err = 1; + goto err; + } goto wait_signal; } } @@ -1206,6 +1277,7 @@ static ssize_t sst_prepare_other (const char* method, char* binlog_opt_val= NULL; char* binlog_index_opt_val= NULL; + char* sst_tmp_dir=NULL; int ret; if ((ret= generate_binlog_opt_val(&binlog_opt_val))) @@ -1223,6 +1295,14 @@ static ssize_t sst_prepare_other (const char* method, return ret; } + if ((ret= generate_sst_opt_tmp_dir(&sst_tmp_dir))) + { + WSREP_ERROR("sst_prepare_other(): generate_sst_opt_tmp_dir() failed %d", + ret); + if (sst_tmp_dir) my_free(sst_tmp_dir); + return ret; + } + make_wsrep_defaults_file(); ret= snprintf (cmd_str(), cmd_len, @@ -1234,15 +1314,18 @@ static ssize_t sst_prepare_other (const char* method, WSREP_SST_OPT_PARENT " %d " WSREP_SST_OPT_PROGRESS " %d" "%s" + "%s" "%s", method, addr_in, mysql_real_data_home, wsrep_defaults_file, (int)getpid(), wsrep_debug ? 1 : 0, - binlog_opt_val, binlog_index_opt_val); + binlog_opt_val, binlog_index_opt_val, + sst_tmp_dir); my_free(binlog_opt_val); my_free(binlog_index_opt_val); + my_free(sst_tmp_dir); if (ret < 0 || size_t(ret) >= cmd_len) { @@ -2138,13 +2221,13 @@ static void* sst_donor_thread (void* a) if (!strncasecmp (out, magic_complete, complete_len)) { - sst_handle_complete(out + complete_len, total_prev, &total, &complete, + sst_handle_complete(false, out + complete_len, total_prev, &total, &complete, from); goto wait_signal; } else if (!strncasecmp (out, magic_total, total_len)) { - sst_handle_total(out + total_len, &total_prev, &total, &complete, from); + sst_handle_total(false, out + total_len, &total_prev, &total, &complete, from); goto wait_signal; } else if (!strcasecmp (out, magic_flush)) diff --git a/sql/wsrep_sst.h b/sql/wsrep_sst.h index 929a6daa8cc89..d37d6585e319b 100644 --- a/sql/wsrep_sst.h +++ b/sql/wsrep_sst.h @@ -1,4 +1,5 @@ -/* Copyright (C) 2013-2018 Codership Oy +/* Copyright (C) 2013-2025 Codership Oy + Copyright (C) 2025 MariaDB plc 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 @@ -47,6 +48,7 @@ #define WSREP_SST_OPT_GTID "--gtid" #define WSREP_SST_OPT_BYPASS "--bypass" #define WSREP_SST_OPT_GTID_DOMAIN_ID "--gtid-domain-id" +#define WSREP_SST_OPT_TMP_DIR "--sst-tmp-dir" #define WSREP_SST_MYSQLDUMP "mysqldump" #define WSREP_SST_RSYNC "rsync" diff --git a/sql/wsrep_var.cc b/sql/wsrep_var.cc index f20eda78e9985..5db95a183df84 100644 --- a/sql/wsrep_var.cc +++ b/sql/wsrep_var.cc @@ -30,6 +30,10 @@ #include "wsrep_server_state.h" #include "wsrep_plugin.h" /* wsrep_provider_plugin_enabled() */ #include "wsrep_schema.h" +#include "wsrep_mysqld.h" + +#include // statinfo +#include // stat ulong wsrep_reject_queries; @@ -1206,3 +1210,59 @@ bool wsrep_max_ws_rows_check(sys_var *self, THD* thd, set_var* var) } return false; } + +/** Function is used to check if user given sst temporary directory +is valid. Function allows nullptr or empty string i.e. no directory +given. If something real is given it must be an path that exists, +short enough, path must be a directory and it must not be +same as datadir. If proper path was not given actual used +parameter value remains as nullptr. If proper path was given +it is copied to wsrep_sst_tmp_dir_real and used on SST. */ +void wsrep_sst_tmp_dir_check(void) +{ + if (wsrep_sst_tmp_dir == nullptr || strlen(wsrep_sst_tmp_dir) == 0) + return; // DEFAULT is ok + + if (strlen(wsrep_sst_tmp_dir) >= FN_REFLEN) + { + WSREP_ERROR("Option --wsrep-sst-tmp-dir value %s is too long", wsrep_sst_tmp_dir); + return; + } + + struct stat statinfo; + int ret = stat(wsrep_sst_tmp_dir, &statinfo); + + if (!ret) + { + /* path exists, ok */ + } else { + /* path does not exist */ + WSREP_WARN("SST temporary path %s does not exists, path not used.", + wsrep_sst_tmp_dir); + return; + } + + if (!S_ISDIR(statinfo.st_mode)) + { + WSREP_WARN("SST temporary path %s is not a directory, path not used.", + wsrep_sst_tmp_dir); + return; + } + + /* If path is almost same, do not allow it. */ + struct stat datadir_stat; + if (stat(mysql_real_data_home_ptr, &datadir_stat) == 0) + { + if (statinfo.st_dev == datadir_stat.st_dev && + statinfo.st_ino == datadir_stat.st_ino) + { + WSREP_WARN("SST temporary path %s is same prefix as datadir %s, path not used.", + wsrep_sst_tmp_dir, mysql_real_data_home_ptr); + return; + } + } + + wsrep_sst_tmp_dir_real= my_strdup(PSI_INSTRUMENT_ME, wsrep_sst_tmp_dir, MYF(0)); + if (wsrep_sst_tmp_dir_real) + WSREP_INFO("SST temporary path set to %s", wsrep_sst_tmp_dir_real); +} diff --git a/sql/wsrep_var.h b/sql/wsrep_var.h index 00cd0baa2a441..2a4ef3ea5a47d 100644 --- a/sql/wsrep_var.h +++ b/sql/wsrep_var.h @@ -113,6 +113,8 @@ extern bool wsrep_gtid_domain_id_update UPDATE_ARGS; extern bool wsrep_mode_check CHECK_ARGS; extern bool wsrep_forced_binlog_format_check CHECK_ARGS; + +extern void wsrep_sst_tmp_dir_check(void); #else /* WITH_WSREP */ #define wsrep_provider_init(X)