diff options
Diffstat (limited to 'sql-common')
-rw-r--r-- | sql-common/client.c | 765 | ||||
-rw-r--r-- | sql-common/client_plugin.c | 37 | ||||
-rw-r--r-- | sql-common/my_time.c | 647 | ||||
-rw-r--r-- | sql-common/my_user.c | 28 | ||||
-rw-r--r-- | sql-common/mysql_async.c | 94 | ||||
-rw-r--r-- | sql-common/pack.c | 96 |
6 files changed, 980 insertions, 687 deletions
diff --git a/sql-common/client.c b/sql-common/client.c index bec778e7d51..4bebf9ec63e 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -35,12 +35,9 @@ */ #include <my_global.h> - +#include <my_default.h> #include "mysql.h" - -#ifndef __WIN__ -#include <netdb.h> -#endif +#include "hash.h" /* Remove client convenience wrappers */ #undef max_allowed_packet @@ -62,6 +59,7 @@ my_bool net_flush(NET *net); #else /*EMBEDDED_LIBRARY*/ #define CLI_MYSQL_REAL_CONNECT STDCALL mysql_real_connect #endif /*EMBEDDED_LIBRARY*/ + #include <my_sys.h> #include <mysys_err.h> #include <m_string.h> @@ -70,6 +68,7 @@ my_bool net_flush(NET *net); #include "mysqld_error.h" #include "errmsg.h" #include <violite.h> + #if !defined(__WIN__) #include <my_pthread.h> /* because of signal() */ #endif /* !defined(__WIN__) */ @@ -77,14 +76,12 @@ my_bool net_flush(NET *net); #include <sys/stat.h> #include <signal.h> #include <time.h> + #ifdef HAVE_PWD_H #include <pwd.h> #endif + #if !defined(__WIN__) -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <netdb.h> #ifdef HAVE_SELECT_H # include <select.h> #endif @@ -96,9 +93,7 @@ my_bool net_flush(NET *net); # include <sys/un.h> #endif -#if defined(__WIN__) -#define perror(A) -#else +#ifndef _WIN32 #include <errno.h> #define SOCKET_ERROR -1 #endif @@ -138,11 +133,7 @@ const char *def_shared_memory_base_name= default_shared_memory_base_name; static void mysql_close_free_options(MYSQL *mysql); static void mysql_close_free(MYSQL *mysql); static void mysql_prune_stmt_list(MYSQL *mysql); -static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length); - -#if !defined(__WIN__) -static int wait_for_data(my_socket fd, uint timeout); -#endif +static int cli_report_progress(MYSQL *mysql, char *packet, uint length); CHARSET_INFO *default_client_charset_info = &my_charset_latin1; @@ -150,187 +141,36 @@ CHARSET_INFO *default_client_charset_info = &my_charset_latin1; unsigned int mysql_server_last_errno; char mysql_server_last_error[MYSQL_ERRMSG_SIZE]; -/**************************************************************************** - A modified version of connect(). my_connect() allows you to specify - a timeout value, in seconds, that we should wait until we - derermine we can't connect to a particular host. If timeout is 0, - my_connect() will behave exactly like connect(). - - Base version coded by Steve Bernacki, Jr. <steve@navinet.net> -*****************************************************************************/ - -int my_connect(my_socket fd, const struct sockaddr *name, uint namelen, - uint timeout) -{ -#if defined(__WIN__) - DBUG_ENTER("my_connect"); - DBUG_RETURN(connect(fd, (struct sockaddr*) name, namelen)); -#else - int flags, res, s_err; - DBUG_ENTER("my_connect"); - DBUG_PRINT("enter", ("fd: %d timeout: %u", fd, timeout)); - - /* - If they passed us a timeout of zero, we should behave - exactly like the normal connect() call does. - */ - - if (timeout == 0) - DBUG_RETURN(connect(fd, (struct sockaddr*) name, namelen)); - - flags = fcntl(fd, F_GETFL, 0); /* Set socket to not block */ -#ifdef O_NONBLOCK - fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* and save the flags.. */ -#endif - - DBUG_PRINT("info", ("connecting non-blocking")); - res= connect(fd, (struct sockaddr*) name, namelen); - DBUG_PRINT("info", ("connect result: %d errno: %d", res, errno)); - s_err= errno; /* Save the error... */ - fcntl(fd, F_SETFL, flags); - if ((res != 0) && (s_err != EINPROGRESS)) - { - errno= s_err; /* Restore it */ - DBUG_RETURN(-1); - } - if (res == 0) /* Connected quickly! */ - DBUG_RETURN(0); - DBUG_RETURN(wait_for_data(fd, timeout)); -#endif -} - +/** + Convert the connect timeout option to a timeout value for VIO + functions (vio_socket_connect() and vio_io_wait()). -/* - Wait up to timeout seconds for a connection to be established. + @param mysql Connection handle (client side). - We prefer to do this with poll() as there is no limitations with this. - If not, we will use select() + @return The timeout value in milliseconds, or -1 if no timeout. */ -#if !defined(__WIN__) - -static int wait_for_data(my_socket fd, uint timeout) +static int get_vio_connect_timeout(MYSQL *mysql) { -#ifdef HAVE_POLL - struct pollfd ufds; - int res; - DBUG_ENTER("wait_for_data"); - - DBUG_PRINT("info", ("polling")); - ufds.fd= fd; - ufds.events= POLLIN | POLLPRI; - if (!(res= poll(&ufds, 1, (int) timeout*1000))) - { - DBUG_PRINT("info", ("poll timed out")); - errno= EINTR; - DBUG_RETURN(-1); - } - DBUG_PRINT("info", - ("poll result: %d errno: %d revents: 0x%02d events: 0x%02d", - res, errno, ufds.revents, ufds.events)); - if (res < 0 || !(ufds.revents & (POLLIN | POLLPRI))) - DBUG_RETURN(-1); - /* - At this point, we know that something happened on the socket. - But this does not means that everything is alright. - The connect might have failed. We need to retrieve the error code - from the socket layer. We must return success only if we are sure - that it was really a success. Otherwise we might prevent the caller - from trying another address to connect to. - */ - { - int s_err; - socklen_t s_len= sizeof(s_err); - - DBUG_PRINT("info", ("Get SO_ERROR from non-blocked connected socket.")); - res= getsockopt(fd, SOL_SOCKET, SO_ERROR, &s_err, &s_len); - DBUG_PRINT("info", ("getsockopt res: %d s_err: %d", res, s_err)); - if (res) - DBUG_RETURN(res); - /* getsockopt() was successful, check the retrieved status value. */ - if (s_err) - { - errno= s_err; - DBUG_RETURN(-1); - } - /* Status from connect() is zero. Socket is successfully connected. */ - } - DBUG_RETURN(0); -#else - SOCKOPT_OPTLEN_TYPE s_err_size = sizeof(uint); - fd_set sfds; - struct timeval tv; - time_t start_time, now_time; - int res, s_err; - DBUG_ENTER("wait_for_data"); - - if (fd >= FD_SETSIZE) /* Check if wrong error */ - DBUG_RETURN(0); /* Can't use timeout */ + int timeout_ms; + uint timeout_sec; /* - Our connection is "in progress." We can use the select() call to wait - up to a specified period of time for the connection to suceed. - If select() returns 0 (after waiting howevermany seconds), our socket - never became writable (host is probably unreachable.) Otherwise, if - select() returns 1, then one of two conditions exist: - - 1. An error occured. We use getsockopt() to check for this. - 2. The connection was set up sucessfully: getsockopt() will - return 0 as an error. - - Thanks goes to Andrew Gierth <andrew@erlenstar.demon.co.uk> - who posted this method of timing out a connect() in - comp.unix.programmer on August 15th, 1997. + A timeout of 0 means no timeout. Also, the connect_timeout + option value is in seconds, while VIO timeouts are measured + in milliseconds. Hence, check for a possible overflow. In + case of overflow, set to no timeout. */ + timeout_sec= mysql->options.connect_timeout; - FD_ZERO(&sfds); - FD_SET(fd, &sfds); - /* - select could be interrupted by a signal, and if it is, - the timeout should be adjusted and the select restarted - to work around OSes that don't restart select and - implementations of select that don't adjust tv upon - failure to reflect the time remaining - */ - start_time= my_time(0); - for (;;) - { - tv.tv_sec = (long) timeout; - tv.tv_usec = 0; -#if defined(HPUX10) - if ((res = select(fd+1, NULL, (int*) &sfds, NULL, &tv)) > 0) - break; -#else - if ((res = select(fd+1, NULL, &sfds, NULL, &tv)) > 0) - break; -#endif - if (res == 0) /* timeout */ - DBUG_RETURN(-1); - now_time= my_time(0); - timeout-= (uint) (now_time - start_time); - if (errno != EINTR || (int) timeout <= 0) - DBUG_RETURN(-1); - } - - /* - select() returned something more interesting than zero, let's - see if we have any errors. If the next two statements pass, - we've got an open socket! - */ - - s_err=0; - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*) &s_err, &s_err_size) != 0) - DBUG_RETURN(-1); + if (!timeout_sec || (timeout_sec > INT_MAX/1000)) + timeout_ms= -1; + else + timeout_ms= (int) (timeout_sec * 1000); - if (s_err) - { /* getsockopt could succeed */ - errno = s_err; - DBUG_RETURN(-1); /* but return an error... */ - } - DBUG_RETURN(0); /* ok */ -#endif /* HAVE_POLL */ + return timeout_ms; } -#endif /* !defined(__WIN__) */ + /** Set the internal error message to mysql handler @@ -744,14 +584,14 @@ cli_safe_read(MYSQL *mysql) restart: if (net->vio != 0) - len=my_net_read(net); - + len= my_net_read_packet(net, 0); + if (len == packet_error || len == 0) { DBUG_PRINT("error",("Wrong connection or packet. fd: %s len: %lu", vio_description(net->vio),len)); #ifdef MYSQL_SERVER - if (net->vio && vio_was_interrupted(net->vio)) + if (net->vio && (net->last_errno == ER_NET_READ_INTERRUPTED)) return (packet_error); #endif /*MYSQL_SERVER*/ end_server(mysql); @@ -763,7 +603,7 @@ restart: { if (len > 3) { - uchar *pos= net->read_pos+1; + char *pos= (char*) net->read_pos+1; uint last_errno=uint2korr(pos); if (last_errno == 65535 && @@ -781,9 +621,9 @@ restart: pos+=2; len-=2; - if (protocol_41(mysql) && (char) pos[0] == '#') + if (protocol_41(mysql) && pos[0] == '#') { - strmake_buf(net->sqlstate, (char*) pos+1); + strmake_buf(net->sqlstate, pos+1); pos+= SQLSTATE_LENGTH+1; } else @@ -797,7 +637,7 @@ restart: } (void) strmake(net->last_error,(char*) pos, - min((uint) len,(uint) sizeof(net->last_error)-1)); + MY_MIN((uint) len,(uint) sizeof(net->last_error)-1)); } else set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); @@ -898,7 +738,10 @@ void free_old_query(MYSQL *mysql) DBUG_ENTER("free_old_query"); if (mysql->fields) free_root(&mysql->field_alloc,MYF(0)); - init_alloc_root(&mysql->field_alloc,8192,0); /* Assume rowlength < 8192 */ + /* Assume rowlength < 8192 */ + init_alloc_root(&mysql->field_alloc, 8192, 0, + MYF(mysql->options.use_thread_specific_memory ? + MY_THREAD_SPECIFIC : 0)); mysql->fields= 0; mysql->field_count= 0; /* For API */ mysql->warning_count= 0; @@ -1044,10 +887,11 @@ static void cli_flush_use_result(MYSQL *mysql, my_bool flush_all_results) 1 error */ -static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length) +static int cli_report_progress(MYSQL *mysql, char *pkt, uint length) { uint stage, max_stage, proc_length; double progress; + uchar *packet= (uchar*)pkt; uchar *start= packet; if (length < 5) @@ -1146,6 +990,7 @@ static const char *default_options[]= "ssl-cipher", "max-allowed-packet", "protocol", "shared-memory-base-name", "multi-results", "multi-statements", "multi-queries", "secure-auth", "report-data-truncation", "plugin-dir", "default-auth", + "bind-address", "ssl-crl", "ssl-crlpath", "enable-cleartext-plugin", NullS }; @@ -1158,6 +1003,7 @@ enum option_id { OPT_ssl_cipher, OPT_max_allowed_packet, OPT_protocol, OPT_shared_memory_base_name, OPT_multi_results, OPT_multi_statements, OPT_multi_queries, OPT_secure_auth, OPT_report_data_truncation, OPT_plugin_dir, OPT_default_auth, + OPT_bind_address, OPT_ssl_crl, OPT_ssl_crlpath, OPT_enable_cleartext_plugin, OPT_keep_this_one_last }; @@ -1178,11 +1024,11 @@ static int add_init_command(struct st_mysql_options *options, const char *cmd) { options->init_commands= (DYNAMIC_ARRAY*)my_malloc(sizeof(DYNAMIC_ARRAY), MYF(MY_WME)); - init_dynamic_array(options->init_commands,sizeof(char*),5,5); + my_init_dynamic_array(options->init_commands,sizeof(char*),5, 5, MYF(0)); } if (!(tmp= my_strdup(cmd,MYF(MY_WME))) || - insert_dynamic(options->init_commands, (uchar*)&tmp)) + insert_dynamic(options->init_commands, &tmp)) { my_free(tmp); return 1; @@ -1191,31 +1037,57 @@ static int add_init_command(struct st_mysql_options *options, const char *cmd) return 0; } -#define EXTENSION_SET(OPTS, X, VAL) \ - if (!(OPTS)->extension) \ + +#define ALLOCATE_EXTENSIONS(OPTS) \ (OPTS)->extension= (struct st_mysql_options_extention *) \ my_malloc(sizeof(struct st_mysql_options_extention), \ - MYF(MY_WME | MY_ZEROFILL)); \ - (OPTS)->extension->X= VAL; + MYF(MY_WME | MY_ZEROFILL)) \ + + +#define ENSURE_EXTENSIONS_PRESENT(OPTS) \ + do { \ + if (!(OPTS)->extension) \ + ALLOCATE_EXTENSIONS(OPTS); \ + } while (0) + + +#define EXTENSION_SET_STRING_X(OPTS, X, STR, dup) \ + do { \ + if ((OPTS)->extension) \ + my_free((OPTS)->extension->X); \ + else \ + ALLOCATE_EXTENSIONS(OPTS); \ + (OPTS)->extension->X= ((STR) != NULL) ? \ + dup((STR), MYF(MY_WME)) : NULL; \ + } while (0) + +#define EXTENSION_SET_STRING(OPTS, X, STR) \ + EXTENSION_SET_STRING_X(OPTS, X, STR, my_strdup) -#define EXTENSION_SET_STRING(OPTS, X, STR) \ - if ((OPTS)->extension) \ - my_free((OPTS)->extension->X); \ - EXTENSION_SET(OPTS, X, my_strdup((STR), MYF(MY_WME))); #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) -static char *set_ssl_option_unpack_path(const char *arg) +#define SET_SSL_OPTION_X(OPTS, opt_var, arg, dup) \ + my_free((OPTS)->opt_var); \ + (OPTS)->opt_var= arg ? dup(arg, MYF(MY_WME)) : NULL; +#define EXTENSION_SET_SSL_STRING_X(OPTS, X, STR, dup) \ + EXTENSION_SET_STRING_X((OPTS), X, (STR), dup); + +static char *set_ssl_option_unpack_path(const char *arg, myf flags) { - char *opt_var= NULL; - if (arg) - { - char buff[FN_REFLEN + 1]; - unpack_filename(buff, (char *)arg); - opt_var= my_strdup(buff, MYF(MY_WME)); - } - return opt_var; + char buff[FN_REFLEN + 1]; + unpack_filename(buff, (char *)arg); + return my_strdup(buff, flags); } -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + +#else +#define SET_SSL_OPTION_X(OPTS, opt_var,arg, dup) do { } while(0) +#define EXTENSION_SET_SSL_STRING_X(OPTS, X, STR, dup) do { } while(0) +#endif /* defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) */ + +#define SET_SSL_OPTION(OPTS, opt_var,arg) SET_SSL_OPTION_X(OPTS, opt_var, arg, my_strdup) +#define EXTENSION_SET_SSL_STRING(OPTS, X, STR) EXTENSION_SET_SSL_STRING_X(OPTS, X, STR, my_strdup) +#define SET_SSL_PATH_OPTION(OPTS, opt_var,arg) SET_SSL_OPTION_X(OPTS, opt_var, arg, set_ssl_option_unpack_path) +#define EXTENSION_SET_SSL_PATH_STRING(OPTS, X, STR) EXTENSION_SET_SSL_STRING_X(OPTS, X, STR, set_ssl_option_unpack_path) void mysql_read_default_options(struct st_mysql_options *options, const char *filename,const char *group) @@ -1320,35 +1192,27 @@ void mysql_read_default_options(struct st_mysql_options *options, case OPT_return_found_rows: options->client_flag|=CLIENT_FOUND_ROWS; break; -#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) case OPT_ssl_key: - my_free(options->ssl_key); - options->ssl_key = my_strdup(opt_arg, MYF(MY_WME)); + SET_SSL_OPTION(options, ssl_key, opt_arg); break; case OPT_ssl_cert: - my_free(options->ssl_cert); - options->ssl_cert = my_strdup(opt_arg, MYF(MY_WME)); + SET_SSL_OPTION(options, ssl_cert, opt_arg); break; case OPT_ssl_ca: - my_free(options->ssl_ca); - options->ssl_ca = my_strdup(opt_arg, MYF(MY_WME)); + SET_SSL_OPTION(options, ssl_ca, opt_arg); break; case OPT_ssl_capath: - my_free(options->ssl_capath); - options->ssl_capath = my_strdup(opt_arg, MYF(MY_WME)); + SET_SSL_OPTION(options, ssl_capath, opt_arg); break; case OPT_ssl_cipher: - my_free(options->ssl_cipher); - options->ssl_cipher= my_strdup(opt_arg, MYF(MY_WME)); + SET_SSL_OPTION(options, ssl_cipher, opt_arg); + break; + case OPT_ssl_crl: + EXTENSION_SET_SSL_STRING(options, ssl_crl, opt_arg); + break; + case OPT_ssl_crlpath: + EXTENSION_SET_SSL_STRING(options, ssl_crlpath, opt_arg); break; -#else - case OPT_ssl_key: - case OPT_ssl_cert: - case OPT_ssl_ca: - case OPT_ssl_capath: - case OPT_ssl_cipher: - break; -#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ case OPT_character_sets_dir: my_free(options->charset_dir); options->charset_dir = my_strdup(opt_arg, MYF(MY_WME)); @@ -1399,11 +1263,11 @@ void mysql_read_default_options(struct st_mysql_options *options, options->secure_auth= TRUE; break; case OPT_report_data_truncation: - options->report_data_truncation= opt_arg ? test(atoi(opt_arg)) : 1; + options->report_data_truncation= opt_arg ? MY_TEST(atoi(opt_arg)) : 1; break; case OPT_plugin_dir: { - char buff[FN_REFLEN], buff2[FN_REFLEN]; + char buff[FN_REFLEN], buff2[FN_REFLEN], *buff2_ptr= buff2; if (strlen(opt_arg) >= FN_REFLEN) opt_arg[FN_REFLEN]= '\0'; if (my_realpath(buff, opt_arg, 0)) @@ -1413,7 +1277,7 @@ void mysql_read_default_options(struct st_mysql_options *options, break; } convert_dirname(buff2, buff, NULL); - EXTENSION_SET_STRING(options, plugin_dir, buff2); + EXTENSION_SET_STRING(options, plugin_dir, buff2_ptr); } break; case OPT_default_auth: @@ -1611,7 +1475,10 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); DBUG_RETURN(0); } - init_alloc_root(&result->alloc,8192,0); /* Assume rowlength < 8192 */ + /* Assume rowlength < 8192 */ + init_alloc_root(&result->alloc, 8192, 0, + MYF(mysql->options.use_thread_specific_memory ? + MY_THREAD_SPECIFIC : 0)); result->alloc.min_malloc=sizeof(MYSQL_ROWS); prev_ptr= &result->data; result->rows=0; @@ -1807,12 +1674,11 @@ mysql_init(MYSQL *mysql) /* - Fill in SSL part of MYSQL structure and set 'use_ssl' flag. + Fill in SSL part of MYSQL structure. NB! Errors are not reported until you do mysql_real_connect. + use_ssl is set in send_client_reply_packet if any ssl option is set. */ -#define strdup_if_not_null(A) (A) == 0 ? 0 : my_strdup((A),MYF(MY_WME)) - my_bool STDCALL mysql_ssl_set(MYSQL *mysql __attribute__((unused)) , const char *key __attribute__((unused)), @@ -1821,21 +1687,18 @@ mysql_ssl_set(MYSQL *mysql __attribute__((unused)) , const char *capath __attribute__((unused)), const char *cipher __attribute__((unused))) { + my_bool result= 0; DBUG_ENTER("mysql_ssl_set"); #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) - my_free(mysql->options.ssl_key); - my_free(mysql->options.ssl_cert); - my_free(mysql->options.ssl_ca); - my_free(mysql->options.ssl_capath); - my_free(mysql->options.ssl_cipher); - mysql->options.ssl_key= set_ssl_option_unpack_path(key); - mysql->options.ssl_cert= set_ssl_option_unpack_path(cert); - mysql->options.ssl_ca= set_ssl_option_unpack_path(ca); - mysql->options.ssl_capath= set_ssl_option_unpack_path(capath); - mysql->options.ssl_cipher= strdup_if_not_null(cipher); + result= (mysql_options(mysql, MYSQL_OPT_SSL_KEY, key) | + mysql_options(mysql, MYSQL_OPT_SSL_CERT, cert) | + mysql_options(mysql, MYSQL_OPT_SSL_CA, ca) | + mysql_options(mysql, MYSQL_OPT_SSL_CAPATH, capath) | + mysql_options(mysql, MYSQL_OPT_SSL_CIPHER, cipher) ? + 1 : 0); #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ mysql->options.use_ssl= TRUE; - DBUG_RETURN(0); + DBUG_RETURN(result); } @@ -1857,6 +1720,11 @@ mysql_ssl_free(MYSQL *mysql __attribute__((unused))) my_free(mysql->options.ssl_ca); my_free(mysql->options.ssl_capath); my_free(mysql->options.ssl_cipher); + if (mysql->options.extension) + { + my_free(mysql->options.extension->ssl_crl); + my_free(mysql->options.extension->ssl_crlpath); + } if (ssl_fd) SSL_CTX_free(ssl_fd->ssl_context); my_free(mysql->connector_fd); @@ -1865,6 +1733,11 @@ mysql_ssl_free(MYSQL *mysql __attribute__((unused))) mysql->options.ssl_ca = 0; mysql->options.ssl_capath = 0; mysql->options.ssl_cipher= 0; + if (mysql->options.extension) + { + mysql->options.extension->ssl_crl = 0; + mysql->options.extension->ssl_crlpath = 0; + } mysql->options.use_ssl = FALSE; mysql->connector_fd = 0; DBUG_VOID_RETURN; @@ -2406,6 +2279,7 @@ static auth_plugin_t old_password_client_plugin= old_password_auth_client }; + struct st_mysql_client_plugin *mysql_client_builtins[]= { (struct st_mysql_client_plugin *)&native_password_client_plugin, @@ -2413,6 +2287,67 @@ struct st_mysql_client_plugin *mysql_client_builtins[]= 0 }; + +static uchar * +write_length_encoded_string3(uchar *buf, char *string, size_t length) +{ + buf= net_store_length(buf, length); + memcpy(buf, string, length); + buf+= length; + return buf; +} + + +uchar * +send_client_connect_attrs(MYSQL *mysql, uchar *buf) +{ + /* check if the server supports connection attributes */ + if (mysql->server_capabilities & CLIENT_CONNECT_ATTRS) + { + + /* Always store the length if the client supports it */ + buf= net_store_length(buf, + mysql->options.extension ? + mysql->options.extension->connection_attributes_length : + 0); + + /* check if we have connection attributes */ + if (mysql->options.extension && + my_hash_inited(&mysql->options.extension->connection_attributes)) + { + HASH *attrs= &mysql->options.extension->connection_attributes; + ulong idx; + + /* loop over and dump the connection attributes */ + for (idx= 0; idx < attrs->records; idx++) + { + LEX_STRING *attr= (LEX_STRING *) my_hash_element(attrs, idx); + LEX_STRING *key= attr, *value= attr + 1; + + /* we can't have zero length keys */ + DBUG_ASSERT(key->length); + + buf= write_length_encoded_string3(buf, key->str, key->length); + buf= write_length_encoded_string3(buf, value->str, value->length); + } + } + } + return buf; +} + + +static size_t get_length_store_length(size_t length) +{ + /* as defined in net_store_length */ + #define MAX_VARIABLE_STRING_LENGTH 9 + uchar length_buffer[MAX_VARIABLE_STRING_LENGTH], *ptr; + + ptr= net_store_length(length_buffer, length); + + return ptr - &length_buffer[0]; +} + + /* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ typedef struct { int (*read_packet)(struct st_plugin_vio *vio, uchar **buf); @@ -2431,6 +2366,29 @@ typedef struct { int last_read_packet_len; /**< the length of the last *read* packet */ } MCPVIO_EXT; + +/* + Write 1-8 bytes of string length header infromation to dest depending on + value of src_len, then copy src_len bytes from src to dest. + + @param dest Destination buffer of size src_len+8 + @param dest_end One byte past the end of the dest buffer + @param src Source buff of size src_len + @param src_end One byte past the end of the src buffer + + @return pointer dest+src_len+header size or NULL if +*/ + +static uchar *write_length_encoded_string4(uchar *dst, size_t dst_len, + const uchar *src, size_t src_len) +{ + uchar *to= safe_net_store_length(dst, dst_len, src_len); + if (to == NULL) + return NULL; + memcpy(to, src, src_len); + return to + src_len; +} + /** sends a COM_CHANGE_USER command with a caller provided payload @@ -2456,8 +2414,13 @@ static int send_change_user_packet(MCPVIO_EXT *mpvio, MYSQL *mysql= mpvio->mysql; char *buff, *end; int res= 1; + size_t connect_attrs_len= + (mysql->server_capabilities & CLIENT_CONNECT_ATTRS && + mysql->options.extension) ? + mysql->options.extension->connection_attributes_length : 0; - buff= my_alloca(USERNAME_LENGTH+1 + data_len+1 + NAME_LEN+1 + 2 + NAME_LEN+1); + buff= my_alloca(USERNAME_LENGTH + data_len + 1 + NAME_LEN + 2 + NAME_LEN + + connect_attrs_len + 9 /* for the length of the attrs */); end= strmake(buff, mysql->user, USERNAME_LENGTH) + 1; @@ -2494,6 +2457,8 @@ static int send_change_user_packet(MCPVIO_EXT *mpvio, if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + end= (char *) send_client_connect_attrs(mysql, (uchar *) end); + res= simple_command(mysql, COM_CHANGE_USER, (uchar*)buff, (ulong)(end-buff), 1); @@ -2502,6 +2467,7 @@ error: return res; } +#define MAX_CONNECTION_ATTR_STORAGE_LENGTH 65536 /** sends a client authentication packet (second packet in the 3-way handshake) @@ -2524,7 +2490,7 @@ error: 1 charset number 23 reserved (always 0) n user name, \0-terminated - n plugin auth data (e.g. scramble), length (1 byte) coded + n plugin auth data (e.g. scramble), length encoded n database name, \0-terminated (if CLIENT_CONNECT_WITH_DB is set in the capabilities) n client auth plugin name - \0-terminated string, @@ -2539,10 +2505,21 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, MYSQL *mysql= mpvio->mysql; NET *net= &mysql->net; char *buff, *end; + size_t buff_size; + size_t connect_attrs_len= + (mysql->server_capabilities & CLIENT_CONNECT_ATTRS && + mysql->options.extension) ? + mysql->options.extension->connection_attributes_length : 0; + + DBUG_ASSERT(connect_attrs_len < MAX_CONNECTION_ATTR_STORAGE_LENGTH); + + /* + see end= buff+32 below, fixed size of the packet is 32 bytes. + +9 because data is a length encoded binary where meta data size is max 9. + */ + buff_size= 33 + USERNAME_LENGTH + data_len + 9 + NAME_LEN + NAME_LEN + connect_attrs_len + 9; + buff= my_alloca(buff_size); - /* see end= buff+32 below, fixed size of the packet is 32 bytes */ - buff= my_alloca(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN); - mysql->client_flag|= mysql->options.client_flag; mysql->client_flag|= CLIENT_CAPABILITIES; @@ -2634,7 +2611,11 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, options->ssl_ca, options->ssl_capath, options->ssl_cipher, - &ssl_init_error))) + &ssl_init_error, + options->extension ? + options->extension->ssl_crl : NULL, + options->extension ? + options->extension->ssl_crlpath : NULL))) { set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, ER(CR_SSL_CONNECTION_ERROR), sslGetErrString(ssl_init_error)); @@ -2685,9 +2666,19 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, { if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) { - *end++= data_len; - memcpy(end, data, data_len); - end+= data_len; + if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) + end= (char*)write_length_encoded_string4((uchar*)end, + buff_size, data, data_len); + else + { + if (data_len > 255) + goto error; + *end++= data_len; + memcpy(end, data, data_len); + end+= data_len; + } + if (end == NULL) + goto error; } else { @@ -2709,6 +2700,8 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + end= (char *) send_client_connect_attrs(mysql, (uchar *) end); + /* Write authentication package */ if (my_net_write(net, (uchar*) buff, (size_t) (end-buff)) || net_flush(net)) { @@ -2836,21 +2829,21 @@ void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info) switch (vio->type) { case VIO_TYPE_TCPIP: info->protocol= MYSQL_VIO_TCP; - info->socket= vio->sd; + info->socket= vio_fd(vio); return; case VIO_TYPE_SOCKET: info->protocol= MYSQL_VIO_SOCKET; - info->socket= vio->sd; + info->socket= vio_fd(vio); return; case VIO_TYPE_SSL: { struct sockaddr addr; SOCKET_SIZE_TYPE addrlen= sizeof(addr); - if (getsockname(vio->sd, &addr, &addrlen)) + if (getsockname(vio_fd(vio), &addr, &addrlen)) return; info->protocol= addr.sa_family == AF_UNIX ? MYSQL_VIO_SOCKET : MYSQL_VIO_TCP; - info->socket= vio->sd; + info->socket= vio_fd(vio); return; } #ifdef _WIN32 @@ -3053,20 +3046,67 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, static int connect_sync_or_async(MYSQL *mysql, NET *net, my_socket fd, - const struct sockaddr *name, uint namelen) + struct sockaddr *name, uint namelen) { + int vio_timeout = get_vio_connect_timeout(mysql); + if (mysql->options.extension && mysql->options.extension->async_context && mysql->options.extension->async_context->active) { my_bool old_mode; vio_blocking(net->vio, FALSE, &old_mode); return my_connect_async(mysql->options.extension->async_context, fd, - name, namelen, mysql->options.connect_timeout); + name, namelen, vio_timeout); } - return my_connect(fd, name, namelen, mysql->options.connect_timeout); + return vio_socket_connect(net->vio, name, namelen, vio_timeout); } + +/** set some default attributes */ +static int +set_connect_attributes(MYSQL *mysql, char *buff, size_t buf_len) +{ + int rc= 0; + + /* + Clean up any values set by the client code. We want these options as + consistent as possible + */ + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_name"); + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_os"); + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_platform"); + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_pid"); + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_thread"); + rc+= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_version"); + + /* + Now let's set up some values + */ + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "_client_name", "libmysql"); + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "_client_version", PACKAGE_VERSION); + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "_os", SYSTEM_TYPE); + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "_platform", MACHINE_TYPE); +#ifdef __WIN__ + snprintf(buff, buf_len, "%lu", (ulong) GetCurrentProcessId()); +#else + snprintf(buff, buf_len, "%lu", (ulong) getpid()); +#endif + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_pid", buff); + +#ifdef __WIN__ + snprintf(buff, buf_len, "%lu", (ulong) GetCurrentThreadId()); + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_thread", buff); +#endif + + return rc > 0 ? 1 : 0; +} + + MYSQL * STDCALL CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, const char *passwd, const char *db, @@ -3100,6 +3140,9 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, DBUG_RETURN(0); } + if (set_connect_attributes(mysql, buff, sizeof(buff))) + DBUG_RETURN(0); + mysql->methods= &client_methods; net->vio = 0; /* If something goes wrong */ mysql->client_flag=0; /* For handshake */ @@ -3306,7 +3349,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, */ DBUG_PRINT("info",("IPV6 getaddrinfo error %d", gai_errno)); set_mysql_extended_error(mysql, CR_UNKNOWN_HOST, unknown_sqlstate, - ER(CR_UNKNOWN_HOST), host, errno); + ER(CR_UNKNOWN_HOST), host, gai_errno); goto error; } @@ -3391,7 +3434,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, if (mysql->options.extension && mysql->options.extension->async_context) net->vio->async_context= mysql->options.extension->async_context; - if (my_net_init(net, net->vio)) + if (my_net_init(net, net->vio, MYF(0))) { vio_delete(net->vio); net->vio = 0; @@ -3414,7 +3457,8 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, /* Get version info */ mysql->protocol_version= PROTOCOL_VERSION; /* Assume this */ if (mysql->options.connect_timeout && - vio_poll_read(net->vio, mysql->options.connect_timeout)) + (vio_io_wait(net->vio, VIO_IO_EVENT_READ, + get_vio_connect_timeout(mysql)) < 1)) { set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER(CR_SERVER_LOST_EXTENDED), @@ -3625,6 +3669,9 @@ error: /* Free alloced memory */ end_server(mysql); mysql_close_free(mysql); + if (!(client_flag & CLIENT_REMEMBER_OPTIONS) && + !mysql->options.extension->async_context) + mysql_close_free_options(mysql); } DBUG_RETURN(0); } @@ -3695,7 +3742,7 @@ my_bool mysql_reconnect(MYSQL *mysql) } if (!mysql_real_connect(&tmp_mysql,mysql->host,mysql->user,mysql->passwd, mysql->db, mysql->port, mysql->unix_socket, - mysql->client_flag)) + mysql->client_flag | CLIENT_REMEMBER_OPTIONS)) { if (ctxt) my_context_install_suspend_resume_hook(ctxt, NULL, NULL); @@ -3799,6 +3846,7 @@ static void mysql_close_free_options(MYSQL *mysql) struct mysql_async_context *ctxt= mysql->options.extension->async_context; my_free(mysql->options.extension->plugin_dir); my_free(mysql->options.extension->default_auth); + my_hash_free(&mysql->options.extension->connection_attributes); if (ctxt) { my_context_destroy(&ctxt->async_context); @@ -4052,7 +4100,6 @@ mysql_send_query(MYSQL* mysql, const char* query, ulong length) DBUG_RETURN(simple_command(mysql, COM_QUERY, (uchar*) query, length, 1)); } - int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { @@ -4312,11 +4359,14 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) mysql->options.secure_auth= *(my_bool *) arg; break; case MYSQL_REPORT_DATA_TRUNCATION: - mysql->options.report_data_truncation= test(*(my_bool *) arg); + mysql->options.report_data_truncation= MY_TEST(*(my_bool*) arg); break; case MYSQL_OPT_RECONNECT: mysql->reconnect= *(my_bool *) arg; break; + case MYSQL_OPT_USE_THREAD_SPECIFIC_MEMORY: + mysql->options.use_thread_specific_memory= *(my_bool *) arg; + break; case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: if (*(my_bool*) arg) mysql->options.client_flag|= CLIENT_SSL_VERIFY_SERVER_CERT; @@ -4370,10 +4420,171 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) my_free(ctxt); DBUG_RETURN(1); } - EXTENSION_SET(&(mysql->options), async_context, ctxt) + ENSURE_EXTENSIONS_PRESENT(&(mysql->options)); + mysql->options.extension->async_context= ctxt; if (mysql->net.vio) mysql->net.vio->async_context= ctxt; break; + case MYSQL_OPT_SSL_KEY: + SET_SSL_PATH_OPTION(&mysql->options,ssl_key, arg); + break; + case MYSQL_OPT_SSL_CERT: + SET_SSL_PATH_OPTION(&mysql->options, ssl_cert, arg); + break; + case MYSQL_OPT_SSL_CA: + SET_SSL_PATH_OPTION(&mysql->options,ssl_ca, arg); + break; + case MYSQL_OPT_SSL_CAPATH: + SET_SSL_PATH_OPTION(&mysql->options,ssl_capath, arg); + break; + case MYSQL_OPT_SSL_CIPHER: + SET_SSL_OPTION(&mysql->options,ssl_cipher, arg); + break; + case MYSQL_OPT_SSL_CRL: + EXTENSION_SET_SSL_PATH_STRING(&mysql->options, ssl_crl, arg); + break; + case MYSQL_OPT_SSL_CRLPATH: + EXTENSION_SET_SSL_PATH_STRING(&mysql->options, ssl_crlpath, arg); + break; + case MYSQL_OPT_CONNECT_ATTR_RESET: + ENSURE_EXTENSIONS_PRESENT(&mysql->options); + if (my_hash_inited(&mysql->options.extension->connection_attributes)) + { + my_hash_free(&mysql->options.extension->connection_attributes); + mysql->options.extension->connection_attributes_length= 0; + } + break; + case MYSQL_OPT_CONNECT_ATTR_DELETE: + ENSURE_EXTENSIONS_PRESENT(&mysql->options); + if (my_hash_inited(&mysql->options.extension->connection_attributes)) + { + size_t len; + uchar *elt; + + len= arg ? strlen(arg) : 0; + + if (len) + { + elt= my_hash_search(&mysql->options.extension->connection_attributes, + arg, len); + if (elt) + { + LEX_STRING *attr= (LEX_STRING *) elt; + LEX_STRING *key= attr, *value= attr + 1; + + mysql->options.extension->connection_attributes_length-= + get_length_store_length(key->length) + key->length + + get_length_store_length(value->length) + value->length; + + my_hash_delete(&mysql->options.extension->connection_attributes, + elt); + + } + } + } + break; + default: + break; + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} +/** + A function to return the key from a connection attribute +*/ +uchar * +get_attr_key(LEX_STRING *part, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= part[0].length; + return (uchar *) part[0].str; +} + +int STDCALL +mysql_options4(MYSQL *mysql,enum mysql_option option, + const void *arg1, const void *arg2) +{ + DBUG_ENTER("mysql_option"); + DBUG_PRINT("enter",("option: %d",(int) option)); + + switch (option) + { + case MYSQL_OPT_CONNECT_ATTR_ADD: + { + LEX_STRING *elt; + char *key, *value; + size_t key_len= arg1 ? strlen(arg1) : 0, + value_len= arg2 ? strlen(arg2) : 0; + size_t attr_storage_length= key_len + value_len; + + /* we can't have a zero length key */ + if (!key_len) + { + set_mysql_error(mysql, CR_INVALID_PARAMETER_NO, unknown_sqlstate); + DBUG_RETURN(1); + } + + /* calculate the total storage length of the attribute */ + attr_storage_length+= get_length_store_length(key_len); + attr_storage_length+= get_length_store_length(value_len); + + ENSURE_EXTENSIONS_PRESENT(&mysql->options); + + /* + Throw and error if the maximum combined length of the attribute value + will be greater than the maximum that we can safely transmit. + */ + if (attr_storage_length + + mysql->options.extension->connection_attributes_length > + MAX_CONNECTION_ATTR_STORAGE_LENGTH) + { + set_mysql_error(mysql, CR_INVALID_PARAMETER_NO, unknown_sqlstate); + DBUG_RETURN(1); + } + + if (!my_hash_inited(&mysql->options.extension->connection_attributes)) + { + if (my_hash_init(&mysql->options.extension->connection_attributes, + &my_charset_bin, 0, 0, 0, (my_hash_get_key) get_attr_key, + my_free, HASH_UNIQUE)) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(1); + } + } + if (!my_multi_malloc(MY_WME, + &elt, 2 * sizeof(LEX_STRING), + &key, key_len + 1, + &value, value_len + 1, + NULL)) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(1); + } + elt[0].str= key; elt[0].length= key_len; + elt[1].str= value; elt[1].length= value_len; + if (key_len) + memcpy(key, arg1, key_len); + key[key_len]= 0; + if (value_len) + memcpy(value, arg2, value_len); + value[value_len]= 0; + if (my_hash_insert(&mysql->options.extension->connection_attributes, + (uchar *) elt)) + { + /* can't insert the value */ + my_free(elt); + set_mysql_error(mysql, CR_DUPLICATE_CONNECTION_ATTR, + unknown_sqlstate); + DBUG_RETURN(1); + } + + mysql->options.extension->connection_attributes_length+= + attr_storage_length; + + break; + } + default: DBUG_RETURN(1); } @@ -4598,6 +4809,6 @@ my_socket STDCALL mysql_get_socket(const MYSQL *mysql) { if (mysql->net.vio) - return mysql->net.vio->sd; + return vio_fd(mysql->net.vio); return INVALID_SOCKET; } diff --git a/sql-common/client_plugin.c b/sql-common/client_plugin.c index f31ddb22a6a..f93e50125c5 100644 --- a/sql-common/client_plugin.c +++ b/sql-common/client_plugin.c @@ -70,7 +70,7 @@ static uint plugin_version[MYSQL_CLIENT_MAX_PLUGINS]= loading the same plugin twice in parallel. */ struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS]; -static pthread_mutex_t LOCK_load_client_plugin; +static mysql_mutex_t LOCK_load_client_plugin; static int is_not_initialized(MYSQL *mysql, const char *name) { @@ -170,7 +170,7 @@ add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle, goto err2; } - safe_mutex_assert_owner(&LOCK_load_client_plugin); + mysql_mutex_assert_owner(&LOCK_load_client_plugin); p->next= plugin_list[plugin->type]; plugin_list[plugin->type]= p; @@ -235,7 +235,7 @@ static void load_env_plugins(MYSQL *mysql) This function must be called before any other client plugin function. @retval 0 successful - @retval != 0 error occured + @retval != 0 error occurred */ int mysql_client_plugin_init() { @@ -250,19 +250,19 @@ int mysql_client_plugin_init() bzero(&mysql, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */ - pthread_mutex_init(&LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW); - init_alloc_root(&mem_root, 128, 128); + mysql_mutex_init(0, &LOCK_load_client_plugin, MY_MUTEX_INIT_SLOW); + init_alloc_root(&mem_root, 128, 128, MYF(0)); bzero(&plugin_list, sizeof(plugin_list)); initialized= 1; - pthread_mutex_lock(&LOCK_load_client_plugin); + mysql_mutex_lock(&LOCK_load_client_plugin); for (builtin= mysql_client_builtins; *builtin; builtin++) add_plugin(&mysql, *builtin, 0, 0, unused); - pthread_mutex_unlock(&LOCK_load_client_plugin); + mysql_mutex_unlock(&LOCK_load_client_plugin); load_env_plugins(&mysql); @@ -295,7 +295,7 @@ void mysql_client_plugin_deinit() bzero(&plugin_list, sizeof(plugin_list)); initialized= 0; free_root(&mem_root, MYF(0)); - pthread_mutex_destroy(&LOCK_load_client_plugin); + mysql_mutex_destroy(&LOCK_load_client_plugin); DBUG_VOID_RETURN; } @@ -313,7 +313,7 @@ mysql_client_register_plugin(MYSQL *mysql, if (is_not_initialized(mysql, plugin->name)) DBUG_RETURN(NULL); - pthread_mutex_lock(&LOCK_load_client_plugin); + mysql_mutex_lock(&LOCK_load_client_plugin); /* make sure the plugin wasn't loaded meanwhile */ if (find_plugin(plugin->name, plugin->type)) @@ -326,7 +326,7 @@ mysql_client_register_plugin(MYSQL *mysql, else plugin= add_plugin(mysql, plugin, 0, 0, unused); - pthread_mutex_unlock(&LOCK_load_client_plugin); + mysql_mutex_unlock(&LOCK_load_client_plugin); DBUG_RETURN(plugin); } @@ -348,7 +348,7 @@ mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, DBUG_RETURN (NULL); } - pthread_mutex_lock(&LOCK_load_client_plugin); + mysql_mutex_lock(&LOCK_load_client_plugin); /* make sure the plugin wasn't loaded meanwhile */ if (type >= 0 && find_plugin(name, type)) @@ -375,8 +375,7 @@ mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, if (!(sym= dlsym(dlhandle, plugin_declarations_sym))) { errmsg= "not a plugin"; - (void)dlclose(dlhandle); - goto err; + goto errc; } plugin= (struct st_mysql_client_plugin*)sym; @@ -384,30 +383,32 @@ mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, if (type >=0 && type != plugin->type) { errmsg= "type mismatch"; - goto err; + goto errc; } if (strcmp(name, plugin->name)) { errmsg= "name mismatch"; - goto err; + goto errc; } if (type < 0 && find_plugin(name, plugin->type)) { errmsg= "it is already loaded"; - goto err; + goto errc; } plugin= add_plugin(mysql, plugin, dlhandle, argc, args); - pthread_mutex_unlock(&LOCK_load_client_plugin); + mysql_mutex_unlock(&LOCK_load_client_plugin); DBUG_PRINT ("leave", ("plugin loaded ok")); DBUG_RETURN (plugin); +errc: + dlclose(dlhandle); err: - pthread_mutex_unlock(&LOCK_load_client_plugin); + mysql_mutex_unlock(&LOCK_load_client_plugin); DBUG_PRINT ("leave", ("plugin load error : %s", errmsg)); set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg); diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 860f69a7f8e..7cf8692a3f6 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -1,5 +1,6 @@ /* Copyright (c) 2004, 2012, Oracle and/or its affiliates. + Copyright (c) 2010, 2013, Monty Program Ab. 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 @@ -24,10 +25,10 @@ ulonglong log_10_int[20]= { 1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL, - ULL(100000000), ULL(1000000000), ULL(10000000000), ULL(100000000000), - ULL(1000000000000), ULL(10000000000000), ULL(100000000000000), - ULL(1000000000000000), ULL(10000000000000000), ULL(100000000000000000), - ULL(1000000000000000000), ULL(10000000000000000000) + 100000000ULL, 1000000000ULL, 10000000000ULL, 100000000000ULL, + 1000000000000ULL, 10000000000000ULL, 100000000000000ULL, + 1000000000000000ULL, 10000000000000000ULL, 100000000000000000ULL, + 1000000000000000000ULL, 10000000000000000000ULL }; @@ -80,6 +81,8 @@ uint calc_days_in_year(uint year) my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, ulonglong flags, int *was_cut) { + if (ltime->time_type == MYSQL_TIMESTAMP_TIME) + return FALSE; if (not_zero_date) { if (((flags & TIME_NO_ZERO_IN_DATE) && @@ -104,6 +107,143 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, return FALSE; } +static int get_number(uint *val, uint *number_of_fields, const char **str, + const char *end) +{ + const char *s = *str; + + if (s >= end) + return 0; + + if (!my_isdigit(&my_charset_latin1, *s)) + return 1; + *val= *s++ - '0'; + + for (; s < end && my_isdigit(&my_charset_latin1, *s); s++) + *val= *val * 10 + *s - '0'; + *str = s; + (*number_of_fields)++; + return 0; +} + +static int get_digits(uint *val, uint *number_of_fields, const char **str, + const char *end, uint length) +{ + return get_number(val, number_of_fields, str, MY_MIN(end, *str + length)); +} + +static int get_punct(const char **str, const char *end) +{ + if (*str >= end) + return 0; + if (my_ispunct(&my_charset_latin1, **str)) + { + (*str)++; + return 0; + } + return 1; +} + +static int get_date_time_separator(uint *number_of_fields, ulonglong flags, + const char **str, const char *end) +{ + const char *s= *str; + if (s >= end) + return 0; + + if (*s == 'T') + { + (*str)++; + return 0; + } + + /* + now, this is tricky, for backward compatibility reasons. + cast("11:11:11.12.12.12" as datetime) should give 2011-11-11 12:12:12 + but + cast("11:11:11.12.12.12" as time) should give 11:11:11.12 + that is, a punctuation character can be accepted as a date/time separator + only if TIME_DATETIME_ONLY (see str_to_time) is not set. + */ + if (my_ispunct(&my_charset_latin1, *s)) + { + if (flags & TIME_DATETIME_ONLY) + { + /* see above, returning 1 is not enough, we need hard abort here */ + *number_of_fields= 0; + return 1; + } + + (*str)++; + return 0; + } + + if (!my_isspace(&my_charset_latin1, *s)) + return 1; + + do + { + s++; + } while (my_isspace(&my_charset_latin1, *s)); + *str= s; + return 0; +} + +static int get_maybe_T(const char **str, const char *end) +{ + if (*str < end && **str == 'T') + (*str)++; + return 0; +} + +static uint skip_digits(const char **str, const char *end) +{ + const char *start= *str, *s= *str; + while (s < end && my_isdigit(&my_charset_latin1, *s)) + s++; + *str= s; + return s - start; +} + + +/** + Check datetime, date, or normalized time (i.e. time without days) range. + @param ltime Datetime value. + @returns + @retval FALSE on success + @retval TRUE on error +*/ +my_bool check_datetime_range(const MYSQL_TIME *ltime) +{ + /* + In case of MYSQL_TIMESTAMP_TIME hour value can be up to TIME_MAX_HOUR. + In case of MYSQL_TIMESTAMP_DATETIME it cannot be bigger than 23. + */ + return + ltime->year > 9999 || ltime->month > 12 || ltime->day > 31 || + ltime->minute > 59 || ltime->second > 59 || + ltime->second_part > TIME_MAX_SECOND_PART || + (ltime->hour > + (uint) (ltime->time_type == MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR : 23)); +} + + +static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status, + uint *number_of_fields, + const char **str, const char *end) +{ + const char *start= *str; + uint tmp= 0; /* For the case '10:10:10.' */ + if (get_digits(&tmp, number_of_fields, str, end, 6)) + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + if ((status->precision= (*str - start)) < 6) + *val= (ulong) (tmp * log_10_int[6 - (*str - start)]); + else + *val= tmp; + if (skip_digits(str, end)) + status->warnings|= MYSQL_TIME_NOTE_TRUNCATED; +} + /* Convert a timestamp string to a MYSQL_TIME value. @@ -119,9 +259,8 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, TIME_NO_ZERO_IN_DATE Don't allow partial dates TIME_NO_ZERO_DATE Don't allow 0000-00-00 date TIME_INVALID_DATES Allow 2000-02-31 - was_cut 0 Value OK - 1 If value was cut during conversion - 2 check_date(date,flags) considers date invalid + status Conversion status + DESCRIPTION At least the following formats are recogniced (based on number of digits) @@ -132,21 +271,12 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, The second part may have an optional .###### fraction part. - NOTES - This function should work with a format position vector as long as the - following things holds: - - All date are kept together and all time parts are kept together - - Date and time parts must be separated by blank - - Second fractions must come after second part and be separated - by a '.'. (The second fractions are optional) - - AM/PM must come after second fractions (or after seconds if no fractions) - - Year must always been specified. - - If time is before date, then we will use datetime format only if - the argument consist of two parts, separated by space. - Otherwise we will assume the argument is a date. - - The hour part must be specified in hour-minute-second order. + status->warnings is set to: + 0 Value OK + MYSQL_TIME_WARN_TRUNCATED If value was cut during conversion + MYSQL_TIME_WARN_OUT_OF_RANGE check_date(date,flags) considers date invalid - RETURN VALUES + l_time->time_type is set as follows: MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like [DD [HH:[MM:[SS]]]].fraction. l_time is not changed. @@ -154,293 +284,126 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, MYSQL_TIMESTAMP_DATETIME Full timestamp MYSQL_TIMESTAMP_ERROR Timestamp with wrong values. All elements in l_time is set to 0 + RETURN VALUES + 0 - Ok + 1 - Error */ #define MAX_DATE_PARTS 8 -enum enum_mysql_timestamp_type +my_bool str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time, - ulonglong flags, int *was_cut) + ulonglong flags, MYSQL_TIME_STATUS *status) { - uint UNINIT_VAR(field_length), UNINIT_VAR(year_length), digits, i, number_of_fields; - uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS]; - uint add_hours= 0, start_loop; - ulong not_zero_date, allow_space; - my_bool is_internal_format; - const char *pos, *UNINIT_VAR(last_field_pos); - const char *end=str+length; - const uchar *format_position; - my_bool found_delimitier= 0, found_space= 0; - uint frac_pos, frac_len; + const char *end=str+length, *pos; + uint number_of_fields= 0, digits, year_length, not_zero_date; DBUG_ENTER("str_to_datetime"); - DBUG_PRINT("enter",("str: %.*s",length,str)); + bzero(l_time, sizeof(*l_time)); if (flags & TIME_TIME_ONLY) { - enum enum_mysql_timestamp_type ret; - ret= str_to_time(str, length, l_time, flags, was_cut); + my_bool ret= str_to_time(str, length, l_time, flags, status); DBUG_RETURN(ret); } - *was_cut= 0; + my_time_status_init(status); /* Skip space at start */ for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++) ; if (str == end || ! my_isdigit(&my_charset_latin1, *str)) { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); + status->warnings= MYSQL_TIME_WARN_TRUNCATED; + l_time->time_type= MYSQL_TIMESTAMP_NONE; + DBUG_RETURN(1); } - is_internal_format= 0; - /* This has to be changed if want to activate different timestamp formats */ - format_position= internal_format_positions; - /* Calculate number of digits in first part. If length= 8 or >= 14 then year is of format YYYY. (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS) */ - for (pos=str; - pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T'); - pos++) - ; + pos= str; + digits= skip_digits(&pos, end); - digits= (uint) (pos-str); - start_loop= 0; /* Start of scan loop */ - date_len[format_position[0]]= 0; /* Length of year field */ - if (pos == end || *pos == '.') + if (pos < end && *pos == 'T') /* YYYYYMMDDHHMMSSThhmmss is supported too */ { - /* Found date in internal format (only numbers like YYYYMMDD) */ - year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; - field_length= year_length; - is_internal_format= 1; - format_position= internal_format_positions; + pos++; + digits+= skip_digits(&pos, end); } - else + if (pos < end && *pos == '.' && digits >= 12) /* YYYYYMMDDHHMMSShhmmss.uuuuuu is supported too */ { - if (format_position[0] >= 3) /* If year is after HHMMDD */ - { - /* - If year is not in first part then we have to determinate if we got - a date field or a datetime field. - We do this by checking if there is two numbers separated by - space in the input. - */ - while (pos < end && !my_isspace(&my_charset_latin1, *pos)) - pos++; - while (pos < end && !my_isdigit(&my_charset_latin1, *pos)) - pos++; - if (pos == end) - { - if (flags & TIME_DATETIME_ONLY) - { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a full datetime */ - } - /* Date field. Set hour, minutes and seconds to 0 */ - date[0]= date[1]= date[2]= date[3]= date[4]= 0; - start_loop= 5; /* Start with first date part */ - } - } - - field_length= format_position[0] == 0 ? 4 : 2; + pos++; + skip_digits(&pos, end); // ignore the return value } - /* - Only allow space in the first "part" of the datetime field and: - - after days, part seconds - - before and after AM/PM (handled by code later) - - 2003-03-03 20:00:20 AM - 20:00:20.000000 AM 03-03-2000 - */ - i= max((uint) format_position[0], (uint) format_position[1]); - set_if_bigger(i, (uint) format_position[2]); - allow_space= ((1 << i) | (1 << format_position[6])); - allow_space&= (1 | 2 | 4 | 8); - - not_zero_date= 0; - for (i = start_loop; - i < MAX_DATE_PARTS-1 && str != end && - my_isdigit(&my_charset_latin1,*str); - i++) + if (pos == end) { - const char *start= str; - ulong tmp_value= (uint) (uchar) (*str++ - '0'); - /* - Internal format means no delimiters; every field has a fixed - width. Otherwise, we scan until we find a delimiter and discard - leading zeroes -- except for the microsecond part, where leading - zeroes are significant, and where we never process more than six - digits. + Found date in internal format + (only numbers like [YY]YYMMDD[T][hhmmss[.uuuuuu]]) */ - my_bool scan_until_delim= !is_internal_format && - ((i != format_position[6])); - - while (str != end && my_isdigit(&my_charset_latin1,str[0]) && - (scan_until_delim || --field_length)) - { - tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0'); - str++; - } - date_len[i]= (uint) (str - start); - if (tmp_value > 999999) /* Impossible date part */ - { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); - } - date[i]=tmp_value; - not_zero_date|= tmp_value; - - /* Length of next field */ - field_length= format_position[i+1] == 0 ? 4 : 2; - - if ((last_field_pos= str) == end) - { - i++; /* Register last found part */ - break; - } - /* Allow a 'T' after day to allow CCYYMMDDT type of fields */ - if (i == format_position[2] && *str == 'T') - { - str++; /* ISO8601: CCYYMMDDThhmmss */ - continue; - } - if (i == format_position[5]) /* Seconds */ - { - if (*str == '.') /* Followed by part seconds */ - { - str++; - field_length= 6; /* 6 digits */ - } - continue; - } - while (str != end && - (my_ispunct(&my_charset_latin1,*str) || - my_isspace(&my_charset_latin1,*str))) - { - if (my_isspace(&my_charset_latin1,*str)) - { - if (!(allow_space & (1 << i))) - { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); - } - found_space= 1; - } - str++; - found_delimitier= 1; /* Should be a 'normal' date */ - } - /* Check if next position is AM/PM */ - if (i == format_position[6]) /* Seconds, time for AM/PM */ - { - i++; /* Skip AM/PM part */ - if (format_position[7] != 255) /* If using AM/PM */ - { - if (str+2 <= end && (str[1] == 'M' || str[1] == 'm')) - { - if (str[0] == 'p' || str[0] == 'P') - add_hours= 12; - else if (str[0] != 'a' && str[0] != 'A') - continue; /* Not AM/PM */ - str+= 2; /* Skip AM/PM */ - /* Skip space after AM/PM */ - while (str != end && my_isspace(&my_charset_latin1,*str)) - str++; - } - } - } - last_field_pos= str; + year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; + if (get_digits(&l_time->year, &number_of_fields, &str, end, year_length) + || get_digits(&l_time->month, &number_of_fields, &str, end, 2) + || get_digits(&l_time->day, &number_of_fields, &str, end, 2) + || get_maybe_T(&str, end) + || get_digits(&l_time->hour, &number_of_fields, &str, end, 2) + || get_digits(&l_time->minute, &number_of_fields, &str, end, 2) + || get_digits(&l_time->second, &number_of_fields, &str, end, 2)) + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; } - if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY)) + else { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a datetime */ + const char *start= str; + if (get_number(&l_time->year, &number_of_fields, &str, end)) + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + year_length= str - start; + + if (!status->warnings && + (get_punct(&str, end) + || get_number(&l_time->month, &number_of_fields, &str, end) + || get_punct(&str, end) + || get_number(&l_time->day, &number_of_fields, &str, end) + || get_date_time_separator(&number_of_fields, flags, &str, end) + || get_number(&l_time->hour, &number_of_fields, &str, end) + || get_punct(&str, end) + || get_number(&l_time->minute, &number_of_fields, &str, end) + || get_punct(&str, end) + || get_number(&l_time->second, &number_of_fields, &str, end))) + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; } - str= last_field_pos; - - number_of_fields= i - start_loop; - while (i < MAX_DATE_PARTS) + /* we're ok if date part is correct. even if the rest is truncated */ + if (number_of_fields < 3) { - date_len[i]= 0; - date[i++]= 0; + l_time->time_type= MYSQL_TIMESTAMP_NONE; + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + DBUG_RETURN(TRUE); } - if (!is_internal_format) + if (!status->warnings && str < end && *str == '.') { - year_length= date_len[(uint) format_position[0]]; - if (!year_length) /* Year must be specified */ - { - *was_cut= 1; - DBUG_RETURN(MYSQL_TIMESTAMP_NONE); - } - - l_time->year= date[(uint) format_position[0]]; - l_time->month= date[(uint) format_position[1]]; - l_time->day= date[(uint) format_position[2]]; - l_time->hour= date[(uint) format_position[3]]; - l_time->minute= date[(uint) format_position[4]]; - l_time->second= date[(uint) format_position[5]]; - - frac_pos= (uint) format_position[6]; - frac_len= date_len[frac_pos]; - if (frac_len < 6) - date[frac_pos]*= (uint) log_10_int[6 - frac_len]; - l_time->second_part= date[frac_pos]; - - if (format_position[7] != (uchar) 255) - { - if (l_time->hour > 12) - { - *was_cut= 1; - goto err; - } - l_time->hour= l_time->hour%12 + add_hours; - } - } - else - { - l_time->year= date[0]; - l_time->month= date[1]; - l_time->day= date[2]; - l_time->hour= date[3]; - l_time->minute= date[4]; - l_time->second= date[5]; - if (date_len[6] < 6) - date[6]*= (uint) log_10_int[6 - date_len[6]]; - l_time->second_part=date[6]; + str++; + get_microseconds(&l_time->second_part, status, + &number_of_fields, &str, end); } - l_time->neg= 0; + + not_zero_date = l_time->year || l_time->month || l_time->day || + l_time->hour || l_time->minute || l_time->second || + l_time->second_part; if (year_length == 2 && not_zero_date) l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900); - if (number_of_fields < 3 || - l_time->year > 9999 || l_time->month > 12 || - l_time->day > 31 || l_time->hour > 23 || - l_time->minute > 59 || l_time->second > 59) + if (l_time->year > 9999 || l_time->month > 12 || l_time->day > 31 || + l_time->hour > 23 || l_time->minute > 59 || l_time->second > 59) { - /* Only give warning for a zero date if there is some garbage after */ - if (!not_zero_date) /* If zero date */ - { - for (; str != end ; str++) - { - if (!my_isspace(&my_charset_latin1, *str)) - { - not_zero_date= 1; /* Give warning */ - break; - } - } - } - *was_cut= test(not_zero_date); + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; goto err; } - if (check_date(l_time, not_zero_date != 0, flags, was_cut)) + if (check_date(l_time, not_zero_date, flags, &status->warnings)) goto err; l_time->time_type= (number_of_fields <= 3 ? @@ -450,16 +413,17 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time, { if (!my_isspace(&my_charset_latin1,*str)) { - *was_cut= 1; + status->warnings= MYSQL_TIME_WARN_TRUNCATED; break; } } - DBUG_RETURN(l_time->time_type); + DBUG_RETURN(FALSE); err: bzero((char*) l_time, sizeof(*l_time)); - DBUG_RETURN(MYSQL_TIMESTAMP_ERROR); + l_time->time_type= MYSQL_TIMESTAMP_ERROR; + DBUG_RETURN(TRUE); } @@ -474,59 +438,60 @@ err: There may be an optional [.second_part] after seconds length Length of str l_time Store result here - warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string - was cut during conversion, and/or - MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is - out of range. + status Conversion status + NOTES + Because of the extra days argument, this function can only work with times where the time arguments are in the above order. + status->warnings is set as follows: + MYSQL_TIME_WARN_TRUNCATED if the input string was cut during conversion, + and/or + MYSQL_TIME_WARN_OUT_OF_RANGE flag is set if the value is out of range. + RETURN - MYSQL_TIMESTAMP_TIME - MYSQL_TIMESTAMP_ERROR + FALSE on success + TRUE on error */ -enum enum_mysql_timestamp_type -str_to_time(const char *str, uint length, MYSQL_TIME *l_time, - ulonglong fuzzydate, int *warning) +my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, + ulonglong fuzzydate, MYSQL_TIME_STATUS *status) { ulong date[5]; ulonglong value; const char *end=str+length, *end_of_days; - my_bool found_days,found_hours; - uint state; + my_bool found_days,found_hours, neg= 0; + uint UNINIT_VAR(state); - l_time->neg=0; - *warning= 0; + my_time_status_init(status); for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++) length--; if (str != end && *str == '-') { - l_time->neg=1; + neg=1; str++; length--; } if (str == end) - return MYSQL_TIMESTAMP_ERROR; + { + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + goto err; + } /* Check first if this is a full TIMESTAMP */ if (length >= 12) { /* Probably full timestamp */ - int was_cut; - enum enum_mysql_timestamp_type - res= str_to_datetime(str, length, l_time, + (void) str_to_datetime(str, length, l_time, (fuzzydate & ~TIME_TIME_ONLY) | TIME_DATETIME_ONLY, - &was_cut); - if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR) - { - if (was_cut) - *warning|= MYSQL_TIME_WARN_TRUNCATED; - return res; - } + status); + if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR) + return l_time->time_type == MYSQL_TIMESTAMP_ERROR; + my_time_status_init(status); } + l_time->neg= neg; /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++) value=value*10L + (long) (*str - '0'); @@ -536,7 +501,6 @@ str_to_time(const char *str, uint length, MYSQL_TIME *l_time, for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++) ; - LINT_INIT(state); found_days=found_hours=0; if ((uint) (end-str) > 1 && str != end_of_days && my_isdigit(&my_charset_latin1, *str)) @@ -592,24 +556,15 @@ str_to_time(const char *str, uint length, MYSQL_TIME *l_time, fractional: /* Get fractional second part */ - if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1])) + if (!status->warnings && str < end && *str == '.') { - int field_length= 5; - str++; value=(uint) (uchar) (*str - '0'); - while (++str != end && my_isdigit(&my_charset_latin1, *str)) - { - if (field_length-- > 0) - value= value*10 + (uint) (uchar) (*str - '0'); - } - if (field_length > 0) - value*= (long) log_10_int[field_length]; - else if (field_length < 0) - *warning|= MYSQL_TIME_WARN_TRUNCATED; - date[4]= (ulong) value; + uint number_of_fields= 0; + str++; + get_microseconds(&date[4], status, &number_of_fields, &str, end); } else - date[4]=0; - + date[4]= 0; + /* Check for exponent part: E<gigit> | E<sign><digit> */ /* (may occur as result of %g formatting of time value) */ if ((end - str) > 1 && @@ -618,7 +573,10 @@ fractional: ((str[1] == '-' || str[1] == '+') && (end - str) > 2 && my_isdigit(&my_charset_latin1, str[2])))) - return MYSQL_TIMESTAMP_ERROR; + { + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + goto err; + } if (internal_format_positions[7] != 255) { @@ -641,8 +599,11 @@ fractional: if (date[0] > UINT_MAX || date[1] > UINT_MAX || date[2] > UINT_MAX || date[3] > UINT_MAX || date[4] > UINT_MAX) - return MYSQL_TIMESTAMP_ERROR; - + { + status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + goto err; + } + l_time->year= 0; /* For protocol::store_time */ l_time->month= 0; l_time->day= 0; @@ -653,9 +614,9 @@ fractional: l_time->time_type= MYSQL_TIMESTAMP_TIME; /* Check if the value is valid and fits into MYSQL_TIME range */ - if (check_time_range(l_time, 6, warning)) - return MYSQL_TIMESTAMP_ERROR; - + if (check_time_range(l_time, 6, &status->warnings)) + return TRUE; + /* Check if there is garbage at end of the MYSQL_TIME specification */ if (str != end) { @@ -663,12 +624,17 @@ fractional: { if (!my_isspace(&my_charset_latin1,*str)) { - *warning|= MYSQL_TIME_WARN_TRUNCATED; + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; break; } } while (++str != end); } - return MYSQL_TIMESTAMP_TIME; + return FALSE; + +err: + bzero((char*) l_time, sizeof(*l_time)); + l_time->time_type= MYSQL_TIMESTAMP_ERROR; + return TRUE; } @@ -698,7 +664,10 @@ int check_time_range(struct st_mysql_time *my_time, uint dec, int *warning) 999000, 999900, 999990, 999999}; if (my_time->minute >= 60 || my_time->second >= 60) + { + *warning|= MYSQL_TIME_WARN_TRUNCATED; return 1; + } hour= my_time->hour + (24*my_time->day); @@ -950,7 +919,8 @@ my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, uint *error_code) #endif tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) - - (long) days_at_timestart)*86400L + (long) t->hour*3600L + + (long) days_at_timestart) * SECONDS_IN_24H + + (long) t->hour*3600L + (long) (t->minute*60 + t->second)) + (time_t) my_time_zone - 3600); @@ -1009,7 +979,7 @@ my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, uint *error_code) /* shift back, if we were dealing with boundary dates */ - tmp+= shift*86400L; + tmp+= shift * SECONDS_IN_24H; /* This is possible for dates, which slightly exceed boundaries. @@ -1189,6 +1159,27 @@ int my_TIME_to_str(const MYSQL_TIME *l_time, char *to, uint digits) } +/** + Print a timestamp with an optional fractional part: XXXXX[.YYYYY] + + @param tm The timestamp value to print. + @param OUT to The string pointer to print at. + @param dec Precision, in the range 0..6. + @return The length of the result string. +*/ +int my_timeval_to_str(const struct timeval *tm, char *to, uint dec) +{ + char *pos= longlong10_to_str((longlong) tm->tv_sec, to, 10); + if (dec) + { + *pos++= '.'; + pos= fmt_number((uint) sec_part_shift(tm->tv_usec, dec), pos, dec); + } + *pos= '\0'; + return (int) (pos - to); +} + + /* Convert datetime value specified as number to broken-down TIME representation and form value of DATETIME type as side-effect. @@ -1254,19 +1245,19 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, time_res->time_type=MYSQL_TIMESTAMP_DATETIME; - if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959)) + if (nr <= (YY_PART_YEAR-1)*10000000000LL+1231235959LL) { - nr= nr+LL(20000000000000); /* YYMMDDHHMMSS, 2000-2069 */ + nr= nr+20000000000000LL; /* YYMMDDHHMMSS, 2000-2069 */ goto ok; } - if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000)) + if (nr < YY_PART_YEAR*10000000000LL+ 101000000LL) goto err; - if (nr <= LL(991231235959)) - nr= nr+LL(19000000000000); /* YYMMDDHHMMSS, 1970-1999 */ + if (nr <= 991231235959LL) + nr= nr+19000000000000LL; /* YYMMDDHHMMSS, 1970-1999 */ ok: - part1=(long) (nr/LL(1000000)); - part2=(long) (nr - (longlong) part1*LL(1000000)); + part1=(long) (nr/1000000LL); + part2=(long) (nr - (longlong) part1*1000000LL); time_res->year= (int) (part1/10000L); part1%=10000L; time_res->month= (int) part1 / 100; time_res->day= (int) part1 % 100; @@ -1290,7 +1281,7 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */ if (nr || !(flags & TIME_NO_ZERO_DATE)) *was_cut= 1; - return LL(-1); + return -1; err: { @@ -1300,7 +1291,7 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, time_res->time_type= save; /* Restore range */ *was_cut= 1; /* Found invalid date */ } - return LL(-1); + return -1; } /* @@ -1323,16 +1314,8 @@ int number_to_time(my_bool neg, ulonglong nr, ulong sec_part, MYSQL_TIME *ltime, int *was_cut) { if (nr > 9999999 && nr < 99991231235959ULL && neg == 0) - { - if (number_to_datetime(nr, sec_part, ltime, - TIME_INVALID_DATES, was_cut) < 0) - return -1; - - ltime->year= ltime->month= ltime->day= 0; - ltime->time_type= MYSQL_TIMESTAMP_TIME; - *was_cut= MYSQL_TIME_NOTE_TRUNCATED; - return 0; - } + return number_to_datetime(nr, sec_part, ltime, + TIME_INVALID_DATES, was_cut) < 0 ? -1 : 0; *was_cut= 0; ltime->year= ltime->month= ltime->day= 0; @@ -1365,7 +1348,7 @@ ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time) { return ((ulonglong) (my_time->year * 10000UL + my_time->month * 100UL + - my_time->day) * ULL(1000000) + + my_time->day) * 1000000ULL + (ulonglong) (my_time->hour * 10000UL + my_time->minute * 100UL + my_time->second)); @@ -1426,7 +1409,7 @@ ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time) return TIME_to_ulonglong_time(my_time); case MYSQL_TIMESTAMP_NONE: case MYSQL_TIMESTAMP_ERROR: - return ULL(0); + return 0; default: DBUG_ASSERT(0); } @@ -1444,7 +1427,7 @@ double TIME_to_double(const MYSQL_TIME *my_time) return my_time->neg ? -d : d; } -longlong pack_time(MYSQL_TIME *my_time) +longlong pack_time(const MYSQL_TIME *my_time) { return ((((((my_time->year * 13ULL + my_time->month) * 32ULL + diff --git a/sql-common/my_user.c b/sql-common/my_user.c index 8d717ea7131..a486f77bc1e 100644 --- a/sql-common/my_user.c +++ b/sql-common/my_user.c @@ -30,34 +30,40 @@ host_name_str [OUT] Buffer to store host name part. Must be not less than HOSTNAME_LENGTH + 1. host_name_len [OUT] A place to store length of the host name part. + + RETURN + 0 - if only a user was set, no '@' was found + 1 - if both user and host were set */ -void parse_user(const char *user_id_str, size_t user_id_len, - char *user_name_str, size_t *user_name_len, - char *host_name_str, size_t *host_name_len) +int parse_user(const char *user_id_str, size_t user_id_len, + char *user_name_str, size_t *user_name_len, + char *host_name_str, size_t *host_name_len) { char *p= strrchr(user_id_str, '@'); if (!p) { - *user_name_len= 0; + *user_name_len= user_id_len; *host_name_len= 0; } else { *user_name_len= (uint) (p - user_id_str); *host_name_len= (uint) (user_id_len - *user_name_len - 1); + } - if (*user_name_len > USERNAME_LENGTH) - *user_name_len= USERNAME_LENGTH; + if (*user_name_len > USERNAME_LENGTH) + *user_name_len= USERNAME_LENGTH; - if (*host_name_len > HOSTNAME_LENGTH) - *host_name_len= HOSTNAME_LENGTH; + if (*host_name_len > HOSTNAME_LENGTH) + *host_name_len= HOSTNAME_LENGTH; - memcpy(user_name_str, user_id_str, *user_name_len); - memcpy(host_name_str, p + 1, *host_name_len); - } + memcpy(user_name_str, user_id_str, *user_name_len); + memcpy(host_name_str, p + 1, *host_name_len); user_name_str[*user_name_len]= 0; host_name_str[*host_name_len]= 0; + + return p != NULL; } diff --git a/sql-common/mysql_async.c b/sql-common/mysql_async.c index c7e720076ea..decf48e0e69 100644 --- a/sql-common/mysql_async.c +++ b/sql-common/mysql_async.c @@ -56,7 +56,7 @@ my_context_install_suspend_resume_hook(struct mysql_async_context *b, /* Asynchronous connect(); socket must already be set non-blocking. */ int my_connect_async(struct mysql_async_context *b, my_socket fd, - const struct sockaddr *name, uint namelen, uint timeout) + const struct sockaddr *name, uint namelen, int vio_timeout) { int res; size_socket s_err_size; @@ -90,15 +90,19 @@ my_connect_async(struct mysql_async_context *b, my_socket fd, return res; #endif b->events_to_wait_for|= MYSQL_WAIT_WRITE; - b->timeout_value= timeout; - if (timeout) + if (vio_timeout >= 0) + { + b->timeout_value= vio_timeout; b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; + } + else + b->timeout_value= 0; if (b->suspend_resume_hook) (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); my_context_yield(&b->async_context); if (b->suspend_resume_hook) (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); - if (b->events_occured & MYSQL_WAIT_TIMEOUT) + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) return -1; s_err_size= sizeof(res); @@ -117,9 +121,15 @@ my_connect_async(struct mysql_async_context *b, my_socket fd, IF_WIN(WSAGetLastError() != WSAEWOULDBLOCK, \ (errno != EAGAIN && errno != EINTR)) +#ifdef _AIX +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif +#endif + ssize_t my_recv_async(struct mysql_async_context *b, int fd, - unsigned char *buf, size_t size, uint timeout) + unsigned char *buf, size_t size, int timeout) { ssize_t res; @@ -129,7 +139,7 @@ my_recv_async(struct mysql_async_context *b, int fd, if (res >= 0 || IS_BLOCKING_ERROR()) return res; b->events_to_wait_for= MYSQL_WAIT_READ; - if (timeout) + if (timeout >= 0) { b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; b->timeout_value= timeout; @@ -139,7 +149,7 @@ my_recv_async(struct mysql_async_context *b, int fd, my_context_yield(&b->async_context); if (b->suspend_resume_hook) (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); - if (b->events_occured & MYSQL_WAIT_TIMEOUT) + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) return -1; } } @@ -147,7 +157,7 @@ my_recv_async(struct mysql_async_context *b, int fd, ssize_t my_send_async(struct mysql_async_context *b, int fd, - const unsigned char *buf, size_t size, uint timeout) + const unsigned char *buf, size_t size, int timeout) { ssize_t res; @@ -157,7 +167,7 @@ my_send_async(struct mysql_async_context *b, int fd, if (res >= 0 || IS_BLOCKING_ERROR()) return res; b->events_to_wait_for= MYSQL_WAIT_WRITE; - if (timeout) + if (timeout >= 0) { b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; b->timeout_value= timeout; @@ -167,23 +177,40 @@ my_send_async(struct mysql_async_context *b, int fd, my_context_yield(&b->async_context); if (b->suspend_resume_hook) (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); - if (b->events_occured & MYSQL_WAIT_TIMEOUT) + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) return -1; } } my_bool -my_poll_read_async(struct mysql_async_context *b, uint timeout) +my_io_wait_async(struct mysql_async_context *b, enum enum_vio_io_event event, + int timeout) { - b->events_to_wait_for= MYSQL_WAIT_READ | MYSQL_WAIT_TIMEOUT; - b->timeout_value= timeout; + switch (event) + { + case VIO_IO_EVENT_READ: + b->events_to_wait_for = MYSQL_WAIT_READ; + break; + case VIO_IO_EVENT_WRITE: + b->events_to_wait_for = MYSQL_WAIT_WRITE; + break; + case VIO_IO_EVENT_CONNECT: + b->events_to_wait_for = MYSQL_WAIT_WRITE | IF_WIN(0, MYSQL_WAIT_EXCEPT); + break; + } + + if (timeout >= 0) + { + b->events_to_wait_for |= MYSQL_WAIT_TIMEOUT; + b->timeout_value= timeout; + } if (b->suspend_resume_hook) (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); my_context_yield(&b->async_context); if (b->suspend_resume_hook) (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); - return (b->events_occured & MYSQL_WAIT_READ) ? 0 : 1; + return (b->events_occurred & MYSQL_WAIT_TIMEOUT) ? 0 : 1; } @@ -239,32 +266,27 @@ my_ssl_write_async(struct mysql_async_context *b, SSL *ssl, } #endif /* HAVE_OPENSSL */ +/* + Legacy support of the MariaDB 5.5 version, where timeouts where only in + seconds resolution. Applications that use this will be asked to set a timeout + at the nearest higher whole-seconds value. +*/ unsigned int STDCALL mysql_get_timeout_value(const MYSQL *mysql) { - return mysql->options.extension->async_context->timeout_value; + unsigned int timeout= mysql->options.extension->async_context->timeout_value; + /* Avoid overflow. */ + if (timeout > UINT_MAX - 999) + return (timeout - 1)/1000 + 1; + else + return (timeout+999)/1000; } -/* - In 10.0, VIO timeouts are in milliseconds, so we support getting the - millisecond timeout value from async applications. - - In 5.5, timeouts are always in seconds, but we support the 10.0 version - that provides milliseconds, so applications can work with either version - of the library easily. - - When merging this to 10.0, this function must be removed and the 10.0 - version used. -*/ unsigned int STDCALL mysql_get_timeout_value_ms(const MYSQL *mysql) { - unsigned int timeout= mysql->options.extension->async_context->timeout_value; - if (timeout <= UINT_MAX / 1000) - return timeout*1000; - else - return UINT_MAX; + return mysql->options.extension->async_context->timeout_value; } @@ -327,7 +349,7 @@ mysql_get_timeout_value_ms(const MYSQL *mysql) } \ \ b->active= 1; \ - b->events_occured= ready_status; \ + b->events_occurred= ready_status; \ res= my_context_continue(&b->async_context); \ b->active= 0; \ if (res > 0) \ @@ -383,7 +405,7 @@ mysql_get_timeout_value_ms(const MYSQL *mysql) } \ \ b->active= 1; \ - b->events_occured= ready_status; \ + b->events_occurred= ready_status; \ res= my_context_continue(&b->async_context); \ b->active= 0; \ if (res > 0) \ @@ -433,7 +455,11 @@ MK_ASYNC_START_BODY( parms.db= db; parms.port= port; parms.unix_socket= unix_socket; - parms.client_flags= client_flags; + /* + async wrapper enforce the CLIENT_REMEMBER_OPTIONS flag to be + functional (otherwise it can't operate) + */ + parms.client_flags= client_flags | CLIENT_REMEMBER_OPTIONS; }, NULL, r_ptr, diff --git a/sql-common/pack.c b/sql-common/pack.c index 02e91b5c3e3..4bb4a0b7a4e 100644 --- a/sql-common/pack.c +++ b/sql-common/pack.c @@ -49,7 +49,7 @@ ulong STDCALL net_field_length(uchar **packet) /* The same as above but returns longlong */ my_ulonglong net_field_length_ll(uchar **packet) { - reg1 uchar *pos= *packet; + uchar *pos= *packet; if (*pos < 251) { (*packet)++; @@ -70,12 +70,47 @@ my_ulonglong net_field_length_ll(uchar **packet) (*packet)+=4; return (my_ulonglong) uint3korr(pos+1); } + DBUG_ASSERT(*pos == 254); (*packet)+=9; /* Must be 254 when here */ -#ifdef NO_CLIENT_LONGLONG - return (my_ulonglong) uint4korr(pos+1); -#else return (my_ulonglong) uint8korr(pos+1); -#endif +} + +my_ulonglong safe_net_field_length_ll(uchar **packet, size_t packet_len) +{ + uchar *pos= *packet; + if (packet_len < 1) + goto err; + if (*pos < 251) + { + (*packet)++; + return (my_ulonglong) *pos; + } + if (*pos == 251) + { + (*packet)++; + return (my_ulonglong) NULL_LENGTH; + } + if (*pos == 252) + { + if (packet_len < 3) + goto err; + (*packet)+=3; + return (my_ulonglong) uint2korr(pos+1); + } + if (*pos == 253) + { + if (packet_len < 4) + goto err; + (*packet)+=4; + return (my_ulonglong) uint3korr(pos+1); + } + if (packet_len < 9 || *pos != 254) + goto err; + (*packet)+=9; + return (my_ulonglong) uint8korr(pos+1); +err: + *packet = NULL; + return 0; } /* @@ -83,38 +118,69 @@ my_ulonglong net_field_length_ll(uchar **packet) SYNOPSIS net_store_length() - pkg Store the packed integer here + packet Store the packed integer here length integers to store NOTES This is mostly used to store lengths of strings. - We have to cast the result for the LL() becasue of a bug in Forte CC - compiler. RETURN - Position in 'pkg' after the packed length + Position in 'packet' after the packed length */ uchar *net_store_length(uchar *packet, ulonglong length) { - if (length < (ulonglong) LL(251)) + if (length < 251) + { + *packet= (uchar) length; + return packet+1; + } + /* 251 is reserved for NULL */ + if (length < 65536) + { + *packet++=252; + int2store(packet, (uint) length); + return packet+2; + } + if (length < 16777216) + { + *packet++=253; + int3store(packet, (ulong) length); + return packet+3; + } + *packet++=254; + int8store(packet,length); + return packet+8; +} + +uchar *safe_net_store_length(uchar *packet, size_t packet_len, ulonglong length) +{ + if (length < 251) { - *packet=(uchar) length; + if (packet_len < 1) + return NULL; + *packet= (uchar) length; return packet+1; } /* 251 is reserved for NULL */ - if (length < (ulonglong) LL(65536)) + if (length < 65536) { + if (packet_len < 3) + return NULL; *packet++=252; - int2store(packet,(uint) length); + int2store(packet, (uint) length); return packet+2; } - if (length < (ulonglong) LL(16777216)) + if (length < 16777216) { + if (packet_len < 4) + return NULL; *packet++=253; - int3store(packet,(ulong) length); + int3store(packet, (ulong) length); return packet+3; } + if (packet_len < 9) + return NULL; *packet++=254; int8store(packet,length); return packet+8; |