Commit 1b478a7a authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-13311 Presence of old logs in 10.2.7 will corrupt restored instance (change in behavior)

Mariabackup 10.2.7 would delete the redo log files after a successful
--prepare operation. If the user is manually copying the prepared files
instead of using the --copy-back option, it could happen that some old
redo log file would be preserved in the restored location. These old
redo log files could cause corruption of the restored data files when
the server is started up.

We prevent this scenario by creating a "poisoned" redo log file
ib_logfile0 at the end of the --prepare step. The poisoning consists
of simply truncating the file to an empty file. InnoDB will refuse
to start up on an empty redo log file.

copy_back(): Delete all redo log files in the target if the source
file ib_logfile0 is empty. (Previously we did this if the source
file is missing.)

SRV_OPERATION_RESTORE_EXPORT: A new variant of SRV_OPERATION_RESTORE
when the --export option is specified. In this mode, we will keep
deleting all redo log files, instead of truncating the first one.

delete_log_files(): Add a parameter for the first file to delete,
to be passed as 0 or 1.

innobase_start_or_create_for_mysql(): In mariabackup --prepare,
tolerate an empty ib_logfile0 file. Otherwise, require the first
redo log file to be longer than 4 blocks (2048 bytes). Unless
--export was specified, truncate the first log file at the
end of --prepare.
parent dc93ce8d
...@@ -1707,7 +1707,8 @@ copy_back() ...@@ -1707,7 +1707,8 @@ copy_back()
if it exists. */ if it exists. */
ds_data = ds_create(dst_dir, DS_TYPE_LOCAL); ds_data = ds_create(dst_dir, DS_TYPE_LOCAL);
if (!file_exists("ib_logfile0")) { MY_STAT stat_arg;
if (!my_stat("ib_logfile0", &stat_arg, MYF(0)) || !stat_arg.st_size) {
/* After completed --prepare, redo log files are redundant. /* After completed --prepare, redo log files are redundant.
We must delete any redo logs at the destination, so that We must delete any redo logs at the destination, so that
the database will not jump to a different log sequence number the database will not jump to a different log sequence number
......
...@@ -4793,7 +4793,8 @@ xtrabackup_prepare_func(char** argv) ...@@ -4793,7 +4793,8 @@ xtrabackup_prepare_func(char** argv)
if (!ok) goto error_cleanup; if (!ok) goto error_cleanup;
} }
srv_operation = SRV_OPERATION_RESTORE; srv_operation = xtrabackup_export
? SRV_OPERATION_RESTORE_EXPORT : SRV_OPERATION_RESTORE;
if (innodb_init_param()) { if (innodb_init_param()) {
goto error_cleanup; goto error_cleanup;
......
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB; CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB;
SELECT * FROM INFORMATION_SCHEMA.ENGINES
WHERE engine = 'innodb'
AND support IN ('YES', 'DEFAULT', 'ENABLED');
ENGINE SUPPORT COMMENT TRANSACTIONS XA SAVEPOINTS
FOUND 1 /InnoDB: Log file .*ib_logfile0 size 0 is too small/ in mysqld.1.err
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
BEGIN; BEGIN;
INSERT INTO t1 VALUES (42); INSERT INTO t1 VALUES (42);
SELECT * FROM t1; SELECT * FROM t1;
......
...@@ -23,14 +23,33 @@ call mtr.add_suppression("InnoDB: Log file .*ib_logfile[01].* size"); ...@@ -23,14 +23,33 @@ call mtr.add_suppression("InnoDB: Log file .*ib_logfile[01].* size");
call mtr.add_suppression("InnoDB: Unable to open .*ib_logfile0. to check native AIO read support"); call mtr.add_suppression("InnoDB: Unable to open .*ib_logfile0. to check native AIO read support");
FLUSH TABLES; FLUSH TABLES;
--enable_query_log --enable_query_log
let MYSQLD_DATADIR= `select @@datadir`;
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB;
--source include/shutdown_mysqld.inc
--move_file $MYSQLD_DATADIR/ib_logfile0 $MYSQLD_DATADIR/ib_logfile.old
write_file $MYSQLD_DATADIR/ib_logfile0;
EOF
let $check_no_innodb=SELECT * FROM INFORMATION_SCHEMA.ENGINES
WHERE engine = 'innodb'
AND support IN ('YES', 'DEFAULT', 'ENABLED');
--let $restart_parameters= --innodb-thread-concurrency=1 --innodb-log-file-size=1m --innodb-log-files-in-group=2 --let $restart_parameters= --innodb-thread-concurrency=1 --innodb-log-file-size=1m --innodb-log-files-in-group=2
--source include/restart_mysqld.inc --source include/start_mysqld.inc
eval $check_no_innodb;
--remove_file $MYSQLD_DATADIR/ib_logfile0
--move_file $MYSQLD_DATADIR/ib_logfile.old $MYSQLD_DATADIR/ib_logfile.0
--source include/shutdown_mysqld.inc
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
let SEARCH_PATTERN= InnoDB: Log file .*ib_logfile0 size 0 is too small;
--source include/search_pattern_in_file.inc
--source include/start_mysqld.inc
CHECK TABLE t1;
--let $restart_parameters= --innodb-thread-concurrency=100 --innodb-log-file-size=10M --innodb-log-files-in-group=2 --let $restart_parameters= --innodb-thread-concurrency=100 --innodb-log-file-size=10M --innodb-log-files-in-group=2
--source include/restart_mysqld.inc --source include/restart_mysqld.inc
CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB;
BEGIN; BEGIN;
INSERT INTO t1 VALUES (42); INSERT INTO t1 VALUES (42);
...@@ -52,9 +71,7 @@ SELECT * FROM t1; ...@@ -52,9 +71,7 @@ SELECT * FROM t1;
INSERT INTO t1 VALUES (0),(123); INSERT INTO t1 VALUES (0),(123);
let MYSQLD_DATADIR= `select @@datadir`;
let SEARCH_ABORT = NOT FOUND; let SEARCH_ABORT = NOT FOUND;
let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err;
BEGIN; BEGIN;
DELETE FROM t1 WHERE a>0; DELETE FROM t1 WHERE a>0;
......
...@@ -9,6 +9,7 @@ INSERT INTO t VALUES('foobar2'); ...@@ -9,6 +9,7 @@ INSERT INTO t VALUES('foobar2');
# remove datadir # remove datadir
# xtrabackup move back # xtrabackup move back
# restart server # restart server
ib_logfile0
SELECT * FROM t; SELECT * FROM t;
c c
foobar1 foobar1
......
...@@ -24,6 +24,7 @@ exec $XTRABACKUP --prepare --target-dir=$targetdir; ...@@ -24,6 +24,7 @@ exec $XTRABACKUP --prepare --target-dir=$targetdir;
--enable_result_log --enable_result_log
--list_files $targetdir ib_logfile* --list_files $targetdir ib_logfile*
--cat_file $targetdir/ib_logfile0
SELECT * FROM t; SELECT * FROM t;
DROP TABLE t; DROP TABLE t;
......
...@@ -508,10 +508,12 @@ enum srv_operation_mode { ...@@ -508,10 +508,12 @@ enum srv_operation_mode {
SRV_OPERATION_NORMAL, SRV_OPERATION_NORMAL,
/** Mariabackup taking a backup */ /** Mariabackup taking a backup */
SRV_OPERATION_BACKUP, SRV_OPERATION_BACKUP,
/** Mariabackup restoring a backup */ /** Mariabackup restoring a backup for subsequent --copy-back */
SRV_OPERATION_RESTORE, SRV_OPERATION_RESTORE,
/** Mariabackup restoring the incremental part of a backup */ /** Mariabackup restoring the incremental part of a backup */
SRV_OPERATION_RESTORE_DELTA SRV_OPERATION_RESTORE_DELTA,
/** Mariabackup restoring a backup for subsequent --export */
SRV_OPERATION_RESTORE_EXPORT
}; };
/** Current mode of operation */ /** Current mode of operation */
......
...@@ -1922,7 +1922,8 @@ void ...@@ -1922,7 +1922,8 @@ void
recv_apply_hashed_log_recs(bool last_batch) recv_apply_hashed_log_recs(bool last_batch)
{ {
ut_ad(srv_operation == SRV_OPERATION_NORMAL ut_ad(srv_operation == SRV_OPERATION_NORMAL
|| srv_operation == SRV_OPERATION_RESTORE); || srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT);
mutex_enter(&recv_sys->mutex); mutex_enter(&recv_sys->mutex);
...@@ -1941,7 +1942,8 @@ recv_apply_hashed_log_recs(bool last_batch) ...@@ -1941,7 +1942,8 @@ recv_apply_hashed_log_recs(bool last_batch)
ut_ad(!last_batch == log_mutex_own()); ut_ad(!last_batch == log_mutex_own());
recv_no_ibuf_operations = !last_batch recv_no_ibuf_operations = !last_batch
|| srv_operation == SRV_OPERATION_RESTORE; || srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT;
ut_d(recv_no_log_write = recv_no_ibuf_operations); ut_d(recv_no_log_write = recv_no_ibuf_operations);
...@@ -2960,7 +2962,8 @@ static ...@@ -2960,7 +2962,8 @@ static
dberr_t dberr_t
recv_init_missing_space(dberr_t err, const recv_spaces_t::const_iterator& i) recv_init_missing_space(dberr_t err, const recv_spaces_t::const_iterator& i)
{ {
if (srv_operation == SRV_OPERATION_RESTORE) { if (srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT) {
ib::warn() << "Tablespace " << i->first << " was not" ib::warn() << "Tablespace " << i->first << " was not"
" found at " << i->second.name << " when" " found at " << i->second.name << " when"
" restoring a (partial?) backup. All redo log" " restoring a (partial?) backup. All redo log"
...@@ -3118,7 +3121,8 @@ recv_recovery_from_checkpoint_start(lsn_t flush_lsn) ...@@ -3118,7 +3121,8 @@ recv_recovery_from_checkpoint_start(lsn_t flush_lsn)
dberr_t err = DB_SUCCESS; dberr_t err = DB_SUCCESS;
ut_ad(srv_operation == SRV_OPERATION_NORMAL ut_ad(srv_operation == SRV_OPERATION_NORMAL
|| srv_operation == SRV_OPERATION_RESTORE); || srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT);
/* Initialize red-black tree for fast insertions into the /* Initialize red-black tree for fast insertions into the
flush_list during recovery process. */ flush_list during recovery process. */
......
...@@ -397,13 +397,14 @@ create_log_file( ...@@ -397,13 +397,14 @@ create_log_file(
/** Delete all log files. /** Delete all log files.
@param[in,out] logfilename buffer for log file name @param[in,out] logfilename buffer for log file name
@param[in] dirnamelen length of the directory path @param[in] dirnamelen length of the directory path
@param[in] n_files number of files to delete */ @param[in] n_files number of files to delete
@param[in] i first file to delete */
static static
void void
delete_log_files(char* logfilename, size_t dirnamelen, unsigned n_files) delete_log_files(char* logfilename, size_t dirnamelen, uint n_files, uint i=0)
{ {
/* Remove any old log files. */ /* Remove any old log files. */
for (unsigned i = 0; i < n_files; i++) { for (; i < n_files; i++) {
sprintf(logfilename + dirnamelen, "ib_logfile%u", i); sprintf(logfilename + dirnamelen, "ib_logfile%u", i);
/* Ignore errors about non-existent files or files /* Ignore errors about non-existent files or files
...@@ -911,6 +912,7 @@ srv_undo_tablespaces_init(bool create_new_db) ...@@ -911,6 +912,7 @@ srv_undo_tablespaces_init(bool create_new_db)
} }
/* fall through */ /* fall through */
case SRV_OPERATION_RESTORE: case SRV_OPERATION_RESTORE:
case SRV_OPERATION_RESTORE_EXPORT:
ut_ad(!create_new_db); ut_ad(!create_new_db);
/* Check if any of the UNDO tablespace needs fix-up because /* Check if any of the UNDO tablespace needs fix-up because
...@@ -1321,6 +1323,7 @@ srv_shutdown_all_bg_threads() ...@@ -1321,6 +1323,7 @@ srv_shutdown_all_bg_threads()
break; break;
case SRV_OPERATION_NORMAL: case SRV_OPERATION_NORMAL:
case SRV_OPERATION_RESTORE: case SRV_OPERATION_RESTORE:
case SRV_OPERATION_RESTORE_EXPORT:
if (!buf_page_cleaner_is_active if (!buf_page_cleaner_is_active
&& os_aio_all_slots_free()) { && os_aio_all_slots_free()) {
os_aio_wake_all_threads_at_shutdown(); os_aio_wake_all_threads_at_shutdown();
...@@ -1492,7 +1495,8 @@ innobase_start_or_create_for_mysql() ...@@ -1492,7 +1495,8 @@ innobase_start_or_create_for_mysql()
unsigned i = 0; unsigned i = 0;
ut_ad(srv_operation == SRV_OPERATION_NORMAL ut_ad(srv_operation == SRV_OPERATION_NORMAL
|| srv_operation == SRV_OPERATION_RESTORE); || srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT);
if (srv_force_recovery == SRV_FORCE_NO_LOG_REDO) { if (srv_force_recovery == SRV_FORCE_NO_LOG_REDO) {
srv_read_only_mode = true; srv_read_only_mode = true;
...@@ -1982,7 +1986,9 @@ innobase_start_or_create_for_mysql() ...@@ -1982,7 +1986,9 @@ innobase_start_or_create_for_mysql()
if (err == DB_NOT_FOUND) { if (err == DB_NOT_FOUND) {
if (i == 0) { if (i == 0) {
if (srv_operation if (srv_operation
== SRV_OPERATION_RESTORE) { == SRV_OPERATION_RESTORE
|| srv_operation
== SRV_OPERATION_RESTORE_EXPORT) {
return(DB_SUCCESS); return(DB_SUCCESS);
} }
if (flushed_lsn if (flushed_lsn
...@@ -2046,6 +2052,26 @@ innobase_start_or_create_for_mysql() ...@@ -2046,6 +2052,26 @@ innobase_start_or_create_for_mysql()
} }
if (i == 0) { if (i == 0) {
if (size == 0
&& (srv_operation
== SRV_OPERATION_RESTORE
|| srv_operation
== SRV_OPERATION_RESTORE_EXPORT)) {
/* Tolerate an empty ib_logfile0
from a previous run of
mariabackup --prepare. */
return(DB_SUCCESS);
}
/* The first log file must consist of
at least the following 512-byte pages:
header, checkpoint page 1, empty,
checkpoint page 2, redo log page(s) */
if (size <= OS_FILE_LOG_BLOCK_SIZE * 4) {
ib::error() << "Log file "
<< logfilename << " size "
<< size << " is too small";
return(srv_init_abort(DB_ERROR));
}
srv_log_file_size = size; srv_log_file_size = size;
} else if (size != srv_log_file_size) { } else if (size != srv_log_file_size) {
...@@ -2312,11 +2338,13 @@ innobase_start_or_create_for_mysql() ...@@ -2312,11 +2338,13 @@ innobase_start_or_create_for_mysql()
recv_recovery_from_checkpoint_finish(); recv_recovery_from_checkpoint_finish();
if (srv_operation == SRV_OPERATION_RESTORE) { if (srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT) {
/* After applying the redo log from /* After applying the redo log from
SRV_OPERATION_BACKUP, flush the changes SRV_OPERATION_BACKUP, flush the changes
to the data files and delete the log file. to the data files and truncate or delete the log.
No further change to InnoDB files is needed. */ Unless --export is specified, no further change to
InnoDB files is needed. */
ut_ad(!srv_force_recovery); ut_ad(!srv_force_recovery);
ut_ad(srv_n_log_files_found <= 1); ut_ad(srv_n_log_files_found <= 1);
ut_ad(recv_no_log_write); ut_ad(recv_no_log_write);
...@@ -2326,8 +2354,18 @@ innobase_start_or_create_for_mysql() ...@@ -2326,8 +2354,18 @@ innobase_start_or_create_for_mysql()
fil_close_log_files(true); fil_close_log_files(true);
log_group_close_all(); log_group_close_all();
if (err == DB_SUCCESS) { if (err == DB_SUCCESS) {
bool trunc = srv_operation
== SRV_OPERATION_RESTORE;
/* Delete subsequent log files. */
delete_log_files(logfilename, dirnamelen, delete_log_files(logfilename, dirnamelen,
srv_n_log_files_found); srv_n_log_files_found, trunc);
if (trunc) {
/* Truncate the first log file. */
strcpy(logfilename + dirnamelen,
"ib_logfile0");
FILE* f = fopen(logfilename, "w");
fclose(f);
}
} }
return(err); return(err);
} }
...@@ -2792,6 +2830,7 @@ innodb_shutdown() ...@@ -2792,6 +2830,7 @@ innodb_shutdown()
case SRV_OPERATION_BACKUP: case SRV_OPERATION_BACKUP:
case SRV_OPERATION_RESTORE: case SRV_OPERATION_RESTORE:
case SRV_OPERATION_RESTORE_DELTA: case SRV_OPERATION_RESTORE_DELTA:
case SRV_OPERATION_RESTORE_EXPORT:
fil_close_all_files(); fil_close_all_files();
break; break;
case SRV_OPERATION_NORMAL: case SRV_OPERATION_NORMAL:
......
...@@ -620,6 +620,7 @@ trx_free_prepared( ...@@ -620,6 +620,7 @@ trx_free_prepared(
&& trx->is_recovered && trx->is_recovered
&& (!srv_was_started && (!srv_was_started
|| srv_operation == SRV_OPERATION_RESTORE || srv_operation == SRV_OPERATION_RESTORE
|| srv_operation == SRV_OPERATION_RESTORE_EXPORT
|| srv_read_only_mode || srv_read_only_mode
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO))); || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO)));
ut_a(trx->magic_n == TRX_MAGIC_N); ut_a(trx->magic_n == TRX_MAGIC_N);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment