#At file:///home/acorreia/workspace.sun/repository.mysql/bzrwork/bug-42861/mysql-5.1-bugteam/ based on revid:joro@stripped
2832 Alfranio Correia 2009-03-18
Bug #42861 Assigning invalid directories to --slave-load-tmpdir crashes the slave
Compiling with debug and assigning an invalid directory to --slave-load-tmpdir
was crashing the slave due to the following assertion DBUG_ASSERT(! is_set() ||
can_overwrite_status). This assertion assumes that a thread can change its
state once (i.e. ok,error, etc) before aborting, cleaning/resuming or completing
its execution unless the overwrite flag (i.e. can_overwrite_status) is true.
The Append_block_log_event::do_apply_event which is responsible for creating
temporary file(s) was not cleaning the thread state. Thus a failure while
trying to create a file in an invalid temporary directory was causing the crash.
To fix the problem we check if the temporary directory is valid before starting
the SQL Thread and reset the thread state before creating a file in
Append_block_log_event::do_apply_event.
added:
mysql-test/suite/rpl/r/rpl_slave_load_remove_tmpfile.result
mysql-test/suite/rpl/r/rpl_slave_load_tmpdir_not_exist.result
mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile-slave.opt
mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test
mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist-slave.opt
mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist.test
modified:
mysql-test/suite/rpl/r/rpl_slave_load_in.result
mysql-test/suite/rpl/t/rpl_slave_load_in.test
sql/log_event.cc
sql/slave.cc
=== modified file 'mysql-test/suite/rpl/r/rpl_slave_load_in.result'
--- a/mysql-test/suite/rpl/r/rpl_slave_load_in.result 2009-02-21 09:36:07 +0000
+++ b/mysql-test/suite/rpl/r/rpl_slave_load_in.result 2009-03-18 10:31:17 +0000
@@ -5,6 +5,15 @@ reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave;
create table t1(a int not null auto_increment, b int, primary key(a));
+create table t2(a int not null auto_increment, b int, primary key(a)) engine=innodb;
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
+start transaction;
+insert into t2(b) values (1);
+insert into t2(b) values (2);
+load data infile '../../std_data/rpl_loaddata.dat' into table t2;
+load data infile '../../std_data/rpl_loaddata.dat' into table t2;
+commit;
Comparing tables master:test.t1 and slave:test.t1
+Comparing tables master:test.t2 and slave:test.t2
drop table t1;
+drop table t2;
=== added file 'mysql-test/suite/rpl/r/rpl_slave_load_remove_tmpfile.result'
--- a/mysql-test/suite/rpl/r/rpl_slave_load_remove_tmpfile.result 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/r/rpl_slave_load_remove_tmpfile.result 2009-03-18 10:31:17 +0000
@@ -0,0 +1,15 @@
+stop slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+reset master;
+reset slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+start slave;
+create table t1(a int not null auto_increment, b int, primary key(a)) engine=innodb;
+start transaction;
+insert into t1(b) values (1);
+insert into t1(b) values (2);
+load data infile '../../std_data/rpl_loaddata.dat' into table t1;
+commit;
+Error in Begin_load_query event: write to '../../tmp/SQL_LOAD-2-1-1.data' failed
+drop table t1;
+drop table t1;
=== added file 'mysql-test/suite/rpl/r/rpl_slave_load_tmpdir_not_exist.result'
--- a/mysql-test/suite/rpl/r/rpl_slave_load_tmpdir_not_exist.result 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/r/rpl_slave_load_tmpdir_not_exist.result 2009-03-18 10:31:17 +0000
@@ -0,0 +1,2 @@
+start slave;
+Unable to use slave's temporary directory ../../../error - Can't read dir of '../../../error' (Errcode: 2)
=== modified file 'mysql-test/suite/rpl/t/rpl_slave_load_in.test'
--- a/mysql-test/suite/rpl/t/rpl_slave_load_in.test 2009-02-21 09:36:07 +0000
+++ b/mysql-test/suite/rpl/t/rpl_slave_load_in.test 2009-03-18 10:31:17 +0000
@@ -3,10 +3,11 @@
# event while the "--secure-file-priv" option is set.
#
# The test is divided in two steps:
-# 1 - Creates a table and populates it through "LOAD DATA INFILE".
+# 1 - Creates tables and populates them through "LOAD DATA INFILE".
# 2 - Compares the master and slave.
##########################################################################
-source include/master-slave.inc;
+--source include/have_innodb.inc
+--source include/master-slave.inc
##########################################################################
# Loading data
@@ -14,8 +15,17 @@ source include/master-slave.inc;
connection master;
create table t1(a int not null auto_increment, b int, primary key(a));
+create table t2(a int not null auto_increment, b int, primary key(a)) engine=innodb;
+
load data infile '../../std_data/rpl_loaddata.dat' into table t1;
+start transaction;
+ insert into t2(b) values (1);
+ insert into t2(b) values (2);
+ load data infile '../../std_data/rpl_loaddata.dat' into table t2;
+ load data infile '../../std_data/rpl_loaddata.dat' into table t2;
+commit;
+
##########################################################################
# Checking Consistency
##########################################################################
@@ -25,11 +35,16 @@ let $diff_table_1=master:test.t1;
let $diff_table_2=slave:test.t1;
source include/diff_tables.inc;
+let $diff_table_1=master:test.t2;
+let $diff_table_2=slave:test.t2;
+source include/diff_tables.inc;
+
##########################################################################
# Clean up
##########################################################################
connection master;
drop table t1;
+drop table t2;
sync_slave_with_master;
=== added file 'mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile-slave.opt'
--- a/mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile-slave.opt 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile-slave.opt 2009-03-18 10:31:17 +0000
@@ -0,0 +1 @@
+--loose-debug=d,remove_slave_load_file_before_write
=== added file 'mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test'
--- a/mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test 2009-03-18 10:31:17 +0000
@@ -0,0 +1,46 @@
+##########################################################################
+# This test verifies if the slave fails gracefully when the temporary
+# file used to load data is removed while it is about to be used it.
+# Similar errors are caught if the temporary directory is removed.
+#
+# Steps:
+# 1 - Creates a table and populates it through "LOAD DATA INFILE".
+# 2 - Catches error.
+##########################################################################
+--source include/have_binlog_format_mixed_or_statement.inc
+--source include/have_innodb.inc
+--source include/have_debug.inc
+--source include/master-slave.inc
+
+##########################################################################
+# Loading data
+##########################################################################
+connection master;
+
+create table t1(a int not null auto_increment, b int, primary key(a)) engine=innodb;
+
+start transaction;
+ insert into t1(b) values (1);
+ insert into t1(b) values (2);
+ load data infile '../../std_data/rpl_loaddata.dat' into table t1;
+commit;
+
+##########################################################################
+# Catch Error
+##########################################################################
+connection slave;
+source include/wait_for_slave_sql_to_stop.inc;
+
+let $error=query_get_value("show slave status", Last_SQL_Error, 1);
+echo $error;
+
+##########################################################################
+# Clean up
+##########################################################################
+connection master;
+
+drop table t1;
+
+connection slave;
+
+drop table t1;
=== added file 'mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist-slave.opt'
--- a/mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist-slave.opt 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist-slave.opt 2009-03-18 10:31:17 +0000
@@ -0,0 +1 @@
+--slave-load-tmpdir=../../../error
=== added file 'mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist.test'
--- a/mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist.test 1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/t/rpl_slave_load_tmpdir_not_exist.test 2009-03-18 10:31:17 +0000
@@ -0,0 +1,15 @@
+##########################################################################
+# This test verifies if the start slave fails gracefuly when an
+# invalid directory is used to set --slave-load-tmpdir.
+##########################################################################
+connect (master,127.0.0.1,root,,test,$MASTER_MYPORT,);
+connect (master1,127.0.0.1,root,,test,$MASTER_MYPORT,);
+connect (slave,127.0.0.1,root,,test,$SLAVE_MYPORT,);
+connect (slave1,127.0.0.1,root,,test,$SLAVE_MYPORT,);
+
+connection slave;
+start slave;
+
+source include/wait_for_slave_sql_to_stop.inc;
+let $error=query_get_value("show slave status", Last_SQL_Error, 1);
+echo $error;
=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc 2009-03-05 10:23:46 +0000
+++ b/sql/log_event.cc 2009-03-18 10:31:17 +0000
@@ -382,7 +382,7 @@ static void cleanup_load_tmpdir()
uint i;
char fname[FN_REFLEN], prefbuf[31], *p;
- if (!(dirp=my_dir(slave_load_tmpdir,MYF(MY_WME))))
+ if (!(dirp=my_dir(slave_load_tmpdir,MYF(0))))
return;
/*
@@ -6178,6 +6178,12 @@ int Append_block_log_event::do_apply_eve
thd_proc_info(thd, proc_info);
if (get_create_or_append())
{
+ /*
+ Usually lex_start() is called by mysql_parse(), but we need it here
+ as the present method does not call mysql_parse().
+ */
+ lex_start(thd);
+ mysql_reset_thd_for_next_command(thd);
my_delete(fname, MYF(0)); // old copy may exist already
if ((fd= my_create(fname, CREATE_MODE,
O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
@@ -6197,6 +6203,10 @@ int Append_block_log_event::do_apply_eve
get_type_str(), fname);
goto err;
}
+
+ DBUG_EXECUTE_IF("remove_slave_load_file_before_write",
+ my_close(fd,MYF(0)); fd= -1; my_delete(fname, MYF(0)););
+
if (my_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP)))
{
rli->report(ERROR_LEVEL, my_errno,
=== modified file 'sql/slave.cc'
--- a/sql/slave.cc 2009-01-09 12:49:24 +0000
+++ b/sql/slave.cc 2009-03-18 10:31:17 +0000
@@ -2632,6 +2632,41 @@ err:
DBUG_RETURN(0); // Can't return anything here
}
+/*
+ Check the temporary directory used by commands like
+ LOAD DATA INFILE.
+ */
+static
+int check_temp_dir(char* tmp_dir, char *tmp_file)
+{
+ int fd;
+ MY_DIR *dirp;
+
+ DBUG_ENTER("check_temp_dir");
+
+ /*
+ Check if the directory exists.
+ */
+ if (!(dirp=my_dir(tmp_dir,MYF(MY_WME))))
+ DBUG_RETURN(1);
+ my_dirend(dirp);
+
+ /*
+ Check permissions to create a file.
+ */
+ if ((fd= my_create(tmp_file, CREATE_MODE,
+ O_WRONLY | O_BINARY | O_EXCL | O_NOFOLLOW,
+ MYF(MY_WME))) < 0)
+ DBUG_RETURN(1);
+
+ /*
+ Clean up.
+ */
+ my_close(fd, MYF(0));
+ my_delete(tmp_file, MYF(0));
+
+ DBUG_RETURN(0);
+}
/**
Slave SQL thread entry point.
@@ -2763,6 +2798,14 @@ log '%s' at position %s, relay log '%s'
llstr(rli->group_master_log_pos,llbuff),rli->group_relay_log_name,
llstr(rli->group_relay_log_pos,llbuff1));
+ if (check_temp_dir(slave_load_tmpdir, rli->slave_patternload_file))
+ {
+ rli->report(ERROR_LEVEL, thd->main_da.sql_errno(),
+ "Unable to use slave's temporary directory %s - %s",
+ slave_load_tmpdir, thd->main_da.message());
+ goto err;
+ }
+
/* execute init_slave variable */
if (sys_init_slave.value_length)
{