Commit afb46158 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-11915 Detect InnoDB system tablespace size mismatch early

InnoDB would refuse to start up if there is a mismatch on
the size of the system tablespace files. However, before this
check is conducted, the system tablespace may already have been
heavily modified.

InnoDB should perform the size check as early as possible.

recv_recovery_from_checkpoint_finish():
Move the recv_apply_hashed_log_recs() call to
innobase_start_or_create_for_mysql().

innobase_start_or_create_for_mysql(): Test the mutex functionality
before doing anything else. Use a compile_time_assert() for a
sizeof() constraint. Check the size of the system tablespace as
early as possible.
parent 49fe9bad
......@@ -3381,15 +3381,6 @@ void
recv_recovery_from_checkpoint_finish(void)
/*======================================*/
{
/* Apply the hashed log records to the respective file pages */
if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
recv_apply_hashed_log_recs(TRUE);
}
DBUG_PRINT("ib_log", ("apply completed"));
if (recv_needed_recovery) {
trx_sys_print_mysql_master_log_pos();
trx_sys_print_mysql_binlog_offset();
......
......@@ -3,6 +3,7 @@
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2008, Google Inc.
Copyright (c) 2009, Percona Inc.
Copyright (c) 2013, 2017, MariaDB Corporation
Portions of this file contain modifications contributed and copyrighted by
Google, Inc. Those modifications are gratefully acknowledged and are described
......@@ -1550,8 +1551,6 @@ innobase_start_or_create_for_mysql(void)
ulint max_arch_log_no;
#endif /* UNIV_LOG_ARCHIVE */
ulint sum_of_new_sizes;
ulint sum_of_data_file_sizes;
ulint tablespace_size_in_header;
dberr_t err;
unsigned i;
ulint srv_n_log_files_found = srv_n_log_files;
......@@ -1564,6 +1563,19 @@ innobase_start_or_create_for_mysql(void)
size_t dirnamelen;
bool sys_datafiles_created = false;
/* Check that os_fast_mutexes work as expected */
os_fast_mutex_init(PFS_NOT_INSTRUMENTED, &srv_os_test_mutex);
ut_a(0 == os_fast_mutex_trylock(&srv_os_test_mutex));
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_lock(&srv_os_test_mutex);
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_free(&srv_os_test_mutex);
high_level_read_only = srv_read_only_mode
|| srv_force_recovery > SRV_FORCE_NO_TRX_UNDO;
......@@ -1606,22 +1618,7 @@ innobase_start_or_create_for_mysql(void)
#endif /* PAGE_ATOMIC_REF_COUNT */
);
if (sizeof(ulint) != sizeof(void*)) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: size of InnoDB's ulint is %lu, "
"but size of void*\n", (ulong) sizeof(ulint));
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: is %lu. The sizes should be the same "
"so that on a 64-bit\n",
(ulong) sizeof(void*));
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: platforms you can allocate more than 4 GB "
"of memory.\n");
}
compile_time_assert(sizeof(ulint) == sizeof(void*));
#ifdef UNIV_DEBUG
ut_print_timestamp(stderr);
......@@ -2332,7 +2329,6 @@ innobase_start_or_create_for_mysql(void)
trx_sys_create();
if (create_new_db) {
ut_a(!srv_read_only_mode);
mtr_start(&mtr);
......@@ -2446,24 +2442,89 @@ innobase_start_or_create_for_mysql(void)
LOG_CHECKPOINT, LSN_MAX,
min_flushed_lsn, max_flushed_lsn);
if (err == DB_SUCCESS) {
/* Initialize the change buffer. */
err = dict_boot();
}
if (err != DB_SUCCESS) {
return(err);
}
return(DB_ERROR);
if (!srv_read_only_mode) {
if (sum_of_new_sizes > 0) {
/* New data file(s) were added */
mtr_start(&mtr);
fsp_header_inc_size(0, sum_of_new_sizes, &mtr);
mtr_commit(&mtr);
/* Immediately write the log record about
increased tablespace size to disk, so that it
is durable even if mysqld would crash
quickly */
log_buffer_flush_to_disk();
}
}
/* Since the insert buffer init is in dict_boot, and the
insert buffer is needed in any disk i/o, first we call
dict_boot(). Note that trx_sys_init_at_db_start() only needs
to access space 0, and the insert buffer at this stage already
works for space 0. */
const ulint tablespace_size_in_header
= fsp_header_get_tablespace_size();
err = dict_boot();
#ifdef UNIV_DEBUG
/* buf_debug_prints = TRUE; */
#endif /* UNIV_DEBUG */
ulint sum_of_data_file_sizes = 0;
for (ulint d = 0; d < srv_n_data_files; d++) {
sum_of_data_file_sizes += srv_data_file_sizes[d];
}
/* Compare the system tablespace file size to what is
stored in FSP_SIZE. In open_or_create_data_files()
we already checked that the file sizes match the
innodb_data_file_path specification. */
if (srv_read_only_mode
|| sum_of_data_file_sizes == tablespace_size_in_header) {
/* Do not complain about the size. */
} else if (!srv_auto_extend_last_data_file
|| sum_of_data_file_sizes
< tablespace_size_in_header) {
ib_logf(IB_LOG_LEVEL_ERROR,
"Tablespace size stored in header is " ULINTPF
" pages, but the sum of data file sizes is "
ULINTPF " pages",
tablespace_size_in_header,
sum_of_data_file_sizes);
if (srv_force_recovery == 0
&& sum_of_data_file_sizes
< tablespace_size_in_header) {
ib_logf(IB_LOG_LEVEL_ERROR,
"Cannot start InnoDB. The tail of"
" the system tablespace is"
" missing. Have you edited"
" innodb_data_file_path in my.cnf"
" in an inappropriate way, removing"
" data files from there?"
" You can set innodb_force_recovery=1"
" in my.cnf to force"
" a startup if you are trying to"
" recover a badly corrupt database.");
if (err != DB_SUCCESS) {
return(err);
return(DB_ERROR);
}
}
/* This must precede recv_apply_hashed_log_recs(TRUE). */
ib_bh = trx_sys_init_at_db_start();
if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
/* Apply the hashed log records to the
respective file pages, for the last batch of
recv_group_scan_log_recs(). */
recv_apply_hashed_log_recs(TRUE);
DBUG_PRINT("ib_log", ("apply completed"));
}
n_recovered_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list);
/* The purge system needs to create the purge view and
......@@ -2610,20 +2671,8 @@ innobase_start_or_create_for_mysql(void)
trx_sys_file_format_tag_init();
}
if (!create_new_db && sum_of_new_sizes > 0) {
/* New data file(s) were added */
mtr_start(&mtr);
fsp_header_inc_size(0, sum_of_new_sizes, &mtr);
mtr_commit(&mtr);
/* Immediately write the log record about increased tablespace
size to disk, so that it is durable even if mysqld would crash
quickly */
log_buffer_flush_to_disk();
}
ut_ad(err == DB_SUCCESS);
ut_a(sum_of_new_sizes != ULINT_UNDEFINED);
#ifdef UNIV_LOG_ARCHIVE
/* Archiving is always off under MySQL */
......@@ -2766,125 +2815,6 @@ innobase_start_or_create_for_mysql(void)
buf_flush_page_cleaner_thread_started = true;
}
#ifdef UNIV_DEBUG
/* buf_debug_prints = TRUE; */
#endif /* UNIV_DEBUG */
sum_of_data_file_sizes = 0;
for (i = 0; i < srv_n_data_files; i++) {
sum_of_data_file_sizes += srv_data_file_sizes[i];
}
tablespace_size_in_header = fsp_header_get_tablespace_size();
if (!srv_read_only_mode
&& !srv_auto_extend_last_data_file
&& sum_of_data_file_sizes != tablespace_size_in_header) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: tablespace size"
" stored in header is %lu pages, but\n",
(ulong) tablespace_size_in_header);
ut_print_timestamp(stderr);
fprintf(stderr,
"InnoDB: the sum of data file sizes is %lu pages\n",
(ulong) sum_of_data_file_sizes);
if (srv_force_recovery == 0
&& sum_of_data_file_sizes < tablespace_size_in_header) {
/* This is a fatal error, the tail of a tablespace is
missing */
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Cannot start InnoDB."
" The tail of the system tablespace is\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: missing. Have you edited"
" innodb_data_file_path in my.cnf in an\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: inappropriate way, removing"
" ibdata files from there?\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: You can set innodb_force_recovery=1"
" in my.cnf to force\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: a startup if you are trying"
" to recover a badly corrupt database.\n");
return(DB_ERROR);
}
}
if (!srv_read_only_mode
&& srv_auto_extend_last_data_file
&& sum_of_data_file_sizes < tablespace_size_in_header) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: tablespace size stored in header"
" is %lu pages, but\n",
(ulong) tablespace_size_in_header);
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: the sum of data file sizes"
" is only %lu pages\n",
(ulong) sum_of_data_file_sizes);
if (srv_force_recovery == 0) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Cannot start InnoDB. The tail of"
" the system tablespace is\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: missing. Have you edited"
" innodb_data_file_path in my.cnf in an\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: inappropriate way, removing"
" ibdata files from there?\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: You can set innodb_force_recovery=1"
" in my.cnf to force\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: a startup if you are trying to"
" recover a badly corrupt database.\n");
return(DB_ERROR);
}
}
/* Check that os_fast_mutexes work as expected */
os_fast_mutex_init(PFS_NOT_INSTRUMENTED, &srv_os_test_mutex);
if (0 != os_fast_mutex_trylock(&srv_os_test_mutex)) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: pthread_mutex_trylock returns"
" an unexpected value on\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: success! Cannot continue.\n");
exit(1);
}
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_lock(&srv_os_test_mutex);
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_free(&srv_os_test_mutex);
if (srv_print_verbose_log) {
ib_logf(IB_LOG_LEVEL_INFO,
"%s started; log sequence number " LSN_PF "",
......
......@@ -3478,15 +3478,6 @@ void
recv_recovery_from_checkpoint_finish(void)
/*======================================*/
{
/* Apply the hashed log records to the respective file pages */
if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
recv_apply_hashed_log_recs(TRUE);
}
DBUG_PRINT("ib_log", ("apply completed"));
if (recv_needed_recovery) {
trx_sys_print_mysql_master_log_pos();
trx_sys_print_mysql_binlog_offset();
......
......@@ -1609,8 +1609,6 @@ innobase_start_or_create_for_mysql(void)
lsn_t max_arch_log_no = LSN_MAX;
#endif /* UNIV_LOG_ARCHIVE */
ulint sum_of_new_sizes;
ulint sum_of_data_file_sizes;
ulint tablespace_size_in_header;
dberr_t err;
unsigned i;
ulint srv_n_log_files_found = srv_n_log_files;
......@@ -1623,6 +1621,19 @@ innobase_start_or_create_for_mysql(void)
size_t dirnamelen;
bool sys_datafiles_created = false;
/* Check that os_fast_mutexes work as expected */
os_fast_mutex_init(PFS_NOT_INSTRUMENTED, &srv_os_test_mutex);
ut_a(0 == os_fast_mutex_trylock(&srv_os_test_mutex));
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_lock(&srv_os_test_mutex);
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_free(&srv_os_test_mutex);
high_level_read_only = srv_read_only_mode
|| srv_force_recovery > SRV_FORCE_NO_TRX_UNDO;
......@@ -1665,22 +1676,7 @@ innobase_start_or_create_for_mysql(void)
#endif /* PAGE_ATOMIC_REF_COUNT */
);
if (sizeof(ulint) != sizeof(void*)) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: size of InnoDB's ulint is %lu, "
"but size of void*\n", (ulong) sizeof(ulint));
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: is %lu. The sizes should be the same "
"so that on a 64-bit\n",
(ulong) sizeof(void*));
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: platforms you can allocate more than 4 GB "
"of memory.\n");
}
compile_time_assert(sizeof(ulint) == sizeof(void*));
/* If stacktrace is used we set up signal handler for SIGUSR2 signal
here. If signal handler set fails we report that and disable
......@@ -2411,7 +2407,6 @@ innobase_start_or_create_for_mysql(void)
trx_sys_create();
if (create_new_db) {
ut_a(!srv_read_only_mode);
init_log_online();
......@@ -2493,25 +2488,92 @@ innobase_start_or_create_for_mysql(void)
min_flushed_lsn, max_flushed_lsn);
if (err != DB_SUCCESS) {
return(DB_ERROR);
return(err);
}
init_log_online();
/* Since the insert buffer init is in dict_boot, and the
insert buffer is needed in any disk i/o, first we call
dict_boot(). Note that trx_sys_init_at_db_start() only needs
to access space 0, and the insert buffer at this stage already
works for space 0. */
/* Initialize the change buffer. */
err = dict_boot();
if (err != DB_SUCCESS) {
return(err);
}
if (!srv_read_only_mode) {
if (sum_of_new_sizes > 0) {
/* New data file(s) were added */
mtr_start(&mtr);
fsp_header_inc_size(0, sum_of_new_sizes, &mtr);
mtr_commit(&mtr);
/* Immediately write the log record about
increased tablespace size to disk, so that it
is durable even if mysqld would crash
quickly */
log_buffer_flush_to_disk();
}
}
const ulint tablespace_size_in_header
= fsp_header_get_tablespace_size();
#ifdef UNIV_DEBUG
/* buf_debug_prints = TRUE; */
#endif /* UNIV_DEBUG */
ulint sum_of_data_file_sizes = 0;
for (ulint d = 0; d < srv_n_data_files; d++) {
sum_of_data_file_sizes += srv_data_file_sizes[d];
}
/* Compare the system tablespace file size to what is
stored in FSP_SIZE. In open_or_create_data_files()
we already checked that the file sizes match the
innodb_data_file_path specification. */
if (srv_read_only_mode
|| sum_of_data_file_sizes == tablespace_size_in_header) {
/* Do not complain about the size. */
} else if (!srv_auto_extend_last_data_file
|| sum_of_data_file_sizes
< tablespace_size_in_header) {
ib_logf(IB_LOG_LEVEL_ERROR,
"Tablespace size stored in header is " ULINTPF
" pages, but the sum of data file sizes is "
ULINTPF " pages",
tablespace_size_in_header,
sum_of_data_file_sizes);
if (srv_force_recovery == 0
&& sum_of_data_file_sizes
< tablespace_size_in_header) {
ib_logf(IB_LOG_LEVEL_ERROR,
"Cannot start InnoDB. The tail of"
" the system tablespace is"
" missing. Have you edited"
" innodb_data_file_path in my.cnf"
" in an inappropriate way, removing"
" data files from there?"
" You can set innodb_force_recovery=1"
" in my.cnf to force"
" a startup if you are trying to"
" recover a badly corrupt database.");
return(DB_ERROR);
}
}
/* This must precede recv_apply_hashed_log_recs(TRUE). */
ib_bh = trx_sys_init_at_db_start();
if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) {
/* Apply the hashed log records to the
respective file pages, for the last batch of
recv_group_scan_log_recs(). */
recv_apply_hashed_log_recs(TRUE);
DBUG_PRINT("ib_log", ("apply completed"));
}
n_recovered_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list);
/* The purge system needs to create the purge view and
......@@ -2664,20 +2726,8 @@ innobase_start_or_create_for_mysql(void)
trx_sys_file_format_tag_init();
}
if (!create_new_db && sum_of_new_sizes > 0) {
/* New data file(s) were added */
mtr_start(&mtr);
fsp_header_inc_size(0, sum_of_new_sizes, &mtr);
mtr_commit(&mtr);
/* Immediately write the log record about increased tablespace
size to disk, so that it is durable even if mysqld would crash
quickly */
log_buffer_flush_to_disk();
}
ut_ad(err == DB_SUCCESS);
ut_a(sum_of_new_sizes != ULINT_UNDEFINED);
#ifdef UNIV_LOG_ARCHIVE
if (!srv_read_only_mode) {
......@@ -2826,125 +2876,6 @@ innobase_start_or_create_for_mysql(void)
buf_flush_lru_manager_thread_handle = os_thread_create(buf_flush_lru_manager_thread, NULL, NULL);
buf_flush_lru_manager_thread_started = true;
#ifdef UNIV_DEBUG
/* buf_debug_prints = TRUE; */
#endif /* UNIV_DEBUG */
sum_of_data_file_sizes = 0;
for (i = 0; i < srv_n_data_files; i++) {
sum_of_data_file_sizes += srv_data_file_sizes[i];
}
tablespace_size_in_header = fsp_header_get_tablespace_size();
if (!srv_read_only_mode
&& !srv_auto_extend_last_data_file
&& sum_of_data_file_sizes != tablespace_size_in_header) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: tablespace size"
" stored in header is %lu pages, but\n",
(ulong) tablespace_size_in_header);
ut_print_timestamp(stderr);
fprintf(stderr,
"InnoDB: the sum of data file sizes is %lu pages\n",
(ulong) sum_of_data_file_sizes);
if (srv_force_recovery == 0
&& sum_of_data_file_sizes < tablespace_size_in_header) {
/* This is a fatal error, the tail of a tablespace is
missing */
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Cannot start InnoDB."
" The tail of the system tablespace is\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: missing. Have you edited"
" innodb_data_file_path in my.cnf in an\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: inappropriate way, removing"
" ibdata files from there?\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: You can set innodb_force_recovery=1"
" in my.cnf to force\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: a startup if you are trying"
" to recover a badly corrupt database.\n");
return(DB_ERROR);
}
}
if (!srv_read_only_mode
&& srv_auto_extend_last_data_file
&& sum_of_data_file_sizes < tablespace_size_in_header) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: tablespace size stored in header"
" is %lu pages, but\n",
(ulong) tablespace_size_in_header);
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: the sum of data file sizes"
" is only %lu pages\n",
(ulong) sum_of_data_file_sizes);
if (srv_force_recovery == 0) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Cannot start InnoDB. The tail of"
" the system tablespace is\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: missing. Have you edited"
" innodb_data_file_path in my.cnf in an\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: inappropriate way, removing"
" ibdata files from there?\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: You can set innodb_force_recovery=1"
" in my.cnf to force\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: a startup if you are trying to"
" recover a badly corrupt database.\n");
return(DB_ERROR);
}
}
/* Check that os_fast_mutexes work as expected */
os_fast_mutex_init(PFS_NOT_INSTRUMENTED, &srv_os_test_mutex);
if (0 != os_fast_mutex_trylock(&srv_os_test_mutex)) {
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: Error: pthread_mutex_trylock returns"
" an unexpected value on\n");
ut_print_timestamp(stderr);
fprintf(stderr,
" InnoDB: success! Cannot continue.\n");
exit(1);
}
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_lock(&srv_os_test_mutex);
os_fast_mutex_unlock(&srv_os_test_mutex);
os_fast_mutex_free(&srv_os_test_mutex);
if (!srv_file_per_table && srv_pass_corrupt_table) {
fprintf(stderr, "InnoDB: Warning:"
" The option innodb_file_per_table is disabled,"
......
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