1786 lines
50 KiB
C++
1786 lines
50 KiB
C++
/* Copyright (C) 1979-1997 TcX AB & Monty Program KB & Detron HB
|
|
|
|
This software is distributed with NO WARRANTY OF ANY KIND. No author or
|
|
distributor accepts any responsibility for the consequences of using it, or
|
|
for whether it serves any particular purpose or works at all, unless he or
|
|
she says so in writing. Refer to the Free Public License (the "License")
|
|
for full details.
|
|
|
|
Every copy of this file must include a copy of the License, normally in a
|
|
plain ASCII text file named PUBLIC. The License grants you the right to
|
|
copy, modify and redistribute this file, but only under certain conditions
|
|
described in the License. Among other things, the License requires that
|
|
the copyright notice and this notice be preserved on all copies. */
|
|
|
|
#include "mysql_priv.h"
|
|
#include "sql_acl.h"
|
|
#include <m_ctype.h>
|
|
#include <thr_alarm.h>
|
|
|
|
extern int yyparse(void);
|
|
extern "C" pthread_mutex_t THR_LOCK_keycache;
|
|
|
|
static bool check_table_access(THD *thd,uint want_access,TABLE_LIST *tables);
|
|
static bool check_dup(THD *thd,char *db,char *name,TABLE_LIST *tables);
|
|
static void mysql_init_query(THD *thd);
|
|
static void remove_escape(char *name);
|
|
static void kill_one_thread(THD *thd, ulong thread);
|
|
static void refresh_status(void);
|
|
|
|
static char *any_db="*any*"; // Special symbol for check_access
|
|
|
|
char *command_name[]={
|
|
"Sleep", "Quit", "Init DB", "Query", "Field List", "Create DB",
|
|
"Drop DB", "Refresh", "Shutdown", "Statistics", "Processes",
|
|
"Connect","Kill","Debug","Ping",
|
|
};
|
|
|
|
#ifdef __WIN32__
|
|
static void test_signal(int sig_ptr)
|
|
{
|
|
#ifndef DBUG_OFF
|
|
MessageBox(NULL,"Test signal","DBUG",MB_OK);
|
|
#endif
|
|
}
|
|
static void init_signals(void)
|
|
{
|
|
int signals[7] = {SIGINT,SIGILL,SIGFPE,SIGSEGV,SIGTERM,SIGBREAK,SIGABRT } ;
|
|
for(int i=0 ; i < 7 ; i++)
|
|
signal( signals[i], test_signal) ;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
** check connnetion and get priviliges
|
|
** returns 0 on ok.
|
|
*/
|
|
|
|
static int
|
|
check_connections(THD *thd)
|
|
{
|
|
uint connect_errors=0;
|
|
NET *net= &thd->net;
|
|
/*
|
|
** store the connection details
|
|
*/
|
|
#ifdef WIN32
|
|
if ( net->nettype == NET_TYPE_NAMEDPIPE )
|
|
{
|
|
DBUG_PRINT( "general", ("New connection received on named pipe") );
|
|
/* host is unknown */
|
|
thd->host=my_strdup("localhost",MYF(0));
|
|
thd->ip = 0;
|
|
bzero( (char*) &thd->local, sizeof(struct sockaddr) );
|
|
bzero( (char*) &thd->remote, sizeof(struct sockaddr) );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
DBUG_PRINT("general",("New connection received on %d", net->fd));
|
|
if (!thd->host) // If TCP/IP connection
|
|
{
|
|
size_socket addrLen= sizeof(struct sockaddr);
|
|
/* check for win32 */
|
|
if (getpeername(net->fd, (struct sockaddr *) &thd->remote, &addrLen))
|
|
{
|
|
return (ER_BAD_HOST_ERROR);
|
|
}
|
|
thd->ip=my_strdup(inet_ntoa(thd->remote.sin_addr),MYF(0));
|
|
#if !defined(HAVE_SYS_UN_H) || defined(HAVE_mit_thread)
|
|
/* Fast local hostname resolve for Win32 */
|
|
if (!strcmp(thd->ip,"127.0.0.1"))
|
|
thd->host=my_strdup("localhost",MYF(0));
|
|
else
|
|
#endif
|
|
if (specialflag & SPECIAL_NO_RESOLVE)
|
|
thd->host=0;
|
|
else
|
|
{
|
|
thd->host=ip_to_hostname(&thd->remote.sin_addr,&connect_errors);
|
|
if (connect_errors > max_connect_errors)
|
|
return(ER_HOST_IS_BLOCKED);
|
|
}
|
|
DBUG_PRINT("general",("Host: %s ip: %s",
|
|
thd->host ? thd->host : "unknown host",
|
|
thd->ip ? thd->ip : "unknown ip"));
|
|
if (acl_check_host(thd->host,thd->ip))
|
|
return(ER_HOST_NOT_PRIVILEGED);
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("general",("Host: localhost"));
|
|
thd->ip=0;
|
|
bzero((char*) &thd->local, sizeof(struct sockaddr));
|
|
bzero((char*) &thd->remote,sizeof(struct sockaddr));
|
|
}
|
|
|
|
uint opt=1;
|
|
VOID(setsockopt(net->fd,SOL_SOCKET,SO_KEEPALIVE, (char *) &opt,
|
|
sizeof(opt)));
|
|
}
|
|
{
|
|
char buff[60],*end;
|
|
uint pkt_len;
|
|
LINT_INIT(pkt_len);
|
|
|
|
end=strmov(buff,server_version)+1;
|
|
int4store((uchar*) end,thd->thread_id);
|
|
end+=4;
|
|
memcpy(end,thd->scramble,9);
|
|
end+=9;
|
|
#ifdef HAVE_COMPRESS
|
|
int2store(end,CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS);
|
|
#else
|
|
int2store(end,CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB);
|
|
#endif
|
|
end+=2;
|
|
if (net_write_command(net,protocol_version, buff,
|
|
(uint) (end-buff)) ||
|
|
(pkt_len=my_net_read(net)) == packet_error || pkt_len < 6)
|
|
{
|
|
inc_host_errors(&thd->remote.sin_addr);
|
|
return(ER_HANDSHAKE_ERROR);
|
|
}
|
|
}
|
|
if (connect_errors)
|
|
reset_host_errors(&thd->remote.sin_addr);
|
|
if (thd->packet.alloc(net_buffer_length))
|
|
return(ER_OUTOFMEMORY);
|
|
|
|
thd->client_capabilities=uint2korr(net->read_pos);
|
|
thd->max_packet_length=uint3korr(net->read_pos+2);
|
|
if (!(thd->user = my_strdup((char*) net->read_pos+5, MYF(MY_FAE))))
|
|
return(ER_OUTOFMEMORY);
|
|
char *passwd= strend((char*) net->read_pos+5)+1;
|
|
thd->master_access=acl_getroot(thd->host, thd->ip, thd->user,
|
|
passwd, thd->scramble, &thd->priv_user,
|
|
protocol_version == 9 ||
|
|
!(thd->client_capabilities &
|
|
CLIENT_LONG_PASSWORD));
|
|
thd->password=test(passwd[0]);
|
|
if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB)
|
|
{
|
|
if (!(thd->db=my_strdup(strend(passwd)+1,MYF(MY_FAE))))
|
|
return(ER_OUTOFMEMORY);
|
|
}
|
|
|
|
DBUG_PRINT("general",
|
|
("Capabilities: %d packet_length: %d Host: %s User: %s Using password: %s Access: %u db: %s",
|
|
thd->client_capabilities, thd->max_packet_length,
|
|
thd->host ? thd->host : thd->ip, thd->priv_user,
|
|
passwd[0] ? "yes": "no",
|
|
thd->master_access, thd->db ? thd->db : ""));
|
|
if (thd->master_access & NO_ACCESS)
|
|
{
|
|
net_printf(net, ER_ACCESS_DENIED_ERROR,
|
|
thd->user,
|
|
thd->host ? thd->host : thd->ip,
|
|
passwd[0] ? ER(ER_YES) : ER(ER_NO));
|
|
mysql_log.write(COM_CONNECT,ER(ER_ACCESS_DENIED_ERROR),
|
|
thd->user,
|
|
thd->host ? thd->host : thd->ip,
|
|
passwd[0] ? ER(ER_YES) : ER(ER_NO));
|
|
return(-1); // Error already given
|
|
}
|
|
if (thread_count >= max_connections && !(thd->master_access & PROCESS_ACL))
|
|
return(ER_CON_COUNT_ERROR); // Too many connections
|
|
|
|
net->timeout=NET_READ_TIMEOUT;
|
|
mysql_log.write(COM_CONNECT,
|
|
(const my_string) (thd->priv_user == thd->user ?
|
|
"%s@%s on %s" :
|
|
"%s@%s as anonymous on %s"),
|
|
thd->user,
|
|
thd->host ? thd->host : thd->ip,
|
|
thd->db ? thd->db : (char*) "");
|
|
return 0;
|
|
}
|
|
|
|
|
|
pthread_handler_decl(handle_one_connection,arg)
|
|
{
|
|
THD *thd=(THD*) arg;
|
|
NET *net= &thd->net;
|
|
#ifndef __WIN32__ /* Win32 calls this in pthread_create */
|
|
if (my_thread_init())
|
|
{
|
|
close_connection(&thd->net,ER_OUT_OF_RESOURCES);
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count));
|
|
aborted_connects++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
end_thread(0);
|
|
return 0;
|
|
}
|
|
#endif
|
|
thd->thread_stack= (char*) &thd;
|
|
|
|
#ifdef __WIN32__
|
|
init_signals(); // IRENA; testing ?
|
|
#endif
|
|
pthread_detach_this_thread();
|
|
if (init_thr_lock() ||
|
|
my_pthread_setspecific_ptr(THR_THD, thd) ||
|
|
my_pthread_setspecific_ptr(THR_MALLOC, &thd->alloc) ||
|
|
my_pthread_setspecific_ptr(THR_NET, &thd->net))
|
|
{
|
|
close_connection(&thd->net,ER_OUT_OF_RESOURCES);
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count));
|
|
aborted_connects++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
end_thread(0);
|
|
return 0;
|
|
}
|
|
thd->mysys_var=my_thread_var;
|
|
thd->dbug_thread_id=my_thread_id();
|
|
#ifndef __WIN32__
|
|
sigset_t set;
|
|
VOID(sigemptyset(&set)); // Get mask in use
|
|
VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals));
|
|
#endif
|
|
|
|
int error;
|
|
if ((error=check_connections(thd)))
|
|
{ // Wrong permissions
|
|
if (error > 0)
|
|
net_printf(net,error,thd->host ? thd->host : thd->ip);
|
|
#ifdef __NT__
|
|
if (net->nettype == NET_TYPE_NAMEDPIPE)
|
|
sleep(1); /* must wait after eof() */
|
|
#endif
|
|
close_connection(net,0);
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count));
|
|
aborted_connects++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
end_thread(0);
|
|
return(0);
|
|
}
|
|
|
|
thd->alloc.free=thd->alloc.used=0;
|
|
if (max_join_size == (ulong) ~0L)
|
|
thd->options |= OPTION_BIG_SELECTS;
|
|
|
|
if (thd->db)
|
|
{ // If login with db
|
|
char *db_name=thd->db;
|
|
thd->db=0;
|
|
if (mysql_change_db(thd,db_name))
|
|
thd->killed=1; // Could not change db
|
|
my_free(db_name,MYF(0));
|
|
}
|
|
else
|
|
send_ok(net); // Ready to handle questions
|
|
|
|
if (thd->client_capabilities & CLIENT_COMPRESS)
|
|
net->compress=1; // Use compression
|
|
|
|
thd->proc_info=0;
|
|
thd->version=refresh_version;
|
|
while (!net->error && net->fd != INVALID_SOCKET && !thd->killed)
|
|
{
|
|
if (do_command(thd))
|
|
break;
|
|
}
|
|
if (net->error && net->fd != INVALID_SOCKET)
|
|
{
|
|
sql_print_error("Aborted connection %ld to db: '%s' user: '%s'",
|
|
thd->thread_id,(thd->db ? thd->db : "unconnected"),
|
|
thd->user);
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count));
|
|
aborted_threads++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
}
|
|
close_connection(net);
|
|
end_thread(0);
|
|
return(0); /* purecov: deadcode */
|
|
}
|
|
|
|
|
|
int handle_bootstrap(THD *thd)
|
|
{
|
|
thd->thread_stack= (char*) &thd;
|
|
|
|
if (init_thr_lock() ||
|
|
my_pthread_setspecific_ptr(THR_THD, thd) ||
|
|
my_pthread_setspecific_ptr(THR_MALLOC, &thd->alloc) ||
|
|
my_pthread_setspecific_ptr(THR_NET, &thd->net))
|
|
{
|
|
close_connection(&thd->net,ER_OUT_OF_RESOURCES);
|
|
end_thread(0);
|
|
return 0;
|
|
}
|
|
thd->mysys_var=my_thread_var;
|
|
thd->dbug_thread_id=my_thread_id();
|
|
#ifndef __WIN32__
|
|
sigset_t set;
|
|
VOID(sigemptyset(&set)); // Get mask in use
|
|
VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals));
|
|
#endif
|
|
|
|
thd->alloc.free=thd->alloc.used=0;
|
|
if (max_join_size == (ulong) ~0L)
|
|
thd->options |= OPTION_BIG_SELECTS;
|
|
|
|
thd->proc_info=0;
|
|
thd->version=refresh_version;
|
|
|
|
while (fgets((char*) thd->net.buff, thd->net.max_packet, stdin))
|
|
{
|
|
uint length=strlen((char*) thd->net.buff);
|
|
while (length && isspace(thd->net.buff[length-1]))
|
|
length--;
|
|
thd->net.buff[length]=0;
|
|
init_sql_alloc(&thd->alloc,8192);
|
|
thd->current_tablenr=0;
|
|
thd->tmp_table=0;
|
|
thd->query=sql_strdup((gptr) thd->net.buff);
|
|
thd->query_id=query_id++;
|
|
mysql_parse(thd,thd->query,length);
|
|
close_thread_tables(thd); // Free tables
|
|
if (thd->fatal_error)
|
|
{
|
|
return(-1);
|
|
}
|
|
free_root(&thd->alloc);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline void free_items(THD *thd)
|
|
{
|
|
/* This works because items are allocated with sql_alloc() */
|
|
for (Item *item=thd->free_list ; item ; item=item->next)
|
|
delete item;
|
|
}
|
|
|
|
/* Execute one command from socket (query or simple command) */
|
|
|
|
bool do_command(THD *thd)
|
|
{
|
|
char *packet;
|
|
uint old_timeout,packet_length;
|
|
bool error=0,tables_used=0;
|
|
NET *net;
|
|
enum enum_server_command command;
|
|
DBUG_ENTER("do_command");
|
|
|
|
init_sql_alloc(&thd->alloc,8192);
|
|
net= &thd->net;
|
|
thd->current_tablenr=0;
|
|
thd->tmp_table=0;
|
|
|
|
packet=0;
|
|
old_timeout=net->timeout;
|
|
net->timeout=net_wait_timeout; /* Wait max for 8 hours */
|
|
net->last_error[0]=0; // Clear error message
|
|
|
|
net_new_transaction(net);
|
|
if ((packet_length=my_net_read(net)) == packet_error)
|
|
{
|
|
command = COM_QUIT;
|
|
DBUG_PRINT("general",("Got error reading command from socket %d",net->fd));
|
|
}
|
|
else
|
|
{
|
|
packet=(char*) net->read_pos;
|
|
command = (enum enum_server_command) (uchar) packet[0];
|
|
DBUG_PRINT("general",("Command on socket %d = %d (%s)",
|
|
net->fd, command, command_name[command]));
|
|
}
|
|
net->timeout=old_timeout; /* Timeout */
|
|
thd->command=command;
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count));
|
|
thd->query_id=query_id;
|
|
if (command != COM_STATISTICS)
|
|
query_id++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
thd->set_time();
|
|
switch(command) {
|
|
case COM_INIT_DB:
|
|
if (!mysql_change_db(thd,packet+1))
|
|
mysql_log.write(command,"%s",thd->db);
|
|
break;
|
|
case COM_QUERY:
|
|
thd->query=sql_memdup((gptr) (packet+1),packet_length);
|
|
tables_used=1;
|
|
if (!(specialflag & SPECIAL_NO_PRIOR))
|
|
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
|
|
#ifdef FIX_LOG // Done in client
|
|
if (opt_log)
|
|
{ // Remove linefeed for log
|
|
for (char *pos=packet+1; pos=strchr(pos,'\n'); pos++)
|
|
*pos=' ';
|
|
}
|
|
#endif
|
|
mysql_log.write(command,"%s",packet+1);
|
|
DBUG_PRINT("query",("%s",packet+1));
|
|
mysql_parse(thd,thd->query,packet_length-1);
|
|
if (!(specialflag & SPECIAL_NO_PRIOR))
|
|
my_pthread_setprio(pthread_self(),WAIT_PRIOR);
|
|
DBUG_PRINT("info",("query ready"));
|
|
break;
|
|
case COM_FIELD_LIST: // This isn't actually neaded
|
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
|
send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
|
break;
|
|
#else
|
|
{
|
|
tables_used=1;
|
|
char *fields;
|
|
TABLE_LIST table_list;
|
|
bzero((char*) &table_list,sizeof(table_list));
|
|
if (!(table_list.db=thd->db))
|
|
{
|
|
send_error(net,ER_NO_DB_ERROR);
|
|
break;
|
|
}
|
|
thd->free_list=0;
|
|
table_list.name=table_list.real_name=sql_strdup(packet+1);
|
|
thd->query=fields=sql_strdup(strend(packet+1)+1);
|
|
mysql_log.write(command,"%s %s",table_list.real_name,fields);
|
|
remove_escape(table_list.real_name); // This can't have wildcards
|
|
|
|
if (check_access(thd,SELECT_ACL,table_list.db,&thd->col_access))
|
|
break;
|
|
table_list.grant.privilege=thd->col_access;
|
|
if (grant_option && check_grant(thd,SELECT_ACL,&table_list,2))
|
|
break;
|
|
mysqld_list_fields(thd,&table_list,fields);
|
|
free_items(thd);
|
|
break;
|
|
}
|
|
#endif
|
|
case COM_QUIT:
|
|
mysql_log.write(command,NullS);
|
|
net->error=0; // Don't give 'abort' message
|
|
error=TRUE; // End server
|
|
break;
|
|
|
|
case COM_CREATE_DB:
|
|
{
|
|
char *db=sql_strdup(packet+1);
|
|
if (check_access(thd,CREATE_ACL,db,0,1))
|
|
break;
|
|
mysql_log.write(command,packet+1);
|
|
mysql_create_db(thd,db);
|
|
break;
|
|
}
|
|
case COM_DROP_DB:
|
|
{
|
|
char *db=sql_strdup(packet+1);
|
|
if (check_access(thd,DROP_ACL,db,0,1))
|
|
break;
|
|
mysql_log.write(command,db);
|
|
mysql_rm_db(thd,db,0);
|
|
break;
|
|
}
|
|
case COM_REFRESH:
|
|
{
|
|
uint options=(uchar) packet[1];
|
|
if (check_access(thd,RELOAD_ACL,any_db))
|
|
break;
|
|
mysql_log.write(command,NullS);
|
|
if (reload_acl_and_cache(options))
|
|
send_error(net,0);
|
|
else
|
|
send_eof(net);
|
|
break;
|
|
}
|
|
case COM_SHUTDOWN:
|
|
if (check_access(thd,SHUTDOWN_ACL,any_db))
|
|
break; /* purecov: inspected */
|
|
DBUG_PRINT("quit",("Got shutdown command"));
|
|
mysql_log.write(command,NullS);
|
|
send_eof(net);
|
|
#ifdef __WIN32__
|
|
sleep(1); /* must wait after eof() */
|
|
#endif
|
|
send_eof(net);
|
|
close_connection(net);
|
|
close_thread_tables(thd); /* Free before kill */
|
|
free_root(&thd->alloc);
|
|
#ifdef __WIN32__
|
|
{
|
|
extern HANDLE hEventShutdown;
|
|
if (!SetEvent(hEventShutdown))
|
|
{
|
|
DBUG_PRINT("error",("Got error: %ld from SetEvent",GetLastError()));
|
|
}
|
|
// or:
|
|
// HANDLE hEvent=OpenEvent(0, FALSE, "MySqlShutdown");
|
|
// SetEvent(hEventShutdown);
|
|
// CloseHandle(hEvent);
|
|
}
|
|
#else
|
|
if (pthread_kill(signal_thread,SIGTERM)) /* End everything nicely */
|
|
{
|
|
DBUG_PRINT("error",("Got error %d from pthread_kill",errno)); /* purecov: inspected */
|
|
}
|
|
#endif
|
|
DBUG_PRINT("quit",("After pthread_kill"));
|
|
error=TRUE;
|
|
break;
|
|
|
|
case COM_STATISTICS:
|
|
{
|
|
mysql_log.write(command,NullS);
|
|
char buff[200];
|
|
sprintf((char*) buff,
|
|
"Uptime: %ld Threads: %d Questions: %ld Slow queries: %d Opens: %ld Flush tables: %ld Open tables: %d",
|
|
(ulong) (time((time_t*) 0) - start_time),
|
|
(int) thread_count,thd->query_id,long_query_count,
|
|
opened_tables,refresh_version, cached_tables());
|
|
#ifdef SAFEMALLOC
|
|
if (lCurMemory) // Using SAFEMALLOC
|
|
sprintf(strend(buff), " Memory in use: %ldK Max memory used: %ldK",
|
|
(lCurMemory+1023L)/1024L,(lMaxMemory+1023L)/1024L);
|
|
#endif
|
|
VOID(my_net_write(net, buff,strlen(buff)));
|
|
VOID(net_flush(net));
|
|
break;
|
|
}
|
|
case COM_PING:
|
|
send_ok(net); // Tell client we are alive
|
|
break;
|
|
case COM_PROCESS_INFO:
|
|
if (!thd->priv_user[0] && check_access(thd,PROCESS_ACL,any_db))
|
|
break;
|
|
mysql_log.write(command,NullS);
|
|
mysqld_list_processes(thd,thd->master_access & PROCESS_ACL ? NullS :
|
|
thd->priv_user);
|
|
break;
|
|
case COM_PROCESS_KILL:
|
|
{
|
|
ulong thread_id=(ulong) uint4korr(packet+1);
|
|
kill_one_thread(thd,thread_id);
|
|
break;
|
|
}
|
|
case COM_DEBUG:
|
|
if (check_access(thd,PROCESS_ACL,any_db))
|
|
break; /* purecov: inspected */
|
|
mysql_print_status();
|
|
mysql_log.write(command,NullS);
|
|
send_eof(net);
|
|
break;
|
|
case COM_SLEEP:
|
|
case COM_CONNECT: // Impossible here
|
|
case COM_TIME: // Impossible from client
|
|
default:
|
|
send_error(net, ER_UNKNOWN_COM_ERROR);
|
|
break;
|
|
}
|
|
thd->proc_info="cleaning up";
|
|
if (tables_used)
|
|
close_thread_tables(thd); /* Free tables */
|
|
|
|
if (thd->fatal_error)
|
|
send_error(net,0); // End of memory ?
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count)); // For process list
|
|
thd->proc_info=0;
|
|
thd->command=COM_SLEEP;
|
|
thd->query=0;
|
|
time_t start_of_query=thd->start_time;
|
|
thd->set_time();
|
|
if ((ulong) (thd->start_time - start_of_query) > long_query_time)
|
|
long_query_count++;
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
thd->packet.shrink(net_buffer_length); // Reclaim some memory
|
|
free_root(&thd->alloc);
|
|
DBUG_RETURN(error);
|
|
}
|
|
|
|
/****************************************************************************
|
|
** mysql_execute_command
|
|
** Execute command saved in thd and current_lex->sql_command
|
|
****************************************************************************/
|
|
|
|
void
|
|
mysql_execute_command(void)
|
|
{
|
|
int res=0;
|
|
THD *thd=current_thd;
|
|
LEX *lex=current_lex;
|
|
TABLE_LIST *tables=(TABLE_LIST*) thd->table_list.first;
|
|
DBUG_ENTER("mysql_execute_command");
|
|
|
|
switch(lex->sql_command) {
|
|
case SQLCOM_SELECT:
|
|
{
|
|
select_result *result;
|
|
if (lex->options & SELECT_DESCRIBE)
|
|
lex->exchange=0;
|
|
if (tables)
|
|
{
|
|
res=check_table_access(thd,
|
|
lex->exchange ? SELECT_ACL | FILE_ACL :
|
|
SELECT_ACL,
|
|
tables);
|
|
}
|
|
else
|
|
res=check_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL,
|
|
any_db);
|
|
if (res)
|
|
{
|
|
res=0;
|
|
break; // Error message is given
|
|
}
|
|
|
|
thd->offset_limit=lex->offset_limit;
|
|
thd->select_limit=lex->select_limit+lex->offset_limit;
|
|
if (thd->select_limit < lex->select_limit)
|
|
thd->select_limit= HA_POS_ERROR; // no limit
|
|
|
|
if (lex->exchange)
|
|
{
|
|
if (!(result=new select_export(lex->exchange)))
|
|
{
|
|
res= -1;
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->having;
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
else if (!(result=new select_send()))
|
|
{
|
|
res= -1;
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->having;
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
if (lex->options & SELECT_HIGH_PRIORITY)
|
|
{
|
|
TABLE_LIST *table;
|
|
for (table = tables ; table ; table=table->next)
|
|
table->lock_type=TL_READ_HIGH_PRIORITY;
|
|
}
|
|
|
|
if (!(res=open_and_lock_tables(thd,tables)))
|
|
{
|
|
res=mysql_select(thd,tables,thd->item_list,
|
|
lex->where,
|
|
(ORDER*) thd->order_list.first,
|
|
(ORDER*) thd->group_list.first,
|
|
lex->having,
|
|
(ORDER*) thd->proc_list.first,
|
|
lex->options | thd->options,
|
|
result);
|
|
}
|
|
delete result;
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->having;
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLCOM_CREATE_TABLE:
|
|
if (check_access(thd,CREATE_ACL,tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,CREATE_ACL,tables))
|
|
goto error;
|
|
|
|
if (strlen(tables->name) > NAME_LEN)
|
|
{
|
|
net_printf(&thd->net,ER_WRONG_TABLE_NAME,tables->name);
|
|
res=0;
|
|
break;
|
|
}
|
|
res = mysql_create_table(thd,tables->db ? tables->db : thd->db,
|
|
tables->name, lex->create_list,
|
|
lex->key_list,0);
|
|
if (!res)
|
|
send_ok(&thd->net);
|
|
break;
|
|
case SQLCOM_CREATE_INDEX:
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd,INDEX_ACL,tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,INDEX_ACL,tables))
|
|
goto error;
|
|
res = mysql_create_index(thd, tables, lex->key_list);
|
|
break;
|
|
case SQLCOM_ALTER_TABLE:
|
|
{
|
|
uint priv=0;
|
|
if (lex->name && strlen(lex->name) > NAME_LEN)
|
|
{
|
|
net_printf(&thd->net,ER_WRONG_TABLE_NAME,lex->name);
|
|
res=0;
|
|
break;
|
|
}
|
|
if (!lex->db)
|
|
lex->db=tables->db;
|
|
if (check_access(thd,ALTER_ACL,tables->db,&tables->grant.privilege) ||
|
|
check_access(thd,INSERT_ACL | CREATE_ACL,lex->db,&priv))
|
|
goto error; /* purecov: inspected */
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (grant_option)
|
|
{
|
|
if (check_grant(thd,ALTER_ACL,tables))
|
|
goto error;
|
|
if (lex->name && !test_all_bits(priv,INSERT_ACL | CREATE_ACL))
|
|
{ // Rename of table
|
|
TABLE_LIST tmp_table;
|
|
bzero((char*) &tmp_table,sizeof(tmp_table));
|
|
tmp_table.real_name=lex->name;
|
|
tmp_table.db=lex->db;
|
|
tmp_table.grant.privilege=priv;
|
|
if (check_grant(thd,INSERT_ACL | CREATE_ACL,tables))
|
|
goto error;
|
|
}
|
|
}
|
|
res= mysql_alter_table(thd, lex->db, lex->name, tables, lex->create_list,
|
|
lex->key_list, lex->drop_list, lex->alter_list,
|
|
lex->drop_primary, lex->duplicates);
|
|
break;
|
|
}
|
|
case SQLCOM_OPTIMIZE:
|
|
/* This is now done with ALTER TABLE, but should be done with isamchk */
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd,SELECT_ACL | INSERT_ACL,tables->db,
|
|
&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,SELECT_ACL | INSERT_ACL,tables))
|
|
goto error;
|
|
|
|
lex->create_list.empty();
|
|
lex->key_list.empty();
|
|
lex->col_list.empty();
|
|
lex->drop_list.empty();
|
|
lex->alter_list.empty();
|
|
res= mysql_alter_table(thd, NullS, NullS, tables, lex->create_list,
|
|
lex->key_list, lex->drop_list, lex->alter_list,
|
|
0,DUP_ERROR);
|
|
break;
|
|
case SQLCOM_UPDATE:
|
|
if (check_access(thd,UPDATE_ACL,tables->db,&tables->grant.privilege))
|
|
goto error;
|
|
if (grant_option && check_grant(thd,UPDATE_ACL,tables))
|
|
goto error;
|
|
if (thd->item_list.elements != thd->value_list.elements)
|
|
{
|
|
send_error(&thd->net,ER_WRONG_VALUE_COUNT);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
res = mysql_update(thd,tables,
|
|
thd->item_list,
|
|
thd->value_list,
|
|
lex->where,
|
|
((thd->options & OPTION_LOW_PRIORITY_UPDATES) ||
|
|
lex->low_priority));
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
case SQLCOM_INSERT:
|
|
if (check_access(thd,INSERT_ACL,tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,INSERT_ACL,tables))
|
|
goto error;
|
|
res = mysql_insert(thd,tables,thd->field_list,lex->many_values,
|
|
lex->duplicates,
|
|
((thd->options & OPTION_LOW_PRIORITY_UPDATES) ||
|
|
lex->low_priority));
|
|
break;
|
|
case SQLCOM_REPLACE:
|
|
if (check_access(thd,INSERT_ACL | UPDATE_ACL | DELETE_ACL,
|
|
tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,INSERT_ACL | UPDATE_ACL | DELETE_ACL,
|
|
tables))
|
|
|
|
goto error;
|
|
res = mysql_insert(thd,tables,thd->field_list,lex->many_values,
|
|
DUP_REPLACE,
|
|
((thd->options & OPTION_LOW_PRIORITY_UPDATES) ||
|
|
lex->low_priority));
|
|
break;
|
|
case SQLCOM_REPLACE_SELECT:
|
|
case SQLCOM_INSERT_SELECT:
|
|
{
|
|
// Check that we have modify privileges for the first table and
|
|
// select privileges for the rest
|
|
uint privilege= (lex->sql_command == SQLCOM_INSERT_SELECT ?
|
|
INSERT_ACL : INSERT_ACL | UPDATE_ACL | DELETE_ACL);
|
|
TABLE_LIST *save_next=tables->next;
|
|
tables->next=0;
|
|
if (check_access(thd, privilege,
|
|
tables->db,&tables->grant.privilege) ||
|
|
(grant_option && check_grant(thd, privilege, tables)))
|
|
goto error;
|
|
tables->next=save_next;
|
|
if ((res=check_table_access(thd, SELECT_ACL, save_next)))
|
|
goto error;
|
|
|
|
select_result *result;
|
|
thd->offset_limit=lex->offset_limit;
|
|
thd->select_limit=lex->select_limit+lex->offset_limit;
|
|
if (thd->select_limit < lex->select_limit)
|
|
thd->select_limit= HA_POS_ERROR; // No limit
|
|
|
|
if (check_dup(thd,tables->db,tables->real_name,tables->next))
|
|
{
|
|
net_printf(&thd->net,ER_INSERT_TABLE_USED,tables->real_name);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
tables->lock_type=TL_WRITE; // update first table
|
|
if (!(res=open_and_lock_tables(thd,tables)))
|
|
{
|
|
if ((result=new select_insert(tables->table,&thd->field_list,
|
|
lex->sql_command == SQLCOM_REPLACE_SELECT ?
|
|
DUP_REPLACE : DUP_IGNORE)))
|
|
{
|
|
res=mysql_select(thd,tables->next,thd->item_list,
|
|
lex->where,
|
|
(ORDER*) thd->order_list.first,
|
|
(ORDER*) thd->group_list.first,
|
|
lex->having,
|
|
(ORDER*) thd->proc_list.first,
|
|
lex->options,
|
|
result);
|
|
delete result;
|
|
}
|
|
else
|
|
res= -1;
|
|
}
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->having;
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLCOM_DELETE:
|
|
{
|
|
if (check_access(thd,DELETE_ACL,tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,DELETE_ACL,tables))
|
|
goto error;
|
|
// Set privilege for the WHERE clause
|
|
tables->grant.want_privilege=(SELECT_ACL & ~tables->grant.privilege);
|
|
res = mysql_delete(thd,tables,lex->where,lex->select_limit,
|
|
((thd->options & OPTION_LOW_PRIORITY_UPDATES) ||
|
|
lex->low_priority));
|
|
#ifdef DELETE_ITEMS
|
|
delete lex->where;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLCOM_DROP_TABLE:
|
|
{
|
|
if (check_table_access(thd,DROP_ACL,tables))
|
|
goto error; /* purecov: inspected */
|
|
res = mysql_rm_table(thd,tables,lex->drop_if_exists);
|
|
}
|
|
break;
|
|
case SQLCOM_DROP_INDEX:
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd,INDEX_ACL,tables->db,&tables->grant.privilege))
|
|
goto error; /* purecov: inspected */
|
|
if (grant_option && check_grant(thd,INDEX_ACL,tables))
|
|
goto error;
|
|
res = mysql_drop_index(thd, tables, lex->drop_list);
|
|
break;
|
|
case SQLCOM_SHOW_DATABASES:
|
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
|
send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
|
DBUG_VOID_RETURN;
|
|
#else
|
|
res= mysqld_show_dbs(thd, (lex->wild ? lex->wild->ptr() : NullS));
|
|
break;
|
|
#endif
|
|
case SQLCOM_SHOW_PROCESSLIST:
|
|
if (!thd->priv_user[0] && check_access(thd,PROCESS_ACL,any_db))
|
|
break;
|
|
mysqld_list_processes(thd,thd->master_access & PROCESS_ACL ? NullS :
|
|
thd->priv_user);
|
|
break;
|
|
case SQLCOM_SHOW_STATUS:
|
|
pthread_mutex_lock(&THR_LOCK_keycache);
|
|
pthread_mutex_lock(&LOCK_status);
|
|
res= mysqld_show(thd,NullS,status_vars);
|
|
pthread_mutex_unlock(&LOCK_status);
|
|
pthread_mutex_unlock(&THR_LOCK_keycache);
|
|
break;
|
|
case SQLCOM_SHOW_VARIABLES:
|
|
pthread_mutex_lock(&THR_LOCK_keycache);
|
|
pthread_mutex_lock(&LOCK_status);
|
|
res= mysqld_show(thd, (lex->wild ? lex->wild->ptr() : NullS),
|
|
init_vars);
|
|
pthread_mutex_unlock(&LOCK_status);
|
|
pthread_mutex_unlock(&THR_LOCK_keycache);
|
|
break;
|
|
case SQLCOM_SHOW_TABLES:
|
|
{
|
|
char *db=lex->db ? lex->db : thd->db;
|
|
if (!db)
|
|
{
|
|
send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: inspected */
|
|
goto error; /* purecov: inspected */
|
|
}
|
|
remove_escape(db); // Fix escaped '_'
|
|
if (strlen(db) > NAME_LEN)
|
|
{
|
|
net_printf(&thd->net,ER_WRONG_DB_NAME, db);
|
|
goto error;
|
|
}
|
|
if (check_access(thd,SELECT_ACL,db,&thd->col_access))
|
|
goto error; /* purecov: inspected */
|
|
/* grant is checked in mysqld_show_tables */
|
|
res= mysqld_show_tables(thd,db,
|
|
(lex->wild ? lex->wild->ptr() : NullS));
|
|
break;
|
|
}
|
|
case SQLCOM_SHOW_FIELDS:
|
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
|
send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
|
DBUG_VOID_RETURN;
|
|
#else
|
|
{
|
|
char *db=tables->db ? tables->db : thd->db;
|
|
if (!db)
|
|
{
|
|
send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: inspected */
|
|
goto error; /* purecov: inspected */
|
|
}
|
|
remove_escape(db); // Fix escaped '_'
|
|
remove_escape(tables->name);
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd,SELECT_ACL,db,&thd->col_access))
|
|
goto error; /* purecov: inspected */
|
|
tables->grant.privilege=thd->col_access;
|
|
if (grant_option && check_grant(thd,SELECT_ACL,tables,2))
|
|
goto error;
|
|
res= mysqld_show_fields(thd,tables,
|
|
(lex->wild ? lex->wild->ptr() : NullS));
|
|
break;
|
|
}
|
|
#endif
|
|
case SQLCOM_SHOW_KEYS:
|
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
|
send_error(&thd->net,ER_NOT_ALLOWED_COMMAND);/* purecov: inspected */
|
|
DBUG_VOID_RETURN;
|
|
#else
|
|
{
|
|
char *db=tables->db ? tables->db : thd->db;
|
|
if (!db)
|
|
{
|
|
send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: inspected */
|
|
goto error; /* purecov: inspected */
|
|
}
|
|
remove_escape(db); // Fix escaped '_'
|
|
remove_escape(tables->name);
|
|
if (!tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd,SELECT_ACL,db,&thd->col_access))
|
|
goto error; /* purecov: inspected */
|
|
tables->grant.privilege=thd->col_access;
|
|
if (grant_option && check_grant(thd,SELECT_ACL,tables,2))
|
|
goto error;
|
|
res= mysqld_show_keys(thd,tables);
|
|
break;
|
|
}
|
|
#endif
|
|
case SQLCOM_CHANGE_DB:
|
|
mysql_change_db(thd,lex->db);
|
|
break;
|
|
case SQLCOM_LOAD:
|
|
if (!(lex->local_file && (thd->client_capabilities & CLIENT_LOCAL_FILES)))
|
|
{
|
|
if (check_access(thd,INSERT_ACL | FILE_ACL,tables->db))
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
if (check_access(thd,INSERT_ACL,tables->db,&tables->grant.privilege) ||
|
|
grant_option && check_grant(thd,INSERT_ACL,tables))
|
|
goto error;
|
|
}
|
|
res=mysql_load(thd, lex->exchange, tables, thd->field_list,
|
|
lex->duplicates, (bool) lex->local_file);
|
|
break;
|
|
case SQLCOM_SET_OPTION:
|
|
thd->options=lex->options;
|
|
thd->default_select_limit=lex->select_limit;
|
|
DBUG_PRINT("info",("options: %ld limit: %ld",
|
|
thd->options,(long) thd->default_select_limit));
|
|
send_ok(&thd->net);
|
|
break;
|
|
case SQLCOM_UNLOCK_TABLES:
|
|
if (thd->locked_tables)
|
|
{
|
|
thd->lock=thd->locked_tables;
|
|
thd->locked_tables=0; // Will be automaticly closed
|
|
}
|
|
send_ok(&thd->net);
|
|
break;
|
|
case SQLCOM_LOCK_TABLES:
|
|
if (thd->locked_tables)
|
|
{
|
|
thd->lock=thd->locked_tables;
|
|
thd->locked_tables=0; // Will be automaticly closed
|
|
close_thread_tables(thd);
|
|
}
|
|
if (!(res=open_and_lock_tables(thd,tables)))
|
|
{
|
|
thd->locked_tables=thd->lock;
|
|
thd->lock=0;
|
|
send_ok(&thd->net);
|
|
}
|
|
break;
|
|
case SQLCOM_CREATE_DB:
|
|
{
|
|
if (check_access(thd,CREATE_ACL,lex->name,0,1))
|
|
break;
|
|
mysql_create_db(thd,lex->name);
|
|
break;
|
|
}
|
|
case SQLCOM_DROP_DB:
|
|
{
|
|
if (check_access(thd,DROP_ACL,lex->name,0,1))
|
|
break;
|
|
mysql_rm_db(thd,lex->name,lex->drop_if_exists);
|
|
break;
|
|
}
|
|
case SQLCOM_CREATE_FUNCTION:
|
|
if (check_access(thd,INSERT_ACL,"mysql",0,1))
|
|
break;
|
|
#ifdef HAVE_DLOPEN
|
|
if (!(res = mysql_create_function(thd,&lex->udf)))
|
|
send_ok(&thd->net);
|
|
#else
|
|
res= -1;
|
|
#endif
|
|
break;
|
|
case SQLCOM_DROP_FUNCTION:
|
|
if (check_access(thd,DELETE_ACL,"mysql",0,1))
|
|
break;
|
|
#ifdef HAVE_DLOPEN
|
|
if (!(res = mysql_drop_function(thd,lex->udf.name)))
|
|
send_ok(&thd->net);
|
|
#else
|
|
res= -1;
|
|
#endif
|
|
break;
|
|
case SQLCOM_REVOKE:
|
|
case SQLCOM_GRANT:
|
|
{
|
|
if (tables && !tables->db)
|
|
tables->db=thd->db;
|
|
if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL,
|
|
tables && tables->db ? tables->db : NullS,
|
|
tables ? &tables->grant.privilege : 0,
|
|
tables ? 0 : 1))
|
|
goto error;
|
|
if (tables)
|
|
{
|
|
if (grant_option && check_grant(thd,
|
|
(lex->grant | lex->grant_tot_col |
|
|
GRANT_ACL),
|
|
tables))
|
|
goto error;
|
|
res = mysql_table_grant(thd,tables,lex->users_list, lex->columns,
|
|
lex->grant, lex->sql_command == SQLCOM_REVOKE);
|
|
}
|
|
else
|
|
{
|
|
if (lex->columns.elements)
|
|
{
|
|
net_printf(&thd->net,ER_ILLEGAL_GRANT_FOR_TABLE);
|
|
res=1;
|
|
}
|
|
else
|
|
res = mysql_grant(thd, lex->db, lex->users_list, lex->grant,
|
|
lex->sql_command == SQLCOM_REVOKE);
|
|
}
|
|
break;
|
|
}
|
|
case SQLCOM_FLUSH:
|
|
if (check_access(thd,RELOAD_ACL,any_db))
|
|
goto error;
|
|
if (reload_acl_and_cache(lex->type))
|
|
send_error(&thd->net,0);
|
|
else
|
|
send_ok(&thd->net);
|
|
break;
|
|
case SQLCOM_KILL:
|
|
kill_one_thread(thd,lex->thread_id);
|
|
break;
|
|
default: /* Impossible */
|
|
send_ok(&thd->net);
|
|
break;
|
|
}
|
|
thd->proc_info="query end"; // QQ
|
|
if (res < 0)
|
|
send_error(&thd->net,thd->killed ? ER_SERVER_SHUTDOWN : 0);
|
|
|
|
error:
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
** Get the user (global) and database privileges for all used tables
|
|
** Return false (error) if we can't get the privileges and we don't use
|
|
** table/column grants.
|
|
****************************************************************************/
|
|
|
|
bool
|
|
check_access(THD *thd,uint want_access,const char *db, uint *save_priv,
|
|
bool no_grant)
|
|
{
|
|
uint access,dummy;
|
|
if (save_priv)
|
|
*save_priv=0;
|
|
else
|
|
save_priv= &dummy;
|
|
|
|
if (!db && !thd->db && !no_grant)
|
|
{
|
|
send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: tested */
|
|
return TRUE; /* purecov: tested */
|
|
}
|
|
|
|
if ((thd->master_access & want_access) == want_access)
|
|
{
|
|
*save_priv=thd->master_access;
|
|
return FALSE;
|
|
}
|
|
if ((want_access & ~thd->master_access) & ~DB_ACLS ||
|
|
! db && no_grant)
|
|
{ // We can never grant this
|
|
net_printf(&thd->net,ER_ACCESS_DENIED_ERROR,
|
|
thd->priv_user,
|
|
thd->host ? thd->host : (thd->ip ? thd->ip : "unknown"),
|
|
thd->password ? ER(ER_YES) : ER(ER_NO));/* purecov: tested */
|
|
return TRUE; /* purecov: tested */
|
|
}
|
|
|
|
if (db == any_db)
|
|
return FALSE; // Allow select on anything
|
|
if (db && (!thd->db || strcmp(db,thd->db)))
|
|
access=acl_get(thd->host, thd->ip, (char*) &thd->remote.sin_addr,
|
|
thd->priv_user, db); /* purecov: inspected */
|
|
else
|
|
access=thd->db_access;
|
|
access= (*save_priv=(access | thd->master_access)) & want_access;
|
|
if (access == want_access ||
|
|
((grant_option && !no_grant) && !(want_access & ~TABLE_ACLS)))
|
|
return FALSE; /* Ok */
|
|
net_printf(&thd->net,ER_DBACCESS_DENIED_ERROR,
|
|
thd->priv_user,
|
|
thd->host ? thd->host : (thd->ip ? thd->ip : "unknown"),
|
|
db ? db : thd->db ? thd->db : "unknown"); /* purecov: tested */
|
|
return TRUE; /* purecov: tested */
|
|
}
|
|
|
|
|
|
/*
|
|
** Check the privilege for all used tables. Table privileges are cached
|
|
** in the table list for GRANT checking
|
|
*/
|
|
|
|
static bool
|
|
check_table_access(THD *thd,uint want_access,TABLE_LIST *tables)
|
|
{
|
|
uint found=0,found_access=0;
|
|
TABLE_LIST *org_tables=tables;
|
|
for (; tables ; tables=tables->next)
|
|
{
|
|
if ((thd->master_access & want_access) == want_access && thd->db)
|
|
tables->grant.privilege= want_access;
|
|
else if (tables->db)
|
|
{
|
|
if (found && !grant_option) // db already checked
|
|
tables->grant.privilege=found_access;
|
|
else
|
|
{
|
|
if (check_access(thd,want_access,tables->db,&tables->grant.privilege))
|
|
return TRUE; // Access denied
|
|
found_access=tables->grant.privilege;
|
|
}
|
|
}
|
|
else if (check_access(thd,want_access,tables->db,&tables->grant.privilege))
|
|
return TRUE; // Access denied
|
|
}
|
|
if (grant_option)
|
|
return check_grant(thd,want_access,org_tables);
|
|
return FALSE;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Check stack size; Send error if there isn't enough stack to continue
|
|
****************************************************************************/
|
|
|
|
#define STACK_MIN_SIZE 2048 /* We will need this much stack later */
|
|
#if STACK_DIRECTION < 0
|
|
#define used_stack(A,B) (long) (A - B)
|
|
#else
|
|
#define used_stack(A,B) (long) (B - A)
|
|
#endif
|
|
|
|
bool check_stack_overrun(THD *thd,char *buf __attribute__((unused)))
|
|
{
|
|
long stack_used;
|
|
if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >=
|
|
(long) (thread_stack-STACK_MIN_SIZE))
|
|
{
|
|
sprintf(errbuff[0],ER(ER_STACK_OVERRUN),stack_used,thread_stack);
|
|
my_message(ER_STACK_OVERRUN,errbuff[0],MYF(0));
|
|
thd->fatal_error=1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define MY_YACC_INIT 1000 // Start with big alloc
|
|
#define MY_YACC_MAX 32000 // Because of 'short'
|
|
|
|
bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, int *yystacksize)
|
|
{
|
|
LEX *lex=current_lex;
|
|
int old_info=0;
|
|
if ((uint) *yystacksize >= MY_YACC_MAX)
|
|
return 1;
|
|
if (!lex->yacc_yyvs)
|
|
old_info= *yystacksize;
|
|
*yystacksize= set_zone((*yystacksize)*2,MY_YACC_INIT,MY_YACC_MAX);
|
|
if (!(lex->yacc_yyvs=
|
|
my_realloc((gptr) lex->yacc_yyvs,
|
|
*yystacksize*sizeof(**yyvs),
|
|
MYF(MY_ALLOW_ZERO_PTR | MY_FREE_ON_ERROR))) ||
|
|
!(lex->yacc_yyss=
|
|
my_realloc((gptr) lex->yacc_yyss,
|
|
*yystacksize*sizeof(**yyss),
|
|
MYF(MY_ALLOW_ZERO_PTR | MY_FREE_ON_ERROR))))
|
|
return 1;
|
|
if (old_info)
|
|
{ // Copy old info from stack
|
|
memcpy(lex->yacc_yyss, (gptr) *yyss, old_info*sizeof(**yyss));
|
|
memcpy(lex->yacc_yyvs, (gptr) *yyvs, old_info*sizeof(**yyvs));
|
|
}
|
|
*yyss=(short*) lex->yacc_yyss;
|
|
*yyvs=(YYSTYPE*) lex->yacc_yyvs;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
Initialize global thd variables neaded for query
|
|
****************************************************************************/
|
|
|
|
static void
|
|
mysql_init_query(THD *thd)
|
|
{
|
|
DBUG_ENTER("mysql_init_query");
|
|
thd->net.last_error[0]=0;
|
|
thd->item_list.empty();
|
|
thd->value_list.empty();
|
|
thd->order_list.elements=thd->table_list.elements= thd->group_list.elements=0;
|
|
thd->free_list=0;
|
|
|
|
thd->order_list.first=0;
|
|
thd->order_list.next= (byte**) &thd->order_list.first;
|
|
thd->table_list.first=0;
|
|
thd->table_list.next= (byte**) &thd->table_list.first;
|
|
thd->group_list.first=0;
|
|
thd->group_list.next= (byte**) &thd->group_list.first;
|
|
thd->proc_list.first=0; // Needed by sql_select
|
|
thd->fatal_error=0; // Safety
|
|
thd->last_insert_id_used=thd->query_start_used=thd->insert_id_used=0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void
|
|
mysql_parse(THD *thd,char *inBuf,uint length)
|
|
{
|
|
DBUG_ENTER("mysql_parse");
|
|
|
|
mysql_init_query(thd);
|
|
LEX *lex=lex_start(thd, (uchar*) inBuf, length);
|
|
if (!yyparse() && ! thd->fatal_error)
|
|
mysql_execute_command();
|
|
thd->proc_info="freeing items";
|
|
free_items(thd); /* Free strings used by items */
|
|
lex_end(lex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
inline static void
|
|
link_in_list(SQL_LIST *list,byte *element,byte **next)
|
|
{
|
|
list->elements++;
|
|
(*list->next)=element;
|
|
list->next=next;
|
|
*next=0;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
** Store field definition for create
|
|
** Return 0 if ok
|
|
******************************************************************************/
|
|
|
|
bool add_field_to_list(char *field_name, enum_field_types type,
|
|
char *length, char *decimals,
|
|
uint type_modifier, Item *default_value,char *change,
|
|
TYPELIB *interval)
|
|
{
|
|
register create_field *new_field;
|
|
THD *thd=current_thd;
|
|
LEX *lex= &thd->lex;
|
|
DBUG_ENTER("add_field_to_list");
|
|
|
|
if (strlen(field_name) > NAME_LEN)
|
|
{
|
|
net_printf(&thd->net, ER_TOO_LONG_IDENT, field_name); /* purecov: inspected */
|
|
DBUG_RETURN(1); /* purecov: inspected */
|
|
}
|
|
if (type_modifier & PRI_KEY_FLAG)
|
|
{
|
|
lex->col_list.push_back(new key_part_spec(field_name,0));
|
|
lex->key_list.push_back(new Key(Key::PRIMARY,NullS,
|
|
lex->col_list));
|
|
lex->col_list.empty();
|
|
}
|
|
|
|
if (default_value && default_value->type() == Item::NULL_ITEM)
|
|
{
|
|
if (type_modifier & NOT_NULL_FLAG)
|
|
{
|
|
net_printf(&thd->net,ER_INVALID_DEFAULT,field_name);
|
|
DBUG_RETURN(1);
|
|
}
|
|
default_value=0;
|
|
}
|
|
/* change FLOAT(precision) to FLOAT or DOUBLE */
|
|
if (type == FIELD_TYPE_FLOAT && length && !decimals)
|
|
{
|
|
uint tmp_length=atoi(length);
|
|
if (tmp_length > sizeof(double))
|
|
{
|
|
net_printf(&thd->net,ER_WRONG_FIELD_SPEC,field_name); /* purecov: inspected */
|
|
DBUG_RETURN(1); /* purecov: inspected */
|
|
}
|
|
else if (tmp_length > sizeof(float))
|
|
type=FIELD_TYPE_DOUBLE;
|
|
length=0; // Use default format
|
|
}
|
|
if (!(new_field=new create_field()))
|
|
DBUG_RETURN(1);
|
|
new_field->field=0;
|
|
new_field->field_name=field_name;
|
|
new_field->def= (type_modifier & AUTO_INCREMENT_FLAG ? 0 : default_value);
|
|
new_field->flags= type_modifier;
|
|
new_field->sql_type=type;
|
|
new_field->unireg_check= (type_modifier & AUTO_INCREMENT_FLAG ?
|
|
Field::NEXT_NUMBER : Field::NONE);
|
|
new_field->decimals= decimals ? (uint) set_zone(atoi(decimals),0,30) : 0;
|
|
new_field->length=0;
|
|
new_field->change=change;
|
|
new_field->interval=0;
|
|
new_field->pack_length=0;
|
|
if (length)
|
|
if (!(new_field->length= (uint) atoi(length)))
|
|
length=0; /* purecov: inspected */
|
|
uint sign_len=type_modifier & UNSIGNED_FLAG ? 0 : 1;
|
|
if (new_field->length && new_field->decimals &&
|
|
new_field->length < new_field->decimals+2)
|
|
new_field->length=new_field->decimals+2; /* purecov: inspected */
|
|
|
|
switch (type) {
|
|
case FIELD_TYPE_TINY:
|
|
if (!length) new_field->length=3+sign_len;
|
|
break;
|
|
case FIELD_TYPE_SHORT:
|
|
if (!length) new_field->length=5+sign_len;
|
|
break;
|
|
case FIELD_TYPE_INT24:
|
|
if (!length) new_field->length=8+sign_len;
|
|
break;
|
|
case FIELD_TYPE_LONG:
|
|
if (!length) new_field->length=10+sign_len;
|
|
break;
|
|
case FIELD_TYPE_LONGLONG:
|
|
if (!length) new_field->length=20+sign_len;
|
|
break;
|
|
case FIELD_TYPE_STRING:
|
|
case FIELD_TYPE_VAR_STRING:
|
|
case FIELD_TYPE_DECIMAL:
|
|
case FIELD_TYPE_NULL:
|
|
break;
|
|
case FIELD_TYPE_BLOB:
|
|
case FIELD_TYPE_TINY_BLOB:
|
|
case FIELD_TYPE_LONG_BLOB:
|
|
case FIELD_TYPE_MEDIUM_BLOB:
|
|
if (default_value) // Allow empty as default value
|
|
{
|
|
String str,*res;
|
|
res=default_value->str(&str);
|
|
if (res->length())
|
|
{
|
|
net_printf(&thd->net,ER_BLOB_CANT_HAVE_DEFAULT,field_name); /* purecov: inspected */
|
|
DBUG_RETURN(1); /* purecov: inspected */
|
|
}
|
|
new_field->def=0;
|
|
}
|
|
new_field->flags|=BLOB_FLAG;
|
|
break;
|
|
case FIELD_TYPE_YEAR:
|
|
if (!length || new_field->length != 2)
|
|
new_field->length=4; // Default length
|
|
new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG;
|
|
break;
|
|
case FIELD_TYPE_FLOAT:
|
|
if (!length)
|
|
{
|
|
new_field->length = 8+2; // Default 2 decimals
|
|
new_field->decimals=2;
|
|
}
|
|
break;
|
|
case FIELD_TYPE_DOUBLE:
|
|
if (!length)
|
|
{
|
|
new_field->length = 12+4;
|
|
new_field->decimals=4;
|
|
}
|
|
break;
|
|
case FIELD_TYPE_TIMESTAMP:
|
|
if (!length)
|
|
new_field->length= 14; // Full date YYYYMMDDHHMMSS
|
|
else
|
|
{
|
|
new_field->length=((new_field->length+1)/2)*2; /* purecov: inspected */
|
|
new_field->length= min(new_field->length,14); /* purecov: inspected */
|
|
}
|
|
new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG | NOT_NULL_FLAG;
|
|
new_field->unireg_check=Field::TIMESTAMP_FIELD;
|
|
break;
|
|
case FIELD_TYPE_DATE: // Old date type
|
|
if (protocol_version != PROTOCOL_VERSION-1)
|
|
new_field->sql_type=FIELD_TYPE_NEWDATE;
|
|
/* fall trough */
|
|
case FIELD_TYPE_NEWDATE:
|
|
new_field->length=10;
|
|
break;
|
|
case FIELD_TYPE_TIME:
|
|
new_field->length=10;
|
|
break;
|
|
case FIELD_TYPE_DATETIME:
|
|
new_field->length=19;
|
|
break;
|
|
case FIELD_TYPE_SET:
|
|
{
|
|
if (interval->count > sizeof(longlong)*8)
|
|
{
|
|
net_printf(&thd->net,ER_TOO_BIG_SET,field_name); /* purecov: inspected */
|
|
DBUG_RETURN(1); /* purecov: inspected */
|
|
}
|
|
new_field->pack_length=(interval->count+7)/8;
|
|
if (new_field->pack_length > 4)
|
|
new_field->pack_length=8;
|
|
new_field->interval=interval;
|
|
new_field->length=strlen(interval->type_names[0]);
|
|
for (char **pos=interval->type_names+1; *pos ; pos++)
|
|
{
|
|
uint length=strlen(*pos);
|
|
set_if_bigger(new_field->length,length);
|
|
}
|
|
set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1);
|
|
if (default_value)
|
|
{
|
|
thd->cuted_fields=0;
|
|
String str,*res;
|
|
res=default_value->str(&str);
|
|
(void) find_set(interval,res->ptr(),res->length());
|
|
if (thd->cuted_fields)
|
|
{
|
|
net_printf(&thd->net,ER_INVALID_DEFAULT,field_name);
|
|
DBUG_RETURN(1);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FIELD_TYPE_ENUM:
|
|
{
|
|
new_field->interval=interval;
|
|
new_field->pack_length=interval->count < 256 ? 1 : 2; // Should be safe
|
|
new_field->length=0;
|
|
for (char **pos=interval->type_names; *pos ; pos++)
|
|
new_field->length+=strlen(*pos)+1;
|
|
new_field->length--;
|
|
set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1);
|
|
if (default_value)
|
|
{
|
|
String str,*res;
|
|
res=default_value->str(&str);
|
|
if (!find_enum(interval,res->ptr(),res->length()))
|
|
{
|
|
net_printf(&thd->net,ER_INVALID_DEFAULT,field_name);
|
|
DBUG_RETURN(1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (new_field->length >= MAX_FIELD_WIDTH ||
|
|
(!new_field->length && !(new_field->flags & BLOB_FLAG)))
|
|
{
|
|
net_printf(&thd->net,ER_TOO_BIG_FIELDLENGTH,field_name,
|
|
MAX_FIELD_WIDTH-1); /* purecov: inspected */
|
|
DBUG_RETURN(1); /* purecov: inspected */
|
|
}
|
|
if (!new_field->pack_length)
|
|
new_field->pack_length=calc_pack_length(new_field->sql_type,
|
|
new_field->length);
|
|
lex->create_list.push_back(new_field);
|
|
thd->last_field=new_field;
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
/* Store position for column in ALTER TABLE .. ADD column */
|
|
|
|
void store_position_for_column(my_string name)
|
|
{
|
|
current_thd->last_field->after=name;
|
|
}
|
|
|
|
bool
|
|
add_proc_to_list(Item *item)
|
|
{
|
|
ORDER *order;
|
|
Item **item_ptr;
|
|
|
|
if (!(order = (ORDER *) sql_alloc(sizeof(ORDER)+sizeof(Item*))))
|
|
return 1;
|
|
item_ptr = (Item**) (order+1);
|
|
*item_ptr= item;
|
|
order->item=item_ptr;
|
|
order->free_me=0;
|
|
link_in_list(¤t_thd->proc_list,(byte*) order,(byte**) &order->next);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Fix escaping of _, % and \ in database and table names (for ODBC) */
|
|
|
|
static void remove_escape(char *name)
|
|
{
|
|
char *to;
|
|
for (to=name; *name ; name++)
|
|
{
|
|
#ifdef USE_BIG5CODE
|
|
if (name[1] && isbig5code(*name,*(name+1)))
|
|
{
|
|
*to++= *name++;
|
|
*to++= *name;
|
|
continue;
|
|
}
|
|
#endif
|
|
#ifdef USE_MB
|
|
int l;
|
|
if ((l = ismbchar(name, name+MBMAXLEN))) {
|
|
while (l--)
|
|
*to++ = *name++;
|
|
name--;
|
|
continue;
|
|
}
|
|
#endif
|
|
if (*name == '\\' && name[1])
|
|
name++; // Skipp '\\'
|
|
*to++= *name;
|
|
}
|
|
*to=0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
** save order by and tables in own lists
|
|
****************************************************************************/
|
|
|
|
|
|
bool add_to_list(SQL_LIST &list,Item *item,bool asc)
|
|
{
|
|
ORDER *order;
|
|
Item **item_ptr;
|
|
DBUG_ENTER("add_to_list");
|
|
if (!(order = (ORDER *) sql_alloc(sizeof(ORDER)+sizeof(Item*))))
|
|
DBUG_RETURN(1);
|
|
item_ptr = (Item**) (order+1);
|
|
*item_ptr=item;
|
|
order->item= item_ptr;
|
|
order->asc = asc;
|
|
order->free_me=0;
|
|
link_in_list(&list,(byte*) order,(byte**) &order->next);
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
TABLE_LIST *add_table_to_list(Table_ident *table, LEX_STRING *alias,
|
|
thr_lock_type flags)
|
|
{
|
|
register TABLE_LIST *ptr;
|
|
THD *thd=current_thd;
|
|
char *alias_str;
|
|
const char *current_db;
|
|
DBUG_ENTER("add_table_to_list");
|
|
|
|
if (!table)
|
|
DBUG_RETURN(0); // End of memory
|
|
alias_str= alias ? alias->str : table->table.str;
|
|
if (table->table.length > NAME_LEN ||
|
|
table->db.str && table->db.length > NAME_LEN)
|
|
{
|
|
net_printf(&thd->net,ER_WRONG_TABLE_NAME,table->table.str);
|
|
DBUG_RETURN(0);
|
|
}
|
|
#ifdef FN_LOWER_CASE
|
|
if (!alias) /* Alias is case sensitive */
|
|
if (!(alias_str=sql_strmake(alias_str,table->table.length)))
|
|
DBUG_RETURN(0);
|
|
casedn_str(table->table.str);
|
|
#endif
|
|
if (!(ptr = (TABLE_LIST *) sql_calloc(sizeof(TABLE_LIST))))
|
|
DBUG_RETURN(0); /* purecov: inspected */
|
|
ptr->db= table->db.str;
|
|
ptr->real_name=table->table.str;
|
|
ptr->name=alias_str;
|
|
ptr->lock_type=flags;
|
|
|
|
/* check that used name is unique */
|
|
current_db=thd->db ? thd->db : "";
|
|
for (TABLE_LIST *tables=(TABLE_LIST*) thd->table_list.first ; tables ;
|
|
tables=tables->next)
|
|
{
|
|
if (!strcmp(alias_str,tables->name) &&
|
|
!strcmp(ptr->db ? ptr->db : current_db,
|
|
tables->db ? tables->db : current_db))
|
|
{
|
|
net_printf(&thd->net,ER_NONUNIQ_TABLE,alias_str); /* purecov: tested */
|
|
DBUG_RETURN(0); /* purecov: tested */
|
|
}
|
|
}
|
|
link_in_list(&thd->table_list,(byte*) ptr,(byte**) &ptr->next);
|
|
DBUG_RETURN(ptr);
|
|
}
|
|
|
|
void add_left_join_on(TABLE_LIST *a __attribute__((unused)),
|
|
TABLE_LIST *b,Item *expr)
|
|
{
|
|
b->on_expr=expr;
|
|
}
|
|
|
|
|
|
void add_left_join_natural(TABLE_LIST *a,TABLE_LIST *b)
|
|
{
|
|
b->natural_join=a;
|
|
}
|
|
|
|
/* Check if name is used in table list */
|
|
|
|
static bool check_dup(THD *thd,char *db,char *name,TABLE_LIST *tables)
|
|
{
|
|
char *thd_db=thd->db ? thd->db : any_db;
|
|
for (; tables ; tables=tables->next)
|
|
if (!strcmp(name,tables->real_name) &&
|
|
!strcmp(db ? db : thd_db, tables->db ? tables->db : thd_db))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
bool reload_acl_and_cache(uint options)
|
|
{
|
|
bool result=0;
|
|
|
|
select_errors=0; /* Write if more errors */
|
|
mysql_log.flush(); // Flush log
|
|
if (options & REFRESH_GRANT)
|
|
{
|
|
acl_reload();
|
|
if (!(specialflag & SPECIAL_NO_NEW_FUNC))
|
|
grant_reload();
|
|
}
|
|
if (options & REFRESH_LOG)
|
|
{
|
|
mysql_log.new_file();
|
|
mysql_update_log.new_file();
|
|
}
|
|
if (options & REFRESH_TABLES)
|
|
{
|
|
my_disable_flush_key_blocks=0;
|
|
result=close_cached_tables((options & REFRESH_FAST) ? 0 : 1);
|
|
}
|
|
if (options & REFRESH_HOSTS)
|
|
hostname_cache_refresh();
|
|
if (options & REFRESH_STATUS)
|
|
refresh_status();
|
|
return result;
|
|
}
|
|
|
|
|
|
static void kill_one_thread(THD *thd, ulong thread_id)
|
|
{
|
|
VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list
|
|
I_List_iterator<THD> it(threads);
|
|
THD *tmp;
|
|
uint error=ER_NO_SUCH_THREAD;
|
|
while ((tmp=it++))
|
|
{
|
|
if (tmp->thread_id == thread_id)
|
|
{
|
|
if ((thd->master_access & PROCESS_ACL) ||
|
|
!strcmp(thd->user,tmp->user))
|
|
{
|
|
thr_alarm_kill(tmp->real_id);
|
|
tmp->killed=1;
|
|
error=0;
|
|
if (tmp->mysys_var)
|
|
{
|
|
pthread_mutex_lock(&tmp->mysys_var->mutex);
|
|
tmp->mysys_var->abort=1;
|
|
if (tmp->mysys_var->current_mutex)
|
|
{
|
|
pthread_mutex_lock(tmp->mysys_var->current_mutex);
|
|
pthread_cond_broadcast(tmp->mysys_var->current_cond);
|
|
pthread_mutex_unlock(tmp->mysys_var->current_mutex);
|
|
}
|
|
pthread_mutex_unlock(&tmp->mysys_var->mutex);
|
|
}
|
|
}
|
|
else
|
|
error=ER_KILL_DENIED_ERROR;
|
|
break; // Found thread
|
|
}
|
|
}
|
|
VOID(pthread_mutex_unlock(&LOCK_thread_count));
|
|
if (!error)
|
|
send_ok(&thd->net);
|
|
else
|
|
net_printf(&thd->net,error,thread_id);
|
|
}
|
|
|
|
/* Clear most status variables */
|
|
|
|
static void refresh_status(void)
|
|
{
|
|
pthread_mutex_lock(&THR_LOCK_keycache);
|
|
pthread_mutex_lock(&LOCK_status);
|
|
for (struct show_var_st *ptr=status_vars; ptr->name; ptr++)
|
|
{
|
|
if (ptr->type == SHOW_LONG)
|
|
*(ulong*) ptr->value=0;
|
|
}
|
|
pthread_mutex_unlock(&LOCK_status);
|
|
pthread_mutex_unlock(&THR_LOCK_keycache);
|
|
}
|
|
|