mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
An attempt to fix a sporadic valgrind memory leak in Event Scheduler:
streamline the event worker thread work flow and try to eliminate possibilities for memory corruptions that might have been lurking in previous (complicated) code. This patch: * removes Event_job_data::compile that was never used * cleans up Event_job_data::execute to minimize juggling with thread context and eliminate unneded code paths * Implements Security_context::change/restore_security_context to be able to re-use these methods in all stored programs This is to maybe fix Bug#27733 "Valgrind failures in remove_table_from_cache". Review comments applied.
This commit is contained in:
@ -23,14 +23,6 @@
|
||||
|
||||
#define EVEX_MAX_INTERVAL_VALUE 1000000000L
|
||||
|
||||
static bool
|
||||
event_change_security_context(THD *thd, LEX_STRING user, LEX_STRING host,
|
||||
LEX_STRING db, Security_context *backup);
|
||||
|
||||
static void
|
||||
event_restore_security_context(THD *thd, Security_context *backup);
|
||||
|
||||
|
||||
/*
|
||||
Initiliazes dbname and name of an Event_queue_element_for_exec
|
||||
object
|
||||
@ -816,27 +808,10 @@ Event_timed::~Event_timed()
|
||||
*/
|
||||
|
||||
Event_job_data::Event_job_data()
|
||||
:sphead(NULL), sql_mode(0)
|
||||
:sql_mode(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Destructor
|
||||
|
||||
SYNOPSIS
|
||||
Event_timed::~Event_timed()
|
||||
*/
|
||||
|
||||
Event_job_data::~Event_job_data()
|
||||
{
|
||||
DBUG_ENTER("Event_job_data::~Event_job_data");
|
||||
delete sphead;
|
||||
sphead= NULL;
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Init all member variables
|
||||
|
||||
@ -1769,102 +1744,198 @@ Event_timed::get_create_event(THD *thd, String *buf)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get SHOW CREATE EVENT as string
|
||||
|
||||
SYNOPSIS
|
||||
Event_job_data::get_create_event(THD *thd, String *buf)
|
||||
thd Thread
|
||||
buf String*, should be already allocated. CREATE EVENT goes inside.
|
||||
|
||||
RETURN VALUE
|
||||
0 OK
|
||||
EVEX_MICROSECOND_UNSUP Error (for now if mysql.event has been
|
||||
tampered and MICROSECONDS interval or
|
||||
derivative has been put there.
|
||||
/**
|
||||
Get an artificial stored procedure to parse as an event definition.
|
||||
*/
|
||||
|
||||
int
|
||||
Event_job_data::get_fake_create_event(String *buf)
|
||||
bool
|
||||
Event_job_data::construct_sp_sql(THD *thd, String *sp_sql)
|
||||
{
|
||||
DBUG_ENTER("Event_job_data::get_create_event");
|
||||
/* FIXME: "EVERY 3337 HOUR" is asking for trouble. */
|
||||
buf->append(STRING_WITH_LEN("CREATE EVENT anonymous ON SCHEDULE "
|
||||
"EVERY 3337 HOUR DO "));
|
||||
buf->append(body.str, body.length);
|
||||
LEX_STRING buffer;
|
||||
const uint STATIC_SQL_LENGTH= 44;
|
||||
|
||||
DBUG_RETURN(0);
|
||||
DBUG_ENTER("Event_job_data::construct_sp_sql");
|
||||
|
||||
/*
|
||||
Allocate a large enough buffer on the thread execution memory
|
||||
root to avoid multiple [re]allocations on system heap
|
||||
*/
|
||||
buffer.length= STATIC_SQL_LENGTH + name.length + body.length;
|
||||
if (! (buffer.str= (char*) thd->alloc(buffer.length)))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
sp_sql->set(buffer.str, buffer.length, system_charset_info);
|
||||
sp_sql->length(0);
|
||||
|
||||
|
||||
sp_sql->append(C_STRING_WITH_LEN("CREATE "));
|
||||
sp_sql->append(C_STRING_WITH_LEN("PROCEDURE "));
|
||||
/*
|
||||
Let's use the same name as the event name to perhaps produce a
|
||||
better error message in case it is a part of some parse error.
|
||||
We're using append_identifier here to successfully parse
|
||||
events with reserved names.
|
||||
*/
|
||||
append_identifier(thd, sp_sql, name.str, name.length);
|
||||
|
||||
/*
|
||||
The default SQL security of a stored procedure is DEFINER. We
|
||||
have already activated the security context of the event, so
|
||||
let's execute the procedure with the invoker rights to save on
|
||||
resets of security contexts.
|
||||
*/
|
||||
sp_sql->append(C_STRING_WITH_LEN("() SQL SECURITY INVOKER "));
|
||||
|
||||
sp_sql->append(body.str, body.length);
|
||||
|
||||
DBUG_RETURN(thd->is_fatal_error);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Executes the event (the underlying sp_head object);
|
||||
|
||||
SYNOPSIS
|
||||
Event_job_data::execute()
|
||||
thd THD
|
||||
/**
|
||||
Compiles and executes the event (the underlying sp_head object)
|
||||
|
||||
RETURN VALUE
|
||||
0 success
|
||||
-99 No rights on this.dbname.str
|
||||
others retcodes of sp_head::execute_procedure()
|
||||
@retval TRUE error (reported to the error log)
|
||||
@retval FALSE success
|
||||
*/
|
||||
|
||||
int
|
||||
bool
|
||||
Event_job_data::execute(THD *thd, bool drop)
|
||||
{
|
||||
Security_context save_ctx;
|
||||
/* this one is local and not needed after exec */
|
||||
int ret= 0;
|
||||
String sp_sql;
|
||||
Security_context event_sctx, *save_sctx= NULL;
|
||||
CHARSET_INFO *charset_connection;
|
||||
List<Item> empty_item_list;
|
||||
bool ret= TRUE;
|
||||
|
||||
DBUG_ENTER("Event_job_data::execute");
|
||||
DBUG_PRINT("info", ("EXECUTING %s.%s", dbname.str, name.str));
|
||||
|
||||
if ((ret= compile(thd, NULL)))
|
||||
goto done;
|
||||
mysql_reset_thd_for_next_command(thd);
|
||||
|
||||
event_change_security_context(thd, definer_user, definer_host, dbname,
|
||||
&save_ctx);
|
||||
/*
|
||||
THD::~THD will clean this or if there is DROP DATABASE in the
|
||||
SP then it will be free there. It should not point to our buffer
|
||||
which is allocated on a mem_root.
|
||||
MySQL parser currently assumes that current database is either
|
||||
present in THD or all names in all statements are fully specified.
|
||||
And yet not fully specified names inside stored programs must be
|
||||
be supported, even if the current database is not set:
|
||||
CREATE PROCEDURE db1.p1() BEGIN CREATE TABLE t1; END//
|
||||
-- in this example t1 should be always created in db1 and the statement
|
||||
must parse even if there is no current database.
|
||||
|
||||
To support this feature and still address the parser limitation,
|
||||
we need to set the current database here.
|
||||
We don't have to call mysql_change_db, since the checks performed
|
||||
in it are unnecessary for the purpose of parsing, and
|
||||
mysql_change_db will be invoked anyway later, to activate the
|
||||
procedure database before it's executed.
|
||||
*/
|
||||
thd->db= my_strdup(dbname.str, MYF(0));
|
||||
thd->db_length= dbname.length;
|
||||
if (!check_access(thd, EVENT_ACL,dbname.str, 0, 0, 0,is_schema_db(dbname.str)))
|
||||
thd->set_db(dbname.str, dbname.length);
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
if (event_sctx.change_security_context(thd,
|
||||
&definer_user, &definer_host,
|
||||
&dbname, &save_sctx))
|
||||
{
|
||||
List<Item> empty_item_list;
|
||||
empty_item_list.empty();
|
||||
sql_print_error("Event Scheduler: "
|
||||
"[%s].[%s.%s] execution failed, "
|
||||
"failed to authenticate the user.",
|
||||
definer.str, dbname.str, name.str);
|
||||
goto end;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (check_access(thd, EVENT_ACL, dbname.str,
|
||||
0, 0, 0, is_schema_db(dbname.str)))
|
||||
{
|
||||
/*
|
||||
This aspect of behavior is defined in the worklog,
|
||||
and this is how triggers work too: if TRIGGER
|
||||
privilege is revoked from trigger definer,
|
||||
triggers are not executed.
|
||||
*/
|
||||
sql_print_error("Event Scheduler: "
|
||||
"[%s].[%s.%s] execution failed, "
|
||||
"user no longer has EVENT privilege.",
|
||||
definer.str, dbname.str, name.str);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (construct_sp_sql(thd, &sp_sql))
|
||||
goto end;
|
||||
|
||||
/*
|
||||
Set up global thread attributes to reflect the properties of
|
||||
this Event. We can simply reset these instead of usual
|
||||
backup/restore employed in stored programs since we know that
|
||||
this is a top level statement and the worker thread is
|
||||
allocated exclusively to execute this event.
|
||||
*/
|
||||
charset_connection= get_charset_by_csname("utf8",
|
||||
MY_CS_PRIMARY, MYF(MY_WME));
|
||||
thd->variables.character_set_client= charset_connection;
|
||||
thd->variables.character_set_results= charset_connection;
|
||||
thd->variables.collation_connection= charset_connection;
|
||||
thd->update_charset();
|
||||
|
||||
thd->variables.sql_mode= sql_mode;
|
||||
thd->variables.time_zone= time_zone;
|
||||
|
||||
thd->query= sp_sql.c_ptr_safe();
|
||||
thd->query_length= sp_sql.length();
|
||||
|
||||
lex_start(thd, thd->query, thd->query_length);
|
||||
|
||||
if (MYSQLparse(thd) || thd->is_fatal_error)
|
||||
{
|
||||
sql_print_error("Event Scheduler: "
|
||||
"%serror during compilation of %s.%s",
|
||||
thd->is_fatal_error ? "fatal " : "",
|
||||
(const char *) dbname.str, (const char *) name.str);
|
||||
goto end;
|
||||
}
|
||||
|
||||
{
|
||||
sp_head *sphead= thd->lex->sphead;
|
||||
|
||||
DBUG_ASSERT(sphead);
|
||||
|
||||
if (thd->enable_slow_log)
|
||||
sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
|
||||
sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
|
||||
|
||||
/* Execute the event in its time zone. */
|
||||
thd->variables.time_zone= time_zone;
|
||||
sphead->set_info(0, 0, &thd->lex->sp_chistics, sql_mode);
|
||||
sphead->optimize();
|
||||
|
||||
ret= sphead->execute_procedure(thd, &empty_item_list);
|
||||
/*
|
||||
There is no pre-locking and therefore there should be no
|
||||
tables open and locked left after execute_procedure.
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_PRINT("error", ("%s@%s has no rights on %s", definer_user.str,
|
||||
definer_host.str, dbname.str));
|
||||
ret= -99;
|
||||
}
|
||||
if (drop)
|
||||
|
||||
end:
|
||||
if (drop && !thd->is_fatal_error)
|
||||
{
|
||||
sql_print_information("Event Scheduler: Dropping %s.%s",
|
||||
dbname.str, name.str);
|
||||
(const char *) dbname.str, (const char *) name.str);
|
||||
/*
|
||||
We must do it here since here we're under the right authentication
|
||||
ID of the event definer
|
||||
ID of the event definer.
|
||||
*/
|
||||
if (Events::drop_event(thd, dbname, name, FALSE))
|
||||
ret= 1;
|
||||
}
|
||||
|
||||
event_restore_security_context(thd, &save_ctx);
|
||||
done:
|
||||
if (thd->lex->sphead) /* NULL only if a parse error */
|
||||
{
|
||||
delete thd->lex->sphead;
|
||||
thd->lex->sphead= NULL;
|
||||
}
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
if (save_sctx)
|
||||
event_sctx.restore_security_context(thd, save_sctx);
|
||||
#endif
|
||||
lex_end(thd->lex);
|
||||
thd->lex->unit.cleanup();
|
||||
thd->end_statement();
|
||||
thd->cleanup_after_query();
|
||||
|
||||
@ -1874,134 +1945,6 @@ done:
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Compiles an event before it's execution. Compiles the anonymous
|
||||
sp_head object held by the event
|
||||
|
||||
SYNOPSIS
|
||||
Event_job_data::compile()
|
||||
thd thread context, used for memory allocation mostly
|
||||
mem_root if != NULL then this memory root is used for allocs
|
||||
instead of thd->mem_root
|
||||
|
||||
RETURN VALUE
|
||||
0 success
|
||||
EVEX_COMPILE_ERROR error during compilation
|
||||
EVEX_MICROSECOND_UNSUP mysql.event was tampered
|
||||
*/
|
||||
|
||||
int
|
||||
Event_job_data::compile(THD *thd, MEM_ROOT *mem_root)
|
||||
{
|
||||
int ret= 0;
|
||||
MEM_ROOT *tmp_mem_root= 0;
|
||||
LEX *old_lex= thd->lex, lex;
|
||||
char *old_db;
|
||||
int old_db_length;
|
||||
char *old_query;
|
||||
uint old_query_len;
|
||||
ulong old_sql_mode= thd->variables.sql_mode;
|
||||
char create_buf[15 * STRING_BUFFER_USUAL_SIZE];
|
||||
String show_create(create_buf, sizeof(create_buf), system_charset_info);
|
||||
CHARSET_INFO *old_character_set_client,
|
||||
*old_collation_connection,
|
||||
*old_character_set_results;
|
||||
Security_context save_ctx;
|
||||
|
||||
DBUG_ENTER("Event_job_data::compile");
|
||||
|
||||
show_create.length(0);
|
||||
|
||||
switch (get_fake_create_event(&show_create)) {
|
||||
case EVEX_MICROSECOND_UNSUP:
|
||||
DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
DBUG_ASSERT(0);
|
||||
}
|
||||
|
||||
old_character_set_client= thd->variables.character_set_client;
|
||||
old_character_set_results= thd->variables.character_set_results;
|
||||
old_collation_connection= thd->variables.collation_connection;
|
||||
|
||||
thd->variables.character_set_client=
|
||||
thd->variables.character_set_results=
|
||||
thd->variables.collation_connection=
|
||||
get_charset_by_csname("utf8", MY_CS_PRIMARY, MYF(MY_WME));
|
||||
|
||||
thd->update_charset();
|
||||
|
||||
DBUG_PRINT("info",("old_sql_mode: %lu new_sql_mode: %lu",old_sql_mode, sql_mode));
|
||||
thd->variables.sql_mode= this->sql_mode;
|
||||
/* Change the memory root for the execution time */
|
||||
if (mem_root)
|
||||
{
|
||||
tmp_mem_root= thd->mem_root;
|
||||
thd->mem_root= mem_root;
|
||||
}
|
||||
old_query_len= thd->query_length;
|
||||
old_query= thd->query;
|
||||
old_db= thd->db;
|
||||
old_db_length= thd->db_length;
|
||||
thd->db= dbname.str;
|
||||
thd->db_length= dbname.length;
|
||||
|
||||
thd->query= show_create.c_ptr_safe();
|
||||
thd->query_length= show_create.length();
|
||||
DBUG_PRINT("info", ("query: %s",thd->query));
|
||||
|
||||
event_change_security_context(thd, definer_user, definer_host, dbname,
|
||||
&save_ctx);
|
||||
thd->lex= &lex;
|
||||
mysql_init_query(thd, thd->query, thd->query_length);
|
||||
if (MYSQLparse((void *)thd) || thd->is_fatal_error)
|
||||
{
|
||||
DBUG_PRINT("error", ("error during compile or thd->is_fatal_error: %d",
|
||||
thd->is_fatal_error));
|
||||
lex.unit.cleanup();
|
||||
|
||||
sql_print_error("Event Scheduler: "
|
||||
"%serror during compilation of %s.%s",
|
||||
thd->is_fatal_error ? "fatal " : "",
|
||||
dbname.str, name.str);
|
||||
|
||||
ret= EVEX_COMPILE_ERROR;
|
||||
goto done;
|
||||
}
|
||||
DBUG_PRINT("note", ("success compiling %s.%s", dbname.str, name.str));
|
||||
|
||||
sphead= lex.sphead;
|
||||
|
||||
sphead->set_definer(definer.str, definer.length);
|
||||
sphead->set_info(0, 0, &lex.sp_chistics, sql_mode);
|
||||
sphead->optimize();
|
||||
ret= 0;
|
||||
done:
|
||||
|
||||
lex_end(&lex);
|
||||
event_restore_security_context(thd, &save_ctx);
|
||||
DBUG_PRINT("note", ("return old data on its place. set back NAMES"));
|
||||
|
||||
thd->lex= old_lex;
|
||||
thd->query= old_query;
|
||||
thd->query_length= old_query_len;
|
||||
thd->db= old_db;
|
||||
|
||||
thd->variables.sql_mode= old_sql_mode;
|
||||
thd->variables.character_set_client= old_character_set_client;
|
||||
thd->variables.character_set_results= old_character_set_results;
|
||||
thd->variables.collation_connection= old_collation_connection;
|
||||
thd->update_charset();
|
||||
|
||||
/* Change the memory root for the execution time. */
|
||||
if (mem_root)
|
||||
thd->mem_root= tmp_mem_root;
|
||||
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Checks whether two events are in the same schema
|
||||
|
||||
@ -2042,64 +1985,3 @@ event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b)
|
||||
return !sortcmp_lex_string(name, b->name, system_charset_info) &&
|
||||
!sortcmp_lex_string(db, b->dbname, system_charset_info);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Switches the security context.
|
||||
|
||||
SYNOPSIS
|
||||
event_change_security_context()
|
||||
thd Thread
|
||||
user The user
|
||||
host The host of the user
|
||||
db The schema for which the security_ctx will be loaded
|
||||
backup Where to store the old context
|
||||
|
||||
RETURN VALUE
|
||||
FALSE OK
|
||||
TRUE Error (generates error too)
|
||||
*/
|
||||
|
||||
static bool
|
||||
event_change_security_context(THD *thd, LEX_STRING user, LEX_STRING host,
|
||||
LEX_STRING db, Security_context *backup)
|
||||
{
|
||||
DBUG_ENTER("event_change_security_context");
|
||||
DBUG_PRINT("info",("%s@%s@%s", user.str, host.str, db.str));
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
|
||||
*backup= thd->main_security_ctx;
|
||||
if (acl_getroot_no_password(&thd->main_security_ctx, user.str, host.str,
|
||||
host.str, db.str))
|
||||
{
|
||||
my_error(ER_NO_SUCH_USER, MYF(0), user.str, host.str);
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
thd->security_ctx= &thd->main_security_ctx;
|
||||
#endif
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Restores the security context.
|
||||
|
||||
SYNOPSIS
|
||||
event_restore_security_context()
|
||||
thd Thread
|
||||
backup Context to switch to
|
||||
*/
|
||||
|
||||
static void
|
||||
event_restore_security_context(THD *thd, Security_context *backup)
|
||||
{
|
||||
DBUG_ENTER("event_restore_security_context");
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
if (backup)
|
||||
{
|
||||
thd->main_security_ctx= *backup;
|
||||
thd->security_ctx= &thd->main_security_ctx;
|
||||
}
|
||||
#endif
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
Reference in New Issue
Block a user