Skip to content

Commit 17afdb9

Browse files
committed
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/)
1 parent 8862a5b commit 17afdb9

File tree

7 files changed

+220
-10
lines changed

7 files changed

+220
-10
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
2+
SET SESSION DEBUG='+d,test_normalize_table_name_low';
3+
DROP TABLE bug11754376;
4+
SET SESSION DEBUG='-d,test_normalize_table_name_low';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Bug#11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON GRACEFUL SHUTDOWN
3+
#
4+
5+
-- source include/have_debug.inc
6+
-- source include/have_innodb.inc
7+
8+
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
9+
10+
# This will invoke test_normalize_table_name_low() in debug builds
11+
12+
SET SESSION DEBUG='+d,test_normalize_table_name_low';
13+
14+
DROP TABLE bug11754376;
15+
16+
SET SESSION DEBUG='-d,test_normalize_table_name_low';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
2+
SET SESSION DEBUG='+d,test_normalize_table_name_low';
3+
DROP TABLE bug11754376;
4+
SET SESSION DEBUG='-d,test_normalize_table_name_low';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Bug#11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON GRACEFUL SHUTDOWN
3+
#
4+
5+
-- source include/have_debug.inc
6+
-- source include/have_innodb_plugin.inc
7+
8+
CREATE TABLE bug11754376 (c INT) ENGINE=INNODB;
9+
10+
# This will invoke test_normalize_table_name_low() in debug builds
11+
12+
SET SESSION DEBUG='+d,test_normalize_table_name_low';
13+
14+
DROP TABLE bug11754376;
15+
16+
SET SESSION DEBUG='-d,test_normalize_table_name_low';

storage/innobase/handler/ha_innodb.cc

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2618,37 +2618,114 @@ normalize_table_name_low(
26182618
{
26192619
char* name_ptr;
26202620
char* db_ptr;
2621+
ulint db_len;
26212622
char* ptr;
26222623

26232624
/* Scan name from the end */
26242625

2625-
ptr = strend(name)-1;
2626+
ptr = strend(name) - 1;
26262627

2628+
/* seek to the last path separator */
26272629
while (ptr >= name && *ptr != '\\' && *ptr != '/') {
26282630
ptr--;
26292631
}
26302632

26312633
name_ptr = ptr + 1;
26322634

2633-
DBUG_ASSERT(ptr > name);
2635+
/* skip any number of path separators */
2636+
while (ptr >= name && (*ptr == '\\' || *ptr == '/')) {
2637+
ptr--;
2638+
}
26342639

2635-
ptr--;
2640+
DBUG_ASSERT(ptr >= name);
26362641

2642+
/* seek to the last but one path separator or one char before
2643+
the beginning of name */
2644+
db_len = 0;
26372645
while (ptr >= name && *ptr != '\\' && *ptr != '/') {
26382646
ptr--;
2647+
db_len++;
26392648
}
26402649

26412650
db_ptr = ptr + 1;
26422651

2643-
memcpy(norm_name, db_ptr, strlen(name) + 1 - (db_ptr - name));
2652+
memcpy(norm_name, db_ptr, db_len);
26442653

2645-
norm_name[name_ptr - db_ptr - 1] = '/';
2654+
norm_name[db_len] = '/';
2655+
2656+
memcpy(norm_name + db_len + 1, name_ptr, strlen(name_ptr) + 1);
26462657

26472658
if (set_lower_case) {
26482659
innobase_casedn_str(norm_name);
26492660
}
26502661
}
26512662

2663+
#if !defined(DBUG_OFF)
2664+
/*********************************************************************
2665+
Test normalize_table_name_low(). */
2666+
static
2667+
void
2668+
test_normalize_table_name_low()
2669+
/*===========================*/
2670+
{
2671+
char norm_name[128];
2672+
const char* test_data[][2] = {
2673+
/* input, expected result */
2674+
{"./mysqltest/t1", "mysqltest/t1"},
2675+
{"./test/#sql-842b_2", "test/#sql-842b_2"},
2676+
{"./test/#sql-85a3_10", "test/#sql-85a3_10"},
2677+
{"./test/#sql2-842b-2", "test/#sql2-842b-2"},
2678+
{"./test/bug29807", "test/bug29807"},
2679+
{"./test/foo", "test/foo"},
2680+
{"./test/innodb_bug52663", "test/innodb_bug52663"},
2681+
{"./test/t", "test/t"},
2682+
{"./test/t1", "test/t1"},
2683+
{"./test/t10", "test/t10"},
2684+
{"/a/b/db/table", "db/table"},
2685+
{"/a/b/db///////table", "db/table"},
2686+
{"/a/b////db///////table", "db/table"},
2687+
{"/var/tmp/mysqld.1/#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
2688+
{"db/table", "db/table"},
2689+
{"ddd/t", "ddd/t"},
2690+
{"d/ttt", "d/ttt"},
2691+
{"d/t", "d/t"},
2692+
{".\\mysqltest\\t1", "mysqltest/t1"},
2693+
{".\\test\\#sql-842b_2", "test/#sql-842b_2"},
2694+
{".\\test\\#sql-85a3_10", "test/#sql-85a3_10"},
2695+
{".\\test\\#sql2-842b-2", "test/#sql2-842b-2"},
2696+
{".\\test\\bug29807", "test/bug29807"},
2697+
{".\\test\\foo", "test/foo"},
2698+
{".\\test\\innodb_bug52663", "test/innodb_bug52663"},
2699+
{".\\test\\t", "test/t"},
2700+
{".\\test\\t1", "test/t1"},
2701+
{".\\test\\t10", "test/t10"},
2702+
{"C:\\a\\b\\db\\table", "db/table"},
2703+
{"C:\\a\\b\\db\\\\\\\\\\\\\\table", "db/table"},
2704+
{"C:\\a\\b\\\\\\\\db\\\\\\\\\\\\\\table", "db/table"},
2705+
{"C:\\var\\tmp\\mysqld.1\\#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
2706+
{"db\\table", "db/table"},
2707+
{"ddd\\t", "ddd/t"},
2708+
{"d\\ttt", "d/ttt"},
2709+
{"d\\t", "d/t"},
2710+
};
2711+
2712+
for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) {
2713+
printf("test_normalize_table_name_low(): "
2714+
"testing \"%s\", expected \"%s\"... ",
2715+
test_data[i][0], test_data[i][1]);
2716+
2717+
normalize_table_name_low(norm_name, test_data[i][0], FALSE);
2718+
2719+
if (strcmp(norm_name, test_data[i][1]) == 0) {
2720+
printf("ok\n");
2721+
} else {
2722+
printf("got \"%s\"\n", norm_name);
2723+
ut_error;
2724+
}
2725+
}
2726+
}
2727+
#endif /* !DBUG_OFF */
2728+
26522729
/************************************************************************
26532730
Get the upper limit of the MySQL integral and floating-point type. */
26542731
static
@@ -5990,6 +6067,11 @@ ha_innobase::delete_table(
59906067

59916068
DBUG_ENTER("ha_innobase::delete_table");
59926069

6070+
DBUG_EXECUTE_IF(
6071+
"test_normalize_table_name_low",
6072+
test_normalize_table_name_low();
6073+
);
6074+
59936075
/* Strangely, MySQL passes the table name without the '.frm'
59946076
extension, in contrast to ::create */
59956077
normalize_table_name(norm_name, name);

storage/innodb_plugin/ChangeLog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2012-02-06 The InnoDB Team
2+
3+
* handler/ha_innodb.cc:
4+
Fix Bug #11754376 45976: INNODB LOST FILES FOR TEMPORARY TABLES ON
5+
GRACEFUL SHUTDOWN
6+
17
2012-01-30 The InnoDB Team
28

39
* fil/fil0fil.c:

storage/innodb_plugin/handler/ha_innodb.cc

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,37 +3048,114 @@ normalize_table_name_low(
30483048
{
30493049
char* name_ptr;
30503050
char* db_ptr;
3051+
ulint db_len;
30513052
char* ptr;
30523053

30533054
/* Scan name from the end */
30543055

3055-
ptr = strend(name)-1;
3056+
ptr = strend(name) - 1;
30563057

3058+
/* seek to the last path separator */
30573059
while (ptr >= name && *ptr != '\\' && *ptr != '/') {
30583060
ptr--;
30593061
}
30603062

30613063
name_ptr = ptr + 1;
30623064

3063-
DBUG_ASSERT(ptr > name);
3065+
/* skip any number of path separators */
3066+
while (ptr >= name && (*ptr == '\\' || *ptr == '/')) {
3067+
ptr--;
3068+
}
30643069

3065-
ptr--;
3070+
DBUG_ASSERT(ptr >= name);
30663071

3072+
/* seek to the last but one path separator or one char before
3073+
the beginning of name */
3074+
db_len = 0;
30673075
while (ptr >= name && *ptr != '\\' && *ptr != '/') {
30683076
ptr--;
3077+
db_len++;
30693078
}
30703079

30713080
db_ptr = ptr + 1;
30723081

3073-
memcpy(norm_name, db_ptr, strlen(name) + 1 - (db_ptr - name));
3082+
memcpy(norm_name, db_ptr, db_len);
3083+
3084+
norm_name[db_len] = '/';
30743085

3075-
norm_name[name_ptr - db_ptr - 1] = '/';
3086+
memcpy(norm_name + db_len + 1, name_ptr, strlen(name_ptr) + 1);
30763087

30773088
if (set_lower_case) {
30783089
innobase_casedn_str(norm_name);
30793090
}
30803091
}
30813092

3093+
#if !defined(DBUG_OFF)
3094+
/*********************************************************************
3095+
Test normalize_table_name_low(). */
3096+
static
3097+
void
3098+
test_normalize_table_name_low()
3099+
/*===========================*/
3100+
{
3101+
char norm_name[128];
3102+
const char* test_data[][2] = {
3103+
/* input, expected result */
3104+
{"./mysqltest/t1", "mysqltest/t1"},
3105+
{"./test/#sql-842b_2", "test/#sql-842b_2"},
3106+
{"./test/#sql-85a3_10", "test/#sql-85a3_10"},
3107+
{"./test/#sql2-842b-2", "test/#sql2-842b-2"},
3108+
{"./test/bug29807", "test/bug29807"},
3109+
{"./test/foo", "test/foo"},
3110+
{"./test/innodb_bug52663", "test/innodb_bug52663"},
3111+
{"./test/t", "test/t"},
3112+
{"./test/t1", "test/t1"},
3113+
{"./test/t10", "test/t10"},
3114+
{"/a/b/db/table", "db/table"},
3115+
{"/a/b/db///////table", "db/table"},
3116+
{"/a/b////db///////table", "db/table"},
3117+
{"/var/tmp/mysqld.1/#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
3118+
{"db/table", "db/table"},
3119+
{"ddd/t", "ddd/t"},
3120+
{"d/ttt", "d/ttt"},
3121+
{"d/t", "d/t"},
3122+
{".\\mysqltest\\t1", "mysqltest/t1"},
3123+
{".\\test\\#sql-842b_2", "test/#sql-842b_2"},
3124+
{".\\test\\#sql-85a3_10", "test/#sql-85a3_10"},
3125+
{".\\test\\#sql2-842b-2", "test/#sql2-842b-2"},
3126+
{".\\test\\bug29807", "test/bug29807"},
3127+
{".\\test\\foo", "test/foo"},
3128+
{".\\test\\innodb_bug52663", "test/innodb_bug52663"},
3129+
{".\\test\\t", "test/t"},
3130+
{".\\test\\t1", "test/t1"},
3131+
{".\\test\\t10", "test/t10"},
3132+
{"C:\\a\\b\\db\\table", "db/table"},
3133+
{"C:\\a\\b\\db\\\\\\\\\\\\\\table", "db/table"},
3134+
{"C:\\a\\b\\\\\\\\db\\\\\\\\\\\\\\table", "db/table"},
3135+
{"C:\\var\\tmp\\mysqld.1\\#sql842b_2_10", "mysqld.1/#sql842b_2_10"},
3136+
{"db\\table", "db/table"},
3137+
{"ddd\\t", "ddd/t"},
3138+
{"d\\ttt", "d/ttt"},
3139+
{"d\\t", "d/t"},
3140+
};
3141+
3142+
for (size_t i = 0; i < UT_ARR_SIZE(test_data); i++) {
3143+
printf("test_normalize_table_name_low(): "
3144+
"testing \"%s\", expected \"%s\"... ",
3145+
test_data[i][0], test_data[i][1]);
3146+
3147+
normalize_table_name_low(norm_name, test_data[i][0], FALSE);
3148+
3149+
if (strcmp(norm_name, test_data[i][1]) == 0) {
3150+
printf("ok\n");
3151+
} else {
3152+
printf("got \"%s\"\n", norm_name);
3153+
ut_error;
3154+
}
3155+
}
3156+
}
3157+
#endif /* !DBUG_OFF */
3158+
30823159
/********************************************************************//**
30833160
Get the upper limit of the MySQL integral and floating-point type.
30843161
@return maximum allowed value for the field */
@@ -7047,6 +7124,11 @@ ha_innobase::delete_table(
70477124

70487125
DBUG_ENTER("ha_innobase::delete_table");
70497126

7127+
DBUG_EXECUTE_IF(
7128+
"test_normalize_table_name_low",
7129+
test_normalize_table_name_low();
7130+
);
7131+
70507132
/* Strangely, MySQL passes the table name without the '.frm'
70517133
extension, in contrast to ::create */
70527134
normalize_table_name(norm_name, name);

0 commit comments

Comments
 (0)