Commit 1c4fd3bb authored by Vasil Dimov's avatar Vasil Dimov

Fix Bug#11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON

GRACEFUL SHUTDOWN

During startup mysql picks up .frm files from the tmpdir directory and
tries to drop those tables in the storage engine.

The problem is that when tmpdir ends in / then ha_innobase::delete_table()
is passed a string like "/var/tmp//#sql123", then it wrongly normalizes it
to "/#sql123" and calls row_drop_table_for_mysql() which of course fails
to delete the table entry from the InnoDB dictionary cache.
ha_innobase::delete_table() returns an error but nevertheless mysql wipes
away the .frm file and the entry in the InnoDB dictionary cache remains
orphaned with no easy way to remove it.

The "no easy" way to remove it is to create a similar temporary table again,
copy its .frm file to tmpdir under "#sql123.frm" and restart mysqld with
tmpdir=/var/tmp (no trailing slash) - this way mysql will pick the .frm file
after restart and will try to issue drop table for "/var/tmp/#sql123"
(notice do double slash), ha_innobase::delete_table() will normalize it to
"tmp/#sql123" and row_drop_table_for_mysql() will successfully remove the
table entry from the dictionary cache.

The solution is to fix normalize_table_name_low() to normalize things like
"/var/tmp//table" correctly to "tmp/table".

This patch also adds a test function which invokes
normalize_table_name_low() with various inputs to make sure it works
correctly and a mtr test that calls this test function.

Reviewed by:	Marko (http://bur03.no.oracle.com/rb/r/929/)
parent cfcb5dab
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
SET SESSION DEBUG='+d,test_normalize_table_name_low';
DROP TABLE bug11754376;
SET SESSION DEBUG='-d,test_normalize_table_name_low';
#
# Bug#11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON GRACEFUL SHUTDOWN
#
-- source include/have_debug.inc
-- source include/have_innodb.inc
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
# This will invoke test_normalize_table_name_low() in debug builds
SET SESSION DEBUG='+d,test_normalize_table_name_low';
DROP TABLE bug11754376;
SET SESSION DEBUG='-d,test_normalize_table_name_low';
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
SET SESSION DEBUG='+d,test_normalize_table_name_low';
DROP TABLE bug11754376;
SET SESSION DEBUG='-d,test_normalize_table_name_low';
#
# Bug#11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON GRACEFUL SHUTDOWN
#
-- source include/have_debug.inc
-- source include/have_innodb_plugin.inc
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
# This will invoke test_normalize_table_name_low() in debug builds
SET SESSION DEBUG='+d,test_normalize_table_name_low';
DROP TABLE bug11754376;
SET SESSION DEBUG='-d,test_normalize_table_name_low';
...@@ -2618,37 +2618,114 @@ normalize_table_name_low( ...@@ -2618,37 +2618,114 @@ normalize_table_name_low(
{ {
char* name_ptr; char* name_ptr;
char* db_ptr; char* db_ptr;
ulint db_len;
char* ptr; char* ptr;
/* Scan name from the end */ /* Scan name from the end */
ptr = strend(name)-1; ptr = strend(name) - 1;
/* seek to the last path separator */
while (ptr >= name && *ptr != '\\' && *ptr != '/') { while (ptr >= name && *ptr != '\\' && *ptr != '/') {
ptr--; ptr--;
} }
name_ptr = ptr + 1; name_ptr = ptr + 1;
DBUG_ASSERT(ptr > name); /* skip any number of path separators */
while (ptr >= name && (*ptr == '\\' || *ptr == '/')) {
ptr--;
}
ptr--; DBUG_ASSERT(ptr >= name);
/* seek to the last but one path separator or one char before
the beginning of name */
db_len = 0;
while (ptr >= name && *ptr != '\\' && *ptr != '/') { while (ptr >= name && *ptr != '\\' && *ptr != '/') {
ptr--; ptr--;
db_len++;
} }
db_ptr = ptr + 1; db_ptr = ptr + 1;
memcpy(norm_name, db_ptr, strlen(name) + 1 - (db_ptr - name)); memcpy(norm_name, db_ptr, db_len);
norm_name[name_ptr - db_ptr - 1] = '/'; norm_name[db_len] = '/';
memcpy(norm_name + db_len + 1, name_ptr, strlen(name_ptr) + 1);
if (set_lower_case) { if (set_lower_case) {
innobase_casedn_str(norm_name); innobase_casedn_str(norm_name);
} }
} }
#if !defined(DBUG_OFF)
/*********************************************************************
Test normalize_table_name_low(). */
static
void
test_normalize_table_name_low()
/*===========================*/
{
char norm_name[128];
const char* test_data[][2] = {
/* input, expected result */
{"./mysqltest/t1", "mysqltest/t1"},
{"./test/#sql-842b_2", "test/#sql-842b_2"},
{"./test/#sql-85a3_10", "test/#sql-85a3_10"},
{"./test/#sql2-842b-2", "test/#sql2-842b-2"},
{"./test/bug29807", "test/bug29807"},
{"./test/foo", "test/foo"},
{"./test/innodb_bug52663", "test/innodb_bug52663"},
{"./test/t", "test/t"},
{"./test/t1", "test/t1"},
{"./test/t10", "test/t10"},
{"/a/b/db/table", "db/table"},
{"/a/b/db///////table", "db/table"},
{"/a/b////db///////table", "db/table"},
{"/var/tmp/mysqld.1/#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
{"db/table", "db/table"},
{"ddd/t", "ddd/t"},
{"d/ttt", "d/ttt"},
{"d/t", "d/t"},
{".\\mysqltest\\t1", "mysqltest/t1"},
{".\\test\\#sql-842b_2", "test/#sql-842b_2"},
{".\\test\\#sql-85a3_10", "test/#sql-85a3_10"},
{".\\test\\#sql2-842b-2", "test/#sql2-842b-2"},
{".\\test\\bug29807", "test/bug29807"},
{".\\test\\foo", "test/foo"},
{".\\test\\innodb_bug52663", "test/innodb_bug52663"},
{".\\test\\t", "test/t"},
{".\\test\\t1", "test/t1"},
{".\\test\\t10", "test/t10"},
{"C:\\a\\b\\db\\table", "db/table"},
{"C:\\a\\b\\db\\\\\\\\\\\\\\table", "db/table"},
{"C:\\a\\b\\\\\\\\db\\\\\\\\\\\\\\table", "db/table"},
{"C:\\var\\tmp\\mysqld.1\\#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
{"db\\table", "db/table"},
{"ddd\\t", "ddd/t"},
{"d\\ttt", "d/ttt"},
{"d\\t", "d/t"},
};
for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) {
printf("test_normalize_table_name_low(): "
"testing \"%s\", expected \"%s\"... ",
test_data[i][0], test_data[i][1]);
normalize_table_name_low(norm_name, test_data[i][0], FALSE);
if (strcmp(norm_name, test_data[i][1]) == 0) {
printf("ok\n");
} else {
printf("got \"%s\"\n", norm_name);
ut_error;
}
}
}
#endif /* !DBUG_OFF */
/************************************************************************ /************************************************************************
Get the upper limit of the MySQL integral and floating-point type. */ Get the upper limit of the MySQL integral and floating-point type. */
static static
...@@ -5990,6 +6067,11 @@ ha_innobase::delete_table( ...@@ -5990,6 +6067,11 @@ ha_innobase::delete_table(
DBUG_ENTER("ha_innobase::delete_table"); DBUG_ENTER("ha_innobase::delete_table");
DBUG_EXECUTE_IF(
"test_normalize_table_name_low",
test_normalize_table_name_low();
);
/* Strangely, MySQL passes the table name without the '.frm' /* Strangely, MySQL passes the table name without the '.frm'
extension, in contrast to ::create */ extension, in contrast to ::create */
normalize_table_name(norm_name, name); normalize_table_name(norm_name, name);
......
2012-02-06 The InnoDB Team
* handler/ha_innodb.cc:
Fix Bug #11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON
GRACEFUL SHUTDOWN
2012-01-30 The InnoDB Team 2012-01-30 The InnoDB Team
* fil/fil0fil.c: * fil/fil0fil.c:
......
...@@ -3048,37 +3048,114 @@ normalize_table_name_low( ...@@ -3048,37 +3048,114 @@ normalize_table_name_low(
{ {
char* name_ptr; char* name_ptr;
char* db_ptr; char* db_ptr;
ulint db_len;
char* ptr; char* ptr;
/* Scan name from the end */ /* Scan name from the end */
ptr = strend(name)-1; ptr = strend(name) - 1;
/* seek to the last path separator */
while (ptr >= name && *ptr != '\\' && *ptr != '/') { while (ptr >= name && *ptr != '\\' && *ptr != '/') {
ptr--; ptr--;
} }
name_ptr = ptr + 1; name_ptr = ptr + 1;
DBUG_ASSERT(ptr > name); /* skip any number of path separators */
while (ptr >= name && (*ptr == '\\' || *ptr == '/')) {
ptr--;
}
ptr--; DBUG_ASSERT(ptr >= name);
/* seek to the last but one path separator or one char before
the beginning of name */
db_len = 0;
while (ptr >= name && *ptr != '\\' && *ptr != '/') { while (ptr >= name && *ptr != '\\' && *ptr != '/') {
ptr--; ptr--;
db_len++;
} }
db_ptr = ptr + 1; db_ptr = ptr + 1;
memcpy(norm_name, db_ptr, strlen(name) + 1 - (db_ptr - name)); memcpy(norm_name, db_ptr, db_len);
norm_name[db_len] = '/';
norm_name[name_ptr - db_ptr - 1] = '/'; memcpy(norm_name + db_len + 1, name_ptr, strlen(name_ptr) + 1);
if (set_lower_case) { if (set_lower_case) {
innobase_casedn_str(norm_name); innobase_casedn_str(norm_name);
} }
} }
#if !defined(DBUG_OFF)
/*********************************************************************
Test normalize_table_name_low(). */
static
void
test_normalize_table_name_low()
/*===========================*/
{
char norm_name[128];
const char* test_data[][2] = {
/* input, expected result */
{"./mysqltest/t1", "mysqltest/t1"},
{"./test/#sql-842b_2", "test/#sql-842b_2"},
{"./test/#sql-85a3_10", "test/#sql-85a3_10"},
{"./test/#sql2-842b-2", "test/#sql2-842b-2"},
{"./test/bug29807", "test/bug29807"},
{"./test/foo", "test/foo"},
{"./test/innodb_bug52663", "test/innodb_bug52663"},
{"./test/t", "test/t"},
{"./test/t1", "test/t1"},
{"./test/t10", "test/t10"},
{"/a/b/db/table", "db/table"},
{"/a/b/db///////table", "db/table"},
{"/a/b////db///////table", "db/table"},
{"/var/tmp/mysqld.1/#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
{"db/table", "db/table"},
{"ddd/t", "ddd/t"},
{"d/ttt", "d/ttt"},
{"d/t", "d/t"},
{".\\mysqltest\\t1", "mysqltest/t1"},
{".\\test\\#sql-842b_2", "test/#sql-842b_2"},
{".\\test\\#sql-85a3_10", "test/#sql-85a3_10"},
{".\\test\\#sql2-842b-2", "test/#sql2-842b-2"},
{".\\test\\bug29807", "test/bug29807"},
{".\\test\\foo", "test/foo"},
{".\\test\\innodb_bug52663", "test/innodb_bug52663"},
{".\\test\\t", "test/t"},
{".\\test\\t1", "test/t1"},
{".\\test\\t10", "test/t10"},
{"C:\\a\\b\\db\\table", "db/table"},
{"C:\\a\\b\\db\\\\\\\\\\\\\\table", "db/table"},
{"C:\\a\\b\\\\\\\\db\\\\\\\\\\\\\\table", "db/table"},
{"C:\\var\\tmp\\mysqld.1\\#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
{"db\\table", "db/table"},
{"ddd\\t", "ddd/t"},
{"d\\ttt", "d/ttt"},
{"d\\t", "d/t"},
};
for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) {
printf("test_normalize_table_name_low(): "
"testing \"%s\", expected \"%s\"... ",
test_data[i][0], test_data[i][1]);
normalize_table_name_low(norm_name, test_data[i][0], FALSE);
if (strcmp(norm_name, test_data[i][1]) == 0) {
printf("ok\n");
} else {
printf("got \"%s\"\n", norm_name);
ut_error;
}
}
}
#endif /* !DBUG_OFF */
/********************************************************************//** /********************************************************************//**
Get the upper limit of the MySQL integral and floating-point type. Get the upper limit of the MySQL integral and floating-point type.
@return maximum allowed value for the field */ @return maximum allowed value for the field */
...@@ -7047,6 +7124,11 @@ ha_innobase::delete_table( ...@@ -7047,6 +7124,11 @@ ha_innobase::delete_table(
DBUG_ENTER("ha_innobase::delete_table"); DBUG_ENTER("ha_innobase::delete_table");
DBUG_EXECUTE_IF(
"test_normalize_table_name_low",
test_normalize_table_name_low();
);
/* Strangely, MySQL passes the table name without the '.frm' /* Strangely, MySQL passes the table name without the '.frm'
extension, in contrast to ::create */ extension, in contrast to ::create */
normalize_table_name(norm_name, name); normalize_table_name(norm_name, name);
......
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