Hello Marc!
Here are my comments about changes to ACL subsystem that your
have introduced in your performance schema tree.
1) The main issue which I have with your patch is that you try to use ACL
subsystem for preventing users from attempting particular operations
which are not supported (e.g. TRUNCATE TABLE).
Whereas our ACL system supports only allowing/disallowing classes of
operations (e.g. all operations which require DROP privilege).
I see the following problems arising from such discrepancy:
a) We no longer can express restrictions enforced by this subsystem
as a series of GRANT statements or of DML to privilege tables.
As result you have to implement your own logic for performing ACL
checks for P_S tables instead of just properly filling ACL's
internal structures and relying on standard ACL code to do its job
(well, you also might need to adjust ACL code so it won't apply
global privileges to special schemas like I_S or P_S).
b) We will have to explain to our users why do they get error
messages like "ERROR 42000: DROP command denied to user
'root'@'localhost' for table 'FILE_INSTANCES'" in situations when
they try to execute TRUNCATE TABLE statement for one of read-only
P_S tables and has enough privileges for such operation.
c) ACL API is changed since its functions start to take into account
not only set of privileges required for performing operation but
also the value of LEX::sql_command member (IMO this fact should
be at least reflected in signatures of functions constituting this
API such as check_access()/grant()).
Also AFAIU such approach conflicts with changes to ACL subsystem
that are planned by SergeiG as part of work on converting I_S
implementation to use storage engine. As far as I know his idea was
that privileges for I_S and P_S tables should be described by static
arrays similar to those that are used for describing structure of
I_S tables and that data from these arrays should be used by ACL
subsystem to perform privilege checks (indeed there should be some
registry for such descriptions similar to one that you have).
A good thing about such approach is that this infrastructure is hard
to misuse for anything else than privilege checks.
IMO we should try to address these issues or at least to avoid
complicating further changes trying to do so. I see the following
ways to achieve this:
I) We can clearly say that the checks that you are trying to
perform are not actually privilege checks but rather checks
if certain operation is supported for this kind of table.
Such checks can be performed outside of ACL subsystem, can
use any data they want (e.g. LEX::sql_command) and can emit
proper error message (not related to lack of privileges)
which won't contradict to privileges which users have.
II) We can separate checks that can be expressed as privilege
limitations from checks that don't fit into ACL well.
The former can still be performed from within ACL subsystem
(using set of functions and classes that you have introduced)
while the latter should be moved out of it. Such checks, which
try to prohibit particular operation (e.g. TRUNCATE TABLE)/
use LEX::sql_command value, probably can be performed in code
implementing corresponding operation (e.g. in handler method)
and should emit error that would clearly say that problem is
not the lack of privileges but is that particular operation is
not supported for this table.
In the current conditions I'm in favor of the second approach.
AFAIK SergeiG prefers it as well.
2) Another issue which I have with your patch is that
check_single_table_access() and check_table_access()
directly call get_cached_schema/table_access().
From my point of view check_table_access()/single_table_access()
are auxiliary wrappers which use ACL API functions like
check_access() and check_grant() to perform actual checks.
I think it is a good idea to limit interaction with functions/
classes that you have introduced to check_access()/check_grant()
(i.e. access them from inside of ACL subsystem only).
Call to get_cached_schema_access() in check_table_access()
can be easily removed with the whole branch of if-statement which
calls it. After all this branch implements an optimization and
privilege checks will work as expected if instead check_access()
will be called in this case (also I think that two remaining
branches of this "if ... else if ... else" statement can be
merged thus simplifying code even more).
Call to get_cached_table_access() in check_single_table_access()
should also be unnecessary since this function calls check_grant()
which in its turns calls get_cached_table_access().
3) I think it is a good idea to change ACL implementation to use
classes and functions that you have introduced for I_S tables
as well. Such change will demonstrate that interfaces that you
have chosen are flexible enough and will make ACL code much
cleaner. Without making this step your patch increases complexity
of ACL code while with it will decrease it.
4) Finally, I was surprised to find out that your patch does not
introduce support for per-column privileges. Since even in updatable
P_S tables not all fields are updatable it makes sense to use the
privilege mechanism to prohibit attempts to update such fields.
It turns out that currently you do this by returning error from
handler::update_row() method and emitting 'Table storage engine for
'table_name' doesn't have this option' error message.
But don't you think that this error message is confusing and can
be easily interpreted as that the whole table is not updatable?
Also why to have discrepancy in handling of tables and columns?
I think we should deal with/discuss these major issues before proceeding
to smaller ones.
------------------------------------------------------------------------
For the reference here are changes from your tree that I have reviewed
(I have omitted changes which I think are not relevant to ACL):
--- mysql-azalea/sql/sql_acl.cc 2009-07-15 12:40:00.000000000 +0400
+++ mysql-azalea-perfschema/sql/sql_acl.cc 2009-07-23 20:04:47.000000000 +0400
@@ -3943,6 +3943,23 @@ bool check_grant(THD *thd, ulong want_ac
sctx = test(table->security_ctx) ?
table->security_ctx : thd->security_ctx;
+ const ACL_internal_table_access *access;
+ access= get_cached_table_access(& table->grant.m_internal,
+ table->get_db_name(),
+ table->get_table_name());
+ if (access)
+ {
+ switch(access->check(thd->lex->sql_command, want_access))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ continue;
+ case ACL_INTERNAL_ACCESS_DENIED:
+ goto err;
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ break;
+ }
+ }
+
want_access&= ~sctx->master_access;
if (!want_access)
continue; // ok
@@ -6491,13 +6508,13 @@ int fill_schema_user_privileges(THD *thd
ulong want_access;
char buff[100];
TABLE *table= tables->table;
- bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0);
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql", 0, NULL, 1, 1, 0);
char *curr_host= thd->security_ctx->priv_host_name();
DBUG_ENTER("fill_schema_user_privileges");
if (!initialized)
DBUG_RETURN(0);
- pthread_mutex_lock(&acl_cache->lock);
+ mysql_mutex_lock(&acl_cache->lock);
for (counter=0 ; counter < acl_users.elements ; counter++)
{
@@ -6565,13 +6582,13 @@ int fill_schema_schema_privileges(THD *t
ulong want_access;
char buff[100];
TABLE *table= tables->table;
- bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0);
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql", 0, NULL, 1, 1, 0);
char *curr_host= thd->security_ctx->priv_host_name();
DBUG_ENTER("fill_schema_schema_privileges");
if (!initialized)
DBUG_RETURN(0);
- pthread_mutex_lock(&acl_cache->lock);
+ mysql_mutex_lock(&acl_cache->lock);
for (counter=0 ; counter < acl_dbs.elements ; counter++)
{
@@ -6640,11 +6657,11 @@ int fill_schema_table_privileges(THD *th
uint index;
char buff[100];
TABLE *table= tables->table;
- bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0);
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql", 0, NULL, 1, 1, 0);
char *curr_host= thd->security_ctx->priv_host_name();
DBUG_ENTER("fill_schema_table_privileges");
- rw_rdlock(&LOCK_grant);
+ mysql_rwlock_rdlock(&LOCK_grant);
for (index=0 ; index < column_priv_hash.records ; index++)
{
@@ -6723,11 +6740,11 @@ int fill_schema_column_privileges(THD *t
uint index;
char buff[100];
TABLE *table= tables->table;
- bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0);
+ bool no_global_access= check_access(thd, SELECT_ACL, "mysql", 0, NULL, 1, 1, 0);
char *curr_host= thd->security_ctx->priv_host_name();
DBUG_ENTER("fill_schema_table_privileges");
- rw_rdlock(&LOCK_grant);
+ mysql_rwlock_rdlock(&LOCK_grant);
for (index=0 ; index < column_priv_hash.records ; index++)
{
@@ -6872,3 +6889,106 @@ bool check_routine_level_acl(THD *thd, c
}
#endif
+
+struct ACL_internal_schema_registry_entry
+{
+ const LEX_STRING *m_name;
+ const ACL_internal_schema_access *m_access;
+};
+
+/**
+ Internal schema registered.
+ Currently, this is only:
+ - performance_schema
+ This can be reused later for:
+ - information_schema,
+ - mysql
+*/
+static ACL_internal_schema_registry_entry registry_array[1];
+static uint m_registry_array_size= 0;
+
+/**
+ Add an internal schema to the registry.
+ @param name the schema name
+ @param access the schema ACL specific rules
+*/
+void ACL_internal_schema_registry::register_schema
+ (const LEX_STRING *name, const ACL_internal_schema_access *access)
+{
+ DBUG_ASSERT(m_registry_array_size < array_elements(registry_array));
+
+ /* Not thread safe, and does not need to be. */
+ registry_array[m_registry_array_size].m_name= name;
+ registry_array[m_registry_array_size].m_access= access;
+ m_registry_array_size++;
+}
+
+/**
+ Search per internal schema ACL by name.
+ @param name a schema name
+ @return per schema rules, or NULL
+*/
+const ACL_internal_schema_access *
+ACL_internal_schema_registry::lookup(const char *name)
+{
+ if (name == NULL)
+ return NULL;
+
+ uint i;
+ uint len= strlen(name);
+
+ for (i= 0; i<m_registry_array_size; i++)
+ {
+ if ((registry_array[i].m_name->length == len) &&
+ (strncasecmp(registry_array[i].m_name->str, name, len) == 0))
+ return registry_array[i].m_access;
+ }
+ return NULL;
+}
+
+/**
+ Get a cached internal schema access.
+ @param grant_internal_info the cache
+ @param schema_name the name of the internal schema
+*/
+const ACL_internal_schema_access *
+get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name)
+{
+ if (grant_internal_info)
+ {
+ if (! grant_internal_info->m_schema_lookup_done)
+ {
+ grant_internal_info->m_schema_access=
+ ACL_internal_schema_registry::lookup(schema_name);
+ grant_internal_info->m_schema_lookup_done= true;
+ }
+ return grant_internal_info->m_schema_access;
+ }
+ return ACL_internal_schema_registry::lookup(schema_name);
+}
+
+/**
+ Get a cached internal table access.
+ @param grant_internal_info the cache
+ @param schema_name the name of the internal schema
+ @param table_name the name of the internal table
+*/
+const ACL_internal_table_access *
+get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name,
+ const char *table_name)
+{
+ DBUG_ASSERT(grant_internal_info);
+ if (! grant_internal_info->m_table_lookup_done)
+ {
+ const ACL_internal_schema_access *schema_access;
+ schema_access= get_cached_schema_access(grant_internal_info, schema_name);
+ if (schema_access)
+ grant_internal_info->m_table_access= schema_access->lookup(table_name);
+ grant_internal_info->m_table_lookup_done= true;
+ }
+ return grant_internal_info->m_table_access;
+}
+
+
--- mysql-azalea/sql/sql_acl.h 2009-07-15 12:40:00.000000000 +0400
+++ mysql-azalea-perfschema/sql/sql_acl.h 2009-07-23 20:05:05.000000000 +0400
@@ -291,3 +291,112 @@ bool has_any_table_level_privileges(THD
#define check_grant(A,B,C,D,E,F) 0
#define check_grant_db(A,B) 0
#endif
+
+/**
+ Result of an access check for an internal schema or table.
+ Internal ACL checks are always performed *before* using
+ the grant tables.
+ This mechanism enforces that the server implementation has full
+ control on its internal tables.
+ Depending on the internal check result, the server implementation
+ can choose to:
+ - always allow access,
+ - always deny access,
+ - delegate the decision to the database administrator,
+ by using the grant tables.
+*/
+enum ACL_internal_access_result
+{
+ /** Access granted, do not use the grant tables. */
+ ACL_INTERNAL_ACCESS_GRANTED,
+ /** Access denied, do not use the grant tables. */
+ ACL_INTERNAL_ACCESS_DENIED,
+ /** No decision yet, use the grant tables. */
+ ACL_INTERNAL_ACCESS_CHECK_GRANT
+};
+
+/**
+ Per internal table ACL access rules.
+ This class is an interface.
+ Per table(s) specific access rule should be implemented in a subclass.
+ @sa ACL_internal_schema_access
+*/
+class ACL_internal_table_access
+{
+public:
+ ACL_internal_table_access()
+ {}
+
+ virtual ~ACL_internal_table_access()
+ {}
+
+ /**
+ Check access to an internal table.
+ @param command the sql operation requested
+ @param want_access the privileges requested
+ @return the access check result
+ */
+ virtual ACL_internal_access_result check(enum_sql_command command,
+ ulong want_access) const= 0;
+};
+
+/**
+ Per internal schema ACL access rules.
+ This class is an interface.
+ Each per schema specific access rule should be implemented
+ in a different subclass, and registered.
+ Per schema access rules can control:
+ - every schema privileges on schema.*
+ - every table privileges on schema.table
+ @sa ACL_internal_schema_registry
+*/
+class ACL_internal_schema_access
+{
+public:
+ ACL_internal_schema_access()
+ {}
+
+ virtual ~ACL_internal_schema_access()
+ {}
+
+ /**
+ Check access to an internal schema.
+ @param command the sql operation requested
+ @param want_access the privileges requested
+ @param [out] save_priv the privileges granted
+ @return the access check result
+ */
+ virtual ACL_internal_access_result check(enum_sql_command command,
+ ulong want_access,
+ ulong *save_priv) const= 0;
+
+ /**
+ Search for per table ACL access rules by table name.
+ @param name the table name
+ @return per table access rules, or NULL
+ */
+ virtual const ACL_internal_table_access *lookup(const char *name) const= 0;
+};
+
+/**
+ A registry for per internal schema ACL.
+ An 'internal schema' is a database schema maintained by the
+ server implementation, such as 'performance_schema' and 'INFORMATION_SCHEMA'.
+*/
+class ACL_internal_schema_registry
+{
+public:
+ static void register_schema(const LEX_STRING *name,
+ const ACL_internal_schema_access *access);
+ static const ACL_internal_schema_access *lookup(const char *name);
+};
+
+const ACL_internal_schema_access *
+get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name);
+
+const ACL_internal_table_access *
+get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info,
+ const char *schema_name,
+ const char *table_name);
+
--- mysql-azalea/sql/sql_parse.cc 2009-08-16 22:52:49.000000000 +0400
+++ mysql-azalea-perfschema/sql/sql_parse.cc 2009-07-23 20:04:47.000000000 +0400
@@ -1150,15 +1162,16 @@ bool dispatch_command(enum enum_server_c
table_list.schema_table= schema_table;
}
- uint query_length= (uint) (packet_end - packet); // Don't count end \0
- if (!(fields= (char *) thd->memdup(packet, query_length + 1)))
+ thd->query_length= (uint) (packet_end - packet); // Don't count end \0
+ if (!(thd->query=fields= (char*) thd->memdup(packet,thd->query_length+1)))
break;
- thd->set_query(fields, query_length);
general_log_print(thd, command, "%s %s", table_list.table_name, fields);
if (lower_case_table_names)
my_casedn_str(files_charset_info, table_list.table_name);
- if (check_access(thd,SELECT_ACL,table_list.db,&table_list.grant.privilege,
+ if (check_access(thd,SELECT_ACL,table_list.db,
+ & table_list.grant.privilege,
+ & table_list.grant.m_internal,
0, 0, test(table_list.schema_table)))
break;
if (check_grant(thd, SELECT_ACL, &table_list, TRUE, UINT_MAX, FALSE))
@@ -2100,7 +2114,7 @@ mysql_execute_command(THD *thd)
else
res= check_access(thd,
privileges_requested,
- any_db, 0, 0, 0, 0);
+ any_db, 0, NULL, 0, 0, 0);
if (res)
break;
@@ -2447,7 +2461,9 @@ mysql_execute_command(THD *thd)
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_access(thd, INDEX_ACL, first_table->db,
- &first_table->grant.privilege, 0, 0,
+ & first_table->grant.privilege,
+ & first_table->grant.m_internal,
+ 0, 0,
test(first_table->schema_table)))
goto error;
res= mysql_assign_to_keycache(thd, first_table, &lex->ident);
@@ -2457,7 +2473,9 @@ mysql_execute_command(THD *thd)
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_access(thd, INDEX_ACL, first_table->db,
- &first_table->grant.privilege, 0, 0,
+ & first_table->grant.privilege,
+ & first_table->grant.m_internal,
+ 0, 0,
test(first_table->schema_table)))
goto error;
res = mysql_preload_keys(thd, first_table);
@@ -2820,9 +2838,14 @@ end_with_restore_list:
/* Must be set in the parser */
DBUG_ASSERT(select_lex->db);
if (check_access(thd, priv_needed, first_table->db,
- &first_table->grant.privilege, 0, 0,
+ & first_table->grant.privilege,
+ & first_table->grant.m_internal,
+ 0, 0,
test(first_table->schema_table)) ||
- check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv,0,0,
+ check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,
+ & priv,
+ & first_table->grant.m_internal,
+ 0,0,
is_schema_db(select_lex->db))||
check_merge_table_access(thd, first_table->db,
(TABLE_LIST *)
@@ -2878,9 +2901,13 @@ end_with_restore_list:
for (table= first_table; table; table= table->next_local->next_local)
{
if (check_access(thd, ALTER_ACL | DROP_ACL, table->db,
- &table->grant.privilege,0,0, test(table->schema_table)) ||
+ & table->grant.privilege,
+ & table->grant.m_internal,
+ 0,0, test(table->schema_table)) ||
check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db,
- &table->next_local->grant.privilege, 0, 0,
+ & table->next_local->grant.privilege,
+ & table->next_local->grant.m_internal,
+ 0, 0,
test(table->next_local->schema_table)))
goto error;
TABLE_LIST old_list, new_list;
@@ -2965,7 +2992,9 @@ end_with_restore_list:
SELECT_ACL : SHOW_CREATE_TABLE_ACLS;
if (check_access(thd, check_privs, first_table->db,
- &save_priv, FALSE, FALSE,
+ &save_priv,
+ & first_table->grant.m_internal,
+ FALSE, FALSE,
test(first_table->schema_table)))
goto error;
@@ -3458,7 +3487,7 @@ end_with_restore_list:
goto error;
#else
{
- if (check_access(thd, FILE_ACL, any_db,0,0,0,0))
+ if (check_access(thd, FILE_ACL, any_db, 0, NULL, 0, 0, 0))
goto error;
res= ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_LOGS);
break;
@@ -3685,7 +3714,7 @@ end_with_restore_list:
break;
}
#endif
- if (check_access(thd,CREATE_ACL,lex->name.str, 0, 1, 0,
+ if (check_access(thd,CREATE_ACL,lex->name.str, 0, NULL, 1, 0,
is_schema_db(lex->name.str)))
break;
res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias :
@@ -3715,7 +3744,7 @@ end_with_restore_list:
break;
}
#endif
- if (check_access(thd,DROP_ACL,lex->name.str,0,1,0,
+ if (check_access(thd,DROP_ACL,lex->name.str, 0, NULL, 1, 0,
is_schema_db(lex->name.str)))
break;
if (thd->locked_tables_mode || thd->active_transaction())
@@ -3745,9 +3774,9 @@ end_with_restore_list:
my_error(ER_WRONG_DB_NAME, MYF(0), db->str);
break;
}
- if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, is_schema_db(db->str)) ||
- check_access(thd, DROP_ACL, db->str, 0, 1, 0, is_schema_db(db->str)) ||
- check_access(thd, CREATE_ACL, db->str, 0, 1, 0, is_schema_db(db->str)))
+ if (check_access(thd, ALTER_ACL, db->str, 0, NULL, 1, 0, is_schema_db(db->str))
||
+ check_access(thd, DROP_ACL, db->str, 0, NULL, 1, 0, is_schema_db(db->str))
||
+ check_access(thd, CREATE_ACL, db->str, 0, NULL, 1, 0,
is_schema_db(db->str)))
{
res= 1;
break;
@@ -3790,7 +3819,7 @@ end_with_restore_list:
break;
}
#endif
- if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, is_schema_db(db->str)))
+ if (check_access(thd, ALTER_ACL, db->str, 0, NULL, 1, 0,
is_schema_db(db->str)))
break;
if (thd->locked_tables_mode || thd->active_transaction())
{
@@ -3875,7 +3904,7 @@ end_with_restore_list:
#endif
case SQLCOM_CREATE_FUNCTION: // UDF function
{
- if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0))
+ if (check_access(thd, INSERT_ACL, "mysql", 0, NULL, 1, 0, 0))
break;
#ifdef HAVE_DLOPEN
if (!(res = mysql_create_function(thd, &lex->udf)))
@@ -3889,7 +3918,7 @@ end_with_restore_list:
#ifndef NO_EMBEDDED_ACCESS_CHECKS
case SQLCOM_CREATE_USER:
{
- if (check_access(thd, INSERT_ACL, "mysql", 0, 1, 1, 0) &&
+ if (check_access(thd, INSERT_ACL, "mysql", 0, NULL, 1, 1, 0) &&
check_global_access(thd,CREATE_USER_ACL))
break;
/* Conditionally writes to binlog */
@@ -3899,7 +3928,7 @@ end_with_restore_list:
}
case SQLCOM_DROP_USER:
{
- if (check_access(thd, DELETE_ACL, "mysql", 0, 1, 1, 0) &&
+ if (check_access(thd, DELETE_ACL, "mysql", 0, NULL, 1, 1, 0) &&
check_global_access(thd,CREATE_USER_ACL))
break;
/* Conditionally writes to binlog */
@@ -3909,7 +3938,7 @@ end_with_restore_list:
}
case SQLCOM_RENAME_USER:
{
- if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) &&
+ if (check_access(thd, UPDATE_ACL, "mysql", 0, NULL, 1, 1, 0) &&
check_global_access(thd,CREATE_USER_ACL))
break;
/* Conditionally writes to binlog */
@@ -3919,7 +3948,7 @@ end_with_restore_list:
}
case SQLCOM_REVOKE_ALL:
{
- if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) &&
+ if (check_access(thd, UPDATE_ACL, "mysql", 0, NULL, 1, 1, 0) &&
check_global_access(thd,CREATE_USER_ACL))
break;
/* Conditionally writes to binlog */
@@ -3933,6 +3962,7 @@ end_with_restore_list:
if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL,
first_table ? first_table->db : select_lex->db,
first_table ? &first_table->grant.privilege : 0,
+ first_table ? &first_table->grant.m_internal : NULL,
first_table ? 0 : 1, 0,
first_table ? (bool) first_table->schema_table :
select_lex->db ? is_schema_db(select_lex->db) : 0))
@@ -3962,7 +3992,7 @@ end_with_restore_list:
// TODO: use check_change_password()
if (is_acl_user(user->host.str, user->user.str) &&
user->password.str &&
- check_access(thd, UPDATE_ACL,"mysql",0,1,1,0))
+ check_access(thd, UPDATE_ACL, "mysql", 0, NULL, 1, 1, 0))
{
my_message(ER_PASSWORD_NOT_ALLOWED,
ER(ER_PASSWORD_NOT_ALLOWED), MYF(0));
@@ -4093,7 +4123,7 @@ end_with_restore_list:
goto error;
if ((thd->security_ctx->priv_user &&
!strcmp(thd->security_ctx->priv_user, grant_user->user.str)) ||
- !check_access(thd, SELECT_ACL, "mysql",0,1,0,0))
+ !check_access(thd, SELECT_ACL, "mysql", 0, NULL, 1, 0, 0))
{
res = mysql_show_grants(thd, grant_user);
}
@@ -4205,7 +4235,7 @@ end_with_restore_list:
goto create_sp_error;
}
- if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, 0, 0, 0,
+ if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, 0, NULL, 0, 0,
is_schema_db(lex->sphead->m_db.str)))
goto create_sp_error;
@@ -4490,7 +4520,7 @@ create_sp_error:
lex->spname->m_name.length);
if (udf)
{
- if (check_access(thd, DELETE_ACL, "mysql", 0, 1, 0, 0))
+ if (check_access(thd, DELETE_ACL, "mysql", NULL, 0, 1, 0, 0))
goto error;
if (!(res = mysql_drop_function(thd, &lex->spname->m_name)))
@@ -4966,10 +4996,38 @@ bool check_single_table_access(THD *thd,
db_name= all_tables->db;
if (check_access(thd, privilege, db_name,
- &all_tables->grant.privilege, 0, no_errors,
- test(all_tables->schema_table)))
+ & all_tables->grant.privilege,
+ & all_tables->grant.m_internal,
+ 0, no_errors, test(all_tables->schema_table)))
goto deny;
+ const ACL_internal_table_access *access;
+ access= get_cached_table_access(& all_tables->grant.m_internal,
+ all_tables->get_db_name(),
+ all_tables->get_table_name());
+ if (access)
+ {
+ switch (access->check(thd->lex->sql_command, privilege))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ goto grant;
+ case ACL_INTERNAL_ACCESS_DENIED:
+ if (! no_errors)
+ {
+ char command[128];
+ get_privilege_desc(command, sizeof(command), privilege);
+ thd->raise_error_printf(ER_TABLEACCESS_DENIED_ERROR,
+ command,
+ thd->security_ctx->priv_user,
+ thd->security_ctx->host_or_ip,
+ all_tables->get_table_name());
+ }
+ goto deny;
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ break;
+ }
+ }
+
/* Show only 1 table for check_grant */
if (!(all_tables->belong_to_view &&
(thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) &&
@@ -4978,6 +5036,7 @@ bool check_single_table_access(THD *thd,
check_grant(thd, privilege, all_tables, 0, 1, no_errors))
goto deny;
+grant:
thd->security_ctx= backup_ctx;
return 0;
@@ -5056,7 +5115,8 @@ bool check_one_table_access(THD *thd, ul
bool
check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv,
- bool dont_check_global_grants, bool no_errors, bool schema_db)
+ GRANT_INTERNAL_INFO *grant_internal_info, bool dont_check_global_grants,
+ bool no_errors, bool schema_db)
{
Security_context *sctx= thd->security_ctx;
ulong db_access;
@@ -5091,6 +5151,29 @@ check_access(THD *thd, ulong want_access
DBUG_RETURN(TRUE); /* purecov: tested */
}
+ const char *db_name= db ? db : thd->db;
+ const ACL_internal_schema_access *access;
+ access= get_cached_schema_access(grant_internal_info, db_name);
+ if (access)
+ {
+ switch (access->check(thd->lex->sql_command, want_access, save_priv))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ DBUG_RETURN(FALSE);
+ case ACL_INTERNAL_ACCESS_DENIED:
+ if (! no_errors)
+ {
+ thd->raise_error_printf(ER_DBACCESS_DENIED_ERROR,
+ sctx->priv_user,
+ sctx->priv_host,
+ db_name);
+ }
+ DBUG_RETURN(TRUE);
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ break;
+ }
+ }
+
if (schema_db)
{
/*
@@ -5102,7 +5185,6 @@ check_access(THD *thd, ulong want_access
{
if (!no_errors)
{
- const char *db_name= db ? db : thd->db;
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
sctx->priv_user, sctx->priv_host, db_name);
}
@@ -5238,7 +5320,7 @@ static bool check_show_access(THD *thd,
DBUG_ASSERT(dst_db_name);
if (check_access(thd, SELECT_ACL, dst_db_name,
- &thd->col_access, FALSE, FALSE,
+ &thd->col_access, NULL, FALSE, FALSE,
is_schema_db(dst_db_name)))
return TRUE;
@@ -5263,7 +5345,9 @@ static bool check_show_access(THD *thd,
DBUG_ASSERT(dst_table);
if (check_access(thd, SELECT_ACL, dst_table->db,
- &dst_table->grant.privilege, FALSE, FALSE,
+ &dst_table->grant.privilege,
+ &dst_table->grant.m_internal,
+ FALSE, FALSE,
test(dst_table->schema_table)))
return TRUE; /* Access denied */
@@ -5324,6 +5408,8 @@ check_table_access(THD *thd, ulong requi
uint number)
{
TABLE_LIST *org_tables= tables;
+ const ACL_internal_schema_access *access;
+ ulong dummy;
TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
uint i= 0;
Security_context *sctx= thd->security_ctx, *backup_ctx= thd->security_ctx;
@@ -5380,16 +5466,44 @@ check_table_access(THD *thd, ulong requi
thd->security_ctx= sctx;
if ((sctx->master_access & want_access) == want_access &&
thd->db)
- tables->grant.privilege= want_access;
+ {
+ access= get_cached_schema_access(& tables->grant.m_internal,
tables->get_db_name());
+ if (access)
+ {
+ switch(access->check(thd->lex->sql_command, want_access, & dummy))
+ {
+ case ACL_INTERNAL_ACCESS_GRANTED:
+ continue;
+ case ACL_INTERNAL_ACCESS_DENIED:
+ if (! no_errors)
+ {
+ thd->raise_error_printf(ER_DBACCESS_DENIED_ERROR,
+ sctx->priv_user,
+ sctx->priv_host,
+ tables->get_db_name());
+ }
+ goto deny;
+ case ACL_INTERNAL_ACCESS_CHECK_GRANT:
+ tables->grant.privilege= want_access;
+ break;
+ }
+ }
+ else
+ tables->grant.privilege= want_access;
+ }
else if (tables->db && thd->db && strcmp(tables->db,
thd->db) == 0)
{
if (check_access(thd, want_access, tables->get_db_name(),
- &tables->grant.privilege, 0, no_errors,
+ &tables->grant.privilege,
+ &tables->grant.m_internal,
+ 0, no_errors,
test(tables->schema_table)))
goto deny; // Access denied
}
else if (check_access(thd, want_access, tables->get_db_name(),
- &tables->grant.privilege, 0, no_errors, 0))
+ &tables->grant.privilege,
+ &tables->grant.m_internal,
+ 0, no_errors, 0))
goto deny;
}
thd->security_ctx= backup_ctx;
@@ -5420,7 +5534,9 @@ check_routine_access(THD *thd, ulong wan
*/
if ((thd->security_ctx->master_access & want_access) == want_access)
tables->grant.privilege= want_access;
- else if (check_access(thd,want_access,db,&tables->grant.privilege,
+ else if (check_access(thd,want_access,db,
+ &tables->grant.privilege,
+ &tables->grant.m_internal,
0, no_errors, 0))
return TRUE;
@@ -5451,7 +5567,7 @@ bool check_some_routine_access(THD *thd,
There are no routines in information_schema db. So we can safely
pass zero to last paramter of check_access function
*/
- if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, 0, 1, 0) ||
+ if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, NULL, 0, 1, 0) ||
(save_priv & SHOW_PROC_ACLS))
return FALSE;
return check_routine_level_acl(thd, db, name, is_proc);
@@ -5481,7 +5597,9 @@ bool check_some_access(THD *thd, ulong w
if (access & want_access)
{
if (!check_access(thd, access, table->db,
- &table->grant.privilege, 0, 1,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 1,
test(table->schema_table)) &&
!check_grant(thd, access, table, 0, 1, 1))
DBUG_RETURN(0);
@@ -7213,11 +7331,15 @@ bool multi_update_precheck(THD *thd, TAB
if (table->derived)
table->grant.privilege= SELECT_ACL;
else if ((check_access(thd, UPDATE_ACL, table->db,
- &table->grant.privilege, 0, 1,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 1,
test(table->schema_table)) ||
check_grant(thd, UPDATE_ACL, table, 0, 1, 1)) &&
(check_access(thd, SELECT_ACL, table->db,
- &table->grant.privilege, 0, 0,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 0,
test(table->schema_table)) ||
check_grant(thd, SELECT_ACL, table, 0, 1, 0)))
DBUG_RETURN(TRUE);
@@ -7235,7 +7357,9 @@ bool multi_update_precheck(THD *thd, TAB
if (!table->table_in_first_from_clause)
{
if (check_access(thd, SELECT_ACL, table->db,
- &table->grant.privilege, 0, 0,
+ &table->grant.privilege,
+ &table->grant.m_internal,
+ 0, 0,
test(table->schema_table)) ||
check_grant(thd, SELECT_ACL, table, 0, 1, 0))
DBUG_RETURN(TRUE);
@@ -7517,7 +7641,9 @@ bool create_table_precheck(THD *thd, TAB
(select_lex->item_list.elements ? INSERT_ACL : 0);
if (check_access(thd, want_priv, create_table->db,
- &create_table->grant.privilege, 0, 0,
+ &create_table->grant.privilege,
+ &create_table->grant.m_internal,
+ 0, 0,
test(create_table->schema_table)) ||
check_merge_table_access(thd, create_table->db,
(TABLE_LIST *)
--- mysql-azalea/sql/table.h 2009-08-16 22:52:50.000000000 +0400
+++ mysql-azalea-perfschema/sql/table.h 2009-07-23 20:04:59.000000000 +0400
@@ -29,6 +29,8 @@
class Security_context;
class MDL_request;
class MDL_ticket;
+class ACL_internal_schema_access;
+class ACL_internal_table_access;
/*************************************************************************/
@@ -72,6 +74,25 @@
} ORDER;
/**
+ State information for internal tables grants.
+ This structure is part of the TABLE_LIST, and is updated
+ during the ACL check process.
+ @sa GRANT_INFO
+*/
+struct st_grant_internal_info
+{
+ /** True if the internal lookup by schema name was done. */
+ bool m_schema_lookup_done;
+ /** Cached internal schema access. */
+ const ACL_internal_schema_access *m_schema_access;
+ /** True if the internal lookup by table name was done. */
+ bool m_table_lookup_done;
+ /** Cached internal table access. */
+ const ACL_internal_table_access *m_table_access;
+};
+typedef struct st_grant_internal_info GRANT_INTERNAL_INFO;
+
+/**
@brief The current state of the privilege checking process for the current
user, SQL statement and SQL object.
@@ -132,6 +153,8 @@
check access rights to the underlying tables of a view.
*/
ulong orig_want_privilege;
+ /** The grant state for internal tables. */
+ GRANT_INTERNAL_INFO m_internal;
} GRANT_INFO;
enum tmp_table_type
+++ mysql-azalea-perfschema/storage/perfschema/pfs_engine_table.h +++++++++++
/* Copyright (C) 2008-2009 Sun Microsystems, Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef PFS_ENGINE_TABLE_H
#define PFS_ENGINE_TABLE_H
/**
@file storage/perfschema/pfs_engine_table.h
Performance schema tables (declarations).
*/
class Field;
struct TABLE;
struct PFS_engine_table_share;
extern bool pfs_acl_bootstrap;
/**
@addtogroup Performance_schema_engine
@{
*/
/**
An abstract PERFORMANCE_SCHEMA table.
Every table implemented in the performance schema schema and storage engine
derives from this class.
*/
class PFS_engine_table
{
public:
static const PFS_engine_table_share*
find_engine_table_share(const char *name);
int read_row(TABLE *table, unsigned char *buf, Field **fields);
int update_row(TABLE *table, const unsigned char *old_buf,
unsigned char *new_buf, Field **fields);
/** Fetch the next row in this cursor. */
virtual int rnd_next(void)= 0;
/**
Fetch a row by position.
@param pos position to fetch
*/
virtual int rnd_pos(const void *pos)= 0;
void get_position(void *ref);
void set_position(const void *ref);
/** Destructor. */
virtual ~PFS_engine_table()
{}
protected:
/**
Read the current row values.
@param table Table handle
@param buf row buffer
@param fields Table fields
@param read_all true if all columns are read.
*/
virtual int read_row_values(TABLE *table, unsigned char *buf,
Field **fields, bool read_all)= 0;
/**
Update the current row values.
@param table Table handle
@param old_buf old row buffer
@param new_buf new row buffer
@param fields Table fields
*/
virtual int update_row_values(TABLE *table, const unsigned char *old_buf,
unsigned char *new_buf, Field **fields)= 0;
/**
Constructor.
@param share table share
@param pos address of the m_pos position member
*/
PFS_engine_table(const PFS_engine_table_share *share, void *pos)
: m_share_ptr(share), m_pos_ptr(pos)
{}
/** Table share. */
const PFS_engine_table_share *m_share_ptr;
/** Opaque pointer to the m_pos position of this cursor. */
void *m_pos_ptr;
};
/** Callback to open a table. */
typedef PFS_engine_table* (*pfs_open_table_t)(void);
/** Callback to write a row. */
typedef int (*pfs_write_row_t)(TABLE *table,
unsigned char *buf, Field **fields);
/** Callback to delete all rows. */
typedef int (*pfs_delete_all_rows_t)(void);
/**
A PERFORMANCE_SCHEMA table share.
This data is shared by all the table handles opened on the same table.
*/
struct PFS_engine_table_share
{
static void init_all_locks(void);
static void delete_all_locks(void);
/** Table name. */
LEX_STRING m_name;
/** Table ACL. */
const ACL_internal_table_access *m_acl;
/** Open table function. */
pfs_open_table_t m_open_table;
/** Write row function. */
pfs_write_row_t m_write_row;
/** Delete all rows function. */
pfs_delete_all_rows_t m_delete_all_rows;
/**
Number or records.
This number does not need to be precise,
it is used by the optimizer to decide if the table
has 0, 1, or many records.
*/
ha_rows m_records;
/** Length of the m_pos position structure. */
uint m_ref_length;
/** The lock, stored on behalf of the SQL layer. */
THR_LOCK *m_thr_lock_ptr;
};
/** Adapter for read only PERFORMANCE_SCHEMA tables. */
class PFS_readonly_table : public PFS_engine_table
{
protected:
/**
Constructor.
@param share table share
@param pos address of the m_pos position member
*/
PFS_readonly_table(const PFS_engine_table_share *share, void *pos)
: PFS_engine_table(share, pos)
{}
~PFS_readonly_table()
{}
virtual int update_row_values(TABLE *table, const unsigned char *old_buf,
unsigned char *new_buf, Field **fields);
};
class PFS_readonly_acl : public ACL_internal_table_access
{
public:
PFS_readonly_acl()
{}
~PFS_readonly_acl()
{}
ACL_internal_access_result check(enum_sql_command command,
ulong want_access) const;
};
extern PFS_readonly_acl pfs_readonly_acl;
class PFS_truncatable_acl : public ACL_internal_table_access
{
public:
PFS_truncatable_acl()
{}
~PFS_truncatable_acl()
{}
ACL_internal_access_result check(enum_sql_command command,
ulong want_access) const;
};
extern PFS_truncatable_acl pfs_truncatable_acl;
class PFS_updatable_acl : public ACL_internal_table_access
{
public:
PFS_updatable_acl()
{}
~PFS_updatable_acl()
{}
ACL_internal_access_result check(enum_sql_command command,
ulong want_access) const;
};
extern PFS_updatable_acl pfs_updatable_acl;
/** Position of a cursor, for simple iterations. */
struct PFS_simple_index
{
/** Current row index. */
uint m_index;
PFS_simple_index(uint index)
: m_index(index)
{}
void set_at(const struct PFS_simple_index *other)
{ m_index= other->m_index; }
void set_after(const struct PFS_simple_index *other)
{ m_index= other->m_index + 1; }
void next(void)
{ m_index++; }
};
struct PFS_double_index
{
/** Outer index. */
uint m_index_1;
/** Current index within index_1. */
uint m_index_2;
PFS_double_index(uint index_1, uint index_2)
: m_index_1(index_1), m_index_2(index_2)
{}
void set_at(const struct PFS_double_index *other)
{
m_index_1= other->m_index_1;
m_index_2= other->m_index_2;
}
void set_after(const struct PFS_double_index *other)
{
m_index_1= other->m_index_1;
m_index_2= other->m_index_2 + 1;
}
};
struct PFS_triple_index
{
/** Outer index. */
uint m_index_1;
/** Current index within index_1. */
uint m_index_2;
/** Current index within index_2. */
uint m_index_3;
PFS_triple_index(uint index_1, uint index_2, uint index_3)
: m_index_1(index_1), m_index_2(index_2), m_index_3(index_3)
{}
void set_at(const struct PFS_triple_index *other)
{
m_index_1= other->m_index_1;
m_index_2= other->m_index_2;
m_index_3= other->m_index_3;
}
void set_after(const struct PFS_triple_index *other)
{
m_index_1= other->m_index_1;
m_index_2= other->m_index_2;
m_index_3= other->m_index_3 + 1;
}
};
struct PFS_instrument_view_constants
{
static const uint VIEW_MUTEX= 1;
static const uint VIEW_RWLOCK= 2;
static const uint VIEW_COND= 3;
static const uint VIEW_FILE= 4;
};
struct PFS_object_view_constants
{
static const uint VIEW_TABLE= 1;
static const uint VIEW_EVENT= 2;
static const uint VIEW_PROCEDURE= 3;
static const uint VIEW_FUNCTION= 4;
};
bool pfs_show_status(handlerton *hton, THD *thd,
stat_print_fn *print, enum ha_stat_type stat);
/** @} */
#endif
++++ mysql-azalea-perfschema/storage/perfschema/pfs_engine_table.cc +++++++
/* Copyright (C) 2008-2009 Sun Microsystems, Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/**
@file storage/perfschema/pfs_engine_table.cc
Performance schema tables (implementation).
*/
#include "mysql_priv.h"
#include "pfs_engine_table.h"
#include "table_events_waits.h"
#include "table_setup_consumers.h"
#include "table_setup_instruments.h"
#include "table_setup_objects.h"
#include "table_setup_timers.h"
#include "table_performance_timers.h"
#include "table_processlist.h"
#include "table_events_waits_summary.h"
#include "table_sync_instances.h"
#include "table_file_instances.h"
#include "table_file_summary.h"
/* For show status */
#include "pfs_column_values.h"
#include "pfs_instr.h"
/**
@addtogroup Performance_schema_engine
@{
*/
static PFS_engine_table_share *all_shares[]=
{
& table_events_waits_current::m_share,
& table_events_waits_history::m_share,
& table_events_waits_history_long::m_share,
& table_setup_consumers::m_share,
& table_setup_instruments::m_share,
& table_setup_objects::m_share,
& table_setup_timers::m_share,
& table_performance_timers::m_share,
& table_processlist::m_share,
& table_events_waits_summary_by_thread_by_event_name::m_share,
& table_events_waits_summary_by_event_name::m_share,
& table_events_waits_summary_by_instance::m_share,
& table_file_summary_by_event_name::m_share,
& table_file_summary_by_instance::m_share,
& table_mutex_instances::m_share,
& table_rwlock_instances::m_share,
& table_cond_instances::m_share,
& table_file_instances::m_share,
NULL
};
/** Initialize all the table share locks. */
void PFS_engine_table_share::init_all_locks(void)
{
PFS_engine_table_share **current;
for (current= & all_shares[0]; (*current) != NULL; current++)
thr_lock_init((*current)->m_thr_lock_ptr);
}
/** Delete all the table share locks. */
void PFS_engine_table_share::delete_all_locks(void)
{
PFS_engine_table_share **current;
for (current= & all_shares[0]; (*current) != NULL; current++)
thr_lock_delete((*current)->m_thr_lock_ptr);
}
static int compare_table_names(const char *name1, const char *name2)
{
if (lower_case_table_names)
return strcasecmp(name1, name2);
return strcmp(name1, name2);
}
/**
Find a table share by name.
@param name The table name
@return table share
*/
const PFS_engine_table_share*
PFS_engine_table::find_engine_table_share(const char *name)
{
DBUG_ENTER("PFS_engine_table::find_table_share");
PFS_engine_table_share **current;
uint name_len= strlen(name);
for (current= & all_shares[0]; (*current) != NULL; current++)
{
if ((name_len == (*current)->m_name.length) &&
(compare_table_names(name, (*current)->m_name.str) == 0))
DBUG_RETURN(*current);
}
DBUG_RETURN(NULL);
}
/**
Read a table row.
@param table Table handle
@param buf Row buffer
@param fields Table fields
@return 0 on success
*/
int PFS_engine_table::read_row(TABLE *table,
unsigned char *buf,
Field **fields)
{
my_bitmap_map *org_bitmap;
/* We must read all columns in case a table is opened for update */
bool read_all= !bitmap_is_clear_all(table->write_set);
/* We internally write to Fields to support the read interface */
org_bitmap= dbug_tmp_use_all_columns(table, table->write_set);
int result= read_row_values(table, buf, fields, read_all);
dbug_tmp_restore_column_map(table->write_set, org_bitmap);
return result;
}
/**
Update a table row.
@param table Table handle
@param old_buf old row buffer
@param new_buf new row buffer
@param fields Table fields
@return 0 on success
*/
int PFS_engine_table::update_row(TABLE *table,
const unsigned char *old_buf,
unsigned char *new_buf,
Field **fields)
{
my_bitmap_map *org_bitmap;
/* We internally read from Fields to support the write interface */
org_bitmap= dbug_tmp_use_all_columns(table, table->read_set);
int result= update_row_values(table, old_buf, new_buf, fields);
dbug_tmp_restore_column_map(table->read_set, org_bitmap);
return result;
}
/**
Get the position of the current row.
@param [out] ref position
*/
void PFS_engine_table::get_position(void *ref)
{
memcpy(ref, m_pos_ptr, m_share_ptr->m_ref_length);
}
/**
Set the table cursor at a given position.
@param [in] ref position
*/
void PFS_engine_table::set_position(const void *ref)
{
memcpy(m_pos_ptr, ref, m_share_ptr->m_ref_length);
}
int PFS_readonly_table::update_row_values(TABLE *,
const unsigned char *,
unsigned char *,
Field **)
{
return HA_ERR_WRONG_COMMAND;
}
bool pfs_acl_bootstrap= false;
class PFS_internal_schema_access : public ACL_internal_schema_access
{
public:
PFS_internal_schema_access()
{}
~PFS_internal_schema_access()
{}
ACL_internal_access_result check(enum_sql_command command,
ulong want_access,
ulong *save_priv) const;
const ACL_internal_table_access *lookup(const char *name) const;
};
ACL_internal_access_result
PFS_internal_schema_access::check(enum_sql_command command,
ulong want_access,
ulong *save_priv) const
{
const ulong always_forbidden= /* CREATE_ACL | DROP_ACL | */ REFERENCES_ACL
| INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | EXECUTE_ACL
| CREATE_VIEW_ACL | SHOW_VIEW_ACL | CREATE_PROC_ACL | ALTER_PROC_ACL
| EVENT_ACL | TRIGGER_ACL ;
if (unlikely(want_access & always_forbidden))
{
/*
See how SHOW CREATE TABLE is implemented ...
This is a work around for the broken
SHOW_CREATE_TABLE_ACLS privilege set.
*/
if ((command == SQLCOM_SHOW_CREATE) &&
(want_access == SHOW_CREATE_TABLE_ACLS))
{
*save_priv= want_access;
return ACL_INTERNAL_ACCESS_GRANTED;
}
return ACL_INTERNAL_ACCESS_DENIED;
}
/**
@TODO: CREATE_ACL and DROP_ACL are temporarily allowed,
for the benefit of mysql_upgrade.
This is needed as long as .FRM files are used.
Once the tables are created natively with a TABLE_SHARE directly,
CREATE/DROP of a database/table should be forbidden.
*/
if (want_access & (CREATE_ACL | DROP_ACL))
{
if ((command == SQLCOM_GRANT) ||
(command == SQLCOM_CREATE_DB) ||
(command == SQLCOM_CREATE_TABLE) ||
(command == SQLCOM_DROP_DB) ||
(command == SQLCOM_DROP_TABLE) ||
(command == SQLCOM_TRUNCATE))
{
/*
See PFS_truncatable_acl::check().
TRUNCATE may be allowed or denied, it depends on the target table,
so the caller needs to look at the nested check.
*/
return ACL_INTERNAL_ACCESS_CHECK_GRANT;
}
return ACL_INTERNAL_ACCESS_DENIED;
}
/*
Proceed with regular grant tables,
to give administrative control to the DBA.
*/
return ACL_INTERNAL_ACCESS_CHECK_GRANT;
}
const ACL_internal_table_access *
PFS_internal_schema_access::lookup(const char *name) const
{
const PFS_engine_table_share* share;
share= PFS_engine_table::find_engine_table_share(name);
if (share)
return share->m_acl;
return NULL;
}
PFS_internal_schema_access pfs_internal_access;
void initialize_performance_schema_acl(bool bootstrap)
{
/*
ACL is always enforced, even if the performance schema
is not enabled (the tables are still visible).
*/
pfs_acl_bootstrap= bootstrap;
if (! pfs_acl_bootstrap)
{
ACL_internal_schema_registry::register_schema(& PERFORMANCE_SCHEMA_str,
& pfs_internal_access);
}
}
PFS_readonly_acl pfs_readonly_acl;
ACL_internal_access_result
PFS_readonly_acl::check(enum_sql_command command, ulong want_access) const
{
const ulong always_forbidden= INSERT_ACL | UPDATE_ACL | DELETE_ACL
| /* CREATE_ACL | DROP_ACL | */ REFERENCES_ACL | INDEX_ACL | ALTER_ACL
| CREATE_VIEW_ACL | SHOW_VIEW_ACL | TRIGGER_ACL | LOCK_TABLES_ACL;
if (unlikely(want_access & always_forbidden))
return ACL_INTERNAL_ACCESS_DENIED;
if ((command == SQLCOM_TRUNCATE) && (want_access & DROP_ACL))
return ACL_INTERNAL_ACCESS_DENIED; /* because DROP_ACL is allowed */
return ACL_INTERNAL_ACCESS_CHECK_GRANT;
}
PFS_truncatable_acl pfs_truncatable_acl;
ACL_internal_access_result
PFS_truncatable_acl::check(enum_sql_command command, ulong want_access) const
{
const ulong always_forbidden= INSERT_ACL | UPDATE_ACL | DELETE_ACL
| /* CREATE_ACL | DROP_ACL | */ REFERENCES_ACL | INDEX_ACL | ALTER_ACL
| CREATE_VIEW_ACL | SHOW_VIEW_ACL | TRIGGER_ACL | LOCK_TABLES_ACL;
if (unlikely(want_access & always_forbidden))
{
/*
The commands DROP TABLE and TRUNCATE TABLE use the same privilege.
DROP is forbidden, but TRUNCATE is allowed in the performance schema,
and has a special semantic (to clear the data).
*/
if ((command == SQLCOM_TRUNCATE) && (want_access & DROP_ACL))
return ACL_INTERNAL_ACCESS_GRANTED; /* dead code for now */
return ACL_INTERNAL_ACCESS_DENIED;
}
return ACL_INTERNAL_ACCESS_CHECK_GRANT;
}
PFS_updatable_acl pfs_updatable_acl;
ACL_internal_access_result
PFS_updatable_acl::check(enum_sql_command command, ulong want_access) const
{
const ulong always_forbidden= INSERT_ACL | DELETE_ACL
| /* CREATE_ACL | DROP_ACL | */ REFERENCES_ACL | INDEX_ACL | ALTER_ACL
| CREATE_VIEW_ACL | SHOW_VIEW_ACL | TRIGGER_ACL;
if (unlikely(want_access & always_forbidden))
return ACL_INTERNAL_ACCESS_DENIED;
if ((command == SQLCOM_TRUNCATE) && (want_access & DROP_ACL))
return ACL_INTERNAL_ACCESS_DENIED; /* because DROP_ACL is allowed */
return ACL_INTERNAL_ACCESS_CHECK_GRANT;
}
/**
SHOW ENGINE PERFORMANCE_SCHEMA STATUS.
@param hton Storage engine handler
@param thd Current thread
@param print Print function
@param stat status to show
*/
bool pfs_show_status(handlerton *, THD *thd,
stat_print_fn *print, enum ha_stat_type stat)
{
char buf[1024];
uint buflen;
const char *name;
int i;
uint size;
DBUG_ENTER("pfs_show_status");
/*
Note about naming conventions:
- Internal buffers exposed as a table in the performance schema are named
after the table, as in 'EVENTS_WAITS_CURRENT'
- Internal buffers not exposed by a table are named with parenthesis,
as in '(PFS_MUTEX_INFO)'.
*/
if (stat != HA_ENGINE_STATUS)
DBUG_RETURN(false);
uint total_memory= 0;
uint total_lost= 0;
for (i=0; /* empty */; i++)
{
switch (i){
case 0:
name= "EVENTS_WAITS_CURRENT.ROW_SIZE";
size= sizeof(PFS_wait_locker);
break;
case 1:
name= "EVENTS_WAITS_CURRENT.ROW_COUNT";
size= LOCKER_STACK_SIZE * thread_max;
break;
case 2:
name= "EVENTS_WAITS_HISTORY.ROW_SIZE";
size= sizeof(PFS_events_waits);
break;
case 3:
name= "EVENTS_WAITS_HISTORY.ROW_COUNT";
size= events_waits_history_per_thread * thread_max;
break;
case 4:
name= "EVENTS_WAITS_HISTORY.MEMORY";
size= events_waits_history_per_thread * thread_max
* sizeof(PFS_events_waits);
total_memory+= size;
break;
case 5:
name= "EVENTS_WAITS_HISTORY_LONG.ROW_SIZE";
size= sizeof(PFS_events_waits);
break;
case 6:
name= "EVENTS_WAITS_HISTORY_LONG.ROW_COUNT";
size= events_waits_history_long_size;
break;
case 7:
name= "EVENTS_WAITS_HISTORY_LONG.MEMORY";
size= events_waits_history_long_size * sizeof(PFS_events_waits);
total_memory+= size;
break;
case 8:
name= "(PFS_MUTEX_CLASS).ROW_SIZE";
size= sizeof(PFS_mutex_class);
break;
case 9:
name= "(PFS_MUTEX_CLASS).ROW_COUNT";
size= mutex_class_max;
break;
case 10:
name= "(PFS_MUTEX_CLASS).ROW_LOST";
size= mutex_class_lost;
total_lost+= size;
break;
case 11:
name= "(PFS_MUTEX_CLASS).MEMORY";
size= mutex_class_max * sizeof(PFS_mutex_class);
total_memory+= size;
break;
case 12:
name= "(PFS_RWLOCK_CLASS).ROW_SIZE";
size= sizeof(PFS_rwlock_class);
break;
case 13:
name= "(PFS_RWLOCK_CLASS).ROW_COUNT";
size= rwlock_class_max;
break;
case 14:
name= "(PFS_RWLOCK_CLASS).ROW_LOST";
size= rwlock_class_lost;
total_lost+= size;
break;
case 15:
name= "(PFS_RWLOCK_CLASS).MEMORY";
size= rwlock_class_max * sizeof(PFS_rwlock_class);
total_memory+= size;
break;
case 16:
name= "(PFS_COND_CLASS).ROW_SIZE";
size= sizeof(PFS_cond_class);
break;
case 17:
name= "(PFS_COND_CLASS).ROW_COUNT";
size= cond_class_max;
break;
case 18:
name= "(PFS_COND_CLASS).ROW_LOST";
size= cond_class_lost;
total_lost+= size;
break;
case 19:
name= "(PFS_COND_CLASS).MEMORY";
size= cond_class_max * sizeof(PFS_cond_class);
total_memory+= size;
break;
case 20:
name= "(PFS_THREAD_CLASS).ROW_SIZE";
size= sizeof(PFS_thread_class);
break;
case 21:
name= "(PFS_THREAD_CLASS).ROW_COUNT";
size= thread_class_max;
break;
case 22:
name= "(PFS_THREAD_CLASS).ROW_LOST";
size= thread_class_lost;
total_lost+= size;
break;
case 23:
name= "(PFS_THREAD_CLASS).MEMORY";
size= thread_class_max * sizeof(PFS_thread_class);
total_memory+= size;
break;
case 24:
name= "(PFS_FILE_CLASS).ROW_SIZE";
size= sizeof(PFS_file_class);
break;
case 25:
name= "(PFS_FILE_CLASS).ROW_COUNT";
size= file_class_max;
break;
case 26:
name= "(PFS_FILE_CLASS).ROW_LOST";
size= file_class_lost;
total_lost+= size;
break;
case 27:
name= "(PFS_FILE_CLASS).MEMORY";
size= file_class_max * sizeof(PFS_file_class);
total_memory+= size;
break;
case 28:
name= "(PFS_MUTEX).ROW_SIZE";
size= sizeof(PFS_mutex);
break;
case 29:
name= "(PFS_MUTEX).ROW_COUNT";
size= mutex_max;
break;
case 30:
name= "(PFS_MUTEX).ROW_LOST";
size= mutex_lost;
total_lost+= size;
break;
case 31:
name= "(PFS_MUTEX).MEMORY";
size= mutex_max * sizeof(PFS_mutex);
total_memory+= size;
break;
case 32:
name= "(PFS_RWLOCK).ROW_SIZE";
size= sizeof(PFS_rwlock);
break;
case 33:
name= "(PFS_RWLOCK).ROW_COUNT";
size= rwlock_max;
break;
case 34:
name= "(PFS_RWLOCK).ROW_LOST";
size= rwlock_lost;
total_lost+= size;
break;
case 35:
name= "(PFS_RWLOCK).MEMORY";
size= rwlock_max * sizeof(PFS_rwlock);
total_memory+= size;
break;
case 36:
name= "(PFS_COND).ROW_SIZE";
size= sizeof(PFS_cond);
break;
case 37:
name= "(PFS_COND).ROW_COUNT";
size= cond_max;
break;
case 38:
name= "(PFS_COND).ROW_LOST";
size= cond_lost;
total_lost+= size;
break;
case 39:
name= "(PFS_COND).MEMORY";
size= cond_max * sizeof(PFS_cond);
total_memory+= size;
break;
case 40:
name= "(PFS_THREAD).ROW_SIZE";
size= sizeof(PFS_thread);
break;
case 41:
name= "(PFS_THREAD).ROW_COUNT";
size= thread_max;
break;
case 42:
name= "(PFS_THREAD).ROW_LOST";
size= thread_lost;
total_lost+= size;
break;
case 43:
name= "(PFS_THREAD).MEMORY";
size= thread_max * sizeof(PFS_thread);
total_memory+= size;
break;
case 44:
name= "(PFS_FILE).ROW_SIZE";
size= sizeof(PFS_file);
break;
case 45:
name= "(PFS_FILE).ROW_COUNT";
size= file_max;
break;
case 46:
name= "(PFS_FILE).ROW_LOST";
size= file_lost;
total_lost+= size;
break;
case 47:
name= "(PFS_FILE).MEMORY";
size= file_max * sizeof(PFS_file);
total_memory+= size;
break;
case 48:
name= "(PFS_FILE_HANDLE).ROW_SIZE";
size= sizeof(PFS_file*);
break;
case 49:
name= "(PFS_FILE_HANDLE).ROW_COUNT";
size= file_handle_max;
break;
case 50:
name= "(PFS_FILE_HANDLE).ROW_LOST";
size= file_handle_lost;
total_lost+= size;
break;
case 51:
name= "(PFS_FILE_HANDLE).MEMORY";
size= file_handle_max * sizeof(PFS_file*);
break;
case 52:
name= "EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME.ROW_SIZE";
size= sizeof(PFS_single_stat_chain);
break;
case 53:
name= "EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME.ROW_COUNT";
size= thread_max * instr_class_per_thread;
break;
case 54:
name= "EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME.MEMORY";
size= thread_max * instr_class_per_thread * sizeof(PFS_single_stat_chain);
total_memory+= size;
break;
case 55:
name= "(LOCKER_STACK_SIZE).ROW_LOST";
size= locker_lost;
total_lost+= size;
break;
/*
These two cases must be last,
for aggregation in total_memory and total_lost.
*/
case 56:
name= "PERFORMANCE_SCHEMA.MEMORY";
size= total_memory;
break;
case 57:
name= "PERFORMANCE_SCHEMA.LOST";
size= total_lost;
break;
default:
goto end;
break;
}
buflen= int10_to_str(size, buf, 10) - buf;
if (print(thd,
PERFORMANCE_SCHEMA_str.str, PERFORMANCE_SCHEMA_str.length,
name, strlen(name),
buf, buflen))
DBUG_RETURN(true);
}
end:
DBUG_RETURN(false);
}
/** @} */
--
Dmitry Lenev, Software Developer
MySQL AB, www.mysql.com
Are you MySQL certified? http://www.mysql.com/certification