List:Commits« Previous MessageNext Message »
From:vasil.dimov Date:February 6 2012 11:07am
Subject:bzr push into mysql-5.1 branch (vasil.dimov:3683 to 3684) Bug#11754376
View as plain text  
 3684 Vasil Dimov	2012-02-06
      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/)

    added:
      mysql-test/suite/innodb/r/innodb_bug11754376.result
      mysql-test/suite/innodb/t/innodb_bug11754376.test
      mysql-test/suite/innodb_plugin/r/innodb_bug11754376.result
      mysql-test/suite/innodb_plugin/t/innodb_bug11754376.test
    modified:
      storage/innobase/handler/ha_innodb.cc
      storage/innodb_plugin/ChangeLog
      storage/innodb_plugin/handler/ha_innodb.cc
 3683 Ashish Agarwal	2012-02-03
      BUG#11748748 - 37280: CHECK AND REPAIR TABLE REPORT TABLE
                            CORRUPTED WHEN RUN CONCURRENTLY WITH
      
      ISSUE: Table corruption due to concurrent queries.
             Different threads running check, repair query
             along with insert. Locks not properly acquired
             in repair query. Rows are inserted inbetween
             repair query.
      
      SOLUTION: Mutex lock is acquired before the
                repair call. Concurrent queries wont
                effect the call to repair.

    modified:
      storage/archive/ha_archive.cc
=== added file 'mysql-test/suite/innodb/r/innodb_bug11754376.result'
--- a/mysql-test/suite/innodb/r/innodb_bug11754376.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/r/innodb_bug11754376.result	revid:vasil.dimov@stripped
@@ -0,0 +1,4 @@
+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';

=== added file 'mysql-test/suite/innodb/t/innodb_bug11754376.test'
--- a/mysql-test/suite/innodb/t/innodb_bug11754376.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/t/innodb_bug11754376.test	revid:vasil.dimov@stripped
@@ -0,0 +1,16 @@
+#
+# 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';

=== added file 'mysql-test/suite/innodb_plugin/r/innodb_bug11754376.result'
--- a/mysql-test/suite/innodb_plugin/r/innodb_bug11754376.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb_plugin/r/innodb_bug11754376.result	revid:vasil.dimov@stripped
@@ -0,0 +1,4 @@
+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';

=== added file 'mysql-test/suite/innodb_plugin/t/innodb_bug11754376.test'
--- a/mysql-test/suite/innodb_plugin/t/innodb_bug11754376.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb_plugin/t/innodb_bug11754376.test	revid:vasil.dimov@stripped
@@ -0,0 +1,16 @@
+#
+# 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';

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- a/storage/innobase/handler/ha_innodb.cc	revid:ashish.y.agarwal@stripped
+++ b/storage/innobase/handler/ha_innodb.cc	revid:vasil.dimov@stripped
@@ -2618,37 +2618,114 @@ normalize_table_name_low(
 {
 	char*	name_ptr;
 	char*	db_ptr;
+	ulint	db_len;
 	char*	ptr;
 
 	/* Scan name from the end */
 
-	ptr = strend(name)-1;
+	ptr = strend(name) - 1;
 
+	/* seek to the last path separator */
 	while (ptr >= name && *ptr != '\\' && *ptr != '/') {
 		ptr--;
 	}
 
 	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 != '/') {
 		ptr--;
+		db_len++;
 	}
 
 	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) {
 		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. */
 static
@@ -5990,6 +6067,11 @@ 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'
 	extension, in contrast to ::create */
 	normalize_table_name(norm_name, name);

=== modified file 'storage/innodb_plugin/ChangeLog'
--- a/storage/innodb_plugin/ChangeLog	revid:ashish.y.agarwal@stripped
+++ b/storage/innodb_plugin/ChangeLog	revid:vasil.dimov@stripped
@@ -1,3 +1,9 @@
+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
 
 	* fil/fil0fil.c:

=== modified file 'storage/innodb_plugin/handler/ha_innodb.cc'
--- a/storage/innodb_plugin/handler/ha_innodb.cc	revid:ashish.y.agarwal@stripped
+++ b/storage/innodb_plugin/handler/ha_innodb.cc	revid:vasil.dimov@stripped
@@ -3048,37 +3048,114 @@ normalize_table_name_low(
 {
 	char*	name_ptr;
 	char*	db_ptr;
+	ulint	db_len;
 	char*	ptr;
 
 	/* Scan name from the end */
 
-	ptr = strend(name)-1;
+	ptr = strend(name) - 1;
 
+	/* seek to the last path separator */
 	while (ptr >= name && *ptr != '\\' && *ptr != '/') {
 		ptr--;
 	}
 
 	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 != '/') {
 		ptr--;
+		db_len++;
 	}
 
 	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) {
 		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.
 @return maximum allowed value for the field */
@@ -7047,6 +7124,11 @@ 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'
 	extension, in contrast to ::create */
 	normalize_table_name(norm_name, name);

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-5.1 branch (vasil.dimov:3683 to 3684) Bug#11754376vasil.dimov6 Feb