summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/log_event.cc32
-rw-r--r--sql/slave.cc18
-rw-r--r--sql/slave.h1
-rw-r--r--sql/sql_class.h24
-rw-r--r--sql/sql_db.cc159
5 files changed, 112 insertions, 122 deletions
diff --git a/sql/log_event.cc b/sql/log_event.cc
index b4707826205..cf5dbb1e77c 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -1635,14 +1635,33 @@ void Query_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
*/
#if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT)
+
+static const char *rewrite_db(const char *db)
+{
+ if (replicate_rewrite_db.is_empty() || db == NULL)
+ return db;
+ I_List_iterator<i_string_pair> it(replicate_rewrite_db);
+ i_string_pair* tmp;
+
+ while ((tmp=it++))
+ {
+ if (strcmp(tmp->key, db) == 0)
+ return tmp->val;
+ }
+ return db;
+}
+
+
int Query_log_event::exec_event(struct st_relay_log_info* rli)
{
return exec_event(rli, query, q_len);
}
-int Query_log_event::exec_event(struct st_relay_log_info* rli, const char *query_arg, uint32 q_len_arg)
+int Query_log_event::exec_event(struct st_relay_log_info* rli,
+ const char *query_arg, uint32 q_len_arg)
{
+ const char *new_db= rewrite_db(db);
int expected_error,actual_error= 0;
/*
Colleagues: please never free(thd->catalog) in MySQL. This would lead to
@@ -1651,8 +1670,7 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli, const char *query
Thank you.
*/
thd->catalog= catalog_len ? (char *) catalog : (char *)"";
- thd->db_length= db_len;
- thd->db= (char*) rewrite_db(db, &thd->db_length);
+ thd->set_db(new_db, strlen(new_db)); /* allocates a copy of 'db' */
thd->variables.auto_increment_increment= auto_increment_increment;
thd->variables.auto_increment_offset= auto_increment_offset;
@@ -1869,7 +1887,7 @@ end:
TABLE uses the db.table syntax.
*/
thd->catalog= 0;
- thd->reset_db(NULL, 0); // prevent db from being freed
+ thd->set_db(NULL, 0); /* will free the current database */
thd->query= 0; // just to be sure
thd->query_length= 0;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
@@ -2817,8 +2835,8 @@ void Load_log_event::set_fields(const char* affected_db,
int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli,
bool use_rli_only_for_errors)
{
- thd->db_length= db_len;
- thd->db= (char*) rewrite_db(db, &thd->db_length);
+ const char *new_db= rewrite_db(db);
+ thd->set_db(new_db, strlen(new_db));
DBUG_ASSERT(thd->query == 0);
thd->query_length= 0; // Should not be needed
thd->query_error= 0;
@@ -3018,7 +3036,7 @@ error:
const char *remember_db= thd->db;
VOID(pthread_mutex_lock(&LOCK_thread_count));
thd->catalog= 0;
- thd->reset_db(NULL, 0);
+ thd->set_db(NULL, 0); /* will free the current database */
thd->query= 0;
thd->query_length= 0;
VOID(pthread_mutex_unlock(&LOCK_thread_count));
diff --git a/sql/slave.cc b/sql/slave.cc
index 4da447c4bc3..b284f4a6a16 100644
--- a/sql/slave.cc
+++ b/sql/slave.cc
@@ -1177,24 +1177,6 @@ bool net_request_file(NET* net, const char* fname)
}
-const char *rewrite_db(const char* db, uint *new_len)
-{
- if (replicate_rewrite_db.is_empty() || !db)
- return db;
- I_List_iterator<i_string_pair> it(replicate_rewrite_db);
- i_string_pair* tmp;
-
- while ((tmp=it++))
- {
- if (!strcmp(tmp->key, db))
- {
- *new_len= (uint32)strlen(tmp->val);
- return tmp->val;
- }
- }
- return db;
-}
-
/*
From other comments and tests in code, it looks like
sometimes Query_log_event and Load_log_event can have db == 0
diff --git a/sql/slave.h b/sql/slave.h
index 7f08105c0b9..c355f7172a9 100644
--- a/sql/slave.h
+++ b/sql/slave.h
@@ -550,7 +550,6 @@ int add_table_rule(HASH* h, const char* table_spec);
int add_wild_table_rule(DYNAMIC_ARRAY* a, const char* table_spec);
void init_table_rule_hash(HASH* h, bool* h_inited);
void init_table_rule_array(DYNAMIC_ARRAY* a, bool* a_inited);
-const char *rewrite_db(const char* db, uint *new_db_len);
const char *print_slave_db_safe(const char *db);
int check_expected_error(THD* thd, RELAY_LOG_INFO* rli, int error_code);
void skip_load_data_infile(NET* net);
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 1ba104df2a4..eb075dd54bb 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1574,21 +1574,23 @@ public:
/*
Initialize the current database from a NULL-terminated string with length
+ If we run out of memory, we free the current database and return TRUE.
+ This way the user will notice the error as there will be no current
+ database selected (in addition to the error message set by malloc).
*/
- void set_db(const char *new_db, uint new_db_len)
+ bool set_db(const char *new_db, uint new_db_len)
{
- if (new_db)
+ /* Do not reallocate memory if current chunk is big enough. */
+ if (db && new_db && db_length >= new_db_len)
+ memcpy(db, new_db, new_db_len+1);
+ else
{
- /* Do not reallocate memory if current chunk is big enough. */
- if (db && db_length >= new_db_len)
- memcpy(db, new_db, new_db_len+1);
- else
- {
- safeFree(db);
- db= my_strdup_with_length(new_db, new_db_len, MYF(MY_WME));
- }
- db_length= db ? new_db_len: 0;
+ x_free(db);
+ db= new_db ? my_strdup_with_length(new_db, new_db_len, MYF(MY_WME)) :
+ NULL;
}
+ db_length= db ? new_db_len : 0;
+ return new_db && !db;
}
void reset_db(char *new_db, uint new_db_len)
{
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
index 44947384b32..902539dfdec 100644
--- a/sql/sql_db.cc
+++ b/sql/sql_db.cc
@@ -781,32 +781,13 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
exit:
(void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */
/*
- If this database was the client's selected database, we silently change the
- client's selected database to nothing (to have an empty SELECT DATABASE()
- in the future). For this we free() thd->db and set it to 0. But we don't do
- free() for the slave thread. Indeed, doing a x_free() on it leads to nasty
- problems (i.e. long painful debugging) because in this thread, thd->db is
- the same as data_buf and db of the Query_log_event which is dropping the
- database. So if you free() thd->db, you're freeing data_buf. You set
- thd->db to 0 but not data_buf (thd->db and data_buf are two distinct
- pointers which point to the same place). Then in ~Query_log_event(), we
- have 'if (data_buf) free(data_buf)' data_buf is !=0 so this makes a
- DOUBLE free().
- Side effects of this double free() are, randomly (depends on the machine),
- when the slave is replicating a DROP DATABASE:
- - garbage characters in the error message:
- "Error 'Can't drop database 'test2'; database doesn't exist' on query
- 'h4zI©'"
- - segfault
- - hang in "free(vio)" (yes!) in the I/O or SQL slave threads (so slave
- server hangs at shutdown etc).
+ If this database was the client's selected database, we silently
+ change the client's selected database to nothing (to have an empty
+ SELECT DATABASE() in the future). For this we free() thd->db and set
+ it to 0.
*/
if (thd->db && !strcmp(thd->db, db))
- {
- if (!(thd->slave_thread)) /* a slave thread will free it itself */
- x_free(thd->db);
- thd->reset_db(NULL, 0);
- }
+ thd->set_db(NULL, 0);
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
start_waiting_global_read_lock(thd);
exit2:
@@ -1099,38 +1080,52 @@ err:
/*
- Change default database.
+ Change the current database.
SYNOPSIS
mysql_change_db()
- thd Thread handler
- name Databasename
- no_access_check True don't do access check. In this case name may be ""
+ thd thread handle
+ name database name
+ no_access_check if TRUE, don't do access check. In this
+ case name may be ""
DESCRIPTION
- Becasue the database name may have been given directly from the
- communication packet (in case of 'connect' or 'COM_INIT_DB')
- we have to do end space removal in this function.
+ Check that the database name corresponds to a valid and
+ existent database, check access rights (unless called with
+ no_access_check), and set the current database. This function
+ is called to change the current database upon user request
+ (COM_CHANGE_DB command) or temporarily, to execute a stored
+ routine.
NOTES
- Do as little as possible in this function, as it is not called for the
- replication slave SQL thread (for that thread, setting of thd->db is done
- in ::exec_event() methods of log_event.cc).
-
- This function does not send anything, including error messages to the
- client, if that should be sent to the client, call net_send_error after
- this function.
+ This function is not the only way to switch the database that
+ is currently employed. When the replication slave thread
+ switches the database before executing a query, it calls
+ thd->set_db directly. However, if the query, in turn, uses
+ a stored routine, the stored routine will use this function,
+ even if it's run on the slave.
+
+ This function allocates the name of the database on the system
+ heap: this is necessary to be able to uniformly change the
+ database from any module of the server. Up to 5.0 different
+ modules were using different memory to store the name of the
+ database, and this led to memory corruption: a stack pointer
+ set by Stored Procedures was used by replication after the
+ stack address was long gone.
+
+ This function does not send anything, including error
+ messages, to the client. If that should be sent to the client,
+ call net_send_error after this function.
RETURN VALUES
- 0 ok
+ 0 OK
1 error
*/
bool mysql_change_db(THD *thd, const char *name, bool no_access_check)
{
- int length, db_length;
- char *dbname= thd->slave_thread ? (char *) name :
- my_strdup((char *) name, MYF(MY_WME));
+ int path_length, db_length;
+ char *db_name;
char path[FN_REFLEN];
HA_CREATE_INFO create;
bool system_db= 0;
@@ -1142,32 +1137,35 @@ bool mysql_change_db(THD *thd, const char *name, bool no_access_check)
DBUG_ENTER("mysql_change_db");
DBUG_PRINT("enter",("name: '%s'",name));
- LINT_INIT(db_length);
-
- /* dbname can only be NULL if malloc failed */
- if (!dbname || !(db_length= strlen(dbname)))
+ if (name == NULL || name[0] == '\0' && no_access_check == FALSE)
{
- if (no_access_check && dbname)
- {
- /* Called from SP when orignal database was not set */
- system_db= 1;
- goto end;
- }
- if (!(thd->slave_thread))
- x_free(dbname); /* purecov: inspected */
- my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR),
- MYF(0)); /* purecov: inspected */
+ my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
DBUG_RETURN(1); /* purecov: inspected */
}
- if (check_db_name(dbname))
+ else if (name[0] == '\0')
{
- my_error(ER_WRONG_DB_NAME, MYF(0), dbname);
- if (!(thd->slave_thread))
- my_free(dbname, MYF(0));
+ /* Called from SP to restore the original database, which was NULL */
+ DBUG_ASSERT(no_access_check);
+ system_db= 1;
+ db_name= NULL;
+ db_length= 0;
+ goto end;
+ }
+ /*
+ Now we need to make a copy because check_db_name requires a
+ non-constant argument. TODO: fix check_db_name.
+ */
+ if ((db_name= my_strdup(name, MYF(MY_WME))) == NULL)
+ DBUG_RETURN(1); /* the error is set */
+ db_length= strlen(db_name);
+ if (check_db_name(db_name))
+ {
+ my_error(ER_WRONG_DB_NAME, MYF(0), db_name);
+ my_free(db_name, MYF(0));
DBUG_RETURN(1);
}
- DBUG_PRINT("info",("Use database: %s", dbname));
- if (!my_strcasecmp(system_charset_info, dbname, information_schema_name.str))
+ DBUG_PRINT("info",("Use database: %s", db_name));
+ if (!my_strcasecmp(system_charset_info, db_name, information_schema_name.str))
{
system_db= 1;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -1182,45 +1180,36 @@ bool mysql_change_db(THD *thd, const char *name, bool no_access_check)
if (test_all_bits(sctx->master_access, DB_ACLS))
db_access=DB_ACLS;
else
- db_access= (acl_get(sctx->host, sctx->ip, sctx->priv_user, dbname, 0) |
+ db_access= (acl_get(sctx->host, sctx->ip, sctx->priv_user, db_name, 0) |
sctx->master_access);
if (!(db_access & DB_ACLS) && (!grant_option ||
- check_grant_db(thd,dbname)))
+ check_grant_db(thd,db_name)))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
sctx->priv_user,
sctx->priv_host,
- dbname);
+ db_name);
mysql_log.write(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
- sctx->priv_user, sctx->priv_host, dbname);
- if (!(thd->slave_thread))
- my_free(dbname,MYF(0));
+ sctx->priv_user, sctx->priv_host, db_name);
+ my_free(db_name, MYF(0));
DBUG_RETURN(1);
}
}
#endif
- (void) sprintf(path,"%s/%s",mysql_data_home,dbname);
- length=unpack_dirname(path,path); // Convert if not unix
- if (length && path[length-1] == FN_LIBCHAR)
- path[length-1]=0; // remove ending '\'
+ (void) sprintf(path,"%s/%s", mysql_data_home, db_name);
+ path_length= unpack_dirname(path, path); // Convert if not UNIX
+ if (path_length && path[path_length-1] == FN_LIBCHAR)
+ path[path_length-1]= '\0'; // remove ending '\'
if (my_access(path,F_OK))
{
- my_error(ER_BAD_DB_ERROR, MYF(0), dbname);
- if (!(thd->slave_thread))
- my_free(dbname,MYF(0));
+ my_error(ER_BAD_DB_ERROR, MYF(0), db_name);
+ my_free(db_name, MYF(0));
DBUG_RETURN(1);
}
end:
- if (!(thd->slave_thread))
- x_free(thd->db);
- if (dbname && dbname[0] == 0)
- {
- if (!(thd->slave_thread))
- my_free(dbname, MYF(0));
- thd->reset_db(NULL, 0);
- }
- else
- thd->reset_db(dbname, db_length); // THD::~THD will free this
+ x_free(thd->db);
+ DBUG_ASSERT(db_name == NULL || db_name[0] != '\0');
+ thd->reset_db(db_name, db_length); // THD::~THD will free this
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (!no_access_check)
sctx->db_access= db_access;