diff options
108 files changed, 3548 insertions, 798 deletions
diff --git a/.bzrignore b/.bzrignore index 45cd7f2dc78..05597220950 100644 --- a/.bzrignore +++ b/.bzrignore @@ -43,6 +43,10 @@ *.vcxproj *.vcxproj.filters */*.dir/* +*.dir +Debug +MySql.sdf +Win32 */*_pure_*warnings */.deps */.libs/* @@ -615,6 +619,7 @@ include/mysql_h.ic include/mysql_version.h include/mysqld_ername.h include/mysqld_error.h +include/mysqld_error.h.rule include/openssl include/probes_mysql_dtrace.h include/readline @@ -1897,7 +1902,9 @@ scripts/mysql_find_rows scripts/mysql_fix_extensions scripts/mysql_fix_privilege_tables scripts/mysql_fix_privilege_tables.sql +scripts/mysql_fix_privilege_tables.sql.rule scripts/mysql_fix_privilege_tables_sql.c +scripts/mysql_fix_privilege_tables_sql.c.rule scripts/mysql_install_db scripts/mysql_secure_installation scripts/mysql_setpermission @@ -2137,6 +2144,7 @@ sql/handlerton.cc sql/html sql/latex sql/lex_hash.h +sql/lex_hash.h.rule sql/link_sources sql/max/* sql/message.h @@ -2168,6 +2176,7 @@ sql/sql_builtin.cc sql/sql_select.cc.orig sql/sql_yacc.cc sql/sql_yacc.h +sql/sql_yacc.h.rule sql/sql_yacc.output sql/sql_yacc.yy.orig sql/test_time diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 4f067c54429..6f19952302c 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -4598,13 +4598,14 @@ static int my_kill(int pid, int sig) command called command DESCRIPTION - shutdown [<timeout>] + shutdown_server [<timeout>] */ void do_shutdown_server(struct st_command *command) { - int timeout=60, pid; + long timeout=60; + int pid; DYNAMIC_STRING ds_pidfile_name; MYSQL* mysql = &cur_con->mysql; static DYNAMIC_STRING ds_timeout; @@ -4619,8 +4620,9 @@ void do_shutdown_server(struct st_command *command) if (ds_timeout.length) { - timeout= atoi(ds_timeout.str); - if (timeout == 0) + char* endptr; + timeout= strtol(ds_timeout.str, &endptr, 10); + if (*endptr != '\0') die("Illegal argument for timeout: '%s'", ds_timeout.str); } dynstr_free(&ds_timeout); @@ -4662,7 +4664,7 @@ void do_shutdown_server(struct st_command *command) DBUG_PRINT("info", ("Process %d does not exist anymore", pid)); DBUG_VOID_RETURN; } - DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); + DBUG_PRINT("info", ("Sleeping, timeout: %ld", timeout)); my_sleep(1000000L); } diff --git a/cmake/abi_check.cmake b/cmake/abi_check.cmake index 2488bcefe33..a671aeff342 100644 --- a/cmake/abi_check.cmake +++ b/cmake/abi_check.cmake @@ -19,8 +19,16 @@ # plugin_audit.h and plugin_ftparser.h. # # We use gcc specific preprocessing command and sed/diff, so it will -# only be run on Unix and only if gcc is used. -IF(CMAKE_COMPILER_IS_GNUCC AND CMAKE_SYSTEM_NAME MATCHES "Linux") +# only be run on Unix and only if gcc is used. On some Unixes, +# (Solaris) sed or diff might act differently from GNU, so we run only +# on systems we can trust. +IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "Linux") + SET(RUN_ABI_CHECK 1) +ELSE() + SET(RUN_ABI_CHECK 0) +ENDIF() + +IF(CMAKE_COMPILER_IS_GNUCC AND RUN_ABI_CHECK) IF(CMAKE_C_COMPILER MATCHES "ccache$") SET(COMPILER ${CMAKE_C_COMPILER_ARG1}) STRING(REGEX REPLACE "^ " "" COMPILER ${COMPILER}) diff --git a/cmake/maintainer.cmake b/cmake/maintainer.cmake index 468b2f40084..d24211a6ff8 100644 --- a/cmake/maintainer.cmake +++ b/cmake/maintainer.cmake @@ -35,7 +35,7 @@ ENDMACRO() # Setup G++ (GNU C++ compiler) warning options. MACRO(SET_MYSQL_MAINTAINER_GNU_CXX_OPTIONS) SET(MY_MAINTAINER_CXX_WARNINGS - "${MY_MAINTAINER_WARNINGS} -Wno-unused-parameter" + "${MY_MAINTAINER_WARNINGS} -Wno-unused-parameter -Woverloaded-virtual" CACHE STRING "C++ warning options used in maintainer builds.") ENDMACRO() diff --git a/cmake/os/WindowsCache.cmake b/cmake/os/WindowsCache.cmake index 3a36f3c780b..477fd1d2b88 100644 --- a/cmake/os/WindowsCache.cmake +++ b/cmake/os/WindowsCache.cmake @@ -101,6 +101,10 @@ SET(HAVE_IPV6_V6ONLY 1 CACHE INTERNAL "") SET(HAVE_ISINF CACHE INTERNAL "") SET(HAVE_ISNAN CACHE INTERNAL "") SET(HAVE_ISSETUGID CACHE INTERNAL "") +SET(HAVE_GETUID CACHE INTERNAL "") +SET(HAVE_GETEUID CACHE INTERNAL "") +SET(HAVE_GETGID CACHE INTERNAL "") +SET(HAVE_GETEGID CACHE INTERNAL "") SET(HAVE_LANGINFO_H CACHE INTERNAL "") SET(HAVE_LDIV 1 CACHE INTERNAL "") SET(HAVE_LIMITS_H 1 CACHE INTERNAL "") diff --git a/cmd-line-utils/libedit/el.c b/cmd-line-utils/libedit/el.c index d99946eb68f..c7f8386773d 100644 --- a/cmd-line-utils/libedit/el.c +++ b/cmd-line-utils/libedit/el.c @@ -478,7 +478,13 @@ el_source(EditLine *el, const char *fname) fp = NULL; if (fname == NULL) { -#ifdef HAVE_ISSETUGID +/* XXXMYSQL: Bug#49967 */ +#if defined(HAVE_GETUID) && defined(HAVE_GETEUID) && \ + defined(HAVE_GETGID) && defined(HAVE_GETEGID) +#define HAVE_IDENTITY_FUNCS 1 +#endif + +#if (defined(HAVE_ISSETUGID) || defined(HAVE_IDENTITY_FUNCS)) static const char elpath[] = "/.editrc"; /* XXXMYSQL: Portability fix (for which platforms?) */ #ifdef MAXPATHLEN @@ -486,9 +492,13 @@ el_source(EditLine *el, const char *fname) #else char path[4096]; #endif - +#ifdef HAVE_ISSETUGID if (issetugid()) return (-1); +#elif defined(HAVE_IDENTITY_FUNCS) + if (getuid() != geteuid() || getgid() != getegid()) + return (-1); +#endif if ((ptr = getenv("HOME")) == NULL) return (-1); if (strlcpy(path, ptr, sizeof(path)) >= sizeof(path)) @@ -498,9 +508,10 @@ el_source(EditLine *el, const char *fname) fname = path; #else /* - * If issetugid() is missing, always return an error, in order - * to keep from inadvertently opening up the user to a security - * hole. + * If issetugid() or the above mentioned get[e][u|g]id() + * functions are missing, always return an error, in order + * to keep from inadvertently opening up the user to a + * security hole. */ return (-1); #endif diff --git a/cmd-line-utils/libedit/vi.c b/cmd-line-utils/libedit/vi.c index d628f076a1d..beffc7b40b5 100644 --- a/cmd-line-utils/libedit/vi.c +++ b/cmd-line-utils/libedit/vi.c @@ -1012,8 +1012,10 @@ vi_histedit(EditLine *el, int c __attribute__((__unused__))) if (fd < 0) return CC_ERROR; cp = el->el_line.buffer; - write(fd, cp, el->el_line.lastchar - cp +0u); - write(fd, "\n", 1); + if (write(fd, cp, el->el_line.lastchar - cp +0u) == -1) + goto error; + if (write(fd, "\n", 1) == -1) + goto error; pid = fork(); switch (pid) { case -1: @@ -1041,6 +1043,12 @@ vi_histedit(EditLine *el, int c __attribute__((__unused__))) unlink(tempfile); /* return CC_REFRESH; */ return ed_newline(el, 0); + +/* XXXMYSQL: Avoid compiler warnings. */ +error: + close(fd); + unlink(tempfile); + return CC_ERROR; } /* vi_history_word(): diff --git a/config.h.cmake b/config.h.cmake index fdff4222417..3247093c613 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -125,6 +125,7 @@ #cmakedefine FIONREAD_IN_SYS_IOCTL 1 #cmakedefine GWINSZ_IN_SYS_IOCTL 1 #cmakedefine TIOCSTAT_IN_SYS_IOCTL 1 +#cmakedefine FIONREAD_IN_SYS_FILIO 1 /* Functions we may want to use. */ #cmakedefine HAVE_AIOWAIT 1 @@ -173,6 +174,10 @@ #cmakedefine gmtime_r @gmtime_r@ #cmakedefine HAVE_INITGROUPS 1 #cmakedefine HAVE_ISSETUGID 1 +#cmakedefine HAVE_GETUID 1 +#cmakedefine HAVE_GETEUID 1 +#cmakedefine HAVE_GETGID 1 +#cmakedefine HAVE_GETEGID 1 #cmakedefine HAVE_ISNAN 1 #cmakedefine HAVE_ISINF 1 #cmakedefine HAVE_LARGE_PAGE_OPTION 1 diff --git a/configure.cmake b/configure.cmake index c0bc1261d15..77d8b9e1faa 100644 --- a/configure.cmake +++ b/configure.cmake @@ -362,6 +362,10 @@ CHECK_FUNCTION_EXISTS (getwd HAVE_GETWD) CHECK_FUNCTION_EXISTS (gmtime_r HAVE_GMTIME_R) CHECK_FUNCTION_EXISTS (initgroups HAVE_INITGROUPS) CHECK_FUNCTION_EXISTS (issetugid HAVE_ISSETUGID) +CHECK_FUNCTION_EXISTS (getuid HAVE_GETUID) +CHECK_FUNCTION_EXISTS (geteuid HAVE_GETEUID) +CHECK_FUNCTION_EXISTS (getgid HAVE_GETGID) +CHECK_FUNCTION_EXISTS (getegid HAVE_GETEGID) CHECK_FUNCTION_EXISTS (ldiv HAVE_LDIV) CHECK_FUNCTION_EXISTS (localtime_r HAVE_LOCALTIME_R) CHECK_FUNCTION_EXISTS (longjmp HAVE_LONGJMP) @@ -487,6 +491,7 @@ CHECK_SYMBOL_EXISTS(getpagesize "unistd.h" HAVE_GETPAGESIZE) CHECK_SYMBOL_EXISTS(TIOCGWINSZ "sys/ioctl.h" GWINSZ_IN_SYS_IOCTL) CHECK_SYMBOL_EXISTS(FIONREAD "sys/ioctl.h" FIONREAD_IN_SYS_IOCTL) CHECK_SYMBOL_EXISTS(TIOCSTAT "sys/ioctl.h" TIOCSTAT_IN_SYS_IOCTL) +CHECK_SYMBOL_EXISTS(FIONREAD "sys/filio.h" FIONREAD_IN_SYS_FILIO) CHECK_SYMBOL_EXISTS(gettimeofday "sys/time.h" HAVE_GETTIMEOFDAY) CHECK_SYMBOL_EXISTS(finite "math.h" HAVE_FINITE_IN_MATH_H) diff --git a/include/m_ctype.h b/include/m_ctype.h index bb7f5ddfb3d..a35aea31a71 100644 --- a/include/m_ctype.h +++ b/include/m_ctype.h @@ -346,7 +346,7 @@ extern CHARSET_INFO my_charset_utf32_bin; extern CHARSET_INFO my_charset_utf32_general_ci; extern CHARSET_INFO my_charset_utf32_unicode_ci; -extern CHARSET_INFO my_charset_utf8_general_ci; +extern MYSQL_PLUGIN_IMPORT CHARSET_INFO my_charset_utf8_general_ci; extern CHARSET_INFO my_charset_utf8_unicode_ci; extern CHARSET_INFO my_charset_utf8_bin; extern CHARSET_INFO my_charset_utf8mb4_bin; diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index cc3f468040f..6b37170aeab 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -156,8 +156,7 @@ mysql_client_register_plugin(struct st_mysql *mysql, @retval 0 on success, 1 in case of failure **/ -int STDCALL mysql_plugin_options(struct st_mysql_client_plugin *plugin, - const char *option, - const void *value); +int mysql_plugin_options(struct st_mysql_client_plugin *plugin, + const char *option, const void *value); #endif diff --git a/include/mysql/client_plugin.h.pp b/include/mysql/client_plugin.h.pp index e508f821aad..93eaff7501e 100644 --- a/include/mysql/client_plugin.h.pp +++ b/include/mysql/client_plugin.h.pp @@ -35,6 +35,5 @@ mysql_client_find_plugin(struct st_mysql *mysql, const char *name, int type); struct st_mysql_client_plugin * mysql_client_register_plugin(struct st_mysql *mysql, struct st_mysql_client_plugin *plugin); -int STDCALL mysql_plugin_options(struct st_mysql_client_plugin *plugin, - const char *option, - const void *value); +int mysql_plugin_options(struct st_mysql_client_plugin *plugin, + const char *option, const void *value); diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt index d7426c465d8..d0e383c6640 100644 --- a/libmysql/CMakeLists.txt +++ b/libmysql/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -134,6 +134,12 @@ CACHE INTERNAL "Functions exported by client API" ) +IF(WIN32) + ADD_SUBDIRECTORY(authentication_win) + SET(WITH_AUTHENTICATION_WIN 1) + ADD_DEFINITIONS(-DAUTHENTICATION_WIN) +ENDIF(WIN32) + SET(CLIENT_SOURCES get_password.c libmysql.c @@ -151,6 +157,10 @@ ADD_DEPENDENCIES(clientlib GenError) SET(LIBS clientlib dbug strings vio mysys ${ZLIB_LIBRARY} ${SSL_LIBRARIES} ${LIBDL}) +IF(WITH_AUTHENTICATION_WIN) + LIST(APPEND LIBS auth_win_client) +ENDIF(WITH_AUTHENTICATION_WIN) + # Merge several convenience libraries into one big mysqlclient # and link them together into shared library. MERGE_LIBRARIES(mysqlclient STATIC ${LIBS} COMPONENT Development) diff --git a/libmysql/authentication_win/CMakeLists.txt b/libmysql/authentication_win/CMakeLists.txt new file mode 100644 index 00000000000..80cd14780e6 --- /dev/null +++ b/libmysql/authentication_win/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# +# Configuration for building Windows Authentication Plugin (client-side) +# + +ADD_DEFINITIONS(-DSECURITY_WIN32) +ADD_DEFINITIONS(-DDEBUG_ERRROR_LOG) # no error logging in production builds +ADD_DEFINITIONS(-DWINAUTH_USE_DBUG_LIB) # it is OK to use dbug library in statically + # linked plugin + +SET(HEADERS common.h handshake.h) +SET(PLUGIN_SOURCES plugin_client.cc handshake_client.cc log_client.cc common.cc handshake.cc) + +ADD_CONVENIENCE_LIBRARY(auth_win_client ${PLUGIN_SOURCES} ${HEADERS}) +TARGET_LINK_LIBRARIES(auth_win_client Secur32) + +# In IDE, group headers in a separate folder. + +SOURCE_GROUP(Headers REGULAR_EXPRESSION ".*h$") diff --git a/libmysql/authentication_win/common.cc b/libmysql/authentication_win/common.cc new file mode 100644 index 00000000000..1d1f2938969 --- /dev/null +++ b/libmysql/authentication_win/common.cc @@ -0,0 +1,492 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "common.h" +#include <sddl.h> // for ConvertSidToStringSid() +#include <secext.h> // for GetUserNameEx() + + +template <> void error_log_print<error_log_level::INFO>(const char *fmt, ...); +template <> void error_log_print<error_log_level::WARNING>(const char *fmt, ...); +template <> void error_log_print<error_log_level::ERROR>(const char *fmt, ...); + + +/** Connection class **************************************************/ + +/** + Create connection out of an active MYSQL_PLUGIN_VIO object. + + @param[in] vio pointer to a @c MYSQL_PLUGIN_VIO object used for + connection - it can not be NULL +*/ + +Connection::Connection(MYSQL_PLUGIN_VIO *vio): m_vio(vio), m_error(0) +{ + DBUG_ASSERT(vio); +} + + +/** + Write data to the connection. + + @param[in] blob data to be written + + @return 0 on success, VIO error code on failure. + + @note In case of error, VIO error code is stored in the connection object + and can be obtained with @c error() method. +*/ + +int Connection::write(const Blob &blob) +{ + m_error= m_vio->write_packet(m_vio, blob.ptr(), blob.len()); + +#ifndef DBUG_OFF + if (m_error) + DBUG_PRINT("error", ("vio write error %d", m_error)); +#endif + + return m_error; +} + + +/** + Read data from connection. + + @return A Blob containing read packet or null Blob in case of error. + + @note In case of error, VIO error code is stored in the connection object + and can be obtained with @c error() method. +*/ + +Blob Connection::read() +{ + unsigned char *ptr; + int len= m_vio->read_packet(m_vio, &ptr); + + if (len < 0) + { + m_error= true; + return Blob(); + } + + return Blob(ptr, len); +} + + +/** Sid class *****************************************************/ + + +/** + Create Sid object corresponding to a given account name. + + @param[in] account_name name of a Windows account + + The account name can be in any form accepted by @c LookupAccountName() + function. + + @note In case of errors created object is invalid and its @c is_valid() + method returns @c false. +*/ + +Sid::Sid(const wchar_t *account_name): m_data(NULL) +#ifndef DBUG_OFF +, m_as_string(NULL) +#endif +{ + DWORD sid_size= 0, domain_size= 0; + bool success; + + // Determine required buffer sizes + + success= LookupAccountNameW(NULL, account_name, NULL, &sid_size, + NULL, &domain_size, &m_type); + + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID buffer size, " + "LookupAccountName() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + return; + } + + // Query for SID (domain is ignored) + + wchar_t *domain= new wchar_t[domain_size]; + m_data= (TOKEN_USER*) new BYTE[sid_size + sizeof(TOKEN_USER)]; + m_data->User.Sid= (BYTE*)m_data + sizeof(TOKEN_USER); + + success= LookupAccountNameW(NULL, account_name, + m_data->User.Sid, &sid_size, + domain, &domain_size, + &m_type); + + if (!success || !is_valid()) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID of '%S', " + "LookupAccountName() failed with error %X (%s)", + account_name, GetLastError(), + get_last_error_message(error_buf))); +#endif + goto fail; + } + + goto end; + +fail: + if (m_data) + delete [] m_data; + m_data= NULL; + +end: + if (domain) + delete [] domain; +} + + +/** + Create Sid object corresponding to a given security token. + + @param[in] token security token of a Windows account + + @note In case of errors created object is invalid and its @c is_valid() + method returns @c false. +*/ + +Sid::Sid(HANDLE token): m_data(NULL) +#ifndef DBUG_OFF +, m_as_string(NULL) +#endif +{ + DWORD req_size= 0; + bool success; + + // Determine required buffer size + + success= GetTokenInformation(token, TokenUser, NULL, 0, &req_size); + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not determine SID buffer size, " + "GetTokenInformation() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + return; + } + + m_data= (TOKEN_USER*) new BYTE[req_size]; + success= GetTokenInformation(token, TokenUser, m_data, req_size, &req_size); + + if (!success || !is_valid()) + { + delete [] m_data; + m_data= NULL; +#ifndef DBUG_OFF + if (!success) + { + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not read SID from security token, " + "GetTokenInformation() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); + } +#endif + } +} + + +Sid::~Sid() +{ + if (m_data) + delete [] m_data; +#ifndef DBUG_OFF + if (m_as_string) + LocalFree(m_as_string); +#endif +} + +/// Check if Sid object is valid. +bool Sid::is_valid(void) const +{ + return m_data && m_data->User.Sid && IsValidSid(m_data->User.Sid); +} + + +#ifndef DBUG_OFF + +/** + Produces string representation of the SID. + + @return String representation of the SID or NULL in case of errors. + + @note Memory allocated for the string is automatically freed in Sid's + destructor. +*/ + +const char* Sid::as_string() +{ + if (!m_data) + return NULL; + + if (!m_as_string) + { + bool success= ConvertSidToStringSid(m_data->User.Sid, &m_as_string); + + if (!success) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not get textual representation of a SID, " + "ConvertSidToStringSid() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + m_as_string= NULL; + return NULL; + } + } + + return m_as_string; +} + +#endif + + +bool Sid::operator ==(const Sid &other) +{ + if (!is_valid() || !other.is_valid()) + return false; + + return EqualSid(m_data->User.Sid, other.m_data->User.Sid); +} + + +/** Generating User Principal Name *************************/ + +/** + Call Windows API functions to get UPN of the current user and store it + in internal buffer. +*/ + +UPN::UPN(): m_buf(NULL) +{ + wchar_t buf1[MAX_SERVICE_NAME_LENGTH]; + + // First we try to use GetUserNameEx. + + m_len= sizeof(buf1)/sizeof(wchar_t); + + if (!GetUserNameExW(NameUserPrincipal, buf1, (PULONG)&m_len)) + { + if (GetLastError()) + { +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("note", ("When determining UPN" + ", GetUserNameEx() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + if (ERROR_MORE_DATA == GetLastError()) + ERROR_LOG(INFO, ("Buffer overrun when determining UPN:" + " need %ul characters but have %ul", + m_len, sizeof(buf1)/sizeof(WCHAR))); + } + + m_len= 0; // m_len == 0 indicates invalid UPN + return; + } + + /* + UPN is stored in buf1 in wide-char format - convert it to utf8 + for sending over network. + */ + + m_buf= wchar_to_utf8(buf1, &m_len); + + if(!m_buf) + ERROR_LOG(ERROR, ("Failed to convert UPN to utf8")); + + // Note: possible error would be indicated by the fact that m_buf is NULL. + return; +} + + +UPN::~UPN() +{ + if (m_buf) + free(m_buf); +} + + +/** + Convert a wide-char string to utf8 representation. + + @param[in] string null-terminated wide-char string to be converted + @param[in,out] len length of the string to be converted or 0; on + return length (in bytes, excluding terminating + null character) of the converted string + + If len is 0 then the length of the string will be computed by this function. + + @return Pointer to a buffer containing utf8 representation or NULL in + case of error. + + @note The returned buffer must be freed with @c free() call. +*/ + +char* wchar_to_utf8(const wchar_t *string, size_t *len) +{ + char *buf= NULL; + size_t str_len= len && *len ? *len : wcslen(string); + + /* + A conversion from utf8 to wchar_t will never take more than 3 bytes per + character, so a buffer of length 3 * str_len schould be sufficient. + We check that assumption with an assertion later. + */ + + size_t buf_len= 3 * str_len; + + buf= (char*)malloc(buf_len + 1); + if (!buf) + { + DBUG_PRINT("error",("Out of memory when converting string '%S' to utf8", + string)); + return NULL; + } + + int res= WideCharToMultiByte(CP_UTF8, // convert to UTF-8 + 0, // conversion flags + string, // input buffer + str_len, // its length + buf, buf_len, // output buffer and its size + NULL, NULL); // default character (not used) + + if (res) + { + buf[res]= '\0'; + if (len) + *len= res; + return buf; + } + + // res is 0 which indicates error + +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not convert string '%S' to utf8" + ", WideCharToMultiByte() failed with error %X (%s)", + string, GetLastError(), + get_last_error_message(error_buf))); +#endif + + // Let's check our assumption about sufficient buffer size + DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError()); + + return NULL; +} + + +/** + Convert an utf8 string to a wide-char string. + + @param[in] string null-terminated utf8 string to be converted + @param[in,out] len length of the string to be converted or 0; on + return length (in chars) of the converted string + + If len is 0 then the length of the string will be computed by this function. + + @return Pointer to a buffer containing wide-char representation or NULL in + case of error. + + @note The returned buffer must be freed with @c free() call. +*/ + +wchar_t* utf8_to_wchar(const char *string, size_t *len) +{ + size_t buf_len; + + /* + Note: length (in bytes) of an utf8 string is always bigger than the + number of characters in this string. Hence a buffer of size len will + be sufficient. We add 1 for the terminating null character. + */ + + buf_len= len && *len ? *len : strlen(string); + wchar_t *buf= (wchar_t*)malloc((buf_len+1)*sizeof(wchar_t)); + + if (!buf) + { + DBUG_PRINT("error",("Out of memory when converting utf8 string '%s'" + " to wide-char representation", string)); + return NULL; + } + + size_t res; + res= MultiByteToWideChar(CP_UTF8, // convert from UTF-8 + 0, // conversion flags + string, // input buffer + buf_len, // its size + buf, buf_len); // output buffer and its size + if (res) + { + buf[res]= '\0'; + if (len) + *len= res; + return buf; + } + + // error in MultiByteToWideChar() + +#ifndef DBUG_OFF + Error_message_buf error_buf; + DBUG_PRINT("error", ("Could not convert UPN from UTF-8" + ", MultiByteToWideChar() failed with error %X (%s)", + GetLastError(), get_last_error_message(error_buf))); +#endif + + // Let's check our assumption about sufficient buffer size + DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError()); + + return NULL; +} + + +/** Error handling ****************************************************/ + + +/** + Returns error message corresponding to the last Windows error given + by GetLastError(). + + @note Error message is overwritten by next call to + @c get_last_error_message(). +*/ + +const char* get_last_error_message(Error_message_buf buf) +{ + int error= GetLastError(); + + buf[0]= '\0'; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)buf, sizeof(buf), NULL ); + + return buf; +} diff --git a/libmysql/authentication_win/common.h b/libmysql/authentication_win/common.h new file mode 100644 index 00000000000..ff0f7153664 --- /dev/null +++ b/libmysql/authentication_win/common.h @@ -0,0 +1,324 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef COMMON_H +#define COMMON_H + +#include <my_global.h> +#include <windows.h> +#include <sspi.h> // for CtxtHandle +#include <mysql/plugin_auth.h> // for MYSQL_PLUGIN_VIO + +/// Maximum length of the target service name. +#define MAX_SERVICE_NAME_LENGTH 1024 + + +/** Debugging and error reporting infrastructure ***************************/ + +/* + Note: We use plugin local logging and error reporting mechanisms until + WL#2940 (plugin service: error reporting) is available. +*/ + +#undef INFO +#undef WARNING +#undef ERROR + +struct error_log_level +{ + typedef enum {INFO, WARNING, ERROR} type; +}; + + +/* + If DEBUG_ERROR_LOG is defined then error logging happens only + in debug-copiled code. Otherwise ERROR_LOG() expands to + error_log_print() even in production code. Note that in client + plugin, error_log_print() will print nothing if opt_auth_win_clinet_log + is 0. + + Note: Macro ERROR_LOG() can use printf-like format string like this: + + ERROR_LOG(Level, ("format string", args)); + + The implementation should handle it correctly. Currently it is passed + to fprintf() (see error_log_vprint() function). +*/ + +extern "C" int opt_auth_win_client_log; + +#if defined(DEBUG_ERROR_LOG) && defined(DBUG_OFF) +#define ERROR_LOG(Level, Msg) do {} while (0) +#else +#define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg +#endif + + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args); + +template <error_log_level::type Level> +void error_log_print(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + error_log_vprint(Level, fmt, args); + va_end(args); +} + +typedef char Error_message_buf[1024]; +const char* get_last_error_message(Error_message_buf); + + +/* + Internal implementation of debug message printing which does not use + dbug library. This is invoked via macro: + + DBUG_PRINT_DO(Keyword, ("format string", args)); + + This is supposed to be used as an implementation of DBUG_PRINT() macro, + unless the dbug library implementation is used or debug messages are disabled. +*/ + +#ifndef DBUG_OFF + +#define DBUG_PRINT_DO(Keyword, Msg) \ + do { \ + if (2 > opt_auth_win_client_log) break; \ + fprintf(stderr, "winauth: %s: ", Keyword); \ + debug_msg Msg; \ + } while (0) + +inline +void debug_msg(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); + va_end(args); +} + +#else +#define DBUG_PRINT_DO(K, M) do {} while (0) +#endif + + +#ifndef WINAUTH_USE_DBUG_LIB + +#undef DBUG_PRINT +#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg) + +/* + Redefine few more debug macros to make sure that no symbols from + dbug library are used. +*/ + +#undef DBUG_ENTER +#define DBUG_ENTER(X) do {} while (0) + +#undef DBUG_RETURN +#define DBUG_RETURN(X) return (X) + +#undef DBUG_ASSERT +#ifndef DBUG_OFF +#define DBUG_ASSERT(X) assert (X) +#else +#define DBUG_ASSERT(X) do {} while (0) +#endif + +#undef DBUG_DUMP +#define DBUG_DUMP(A,B,C) do {} while (0) + +#endif + + +/** Blob class *************************************************************/ + +typedef unsigned char byte; + +/** + Class representing a region of memory (e.g., a string or binary buffer). + + @note This class does not allocate memory. It merely describes a region + of memory which must be allocated externally (if it is dynamic memory). +*/ + +class Blob +{ + byte *m_ptr; ///< Pointer to the first byte of the memory region. + size_t m_len; ///< Length of the memory region. + +public: + + Blob(): m_ptr(NULL), m_len(0) + {} + + Blob(const byte *ptr, const size_t len) + : m_ptr(const_cast<byte*>(ptr)), m_len(len) + {} + + Blob(const char *str): m_ptr((byte*)str) + { + m_len= strlen(str); + } + + byte* ptr() const + { + return m_ptr; + } + + size_t len() const + { + return m_len; + } + + byte& operator[](unsigned pos) const + { + static byte out_of_range= 0; // alas, no exceptions... + return pos < len() ? m_ptr[pos] : out_of_range; + } + + bool is_null() const + { + return m_ptr == NULL; + } + + void trim(size_t l) + { + m_len= l; + } +}; + + +/** Connection class *******************************************************/ + +/** + Convenience wrapper around MYSQL_PLUGIN_VIO object providing basic + read/write operations. +*/ + +class Connection +{ + MYSQL_PLUGIN_VIO *m_vio; ///< Pointer to @c MYSQL_PLUGIN_VIO structure. + + /** + If non-zero, indicates that connection is broken. If this has happened + because of failed operation, stores non-zero error code from that failure. + */ + int m_error; + +public: + + Connection(MYSQL_PLUGIN_VIO *vio); + int write(const Blob&); + Blob read(); + + int error() const + { + return m_error; + } +}; + + +/** Sid class **************************************************************/ + +/** + Class for storing and manipulating Windows security identifiers (SIDs). +*/ + +class Sid +{ + TOKEN_USER *m_data; ///< Pointer to structure holding identifier's data. + SID_NAME_USE m_type; ///< Type of identified entity. + +public: + + Sid(const wchar_t*); + Sid(HANDLE sec_token); + ~Sid(); + + bool is_valid(void) const; + + bool is_group(void) const + { + return m_type == SidTypeGroup + || m_type == SidTypeWellKnownGroup + || m_type == SidTypeAlias; + } + + bool is_user(void) const + { + return m_type == SidTypeUser; + } + + bool operator==(const Sid&); + + operator PSID() const + { + return (PSID)m_data->User.Sid; + } + +#ifndef DBUG_OFF + +private: + char *m_as_string; ///< Cached string representation of the SID. +public: + const char* as_string(); + +#endif +}; + + +/** UPN class **************************************************************/ + +/** + An object of this class obtains and stores User Principal Name of the + account under which current process is running. +*/ + +class UPN +{ + char *m_buf; ///< Pointer to UPN in utf8 representation. + size_t m_len; ///< Length of the name. + +public: + + UPN(); + ~UPN(); + + bool is_valid() const + { + return m_len > 0; + } + + const Blob as_blob() const + { + return m_len ? Blob((byte*)m_buf, m_len) : Blob(); + } + + const char* as_string() const + { + return (const char*)m_buf; + } + +}; + + +char* wchar_to_utf8(const wchar_t*, size_t*); +wchar_t* utf8_to_wchar(const char*, size_t*); + +#endif diff --git a/libmysql/authentication_win/handshake.cc b/libmysql/authentication_win/handshake.cc new file mode 100644 index 00000000000..ec665af9ef9 --- /dev/null +++ b/libmysql/authentication_win/handshake.cc @@ -0,0 +1,289 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "handshake.h" + + +/** Handshake class implementation **********************************/ + +/** + Create common part of handshake context. + + @param[in] ssp name of the SSP (Security Service Provider) to + be used for authentication + @param[in] side is this handshake object used for server- or + client-side handshake + + Prepare for handshake using the @c ssp security module. We use + "Negotiate" which picks best available module. Parameter @c side + tells if this is preparing for server or client side authentication + and is used to prepare appropriate credentials. +*/ + +Handshake::Handshake(const char *ssp, side_t side) +: m_atts(0L), m_error(0), m_complete(FALSE), + m_have_credentials(false), m_have_sec_context(false) +#ifndef DBUG_OFF + , m_ssp_info(NULL) +#endif +{ + SECURITY_STATUS ret; + + // Obtain credentials for the authentication handshake. + + ret= AcquireCredentialsHandle(NULL, (SEC_CHAR*)ssp, + side == SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, + NULL, NULL, NULL, NULL, &m_cred, &m_expire); + + if (ret != SEC_E_OK) + { + DBUG_PRINT("error", ("AcqireCredentialsHandle() failed" + " with error %X", ret)); + ERROR_LOG(ERROR, ("Could not obtain local credentials" + " required for authentication")); + m_error= ret; + } + + m_have_credentials= true; +} + + +Handshake::~Handshake() +{ + if (m_have_credentials) + FreeCredentialsHandle(&m_cred); + if (m_have_sec_context) + DeleteSecurityContext(&m_sctx); + m_output.free(); + +#ifndef DBUG_OFF + if (m_ssp_info) + FreeContextBuffer(m_ssp_info); +#endif +} + + +/** + Read and process data packets from the other end of a connection. + + @param[IN] con a connection to read packets from + + Packets are read and processed until authentication handshake is + complete. It is assumed that the peer will send at least one packet. + Packets are processed with @c process_data() method. If new data is + generated during packet processing, this data is sent to the peer and + another round of packet exchange starts. + + @return 0 on success. + + @note In case of error, appropriate error message is logged. +*/ +int Handshake::packet_processing_loop() +{ + m_round= 0; + + do { + ++m_round; + // Read packet send by the peer + + DBUG_PRINT("info", ("Waiting for packet")); + Blob packet= read_packet(); + if (error()) + { + ERROR_LOG(ERROR, ("Error reading packet in round %d", m_round)); + return 1; + } + DBUG_PRINT("info", ("Got packet of length %d", packet.len())); + + /* + Process received data, possibly generating new data to be sent. + */ + + Blob new_data= process_data(packet); + + if (error()) + { + ERROR_LOG(ERROR, ("Error processing packet in round %d", m_round)); + return 1; + } + + /* + If new data has been generated, send it to the peer. Otherwise + handshake must be completed. + */ + + if (!new_data.is_null()) + { + DBUG_PRINT("info", ("Round %d started", m_round)); + + DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); + int ret= write_packet(new_data); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing packet in round %d", m_round)); + return 1; + } + DBUG_PRINT("info", ("Data sent")); + } + else if (!is_complete()) + { + ERROR_LOG(ERROR, ("No data to send in round %d" + " but handshake is not complete", m_round)); + return 1; + } + + /* + To protect against malicious clients, break handshake exchange if + too many rounds. + */ + + if (m_round > MAX_HANDSHAKE_ROUNDS) + { + ERROR_LOG(ERROR, ("Authentication handshake could not be completed" + " after %d rounds", m_round)); + return 1; + } + + } while(!is_complete()); + + ERROR_LOG(INFO, ("Handshake completed after %d rounds", m_round)); + return 0; +} + + +#ifndef DBUG_OFF + +/** + Get name of the security package which was used in authentication. + + This method should be called only after handshake was completed. It is + available only in debug builds. + + @return Name of security package or NULL if it can not be obtained. +*/ + +const char* Handshake::ssp_name() +{ + if (!m_ssp_info && m_complete) + { + SecPkgContext_PackageInfo pinfo; + + int ret= QueryContextAttributes(&m_sctx, SECPKG_ATTR_PACKAGE_INFO, &pinfo); + + if (SEC_E_OK == ret) + { + m_ssp_info= pinfo.PackageInfo; + } + else + DBUG_PRINT("error", + ("Could not obtain SSP info from authentication context" + ", QueryContextAttributes() failed with error %X", ret)); + } + + return m_ssp_info ? m_ssp_info->Name : NULL; +} + +#endif + + +/** + Process result of @c {Initialize,Accept}SecurityContext() function. + + @param[in] ret return code from @c {Initialize,Accept}SecurityContext() + function + + This function analyses return value of Windows + @c {Initialize,Accept}SecurityContext() function. A call to + @c CompleteAuthToken() is done if requested. If authentication is complete, + this fact is marked in the internal state of the Handshake object. + If errors are detected the object is moved to error state. + + @return True if error has been detected. +*/ + +bool Handshake::process_result(int ret) +{ + /* + First check for errors and set the m_complete flag if the result + indicates that handshake is complete. + */ + + switch (ret) + { + case SEC_E_OK: + case SEC_I_COMPLETE_NEEDED: + // Handshake completed + m_complete= true; + break; + + case SEC_I_CONTINUE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + break; + + default: + m_error= ret; + return true; + } + + m_have_sec_context= true; + + /* + If the result indicates a need for this, complete the authentication + token. + */ + + switch (ret) + { + case SEC_I_COMPLETE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + ret= CompleteAuthToken(&m_sctx, &m_output); + if (ret != 0) + { + DBUG_PRINT("error", ("CompleteAuthToken() failed with error %X", ret)); + m_error= ret; + return true; + } + default: + break; + } + + return false; +} + + +/** Security_buffer class implementation **********************************/ + + +Security_buffer::Security_buffer(const Blob &blob): m_allocated(false) +{ + init(blob.ptr(), blob.len()); +} + + +Security_buffer::Security_buffer(): m_allocated(true) +{ + init(NULL, 0); +} + + +void Security_buffer::free(void) +{ + if (!m_allocated) + return; + if (!ptr()) + return; + FreeContextBuffer(ptr()); + m_allocated= false; +} diff --git a/libmysql/authentication_win/handshake.h b/libmysql/authentication_win/handshake.h new file mode 100644 index 00000000000..5292948b583 --- /dev/null +++ b/libmysql/authentication_win/handshake.h @@ -0,0 +1,181 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef HANDSHAKE_H +#define HANDSHAKE_H + +#include "common.h" + +/** + Name of the SSP (Security Support Provider) to be used for authentication. + + We use "Negotiate" which will find the most secure SSP which can be used + and redirect to that SSP. +*/ +#define SSP_NAME "Negotiate" + +/** + Maximal number of rounds in authentication handshake. + + Server will interrupt authentication handshake with error if client's + identity can not be determined within this many rounds. +*/ +#define MAX_HANDSHAKE_ROUNDS 50 + + +/// Convenience wrapper around @c SecBufferDesc. + +class Security_buffer: public SecBufferDesc +{ + SecBuffer m_buf; ///< A @c SecBuffer instance. + + void init(byte *ptr, size_t len) + { + ulVersion= 0; + cBuffers= 1; + pBuffers= &m_buf; + + m_buf.BufferType= SECBUFFER_TOKEN; + m_buf.pvBuffer= ptr; + m_buf.cbBuffer= len; + } + + /// If @c false, no deallocation will be done in the destructor. + bool m_allocated; + + public: + + Security_buffer(const Blob&); + Security_buffer(); + + ~Security_buffer() + { + free(); + } + + byte* ptr() const + { + return (byte*)m_buf.pvBuffer; + } + + size_t len() const + { + return m_buf.cbBuffer; + } + + bool is_valid() const + { + return ptr() != NULL; + } + + const Blob as_blob() const + { + return Blob(ptr(), len()); + } + + void free(void); +}; + + +/// Common base for Handshake_{server,client}. + +class Handshake +{ +public: + + typedef enum {CLIENT, SERVER} side_t; + + Handshake(const char *ssp, side_t side); + virtual ~Handshake(); + + int Handshake::packet_processing_loop(); + + bool virtual is_complete() const + { + return m_complete; + } + + int error() const + { + return m_error; + } + +protected: + + /// Security context object created during the handshake. + CtxtHandle m_sctx; + + /// Credentials of the principal performing this handshake. + CredHandle m_cred; + + /// Stores expiry date of the created security context. + TimeStamp m_expire; + + /// Stores attributes of the created security context. + ULONG m_atts; + + /** + Round of the handshake (starting from round 1). One round + consist of reading packet from the other side, processing it and + optionally sending a reply (see @c packet_processing_loop()). + */ + unsigned int m_round; + + /// If non-zero, stores error code of the last failed operation. + int m_error; + + /// @c true when handshake is complete. + bool m_complete; + + /// @c true when the principal credentials has been determined. + bool m_have_credentials; + + /// @c true when the security context has been created. + bool m_have_sec_context; + + /// Buffer for data to be send to the other side. + Security_buffer m_output; + + bool process_result(int); + + /** + This method is used inside @c packet_processing_loop to process + data packets received from the other end. + + @param[IN] data data to be processed + + @return A blob with data to be sent to the other end or null blob if + no more data needs to be exchanged. + */ + virtual Blob process_data(const Blob &data) =0; + + /// Read packet from the other end. + virtual Blob read_packet() =0; + + /// Write packet to the other end. + virtual int write_packet(Blob &data) =0; + +#ifndef DBUG_OFF + +private: + SecPkgInfo *m_ssp_info; +public: + const char* ssp_name(); + +#endif +}; + + +#endif diff --git a/libmysql/authentication_win/handshake_client.cc b/libmysql/authentication_win/handshake_client.cc new file mode 100644 index 00000000000..7e89fc92ae7 --- /dev/null +++ b/libmysql/authentication_win/handshake_client.cc @@ -0,0 +1,378 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "handshake.h" + +#include <mysql.h> // for MYSQL structure + + +/// Client-side context for authentication handshake + +class Handshake_client: public Handshake +{ + /** + Name of the server's service for which we authenticate. + + The service name is sent by server in the initial packet. If no + service name is used, this member is @c NULL. + */ + SEC_WCHAR *m_service_name; + + /// Buffer for storing service name obtained from server. + SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH]; + + Connection &m_con; + +public: + + Handshake_client(Connection &con, const char *target, size_t len); + ~Handshake_client(); + + Blob first_packet(); + Blob process_data(const Blob&); + + Blob read_packet(); + int write_packet(Blob &data); +}; + + +/** + Create authentication handshake context for client. + + @param con connection for communication with the peer + @param target name of the target service with which we will authenticate + (can be NULL if not used) + + Some security packages (like Kerberos) require providing explicit name + of the service with which a client wants to authenticate. The server-side + authentication plugin sends this name in the greeting packet + (see @c win_auth_handshake_{server,client}() functions). +*/ + +Handshake_client::Handshake_client(Connection &con, + const char *target, size_t len) +: Handshake(SSP_NAME, CLIENT), m_service_name(NULL), m_con(con) +{ + if (!target || 0 == len) + return; + + // Convert received UPN to internal WCHAR representation. + + m_service_name= utf8_to_wchar(target, &len); + + if (m_service_name) + DBUG_PRINT("info", ("Using target service: %S\n", m_service_name)); + else + { + /* + Note: we ignore errors here - m_target will be NULL, the target name + will not be used and system will fall-back to NTLM authentication. But + we leave trace in error log. + */ + ERROR_LOG(WARNING, ("Could not decode UPN sent by the server" + "; target service name will not be used" + " and Kerberos authentication will not work")); + } +} + + +Handshake_client::~Handshake_client() +{ + if (m_service_name) + free(m_service_name); +} + + +Blob Handshake_client::read_packet() +{ + /* + We do a fake read in the first round because first + packet from the server containing UPN must be read + before the handshake context is created and the packet + processing loop starts. We return an empty blob here + and process_data() function will ignore it. + */ + if (m_round == 1) + return Blob(); + + // Otherwise we read packet from the connection. + + Blob packet= m_con.read(); + m_error= m_con.error(); + if (!m_error && packet.is_null()) + m_error= true; // (no specific error code assigned) + + if (m_error) + return Blob(); + + DBUG_PRINT("dump", ("Got the following bytes")); + DBUG_DUMP("dump", packet.ptr(), packet.len()); + return packet; +} + + + +int Handshake_client::write_packet(Blob &data) +{ + /* + Length of the first data payload send by client authentication plugin is + limited to 255 bytes (because it is wrapped inside client authentication + packet and is length-encoded with 1 byte for the length). + + If the data payload is longer than 254 bytes, then it is sent in two parts: + first part of length 255 will be embedded in the authentication packet, + second part will be sent in the following packet. Byte 255 of the first + part contains information about the total length of the payload. It is a + number of blocks of size 512 bytes which is sufficient to store the + combined packets. + + Server's logic for reading first client's payload is as follows + (see Handshake_server::read_packet()): + 1. Read data from the authentication packet, if it is shorter than 255 bytes + then that is all data sent by client. + 2. If there is 255 bytes of data in the authentication packet, read another + packet and append it to the data, skipping byte 255 of the first packet + which can be used to allocate buffer of appropriate size. + */ + + size_t len2= 0; // length of the second part of first data payload + byte saved_byte; // for saving byte 255 in which data length is stored + + if (m_round == 1 && data.len() > 254) + { + len2= data.len() - 254; + DBUG_PRINT("info", ("Splitting first packet of length %lu" + ", %lu bytes will be sent in a second part", + data.len(), len2)); + /* + Store in byte 255 the number of 512b blocks that are needed to + keep all the data. + */ + unsigned block_count= data.len()/512 + ((data.len() % 512) ? 1 : 0); + DBUG_ASSERT(block_count < (unsigned)0x100); + saved_byte= data[254]; + data[254] = block_count; + + data.trim(255); + } + + DBUG_PRINT("dump", ("Sending the following data")); + DBUG_DUMP("dump", data.ptr(), data.len()); + int ret= m_con.write(data); + + if (ret) + return ret; + + // Write second part if it is present. + if (len2) + { + data[254]= saved_byte; + Blob data2(data.ptr() + 254, len2); + DBUG_PRINT("info", ("Sending second part of data")); + DBUG_DUMP("info", data2.ptr(), data2.len()); + ret= m_con.write(data2); + } + + return ret; +} + + +/** + Process data sent by server. + + @param[in] data blob with data from server + + This method analyses data sent by server during authentication handshake. + If client should continue packet exchange, this method returns data to + be sent to the server next. If no more data needs to be exchanged, an + empty blob is returned and @c is_complete() is @c true. In case of error + an empty blob is returned and @c error() gives non-zero error code. + + When invoked for the first time (in the first round of the handshake) + there is no data from the server (data blob is null) and the intial + packet is generated without an input. + + @return Data to be sent to the server next or null blob if no more data + needs to be exchanged or in case of error. +*/ + +Blob Handshake_client::process_data(const Blob &data) +{ +#if !defined(DBUG_OFF) && defined(WINAUTH_USE_DBUG_LIB) + /* + Code for testing the logic for sending the first client payload. + + A fake data of length given by environment variable TEST_PACKET_LENGTH + (or default 255 bytes) is sent to the server. First 2 bytes of the + payload contain its total length (LSB first). The length of test data + is limited to 2048 bytes. + + Upon receiving test data, server will check that data is correct and + refuse connection. If server detects data errors it will crash on + assertion. + + This code is executed if debug flag "winauth_first_packet_test" is + set, e.g. using client option: + + --debug="d,winauth_first_packet_test" + + The same debug flag must be enabled in the server, e.g. using + statement: + + SET GLOBAL debug= '+d,winauth_first_packet_test'; + */ + + static byte test_buf[2048]; + + if (m_round == 1 + && DBUG_EVALUATE_IF("winauth_first_packet_test", true, false)) + { + const char *env= getenv("TEST_PACKET_LENGTH"); + size_t len= env ? atoi(env) : 0; + if (!len) + len= 255; + if (len > sizeof(test_buf)) + len= sizeof(test_buf); + + // Store data length in first 2 bytes. + byte *ptr= test_buf; + *ptr++= len & 0xFF; + *ptr++= len >> 8; + + // Fill remaining bytes with known values. + for (byte b= 0; ptr < test_buf + len; ++ptr, ++b) + *ptr= b; + + return Blob(test_buf, len); + }; + +#endif + + Security_buffer input(data); + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + m_round == 1 ? NULL : &m_sctx, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + m_round == 1 ? NULL : &input, // input data + 0, // reserved + &m_sctx, // context + &m_output, // output data + &m_atts, // attributes + &m_expire); // expire date + + if (process_result(ret)) + { + DBUG_PRINT("error", + ("InitializeSecurityContext() failed with error %X", ret)); + return Blob(); + } + + return m_output.as_blob(); +} + + +/**********************************************************************/ + + +/** + Perform authentication handshake from client side. + + @param[in] vio pointer to @c MYSQL_PLUGIN_VIO instance to be used + for communication with the server + @param[in] mysql pointer to a MySQL connection for which we authenticate + + After reading the initial packet from server, containing its UPN to be + used as service name, client starts packet exchange by sending the first + packet in this exchange. While handshake is not yet completed, client + reads packets sent by the server and process them, possibly generating new + data to be sent to the server. + + This function reports errors. + + @return 0 on success. +*/ + +int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + DBUG_ENTER("win_auth_handshake_client"); + + /* + Check if we should enable logging. + */ + { + const char *opt= getenv("AUTHENTICATION_WIN_LOG"); + int opt_val= opt ? atoi(opt) : 0; + if (opt && !opt_val) + { + if (!strncasecmp("on", opt, 2)) opt_val= 1; + if (!strncasecmp("yes", opt, 3)) opt_val= 1; + if (!strncasecmp("true", opt, 4)) opt_val= 1; + if (!strncasecmp("debug", opt, 5)) opt_val= 2; + if (!strncasecmp("dbug", opt, 4)) opt_val= 2; + } + opt_auth_win_client_log= opt_val; + } + + ERROR_LOG(INFO, ("Authentication handshake for account %s", mysql->user)); + + // Create connection object. + + Connection con(vio); + DBUG_ASSERT(!con.error()); + + // Read initial packet from server containing service name. + + Blob service_name= con.read(); + + if (con.error() || service_name.is_null()) + { + ERROR_LOG(ERROR, ("Error reading initial packet")); + DBUG_RETURN(CR_ERROR); + } + DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len())); + + // Create authentication handshake context using the given service name. + + Handshake_client hndshk(con, + service_name[0] ? (char *)service_name.ptr() : NULL, + service_name.len()); + if (hndshk.error()) + { + ERROR_LOG(ERROR, ("Could not create authentication handshake context")); + DBUG_RETURN(CR_ERROR); + } + + DBUG_ASSERT(!hndshk.error()); + + /* + Read and process packets from server until handshake is complete. + Note that the first read from server is dummy + (see Handshake_client::read_packet()) as we already have read the + first packet to establish service name. + */ + if (hndshk.packet_processing_loop()) + DBUG_RETURN(CR_ERROR); + + DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); + + DBUG_RETURN(CR_OK); +} diff --git a/libmysql/authentication_win/log_client.cc b/libmysql/authentication_win/log_client.cc new file mode 100644 index 00000000000..df4ce4f9c2a --- /dev/null +++ b/libmysql/authentication_win/log_client.cc @@ -0,0 +1,55 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "common.h" + +/** + This option is set in win_auth_handshake_client() function + in handshake_client.cc. + + Values: + 0 - no logging + 1 - log error/warning/info messages + 2 - also log debug messages + + Note: No error or debug messages are logged in production code + (see logging macros in common.h). +*/ +int opt_auth_win_client_log= 0; + + +// Client-side logging function + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args) +{ + if (0 == opt_auth_win_client_log) + return; + + const char *level_string= ""; + + switch (level) + { + case error_log_level::INFO: level_string= "Note"; break; + case error_log_level::WARNING: level_string= "Warning"; break; + case error_log_level::ERROR: level_string= "ERROR"; break; + } + + fprintf(stderr, "Windows Authentication Plugin %s: ", level_string); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); +} diff --git a/libmysql/authentication_win/plugin_client.cc b/libmysql/authentication_win/plugin_client.cc new file mode 100644 index 00000000000..72769ada8f6 --- /dev/null +++ b/libmysql/authentication_win/plugin_client.cc @@ -0,0 +1,58 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <mysql.h> +#include <mysql/plugin_auth.h> +#include <mysql/client_plugin.h> + +#include "common.h" + +static int win_auth_client_plugin_init(char*, size_t, int, va_list) +{ + return 0; +} + + +static int win_auth_client_plugin_deinit() +{ + return 0; +} + + +int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); + + +/* + Client plugin declaration. This is added to mysql_client_builtins[] + in sql-common/client.c +*/ + +extern "C" +st_mysql_client_plugin_AUTHENTICATION win_auth_client_plugin= +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, + "authentication_windows_client", + "Rafal Somla", + "Windows Authentication Plugin - client side", + {0,1,0}, + "GPL", + NULL, + win_auth_client_plugin_init, + win_auth_client_plugin_deinit, + NULL, // option handling + win_auth_handshake_client +}; diff --git a/libmysql/libmysql.def b/libmysql/libmysql.def index fc15fcf0884..ce85d2a4086 100644 --- a/libmysql/libmysql.def +++ b/libmysql/libmysql.def @@ -104,4 +104,3 @@ EXPORTS mysql_server_end mysql_set_character_set mysql_get_character_set_info - mysql_plugin_options diff --git a/mysql-test/collections/default.experimental b/mysql-test/collections/default.experimental index 5afbba36843..5ecc8ef8c64 100644 --- a/mysql-test/collections/default.experimental +++ b/mysql-test/collections/default.experimental @@ -2,9 +2,7 @@ # in alphabetical order. This also helps with merge conflict resolution. binlog.binlog_multi_engine # joro : NDB tests marked as experimental as agreed with bochklin -binlog.binlog_bug23533 # WL#5867: skozlov: test case moved from unused bugs suite -binlog.binlog_bug36391 # WL#5867: skozlov: test case moved from unused bugs suite - +binlog.binlog_bug23533 # skozlov: BUG#12371924 funcs_1.charset_collation_1 # depends on compile-time decisions @@ -17,8 +15,9 @@ main.sp @solaris # Bug#47791 2010-01-20 alik Several tes main.type_float @freebsd # Bug#38965 2010-05-04 alik test cases gis-rtree, type_float, type_newdecimal fail in embedded server main.wait_timeout @solaris # Bug#51244 2010-04-26 alik wait_timeout fails on OpenSolaris +rpl.rpl_heartbeat_basic # BUG#12403008 2011-04-27 sven fails sporadically rpl.rpl_innodb_bug28430 # Bug#46029 -rpl.rpl_bug37426 # WL#5867: skozlov: test case moved from unused bugs suite +rpl.rpl_show_slave_hosts # BUG#12416700 2011-05-02 sven fails sporadically sys_vars.max_sp_recursion_depth_func @solaris # Bug#47791 2010-01-20 alik Several test cases fail on Solaris with error Thread stack overrun sys_vars.plugin_dir_basic # Bug#52223 2010-11-24 alik Test "plugin_dir_basic" does not support RPM build (test) directory structure diff --git a/mysql-test/extra/rpl_tests/rpl_extra_col_slave.test b/mysql-test/extra/rpl_tests/rpl_extra_col_slave.test index 482c7e44922..119e081878c 100644 --- a/mysql-test/extra/rpl_tests/rpl_extra_col_slave.test +++ b/mysql-test/extra/rpl_tests/rpl_extra_col_slave.test @@ -396,7 +396,7 @@ sync_slave_with_master; # Error reaction is up to sql_mode of the slave sql (bug#38173) #--echo *** Create t9 on slave *** # Please, check BUG#47741 to see why you are not testing NDB. -if ($engine_type != NDB) +if (`SELECT UPPER(LEFT($engine_type, 3)) != 'NDB'`) { STOP SLAVE; RESET SLAVE; @@ -440,12 +440,13 @@ if ($engine_type != NDB) #--let $slave_skip_counter= 2 #--let $show_slave_sql_error= 1 #--source include/wait_for_slave_sql_error_and_skip.inc -} -#--echo *** Drop t9 *** -connection master; -DROP TABLE t9; -sync_slave_with_master; + #--echo *** Drop t9 *** + connection master; + DROP TABLE t9; + sync_slave_with_master; + +} ############################################ # More columns in slave at middle of table # diff --git a/mysql-test/lib/My/ConfigFactory.pm b/mysql-test/lib/My/ConfigFactory.pm index 6ba23754890..cca4fa2497e 100644 --- a/mysql-test/lib/My/ConfigFactory.pm +++ b/mysql-test/lib/My/ConfigFactory.pm @@ -1,5 +1,5 @@ # -*- cperl -*- -# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public @@ -150,7 +150,11 @@ sub fix_tmpdir { sub fix_log_error { my ($self, $config, $group_name, $group)= @_; my $dir= $self->{ARGS}->{vardir}; - return "$dir/log/$group_name.err"; + if ( $::opt_valgrind and $::opt_debug ) { + return "$dir/log/$group_name.trace"; + } else { + return "$dir/log/$group_name.err"; + } } sub fix_log { diff --git a/mysql-test/lib/My/Find.pm b/mysql-test/lib/My/Find.pm index 521adecfd13..9d1d2915012 100644 --- a/mysql-test/lib/My/Find.pm +++ b/mysql-test/lib/My/Find.pm @@ -1,5 +1,5 @@ # -*- cperl -*- -# Copyright (C) 2008 MySQL AB +# Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -156,8 +156,7 @@ sub my_find_paths { # User can select to look in a special build dir # which is a subdirectory of any of the paths my @extra_dirs; - my $build_dir= $::opt_config_dir || $ENV{MTR_VS_CONFIG} - || $ENV{MTR_BUILD_DIR}; + my $build_dir= $::opt_vs_config || $ENV{MTR_VS_CONFIG} || $ENV{MTR_BUILD_DIR}; push(@extra_dirs, $build_dir) if defined $build_dir; if (defined $extension){ diff --git a/mysql-test/lib/My/SafeProcess/safe_process.pl b/mysql-test/lib/My/SafeProcess/safe_process.pl index e3114a749d3..54b0073f8df 100644 --- a/mysql-test/lib/My/SafeProcess/safe_process.pl +++ b/mysql-test/lib/My/SafeProcess/safe_process.pl @@ -94,7 +94,7 @@ eval { local $SIG{INT}= \&handle_signal; local $SIG{CHLD}= sub { message("Got signal @_"); - kill(9, -$child_pid); + kill('KILL', -$child_pid); my $ret= waitpid($child_pid, 0); if ($? & 127){ exit(65); # Killed by signal @@ -134,7 +134,7 @@ if ( $@ ) { # Use negative pid in order to kill the whole # process group # -my $ret= kill(9, -$child_pid); +my $ret= kill('KILL', -$child_pid); message("Killed child: $child_pid, ret: $ret"); if ($ret > 0) { message("Killed child: $child_pid"); diff --git a/mysql-test/lib/mtr_gcov.pl b/mysql-test/lib/mtr_gcov.pl index d8fb1c0a07d..6f9e744a548 100644 --- a/mysql-test/lib/mtr_gcov.pl +++ b/mysql-test/lib/mtr_gcov.pl @@ -1,5 +1,5 @@ # -*- cperl -*- -# Copyright (C) 2004, 2006 MySQL AB, 2009 Sun Microsystems, Inc. +# Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -31,7 +31,7 @@ sub gcov_prepare ($) { # # Collect gcov statistics. # Arguments: -# $dir basedir, normally source directory +# $dir basedir, normally build directory # $gcov gcov utility program [path] name # $gcov_msg message file name # $gcov_err error file name @@ -43,29 +43,25 @@ sub gcov_collect ($$$) { my $start_dir= cwd(); print "Collecting source coverage info using '$gcov'...\n"; - -f "$start_dir/$gcov_msg" and unlink("$start_dir/$gcov_msg"); - -f "$start_dir/$gcov_err" and unlink("$start_dir/$gcov_err"); + -f "$dir/$gcov_msg" and unlink("$dir/$gcov_msg"); + -f "$dir/$gcov_err" and unlink("$dir/$gcov_err"); my @dirs= `find "$dir" -type d -print | sort`; #print "List of directories:\n@dirs\n"; foreach my $d ( @dirs ) { - my $dir_reported= 0; chomp($d); chdir($d) or next; - foreach my $f ( (glob("*.h"), glob("*.cc"), glob("*.c")) ) { - $f =~ /(.*)\.[ch]c?/; - -f "$1.gcno" or next; - if (!$dir_reported) { - print "Collecting in '$d'...\n"; - $dir_reported= 1; - } - system("$gcov $f 2>>$start_dir/$gcov_err >>$start_dir/$gcov_msg"); + my @flist= glob("*.*.gcno"); + print ("Collecting in '$d'...\n") if @flist; + + foreach my $f (@flist) { + system("$gcov $f 2>>$dir/$gcov_err >>$dir/$gcov_msg"); } chdir($start_dir); } - print "gcov info in $gcov_msg, errors in $gcov_err\n"; + print "gcov info in $dir/$gcov_msg, errors in $dir/$gcov_err\n"; } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 0d227170476..f3f1181562b 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -210,8 +210,8 @@ our $opt_clean_vardir= $ENV{'MTR_CLEAN_VARDIR'}; our $opt_gcov; our $opt_gcov_exe= "gcov"; -our $opt_gcov_err= "mysql-test-gcov.msg"; -our $opt_gcov_msg= "mysql-test-gcov.err"; +our $opt_gcov_err= "mysql-test-gcov.err"; +our $opt_gcov_msg= "mysql-test-gcov.msg"; our $opt_gprof; our %gprof_dirs; @@ -274,12 +274,13 @@ my $opt_strace_client; our $opt_user = "root"; -my $opt_valgrind= 0; +our $opt_valgrind= 0; my $opt_valgrind_mysqld= 0; my $opt_valgrind_mysqltest= 0; my @default_valgrind_args= ("--show-reachable=yes"); my @valgrind_args; my $opt_valgrind_path; +my $valgrind_reports= 0; my $opt_callgrind; my %mysqld_logs; my $opt_debug_sync_timeout= 300; # Default timeout for WAIT_FOR actions. @@ -504,10 +505,29 @@ sub main { push @$completed, run_ctest() if $opt_ctest; + if ($opt_valgrind) { + # Create minimalistic "test" for the reporting + my $tinfo = My::Test->new + ( + name => 'valgrind_report', + ); + # Set dummy worker id to align report with normal tests + $tinfo->{worker} = 0 if $opt_parallel > 1; + if ($valgrind_reports) { + $tinfo->{result}= 'MTR_RES_FAILED'; + $tinfo->{comment}= "Valgrind reported failures at shutdown, see above"; + $tinfo->{failures}= 1; + } else { + $tinfo->{result}= 'MTR_RES_PASSED'; + } + mtr_report_test($tinfo); + push @$completed, $tinfo; + } + mtr_print_line(); if ( $opt_gcov ) { - gcov_collect($basedir, $opt_gcov_exe, + gcov_collect($bindir, $opt_gcov_exe, $opt_gcov_msg, $opt_gcov_err); } @@ -704,6 +724,9 @@ sub run_test_server ($$$) { elsif ($line =~ /^SPENT/) { add_total_times($line); } + elsif ($line eq 'VALGREP' && $opt_valgrind) { + $valgrind_reports= 1; + } else { mtr_error("Unknown response: '$line' from client"); } @@ -889,6 +912,7 @@ sub run_worker ($) { my $valgrind_reports= 0; if ($opt_valgrind_mysqld) { $valgrind_reports= valgrind_exit_reports(); + print $server "VALGREP\n" if $valgrind_reports; } if ( $opt_gprof ) { gprof_collect (find_mysqld($basedir), keys %gprof_dirs); @@ -1198,7 +1222,7 @@ sub command_line_setup { chomp; # remove comments (# foo) at the beginning of the line, or after a # blank at the end of the line - s/( +|^)#.*$//; + s/(\s+|^)#.*$//; # If @ platform specifier given, use this entry only if it contains # @<platform> or @!<xxx> where xxx != platform if (/\@.*/) @@ -1209,8 +1233,8 @@ sub command_line_setup { s/\@.*$//; } # remove whitespace - s/^ +//; - s/ +$//; + s/^\s+//; + s/\s+$//; # if nothing left, don't need to remember this line if ( $_ eq "" ) { next; @@ -2196,7 +2220,12 @@ sub environment_setup { $ENV{'DEFAULT_MASTER_PORT'}= $mysqld_variables{'port'}; $ENV{'MYSQL_TMP_DIR'}= $opt_tmpdir; $ENV{'MYSQLTEST_VARDIR'}= $opt_vardir; + # Used for guessing default plugin dir, we can't really know for sure $ENV{'MYSQL_LIBDIR'}= "$basedir/lib"; + # Override if this does not exist, but lib64 does (best effort) + if (! -d "$basedir/lib" && -d "$basedir/lib64") { + $ENV{'MYSQL_LIBDIR'}= "$basedir/lib64"; + } $ENV{'MYSQL_BINDIR'}= "$bindir"; $ENV{'MYSQL_SHAREDIR'}= $path_language; $ENV{'MYSQL_CHARSETSDIR'}= $path_charsetsdir; @@ -4092,6 +4121,9 @@ sub extract_warning_lines ($$) { ); my $skip_valgrind= 0; + my $last_pat= ""; + my $num_rep= 0; + foreach my $line ( @lines ) { if ($opt_valgrind_mysqld) { @@ -4106,11 +4138,29 @@ sub extract_warning_lines ($$) { { if ( $line =~ /$pat/ ) { - print $Fwarn $line; + # Remove initial timestamp and look for consecutive identical lines + my $line_pat= $line; + $line_pat =~ s/^[0-9: ]*//; + if ($line_pat eq $last_pat) { + $num_rep++; + } else { + # Previous line had been repeated, report that first + if ($num_rep) { + print $Fwarn ".... repeated $num_rep times: $last_pat"; + $num_rep= 0; + } + $last_pat= $line_pat; + print $Fwarn $line; + } last; } } } + # Catch the case of last warning being repeated + if ($num_rep) { + print $Fwarn ".... repeated $num_rep times: $last_pat"; + } + $Fwarn = undef; # Close file } @@ -4747,13 +4797,6 @@ sub mysqld_start ($$) { unlink($mysqld->value('pid-file')); my $output= $mysqld->value('#log-error'); - if ( $opt_valgrind and $opt_debug ) - { - # When both --valgrind and --debug is selected, send - # all output to the trace file, making it possible to - # see the exact location where valgrind complains - $output= "$opt_vardir/log/".$mysqld->name().".trace"; - } # Remember this log file for valgrind error report search $mysqld_logs{$output}= 1 if $opt_valgrind; # Remember data dir for gmon.out files if using gprof @@ -5660,6 +5703,7 @@ sub valgrind_exit_reports() { @culprits); mtr_print_line(); print ("$valgrind_rep\n"); + $found_err= 1; $err_in_report= 0; } # Make ready to collect new report diff --git a/mysql-test/r/func_math.result b/mysql-test/r/func_math.result index 525ee0dc506..ac24ef4dea9 100644 --- a/mysql-test/r/func_math.result +++ b/mysql-test/r/func_math.result @@ -521,6 +521,28 @@ CREATE TABLE t1 SELECT CEIL(LINESTRINGFROMWKB(1) DIV NULL); DROP TABLE t1; CREATE TABLE t1 SELECT FLOOR(LINESTRINGFROMWKB(1) DIV NULL); DROP TABLE t1; +# +# Bug#11765923 58937: MANY VALGRIND ERRORS AFTER GROUPING BY RESULT OF DECIMAL COLUMN FUNCTION +# +CREATE TABLE t1(f1 DECIMAL(22,1)); +INSERT INTO t1 VALUES (0),(1); +SELECT ROUND(f1, f1) FROM t1; +ROUND(f1, f1) +0.0 +1.0 +SELECT ROUND(f1, f1) FROM t1 GROUP BY 1; +ROUND(f1, f1) +0.0 +1.0 +DROP TABLE t1; +# +# Bug#11764671 57533: UNINITIALISED VALUES IN COPY_AND_CONVERT (SQL_STRING.CC) WITH CERTAIN CHA +# +SELECT ROUND(LEAST(15, -4939092, 0.2704), STDDEV('a')); +ROUND(LEAST(15, -4939092, 0.2704), STDDEV('a')) +-4939092.0000 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'a' End of 5.1 tests # # Bug #8433: Overflow must be an error diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result index 43e37dab9d5..b1bf0a6a830 100644 --- a/mysql-test/r/func_time.result +++ b/mysql-test/r/func_time.result @@ -1377,6 +1377,18 @@ NULL SELECT ADDDATE(MONTH(FROM_UNIXTIME(NULL)),INTERVAL 1 HOUR); ADDDATE(MONTH(FROM_UNIXTIME(NULL)),INTERVAL 1 HOUR) NULL +# +# Bug#11889186 60503: CRASH IN MAKE_DATE_TIME WITH DATE_FORMAT / STR_TO_DATE COMBINATION +# +SELECT DATE_FORMAT('0000-00-11', '%W'); +DATE_FORMAT('0000-00-11', '%W') +NULL +SELECT DATE_FORMAT('0000-00-11', '%a'); +DATE_FORMAT('0000-00-11', '%a') +NULL +SELECT DATE_FORMAT('0000-00-11', '%w'); +DATE_FORMAT('0000-00-11', '%w') +NULL End of 5.1 tests # # Bug#57039: constant subtime expression returns incorrect result. diff --git a/mysql-test/r/having.result b/mysql-test/r/having.result index daf043860a4..1bbdd5011c4 100644 --- a/mysql-test/r/having.result +++ b/mysql-test/r/having.result @@ -547,4 +547,26 @@ FROM t1 JOIN t2 ON t2.f2 LIKE 'x' HAVING field1 < 7; field1 DROP TABLE t1,t2; +# +# Bug#48916 Server incorrectly processing HAVING clauses with an ORDER BY clause +# +CREATE TABLE t1 (f1 INT, f2 INT); +INSERT INTO t1 VALUES (1, 0), (2, 1), (3, 2); +CREATE TABLE t2 (f1 INT, f2 INT); +SELECT t1.f1 +FROM t1 +HAVING (3, 2) IN (SELECT f1, f2 FROM t2) AND t1.f1 >= 0 +ORDER BY t1.f1; +f1 +SELECT t1.f1 +FROM t1 +HAVING (3, 2) IN (SELECT 4, 2) AND t1.f1 >= 0 +ORDER BY t1.f1; +f1 +SELECT t1.f1 +FROM t1 +HAVING 2 IN (SELECT f2 FROM t2) AND t1.f1 >= 0 +ORDER BY t1.f1; +f1 +DROP TABLE t1,t2; End of 5.1 tests diff --git a/mysql-test/r/loaddata.result b/mysql-test/r/loaddata.result index 78d169230a3..c4c8216c14a 100644 --- a/mysql-test/r/loaddata.result +++ b/mysql-test/r/loaddata.result @@ -539,4 +539,13 @@ CREATE TABLE t1(f1 INT); SELECT 0xE1BB30 INTO OUTFILE 't1.dat'; LOAD DATA INFILE 't1.dat' IGNORE INTO TABLE t1 CHARACTER SET utf8; DROP TABLE t1; +# +# Bug#11765141 - 58072: LOAD DATA INFILE: LEAKS IO CACHE MEMORY +# WHEN ERROR OCCURS +# +SELECT '1\n' INTO DUMPFILE 'MYSQLTEST_VARDIR/tmp/bug11735141.txt'; +create table t1(a point); +LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/bug11735141.txt' INTO TABLE t1; +ERROR 22003: Cannot get geometry object from data you send to the GEOMETRY field +drop table t1; End of 5.1 tests diff --git a/mysql-test/r/partition_myisam.result b/mysql-test/r/partition_myisam.result index 57228c8d9a0..97bcc11495c 100644 --- a/mysql-test/r/partition_myisam.result +++ b/mysql-test/r/partition_myisam.result @@ -239,3 +239,12 @@ a DROP TABLE t1; # Should not be any files left here # End of bug#30102 test. +# Test of post-push fix for bug#11766249/59316 +CREATE TABLE t1 (a INT, b VARCHAR(255), PRIMARY KEY (a)) +ENGINE = MyISAM +PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (0) MAX_ROWS=100, +PARTITION p1 VALUES LESS THAN (100) MAX_ROWS=100, +PARTITION pMax VALUES LESS THAN MAXVALUE); +INSERT INTO t1 VALUES (1, "Partition p1, first row"); +DROP TABLE t1; diff --git a/mysql-test/r/type_timestamp.result b/mysql-test/r/type_timestamp.result index d5769bfd59a..55f8b0753a8 100644 --- a/mysql-test/r/type_timestamp.result +++ b/mysql-test/r/type_timestamp.result @@ -523,6 +523,69 @@ a 2000-01-01 00:00:01 2000-01-01 00:00:01 DROP TABLE t1; +# +# Bug#50774: failed to get the correct resultset when timestamp values +# are appended with .0 +# +CREATE TABLE t1 ( a TIMESTAMP, KEY ( a ) ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:01' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:02' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:03' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:04' ); +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; +a +2010-02-01 09:31:02 +2010-02-01 09:31:03 +2010-02-01 09:31:04 +SELECT * FROM t1 WHERE '2010-02-01 09:31:02.0' <= a; +a +2010-02-01 09:31:02 +2010-02-01 09:31:03 +2010-02-01 09:31:04 +SELECT * FROM t1 WHERE a <= '2010-02-01 09:31:02.0'; +a +2010-02-01 09:31:01 +2010-02-01 09:31:02 +SELECT * FROM t1 WHERE '2010-02-01 09:31:02.0' >= a; +a +2010-02-01 09:31:01 +2010-02-01 09:31:02 +EXPLAIN +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; +id select_type table type possible_keys key key_len ref rows Extra +x x x range x x x x x x +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; +a +2010-02-01 09:31:02 +2010-02-01 09:31:03 +2010-02-01 09:31:04 +CREATE TABLE t2 ( a TIMESTAMP, KEY ( a DESC ) ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:01' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:02' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:03' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:04' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:05' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:06' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:07' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:08' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:09' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:10' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:11' ); +# The bug would cause the range optimizer's comparison to use an open +# interval here. This reveals itself only in the number of reads +# performed. +FLUSH STATUS; +EXPLAIN +SELECT * FROM t2 WHERE a < '2010-02-01 09:31:02.0'; +id select_type table type possible_keys key key_len ref rows Extra +x x x range x x x x x x +SELECT * FROM t2 WHERE a < '2010-02-01 09:31:02.0'; +a +2010-02-01 09:31:01 +SHOW STATUS LIKE 'Handler_read_next'; +Variable_name Value +Handler_read_next 1 +DROP TABLE t1, t2; End of 5.1 tests Bug#50888 valgrind warnings in Field_timestamp::val_str diff --git a/mysql-test/suite/binlog/r/binlog_bug23533.result b/mysql-test/suite/binlog/r/binlog_bug23533.result index 07b124793d1..d5cd93284a2 100644 --- a/mysql-test/suite/binlog/r/binlog_bug23533.result +++ b/mysql-test/suite/binlog/r/binlog_bug23533.result @@ -3,7 +3,7 @@ CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT, b TEXT, PRIMARY KEY(a)) ENGINE=I SELECT COUNT(*) FROM t1; COUNT(*) 1000 -SET @saved_max_binlog_cache_size=@@max_binlog_cache_size; +SET GLOBAL binlog_cache_size=4096; SET GLOBAL max_binlog_cache_size=4096; START TRANSACTION; CREATE TABLE t2 SELECT * FROM t1; @@ -12,5 +12,4 @@ COMMIT; SHOW TABLES LIKE 't%'; Tables_in_test (t%) t1 -SET GLOBAL max_binlog_cache_size=@saved_max_binlog_cache_size; DROP TABLE t1; diff --git a/mysql-test/suite/binlog/t/binlog_bug23533.test b/mysql-test/suite/binlog/t/binlog_bug23533.test index fb2fc808b7b..c05abe788c6 100644 --- a/mysql-test/suite/binlog/t/binlog_bug23533.test +++ b/mysql-test/suite/binlog/t/binlog_bug23533.test @@ -15,16 +15,24 @@ CREATE TABLE t1 (a INT NOT NULL AUTO_INCREMENT, b TEXT, PRIMARY KEY(a)) ENGINE=I let $i= 1000; while ($i) { + BEGIN; eval INSERT INTO t1 VALUES($i, REPEAT('x', 4096)); + COMMIT; dec $i; } --enable_query_log SELECT COUNT(*) FROM t1; # Set small value for max_binlog_cache_size -SET @saved_max_binlog_cache_size=@@max_binlog_cache_size; +let $saved_binlog_cache_size= query_get_value(SELECT @@binlog_cache_size AS Value, Value, 1); +let $saved_max_binlog_cache_size= query_get_value(SELECT @@max_binlog_cache_size AS Value, Value, 1); +SET GLOBAL binlog_cache_size=4096; SET GLOBAL max_binlog_cache_size=4096; +# New value of max_binlog_cache_size will apply to new session +disconnect default; +connect(default,localhost,root,,test); + # Copied data from t1 into t2 large than max_binlog_cache_size START TRANSACTION; --error 1197 @@ -33,5 +41,10 @@ COMMIT; SHOW TABLES LIKE 't%'; # 5.1 End of Test -SET GLOBAL max_binlog_cache_size=@saved_max_binlog_cache_size; +--disable_query_log +eval SET GLOBAL max_binlog_cache_size=$saved_max_binlog_cache_size; +eval SET GLOBAL binlog_cache_size=$saved_binlog_cache_size; +--enable_query_log DROP TABLE t1; +disconnect default; +connect(default,localhost,root,,test); diff --git a/mysql-test/suite/innodb/r/innodb_bug59410.result b/mysql-test/suite/innodb/r/innodb_bug59410.result new file mode 100644 index 00000000000..494d601ba4f --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug59410.result @@ -0,0 +1,17 @@ +create table `bug59410_1`(`a` int)engine=innodb; +insert into `bug59410_1` values (1),(2),(3); +select 1 from `bug59410_1` where `a` <> any ( +select 1 from `bug59410_1` where `a` <> 1 for update) +for update; +1 +1 +1 +drop table bug59410_1; +create table bug59410_2(`a` char(1),`b` int)engine=innodb; +insert into bug59410_2 values('0',0); +set transaction isolation level read uncommitted; +start transaction; +set @a=(select b from bug59410_2 where +(select 1 from bug59410_2 where a group by @a=b) +group by @a:=b); +drop table bug59410_2; diff --git a/mysql-test/suite/innodb/r/innodb_bug59641.result b/mysql-test/suite/innodb/r/innodb_bug59641.result new file mode 100644 index 00000000000..361172aa82b --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug59641.result @@ -0,0 +1,57 @@ +CREATE TABLE t(a INT PRIMARY KEY, b INT)ENGINE=InnoDB; +INSERT INTO t VALUES(2,2),(4,4),(8,8),(16,16),(32,32); +COMMIT; +XA START '123'; +INSERT INTO t VALUES(1,1); +XA END '123'; +XA PREPARE '123'; +XA START '456'; +INSERT INTO t VALUES(3,47),(5,67); +UPDATE t SET b=2*b WHERE a BETWEEN 5 AND 8; +XA END '456'; +XA PREPARE '456'; +XA START '789'; +UPDATE t SET b=4*a WHERE a=32; +XA END '789'; +XA PREPARE '789'; +call mtr.add_suppression("Found 3 prepared XA transactions"); +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT * FROM t; +a b +1 1 +2 2 +3 47 +4 4 +5 134 +8 16 +16 16 +32 128 +COMMIT; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT * FROM t; +a b +1 1 +2 2 +3 47 +4 4 +5 134 +8 16 +16 16 +32 128 +COMMIT; +XA RECOVER; +formatID gtrid_length bqual_length data +1 3 0 789 +1 3 0 456 +1 3 0 123 +XA ROLLBACK '123'; +XA ROLLBACK '456'; +XA COMMIT '789'; +SELECT * FROM t; +a b +2 2 +4 4 +8 8 +16 16 +32 128 +DROP TABLE t; diff --git a/mysql-test/suite/innodb/r/innodb_bug60196.result b/mysql-test/suite/innodb/r/innodb_bug60196.result index 85707f81a28..411950b49dd 100755 --- a/mysql-test/suite/innodb/r/innodb_bug60196.result +++ b/mysql-test/suite/innodb/r/innodb_bug60196.result @@ -71,3 +71,47 @@ FK1_Key FK2_Key DROP TABLE Bug_60196; DROP TABLE Bug_60196_FK1; DROP TABLE Bug_60196_FK2; +CREATE TABLE Bug_60309_FK ( +ID INT PRIMARY KEY, +ID2 INT, +KEY K2(ID2) +) ENGINE=InnoDB; +CREATE TABLE Bug_60309 ( +ID INT PRIMARY KEY, +FK_ID INT, +KEY (FK_ID), +CONSTRAINT FK FOREIGN KEY (FK_ID) REFERENCES Bug_60309_FK (ID) +) ENGINE=InnoDB; +INSERT INTO Bug_60309_FK (ID, ID2) VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO Bug_60309 VALUES (1, 1); +INSERT INTO Bug_60309 VALUES (2, 99); +ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`bug_60309`, CONSTRAINT `FK` FOREIGN KEY (`FK_ID`) REFERENCES `Bug_60309_FK` (`ID`)) +SELECT * FROM Bug_60309_FK; +ID ID2 +1 1 +2 2 +3 3 +SELECT * FROM Bug_60309; +ID FK_ID +1 1 +# Stop server +# Restart server. +# +# Try to insert more to the example table with foreign keys. +# Bug60309 causes the foreign key file not to be found after +# the resstart above. +# +SELECT * FROM Bug_60309; +ID FK_ID +1 1 +INSERT INTO Bug_60309 VALUES (2, 2); +INSERT INTO Bug_60309 VALUES (3, 3); +SELECT * FROM Bug_60309; +ID FK_ID +1 1 +2 2 +3 3 + +# Clean up. +DROP TABLE Bug_60309; +DROP TABLE Bug_60309_FK; diff --git a/mysql-test/suite/innodb/t/innodb_bug59410.test b/mysql-test/suite/innodb/t/innodb_bug59410.test new file mode 100644 index 00000000000..30bb0642679 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug59410.test @@ -0,0 +1,24 @@ +# +# Bug#59410 read uncommitted: unlock row could not find a 3 mode lock on the record +# +-- source include/have_innodb.inc + +# only interested that the following do not produce something like +# InnoDB: Error: unlock row could not find a 2 mode lock on the record +# in the error log + +create table `bug59410_1`(`a` int)engine=innodb; +insert into `bug59410_1` values (1),(2),(3); +select 1 from `bug59410_1` where `a` <> any ( +select 1 from `bug59410_1` where `a` <> 1 for update) +for update; +drop table bug59410_1; + +create table bug59410_2(`a` char(1),`b` int)engine=innodb; +insert into bug59410_2 values('0',0); +set transaction isolation level read uncommitted; +start transaction; +set @a=(select b from bug59410_2 where +(select 1 from bug59410_2 where a group by @a=b) +group by @a:=b); +drop table bug59410_2; diff --git a/mysql-test/suite/innodb/t/innodb_bug59641.test b/mysql-test/suite/innodb/t/innodb_bug59641.test new file mode 100644 index 00000000000..b933abd1d14 --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug59641.test @@ -0,0 +1,68 @@ +# Bug #59641 Prepared XA transaction causes shutdown hang after a crash + +-- source include/not_embedded.inc +-- source include/have_innodb.inc + +CREATE TABLE t(a INT PRIMARY KEY, b INT)ENGINE=InnoDB; +INSERT INTO t VALUES(2,2),(4,4),(8,8),(16,16),(32,32); +COMMIT; +XA START '123'; +INSERT INTO t VALUES(1,1); +XA END '123'; +XA PREPARE '123'; + +CONNECT (con1,localhost,root,,); +CONNECTION con1; + +XA START '456'; +INSERT INTO t VALUES(3,47),(5,67); +UPDATE t SET b=2*b WHERE a BETWEEN 5 AND 8; +XA END '456'; +XA PREPARE '456'; + +CONNECT (con2,localhost,root,,); +CONNECTION con2; + +XA START '789'; +UPDATE t SET b=4*a WHERE a=32; +XA END '789'; +XA PREPARE '789'; + +CONNECT (con3,localhost,root,,); +CONNECTION con3; +# The server would issue this warning on restart. +call mtr.add_suppression("Found 3 prepared XA transactions"); + +# Kill the server without sending a shutdown command +-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +-- shutdown_server 0 +-- source include/wait_until_disconnected.inc + +# Restart the server. +-- exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +-- enable_reconnect +-- source include/wait_until_connected_again.inc +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT * FROM t; +COMMIT; + +# Shut down the server. This would hang because of the bug. +-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +-- shutdown_server +-- source include/wait_until_disconnected.inc + +# Restart the server. +-- exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +-- enable_reconnect +-- source include/wait_until_connected_again.inc + +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +SELECT * FROM t; +COMMIT; +XA RECOVER; +XA ROLLBACK '123'; +XA ROLLBACK '456'; +XA COMMIT '789'; +SELECT * FROM t; + +DROP TABLE t; diff --git a/mysql-test/suite/innodb/t/innodb_bug60196.test b/mysql-test/suite/innodb/t/innodb_bug60196.test index 47c646a5a75..ea85653f1af 100755 --- a/mysql-test/suite/innodb/t/innodb_bug60196.test +++ b/mysql-test/suite/innodb/t/innodb_bug60196.test @@ -85,3 +85,73 @@ DROP TABLE Bug_60196; DROP TABLE Bug_60196_FK1; DROP TABLE Bug_60196_FK2; + +# Bug#60309/12356829 +# MYSQL 5.5.9 FOR MAC OSX HAS BUG WITH FOREIGN KEY CONSTRAINTS +# This testcase is different from that for Bug#60196 in that the +# referenced table contains a secondary key. When the engine is +# restarted, the referenced table is opened by the purge thread, +# which does not notice that lower_case_table_names == 2. + +# +# Create test data. +# +CREATE TABLE Bug_60309_FK ( + ID INT PRIMARY KEY, + ID2 INT, + KEY K2(ID2) +) ENGINE=InnoDB; +CREATE TABLE Bug_60309 ( + ID INT PRIMARY KEY, + FK_ID INT, + KEY (FK_ID), + CONSTRAINT FK FOREIGN KEY (FK_ID) REFERENCES Bug_60309_FK (ID) +) ENGINE=InnoDB; + +INSERT INTO Bug_60309_FK (ID, ID2) VALUES (1, 1), (2, 2), (3, 3); +INSERT INTO Bug_60309 VALUES (1, 1); +--error ER_NO_REFERENCED_ROW_2 +INSERT INTO Bug_60309 VALUES (2, 99); + +SELECT * FROM Bug_60309_FK; +SELECT * FROM Bug_60309; + +--echo # Stop server + +# Write file to make mysql-test-run.pl wait for the server to stop +-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +# Send a shutdown request to the server +-- shutdown_server 10 + +# Call script that will poll the server waiting for it to disapear +-- source include/wait_until_disconnected.inc + +--echo # Restart server. + +# Write file to make mysql-test-run.pl start up the server again +--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +# Turn on reconnect +--enable_reconnect + +# Call script that will poll the server waiting for it to be back online again +--source include/wait_until_connected_again.inc + +# Turn off reconnect again +--disable_reconnect + +--echo # +--echo # Try to insert more to the example table with foreign keys. +--echo # Bug60309 causes the foreign key file not to be found after +--echo # the resstart above. +--echo # +SELECT * FROM Bug_60309; +INSERT INTO Bug_60309 VALUES (2, 2); +INSERT INTO Bug_60309 VALUES (3, 3); +SELECT * FROM Bug_60309; + +--echo +--echo # Clean up. +DROP TABLE Bug_60309; +DROP TABLE Bug_60309_FK; diff --git a/mysql-test/suite/parts/inc/partition_check_drop.inc b/mysql-test/suite/parts/inc/partition_check_drop.inc index 0941cd9703b..7f8b5b7929b 100644 --- a/mysql-test/suite/parts/inc/partition_check_drop.inc +++ b/mysql-test/suite/parts/inc/partition_check_drop.inc @@ -37,7 +37,7 @@ if ($do_file_tests) eval SET @aux = load_file('$ls_file'); # clean up - remove_file $ls_file; + --remove_file $ls_file } if (!$do_file_tests) { diff --git a/mysql-test/suite/parts/inc/partition_fail.inc b/mysql-test/suite/parts/inc/partition_fail.inc index f433712e284..dd79cdf1dc8 100644 --- a/mysql-test/suite/parts/inc/partition_fail.inc +++ b/mysql-test/suite/parts/inc/partition_fail.inc @@ -23,6 +23,7 @@ DROP TABLE t1; --eval $create_statement --eval $insert_statement --echo # State before failure +--replace_result #p# #P# #sp# #SP# --list_files $DATADIR/test SHOW CREATE TABLE t1; --sorted_result @@ -32,6 +33,7 @@ LOCK TABLE t1 WRITE; --eval $fail_statement --enable_abort_on_error --echo # State after failure +--replace_result #p# #P# #sp# #SP# --list_files $DATADIR/test SHOW CREATE TABLE t1; --sorted_result diff --git a/mysql-test/suite/parts/inc/partition_layout_check1.inc b/mysql-test/suite/parts/inc/partition_layout_check1.inc index de35d3f2cad..76df4c1a250 100644 --- a/mysql-test/suite/parts/inc/partition_layout_check1.inc +++ b/mysql-test/suite/parts/inc/partition_layout_check1.inc @@ -45,6 +45,9 @@ if ($do_file_tests) --list_files_append_file $ls_file $MYSQLTEST_VARDIR/mysql-test-idx-dir t1* } eval SET @aux = load_file('$ls_file'); + + # clean up + --remove_file $ls_file } if (!$do_file_tests) { diff --git a/mysql-test/suite/parts/inc/partition_layout_check2.inc b/mysql-test/suite/parts/inc/partition_layout_check2.inc index 028b1242e90..23e4a9e1e95 100644 --- a/mysql-test/suite/parts/inc/partition_layout_check2.inc +++ b/mysql-test/suite/parts/inc/partition_layout_check2.inc @@ -43,6 +43,9 @@ if ($do_file_tests) --list_files_append_file $ls_file $MYSQLTEST_VARDIR/mysql-test-idx-dir t1* } eval SET @aux = load_file('$ls_file'); + + # clean up + --remove_file $ls_file } if (!$do_file_tests) { diff --git a/mysql-test/t/func_math.test b/mysql-test/t/func_math.test index 687d96f8804..0e704fecf57 100644 --- a/mysql-test/t/func_math.test +++ b/mysql-test/t/func_math.test @@ -354,6 +354,22 @@ DROP TABLE t1; CREATE TABLE t1 SELECT FLOOR(LINESTRINGFROMWKB(1) DIV NULL); DROP TABLE t1; +--echo # +--echo # Bug#11765923 58937: MANY VALGRIND ERRORS AFTER GROUPING BY RESULT OF DECIMAL COLUMN FUNCTION +--echo # + +CREATE TABLE t1(f1 DECIMAL(22,1)); +INSERT INTO t1 VALUES (0),(1); +SELECT ROUND(f1, f1) FROM t1; +SELECT ROUND(f1, f1) FROM t1 GROUP BY 1; +DROP TABLE t1; + +--echo # +--echo # Bug#11764671 57533: UNINITIALISED VALUES IN COPY_AND_CONVERT (SQL_STRING.CC) WITH CERTAIN CHA +--echo # + +SELECT ROUND(LEAST(15, -4939092, 0.2704), STDDEV('a')); + --echo End of 5.1 tests --echo # diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test index 96a73a454c3..add741e12a7 100644 --- a/mysql-test/t/func_time.test +++ b/mysql-test/t/func_time.test @@ -894,6 +894,14 @@ SELECT CAST((MONTH(FROM_UNIXTIME(@@GLOBAL.SQL_MODE))) AS BINARY(1025)); SELECT ADDDATE(MONTH(FROM_UNIXTIME(NULL)),INTERVAL 1 HOUR); +--echo # +--echo # Bug#11889186 60503: CRASH IN MAKE_DATE_TIME WITH DATE_FORMAT / STR_TO_DATE COMBINATION +--echo # + +SELECT DATE_FORMAT('0000-00-11', '%W'); +SELECT DATE_FORMAT('0000-00-11', '%a'); +SELECT DATE_FORMAT('0000-00-11', '%w'); + --echo End of 5.1 tests --echo # diff --git a/mysql-test/t/having.test b/mysql-test/t/having.test index c808e747523..2ed8b40b858 100644 --- a/mysql-test/t/having.test +++ b/mysql-test/t/having.test @@ -564,4 +564,30 @@ HAVING field1 < 7; DROP TABLE t1,t2; +--echo # +--echo # Bug#48916 Server incorrectly processing HAVING clauses with an ORDER BY clause +--echo # + +CREATE TABLE t1 (f1 INT, f2 INT); +INSERT INTO t1 VALUES (1, 0), (2, 1), (3, 2); +CREATE TABLE t2 (f1 INT, f2 INT); + +SELECT t1.f1 +FROM t1 +HAVING (3, 2) IN (SELECT f1, f2 FROM t2) AND t1.f1 >= 0 +ORDER BY t1.f1; + +SELECT t1.f1 +FROM t1 +HAVING (3, 2) IN (SELECT 4, 2) AND t1.f1 >= 0 +ORDER BY t1.f1; + +SELECT t1.f1 +FROM t1 +HAVING 2 IN (SELECT f2 FROM t2) AND t1.f1 >= 0 +ORDER BY t1.f1; + +DROP TABLE t1,t2; + + --echo End of 5.1 tests diff --git a/mysql-test/t/loaddata.test b/mysql-test/t/loaddata.test index 8e5cba01d28..0bb3cf64444 100644 --- a/mysql-test/t/loaddata.test +++ b/mysql-test/t/loaddata.test @@ -615,4 +615,19 @@ DROP TABLE t1; let $MYSQLD_DATADIR= `select @@datadir`; remove_file $MYSQLD_DATADIR/test/t1.dat; +--echo # +--echo # Bug#11765141 - 58072: LOAD DATA INFILE: LEAKS IO CACHE MEMORY +--echo # WHEN ERROR OCCURS +--echo # + +--let $file=$MYSQLTEST_VARDIR/tmp/bug11735141.txt +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--eval SELECT '1\n' INTO DUMPFILE '$file' + +create table t1(a point); +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +--error ER_CANT_CREATE_GEOMETRY_OBJECT +--eval LOAD DATA INFILE '$file' INTO TABLE t1 +drop table t1; + --echo End of 5.1 tests diff --git a/mysql-test/t/partition_myisam.test b/mysql-test/t/partition_myisam.test index 37d4a071fb9..c3766430275 100644 --- a/mysql-test/t/partition_myisam.test +++ b/mysql-test/t/partition_myisam.test @@ -1,5 +1,4 @@ --- source include/have_partition.inc - +--source include/have_partition.inc --disable_warnings DROP TABLE IF EXISTS t1, t2; --enable_warnings @@ -171,3 +170,13 @@ DROP TABLE t1; --list_files $MYSQLD_DATADIR/test t1* --list_files $MYSQLD_DATADIR/test t2* --echo # End of bug#30102 test. + +--echo # Test of post-push fix for bug#11766249/59316 +CREATE TABLE t1 (a INT, b VARCHAR(255), PRIMARY KEY (a)) +ENGINE = MyISAM +PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (0) MAX_ROWS=100, + PARTITION p1 VALUES LESS THAN (100) MAX_ROWS=100, + PARTITION pMax VALUES LESS THAN MAXVALUE); +INSERT INTO t1 VALUES (1, "Partition p1, first row"); +DROP TABLE t1; diff --git a/mysql-test/t/type_timestamp.test b/mysql-test/t/type_timestamp.test index 76423b11b99..cd4ba18455b 100644 --- a/mysql-test/t/type_timestamp.test +++ b/mysql-test/t/type_timestamp.test @@ -362,6 +362,53 @@ SELECT a FROM t1 WHERE a >= '20000101000000'; DROP TABLE t1; +--echo # +--echo # Bug#50774: failed to get the correct resultset when timestamp values +--echo # are appended with .0 +--echo # +CREATE TABLE t1 ( a TIMESTAMP, KEY ( a ) ); + +INSERT INTO t1 VALUES( '2010-02-01 09:31:01' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:02' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:03' ); +INSERT INTO t1 VALUES( '2010-02-01 09:31:04' ); + +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; +SELECT * FROM t1 WHERE '2010-02-01 09:31:02.0' <= a; +SELECT * FROM t1 WHERE a <= '2010-02-01 09:31:02.0'; +SELECT * FROM t1 WHERE '2010-02-01 09:31:02.0' >= a; + +--replace_column 1 x 2 x 3 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; +SELECT * FROM t1 WHERE a >= '2010-02-01 09:31:02.0'; + +CREATE TABLE t2 ( a TIMESTAMP, KEY ( a DESC ) ); + +INSERT INTO t2 VALUES( '2010-02-01 09:31:01' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:02' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:03' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:04' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:05' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:06' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:07' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:08' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:09' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:10' ); +INSERT INTO t2 VALUES( '2010-02-01 09:31:11' ); + +--echo # The bug would cause the range optimizer's comparison to use an open +--echo # interval here. This reveals itself only in the number of reads +--echo # performed. +FLUSH STATUS; +--replace_column 1 x 2 x 3 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t2 WHERE a < '2010-02-01 09:31:02.0'; +SELECT * FROM t2 WHERE a < '2010-02-01 09:31:02.0'; +SHOW STATUS LIKE 'Handler_read_next'; + +DROP TABLE t1, t2; + --echo End of 5.1 tests --echo diff --git a/sql-common/client.c b/sql-common/client.c index 90d07f3e409..abaea310aae 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -2314,11 +2314,18 @@ static auth_plugin_t clear_password_client_plugin= clear_password_auth_client }; +#ifdef AUTHENTICATION_WIN +extern auth_plugin_t win_auth_client_plugin; +#endif + struct st_mysql_client_plugin *mysql_client_builtins[]= { (struct st_mysql_client_plugin *)&native_password_client_plugin, (struct st_mysql_client_plugin *)&old_password_client_plugin, (struct st_mysql_client_plugin *)&clear_password_client_plugin, +#ifdef AUTHENTICATION_WIN + (struct st_mysql_client_plugin *)&win_auth_client_plugin, +#endif 0 }; diff --git a/sql-common/client_plugin.c b/sql-common/client_plugin.c index 47409e34d9a..382f14c6b3d 100644 --- a/sql-common/client_plugin.c +++ b/sql-common/client_plugin.c @@ -460,7 +460,7 @@ mysql_client_find_plugin(MYSQL *mysql, const char *name, int type) /* see <mysql/client_plugin.h> for a full description */ -int STDCALL mysql_plugin_options(struct st_mysql_client_plugin *plugin, +int mysql_plugin_options(struct st_mysql_client_plugin *plugin, const char *option, const void *value) { diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 39eee57a1c0..de88e8756b5 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -772,7 +772,7 @@ long calc_daynr(uint year,uint month,uint day) int y= year; /* may be < 0 temporarily */ DBUG_ENTER("calc_daynr"); - if (y == 0 && month == 0 && day == 0) + if (y == 0 && month == 0) DBUG_RETURN(0); /* Skip errors */ /* Cast to int to be able to handle month == 0 */ delsum= (long) (365 * y + 31 *((int) month - 1) + (int) day); @@ -783,6 +783,7 @@ long calc_daynr(uint year,uint month,uint day) temp=(int) ((y/100+1)*3)/4; DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld", y+(month <= 2),month,day,delsum+y/4-temp)); + DBUG_ASSERT(delsum+(int) y/4-temp > 0); DBUG_RETURN(delsum+(int) y/4-temp); } /* calc_daynr */ diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 59b904aceac..754d5dc99cf 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -162,8 +162,7 @@ const uint ha_partition::NO_CURRENT_PART_ID= 0xFFFFFFFF; */ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share) - :handler(hton, share), m_part_info(NULL), m_create_handler(FALSE), - m_is_sub_partitioned(0) + :handler(hton, share) { DBUG_ENTER("ha_partition::ha_partition(table)"); init_handler_variables(); @@ -183,15 +182,44 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share) */ ha_partition::ha_partition(handlerton *hton, partition_info *part_info) - :handler(hton, NULL), m_part_info(part_info), m_create_handler(TRUE), - m_is_sub_partitioned(m_part_info->is_sub_partitioned()) + :handler(hton, NULL) { DBUG_ENTER("ha_partition::ha_partition(part_info)"); + DBUG_ASSERT(part_info); init_handler_variables(); - DBUG_ASSERT(m_part_info); + m_part_info= part_info; + m_create_handler= TRUE; + m_is_sub_partitioned= m_part_info->is_sub_partitioned(); DBUG_VOID_RETURN; } +/** + ha_partition constructor method used by ha_partition::clone() + + @param hton Handlerton (partition_hton) + @param share Table share object + @param part_info_arg partition_info to use + @param clone_arg ha_partition to clone + @param clme_mem_root_arg MEM_ROOT to use + + @return New partition handler +*/ + +ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share, + partition_info *part_info_arg, + ha_partition *clone_arg, + MEM_ROOT *clone_mem_root_arg) + :handler(hton, share) +{ + DBUG_ENTER("ha_partition::ha_partition(clone)"); + init_handler_variables(); + m_part_info= part_info_arg; + m_create_handler= TRUE; + m_is_sub_partitioned= m_part_info->is_sub_partitioned(); + m_is_clone_of= clone_arg; + m_clone_mem_root= clone_mem_root_arg; + DBUG_VOID_RETURN; +} /* Initialize handler object @@ -243,7 +271,6 @@ void ha_partition::init_handler_variables() m_rec0= 0; m_curr_key_info[0]= NULL; m_curr_key_info[1]= NULL; - is_clone= FALSE, m_part_func_monotonicity_info= NON_MONOTONIC; auto_increment_lock= FALSE; auto_increment_safe_stmt_log_lock= FALSE; @@ -251,6 +278,11 @@ void ha_partition::init_handler_variables() this allows blackhole to work properly */ m_num_locks= 0; + m_part_info= NULL; + m_create_handler= FALSE; + m_is_sub_partitioned= 0; + m_is_clone_of= NULL; + m_clone_mem_root= NULL; #ifdef DONT_HAVE_TO_BE_INITALIZED m_start_key.flag= 0; @@ -358,7 +390,8 @@ bool ha_partition::initialize_partition(MEM_ROOT *mem_root) */ DBUG_RETURN(0); } - else if (get_from_handler_file(table_share->normalized_path.str, mem_root)) + else if (get_from_handler_file(table_share->normalized_path.str, + mem_root, false)) { my_error(ER_FAILED_READ_FROM_PAR_FILE, MYF(0)); DBUG_RETURN(1); @@ -1890,7 +1923,7 @@ uint ha_partition::del_ren_cre_table(const char *from, DBUG_RETURN(TRUE); } - if (get_from_handler_file(from, ha_thd()->mem_root)) + if (get_from_handler_file(from, ha_thd()->mem_root, false)) DBUG_RETURN(TRUE); DBUG_ASSERT(m_file_buffer); DBUG_PRINT("enter", ("from: (%s) to: (%s)", from, to ? to : "(nil)")); @@ -2105,18 +2138,16 @@ static uint name_add(char *dest, const char *first_name, const char *sec_name) } -/* +/** Create the special .par file - SYNOPSIS - create_handler_file() - name Full path of table name + @param name Full path of table name - RETURN VALUE - >0 Error code - 0 Success + @return Operation status + @retval FALSE Error code + @retval TRUE Success - DESCRIPTION + @note Method used to create handler file with names of partitions, their engine types and the number of partitions. */ @@ -2180,19 +2211,22 @@ bool ha_partition::create_handler_file(const char *name) Array of engine types n * 4 bytes where n = (m_tot_parts + 3)/4 Length of name part in bytes 4 bytes + (Names in filename format) Name part m * 4 bytes where m = ((length_name_part + 3)/4)*4 All padding bytes are zeroed */ - tot_partition_words= (tot_parts + 3) / 4; - tot_name_words= (tot_name_len + 3) / 4; + tot_partition_words= (tot_parts + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE; + tot_name_words= (tot_name_len + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE; + /* 4 static words (tot words, checksum, tot partitions, name length) */ tot_len_words= 4 + tot_partition_words + tot_name_words; - tot_len_byte= 4 * tot_len_words; + tot_len_byte= PAR_WORD_SIZE * tot_len_words; if (!(file_buffer= (uchar *) my_malloc(tot_len_byte, MYF(MY_ZEROFILL)))) DBUG_RETURN(TRUE); - engine_array= (file_buffer + 12); - name_buffer_ptr= (char*) (file_buffer + ((4 + tot_partition_words) * 4)); + engine_array= (file_buffer + PAR_ENGINES_OFFSET); + name_buffer_ptr= (char*) (engine_array + tot_partition_words * PAR_WORD_SIZE + + PAR_WORD_SIZE); part_it.rewind(); for (i= 0; i < num_parts; i++) { @@ -2230,13 +2264,15 @@ bool ha_partition::create_handler_file(const char *name) } chksum= 0; int4store(file_buffer, tot_len_words); - int4store(file_buffer + 8, tot_parts); - int4store(file_buffer + 12 + (tot_partition_words * 4), tot_name_len); + int4store(file_buffer + PAR_NUM_PARTS_OFFSET, tot_parts); + int4store(file_buffer + PAR_ENGINES_OFFSET + + (tot_partition_words * PAR_WORD_SIZE), + tot_name_len); for (i= 0; i < tot_len_words; i++) - chksum^= uint4korr(file_buffer + 4 * i); - int4store(file_buffer + 4, chksum); + chksum^= uint4korr(file_buffer + PAR_WORD_SIZE * i); + int4store(file_buffer + PAR_CHECKSUM_OFFSET, chksum); /* - Remove .frm extension and replace with .par + Add .par extension to the file name. Create and write and close file to be used at open, delete_table and rename_table */ @@ -2255,14 +2291,9 @@ bool ha_partition::create_handler_file(const char *name) DBUG_RETURN(result); } -/* - Clear handler variables and free some memory - SYNOPSIS - clear_handler_file() - - RETURN VALUE - NONE +/** + Clear handler variables and free some memory */ void ha_partition::clear_handler_file() @@ -2275,16 +2306,15 @@ void ha_partition::clear_handler_file() m_engine_array= NULL; } -/* + +/** Create underlying handler objects - SYNOPSIS - create_handlers() - mem_root Allocate memory through this + @param mem_root Allocate memory through this - RETURN VALUE - TRUE Error - FALSE Success + @return Operation status + @retval TRUE Error + @retval FALSE Success */ bool ha_partition::create_handlers(MEM_ROOT *mem_root) @@ -2322,6 +2352,7 @@ bool ha_partition::create_handlers(MEM_ROOT *mem_root) DBUG_RETURN(FALSE); } + /* Create underlying handler objects from partition info @@ -2393,101 +2424,165 @@ error_end: } -/* - Get info about partition engines and their names from the .par file +/** + Read the .par file to get the partitions engines and names - SYNOPSIS - get_from_handler_file() - name Full path of table name - mem_root Allocate memory through this + @param name Name of table file (without extention) - RETURN VALUE - TRUE Error - FALSE Success + @return Operation status + @retval true Failure + @retval false Success - DESCRIPTION - Open handler file to get partition names, engine types and number of - partitions. + @note On success, m_file_buffer is allocated and must be + freed by the caller. m_name_buffer_ptr and m_tot_parts is also set. */ -bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root) +bool ha_partition::read_par_file(const char *name) { - char buff[FN_REFLEN], *address_tot_name_len; + char buff[FN_REFLEN], *tot_name_len_offset; File file; - char *file_buffer, *name_buffer_ptr; - handlerton **engine_array; + char *file_buffer; uint i, len_bytes, len_words, tot_partition_words, tot_name_words, chksum; - DBUG_ENTER("ha_partition::get_from_handler_file"); + DBUG_ENTER("ha_partition::read_par_file"); DBUG_PRINT("enter", ("table name: '%s'", name)); if (m_file_buffer) - DBUG_RETURN(FALSE); + DBUG_RETURN(false); fn_format(buff, name, "", ha_par_ext, MY_APPEND_EXT); /* Following could be done with mysql_file_stat to read in whole file */ if ((file= mysql_file_open(key_file_partition, buff, O_RDONLY | O_SHARE, MYF(0))) < 0) DBUG_RETURN(TRUE); - if (mysql_file_read(file, (uchar *) &buff[0], 8, MYF(MY_NABP))) + if (mysql_file_read(file, (uchar *) &buff[0], PAR_WORD_SIZE, MYF(MY_NABP))) goto err1; len_words= uint4korr(buff); - len_bytes= 4 * len_words; + len_bytes= PAR_WORD_SIZE * len_words; + if (mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) + goto err1; if (!(file_buffer= (char*) my_malloc(len_bytes, MYF(0)))) goto err1; - mysql_file_seek(file, 0, MY_SEEK_SET, MYF(0)); if (mysql_file_read(file, (uchar *) file_buffer, len_bytes, MYF(MY_NABP))) goto err2; chksum= 0; for (i= 0; i < len_words; i++) - chksum ^= uint4korr((file_buffer) + 4 * i); + chksum ^= uint4korr((file_buffer) + PAR_WORD_SIZE * i); if (chksum) goto err2; - m_tot_parts= uint4korr((file_buffer) + 8); + m_tot_parts= uint4korr((file_buffer) + PAR_NUM_PARTS_OFFSET); DBUG_PRINT("info", ("No of parts = %u", m_tot_parts)); - tot_partition_words= (m_tot_parts + 3) / 4; + tot_partition_words= (m_tot_parts + PAR_WORD_SIZE - 1) / PAR_WORD_SIZE; + + tot_name_len_offset= file_buffer + PAR_ENGINES_OFFSET + + PAR_WORD_SIZE * tot_partition_words; + tot_name_words= (uint4korr(tot_name_len_offset) + PAR_WORD_SIZE - 1) / + PAR_WORD_SIZE; + /* + Verify the total length = tot size word, checksum word, num parts word + + engines array + name length word + name array. + */ + if (len_words != (tot_partition_words + tot_name_words + 4)) + goto err2; + (void) mysql_file_close(file, MYF(0)); + m_file_buffer= file_buffer; // Will be freed in clear_handler_file() + m_name_buffer_ptr= tot_name_len_offset + PAR_WORD_SIZE; + + DBUG_RETURN(false); + +err2: + my_free(file_buffer); +err1: + (void) mysql_file_close(file, MYF(0)); + DBUG_RETURN(true); +} + + +/** + Setup m_engine_array + + @param mem_root MEM_ROOT to use for allocating new handlers + + @return Operation status + @retval false Success + @retval true Failure +*/ + +bool ha_partition::setup_engine_array(MEM_ROOT *mem_root) +{ + uint i; + uchar *buff; + handlerton **engine_array; + + DBUG_ASSERT(!m_file); + DBUG_ENTER("ha_partition::setup_engine_array"); engine_array= (handlerton **) my_alloca(m_tot_parts * sizeof(handlerton*)); + if (!engine_array) + DBUG_RETURN(true); + + buff= (uchar *) (m_file_buffer + PAR_ENGINES_OFFSET); for (i= 0; i < m_tot_parts; i++) { engine_array[i]= ha_resolve_by_legacy_type(ha_thd(), (enum legacy_db_type) - *(uchar *) ((file_buffer) + - 12 + i)); + *(buff + i)); if (!engine_array[i]) - goto err3; + goto err; } - address_tot_name_len= file_buffer + 12 + 4 * tot_partition_words; - tot_name_words= (uint4korr(address_tot_name_len) + 3) / 4; - if (len_words != (tot_partition_words + tot_name_words + 4)) - goto err3; - name_buffer_ptr= file_buffer + 16 + 4 * tot_partition_words; - (void) mysql_file_close(file, MYF(0)); - m_file_buffer= file_buffer; // Will be freed in clear_handler_file() - m_name_buffer_ptr= name_buffer_ptr; - if (!(m_engine_array= (plugin_ref*) my_malloc(m_tot_parts * sizeof(plugin_ref), MYF(MY_WME)))) - goto err3; + goto err; for (i= 0; i < m_tot_parts; i++) m_engine_array[i]= ha_lock_engine(NULL, engine_array[i]); my_afree((gptr) engine_array); - if (!m_file && create_handlers(mem_root)) + if (create_handlers(mem_root)) { clear_handler_file(); - DBUG_RETURN(TRUE); + DBUG_RETURN(true); } - DBUG_RETURN(FALSE); -err3: + DBUG_RETURN(false); + +err: my_afree((gptr) engine_array); -err2: - my_free(file_buffer); -err1: - (void) mysql_file_close(file, MYF(0)); - DBUG_RETURN(TRUE); + DBUG_RETURN(true); +} + + +/** + Get info about partition engines and their names from the .par file + + @param name Full path of table name + @param mem_root Allocate memory through this + @param is_clone If it is a clone, don't create new handlers + + @return Operation status + @retval true Error + @retval false Success + + @note Open handler file to get partition names, engine types and number of + partitions. +*/ + +bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root, + bool is_clone) +{ + DBUG_ENTER("ha_partition::get_from_handler_file"); + DBUG_PRINT("enter", ("table name: '%s'", name)); + + if (m_file_buffer) + DBUG_RETURN(false); + + if (read_par_file(name)) + DBUG_RETURN(true); + + if (!is_clone && setup_engine_array(mem_root)) + DBUG_RETURN(true); + + DBUG_RETURN(false); } @@ -2533,13 +2628,13 @@ void ha_data_partition_destroy(HA_DATA_PARTITION* ha_part_data) int ha_partition::open(const char *name, int mode, uint test_if_locked) { - char *name_buffer_ptr= m_name_buffer_ptr; - int error; + char *name_buffer_ptr; + int error= HA_ERR_INITIALIZATION; uint alloc_len; handler **file; char name_buff[FN_REFLEN]; bool is_not_tmp_table= (table_share->tmp_table == NO_TMP_TABLE); - ulonglong check_table_flags= 0; + ulonglong check_table_flags; DBUG_ENTER("ha_partition::open"); DBUG_ASSERT(table->s == table_share); @@ -2547,8 +2642,9 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) m_mode= mode; m_open_test_lock= test_if_locked; m_part_field_array= m_part_info->full_part_field_array; - if (get_from_handler_file(name, &table->mem_root)) - DBUG_RETURN(1); + if (get_from_handler_file(name, &table->mem_root, test(m_is_clone_of))) + DBUG_RETURN(error); + name_buffer_ptr= m_name_buffer_ptr; m_start_key.length= 0; m_rec0= table->record[0]; m_rec_length= table_share->reclength; @@ -2558,7 +2654,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) { if (!(m_ordered_rec_buffer= (uchar*)my_malloc(alloc_len, MYF(MY_WME)))) { - DBUG_RETURN(1); + DBUG_RETURN(error); } { /* @@ -2581,48 +2677,84 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */ if (bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE)) - DBUG_RETURN(1); + DBUG_RETURN(error); bitmap_clear_all(&m_bulk_insert_started); /* Initialize the bitmap we use to determine what partitions are used */ - if (!is_clone) + if (!m_is_clone_of) { + DBUG_ASSERT(!m_clone_mem_root); if (bitmap_init(&(m_part_info->used_partitions), NULL, m_tot_parts, TRUE)) { bitmap_free(&m_bulk_insert_started); - DBUG_RETURN(1); + DBUG_RETURN(error); } bitmap_set_all(&(m_part_info->used_partitions)); } + if (m_is_clone_of) + { + uint i; + DBUG_ASSERT(m_clone_mem_root); + /* Allocate an array of handler pointers for the partitions handlers. */ + alloc_len= (m_tot_parts + 1) * sizeof(handler*); + if (!(m_file= (handler **) alloc_root(m_clone_mem_root, alloc_len))) + goto err_alloc; + memset(m_file, 0, alloc_len); + /* + Populate them by cloning the original partitions. This also opens them. + Note that file->ref is allocated too. + */ + file= m_is_clone_of->m_file; + for (i= 0; i < m_tot_parts; i++) + { + create_partition_name(name_buff, name, name_buffer_ptr, NORMAL_PART_NAME, + FALSE); + if (!(m_file[i]= file[i]->clone(name_buff, m_clone_mem_root))) + { + error= HA_ERR_INITIALIZATION; + file= &m_file[i]; + goto err_handler; + } + name_buffer_ptr+= strlen(name_buffer_ptr) + 1; + } + } + else + { + file= m_file; + do + { + create_partition_name(name_buff, name, name_buffer_ptr, NORMAL_PART_NAME, + FALSE); + if ((error= (*file)->ha_open(table, name_buff, mode, test_if_locked))) + goto err_handler; + m_num_locks+= (*file)->lock_count(); + name_buffer_ptr+= strlen(name_buffer_ptr) + 1; + } while (*(++file)); + } + file= m_file; - do + ref_length= (*file)->ref_length; + check_table_flags= (((*file)->ha_table_flags() & + ~(PARTITION_DISABLED_TABLE_FLAGS)) | + (PARTITION_ENABLED_TABLE_FLAGS)); + while (*(++file)) { - create_partition_name(name_buff, name, name_buffer_ptr, NORMAL_PART_NAME, - FALSE); - if ((error= (*file)->ha_open(table, (const char*) name_buff, mode, - test_if_locked))) - goto err_handler; - m_num_locks+= (*file)->lock_count(); - name_buffer_ptr+= strlen(name_buffer_ptr) + 1; + /* MyISAM can have smaller ref_length for partitions with MAX_ROWS set */ set_if_bigger(ref_length, ((*file)->ref_length)); /* Verify that all partitions have the same set of table flags. Mask all flags that partitioning enables/disables. */ - if (!check_table_flags) - { - check_table_flags= (((*file)->ha_table_flags() & - ~(PARTITION_DISABLED_TABLE_FLAGS)) | - (PARTITION_ENABLED_TABLE_FLAGS)); - } - else if (check_table_flags != (((*file)->ha_table_flags() & - ~(PARTITION_DISABLED_TABLE_FLAGS)) | - (PARTITION_ENABLED_TABLE_FLAGS))) + if (check_table_flags != (((*file)->ha_table_flags() & + ~(PARTITION_DISABLED_TABLE_FLAGS)) | + (PARTITION_ENABLED_TABLE_FLAGS))) { error= HA_ERR_INITIALIZATION; + /* set file to last handler, so all of them is closed */ + file = &m_file[m_tot_parts - 1]; goto err_handler; } - } while (*(++file)); + } key_used_on_scan= m_file[0]->key_used_on_scan; implicit_emptied= m_file[0]->implicit_emptied; /* @@ -2631,6 +2763,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) */ ref_length+= PARTITION_BYTES_IN_POS; m_ref_length= ref_length; + /* Release buffer read from .par file. It will not be reused again after being opened once. @@ -2690,25 +2823,54 @@ err_handler: DEBUG_SYNC(ha_thd(), "partition_open_error"); while (file-- != m_file) (*file)->close(); +err_alloc: bitmap_free(&m_bulk_insert_started); - if (!is_clone) + if (!m_is_clone_of) bitmap_free(&(m_part_info->used_partitions)); DBUG_RETURN(error); } -handler *ha_partition::clone(MEM_ROOT *mem_root) + +/** + Clone the open and locked partitioning handler. + + @param mem_root MEM_ROOT to use. + + @return Pointer to the successfully created clone or NULL + + @details + This function creates a new ha_partition handler as a clone/copy. The + original (this) must already be opened and locked. The clone will use + the originals m_part_info. + It also allocates memory for ref + ref_dup. + In ha_partition::open() it will clone its original handlers partitions + which will allocate then on the correct MEM_ROOT and also open them. +*/ + +handler *ha_partition::clone(const char *name, MEM_ROOT *mem_root) { - handler *new_handler= get_new_handler(table->s, mem_root, - table->s->db_type()); - ((ha_partition*)new_handler)->m_part_info= m_part_info; - ((ha_partition*)new_handler)->is_clone= TRUE; - if (new_handler && !new_handler->ha_open(table, - table->s->normalized_path.str, - table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED)) - return new_handler; - return NULL; + ha_partition *new_handler; + + DBUG_ENTER("ha_partition::clone"); + new_handler= new (mem_root) ha_partition(ht, table_share, m_part_info, + this, mem_root); + /* + Allocate new_handler->ref here because otherwise ha_open will allocate it + on this->table->mem_root and we will not be able to reclaim that memory + when the clone handler object is destroyed. + */ + if (new_handler && + !(new_handler->ref= (uchar*) alloc_root(mem_root, + ALIGN_SIZE(m_ref_length)*2))) + new_handler= NULL; + + if (new_handler && + new_handler->ha_open(table, name, + table->db_stat, HA_OPEN_IGNORE_IF_LOCKED)) + new_handler= NULL; + + DBUG_RETURN((handler*) new_handler); } @@ -2739,7 +2901,7 @@ int ha_partition::close(void) DBUG_ASSERT(table->s == table_share); delete_queue(&m_queue); bitmap_free(&m_bulk_insert_started); - if (!is_clone) + if (!m_is_clone_of) bitmap_free(&(m_part_info->used_partitions)); file= m_file; @@ -3927,19 +4089,16 @@ end_dont_reset_start_part: void ha_partition::position(const uchar *record) { handler *file= m_file[m_last_part]; + uint pad_length; DBUG_ENTER("ha_partition::position"); file->position(record); int2store(ref, m_last_part); - memcpy((ref + PARTITION_BYTES_IN_POS), file->ref, - (ref_length - PARTITION_BYTES_IN_POS)); - -#ifdef SUPPORTING_PARTITION_OVER_DIFFERENT_ENGINES -#ifdef HAVE_purify - bzero(ref + PARTITION_BYTES_IN_POS + ref_length, - max_ref_length-ref_length); -#endif /* HAVE_purify */ -#endif + memcpy((ref + PARTITION_BYTES_IN_POS), file->ref, file->ref_length); + pad_length= m_ref_length - PARTITION_BYTES_IN_POS - file->ref_length; + if (pad_length) + memset((ref + PARTITION_BYTES_IN_POS + file->ref_length), 0, pad_length); + DBUG_VOID_RETURN; } diff --git a/sql/ha_partition.h b/sql/ha_partition.h index 57456ddf8ae..b0b11da823f 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -37,6 +37,16 @@ enum partition_keywords HA_DUPLICATE_POS | \ HA_CAN_SQL_HANDLER | \ HA_CAN_INSERT_DELAYED) + +/* First 4 bytes in the .par file is the number of 32-bit words in the file */ +#define PAR_WORD_SIZE 4 +/* offset to the .par file checksum */ +#define PAR_CHECKSUM_OFFSET 4 +/* offset to the total number of partitions */ +#define PAR_NUM_PARTS_OFFSET 8 +/* offset to the engines array */ +#define PAR_ENGINES_OFFSET 12 + class ha_partition :public handler { private: @@ -53,7 +63,7 @@ private: /* Data for the partition handler */ int m_mode; // Open mode uint m_open_test_lock; // Open test_if_locked - char *m_file_buffer; // Buffer with names + char *m_file_buffer; // Content of the .par file char *m_name_buffer_ptr; // Pointer to first partition name plugin_ref *m_engine_array; // Array of types of the handlers handler **m_file; // Array of references to handler inst. @@ -115,6 +125,13 @@ private: bool m_is_sub_partitioned; // Is subpartitioned bool m_ordered_scan_ongoing; + /* + If set, this object was created with ha_partition::clone and doesn't + "own" the m_part_info structure. + */ + ha_partition *m_is_clone_of; + MEM_ROOT *m_clone_mem_root; + /* We keep track if all underlying handlers are MyISAM since MyISAM has a great number of extra flags not needed by other handlers. @@ -148,11 +165,6 @@ private: */ THR_LOCK_DATA lock; /* MySQL lock */ - /* - TRUE <=> this object was created with ha_partition::clone and doesn't - "own" the m_part_info structure. - */ - bool is_clone; bool auto_increment_lock; /**< lock reading/updating auto_inc */ /** Flag to keep the auto_increment lock through out the statement. @@ -165,7 +177,7 @@ private: /** used for prediction of start_bulk_insert rows */ enum_monotonicity_info m_part_func_monotonicity_info; public: - handler *clone(MEM_ROOT *mem_root); + handler *clone(const char *name, MEM_ROOT *mem_root); virtual void set_part_info(partition_info *part_info) { m_part_info= part_info; @@ -184,6 +196,10 @@ public: */ ha_partition(handlerton *hton, TABLE_SHARE * table); ha_partition(handlerton *hton, partition_info * part_info); + ha_partition(handlerton *hton, TABLE_SHARE *share,
+ partition_info *part_info_arg,
+ ha_partition *clone_arg,
+ MEM_ROOT *clone_mem_root_arg); ~ha_partition(); /* A partition handler has no characteristics in itself. It only inherits @@ -254,7 +270,10 @@ private: And one method to read it in. */ bool create_handler_file(const char *name); - bool get_from_handler_file(const char *name, MEM_ROOT *mem_root); + bool setup_engine_array(MEM_ROOT *mem_root); + bool read_par_file(const char *name); + bool get_from_handler_file(const char *name, MEM_ROOT *mem_root, + bool is_clone); bool new_handlers_from_part_info(MEM_ROOT *mem_root); bool create_handlers(MEM_ROOT *mem_root); void clear_handler_file(); diff --git a/sql/handler.cc b/sql/handler.cc index fefc0553c88..445e1ec5a3c 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2076,22 +2076,29 @@ int ha_delete_table(THD *thd, handlerton *table_type, const char *path, /**************************************************************************** ** General handler functions ****************************************************************************/ -handler *handler::clone(MEM_ROOT *mem_root) +handler *handler::clone(const char *name, MEM_ROOT *mem_root) { - handler *new_handler= get_new_handler(table->s, mem_root, table->s->db_type()); + handler *new_handler= get_new_handler(table->s, mem_root, ht); /* Allocate handler->ref here because otherwise ha_open will allocate it on this->table->mem_root and we will not be able to reclaim that memory when the clone handler object is destroyed. */ - if (!(new_handler->ref= (uchar*) alloc_root(mem_root, ALIGN_SIZE(ref_length)*2))) - return NULL; - if (new_handler && !new_handler->ha_open(table, - table->s->normalized_path.str, - table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED)) - return new_handler; - return NULL; + if (new_handler && + !(new_handler->ref= (uchar*) alloc_root(mem_root, + ALIGN_SIZE(ref_length)*2))) + new_handler= NULL; + /* + TODO: Implement a more efficient way to have more than one index open for + the same table instance. The ha_open call is not cachable for clone. + */ + if (new_handler && new_handler->ha_open(table, + name, + table->db_stat, + HA_OPEN_IGNORE_IF_LOCKED)) + new_handler= NULL; + + return new_handler; } diff --git a/sql/handler.h b/sql/handler.h index 911f7516e98..1250388f459 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1262,7 +1262,7 @@ public: DBUG_ASSERT(locked == FALSE); DBUG_ASSERT(inited == NONE); } - virtual handler *clone(MEM_ROOT *mem_root); + virtual handler *clone(const char *name, MEM_ROOT *mem_root); /** This is called after create to allow us to set up cached variables */ void init() { diff --git a/sql/item_func.cc b/sql/item_func.cc index 3fca22e0aaa..9035d644962 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2409,10 +2409,7 @@ my_decimal *Item_func_round::decimal_op(my_decimal *decimal_value) if (!(null_value= (args[0]->null_value || args[1]->null_value || my_decimal_round(E_DEC_FATAL_ERROR, value, (int) dec, truncate, decimal_value) > 1))) - { - decimal_value->frac= decimals; return decimal_value; - } return 0; } diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index c2363409269..68e75a262dc 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -669,7 +669,7 @@ bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, system_charset_info); break; case 'W': - if (type == MYSQL_TIMESTAMP_TIME) + if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year)) return 1; weekday= calc_weekday(calc_daynr(l_time->year,l_time->month, l_time->day),0); @@ -678,7 +678,7 @@ bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, system_charset_info); break; case 'a': - if (type == MYSQL_TIMESTAMP_TIME) + if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year)) return 1; weekday=calc_weekday(calc_daynr(l_time->year,l_time->month, l_time->day),0); @@ -837,7 +837,7 @@ bool make_date_time(DATE_TIME_FORMAT *format, MYSQL_TIME *l_time, } break; case 'w': - if (type == MYSQL_TIMESTAMP_TIME) + if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year)) return 1; weekday=calc_weekday(calc_daynr(l_time->year,l_time->month, l_time->day),1); diff --git a/sql/mysqld.h b/sql/mysqld.h index 8e58e7534b2..4469e7f47be 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -73,7 +73,7 @@ void flush_thread_cache(); void refresh_status(THD *thd); bool is_secure_file_path(char *path); -extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *system_charset_info; +extern "C" MYSQL_PLUGIN_IMPORT CHARSET_INFO *system_charset_info; extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *files_charset_info ; extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *national_charset_info; extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *table_alias_charset; @@ -182,7 +182,8 @@ extern ulong opt_binlog_rows_event_max_size; extern ulong rpl_recovery_rank, thread_cache_size; extern ulong back_log; extern char language[FN_REFLEN]; -extern ulong server_id, concurrency; +extern "C" MYSQL_PLUGIN_IMPORT ulong server_id; +extern ulong concurrency; extern time_t server_start_time, flush_status_time; extern char *opt_mysql_tmpdir, mysql_charsets_dir[]; extern int mysql_unpacked_real_data_home_len; @@ -203,8 +204,8 @@ extern handlerton *heap_hton; extern const char *load_default_groups[]; extern struct my_option my_long_options[]; extern int mysqld_server_started; -extern int orig_argc; -extern char **orig_argv; +extern "C" MYSQL_PLUGIN_IMPORT int orig_argc; +extern "C" MYSQL_PLUGIN_IMPORT char **orig_argv; extern MYSQL_PLUGIN_IMPORT pthread_attr_t connection_attrib; extern MYSQL_FILE *bootstrap_file; extern my_bool old_mode; @@ -312,7 +313,7 @@ extern uint mysql_real_data_home_len; extern const char *mysql_real_data_home_ptr; extern ulong thread_handling; extern MYSQL_PLUGIN_IMPORT char *mysql_data_home; -extern char server_version[SERVER_VERSION_LENGTH]; +extern "C" MYSQL_PLUGIN_IMPORT char server_version[SERVER_VERSION_LENGTH]; extern MYSQL_PLUGIN_IMPORT char mysql_real_data_home[]; extern char mysql_unpacked_real_data_home[]; extern MYSQL_PLUGIN_IMPORT struct system_variables global_system_variables; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 4cdabab31ae..6e106010968 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -1368,7 +1368,7 @@ int QUICK_RANGE_SELECT::init_ror_merged_scan(bool reuse_handler) } thd= head->in_use; - if (!(file= head->file->clone(thd->mem_root))) + if (!(file= head->file->clone(head->s->normalized_path.str, thd->mem_root))) { /* Manually set the error flag. Note: there seems to be quite a few diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 026166726b2..2646cc82717 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3916,6 +3916,7 @@ bool xid_cache_insert(XID *xid, enum xa_states xa_state) xs->xa_state=xa_state; xs->xid.set(xid); xs->in_thd=0; + xs->rm_error=0; res=my_hash_insert(&xid_cache, (uchar*)xs); } mysql_mutex_unlock(&LOCK_xid_cache); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 0589488d850..ae708178097 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -1302,9 +1302,10 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, String &field_term, String &line_start, String &line_term, String &enclosed_par, int escape, bool get_it_from_net, bool is_fifo) - :file(file_par),escape_char(escape) + :file(file_par), buff_length(tot_length), escape_char(escape), + found_end_of_line(false), eof(false), need_end_io_cache(false), + error(false), line_cuted(false), found_null(false), read_charset(cs) { - read_charset= cs; field_term_ptr=(char*) field_term.ptr(); field_term_length= field_term.length(); line_term_ptr=(char*) line_term.ptr(); @@ -1332,8 +1333,6 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, (uchar) enclosed_par[0] : INT_MAX; field_term_char= field_term_length ? (uchar) field_term_ptr[0] : INT_MAX; line_term_char= line_term_length ? (uchar) line_term_ptr[0] : INT_MAX; - error=eof=found_end_of_line=found_null=line_cuted=0; - buff_length=tot_length; /* Set of a stack for unget if long terminators */ @@ -1379,7 +1378,7 @@ READ_INFO::READ_INFO(File file_par, uint tot_length, CHARSET_INFO *cs, READ_INFO::~READ_INFO() { - if (!error && need_end_io_cache) + if (need_end_io_cache) ::end_io_cache(&cache); if (buffer != NULL) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7334b03f076..85d4aa24ea9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2263,7 +2263,7 @@ JOIN::exec() Item* sort_table_cond= make_cond_for_table(curr_join->tmp_having, used_tables, - used_tables); + (table_map) 0); if (sort_table_cond) { if (!curr_table->select) @@ -13102,6 +13102,42 @@ static bool test_if_ref(Item_field *left_item,Item *right_item) return 0; // keep test } +/** + Extract a condition that can be checked after reading given table + + @param cond Condition to analyze + @param tables Tables for which "current field values" are available + @param used_table Table that we're extracting the condition for (may + also include PSEUDO_TABLE_BITS, and may be zero) + @param exclude_expensive_cond Do not push expensive conditions + + @retval <>NULL Generated condition + @retval =NULL Already checked, OR error + + @details + Extract the condition that can be checked after reading the table + specified in 'used_table', given that current-field values for tables + specified in 'tables' bitmap are available. + If 'used_table' is 0 + - extract conditions for all tables in 'tables'. + - extract conditions are unrelated to any tables + in the same query block/level(i.e. conditions + which have used_tables == 0). + + The function assumes that + - Constant parts of the condition has already been checked. + - Condition that could be checked for tables in 'tables' has already + been checked. + + The function takes into account that some parts of the condition are + guaranteed to be true by employed 'ref' access methods (the code that + does this is located at the end, search down for "EQ_FUNC"). + + @note + Make sure to keep the implementations of make_cond_for_table() and + make_cond_after_sjm() synchronized. + make_cond_for_info_schema() uses similar algorithm as well. +*/ static COND * make_cond_for_table(COND *cond, table_map tables, table_map used_table) diff --git a/storage/heap/ha_heap.cc b/storage/heap/ha_heap.cc index fc870d33fb2..1b1dfdc003e 100644 --- a/storage/heap/ha_heap.cc +++ b/storage/heap/ha_heap.cc @@ -157,11 +157,11 @@ int ha_heap::close(void) DESCRIPTION Do same as default implementation but use file->s->name instead of table->s->path. This is needed by Windows where the clone() call sees - '/'-delimited path in table->s->path, while ha_peap::open() was called + '/'-delimited path in table->s->path, while ha_heap::open() was called with '\'-delimited path. */ -handler *ha_heap::clone(MEM_ROOT *mem_root) +handler *ha_heap::clone(const char *name, MEM_ROOT *mem_root) { handler *new_handler= get_new_handler(table->s, mem_root, table->s->db_type()); if (new_handler && !new_handler->ha_open(table, file->s->name, table->db_stat, diff --git a/storage/heap/ha_heap.h b/storage/heap/ha_heap.h index 5f3d66cd53c..cc335870f06 100644 --- a/storage/heap/ha_heap.h +++ b/storage/heap/ha_heap.h @@ -35,7 +35,7 @@ class ha_heap: public handler public: ha_heap(handlerton *hton, TABLE_SHARE *table); ~ha_heap() {} - handler *clone(MEM_ROOT *mem_root); + handler *clone(const char *name, MEM_ROOT *mem_root); const char *table_type() const { return (table->in_use->variables.sql_mode & MODE_MYSQL323) ? diff --git a/storage/innobase/btr/btr0cur.c b/storage/innobase/btr/btr0cur.c index c4034d0896a..9fa8fc663c9 100644 --- a/storage/innobase/btr/btr0cur.c +++ b/storage/innobase/btr/btr0cur.c @@ -2429,8 +2429,8 @@ make_external: record on its page? */ was_first = page_cur_is_before_first(page_cursor); - /* The first parameter means that no lock checking and undo logging - is made in the insert */ + /* Lock checks and undo logging were already performed by + btr_cur_upd_lock_and_undo(). */ err = btr_cur_pessimistic_insert(BTR_NO_UNDO_LOG_FLAG | BTR_NO_LOCKING_FLAG diff --git a/storage/innobase/buf/buf0flu.c b/storage/innobase/buf/buf0flu.c index 07a32e55f97..ebe96a82a10 100644 --- a/storage/innobase/buf/buf0flu.c +++ b/storage/innobase/buf/buf0flu.c @@ -1716,7 +1716,7 @@ buf_flush_batch( ut_ad(flush_type == BUF_FLUSH_LRU || flush_type == BUF_FLUSH_LIST); #ifdef UNIV_SYNC_DEBUG ut_ad((flush_type != BUF_FLUSH_LIST) - || sync_thread_levels_empty_gen(TRUE)); + || sync_thread_levels_empty_except_dict()); #endif /* UNIV_SYNC_DEBUG */ buf_pool_mutex_enter(buf_pool); diff --git a/storage/innobase/dict/dict0dict.c b/storage/innobase/dict/dict0dict.c index ebc7747640e..df9db4d7428 100644 --- a/storage/innobase/dict/dict0dict.c +++ b/storage/innobase/dict/dict0dict.c @@ -52,7 +52,6 @@ UNIV_INTERN dict_index_t* dict_ind_compact; #include "que0que.h" #include "rem0cmp.h" #include "row0merge.h" -#include "srv0srv.h" /* srv_lower_case_table_names */ #include "m_ctype.h" /* my_isspace() */ #include "ha_prototypes.h" /* innobase_strcasecmp(), innobase_casedn_str()*/ @@ -3029,14 +3028,14 @@ dict_scan_table_name( /* Values; 0 = Store and compare as given; case sensitive 1 = Store and compare in lower; case insensitive 2 = Store as given, compare in lower; case semi-sensitive */ - if (srv_lower_case_table_names == 2) { + if (innobase_get_lower_case_table_names() == 2) { innobase_casedn_str(ref); *table = dict_table_get_low(ref); memcpy(ref, database_name, database_name_len); ref[database_name_len] = '/'; memcpy(ref + database_name_len + 1, table_name, table_name_len + 1); } else { - if (srv_lower_case_table_names == 1) { + if (innobase_get_lower_case_table_names() == 1) { innobase_casedn_str(ref); } *table = dict_table_get_low(ref); diff --git a/storage/innobase/dict/dict0load.c b/storage/innobase/dict/dict0load.c index 37bf4a1ad59..aad3145f7a4 100644 --- a/storage/innobase/dict/dict0load.c +++ b/storage/innobase/dict/dict0load.c @@ -2262,7 +2262,7 @@ loop: may not be the same case, but the previous comparison showed that they match with no-case. */ - if ((srv_lower_case_table_names != 2) + if ((innobase_get_lower_case_table_names() != 2) && (0 != ut_memcmp(field, table_name, len))) { goto next_rec; } diff --git a/storage/innobase/dict/dict0mem.c b/storage/innobase/dict/dict0mem.c index a442a3811d8..8785dfb57ed 100644 --- a/storage/innobase/dict/dict0mem.c +++ b/storage/innobase/dict/dict0mem.c @@ -33,7 +33,6 @@ Created 1/8/1996 Heikki Tuuri #include "data0type.h" #include "mach0data.h" #include "dict0dict.h" -#include "srv0srv.h" /* srv_lower_case_table_names */ #include "ha_prototypes.h" /* innobase_casedn_str()*/ #ifndef UNIV_HOTBACKUP # include "lock0lock.h" @@ -294,9 +293,9 @@ dict_mem_foreign_create(void) /**********************************************************************//** Sets the foreign_table_name_lookup pointer based on the value of -srv_lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup -will point to foreign_table_name. If 2, then another string is allocated -of the heap and set to lower case. */ +lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup +will point to foreign_table_name. If 2, then another string is +allocated from foreign->heap and set to lower case. */ UNIV_INTERN void dict_mem_foreign_table_name_lookup_set( @@ -304,7 +303,7 @@ dict_mem_foreign_table_name_lookup_set( dict_foreign_t* foreign, /*!< in/out: foreign struct */ ibool do_alloc) /*!< in: is an alloc needed */ { - if (srv_lower_case_table_names == 2) { + if (innobase_get_lower_case_table_names() == 2) { if (do_alloc) { foreign->foreign_table_name_lookup = mem_heap_alloc( foreign->heap, @@ -321,9 +320,9 @@ dict_mem_foreign_table_name_lookup_set( /**********************************************************************//** Sets the referenced_table_name_lookup pointer based on the value of -srv_lower_case_table_names. If that is 0 or 1, -referenced_table_name_lookup will point to referenced_table_name. If 2, -then another string is allocated of the heap and set to lower case. */ +lower_case_table_names. If that is 0 or 1, referenced_table_name_lookup +will point to referenced_table_name. If 2, then another string is +allocated from foreign->heap and set to lower case. */ UNIV_INTERN void dict_mem_referenced_table_name_lookup_set( @@ -331,7 +330,7 @@ dict_mem_referenced_table_name_lookup_set( dict_foreign_t* foreign, /*!< in/out: foreign struct */ ibool do_alloc) /*!< in: is an alloc needed */ { - if (srv_lower_case_table_names == 2) { + if (innobase_get_lower_case_table_names() == 2) { if (do_alloc) { foreign->referenced_table_name_lookup = mem_heap_alloc( foreign->heap, diff --git a/storage/innobase/fil/fil0fil.c b/storage/innobase/fil/fil0fil.c index 6c50b853187..0d9846fdbf8 100644 --- a/storage/innobase/fil/fil0fil.c +++ b/storage/innobase/fil/fil0fil.c @@ -4527,8 +4527,8 @@ fil_aio_wait( ret = os_aio_linux_handle(segment, &fil_node, &message, &type); #else - ret = 0; /* Eliminate compiler warning */ ut_error; + ret = 0; /* Eliminate compiler warning */ #endif } else { srv_set_io_thread_op_info(segment, "simulated aio handle"); @@ -4538,6 +4538,10 @@ fil_aio_wait( } ut_a(ret); + if (UNIV_UNLIKELY(fil_node == NULL)) { + ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); + return; + } srv_set_io_thread_op_info(segment, "complete io for fil node"); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 74a33d05760..8efa0523927 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1199,6 +1199,20 @@ innobase_get_stmt( return(stmt->str); } +/**********************************************************************//** +Get the current setting of the lower_case_table_names global parameter from +mysqld.cc. We do a dirty read because for one there is no synchronization +object and secondly there is little harm in doing so even if we get a torn +read. +@return value of lower_case_table_names */ +extern "C" UNIV_INTERN +ulint +innobase_get_lower_case_table_names(void) +/*=====================================*/ +{ + return(lower_case_table_names); +} + #if defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN) extern MYSQL_PLUGIN_IMPORT MY_TMPDIR mysql_tmpdir_list; /*******************************************************************//** @@ -3671,7 +3685,6 @@ ha_innobase::open( UT_NOT_USED(test_if_locked); thd = ha_thd(); - srv_lower_case_table_names = lower_case_table_names; /* Under some cases MySQL seems to call this function while holding btr_search_latch. This breaks the latching order as @@ -6242,10 +6255,6 @@ create_table_def( DBUG_PRINT("enter", ("table_name: %s", table_name)); ut_a(trx->mysql_thd != NULL); - if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(table_name, - (THD*) trx->mysql_thd)) { - DBUG_RETURN(HA_ERR_GENERIC); - } /* MySQL does the name length check. But we do additional check on the name length here */ @@ -6782,38 +6791,17 @@ ha_innobase::create( DBUG_RETURN(HA_ERR_TO_BIG_ROW); } - /* Get the transaction associated with the current thd, or create one - if not yet created */ - - parent_trx = check_trx_exists(thd); - - /* In case MySQL calls this in the middle of a SELECT query, release - possible adaptive hash latch to avoid deadlocks of threads */ - - trx_search_latch_release_if_reserved(parent_trx); - - trx = innobase_trx_allocate(thd); - - srv_lower_case_table_names = lower_case_table_names; - strcpy(name2, name); normalize_table_name(norm_name, name2); - /* Latch the InnoDB data dictionary exclusively so that no deadlocks - or lock waits can happen in it during a table create operation. - Drop table etc. do this latching in row0mysql.c. */ - - row_mysql_lock_data_dictionary(trx); - /* Create the table definition in InnoDB */ flags = 0; /* Validate create options if innodb_strict_mode is set. */ if (!create_options_are_valid(thd, form, create_info)) { - error = ER_ILLEGAL_HA_CREATE_OPTION; - goto cleanup; + DBUG_RETURN(ER_ILLEGAL_HA_CREATE_OPTION); } if (create_info->key_block_size) { @@ -6955,16 +6943,37 @@ ha_innobase::create( /* Check for name conflicts (with reserved name) for any user indices to be created. */ - if (innobase_index_name_is_reserved(trx, form->key_info, + if (innobase_index_name_is_reserved(thd, form->key_info, form->s->keys)) { - error = -1; - goto cleanup; + DBUG_RETURN(-1); + } + + if (IS_MAGIC_TABLE_AND_USER_DENIED_ACCESS(norm_name, thd)) { + DBUG_RETURN(HA_ERR_GENERIC); } if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { flags |= DICT_TF2_TEMPORARY << DICT_TF2_SHIFT; } + /* Get the transaction associated with the current thd, or create one + if not yet created */ + + parent_trx = check_trx_exists(thd); + + /* In case MySQL calls this in the middle of a SELECT query, release + possible adaptive hash latch to avoid deadlocks of threads */ + + trx_search_latch_release_if_reserved(parent_trx); + + trx = innobase_trx_allocate(thd); + + /* Latch the InnoDB data dictionary exclusively so that no deadlocks + or lock waits can happen in it during a table create operation. + Drop table etc. do this latching in row0mysql.c. */ + + row_mysql_lock_data_dictionary(trx); + error = create_table_def(trx, form, norm_name, create_info->options & HA_LEX_CREATE_TMP_TABLE ? name2 : NULL, flags); @@ -7219,8 +7228,6 @@ ha_innobase::delete_table( trx = innobase_trx_allocate(thd); - srv_lower_case_table_names = lower_case_table_names; - name_len = strlen(name); ut_a(name_len < 1000); @@ -7342,8 +7349,6 @@ innobase_rename_table( char* norm_to; char* norm_from; - srv_lower_case_table_names = lower_case_table_names; - // Magic number 64 arbitrary norm_to = (char*) my_malloc(strlen(to) + 64, MYF(0)); norm_from = (char*) my_malloc(strlen(from) + 64, MYF(0)); @@ -10263,7 +10268,7 @@ innobase_commit_by_xid( if (trx) { innobase_commit_low(trx); - + trx_free_for_background(trx); return(XA_OK); } else { return(XAER_NOTA); @@ -10289,7 +10294,9 @@ innobase_rollback_by_xid( trx = trx_get_trx_by_xid(xid); if (trx) { - return(innobase_rollback_trx(trx)); + int ret = innobase_rollback_trx(trx); + trx_free_for_background(trx); + return(ret); } else { return(XAER_NOTA); } @@ -10922,19 +10929,19 @@ static int show_innodb_vars(THD *thd, SHOW_VAR *var, char *buff) return 0; } -/*********************************************************************** +/*********************************************************************//** This function checks each index name for a table against reserved -system default primary index name 'GEN_CLUST_INDEX'. If a name matches, -this function pushes an warning message to the client, and returns true. */ +system default primary index name 'GEN_CLUST_INDEX'. If a name +matches, this function pushes an warning message to the client, +and returns true. +@return true if the index name matches the reserved name */ extern "C" UNIV_INTERN bool innobase_index_name_is_reserved( /*============================*/ - /* out: true if an index name - matches the reserved name */ - const trx_t* trx, /* in: InnoDB transaction handle */ - const KEY* key_info, /* in: Indexes to be created */ - ulint num_of_keys) /* in: Number of indexes to + THD* thd, /*!< in/out: MySQL connection */ + const KEY* key_info, /*!< in: Indexes to be created */ + ulint num_of_keys) /*!< in: Number of indexes to be created. */ { const KEY* key; @@ -10946,7 +10953,7 @@ innobase_index_name_is_reserved( if (innobase_strcasecmp(key->name, innobase_index_reserve_name) == 0) { /* Push warning to mysql */ - push_warning_printf((THD*) trx->mysql_thd, + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WRONG_NAME_FOR_INDEX, "Cannot Create Index with name " diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index f05ea8801f0..0c0af8dd887 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -321,15 +321,14 @@ innobase_trx_allocate( This function checks each index name for a table against reserved system default primary index name 'GEN_CLUST_INDEX'. If a name matches, this function pushes an warning message to the client, -and returns true. */ +and returns true. +@return true if the index name matches the reserved name */ extern "C" bool innobase_index_name_is_reserved( /*============================*/ - /* out: true if the index name - matches the reserved name */ - const trx_t* trx, /* in: InnoDB transaction handle */ - const KEY* key_info, /* in: Indexes to be created */ - ulint num_of_keys); /* in: Number of indexes to + THD* thd, /*!< in/out: MySQL connection */ + const KEY* key_info, /*!< in: Indexes to be created */ + ulint num_of_keys); /*!< in: Number of indexes to be created. */ diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index e0279c13de2..cc7e48ebd44 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -653,44 +653,37 @@ ha_innobase::add_index( update_thd(); - heap = mem_heap_create(1024); - /* In case MySQL calls this in the middle of a SELECT query, release possible adaptive hash latch to avoid deadlocks of threads. */ trx_search_latch_release_if_reserved(prebuilt->trx); - trx_start_if_not_started(prebuilt->trx); - /* Create a background transaction for the operations on - the data dictionary tables. */ - trx = innobase_trx_allocate(user_thd); - trx_start_if_not_started(trx); + /* Check if the index name is reserved. */ + if (innobase_index_name_is_reserved(user_thd, key_info, num_of_keys)) { + DBUG_RETURN(-1); + } innodb_table = indexed_table = dict_table_get(prebuilt->table->name, FALSE); if (UNIV_UNLIKELY(!innodb_table)) { - error = HA_ERR_NO_SUCH_TABLE; - goto err_exit; + DBUG_RETURN(HA_ERR_NO_SUCH_TABLE); } - /* Check if the index name is reserved. */ - if (innobase_index_name_is_reserved(trx, key_info, num_of_keys)) { - error = -1; - } else { - /* Check that index keys are sensible */ - error = innobase_check_index_keys(key_info, num_of_keys, - innodb_table); - } + /* Check that index keys are sensible */ + error = innobase_check_index_keys(key_info, num_of_keys, innodb_table); if (UNIV_UNLIKELY(error)) { -err_exit: - mem_heap_free(heap); - trx_general_rollback_for_mysql(trx, NULL); - trx_free_for_mysql(trx); - trx_commit_for_mysql(prebuilt->trx); DBUG_RETURN(error); } + heap = mem_heap_create(1024); + trx_start_if_not_started(prebuilt->trx); + + /* Create a background transaction for the operations on + the data dictionary tables. */ + trx = innobase_trx_allocate(user_thd); + trx_start_if_not_started(trx); + /* Create table containing all indexes to be built in this alter table add index so that they are in the correct order in the table. */ @@ -762,8 +755,12 @@ err_exit: ut_d(dict_table_check_for_dup_indexes(innodb_table, FALSE)); + mem_heap_free(heap); + trx_general_rollback_for_mysql(trx, NULL); row_mysql_unlock_data_dictionary(trx); - goto err_exit; + trx_free_for_mysql(trx); + trx_commit_for_mysql(prebuilt->trx); + DBUG_RETURN(error); } trx->table_id = indexed_table->id; diff --git a/storage/innobase/ibuf/ibuf0ibuf.c b/storage/innobase/ibuf/ibuf0ibuf.c index 8110bccc162..7d94ebc6438 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.c +++ b/storage/innobase/ibuf/ibuf0ibuf.c @@ -1179,18 +1179,7 @@ ibuf_page_low( ibuf_bitmap_page_no_calc(zip_size, page_no), RW_NO_LATCH, NULL, BUF_GET_NO_LATCH, file, line, &local_mtr)); -# ifdef UNIV_SYNC_DEBUG - /* This is for tracking Bug #58212. This check and message can - be removed once it has been established that our assumptions - about this condition are correct. The bug was only a one-time - occurrence, unable to repeat since then. */ - void* latch = sync_thread_levels_contains(SYNC_IBUF_BITMAP); - if (latch) { - fprintf(stderr, "Bug#58212 UNIV_SYNC_DEBUG" - " levels %p (%u,%u)\n", - latch, (unsigned) space, (unsigned) page_no); - } -# endif /* UNIV_SYNC_DEBUG */ + ret = ibuf_bitmap_page_get_bits_low( bitmap_page, page_no, zip_size, MTR_MEMO_BUF_FIX, &local_mtr, IBUF_BITMAP_IBUF); diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 75d3b2c3302..51c9c2d1797 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -240,7 +240,9 @@ dict_mem_foreign_create(void); /**********************************************************************//** Sets the foreign_table_name_lookup pointer based on the value of -srv_lower_case_table_names. */ +lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup +will point to foreign_table_name. If 2, then another string is +allocated from the heap and set to lower case. */ UNIV_INTERN void dict_mem_foreign_table_name_lookup_set( @@ -249,8 +251,10 @@ dict_mem_foreign_table_name_lookup_set( ibool do_alloc); /*!< in: is an alloc needed */ /**********************************************************************//** -Sets the reference_table_name_lookup pointer based on the value of -srv_lower_case_table_names. */ +Sets the referenced_table_name_lookup pointer based on the value of +lower_case_table_names. If that is 0 or 1, referenced_table_name_lookup +will point to referenced_table_name. If 2, then another string is +allocated from the heap and set to lower case. */ UNIV_INTERN void dict_mem_referenced_table_name_lookup_set( diff --git a/storage/innobase/include/ha_prototypes.h b/storage/innobase/include/ha_prototypes.h index dd9e8db82ee..edf7a1a28c1 100644 --- a/storage/innobase/include/ha_prototypes.h +++ b/storage/innobase/include/ha_prototypes.h @@ -285,4 +285,15 @@ thd_set_lock_wait_time( void* thd, /*!< in: thread handle (THD*) */ ulint value); /*!< in: time waited for the lock */ +/**********************************************************************//** +Get the current setting of the lower_case_table_names global parameter from +mysqld.cc. We do a dirty read because for one there is no synchronization +object and secondly there is little harm in doing so even if we get a torn +read. +@return value of lower_case_table_names */ +UNIV_INTERN +ulint +innobase_get_lower_case_table_names(void); +/*=====================================*/ + #endif diff --git a/storage/innobase/include/log0log.ic b/storage/innobase/include/log0log.ic index 1ce00fd7313..67db6695cab 100644 --- a/storage/innobase/include/log0log.ic +++ b/storage/innobase/include/log0log.ic @@ -435,7 +435,7 @@ log_free_check(void) { #ifdef UNIV_SYNC_DEBUG - ut_ad(sync_thread_levels_empty_gen(TRUE)); + ut_ad(sync_thread_levels_empty_except_dict()); #endif /* UNIV_SYNC_DEBUG */ if (log_sys->check_flush_or_checkpoint) { diff --git a/storage/innobase/include/os0sync.h b/storage/innobase/include/os0sync.h index b294d7421c8..1b98f94f641 100644 --- a/storage/innobase/include/os0sync.h +++ b/storage/innobase/include/os0sync.h @@ -150,10 +150,7 @@ os_event_free( os_event_t event); /*!< in: event to free */ /**********************************************************//** -Waits for an event object until it is in the signaled state. If -srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS this also exits the -waiting thread when the event becomes signaled (or immediately if the -event is already in the signaled state). +Waits for an event object until it is in the signaled state. Typically, if the event has been signalled after the os_event_reset() we'll return immediately because event->is_set == TRUE. diff --git a/storage/innobase/include/os0thread.h b/storage/innobase/include/os0thread.h index cc56e2158ee..df3cdb7728e 100644 --- a/storage/innobase/include/os0thread.h +++ b/storage/innobase/include/os0thread.h @@ -107,8 +107,9 @@ UNIV_INTERN void os_thread_exit( /*===========*/ - void* exit_value); /*!< in: exit value; in Windows this void* + void* exit_value) /*!< in: exit value; in Windows this void* is cast as a DWORD */ + UNIV_COLD __attribute__((noreturn)); /*****************************************************************//** Returns the thread identifier of current thread. @return current thread identifier */ @@ -117,13 +118,6 @@ os_thread_id_t os_thread_get_curr_id(void); /*========================*/ /*****************************************************************//** -Returns handle to the current thread. -@return current thread handle */ -UNIV_INTERN -os_thread_t -os_thread_get_curr(void); -/*====================*/ -/*****************************************************************//** Advises the os to give up remainder of the thread's time slice. */ UNIV_INTERN void @@ -136,29 +130,6 @@ void os_thread_sleep( /*============*/ ulint tm); /*!< in: time in microseconds */ -/******************************************************************//** -Gets a thread priority. -@return priority */ -UNIV_INTERN -ulint -os_thread_get_priority( -/*===================*/ - os_thread_t handle);/*!< in: OS handle to the thread */ -/******************************************************************//** -Sets a thread priority. */ -UNIV_INTERN -void -os_thread_set_priority( -/*===================*/ - os_thread_t handle, /*!< in: OS handle to the thread */ - ulint pri); /*!< in: priority: one of OS_PRIORITY_... */ -/******************************************************************//** -Gets the last operating system error code for the calling thread. -@return last error on Windows, 0 otherwise */ -UNIV_INTERN -ulint -os_thread_get_last_error(void); -/*==========================*/ #ifndef UNIV_NONINL #include "os0thread.ic" diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h index fb5bc56920a..fc9401a12f5 100644 --- a/storage/innobase/include/srv0srv.h +++ b/storage/innobase/include/srv0srv.h @@ -71,9 +71,6 @@ at a time */ #define SRV_AUTO_EXTEND_INCREMENT \ (srv_auto_extend_increment * ((1024 * 1024) / UNIV_PAGE_SIZE)) -/* This is set to the MySQL server value for this variable. */ -extern uint srv_lower_case_table_names; - /* Mutex for locking srv_monitor_file */ extern mutex_t srv_monitor_file_mutex; /* Temporary file for innodb monitor output */ diff --git a/storage/innobase/include/sync0sync.h b/storage/innobase/include/sync0sync.h index a24c2106033..b823c9d5259 100644 --- a/storage/innobase/include/sync0sync.h +++ b/storage/innobase/include/sync0sync.h @@ -413,13 +413,6 @@ sync_thread_reset_level( /*====================*/ void* latch); /*!< in: pointer to a mutex or an rw-lock */ /******************************************************************//** -Checks that the level array for the current thread is empty. -@return TRUE if empty */ -UNIV_INTERN -ibool -sync_thread_levels_empty(void); -/*==========================*/ -/******************************************************************//** Checks if the level array for the current thread contains a mutex or rw-latch at the specified level. @return a matching latch, or NULL if not found */ @@ -430,17 +423,33 @@ sync_thread_levels_contains( ulint level); /*!< in: latching order level (SYNC_DICT, ...)*/ /******************************************************************//** -Checks if the level array for the current thread is empty. +Checks that the level array for the current thread is empty. @return a latch, or NULL if empty except the exceptions specified below */ UNIV_INTERN void* sync_thread_levels_nonempty_gen( /*============================*/ - ibool dict_mutex_allowed); /*!< in: TRUE if dictionary mutex is - allowed to be owned by the thread, - also purge_is_running mutex is - allowed */ -#define sync_thread_levels_empty_gen(d) (!sync_thread_levels_nonempty_gen(d)) + ibool dict_mutex_allowed) /*!< in: TRUE if dictionary mutex is + allowed to be owned by the thread */ + __attribute__((warn_unused_result)); +/******************************************************************//** +Checks if the level array for the current thread is empty, +except for data dictionary latches. */ +#define sync_thread_levels_empty_except_dict() \ + (!sync_thread_levels_nonempty_gen(TRUE)) +/******************************************************************//** +Checks if the level array for the current thread is empty, +except for the btr_search_latch. +@return a latch, or NULL if empty except the exceptions specified below */ +UNIV_INTERN +void* +sync_thread_levels_nonempty_trx( +/*============================*/ + ibool has_search_latch) + /*!< in: TRUE if and only if the thread + is supposed to hold btr_search_latch */ + __attribute__((warn_unused_result)); + /******************************************************************//** Gets the debug information for a reserved mutex. */ UNIV_INTERN diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 83f6182c347..588ddd65e88 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -44,6 +44,9 @@ extern sess_t* trx_dummy_sess; /** Number of transactions currently allocated for MySQL: protected by the kernel mutex */ extern ulint trx_n_mysql_transactions; +/** Number of transactions currently in the XA PREPARED state: protected by +the kernel mutex */ +extern ulint trx_n_prepared; /********************************************************************//** Releases the search latch if trx has reserved it. */ @@ -108,6 +111,14 @@ trx_free( /*=====*/ trx_t* trx); /*!< in, own: trx object */ /********************************************************************//** +At shutdown, frees a transaction object that is in the PREPARED state. */ +UNIV_INTERN +void +trx_free_prepared( +/*==============*/ + trx_t* trx) /*!< in, own: trx object */ + UNIV_COLD __attribute__((nonnull)); +/********************************************************************//** Frees a transaction object for MySQL. */ UNIV_INTERN void @@ -569,11 +580,6 @@ struct trx_struct{ ib_int64_t mysql_log_offset;/* if MySQL binlog is used, this field contains the end offset of the binlog entry */ - os_thread_id_t mysql_thread_id;/* id of the MySQL thread associated - with this transaction object */ - ulint mysql_process_no;/* since in Linux, 'top' reports - process id's and not thread id's, we - store the process number too */ /*------------------------------*/ ulint n_mysql_tables_in_use; /* number of Innobase tables used in the processing of the current diff --git a/storage/innobase/include/trx0undo.h b/storage/innobase/include/trx0undo.h index 937e9d7ef79..df16c939070 100644 --- a/storage/innobase/include/trx0undo.h +++ b/storage/innobase/include/trx0undo.h @@ -296,6 +296,15 @@ void trx_undo_insert_cleanup( /*====================*/ trx_t* trx); /*!< in: transaction handle */ + +/********************************************************************//** +At shutdown, frees the undo logs of a PREPARED transaction. */ +UNIV_INTERN +void +trx_undo_free_prepared( +/*===================*/ + trx_t* trx) /*!< in/out: PREPARED transaction */ + UNIV_COLD __attribute__((nonnull)); #endif /* !UNIV_HOTBACKUP */ /***********************************************************//** Parses the redo log entry of an undo log page initialization. diff --git a/storage/innobase/include/univ.i b/storage/innobase/include/univ.i index f561226a2de..9816edc2eaa 100644 --- a/storage/innobase/include/univ.i +++ b/storage/innobase/include/univ.i @@ -51,7 +51,7 @@ Created 1/20/1994 Heikki Tuuri #define INNODB_VERSION_MAJOR 1 #define INNODB_VERSION_MINOR 1 -#define INNODB_VERSION_BUGFIX 6 +#define INNODB_VERSION_BUGFIX 7 /* The following is the InnoDB version as shown in SELECT plugin_version FROM information_schema.plugins; @@ -255,6 +255,19 @@ easy way to get it to work. See http://bugs.mysql.com/bug.php?id=52263. */ #else # define UNIV_INTERN #endif +#if defined __GNUC__ && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 3) +/** Starting with GCC 4.3, the "cold" attribute is used to inform the +compiler that a function is unlikely executed. The function is +optimized for size rather than speed and on many targets it is placed +into special subsection of the text section so all cold functions +appears close together improving code locality of non-cold parts of +program. The paths leading to call of cold functions within code are +marked as unlikely by the branch prediction mechanism. optimize a +rarely invoked function for size instead for speed. */ +# define UNIV_COLD __attribute__((cold)) +#else +# define UNIV_COLD /* empty */ +#endif #ifndef UNIV_MUST_NOT_INLINE /* Definition for inline version */ diff --git a/storage/innobase/include/ut0dbg.h b/storage/innobase/include/ut0dbg.h index d7ec90db0fb..07730176d81 100644 --- a/storage/innobase/include/ut0dbg.h +++ b/storage/innobase/include/ut0dbg.h @@ -50,9 +50,10 @@ UNIV_INTERN void ut_dbg_assertion_failed( /*====================*/ - const char* expr, /*!< in: the failed assertion */ - const char* file, /*!< in: source file containing the assertion */ - ulint line); /*!< in: line number of the assertion */ + const char* expr, /*!< in: the failed assertion */ + const char* file, /*!< in: source file containing the assertion */ + ulint line) /*!< in: line number of the assertion */ + UNIV_COLD __attribute__((nonnull(2))); #if defined(__WIN__) || defined(__INTEL_COMPILER) # undef UT_DBG_USE_ABORT diff --git a/storage/innobase/include/ut0ut.h b/storage/innobase/include/ut0ut.h index cd5c7ca99f1..cad39e9a34f 100644 --- a/storage/innobase/include/ut0ut.h +++ b/storage/innobase/include/ut0ut.h @@ -275,7 +275,8 @@ UNIV_INTERN void ut_print_timestamp( /*===============*/ - FILE* file); /*!< in: file where to print */ + FILE* file) /*!< in: file where to print */ + UNIV_COLD __attribute__((nonnull)); /**********************************************************//** Sprintfs a timestamp to a buffer, 13..14 chars plus terminating NUL. */ UNIV_INTERN diff --git a/storage/innobase/log/log0log.c b/storage/innobase/log/log0log.c index 3fef4ee4fc5..fd2258945b6 100644 --- a/storage/innobase/log/log0log.c +++ b/storage/innobase/log/log0log.c @@ -3078,6 +3078,7 @@ logs_empty_and_mark_files_at_shutdown(void) { ib_uint64_t lsn; ulint arch_log_no; + ibool server_busy; if (srv_print_verbose_log) { ut_print_timestamp(stderr); @@ -3092,14 +3093,12 @@ loop: mutex_enter(&kernel_mutex); - /* We need the monitor threads to stop before we proceed with a - normal shutdown. In case of very fast shutdown, however, we can - proceed without waiting for monitor threads. */ + /* We need the monitor threads to stop before we proceed with + a shutdown. */ - if (srv_fast_shutdown < 2 - && (srv_error_monitor_active - || srv_lock_timeout_active - || srv_monitor_active)) { + if (srv_error_monitor_active + || srv_lock_timeout_active + || srv_monitor_active) { mutex_exit(&kernel_mutex); @@ -3110,69 +3109,70 @@ loop: goto loop; } - /* Check that there are no longer transactions. We need this wait even - for the 'very fast' shutdown, because the InnoDB layer may have - committed or prepared transactions and we don't want to lose them. */ - - if (trx_n_mysql_transactions > 0 - || UT_LIST_GET_LEN(trx_sys->trx_list) > 0) { - - mutex_exit(&kernel_mutex); - - goto loop; - } - - if (srv_fast_shutdown == 2) { - /* In this fastest shutdown we do not flush the buffer pool: - it is essentially a 'crash' of the InnoDB server. Make sure - that the log is all flushed to disk, so that we can recover - all committed transactions in a crash recovery. We must not - write the lsn stamps to the data files, since at a startup - InnoDB deduces from the stamps if the previous shutdown was - clean. */ - - log_buffer_flush_to_disk(); - - mutex_exit(&kernel_mutex); - - return; /* We SKIP ALL THE REST !! */ - } + /* Check that there are no longer transactions, except for + PREPARED ones. We need this wait even for the 'very fast' + shutdown, because the InnoDB layer may have committed or + prepared transactions and we don't want to lose them. */ + server_busy = trx_n_mysql_transactions > 0 + || UT_LIST_GET_LEN(trx_sys->trx_list) > trx_n_prepared; mutex_exit(&kernel_mutex); - /* Check that the background threads are suspended */ - - if (srv_is_any_background_thread_active()) { + if (server_busy || srv_is_any_background_thread_active()) { goto loop; } - mutex_enter(&(log_sys->mutex)); - - if (log_sys->n_pending_checkpoint_writes + mutex_enter(&log_sys->mutex); + server_busy = log_sys->n_pending_checkpoint_writes #ifdef UNIV_LOG_ARCHIVE - || log_sys->n_pending_archive_ios + || log_sys->n_pending_archive_ios #endif /* UNIV_LOG_ARCHIVE */ - || log_sys->n_pending_writes) { - - mutex_exit(&(log_sys->mutex)); - - goto loop; - } - - mutex_exit(&(log_sys->mutex)); - - if (!buf_pool_check_no_pending_io()) { + || log_sys->n_pending_writes; + mutex_exit(&log_sys->mutex); + if (server_busy || !buf_pool_check_no_pending_io()) { goto loop; } #ifdef UNIV_LOG_ARCHIVE log_archive_all(); #endif /* UNIV_LOG_ARCHIVE */ + if (srv_fast_shutdown == 2) { + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: MySQL has requested a very fast shutdown" + " without flushing " + "the InnoDB buffer pool to data files." + " At the next mysqld startup " + "InnoDB will do a crash recovery!\n"); + + /* In this fastest shutdown we do not flush the buffer + pool: it is essentially a 'crash' of the InnoDB + server. Make sure that the log is all flushed to disk, + so that we can recover all committed transactions in a + crash recovery. We must not write the lsn stamps to + the data files, since at a startup InnoDB deduces from + the stamps if the previous shutdown was clean. */ + + log_buffer_flush_to_disk(); + + /* Check that the background threads stay suspended */ + if (srv_is_any_background_thread_active()) { + fprintf(stderr, + "InnoDB: Warning: some background thread" + " woke up during shutdown\n"); + goto loop; + } + + srv_shutdown_state = SRV_SHUTDOWN_LAST_PHASE; + fil_close_all_files(); + ut_a(!srv_is_any_background_thread_active()); + return; + } log_make_checkpoint_at(IB_ULONGLONG_MAX, TRUE); - mutex_enter(&(log_sys->mutex)); + mutex_enter(&log_sys->mutex); lsn = log_sys->lsn; @@ -3183,7 +3183,7 @@ loop: #endif /* UNIV_LOG_ARCHIVE */ ) { - mutex_exit(&(log_sys->mutex)); + mutex_exit(&log_sys->mutex); goto loop; } @@ -3201,7 +3201,7 @@ loop: log_archive_close_groups(TRUE); #endif /* UNIV_LOG_ARCHIVE */ - mutex_exit(&(log_sys->mutex)); + mutex_exit(&log_sys->mutex); /* Check that the background threads stay suspended */ if (srv_is_any_background_thread_active()) { diff --git a/storage/innobase/os/os0file.c b/storage/innobase/os/os0file.c index 74dbac3bc96..50607e07076 100644 --- a/storage/innobase/os/os0file.c +++ b/storage/innobase/os/os0file.c @@ -4064,13 +4064,13 @@ os_aio_func( } try_again: - if (mode == OS_AIO_NORMAL) { - if (type == OS_FILE_READ) { - array = os_aio_read_array; - } else { - array = os_aio_write_array; - } - } else if (mode == OS_AIO_IBUF) { + switch (mode) { + case OS_AIO_NORMAL: + array = (type == OS_FILE_READ) + ? os_aio_read_array + : os_aio_write_array; + break; + case OS_AIO_IBUF: ut_ad(type == OS_FILE_READ); /* Reduce probability of deadlock bugs in connection with ibuf: do not let the ibuf i/o handler sleep */ @@ -4078,19 +4078,21 @@ try_again: wake_later = FALSE; array = os_aio_ibuf_array; - } else if (mode == OS_AIO_LOG) { - + break; + case OS_AIO_LOG: array = os_aio_log_array; - } else if (mode == OS_AIO_SYNC) { + break; + case OS_AIO_SYNC: array = os_aio_sync_array; #if defined(LINUX_NATIVE_AIO) /* In Linux native AIO we don't use sync IO array. */ ut_a(!srv_use_native_aio); #endif /* LINUX_NATIVE_AIO */ - } else { - array = NULL; /* Eliminate compiler warning */ + break; + default: ut_error; + array = NULL; /* Eliminate compiler warning */ } slot = os_aio_array_reserve_slot(type, array, message1, message2, file, @@ -4253,11 +4255,17 @@ os_aio_windows_handle( INFINITE); } - if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - os_thread_exit(NULL); + os_mutex_enter(array->mutex); + + if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS + && array->n_reserved == 0) { + *message1 = NULL; + *message2 = NULL; + os_mutex_exit(array->mutex); + return(TRUE); } - os_mutex_enter(array->mutex); + ut_a(i >= WAIT_OBJECT_0 && i <= WAIT_OBJECT_0 + n); slot = os_aio_array_get_nth_slot(array, i + segment * n); @@ -4403,14 +4411,6 @@ os_aio_linux_collect( retry: - /* Go down if we are in shutdown mode. - In case of srv_fast_shutdown == 2, there may be pending - IO requests but that should be OK as we essentially treat - that as a crash of InnoDB. */ - if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - os_thread_exit(NULL); - } - /* Initialize the events. The timeout value is arbitrary. We probably need to experiment with it a little. */ memset(events, 0, sizeof(*events) * seg_size); @@ -4419,76 +4419,72 @@ retry: ret = io_getevents(io_ctx, 1, seg_size, events, &timeout); - /* This error handling is for any error in collecting the - IO requests. The errors, if any, for any particular IO - request are simply passed on to the calling routine. */ - - /* Not enough resources! Try again. */ - if (ret == -EAGAIN) { - goto retry; - } - - /* Interrupted! I have tested the behaviour in case of an - interrupt. If we have some completed IOs available then - the return code will be the number of IOs. We get EINTR only - if there are no completed IOs and we have been interrupted. */ - if (ret == -EINTR) { - goto retry; - } - - /* No pending request! Go back and check again. */ - if (ret == 0) { - goto retry; - } + if (ret > 0) { + for (i = 0; i < ret; i++) { + os_aio_slot_t* slot; + struct iocb* control; - /* All other errors! should cause a trap for now. */ - if (UNIV_UNLIKELY(ret < 0)) { - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: unexpected ret_code[%d] from" - " io_getevents()!\n", ret); - ut_error; - } + control = (struct iocb *)events[i].obj; + ut_a(control != NULL); - ut_a(ret > 0); + slot = (os_aio_slot_t *) control->data; - for (i = 0; i < ret; i++) { - os_aio_slot_t* slot; - struct iocb* control; + /* Some sanity checks. */ + ut_a(slot != NULL); + ut_a(slot->reserved); - control = (struct iocb *)events[i].obj; - ut_a(control != NULL); +#if defined(UNIV_AIO_DEBUG) + fprintf(stderr, + "io_getevents[%c]: slot[%p] ctx[%p]" + " seg[%lu]\n", + (slot->type == OS_FILE_WRITE) ? 'w' : 'r', + slot, io_ctx, segment); +#endif - slot = (os_aio_slot_t *) control->data; + /* We are not scribbling previous segment. */ + ut_a(slot->pos >= start_pos); - /* Some sanity checks. */ - ut_a(slot != NULL); - ut_a(slot->reserved); + /* We have not overstepped to next segment. */ + ut_a(slot->pos < end_pos); -#if defined(UNIV_AIO_DEBUG) - fprintf(stderr, - "io_getevents[%c]: slot[%p] ctx[%p]" - " seg[%lu]\n", - (slot->type == OS_FILE_WRITE) ? 'w' : 'r', - slot, io_ctx, segment); -#endif + /* Mark this request as completed. The error handling + will be done in the calling function. */ + os_mutex_enter(array->mutex); + slot->n_bytes = events[i].res; + slot->ret = events[i].res2; + slot->io_already_done = TRUE; + os_mutex_exit(array->mutex); + } + return; + } - /* We are not scribbling previous segment. */ - ut_a(slot->pos >= start_pos); + if (UNIV_UNLIKELY(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS)) { + return; + } - /* We have not overstepped to next segment. */ - ut_a(slot->pos < end_pos); + /* This error handling is for any error in collecting the + IO requests. The errors, if any, for any particular IO + request are simply passed on to the calling routine. */ - /* Mark this request as completed. The error handling - will be done in the calling function. */ - os_mutex_enter(array->mutex); - slot->n_bytes = events[i].res; - slot->ret = events[i].res2; - slot->io_already_done = TRUE; - os_mutex_exit(array->mutex); + switch (ret) { + case -EAGAIN: + /* Not enough resources! Try again. */ + case -EINTR: + /* Interrupted! I have tested the behaviour in case of an + interrupt. If we have some completed IOs available then + the return code will be the number of IOs. We get EINTR only + if there are no completed IOs and we have been interrupted. */ + case 0: + /* No pending request! Go back and check again. */ + goto retry; } - return; + /* All other errors should cause a trap for now. */ + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: unexpected ret_code[%d] from io_getevents()!\n", + ret); + ut_error; } /**********************************************************************//** @@ -4532,20 +4528,35 @@ os_aio_linux_handle( /* Loop until we have found a completed request. */ for (;;) { + ibool any_reserved = FALSE; os_mutex_enter(array->mutex); for (i = 0; i < n; ++i) { slot = os_aio_array_get_nth_slot( - array, i + segment * n); - if (slot->reserved && slot->io_already_done) { + array, i + segment * n); + if (!slot->reserved) { + continue; + } else if (slot->io_already_done) { /* Something for us to work on. */ goto found; + } else { + any_reserved = TRUE; } } os_mutex_exit(array->mutex); - /* We don't have any completed request. - Wait for some request. Note that we return + /* There is no completed request. + If there is no pending request at all, + and the system is being shut down, exit. */ + if (UNIV_UNLIKELY + (!any_reserved + && srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS)) { + *message1 = NULL; + *message2 = NULL; + return(TRUE); + } + + /* Wait for some request. Note that we return from wait iff we have found a request. */ srv_set_io_thread_op_info(global_seg, @@ -4641,6 +4652,7 @@ os_aio_simulated_handle( byte* combined_buf; byte* combined_buf2; ibool ret; + ibool any_reserved; ulint n; ulint i; @@ -4671,18 +4683,21 @@ restart: goto recommended_sleep; } - os_mutex_enter(array->mutex); - srv_set_io_thread_op_info(global_segment, "looking for i/o requests (b)"); /* Check if there is a slot for which the i/o has already been done */ + any_reserved = FALSE; + + os_mutex_enter(array->mutex); for (i = 0; i < n; i++) { slot = os_aio_array_get_nth_slot(array, i + segment * n); - if (slot->reserved && slot->io_already_done) { + if (!slot->reserved) { + continue; + } else if (slot->io_already_done) { if (os_aio_print_debug) { fprintf(stderr, @@ -4694,9 +4709,23 @@ restart: ret = TRUE; goto slot_io_done; + } else { + any_reserved = TRUE; } } + /* There is no completed request. + If there is no pending request at all, + and the system is being shut down, exit. */ + if (UNIV_UNLIKELY + (!any_reserved + && srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS)) { + os_mutex_exit(array->mutex); + *message1 = NULL; + *message2 = NULL; + return(TRUE); + } + n_consecutive = 0; /* If there are at least 2 seconds old requests, then pick the oldest diff --git a/storage/innobase/os/os0sync.c b/storage/innobase/os/os0sync.c index b461f9b7c78..41a19843812 100644 --- a/storage/innobase/os/os0sync.c +++ b/storage/innobase/os/os0sync.c @@ -558,10 +558,7 @@ os_event_free( } /**********************************************************//** -Waits for an event object until it is in the signaled state. If -srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS this also exits the -waiting thread when the event becomes signaled (or immediately if the -event is already in the signaled state). +Waits for an event object until it is in the signaled state. Typically, if the event has been signalled after the os_event_reset() we'll return immediately because event->is_set == TRUE. @@ -586,8 +583,6 @@ os_event_wait_low( returned by previous call of os_event_reset(). */ { - ib_int64_t old_signal_count; - #ifdef __WIN__ if(!srv_use_native_conditions) { DWORD err; @@ -600,43 +595,25 @@ os_event_wait_low( err = WaitForSingleObject(event->handle, INFINITE); ut_a(err == WAIT_OBJECT_0); - - if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - os_thread_exit(NULL); - } return; } #endif - os_fast_mutex_lock(&(event->os_mutex)); + os_fast_mutex_lock(&event->os_mutex); - if (reset_sig_count) { - old_signal_count = reset_sig_count; - } else { - old_signal_count = event->signal_count; + if (!reset_sig_count) { + reset_sig_count = event->signal_count; } - for (;;) { - if (event->is_set == TRUE - || event->signal_count != old_signal_count) { - - os_fast_mutex_unlock(&(event->os_mutex)); - - if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - - os_thread_exit(NULL); - } - /* Ok, we may return */ - - return; - } - + while (!event->is_set && event->signal_count == reset_sig_count) { os_cond_wait(&(event->cond_var), &(event->os_mutex)); /* Solaris manual said that spurious wakeups may occur: we have to check if the event really has been signaled after we came here to wait */ } + + os_fast_mutex_unlock(&event->os_mutex); } /**********************************************************//** @@ -657,7 +634,6 @@ os_event_wait_time_low( { ibool timed_out = FALSE; - ib_int64_t old_signal_count; #ifdef __WIN__ DWORD time_in_ms; @@ -727,15 +703,12 @@ os_event_wait_time_low( os_fast_mutex_lock(&event->os_mutex); - if (reset_sig_count) { - old_signal_count = reset_sig_count; - } else { - old_signal_count = event->signal_count; + if (!reset_sig_count) { + reset_sig_count = event->signal_count; } do { - if (event->is_set == TRUE - || event->signal_count != old_signal_count) { + if (event->is_set || event->signal_count != reset_sig_count) { break; } @@ -753,11 +726,6 @@ os_event_wait_time_low( os_fast_mutex_unlock(&event->os_mutex); - if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - - os_thread_exit(NULL); - } - return(timed_out ? OS_SYNC_TIME_EXCEEDED : 0); } diff --git a/storage/innobase/os/os0thread.c b/storage/innobase/os/os0thread.c index adc876be5d5..12b6805d98e 100644 --- a/storage/innobase/os/os0thread.c +++ b/storage/innobase/os/os0thread.c @@ -220,21 +220,6 @@ os_thread_exit( } /*****************************************************************//** -Returns handle to the current thread. -@return current thread handle */ -UNIV_INTERN -os_thread_t -os_thread_get_curr(void) -/*====================*/ -{ -#ifdef __WIN__ - return(GetCurrentThread()); -#else - return(pthread_self()); -#endif -} - -/*****************************************************************//** Advises the os to give up remainder of the thread's time slice. */ UNIV_INTERN void @@ -274,81 +259,3 @@ os_thread_sleep( select(0, NULL, NULL, NULL, &t); #endif } - -#ifndef UNIV_HOTBACKUP -/******************************************************************//** -Sets a thread priority. */ -UNIV_INTERN -void -os_thread_set_priority( -/*===================*/ - os_thread_t handle, /*!< in: OS handle to the thread */ - ulint pri) /*!< in: priority */ -{ -#ifdef __WIN__ - int os_pri; - - if (pri == OS_THREAD_PRIORITY_BACKGROUND) { - os_pri = THREAD_PRIORITY_BELOW_NORMAL; - } else if (pri == OS_THREAD_PRIORITY_NORMAL) { - os_pri = THREAD_PRIORITY_NORMAL; - } else if (pri == OS_THREAD_PRIORITY_ABOVE_NORMAL) { - os_pri = THREAD_PRIORITY_HIGHEST; - } else { - ut_error; - } - - ut_a(SetThreadPriority(handle, os_pri)); -#else - UT_NOT_USED(handle); - UT_NOT_USED(pri); -#endif -} - -/******************************************************************//** -Gets a thread priority. -@return priority */ -UNIV_INTERN -ulint -os_thread_get_priority( -/*===================*/ - os_thread_t handle __attribute__((unused))) - /*!< in: OS handle to the thread */ -{ -#ifdef __WIN__ - int os_pri; - ulint pri; - - os_pri = GetThreadPriority(handle); - - if (os_pri == THREAD_PRIORITY_BELOW_NORMAL) { - pri = OS_THREAD_PRIORITY_BACKGROUND; - } else if (os_pri == THREAD_PRIORITY_NORMAL) { - pri = OS_THREAD_PRIORITY_NORMAL; - } else if (os_pri == THREAD_PRIORITY_HIGHEST) { - pri = OS_THREAD_PRIORITY_ABOVE_NORMAL; - } else { - ut_error; - } - - return(pri); -#else - return(0); -#endif -} - -/******************************************************************//** -Gets the last operating system error code for the calling thread. -@return last error on Windows, 0 otherwise */ -UNIV_INTERN -ulint -os_thread_get_last_error(void) -/*==========================*/ -{ -#ifdef __WIN__ - return(GetLastError()); -#else - return(0); -#endif -} -#endif /* !UNIV_HOTBACKUP */ diff --git a/storage/innobase/row/row0sel.c b/storage/innobase/row/row0sel.c index 58043753cce..66aff528f38 100644 --- a/storage/innobase/row/row0sel.c +++ b/storage/innobase/row/row0sel.c @@ -1951,7 +1951,7 @@ stop_for_a_while: mtr_commit(&mtr); #ifdef UNIV_SYNC_DEBUG - ut_ad(sync_thread_levels_empty_gen(TRUE)); + ut_ad(sync_thread_levels_empty_except_dict()); #endif /* UNIV_SYNC_DEBUG */ err = DB_SUCCESS; goto func_exit; @@ -1971,7 +1971,7 @@ commit_mtr_for_a_while: mtr_has_extra_clust_latch = FALSE; #ifdef UNIV_SYNC_DEBUG - ut_ad(sync_thread_levels_empty_gen(TRUE)); + ut_ad(sync_thread_levels_empty_except_dict()); #endif /* UNIV_SYNC_DEBUG */ goto table_loop; @@ -1988,7 +1988,7 @@ lock_wait_or_error: mtr_commit(&mtr); #ifdef UNIV_SYNC_DEBUG - ut_ad(sync_thread_levels_empty_gen(TRUE)); + ut_ad(sync_thread_levels_empty_except_dict()); #endif /* UNIV_SYNC_DEBUG */ func_exit: @@ -3386,11 +3386,17 @@ row_search_for_mysql( "InnoDB: how you can resolve the problem.\n", prebuilt->table->name); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ return(DB_ERROR); } if (UNIV_UNLIKELY(!prebuilt->index_usable)) { +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ return(DB_MISSING_HISTORY); } @@ -4679,6 +4685,10 @@ func_exit: prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT; } } + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ return(err); } diff --git a/storage/innobase/srv/srv0srv.c b/storage/innobase/srv/srv0srv.c index dcf22656ad8..04503d25cd4 100644 --- a/storage/innobase/srv/srv0srv.c +++ b/storage/innobase/srv/srv0srv.c @@ -86,12 +86,6 @@ Created 10/8/1995 Heikki Tuuri #include "mysql/plugin.h" #include "mysql/service_thd_wait.h" -/* This is set to the MySQL server value for this variable. It is only -needed for FOREIGN KEY definition parsing since FOREIGN KEY names are not -stored in the server metadata. The server stores and enforces it for -regular database and table names.*/ -UNIV_INTERN uint srv_lower_case_table_names = 0; - /* The following counter is incremented whenever there is some user activity in the server */ UNIV_INTERN ulint srv_activity_count = 0; @@ -687,8 +681,6 @@ Unix.*/ /* Thread slot in the thread table */ struct srv_slot_struct{ - os_thread_id_t id; /*!< thread id */ - os_thread_t handle; /*!< thread handle */ unsigned type:1; /*!< thread type: user, utility etc. */ unsigned in_use:1; /*!< TRUE if this slot is in use */ unsigned suspended:1; /*!< TRUE if the thread is waiting @@ -887,8 +879,6 @@ srv_table_reserve_slot( slot->suspended = FALSE; slot->type = type; ut_ad(srv_slot_get_type(slot) == type); - slot->id = os_thread_get_curr_id(); - slot->handle = os_thread_get_curr(); return(slot); } @@ -907,7 +897,6 @@ srv_suspend_thread( ut_ad(mutex_own(&kernel_mutex)); ut_ad(slot->in_use); ut_ad(!slot->suspended); - ut_ad(slot->id == os_thread_get_curr_id()); if (srv_print_thread_releases) { fprintf(stderr, @@ -962,10 +951,9 @@ srv_release_threads( if (srv_print_thread_releases) { fprintf(stderr, - "Releasing thread %lu type %lu" + "Releasing thread type %lu" " from slot %lu\n", - (ulong) slot->id, (ulong) type, - (ulong) i); + (ulong) type, (ulong) i); } count++; @@ -1149,6 +1137,10 @@ srv_conc_enter_innodb( srv_conc_slot_t* slot = NULL; ulint i; +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ + if (trx->mysql_thd != NULL && thd_is_replication_slave_thread(trx->mysql_thd)) { @@ -1272,6 +1264,10 @@ retry: /* Go to wait for the event; when a thread leaves InnoDB it will release this thread */ + ut_ad(!trx->has_search_latch); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ trx->op_info = "waiting in InnoDB queue"; thd_wait_begin(trx->mysql_thd, THD_WAIT_USER_LOCK); @@ -1307,6 +1303,10 @@ srv_conc_force_enter_innodb( trx_t* trx) /*!< in: transaction object associated with the thread */ { +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ + if (UNIV_LIKELY(!srv_thread_concurrency)) { return; @@ -1378,6 +1378,10 @@ srv_conc_force_exit_innodb( if (slot != NULL) { os_event_set(slot->event); } + +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ } /*********************************************************************//** @@ -1389,6 +1393,10 @@ srv_conc_exit_innodb( trx_t* trx) /*!< in: transaction object associated with the thread */ { +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ + if (trx->n_tickets_to_enter_innodb > 0) { /* We will pretend the thread is still inside InnoDB though it now leaves the InnoDB engine. In this way we save @@ -1505,10 +1513,9 @@ srv_table_reserve_slot_for_mysql(void) slot = srv_mysql_table + i; fprintf(stderr, - "Slot %lu: thread id %lu, type %lu," + "Slot %lu: thread type %lu," " in use %lu, susp %lu, time %lu\n", (ulong) i, - (ulong) os_thread_pf(slot->id), (ulong) slot->type, (ulong) slot->in_use, (ulong) slot->suspended, @@ -1525,8 +1532,6 @@ srv_table_reserve_slot_for_mysql(void) ut_a(slot->in_use == FALSE); slot->in_use = TRUE; - slot->id = os_thread_get_curr_id(); - slot->handle = os_thread_get_curr(); return(slot); } @@ -1613,17 +1618,6 @@ srv_suspend_mysql_thread( mutex_exit(&kernel_mutex); - if (trx->declared_to_be_inside_innodb) { - - was_declared_inside_innodb = TRUE; - - /* We must declare this OS thread to exit InnoDB, since a - possible other thread holding a lock which this thread waits - for must be allowed to enter, sooner or later */ - - srv_conc_force_exit_innodb(trx); - } - had_dict_lock = trx->dict_operation_lock_mode; switch (had_dict_lock) { @@ -1651,12 +1645,34 @@ srv_suspend_mysql_thread( ut_a(trx->dict_operation_lock_mode == 0); + if (trx->declared_to_be_inside_innodb) { + + was_declared_inside_innodb = TRUE; + + /* We must declare this OS thread to exit InnoDB, since a + possible other thread holding a lock which this thread waits + for must be allowed to enter, sooner or later */ + + srv_conc_force_exit_innodb(trx); + } + /* Suspend this thread and wait for the event. */ thd_wait_begin(trx->mysql_thd, THD_WAIT_ROW_LOCK); os_event_wait(event); thd_wait_end(trx->mysql_thd); +#ifdef UNIV_SYNC_DEBUG + ut_ad(!sync_thread_levels_nonempty_trx(trx->has_search_latch)); +#endif /* UNIV_SYNC_DEBUG */ + + if (was_declared_inside_innodb) { + + /* Return back inside InnoDB */ + + srv_conc_force_enter_innodb(trx); + } + /* After resuming, reacquire the data dictionary latch if necessary. */ @@ -1672,13 +1688,6 @@ srv_suspend_mysql_thread( break; } - if (was_declared_inside_innodb) { - - /* Return back inside InnoDB */ - - srv_conc_force_enter_innodb(trx); - } - mutex_enter(&kernel_mutex); /* Release the slot for others to use */ @@ -3067,11 +3076,7 @@ suspend_thread: os_event_wait(slot->event); if (srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS) { - /* This is only extra safety, the thread should exit - already when the event wait ends */ - os_thread_exit(NULL); - } /* When there is user activity, InnoDB will set the event and the diff --git a/storage/innobase/srv/srv0start.c b/storage/innobase/srv/srv0start.c index 79eae610a05..cf11e75b8e0 100644 --- a/storage/innobase/srv/srv0start.c +++ b/storage/innobase/srv/srv0start.c @@ -2097,17 +2097,6 @@ innobase_shutdown_for_mysql(void) The step 1 is the real InnoDB shutdown. The remaining steps 2 - ... just free data structures after the shutdown. */ - - if (srv_fast_shutdown == 2) { - ut_print_timestamp(stderr); - fprintf(stderr, - " InnoDB: MySQL has requested a very fast shutdown" - " without flushing " - "the InnoDB buffer pool to data files." - " At the next mysqld startup " - "InnoDB will do a crash recovery!\n"); - } - logs_empty_and_mark_files_at_shutdown(); if (srv_conc_n_threads != 0) { @@ -2122,17 +2111,9 @@ innobase_shutdown_for_mysql(void) srv_shutdown_state = SRV_SHUTDOWN_EXIT_THREADS; - /* In a 'very fast' shutdown, we do not need to wait for these threads - to die; all which counts is that we flushed the log; a 'very fast' - shutdown is essentially a crash. */ - - if (srv_fast_shutdown == 2) { - return(DB_SUCCESS); - } - /* All threads end up waiting for certain events. Put those events - to the signaled state. Then the threads will exit themselves in - os_thread_event_wait(). */ + to the signaled state. Then the threads will exit themselves after + os_event_wait(). */ for (i = 0; i < 1000; i++) { /* NOTE: IF YOU CREATE THREADS IN INNODB, YOU MUST EXIT THEM diff --git a/storage/innobase/sync/sync0sync.c b/storage/innobase/sync/sync0sync.c index 23fd64cc5ed..0b56e736209 100644 --- a/storage/innobase/sync/sync0sync.c +++ b/storage/innobase/sync/sync0sync.c @@ -189,12 +189,12 @@ UNIV_INTERN sync_array_t* sync_primary_wait_array; /** This variable is set to TRUE when sync_init is called */ UNIV_INTERN ibool sync_initialized = FALSE; +#ifdef UNIV_SYNC_DEBUG /** An acquired mutex or rw-lock and its level in the latching order */ typedef struct sync_level_struct sync_level_t; /** Mutexes or rw-locks held by a thread */ typedef struct sync_thread_struct sync_thread_t; -#ifdef UNIV_SYNC_DEBUG /** The latch levels currently owned by threads are stored in this data structure; the size of this array is OS_THREAD_MAX_N */ @@ -221,7 +221,6 @@ UNIV_INTERN mysql_pfs_key_t mutex_list_mutex_key; #ifdef UNIV_SYNC_DEBUG /** Latching order checks start when this is set TRUE */ UNIV_INTERN ibool sync_order_checks_on = FALSE; -#endif /* UNIV_SYNC_DEBUG */ /** Number of slots reserved for each OS thread in the sync level array */ static const ulint SYNC_THREAD_N_LEVELS = 10000; @@ -258,6 +257,7 @@ struct sync_level_struct{ the ordinal value of the next free element */ }; +#endif /* UNIV_SYNC_DEBUG */ /******************************************************************//** Creates, or rather, initializes a mutex object in a specified memory @@ -1020,9 +1020,7 @@ void* sync_thread_levels_nonempty_gen( /*============================*/ ibool dict_mutex_allowed) /*!< in: TRUE if dictionary mutex is - allowed to be owned by the thread, - also purge_is_running mutex is - allowed */ + allowed to be owned by the thread */ { ulint i; sync_arr_t* arr; @@ -1069,14 +1067,61 @@ sync_thread_levels_nonempty_gen( } /******************************************************************//** -Checks that the level array for the current thread is empty. -@return TRUE if empty */ +Checks if the level array for the current thread is empty, +except for the btr_search_latch. +@return a latch, or NULL if empty except the exceptions specified below */ UNIV_INTERN -ibool -sync_thread_levels_empty(void) -/*==========================*/ +void* +sync_thread_levels_nonempty_trx( +/*============================*/ + ibool has_search_latch) + /*!< in: TRUE if and only if the thread + is supposed to hold btr_search_latch */ { - return(sync_thread_levels_empty_gen(FALSE)); + ulint i; + sync_arr_t* arr; + sync_thread_t* thread_slot; + + if (!sync_order_checks_on) { + + return(NULL); + } + + ut_a(!has_search_latch + || sync_thread_levels_contains(SYNC_SEARCH_SYS)); + + mutex_enter(&sync_thread_mutex); + + thread_slot = sync_thread_level_arrays_find_slot(); + + if (thread_slot == NULL) { + + mutex_exit(&sync_thread_mutex); + + return(NULL); + } + + arr = thread_slot->levels; + + for (i = 0; i < arr->n_elems; ++i) { + const sync_level_t* slot; + + slot = &arr->elems[i]; + + if (slot->latch != NULL + && (!has_search_latch + || slot->level != SYNC_SEARCH_SYS)) { + + mutex_exit(&sync_thread_mutex); + ut_error; + + return(slot->latch); + } + } + + mutex_exit(&sync_thread_mutex); + + return(NULL); } /******************************************************************//** diff --git a/storage/innobase/trx/trx0roll.c b/storage/innobase/trx/trx0roll.c index f9d421e3705..b55471959ce 100644 --- a/storage/innobase/trx/trx0roll.c +++ b/storage/innobase/trx/trx0roll.c @@ -460,10 +460,6 @@ trx_rollback_active( (ulong) rows_to_undo, unit); mutex_exit(&kernel_mutex); - trx->mysql_thread_id = os_thread_get_curr_id(); - - trx->mysql_process_no = os_proc_get_number(); - if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) { row_mysql_lock_data_dictionary(trx); dictionary_locked = TRUE; diff --git a/storage/innobase/trx/trx0sys.c b/storage/innobase/trx/trx0sys.c index 7af9fbb1af8..8e595353024 100644 --- a/storage/innobase/trx/trx0sys.c +++ b/storage/innobase/trx/trx0sys.c @@ -37,6 +37,7 @@ Created 3/26/1996 Heikki Tuuri #include "trx0rseg.h" #include "trx0undo.h" #include "srv0srv.h" +#include "srv0start.h" #include "trx0purge.h" #include "log0log.h" #include "log0recv.h" @@ -1617,10 +1618,12 @@ void trx_sys_close(void) /*===============*/ { + trx_t* trx; trx_rseg_t* rseg; read_view_t* view; ut_ad(trx_sys != NULL); + ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); /* Check that all read views are closed except read view owned by a purge. */ @@ -1652,6 +1655,13 @@ trx_sys_close(void) mem_free(trx_doublewrite); trx_doublewrite = NULL; + /* Only prepared transactions may be left in the system. Free them. */ + ut_a(UT_LIST_GET_LEN(trx_sys->trx_list) == trx_n_prepared); + + while ((trx = UT_LIST_GET_FIRST(trx_sys->trx_list)) != NULL) { + trx_free_prepared(trx); + } + /* There can't be any active transactions. */ rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); diff --git a/storage/innobase/trx/trx0trx.c b/storage/innobase/trx/trx0trx.c index 0d01bedc4fb..7b99b86c732 100644 --- a/storage/innobase/trx/trx0trx.c +++ b/storage/innobase/trx/trx0trx.c @@ -50,6 +50,9 @@ UNIV_INTERN sess_t* trx_dummy_sess = NULL; /** Number of transactions currently allocated for MySQL: protected by the kernel mutex */ UNIV_INTERN ulint trx_n_mysql_transactions = 0; +/** Number of transactions currently in the XA PREPARED state: protected by +the kernel mutex */ +UNIV_INTERN ulint trx_n_prepared = 0; #ifdef UNIV_PFS_MUTEX /* Key to register the mutex with performance schema */ @@ -214,10 +217,6 @@ trx_allocate_for_mysql(void) mutex_exit(&kernel_mutex); - trx->mysql_thread_id = os_thread_get_curr_id(); - - trx->mysql_process_no = os_proc_get_number(); - return(trx); } @@ -342,6 +341,60 @@ trx_free( } /********************************************************************//** +At shutdown, frees a transaction object that is in the PREPARED state. */ +UNIV_INTERN +void +trx_free_prepared( +/*==============*/ + trx_t* trx) /*!< in, own: trx object */ +{ + ut_ad(mutex_own(&kernel_mutex)); + ut_a(trx->conc_state == TRX_PREPARED); + ut_a(trx->magic_n == TRX_MAGIC_N); + + /* Prepared transactions are sort of active; they allow + ROLLBACK and COMMIT operations. Because the system does not + contain any other transactions than prepared transactions at + the shutdown stage and because a transaction cannot become + PREPARED while holding locks, it is safe to release the locks + held by PREPARED transactions here at shutdown.*/ + lock_release_off_kernel(trx); + + trx_undo_free_prepared(trx); + + mutex_free(&trx->undo_mutex); + + if (trx->undo_no_arr) { + trx_undo_arr_free(trx->undo_no_arr); + } + + ut_a(UT_LIST_GET_LEN(trx->signals) == 0); + ut_a(UT_LIST_GET_LEN(trx->reply_signals) == 0); + + ut_a(trx->wait_lock == NULL); + ut_a(UT_LIST_GET_LEN(trx->wait_thrs) == 0); + + ut_a(!trx->has_search_latch); + + ut_a(trx->dict_operation_lock_mode == 0); + + if (trx->lock_heap) { + mem_heap_free(trx->lock_heap); + } + + if (trx->global_read_view_heap) { + mem_heap_free(trx->global_read_view_heap); + } + + ut_a(ib_vector_is_empty(trx->autoinc_locks)); + ib_vector_free(trx->autoinc_locks); + + UT_LIST_REMOVE(trx_list, trx_sys->trx_list, trx); + + mem_free(trx); +} + +/********************************************************************//** Frees a transaction object for MySQL. */ UNIV_INTERN void @@ -471,6 +524,7 @@ trx_lists_init_at_db_start(void) if (srv_force_recovery == 0) { trx->conc_state = TRX_PREPARED; + trx_n_prepared++; } else { fprintf(stderr, "InnoDB: Since" @@ -547,6 +601,7 @@ trx_lists_init_at_db_start(void) trx->conc_state = TRX_PREPARED; + trx_n_prepared++; } else { fprintf(stderr, "InnoDB: Since" @@ -881,6 +936,11 @@ trx_commit_off_kernel( ut_ad(trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED); ut_ad(mutex_own(&kernel_mutex)); + if (UNIV_UNLIKELY(trx->conc_state == TRX_PREPARED)) { + ut_a(trx_n_prepared > 0); + trx_n_prepared--; + } + /* The following assignment makes the transaction committed in memory and makes its changes to data visible to other transactions. NOTE that there is a small discrepancy from the strict formal @@ -1729,12 +1789,6 @@ trx_print( fprintf(f, " state %lu", (ulong) trx->conc_state); } -#ifdef UNIV_LINUX - fprintf(f, ", process no %lu", trx->mysql_process_no); -#endif - fprintf(f, ", OS thread id %lu", - (ulong) os_thread_pf(trx->mysql_thread_id)); - if (*trx->op_info) { putc(' ', f); fputs(trx->op_info, f); @@ -1911,6 +1965,7 @@ trx_prepare_off_kernel( /*--------------------------------------*/ trx->conc_state = TRX_PREPARED; + trx_n_prepared++; /*--------------------------------------*/ if (lsn) { @@ -2087,7 +2142,8 @@ trx_get_trx_by_xid( of gtrid_length+bqual_length bytes should be the same */ - if (trx->conc_state == TRX_PREPARED + if (trx->is_recovered + && trx->conc_state == TRX_PREPARED && xid->gtrid_length == trx->xid.gtrid_length && xid->bqual_length == trx->xid.bqual_length && memcmp(xid->data, trx->xid.data, diff --git a/storage/innobase/trx/trx0undo.c b/storage/innobase/trx/trx0undo.c index 4021a2ed573..070d6332a4f 100644 --- a/storage/innobase/trx/trx0undo.c +++ b/storage/innobase/trx/trx0undo.c @@ -36,6 +36,7 @@ Created 3/26/1996 Heikki Tuuri #include "trx0rseg.h" #include "trx0trx.h" #include "srv0srv.h" +#include "srv0start.h" #include "trx0rec.h" #include "trx0purge.h" @@ -1975,4 +1976,31 @@ trx_undo_insert_cleanup( mutex_exit(&(rseg->mutex)); } + +/********************************************************************//** +At shutdown, frees the undo logs of a PREPARED transaction. */ +UNIV_INTERN +void +trx_undo_free_prepared( +/*===================*/ + trx_t* trx) /*!< in/out: PREPARED transaction */ +{ + mutex_enter(&trx->rseg->mutex); + + ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS); + + if (trx->update_undo) { + ut_a(trx->update_undo->state == TRX_UNDO_PREPARED); + UT_LIST_REMOVE(undo_list, trx->rseg->update_undo_list, + trx->update_undo); + trx_undo_mem_free(trx->update_undo); + } + if (trx->insert_undo) { + ut_a(trx->insert_undo->state == TRX_UNDO_PREPARED); + UT_LIST_REMOVE(undo_list, trx->rseg->insert_undo_list, + trx->insert_undo); + trx_undo_mem_free(trx->insert_undo); + } + mutex_exit(&trx->rseg->mutex); +} #endif /* !UNIV_HOTBACKUP */ diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 16457088f9c..428601b7f6f 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -643,9 +643,10 @@ ha_myisam::ha_myisam(handlerton *hton, TABLE_SHARE *table_arg) can_enable_indexes(1) {} -handler *ha_myisam::clone(MEM_ROOT *mem_root) +handler *ha_myisam::clone(const char *name, MEM_ROOT *mem_root) { - ha_myisam *new_handler= static_cast <ha_myisam *>(handler::clone(mem_root)); + ha_myisam *new_handler= static_cast <ha_myisam *>(handler::clone(name, + mem_root)); if (new_handler) new_handler->file->state= file->state; return new_handler; diff --git a/storage/myisam/ha_myisam.h b/storage/myisam/ha_myisam.h index 46b732512ac..f84b7bdfe66 100644 --- a/storage/myisam/ha_myisam.h +++ b/storage/myisam/ha_myisam.h @@ -50,7 +50,7 @@ class ha_myisam: public handler public: ha_myisam(handlerton *hton, TABLE_SHARE *table_arg); ~ha_myisam() {} - handler *clone(MEM_ROOT *mem_root); + handler *clone(const char *name, MEM_ROOT *mem_root); const char *table_type() const { return "MyISAM"; } const char *index_type(uint key_number); const char **bas_ext() const; diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 79696cad721..38238a8f6c6 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -681,7 +681,7 @@ CPP_UNNAMED_NS_END @return A cloned handler instance. */ -handler *ha_myisammrg::clone(MEM_ROOT *mem_root) +handler *ha_myisammrg::clone(const char *name, MEM_ROOT *mem_root) { MYRG_TABLE *u_table,*newu_table; ha_myisammrg *new_handler= @@ -702,8 +702,8 @@ handler *ha_myisammrg::clone(MEM_ROOT *mem_root) return NULL; } - if (new_handler->ha_open(table, table->s->normalized_path.str, table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED)) + if (new_handler->ha_open(table, name, table->db_stat, + HA_OPEN_IGNORE_IF_LOCKED)) { delete new_handler; return NULL; diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h index cae4bd17f50..1b8e1038c3f 100644 --- a/storage/myisammrg/ha_myisammrg.h +++ b/storage/myisammrg/ha_myisammrg.h @@ -110,7 +110,7 @@ public: int add_children_list(void); int attach_children(void); int detach_children(void); - virtual handler *clone(MEM_ROOT *mem_root); + virtual handler *clone(const char *name, MEM_ROOT *mem_root); int close(void); int write_row(uchar * buf); int update_row(const uchar * old_data, uchar * new_data); diff --git a/vio/viosocket.c b/vio/viosocket.c index 0f3a32d62ae..9ad7134312c 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -22,6 +22,10 @@ #include "vio_priv.h" +#ifdef FIONREAD_IN_SYS_FILIO +# include <sys/filio.h> +#endif + int vio_errno(Vio *vio __attribute__((unused))) { return socket_errno; /* On Win32 this mapped to WSAGetLastError() */ @@ -583,13 +587,13 @@ static my_bool socket_poll_read(my_socket sd, uint timeout) static my_bool socket_peek_read(Vio *vio, uint *bytes) { -#ifdef __WIN__ +#if defined(_WIN32) int len; if (ioctlsocket(vio->sd, FIONREAD, &len)) return TRUE; *bytes= len; return FALSE; -#elif FIONREAD_IN_SYS_IOCTL +#elif defined(FIONREAD_IN_SYS_IOCTL) || defined(FIONREAD_IN_SYS_FILIO) int len; if (ioctl(vio->sd, FIONREAD, &len) < 0) return TRUE; |