summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Kosov <claprix@yandex.ru>2021-07-16 00:39:39 +0300
committerEugene Kosov <claprix@yandex.ru>2021-08-17 20:28:42 +0600
commit890f2ad76975d66bfc78f54ec38dfa95c7248bdd (patch)
treee22a67c6df20802201d76f0054a1a5cf3edea43f
parent89445b64fef2c3c9f2cfb9f572dd19b88f3a48df (diff)
downloadmariadb-git-890f2ad76975d66bfc78f54ec38dfa95c7248bdd.tar.gz
MDEV-20931 ALTER...IMPORT can crash the server
Main idea: don't log-and-crash but propogate error to the upper layers of stack to handle it and show to a user.
-rw-r--r--mysql-test/suite/innodb/r/import_corrupted.result30
-rw-r--r--mysql-test/suite/innodb/t/import_corrupted.test68
-rw-r--r--storage/innobase/btr/btr0btr.cc22
-rw-r--r--storage/innobase/btr/btr0cur.cc10
-rw-r--r--storage/innobase/btr/btr0defragment.cc3
-rw-r--r--storage/innobase/buf/buf0buf.cc4
-rw-r--r--storage/innobase/buf/buf0rea.cc3
-rw-r--r--storage/innobase/fil/fil0fil.cc57
-rw-r--r--storage/innobase/include/btr0btr.h2
9 files changed, 166 insertions, 33 deletions
diff --git a/mysql-test/suite/innodb/r/import_corrupted.result b/mysql-test/suite/innodb/r/import_corrupted.result
new file mode 100644
index 00000000000..c0474ebbb1d
--- /dev/null
+++ b/mysql-test/suite/innodb/r/import_corrupted.result
@@ -0,0 +1,30 @@
+call mtr.add_suppression("Table `test`.`t2` should have 2 indexes but the tablespace has 1 indexes");
+call mtr.add_suppression("Index for table 't2' is corrupt; try to repair it");
+call mtr.add_suppression("Trying to read page number 23 in space .*, space name test/t2, which is outside the tablespace bounds. Byte offset 0, len 16384");
+CREATE TABLE t1 (
+id INT AUTO_INCREMENT PRIMARY KEY,
+not_id INT,
+data CHAR(255),
+data2 BLOB
+) ENGINE=INNODB;
+ALTER TABLE t1 MODIFY not_id INT UNIQUE KEY;
+connect purge_control,localhost,root,,;
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection default;
+DELETE FROM t1 WHERE id % 2 = 1;
+FLUSH TABLES t1 FOR EXPORT;
+UNLOCK TABLES;
+connection purge_control;
+COMMIT;
+connection default;
+DROP TABLE t1;
+CREATE TABLE t2 (
+id INT AUTO_INCREMENT PRIMARY KEY,
+not_id INT UNIQUE KEY,
+data CHAR(255),
+data2 BLOB
+) ENGINE=INNODB;
+ALTER TABLE t2 DISCARD TABLESPACE;
+ALTER TABLE t2 IMPORT TABLESPACE;
+ERROR HY000: Index for table 't2' is corrupt; try to repair it
+DROP TABLE t2;
diff --git a/mysql-test/suite/innodb/t/import_corrupted.test b/mysql-test/suite/innodb/t/import_corrupted.test
new file mode 100644
index 00000000000..ad8db5eb339
--- /dev/null
+++ b/mysql-test/suite/innodb/t/import_corrupted.test
@@ -0,0 +1,68 @@
+--source include/have_innodb.inc
+
+call mtr.add_suppression("Table `test`.`t2` should have 2 indexes but the tablespace has 1 indexes");
+call mtr.add_suppression("Index for table 't2' is corrupt; try to repair it");
+call mtr.add_suppression("Trying to read page number 23 in space .*, space name test/t2, which is outside the tablespace bounds. Byte offset 0, len 16384");
+
+let MYSQLD_DATADIR = `SELECT @@datadir`;
+
+CREATE TABLE t1 (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ not_id INT,
+ data CHAR(255),
+ data2 BLOB
+) ENGINE=INNODB;
+
+--disable_query_log
+--let i = 0
+while ($i != 1000) {
+ eval INSERT INTO t1 VALUES (DEFAULT, $i, REPEAT('b', 255), REPEAT('a', 5000));
+ --inc $i
+}
+--enable_query_log
+
+ALTER TABLE t1 MODIFY not_id INT UNIQUE KEY;
+
+connect (purge_control,localhost,root,,);
+START TRANSACTION WITH CONSISTENT SNAPSHOT;
+connection default;
+
+DELETE FROM t1 WHERE id % 2 = 1;
+
+FLUSH TABLES t1 FOR EXPORT;
+
+--copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/tmp.ibd
+--copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/tmp.cfg
+
+perl;
+use strict;
+die unless open(FILE, "+<$ENV{MYSQLD_DATADIR}/test/tmp.ibd");
+die unless truncate(FILE, 16384*23);
+close(FILE);
+EOF
+
+UNLOCK TABLES;
+connection purge_control;
+COMMIT;
+connection default;
+DROP TABLE t1;
+
+CREATE TABLE t2 (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ not_id INT UNIQUE KEY,
+ data CHAR(255),
+ data2 BLOB
+) ENGINE=INNODB;
+
+ALTER TABLE t2 DISCARD TABLESPACE;
+
+--copy_file $MYSQLD_DATADIR/test/tmp.ibd $MYSQLD_DATADIR/test/t2.ibd
+--copy_file $MYSQLD_DATADIR/test/tmp.cfg $MYSQLD_DATADIR/test/t2.cfg
+
+--error ER_NOT_KEYFILE
+ALTER TABLE t2 IMPORT TABLESPACE;
+
+DROP TABLE t2;
+
+--remove_file $MYSQLD_DATADIR/test/tmp.ibd
+--remove_file $MYSQLD_DATADIR/test/t2.ibd
diff --git a/storage/innobase/btr/btr0btr.cc b/storage/innobase/btr/btr0btr.cc
index f7fe4413086..a8ab25f12e9 100644
--- a/storage/innobase/btr/btr0btr.cc
+++ b/storage/innobase/btr/btr0btr.cc
@@ -3141,7 +3141,7 @@ func_exit:
@param[in,out] page page to remove
@param[in] index index tree
@param[in,out] mtr mini-transaction */
-void
+dberr_t
btr_level_list_remove_func(
ulint space,
const page_size_t& page_size,
@@ -3184,6 +3184,10 @@ btr_level_list_remove_func(
page_id_t(space, next_page_no), page_size,
RW_X_LATCH, index, mtr);
+ if (!next_block) {
+ return DB_ERROR;
+ }
+
page_t* next_page
= buf_block_get_frame(next_block);
#ifdef UNIV_BTR_DEBUG
@@ -3196,6 +3200,8 @@ btr_level_list_remove_func(
buf_block_get_page_zip(next_block),
prev_page_no, mtr);
}
+
+ return DB_SUCCESS;
}
/****************************************************************//**
@@ -3675,7 +3681,10 @@ retry:
btr_search_drop_page_hash_index(block);
/* Remove the page from the level list */
- btr_level_list_remove(space, page_size, page, index, mtr);
+ if (DB_SUCCESS != btr_level_list_remove(space, page_size,
+ page, index, mtr)) {
+ goto err_exit;
+ }
if (dict_index_is_spatial(index)) {
rec_t* my_rec = father_cursor.page_cur.rec;
@@ -3807,7 +3816,11 @@ retry:
#endif /* UNIV_BTR_DEBUG */
/* Remove the page from the level list */
- btr_level_list_remove(space, page_size, (page_t*)page, index, mtr);
+ if (DB_SUCCESS != btr_level_list_remove(space, page_size,
+ (page_t*)page,
+ index, mtr)) {
+ goto err_exit;
+ }
ut_ad(btr_node_ptr_get_child_page_no(
btr_cur_get_rec(&father_cursor), offsets)
@@ -4186,7 +4199,8 @@ btr_discard_page(
}
/* Remove the page from the level list */
- btr_level_list_remove(space, page_size, page, index, mtr);
+ ut_a(DB_SUCCESS == btr_level_list_remove(space, page_size, page,
+ index, mtr));
#ifdef UNIV_ZIP_DEBUG
{
diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc
index 3bbddb79a0c..8d0a34d07a1 100644
--- a/storage/innobase/btr/btr0cur.cc
+++ b/storage/innobase/btr/btr0cur.cc
@@ -344,10 +344,12 @@ btr_cur_latch_leaves(
page_size, RW_X_LATCH, cursor->index, mtr);
latch_leaves.blocks[2] = get_block;
#ifdef UNIV_BTR_DEBUG
- ut_a(page_is_comp(get_block->frame)
- == page_is_comp(page));
- ut_a(btr_page_get_prev(get_block->frame)
- == page_get_page_no(page));
+ if (get_block) {
+ ut_a(page_is_comp(get_block->frame)
+ == page_is_comp(page));
+ ut_a(btr_page_get_prev(get_block->frame)
+ == page_get_page_no(page));
+ }
#endif /* UNIV_BTR_DEBUG */
if (spatial) {
cursor->rtr_info->tree_blocks[
diff --git a/storage/innobase/btr/btr0defragment.cc b/storage/innobase/btr/btr0defragment.cc
index 645334cbf4d..38ef1db9cec 100644
--- a/storage/innobase/btr/btr0defragment.cc
+++ b/storage/innobase/btr/btr0defragment.cc
@@ -487,7 +487,8 @@ btr_defragment_merge_pages(
lock_update_merge_left(to_block, orig_pred,
from_block);
btr_search_drop_page_hash_index(from_block);
- btr_level_list_remove(space, page_size, (page_t*)from_page, index, mtr);
+ ut_a(DB_SUCCESS == btr_level_list_remove(space, page_size,
+ (page_t*)from_page, index, mtr));
btr_page_get_father(index, from_block, mtr, &parent);
btr_cur_node_ptr_delete(&parent, mtr);
/* btr_blob_dbg_remove(from_page, index,
diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc
index 3f399024b13..4c47c01bf63 100644
--- a/storage/innobase/buf/buf0buf.cc
+++ b/storage/innobase/buf/buf0buf.cc
@@ -4360,6 +4360,10 @@ loop:
return (NULL);
}
+ if (local_err == DB_IO_ERROR) {
+ return NULL;
+ }
+
ib::fatal() << "Unable to read page " << page_id
<< " into the buffer pool after "
<< BUF_PAGE_READ_MAX_RETRIES
diff --git a/storage/innobase/buf/buf0rea.cc b/storage/innobase/buf/buf0rea.cc
index 6b68e9f8fa5..c3f23df7509 100644
--- a/storage/innobase/buf/buf0rea.cc
+++ b/storage/innobase/buf/buf0rea.cc
@@ -201,7 +201,8 @@ buf_read_page_low(
}
return(0);
} else if (IORequest::ignore_missing(type)
- || *err == DB_TABLESPACE_DELETED) {
+ || *err == DB_TABLESPACE_DELETED
+ || *err == DB_IO_ERROR) {
buf_read_page_handle_error(bpage);
return(0);
}
diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc
index a727215b433..a0542825891 100644
--- a/storage/innobase/fil/fil0fil.cc
+++ b/storage/innobase/fil/fil0fil.cc
@@ -4794,27 +4794,30 @@ fil_node_complete_io(fil_node_t* node, const IORequest& type)
}
}
-/** Report information about an invalid page access. */
-static
-void
-fil_report_invalid_page_access(
- ulint block_offset, /*!< in: block offset */
- ulint space_id, /*!< in: space id */
- const char* space_name, /*!< in: space name */
- ulint byte_offset, /*!< in: byte offset */
- ulint len, /*!< in: I/O length */
- bool is_read) /*!< in: I/O type */
+/** Compose error message about an invalid page access.
+@param[in] block_offset block offset
+@param[in] space_id space id
+@param[in] space_name space name
+@param[in] byte_offset byte offset
+@param[in] len I/O length
+@param[in] is_read I/O type
+@return std::string with error message */
+static std::string fil_invalid_page_access_msg(size_t block_offset,
+ size_t space_id,
+ const char *space_name,
+ size_t byte_offset, size_t len,
+ bool is_read)
{
- ib::fatal()
- << "Trying to " << (is_read ? "read" : "write")
- << " page number " << block_offset << " in"
- " space " << space_id << ", space name " << space_name << ","
- " which is outside the tablespace bounds. Byte offset "
- << byte_offset << ", len " << len <<
- (space_id == 0 && !srv_was_started
- ? "Please check that the configuration matches"
- " the InnoDB system tablespace location (ibdata files)"
- : "");
+ std::stringstream ss;
+ ss << "Trying to " << (is_read ? "read" : "write") << " page number "
+ << block_offset << " in space " << space_id << ", space name "
+ << space_name << ", which is outside the tablespace bounds. Byte offset "
+ << byte_offset << ", len " << len
+ << (space_id == 0 && !srv_was_started
+ ? "Please check that the configuration matches"
+ " the InnoDB system tablespace location (ibdata files)"
+ : "");
+ return ss.str();
}
/** Reads or writes data. This operation could be asynchronous (aio).
@@ -4951,7 +4954,17 @@ fil_io(
return(DB_ERROR);
}
- fil_report_invalid_page_access(
+ if (space->purpose == FIL_TYPE_IMPORT) {
+ mutex_exit(&fil_system->mutex);
+ ib::error() << fil_invalid_page_access_msg(
+ page_id.page_no(), page_id.space(),
+ space->name, byte_offset, len,
+ req_type.is_read());
+
+ return DB_IO_ERROR;
+ }
+
+ ib::fatal() << fil_invalid_page_access_msg(
page_id.page_no(), page_id.space(),
space->name, byte_offset, len,
req_type.is_read());
@@ -5032,7 +5045,7 @@ fil_io(
return(DB_ERROR);
}
- fil_report_invalid_page_access(
+ ib::fatal() << fil_invalid_page_access_msg(
page_id.page_no(), page_id.space(),
space->name, byte_offset, len, req_type.is_read());
}
diff --git a/storage/innobase/include/btr0btr.h b/storage/innobase/include/btr0btr.h
index 29ece955702..04f2cd0f160 100644
--- a/storage/innobase/include/btr0btr.h
+++ b/storage/innobase/include/btr0btr.h
@@ -731,7 +731,7 @@ btr_validate_index(
/*************************************************************//**
Removes a page from the level list of pages. */
UNIV_INTERN
-void
+MY_ATTRIBUTE((warn_unused_result)) dberr_t
btr_level_list_remove_func(
/*=======================*/
ulint space, /*!< in: space where removed */