summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <dlenev@mockturtle.local>2007-05-23 15:22:13 +0400
committerunknown <dlenev@mockturtle.local>2007-05-23 15:22:13 +0400
commitf9d7642e7b46d911cda5512ac0bcb55dd85dcd95 (patch)
tree03812138feda644bf3f4473b2cb4c51f59b88027
parentfda27597eef5315ae0334ce6354d0dfe807e6672 (diff)
downloadmariadb-git-f9d7642e7b46d911cda5512ac0bcb55dd85dcd95.tar.gz
5.0 version of fix for:
Bug #23667 "CREATE TABLE LIKE is not isolated from alteration by other connections" Bug #18950 "CREATE TABLE LIKE does not obtain LOCK_open" As well as: Bug #25578 "CREATE TABLE LIKE does not require any privileges on source table". The first and the second bugs resulted in various errors and wrong binary log order when one tried to execute concurrently CREATE TABLE LIKE statement and DDL statements on source table or DML/DDL statements on its target table. The problem was caused by incomplete protection/table-locking against concurrent statements implemented in mysql_create_like_table() routine. We solve it by simply implementing such protection in proper way (see comment for sql_table.cc for details). The third bug allowed user who didn't have any privileges on table create its copy and therefore circumvent privilege check for SHOW CREATE TABLE. This patch solves this problem by adding privilege check, which was missing. Finally it also removes some duplicated code from mysql_create_like_table(). Note that, altough tests covering concurrency-related aspects of CREATE TABLE LIKE behaviour will only be introduced in 5.1, they were run manually for this patch as well. mysql-test/r/grant2.result: Added test for bug#25578 "CREATE TABLE LIKE does not require any privileges on source table". mysql-test/t/grant2.test: Added test for bug#25578 "CREATE TABLE LIKE does not require any privileges on source table". sql/handler.h: Introduced new flag for HA_CREATE_INFO::options in order to be able to distinguish CREATE TABLE ... LIKE from other types of CREATE TABLE. sql/mysql_priv.h: mysql_create_like_table() now takes source table name not as a Table_ident object but as regular table list element. sql/sql_parse.cc: CREATE TABLE ... LIKE implementation now uses statement's table list for storing information about the source table. We also use flag in LEX::create_info.options for distinguishing it from other types of CREATE TABLE. Finally CREATE TABLE ... LIKE now requires the same privileges on the source tables as SHOW CREATE TABLE. Moved this privilege check to check_show_create_table_access() function. sql/sql_table.cc: mysql_create_like_table(): - Provided proper protection from concurrent statements. This is achieved by keeping name-lock on the source table and holding LOCK_open mutex during whole operation. This gives protection against concurrent DDL on source table. Also holding this mutex makes copying of .frm file, call to ha_create_table() and binlogging atomic against concurrent DML and DDL operations on target table. - Get rid of duplicated code related to source database/table name handling. All these operations are already done in st_select_lex::add_table_to_list(), so we achieve the same effect by including source table into the statement's table list. sql/sql_yacc.yy: Now we use special flag in LEX::create_info::options for distinguishing CREATE TABLE ... LIKE from other types of CREATE TABLE and store name of source table as regular element in statement's table list.
-rw-r--r--mysql-test/r/grant2.result24
-rw-r--r--mysql-test/t/grant2.test44
-rw-r--r--sql/handler.h1
-rw-r--r--sql/mysql_priv.h5
-rw-r--r--sql/sql_parse.cc37
-rw-r--r--sql/sql_table.cc91
-rw-r--r--sql/sql_yacc.yy21
7 files changed, 146 insertions, 77 deletions
diff --git a/mysql-test/r/grant2.result b/mysql-test/r/grant2.result
index ff9b7bc6f1f..93098e68070 100644
--- a/mysql-test/r/grant2.result
+++ b/mysql-test/r/grant2.result
@@ -380,3 +380,27 @@ drop function f2;
drop table t2;
REVOKE ALL PRIVILEGES, GRANT OPTION FROM `a@`@localhost;
drop user `a@`@localhost;
+drop database if exists mysqltest_1;
+drop database if exists mysqltest_2;
+drop user mysqltest_u1@localhost;
+create database mysqltest_1;
+create database mysqltest_2;
+grant all on mysqltest_1.* to mysqltest_u1@localhost;
+use mysqltest_2;
+create table t1 (i int);
+show create table mysqltest_2.t1;
+ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
+create table t1 like mysqltest_2.t1;
+ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table 't1'
+grant select on mysqltest_2.t1 to mysqltest_u1@localhost;
+show create table mysqltest_2.t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(11) default NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+create table t1 like mysqltest_2.t1;
+use test;
+drop database mysqltest_1;
+drop database mysqltest_2;
+drop user mysqltest_u1@localhost;
+End of 5.0 tests
diff --git a/mysql-test/t/grant2.test b/mysql-test/t/grant2.test
index d08a9e3f83d..4a3324b1833 100644
--- a/mysql-test/t/grant2.test
+++ b/mysql-test/t/grant2.test
@@ -513,3 +513,47 @@ disconnect bug13310;
connection default;
REVOKE ALL PRIVILEGES, GRANT OPTION FROM `a@`@localhost;
drop user `a@`@localhost;
+
+
+#
+# Bug#25578 "CREATE TABLE LIKE does not require any privileges on source table"
+#
+--disable_warnings
+drop database if exists mysqltest_1;
+drop database if exists mysqltest_2;
+--enable_warnings
+--error 0,ER_CANNOT_USER
+drop user mysqltest_u1@localhost;
+
+create database mysqltest_1;
+create database mysqltest_2;
+grant all on mysqltest_1.* to mysqltest_u1@localhost;
+use mysqltest_2;
+create table t1 (i int);
+
+# Connect as user with all rights on mysqltest_1 but with no rights on mysqltest_2.
+connect (user1,localhost,mysqltest_u1,,mysqltest_1);
+connection user1;
+# As expected error is emitted
+--error ER_TABLEACCESS_DENIED_ERROR
+show create table mysqltest_2.t1;
+# This should emit error as well
+--error ER_TABLEACCESS_DENIED_ERROR
+create table t1 like mysqltest_2.t1;
+
+# Now let us check that SELECT privilege on the source is enough
+connection default;
+grant select on mysqltest_2.t1 to mysqltest_u1@localhost;
+connection user1;
+show create table mysqltest_2.t1;
+create table t1 like mysqltest_2.t1;
+
+# Clean-up
+connection default;
+use test;
+drop database mysqltest_1;
+drop database mysqltest_2;
+drop user mysqltest_u1@localhost;
+
+--echo End of 5.0 tests
+
diff --git a/sql/handler.h b/sql/handler.h
index 9863d541b5f..a59b7a09740 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -162,6 +162,7 @@
#define HA_LEX_CREATE_TMP_TABLE 1
#define HA_LEX_CREATE_IF_NOT_EXISTS 2
+#define HA_LEX_CREATE_TABLE_LIKE 4
#define HA_OPTION_NO_CHECKSUM (1L << 17)
#define HA_OPTION_NO_DELAY_KEY_WRITE (1L << 18)
#define HA_MAX_REC_LENGTH 65535
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index f03ef487154..3d061c9eb9e 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -816,9 +816,8 @@ bool mysql_alter_table(THD *thd, char *new_db, char *new_name,
Alter_info *alter_info,
uint order_num, ORDER *order, bool ignore);
bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list);
-bool mysql_create_like_table(THD *thd, TABLE_LIST *table,
- HA_CREATE_INFO *create_info,
- Table_ident *src_table);
+bool mysql_create_like_table(THD *thd, TABLE_LIST *table, TABLE_LIST *src_table,
+ HA_CREATE_INFO *create_info);
bool mysql_rename_table(enum db_type base,
const char *old_db,
const char * old_name,
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 7e58b36a939..6277a6c0c10 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -75,6 +75,7 @@ static bool check_db_used(THD *thd,TABLE_LIST *tables);
static void remove_escape(char *name);
static bool append_file_to_dir(THD *thd, const char **filename_ptr,
const char *table_name);
+static bool check_show_create_table_access(THD *thd, TABLE_LIST *table);
const char *any_db="*any*"; // Special symbol for check_access
@@ -3080,9 +3081,9 @@ mysql_execute_command(THD *thd)
else
{
/* regular create */
- if (lex->name)
- res= mysql_create_like_table(thd, create_table, &create_info,
- (Table_ident *)lex->name);
+ if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE)
+ res= mysql_create_like_table(thd, create_table, select_tables,
+ &create_info);
else
{
res= mysql_create_table(thd, create_table->db,
@@ -3319,11 +3320,7 @@ end_with_restore_list:
first_table->skip_temporary= 1;
if (check_db_used(thd, all_tables) ||
- check_access(thd, SELECT_ACL | EXTRA_ACL, first_table->db,
- &first_table->grant.privilege, 0, 0,
- test(first_table->schema_table)))
- goto error;
- if (grant_option && check_grant(thd, SELECT_ACL, all_tables, 2, UINT_MAX, 0))
+ check_show_create_table_access(thd, first_table))
goto error;
res= mysqld_show_create(thd, first_table);
break;
@@ -7519,6 +7516,25 @@ bool insert_precheck(THD *thd, TABLE_LIST *tables)
}
+/**
+ @brief Check privileges for SHOW CREATE TABLE statement.
+
+ @param thd Thread context
+ @param table Target table
+
+ @retval TRUE Failure
+ @retval FALSE Success
+*/
+
+static bool check_show_create_table_access(THD *thd, TABLE_LIST *table)
+{
+ return check_access(thd, SELECT_ACL | EXTRA_ACL, table->db,
+ &table->grant.privilege, 0, 0,
+ test(table->schema_table)) ||
+ grant_option && check_grant(thd, SELECT_ACL, table, 2, UINT_MAX, 0);
+}
+
+
/*
CREATE TABLE query pre-check
@@ -7583,6 +7599,11 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables,
if (tables && check_table_access(thd, SELECT_ACL, tables,0))
goto err;
}
+ else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE)
+ {
+ if (check_show_create_table_access(thd, tables))
+ goto err;
+ }
error= FALSE;
err:
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 079cc0d6456..4a9f6074b59 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -2718,7 +2718,8 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
SYNOPSIS
mysql_create_like_table()
thd Thread object
- table Table list (one table only)
+ table Table list element for target table
+ src_table Table list element for source table
create_info Create info
table_ident Src table_ident
@@ -2727,61 +2728,52 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
TRUE error
*/
-bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
- HA_CREATE_INFO *create_info,
- Table_ident *table_ident)
+bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table,
+ HA_CREATE_INFO *create_info)
{
TABLE **tmp_table;
char src_path[FN_REFLEN], dst_path[FN_REFLEN];
char *db= table->db;
char *table_name= table->table_name;
- char *src_db;
- char *src_table= table_ident->table.str;
int err;
bool res= TRUE;
db_type not_used;
-
- TABLE_LIST src_tables_list;
DBUG_ENTER("mysql_create_like_table");
- DBUG_ASSERT(table_ident->db.str); /* Must be set in the parser */
- src_db= table_ident->db.str;
/*
- Validate the source table
+ By taking name-lock on the source table and holding LOCK_open mutex we
+ ensure that no concurrent DDL operation will mess with this table. Note
+ that holding only name-lock is not enough for this, because it won't block
+ other DDL statements that only take name-locks on the table and don't
+ open it (simple name-locks are not exclusive between each other).
+
+ Unfortunately, simply opening this table is not enough for our purproses,
+ since in 5.0 ALTER TABLE may change .FRM files on disk even if there are
+ connections that still have old version of table open. This 'optimization'
+ was removed in 5.1 so there we open the source table instead of taking
+ name-lock on it.
+
+ We also have to acquire LOCK_open to make copying of .frm file, call to
+ ha_create_table() and binlogging atomic against concurrent DML and DDL
+ operations on the target table.
*/
- if (table_ident->table.length > NAME_LEN ||
- (table_ident->table.length &&
- check_table_name(src_table,table_ident->table.length)))
- {
- my_error(ER_WRONG_TABLE_NAME, MYF(0), src_table);
- DBUG_RETURN(TRUE);
- }
- if (!src_db || check_db_name(src_db))
- {
- my_error(ER_WRONG_DB_NAME, MYF(0), src_db ? src_db : "NULL");
- DBUG_RETURN(-1);
- }
-
- bzero((gptr)&src_tables_list, sizeof(src_tables_list));
- src_tables_list.db= src_db;
- src_tables_list.table_name= src_table;
-
- if (lock_and_wait_for_table_name(thd, &src_tables_list))
+ if (lock_and_wait_for_table_name(thd, src_table))
goto err;
- if ((tmp_table= find_temporary_table(thd, src_db, src_table)))
+ pthread_mutex_lock(&LOCK_open);
+
+ if ((tmp_table= find_temporary_table(thd, src_table->db,
+ src_table->table_name)))
strxmov(src_path, (*tmp_table)->s->path, reg_ext, NullS);
else
{
- strxmov(src_path, mysql_data_home, "/", src_db, "/", src_table,
- reg_ext, NullS);
+ strxmov(src_path, mysql_data_home, "/", src_table->db, "/",
+ src_table->table_name, reg_ext, NullS);
/* Resolve symlinks (for windows) */
fn_format(src_path, src_path, "", "", MYF(MY_UNPACK_FILENAME));
- if (lower_case_table_names)
- my_casedn_str(files_charset_info, src_path);
if (access(src_path, F_OK))
{
- my_error(ER_BAD_TABLE_ERROR, MYF(0), src_table);
+ my_error(ER_BAD_TABLE_ERROR, MYF(0), src_table->table_name);
goto err;
}
}
@@ -2791,10 +2783,13 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
*/
if (mysql_frm_type(thd, src_path, &not_used) != FRMTYPE_TABLE)
{
- my_error(ER_WRONG_OBJECT, MYF(0), src_db, src_table, "BASE TABLE");
+ my_error(ER_WRONG_OBJECT, MYF(0), src_table->db, src_table->table_name,
+ "BASE TABLE");
goto err;
}
+ DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
+
/*
Validate the destination table
@@ -2810,27 +2805,22 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
}
else
{
- bool exists;
strxmov(dst_path, mysql_data_home, "/", db, "/", table_name,
reg_ext, NullS);
fn_format(dst_path, dst_path, "", "", MYF(MY_UNPACK_FILENAME));
/*
- Note that this critical section should actually cover most
- of mysql_create_like_table() function. See bugs #18950 and
- #23667 for more information.
- Also note that starting from 5.1 we obtain name-lock on
- target table instead of inspecting table cache for presence
+ Note that starting from 5.1 we obtain name-lock on target
+ table instead of inspecting table cache for presence
of open placeholders (see comment in mysql_create_table()).
*/
- pthread_mutex_lock(&LOCK_open);
- exists= (table_cache_has_open_placeholder(thd, db, table_name) ||
- !access(dst_path, F_OK));
- pthread_mutex_unlock(&LOCK_open);
- if (exists)
+ if (table_cache_has_open_placeholder(thd, db, table_name) ||
+ !access(dst_path, F_OK))
goto table_exists;
}
+ DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000););
+
/*
Create a new table by copying from source table
*/
@@ -2843,6 +2833,8 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
goto err;
}
+ DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000););
+
/*
As mysql_truncate don't work on a new table at this stage of
creation, instead create the table directly (for both normal
@@ -2867,6 +2859,8 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
goto err; /* purecov: inspected */
}
+ DBUG_EXECUTE_IF("sleep_create_like_before_binlogging", my_sleep(6000000););
+
// Must be written before unlock
if (mysql_bin_log.is_open())
{
@@ -2891,8 +2885,7 @@ table_exists:
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
err:
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, &src_tables_list);
+ unlock_table_name(thd, src_table);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(res);
}
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index c1da870960b..b970bcaedd6 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -1483,7 +1483,6 @@ create:
lex->create_info.options=$2 | $4;
lex->create_info.db_type= (enum db_type) lex->thd->variables.table_type;
lex->create_info.default_table_charset= NULL;
- lex->name=0;
}
create2
{ Lex->current_select= &Lex->select_lex; }
@@ -2763,27 +2762,15 @@ create2:
| opt_create_table_options create3 {}
| LIKE table_ident
{
- LEX *lex=Lex;
- THD *thd= lex->thd;
- if (!(lex->name= (char *)$2))
+ Lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE;
+ if (!Lex->select_lex.add_table_to_list(YYTHD, $2, NULL, 0, TL_READ))
MYSQL_YYABORT;
- if ($2->db.str == NULL &&
- thd->copy_db_to(&($2->db.str), &($2->db.length)))
- {
- MYSQL_YYABORT;
- }
}
| '(' LIKE table_ident ')'
{
- LEX *lex=Lex;
- THD *thd= lex->thd;
- if (!(lex->name= (char *)$3))
+ Lex->create_info.options|= HA_LEX_CREATE_TABLE_LIKE;
+ if (!Lex->select_lex.add_table_to_list(YYTHD, $3, NULL, 0, TL_READ))
MYSQL_YYABORT;
- if ($3->db.str == NULL &&
- thd->copy_db_to(&($3->db.str), &($3->db.length)))
- {
- MYSQL_YYABORT;
- }
}
;