From 1d0a11fd159fb05cd1c5c553b152c3848ceb552f Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Thu, 11 Nov 2010 09:46:49 +0000 Subject: [PATCH 01/43] Bug#55436: buffer overflow in debug binary of dbug_buff in Field_new_decimal::store_value There were some misunderstandings about parameters pertaining to buffer-size. Patches fixes the reported off by one and clarifies the documentation. --- mysql-test/r/type_newdecimal.result | 13 +++++++++++++ mysql-test/t/type_newdecimal.test | 14 ++++++++++++++ sql/field.cc | 6 +++--- sql/my_decimal.cc | 11 ++++++----- sql/my_decimal.h | 3 ++- strings/decimal.c | 5 +++-- 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/mysql-test/r/type_newdecimal.result b/mysql-test/r/type_newdecimal.result index 70ee3a56cf3..c301a7dd629 100644 --- a/mysql-test/r/type_newdecimal.result +++ b/mysql-test/r/type_newdecimal.result @@ -1913,4 +1913,17 @@ group by PAY.id + 1; mult v_net_with_discount v_total 1.0000 27.18 27.180000 DROP TABLE currencies, payments, sub_tasks; +# +# Bug#55436: buffer overflow in debug binary of dbug_buff in +# Field_new_decimal::store_value +# +SET SQL_MODE=''; +CREATE TABLE t1(f1 DECIMAL(44,24)) ENGINE=MYISAM; +INSERT INTO t1 SET f1 = -64878E-85; +Warnings: +Note 1265 Data truncated for column 'f1' at row 1 +SELECT f1 FROM t1; +f1 +0.000000000000000000000000 +DROP TABLE IF EXISTS t1; End of 5.1 tests diff --git a/mysql-test/t/type_newdecimal.test b/mysql-test/t/type_newdecimal.test index 2cf7ab8fbdf..31a8808da55 100644 --- a/mysql-test/t/type_newdecimal.test +++ b/mysql-test/t/type_newdecimal.test @@ -1510,5 +1510,19 @@ group by PAY.id + 1; DROP TABLE currencies, payments, sub_tasks; +--echo # +--echo # Bug#55436: buffer overflow in debug binary of dbug_buff in +--echo # Field_new_decimal::store_value +--echo # + +# this threw memory warnings on Windows. Also make sure future changes +# don't change these results, as per usual. +SET SQL_MODE=''; +CREATE TABLE t1(f1 DECIMAL(44,24)) ENGINE=MYISAM; +INSERT INTO t1 SET f1 = -64878E-85; +SELECT f1 FROM t1; +DROP TABLE IF EXISTS t1; + + --echo End of 5.1 tests diff --git a/sql/field.cc b/sql/field.cc index c887a5f1c9b..cb23ae4fe9f 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2583,7 +2583,7 @@ bool Field_new_decimal::store_value(const my_decimal *decimal_value) DBUG_ENTER("Field_new_decimal::store_value"); #ifndef DBUG_OFF { - char dbug_buff[DECIMAL_MAX_STR_LENGTH+1]; + char dbug_buff[DECIMAL_MAX_STR_LENGTH+2]; DBUG_PRINT("enter", ("value: %s", dbug_decimal_as_string(dbug_buff, decimal_value))); } #endif @@ -2598,7 +2598,7 @@ bool Field_new_decimal::store_value(const my_decimal *decimal_value) } #ifndef DBUG_OFF { - char dbug_buff[DECIMAL_MAX_STR_LENGTH+1]; + char dbug_buff[DECIMAL_MAX_STR_LENGTH+2]; DBUG_PRINT("info", ("saving with precision %d scale: %d value %s", (int)precision, (int)dec, dbug_decimal_as_string(dbug_buff, decimal_value))); @@ -2673,7 +2673,7 @@ int Field_new_decimal::store(const char *from, uint length, } #ifndef DBUG_OFF - char dbug_buff[DECIMAL_MAX_STR_LENGTH+1]; + char dbug_buff[DECIMAL_MAX_STR_LENGTH+2]; DBUG_PRINT("enter", ("value: %s", dbug_decimal_as_string(dbug_buff, &decimal_value))); #endif diff --git a/sql/my_decimal.cc b/sql/my_decimal.cc index 3aa01880b83..a38dc341684 100644 --- a/sql/my_decimal.cc +++ b/sql/my_decimal.cc @@ -95,10 +95,11 @@ int my_decimal2string(uint mask, const my_decimal *d, UNSIGNED. Hence the buffer for a ZEROFILLed value is the length the user requested, plus one for a possible decimal point, plus one if the user only wanted decimal places, but we force a leading - zero on them. Because the type is implicitly UNSIGNED, we do not - need to reserve a character for the sign. For all other cases, - fixed_prec will be 0, and my_decimal_string_length() will be called - instead to calculate the required size of the buffer. + zero on them, plus one for the '\0' terminator. Because the type + is implicitly UNSIGNED, we do not need to reserve a character for + the sign. For all other cases, fixed_prec will be 0, and + my_decimal_string_length() will be called instead to calculate the + required size of the buffer. */ int length= (fixed_prec ? (fixed_prec + ((fixed_prec == fixed_dec) ? 1 : 0) + 1) @@ -275,7 +276,7 @@ print_decimal_buff(const my_decimal *dec, const uchar* ptr, int length) const char *dbug_decimal_as_string(char *buff, const my_decimal *val) { - int length= DECIMAL_MAX_STR_LENGTH; + int length= DECIMAL_MAX_STR_LENGTH + 1; /* minimum size for buff */ if (!val) return "NULL"; (void)decimal2string((decimal_t*) val, buff, &length, 0,0,0); diff --git a/sql/my_decimal.h b/sql/my_decimal.h index 21669e82c44..2c13142bb60 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -55,7 +55,7 @@ C_MODE_END /** maximum length of string representation (number of maximum decimal - digits + 1 position for sign + 1 position for decimal point) + digits + 1 position for sign + 1 position for decimal point, no terminator) */ #define DECIMAL_MAX_STR_LENGTH (DECIMAL_MAX_POSSIBLE_PRECISION + 2) @@ -212,6 +212,7 @@ inline uint32 my_decimal_precision_to_length(uint precision, uint8 scale, inline int my_decimal_string_length(const my_decimal *d) { + /* length of string representation including terminating '\0' */ return decimal_string_size(d); } diff --git a/strings/decimal.c b/strings/decimal.c index bda296ce832..c91a5d1a7ec 100644 --- a/strings/decimal.c +++ b/strings/decimal.c @@ -320,8 +320,8 @@ int decimal_actual_fraction(decimal_t *from) from - value to convert to - points to buffer where string representation should be stored - *to_len - in: size of to buffer - out: length of the actually written string + *to_len - in: size of to buffer (incl. terminating '\0') + out: length of the actually written string (excl. '\0') fixed_precision - 0 if representation can be variable length and fixed_decimals will not be checked in this case. Put number as with fixed point position with this @@ -338,6 +338,7 @@ int decimal2string(decimal_t *from, char *to, int *to_len, int fixed_precision, int fixed_decimals, char filler) { + /* {intg_len, frac_len} output widths; {intg, frac} places in input */ int len, intg, frac= from->frac, i, intg_len, frac_len, fill; /* number digits before decimal point */ int fixed_intg= (fixed_precision ? From fb18601ee8c2e6cf8d2a9bfbaf6f7ca317b982af Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Tue, 8 Feb 2011 17:36:25 +0200 Subject: [PATCH 02/43] Bug #59815: Missing License information with enterprise GPL packages on behalf of Kent: Include the README into the binary packages --- scripts/make_win_bin_dist | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/make_win_bin_dist b/scripts/make_win_bin_dist index c1d01a0342d..7859f42ca29 100755 --- a/scripts/make_win_bin_dist +++ b/scripts/make_win_bin_dist @@ -198,6 +198,7 @@ cp Docs/INSTALL-BINARY $DESTDIR/Docs/ cp Docs/manual.chm $DESTDIR/Docs/ || /bin/true cp ChangeLog $DESTDIR/Docs/ || /bin/true cp support-files/my-*.ini $DESTDIR/ +cp README $DESTDIR/ if [ -f COPYING ] ; then cp COPYING $DESTDIR/ From d5ffcb42350efa6aff05815b237183836bcc778d Mon Sep 17 00:00:00 2001 From: Jonathan Perkin Date: Fri, 11 Feb 2011 11:32:03 +0100 Subject: [PATCH 03/43] Raise version number after cloning 5.1.56 --- configure.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.in b/configure.in index 6d5bc07ba9a..dc944386f22 100644 --- a/configure.in +++ b/configure.in @@ -12,7 +12,7 @@ dnl dnl When changing the major version number please also check the switch dnl statement in mysqlbinlog::check_master_version(). You may also need dnl to update version.c in ndb. -AC_INIT([MySQL Server], [5.1.56], [], [mysql]) +AC_INIT([MySQL Server], [5.1.57], [], [mysql]) AC_CONFIG_SRCDIR([sql/mysqld.cc]) AC_CANONICAL_SYSTEM From 9afa024034cff75cca62b5c5939da1ad01d32330 Mon Sep 17 00:00:00 2001 From: Jonathan Perkin Date: Fri, 11 Feb 2011 11:50:37 +0100 Subject: [PATCH 04/43] Raise version number after cloning 5.5.10 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 17335ff3fc5..ca1578a8642 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=5 -MYSQL_VERSION_PATCH=10 +MYSQL_VERSION_PATCH=11 MYSQL_VERSION_EXTRA= From a6ea6dc217f8ed4c4ff4a6f44e091801fed0420c Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Thu, 24 Feb 2011 12:23:38 +0100 Subject: [PATCH 05/43] Bug#11767480 - SPATIAL INDEXES ON NON-SPATIAL COLUMNS CAUSE CRASHES. This is a backport of the patch for MySQL Bug#50574. Adding a SPATIAL INDEX on non-geometrical columns caused a segmentation fault when the table was subsequently inserted into. A test was added in mysql_prepare_create_table to explicitly check whether non-geometrical columns are used in a spatial index, and throw an error if so. For MySQL 5.5 and later, a new and more meaningful error message was introduced. For 5.1, we (re-)use an existing error code. --- mysql-test/r/gis.result | 33 +++++++++++++++++++++++++++++ mysql-test/t/gis.test | 47 +++++++++++++++++++++++++++++++++++++++++ sql/sql_table.cc | 22 +++++++++++++------ 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result index a9beb9631ae..151d0cfffa1 100644 --- a/mysql-test/r/gis.result +++ b/mysql-test/r/gis.result @@ -1034,4 +1034,37 @@ p NULL NULL drop table t1; +CREATE TABLE t0 (a BINARY(32) NOT NULL); +CREATE SPATIAL INDEX i on t0 (a); +ERROR HY000: Incorrect arguments to SPATIAL INDEX +INSERT INTO t0 VALUES (1); +CREATE TABLE t1( +col0 BINARY NOT NULL, +col2 TIMESTAMP, +SPATIAL INDEX i1 (col0) +) ENGINE=MyISAM; +ERROR HY000: Incorrect arguments to SPATIAL INDEX +CREATE TABLE t1 ( +col0 BINARY NOT NULL, +col2 TIMESTAMP +) ENGINE=MyISAM; +CREATE SPATIAL INDEX idx0 ON t1(col0); +ERROR HY000: Incorrect arguments to SPATIAL INDEX +ALTER TABLE t1 ADD SPATIAL INDEX i1 (col0); +ERROR HY000: Incorrect arguments to SPATIAL INDEX +CREATE TABLE t2 ( +col0 INTEGER NOT NULL, +col1 POINT, +col2 POINT +); +CREATE SPATIAL INDEX idx0 ON t2 (col1, col2); +ERROR HY000: Incorrect arguments to SPATIAL INDEX +CREATE TABLE t3 ( +col0 INTEGER NOT NULL, +col1 POINT, +col2 LINESTRING, +SPATIAL INDEX i1 (col1, col2) +); +ERROR HY000: Incorrect arguments to SPATIAL INDEX +DROP TABLE t0, t1, t2; End of 5.1 tests diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test index bdbbfc7c064..b50df062d7e 100644 --- a/mysql-test/t/gis.test +++ b/mysql-test/t/gis.test @@ -754,4 +754,51 @@ insert into t1 values (geomfromtext("point(1 0)")); select * from (select polygon(t1.a) as p from t1 order by t1.a) d; drop table t1; +# +# Bug#11767480 - SPATIAL INDEXES ON NON-SPATIAL COLUMNS CAUSE CRASHES. +# +CREATE TABLE t0 (a BINARY(32) NOT NULL); +--error ER_WRONG_ARGUMENTS +CREATE SPATIAL INDEX i on t0 (a); +INSERT INTO t0 VALUES (1); + +--error ER_WRONG_ARGUMENTS +CREATE TABLE t1( + col0 BINARY NOT NULL, + col2 TIMESTAMP, + SPATIAL INDEX i1 (col0) +) ENGINE=MyISAM; + +# Test other ways to add indices +CREATE TABLE t1 ( + col0 BINARY NOT NULL, + col2 TIMESTAMP +) ENGINE=MyISAM; + +--error ER_WRONG_ARGUMENTS +CREATE SPATIAL INDEX idx0 ON t1(col0); + +--error ER_WRONG_ARGUMENTS +ALTER TABLE t1 ADD SPATIAL INDEX i1 (col0); + +CREATE TABLE t2 ( + col0 INTEGER NOT NULL, + col1 POINT, + col2 POINT +); + +--error ER_WRONG_ARGUMENTS +CREATE SPATIAL INDEX idx0 ON t2 (col1, col2); + +--error ER_WRONG_ARGUMENTS +CREATE TABLE t3 ( + col0 INTEGER NOT NULL, + col1 POINT, + col2 LINESTRING, + SPATIAL INDEX i1 (col1, col2) +); + +# cleanup +DROP TABLE t0, t1, t2; + --echo End of 5.1 tests diff --git a/sql/sql_table.cc b/sql/sql_table.cc index b919ea9eae7..c5fc037a49e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1,4 +1,4 @@ -/* Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc. +/* Copyright 2000-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 @@ -11,7 +11,8 @@ 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 */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + MA 02110-1301 USA */ /* drop and alter of tables */ @@ -3184,11 +3185,20 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { column->length*= sql_field->charset->mbmaxlen; - if (key->type == Key::SPATIAL && column->length) + if (key->type == Key::SPATIAL) { - my_error(ER_WRONG_SUB_KEY, MYF(0)); - DBUG_RETURN(TRUE); - } + if (column->length) + { + my_error(ER_WRONG_SUB_KEY, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (!f_is_geom(sql_field->pack_flag)) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX"); + DBUG_RETURN(TRUE); + } + } if (f_is_blob(sql_field->pack_flag) || (f_is_geom(sql_field->pack_flag) && key->type != Key::SPATIAL)) From 929d13ca4977ed1fa3dcbe6628c393ffc148004f Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Thu, 3 Mar 2011 11:43:07 +0300 Subject: [PATCH 06/43] BUG#11764339 - valgrind errors, random data when returning ordered data from archive tables Archive was using wrong memory address to check if field is NULL (after filesort, when reading record again). --- mysql-test/r/archive.result | 16 ++++++++++++++++ mysql-test/t/archive.test | 15 +++++++++++++++ storage/archive/ha_archive.cc | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/archive.result b/mysql-test/r/archive.result index f90bcb521e1..15ded03f414 100644 --- a/mysql-test/r/archive.result +++ b/mysql-test/r/archive.result @@ -12756,3 +12756,19 @@ a 1 2 DROP TABLE t1; +# +# BUG#57162 - valgrind errors, random data when returning +# ordered data from archive tables +# +SET sort_buffer_size=32804; +CREATE TABLE t1(a INT, b CHAR(255), c CHAR(255), d CHAR(255), +e CHAR(255), f INT) ENGINE=ARCHIVE DEFAULT CHARSET utf8; +INSERT INTO t1 VALUES(-1,'b','c','d','e',1); +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT t1.* FROM t1,t1 t2,t1 t3,t1 t4,t1 t5,t1 t6; +SELECT * FROM t1 ORDER BY f LIMIT 1; +a b c d e f +-1 b c d e 1 +DROP TABLE t1; +SET sort_buffer_size=DEFAULT; diff --git a/mysql-test/t/archive.test b/mysql-test/t/archive.test index 7084f5f540e..98ba5e03ede 100644 --- a/mysql-test/t/archive.test +++ b/mysql-test/t/archive.test @@ -1678,3 +1678,18 @@ SELECT * FROM t1; REPAIR TABLE t1 EXTENDED; SELECT * FROM t1; DROP TABLE t1; + +--echo # +--echo # BUG#57162 - valgrind errors, random data when returning +--echo # ordered data from archive tables +--echo # +SET sort_buffer_size=32804; +CREATE TABLE t1(a INT, b CHAR(255), c CHAR(255), d CHAR(255), + e CHAR(255), f INT) ENGINE=ARCHIVE DEFAULT CHARSET utf8; +INSERT INTO t1 VALUES(-1,'b','c','d','e',1); +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT t1.* FROM t1,t1 t2,t1 t3,t1 t4,t1 t5,t1 t6; +SELECT * FROM t1 ORDER BY f LIMIT 1; +DROP TABLE t1; +SET sort_buffer_size=DEFAULT; diff --git a/storage/archive/ha_archive.cc b/storage/archive/ha_archive.cc index 988337ec50e..9740bf934cd 100644 --- a/storage/archive/ha_archive.cc +++ b/storage/archive/ha_archive.cc @@ -1111,7 +1111,7 @@ int ha_archive::unpack_row(azio_stream *file_to_read, uchar *record) ptr+= table->s->null_bytes; for (Field **field=table->field ; *field ; field++) { - if (!((*field)->is_null())) + if (!((*field)->is_null_in_record(record))) { ptr= (*field)->unpack(record + (*field)->offset(table->record[0]), ptr); } From 0e640801776173236c6eee57779a70474e27afee Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Wed, 9 Mar 2011 17:21:22 +0200 Subject: [PATCH 07/43] Fixed a wrong error code in gis.test --- mysql-test/r/gis.result | 2 +- mysql-test/t/gis.test | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result index d700865d5f3..acb55d225a7 100644 --- a/mysql-test/r/gis.result +++ b/mysql-test/r/gis.result @@ -1040,7 +1040,7 @@ drop table t1; # create table t1(a char(32) not null) engine=myisam; create spatial index i on t1 (a); -ERROR HY000: Can't create table '#sql-temporary' (errno: 140) +ERROR HY000: Incorrect arguments to SPATIAL INDEX drop table t1; CREATE TABLE t0 (a BINARY(32) NOT NULL); CREATE SPATIAL INDEX i on t0 (a); diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test index f81cd4a72a6..f8cec14d9ae 100644 --- a/mysql-test/t/gis.test +++ b/mysql-test/t/gis.test @@ -760,8 +760,7 @@ drop table t1; --echo # on char > 31 bytes". --echo # create table t1(a char(32) not null) engine=myisam; ---replace_regex /'[^']*test\.#sql-[0-9a-f_]*'/'#sql-temporary'/ ---error ER_CANT_CREATE_TABLE +--error ER_WRONG_ARGUMENTS create spatial index i on t1 (a); drop table t1; From feeac7d1a4a804a69009d7b21c9e46c8758d0daa Mon Sep 17 00:00:00 2001 From: Kristofer Pettersson Date: Fri, 11 Mar 2011 15:10:15 +0100 Subject: [PATCH 08/43] Certain fields in the protcol required a strict formatting. If off bound values were sent to the server this could under some circumstances lead to a crash on the Windows platform. --- sql/sql_connect.cc | 175 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 149 insertions(+), 26 deletions(-) diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index 9fa6966baa2..4c4f30600de 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -630,6 +630,94 @@ bool init_new_connection_handler_thread() return 0; } +#ifndef EMBEDDED_LIBRARY +/** + Get a null character terminated string from a user-supplied buffer. + + @param buffer[in, out] Pointer to the buffer to be scanned. + @param max_bytes_available[in, out] Limit the bytes to scan. + @param string_length[out] The number of characters scanned not including + the null character. + + @remark The string_length does not include the terminating null character. + However, after the call, the buffer is increased by string_length+1 + bytes, beyond the null character if there still available bytes to + scan. + + @return pointer to beginning of the string scanned. + @retval NULL The buffer content is malformed +*/ + +static +char *get_null_terminated_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + char *str= (char *)memchr(*buffer, '\0', *max_bytes_available); + + if (str == NULL) + return NULL; + + *string_length= (size_t)(str - *buffer); + *max_bytes_available-= *string_length + 1; + str= *buffer; + *buffer += *string_length + 1; + + return str; +} + + +/** + Get a length encoded string from a user-supplied buffer. + + @param buffer[in, out] The buffer to scan; updates position after scan. + @param max_bytes_available[in, out] Limit the number of bytes to scan + @param string_length[out] Number of characters scanned + + @remark In case the length is zero, then the total size of the string is + considered to be 1 byte; the size byte. + + @return pointer to first byte after the header in buffer. + @retval NULL The buffer content is malformed +*/ + +static +char *get_length_encoded_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + if (*max_bytes_available == 0) + return NULL; + + /* Do double cast to prevent overflow from signed / unsigned conversion */ + size_t str_len= (size_t)(unsigned char)**buffer; + + /* + If the length encoded string has the length 0 + the total size of the string is only one byte long (the size byte) + */ + if (str_len == 0) + { + ++*buffer; + *string_length= 0; + /* + Return a pointer to the 0 character so the return value will be + an empty string. + */ + return *buffer-1; + } + + if (str_len >= *max_bytes_available) + return NULL; + + char *str= *buffer+1; + *string_length= str_len; + *max_bytes_available-= *string_length + 1; + *buffer+= *string_length + 1; + return str; +} + + /* Perform handshake, authorize client and update thd ACL variables. @@ -643,7 +731,6 @@ bool init_new_connection_handler_thread() > 0 error code (not sent to user) */ -#ifndef EMBEDDED_LIBRARY static int check_connection(THD *thd) { uint connect_errors= 0; @@ -831,7 +918,7 @@ static int check_connection(THD *thd) } #endif /* HAVE_OPENSSL */ - if (end >= (char*) net->read_pos+ pkt_len +2) + if (end > (char *)net->read_pos + pkt_len) { inc_host_errors(&thd->remote.sin_addr); my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); @@ -843,39 +930,75 @@ static int check_connection(THD *thd) if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && opt_using_transactions) net->return_status= &thd->server_status; - - char *user= end; - char *passwd= strend(user)+1; - uint user_len= passwd - user - 1; - char *db= passwd; - char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 - char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 - uint dummy_errors; - + /* - Old clients send null-terminated string as password; new clients send - the size (1 byte) + string (not null-terminated). Hence in case of empty - password both send '\0'. - - This strlen() can't be easily deleted without changing protocol. - - Cast *passwd to an unsigned char, so that it doesn't extend the sign for - *passwd > 127 and become 2**32-127+ after casting to uint. + In order to safely scan a head for '\0' string terminators + we must keep track of how many bytes remain in the allocated + buffer or we might read past the end of the buffer. */ - uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar)(*passwd++) : strlen(passwd); - db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? - db + passwd_len + 1 : 0; - /* strlen() can't be easily deleted without changing protocol */ - uint db_len= db ? strlen(db) : 0; + size_t bytes_remaining_in_packet= pkt_len - (end - (char *)net->read_pos); - if (passwd + passwd_len + db_len > (char *)net->read_pos + pkt_len) + size_t user_len; + char *user= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &user_len); + if (user == NULL) { inc_host_errors(&thd->remote.sin_addr); my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); return 1; } + /* + Old clients send a null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + */ + size_t passwd_len= 0; + char *passwd= NULL; + + if (thd->client_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + 4.1+ password. First byte is password length. + */ + passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet, + &passwd_len); + } + else + { + /* + Old passwords are zero terminated strings. + */ + passwd= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &passwd_len); + } + + if (passwd == NULL) + { + inc_host_errors(&thd->remote.sin_addr); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); + return 1; + } + + size_t db_len= 0; + char *db= NULL; + + if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB) + { + db= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &db_len); + if (db == NULL) + { + inc_host_errors(&thd->remote.sin_addr); + my_error(ER_HANDSHAKE_ERROR, MYF(0), thd->main_security_ctx.host_or_ip); + return 1; + } + } + + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + /* Since 4.1 all database names are stored in utf8 */ if (db) { From 9f29134a4a319bebc2a7fc83439c747faa1ba26d Mon Sep 17 00:00:00 2001 From: Kristofer Pettersson Date: Mon, 14 Mar 2011 11:13:11 +0100 Subject: [PATCH 09/43] Certain fields in the protcol required a strict formatting. If off bound values were sent to the server this could under some circumstances lead to a crash on the Windows platform. --- sql/sql_acl.cc | 176 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 32 deletions(-) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index fae609c0d4d..09609a7fa0c 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -8397,6 +8397,92 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) DBUG_RETURN (0); } +/** + Get a null character terminated string from a user-supplied buffer. + + @param buffer[in, out] Pointer to the buffer to be scanned. + @param max_bytes_available[in, out] Limit the bytes to scan. + @param string_length[out] The number of characters scanned not including + the null character. + + @remark The string_length does not include the terminating null character. + However, after the call, the buffer is increased by string_length+1 + bytes, beyond the null character if there still available bytes to + scan. + + @return pointer to beginning of the string scanned. + @retval NULL The buffer content is malformed +*/ + +static +char *get_null_terminated_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + char *str= (char *)memchr(*buffer, '\0', *max_bytes_available); + + if (str == NULL) + return NULL; + + *string_length= (size_t)(str - *buffer); + *max_bytes_available-= *string_length + 1; + str= *buffer; + *buffer += *string_length + 1; + + return str; +} + +/** + Get a length encoded string from a user-supplied buffer. + + @param buffer[in, out] The buffer to scan; updates position after scan. + @param max_bytes_available[in, out] Limit the number of bytes to scan + @param string_length[out] Number of characters scanned + + @remark In case the length is zero, then the total size of the string is + considered to be 1 byte; the size byte. + + @return pointer to first byte after the header in buffer. + @retval NULL The buffer content is malformed +*/ + +static +char *get_length_encoded_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + if (*max_bytes_available == 0) + return NULL; + + /* Do double cast to prevent overflow from signed / unsigned conversion */ + size_t str_len= (size_t)(unsigned char)**buffer; + + /* + If the length encoded string has the length 0 + the total size of the string is only one byte long (the size byte) + */ + if (str_len == 0) + { + ++*buffer; + *string_length= 0; + /* + Return a pointer to the 0 character so the return value will be + an empty string. + */ + return *buffer-1; + } + + if (str_len >= *max_bytes_available) + return NULL; + + char *str= *buffer+1; + *string_length= str_len; + *max_bytes_available-= *string_length + 1; + *buffer+= *string_length + 1; + return str; +} + + /* the packet format is described in send_client_reply_packet() */ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, uchar **buff, ulong pkt_len) @@ -8461,50 +8547,76 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, } #endif - if (end >= (char*) net->read_pos + pkt_len + 2) + if (end > (char *)net->read_pos + pkt_len) return packet_error; if ((mpvio->client_capabilities & CLIENT_TRANSACTIONS) && opt_using_transactions) net->return_status= mpvio->server_status; + + /* + In order to safely scan a head for '\0' string terminators + we must keep track of how many bytes remain in the allocated + buffer or we might read past the end of the buffer. + */ + size_t bytes_remaining_in_packet= pkt_len - (end - (char *)net->read_pos); - char *user= end; - char *passwd= strend(user) + 1; - uint user_len= passwd - user - 1, db_len; - char *db= passwd; - char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 - char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 - uint dummy_errors; + size_t user_len; + char *user= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &user_len); + if (user == NULL) + return packet_error; /* - Old clients send null-terminated string as password; new clients send + Old clients send a null-terminated string as password; new clients send the size (1 byte) + string (not null-terminated). Hence in case of empty password both send '\0'. - - This strlen() can't be easily deleted without changing protocol. - - Cast *passwd to an unsigned char, so that it doesn't extend the sign for - *passwd > 127 and become 2**32-127+ after casting to uint. */ - uint passwd_len= mpvio->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar) (*passwd++) : strlen(passwd); - - if (mpvio->client_capabilities & CLIENT_CONNECT_WITH_DB) + size_t passwd_len= 0; + char *passwd= NULL; + + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) { - db= db + passwd_len + 1; - /* strlen() can't be easily deleted without changing protocol */ - db_len= strlen(db); + /* + 4.1+ password. First byte is password length. + */ + passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet, + &passwd_len); } else { - db= 0; - db_len= 0; + /* + Old passwords are zero terminated strings. + */ + passwd= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &passwd_len); } - if (passwd + passwd_len + db_len > (char *) net->read_pos + pkt_len) + if (passwd == NULL) return packet_error; - char *client_plugin= passwd + passwd_len + (db ? db_len + 1 : 0); + size_t db_len= 0; + char *db= NULL; + + if (mpvio->client_capabilities & CLIENT_CONNECT_WITH_DB) + { + db= get_null_terminated_string(&end, &bytes_remaining_in_packet, + &db_len); + if (db == NULL) + return packet_error; + } + + size_t client_plugin_len= 0; + char *client_plugin= get_null_terminated_string(&end, + &bytes_remaining_in_packet, + &client_plugin_len); + if (client_plugin == NULL) + client_plugin= &empty_c_string[0]; + + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + /* Since 4.1 all database names are stored in utf8 */ if (db) @@ -8550,18 +8662,18 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, if (find_mpvio_user(mpvio)) return packet_error; - if (mpvio->client_capabilities & CLIENT_PLUGIN_AUTH) - { - if ((client_plugin + strlen(client_plugin)) > - (char *) net->read_pos + pkt_len) - return packet_error; - } - else + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) { + /* + An old client is connecting + */ if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) client_plugin= native_password_plugin_name.str; else { + /* + A really old client is connecting + */ client_plugin= old_password_plugin_name.str; /* For a passwordless accounts we use native_password_plugin. From 2de6586287658f871a69167f6365d65051b5b7bc Mon Sep 17 00:00:00 2001 From: Jorgen Loland Date: Mon, 14 Mar 2011 14:30:36 +0100 Subject: [PATCH 10/43] BUG#11766234: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) FAILS IN SET_FIELD_ITERATOR (Former 59299) When a PROCEDURE does a natural join, resolving of which columns are used in the join is done only once; consecutive CALLs to the procedure will reuse this information: CREATE PROCEDURE proc() SELECT * FROM t1 NATURAL JOIN v1; CALL proc(); <- natural join columns resolved here CALL proc(); <- reuse resolved NJ columns from first CALL The second CALL knows that it can reuse the resolved NJ columns because the first CALL sets st_select_lex::first_natural_join_processing=false. The problem in this bug was that the table the view v1 depends on changed between CREATE PROCEDURE and the first CALL: CREATE PROCEDURE... ALTER TABLE t2 CHANGE COLUMN a b CHAR; CALL proc(); <- error when resolving natural join columns CALL proc(); <- tries to reuse from first CALL => crash The fix for this bug is to set first_natural_join_processing= FALSE iff the natural join columns resolving was successful. --- mysql-test/r/sp.result | 18 ++++++++++++++++++ mysql-test/t/sp.test | 22 ++++++++++++++++++++++ sql/sql_base.cc | 15 ++++++--------- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 243bfb6c07d..f1c86d016e2 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -7452,4 +7452,22 @@ c1 # Cleanup drop table t1; drop procedure p1; +# +# BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) +# FAILS IN SET_FIELD_ITERATOR +# +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +CREATE VIEW v1 AS SELECT a FROM t2; +CREATE PROCEDURE proc() SELECT * FROM t1 NATURAL JOIN v1; +ALTER TABLE t2 CHANGE COLUMN a b CHAR; + +CALL proc(); +ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them +CALL proc(); +ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them + +DROP TABLE t1,t2; +DROP VIEW v1; +DROP PROCEDURE proc; # End of 5.5 test diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index 11edeaf9811..cf89a6ece80 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -8713,4 +8713,26 @@ call p1(3, 2); drop table t1; drop procedure p1; +--echo # +--echo # BUG#11766234: 59299: ASSERT (TABLE_REF->TABLE || TABLE_REF->VIEW) +--echo # FAILS IN SET_FIELD_ITERATOR +--echo # + +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (a INT); +CREATE VIEW v1 AS SELECT a FROM t2; +CREATE PROCEDURE proc() SELECT * FROM t1 NATURAL JOIN v1; +ALTER TABLE t2 CHANGE COLUMN a b CHAR; + +--echo +--error ER_VIEW_INVALID +CALL proc(); +--error ER_VIEW_INVALID +CALL proc(); + +--echo +DROP TABLE t1,t2; +DROP VIEW v1; +DROP PROCEDURE proc; + --echo # End of 5.5 test diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7c020515f87..f37f800d091 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7594,9 +7594,10 @@ static bool setup_natural_join_row_types(THD *thd, List *from_clause, Name_resolution_context *context) { + DBUG_ENTER("setup_natural_join_row_types"); thd->where= "from clause"; if (from_clause->elements == 0) - return FALSE; /* We come here in the case of UNIONs. */ + DBUG_RETURN(false); /* We come here in the case of UNIONs. */ List_iterator_fast table_ref_it(*from_clause); TABLE_LIST *table_ref; /* Current table reference. */ @@ -7604,10 +7605,6 @@ static bool setup_natural_join_row_types(THD *thd, TABLE_LIST *left_neighbor; /* Table reference to the right of the current. */ TABLE_LIST *right_neighbor= NULL; - bool save_first_natural_join_processing= - context->select_lex->first_natural_join_processing; - - context->select_lex->first_natural_join_processing= FALSE; /* Note that tables in the list are in reversed order */ for (left_neighbor= table_ref_it++; left_neighbor ; ) @@ -7619,12 +7616,11 @@ static bool setup_natural_join_row_types(THD *thd, 1) for stored procedures, 2) for multitable update after lock failure and table reopening. */ - if (save_first_natural_join_processing) + if (context->select_lex->first_natural_join_processing) { - context->select_lex->first_natural_join_processing= FALSE; if (store_top_level_join_columns(thd, table_ref, left_neighbor, right_neighbor)) - return TRUE; + DBUG_RETURN(true); if (left_neighbor) { TABLE_LIST *first_leaf_on_the_right; @@ -7644,8 +7640,9 @@ static bool setup_natural_join_row_types(THD *thd, DBUG_ASSERT(right_neighbor); context->first_name_resolution_table= right_neighbor->first_leaf_for_name_resolution(); + context->select_lex->first_natural_join_processing= false; - return FALSE; + DBUG_RETURN (false); } From 405f7ca69a36a2b2d7b02bdb945f1e6879c5aaea Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Tue, 15 Mar 2011 13:19:30 +0200 Subject: [PATCH 11/43] Bug #11765023: 57934: DOS POSSIBLE SINCE BINARY CASTING DOESN'T ADHERE TO MAX_ALLOWED_PACKET Added a check for max_packet_length in CONVERT(, BINARY|CHAR). Added a test case. --- mysql-test/r/cast.result | 15 +++++++++++++++ mysql-test/t/cast.test | 14 ++++++++++++++ sql/item_timefunc.cc | 13 +++++++++++++ 3 files changed, 42 insertions(+) diff --git a/mysql-test/r/cast.result b/mysql-test/r/cast.result index dd61396e485..974a6bee63f 100644 --- a/mysql-test/r/cast.result +++ b/mysql-test/r/cast.result @@ -451,4 +451,19 @@ SELECT CONVERT(t2.a USING UTF8) FROM t1, t1 t2 LIMIT 1 1 1 DROP TABLE t1; +# +# Bug #11765023: 57934: DOS POSSIBLE SINCE BINARY CASTING +# DOESN'T ADHERE TO MAX_ALLOWED_PACKET +SET @@GLOBAL.max_allowed_packet=2048; +SELECT CONVERT('a', BINARY(2049)); +CONVERT('a', BINARY(2049)) +NULL +Warnings: +Warning 1301 Result of cast_as_binary() was larger than max_allowed_packet (2048) - truncated +SELECT CONVERT('a', CHAR(2049)); +CONVERT('a', CHAR(2049)) +NULL +Warnings: +Warning 1301 Result of cast_as_char() was larger than max_allowed_packet (2048) - truncated +SET @@GLOBAL.max_allowed_packet=default; End of 5.1 tests diff --git a/mysql-test/t/cast.test b/mysql-test/t/cast.test index 8e60d548c2f..426d7c7fdf2 100644 --- a/mysql-test/t/cast.test +++ b/mysql-test/t/cast.test @@ -282,5 +282,19 @@ SELECT 1 FROM ) AS s LIMIT 1; DROP TABLE t1; +--echo # +--echo # Bug #11765023: 57934: DOS POSSIBLE SINCE BINARY CASTING +--echo # DOESN'T ADHERE TO MAX_ALLOWED_PACKET + +SET @@GLOBAL.max_allowed_packet=2048; +# reconnect to make the new max packet size take effect +--connect (newconn, localhost, root,,) + +SELECT CONVERT('a', BINARY(2049)); +SELECT CONVERT('a', CHAR(2049)); + +connection default; +disconnect newconn; +SET @@GLOBAL.max_allowed_packet=default; --echo End of 5.1 tests diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 6335199b8de..74aae94b6f2 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -2444,6 +2444,19 @@ String *Item_char_typecast::val_str(String *str) String *res; uint32 length; + if (cast_length >= 0 && + ((unsigned) cast_length) > current_thd->variables.max_allowed_packet) + { + push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_ALLOWED_PACKET_OVERFLOWED, + ER(ER_WARN_ALLOWED_PACKET_OVERFLOWED), + cast_cs == &my_charset_bin ? + "cast_as_binary" : func_name(), + current_thd->variables.max_allowed_packet); + null_value= 1; + return 0; + } + if (!charset_conversion) { if (!(res= args[0]->val_str(str))) From f9756a6c16ea3343cef50ae8fafc0528896b0c6c Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Thu, 17 Mar 2011 13:29:59 +0200 Subject: [PATCH 12/43] Fixed a post-merge embedded compilation error --- sql/sql_acl.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 09609a7fa0c..9f8510cab03 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -8397,6 +8397,7 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) DBUG_RETURN (0); } +#ifndef EMBEDDED_LIBRARY /** Get a null character terminated string from a user-supplied buffer. @@ -8481,6 +8482,7 @@ char *get_length_encoded_string(char **buffer, *buffer+= *string_length + 1; return str; } +#endif /* the packet format is described in send_client_reply_packet() */ From 5a3586b1c31fefbb985f82a24e9dda875b423d7d Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Thu, 17 Mar 2011 14:07:18 +0200 Subject: [PATCH 13/43] Fixed the test cleanup code post-merge --- mysql-test/r/mysqldump.result | 1 + mysql-test/t/mysqldump.test | 1 + 2 files changed, 2 insertions(+) diff --git a/mysql-test/r/mysqldump.result b/mysql-test/r/mysqldump.result index 124a0d364ce..8f6add75fd3 100644 --- a/mysql-test/r/mysqldump.result +++ b/mysql-test/r/mysqldump.result @@ -4626,6 +4626,7 @@ DELIMITER ; /*!50003 SET collation_connection = @saved_col_connection */ ; ALTER DATABASE `test-database` CHARACTER SET utf8 COLLATE utf8_unicode_ci ; DROP DATABASE `test-database`; +USE test; # # End of 5.1 tests # diff --git a/mysql-test/t/mysqldump.test b/mysql-test/t/mysqldump.test index 5ed66646432..4eed3f1d4a6 100644 --- a/mysql-test/t/mysqldump.test +++ b/mysql-test/t/mysqldump.test @@ -2198,6 +2198,7 @@ ALTER DATABASE `test-database` CHARACTER SET utf8 COLLATE utf8_unicode_ci ; --exec $MYSQL_DUMP --quote-names --compact test-database DROP DATABASE `test-database`; +USE test; --echo # --echo # End of 5.1 tests From 4a6d020e8dda2c76d9902820606cd534324f3df7 Mon Sep 17 00:00:00 2001 From: Tor Didriksen Date: Thu, 24 Mar 2011 11:27:11 +0100 Subject: [PATCH 14/43] Bug#11829785 EXPLAIN EXTENDED CRASH WITH RIGHT OUTER JOIN, SUBQUERIES This is a backport of Bug #46860 Crash/segfault using EXPLAIN EXTENDED on query using UNION in subquery. --- mysql-test/r/explain.result | 15 ++++++++++++++- mysql-test/r/func_gconcat.result | 1 - mysql-test/r/subselect3.result | 3 --- mysql-test/t/explain.test | 23 ++++++++++++++++++++++- sql/sql_parse.cc | 6 +++++- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/mysql-test/r/explain.result b/mysql-test/r/explain.result index b9ae362f6cd..b8f791b27f4 100644 --- a/mysql-test/r/explain.result +++ b/mysql-test/r/explain.result @@ -180,7 +180,6 @@ ERROR 42000: Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP col SHOW WARNINGS; Level Code Message Error 1140 Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause -Note 1003 select 1 AS `1` from `test`.`t1` where ((...)) SET SESSION sql_mode=@old_sql_mode; DROP TABLE t1; End of 5.0 tests. @@ -318,3 +317,17 @@ id select_type table type possible_keys key key_len ref rows Extra DEALLOCATE PREPARE stmt; DROP TABLE t1; End of 5.1 tests. +# +# Bug#11829785 EXPLAIN EXTENDED CRASH WITH RIGHT OUTER JOIN, SUBQUERIES +# +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES (0), (0); +PREPARE s FROM +'EXPLAIN EXTENDED +SELECT SUBSTRING(1, (SELECT 1 FROM t1 a1 RIGHT OUTER JOIN t1 ON 0)) AS d +FROM t1 WHERE 0 > ANY (SELECT @a FROM t1)'; +EXECUTE s; +ERROR 21000: Subquery returns more than 1 row +DEALLOCATE PREPARE s; +DROP TABLE t1; +# diff --git a/mysql-test/r/func_gconcat.result b/mysql-test/r/func_gconcat.result index 01b93df6894..6c400a8ddcc 100644 --- a/mysql-test/r/func_gconcat.result +++ b/mysql-test/r/func_gconcat.result @@ -1056,7 +1056,6 @@ ERROR HY000: Only constant XPATH queries are supported SHOW WARNINGS; Level Code Message Error 1105 Only constant XPATH queries are supported -Note 1003 select updatexml('1',`test`.`t1`.`a`,'1') AS `UPDATEXML('1', a, '1')` from `test`.`t1` order by (select group_concat(1 separator ',') from `test`.`t1`) DROP TABLE t1; End of 5.1 tests DROP TABLE IF EXISTS t1, t2; diff --git a/mysql-test/r/subselect3.result b/mysql-test/r/subselect3.result index 2962a12d9a2..4e18a81b534 100644 --- a/mysql-test/r/subselect3.result +++ b/mysql-test/r/subselect3.result @@ -865,9 +865,6 @@ Level Code Message Note 1276 Field or reference 'test.t1.a' of SELECT #3 was resolved in SELECT #2 Note 1276 Field or reference 'test.t1.c' of SELECT #3 was resolved in SELECT #2 Error 1054 Unknown column 'c' in 'field list' -Note 1003 select `c` AS `c` from (select (select count(`test`.`t1`.`a`) from dual group by `c`) AS `(SELECT COUNT(a) FROM -(SELECT COUNT(b) FROM t1) AS x GROUP BY c -)` from `test`.`t1` group by `test`.`t1`.`b`) `y` DROP TABLE t1; End of 5.0 tests create table t0 (a int); diff --git a/mysql-test/t/explain.test b/mysql-test/t/explain.test index 931948b1b65..8376fdf1ad1 100644 --- a/mysql-test/t/explain.test +++ b/mysql-test/t/explain.test @@ -1,5 +1,5 @@ # -# Test of different EXPLAIN's +# Test of different EXPLAINs --disable_warnings drop table if exists t1; @@ -275,3 +275,24 @@ DEALLOCATE PREPARE stmt; DROP TABLE t1; --echo End of 5.1 tests. + +--echo # +--echo # Bug#11829785 EXPLAIN EXTENDED CRASH WITH RIGHT OUTER JOIN, SUBQUERIES +--echo # + +CREATE TABLE t1(a INT); + +INSERT INTO t1 VALUES (0), (0); + +PREPARE s FROM +'EXPLAIN EXTENDED +SELECT SUBSTRING(1, (SELECT 1 FROM t1 a1 RIGHT OUTER JOIN t1 ON 0)) AS d +FROM t1 WHERE 0 > ANY (SELECT @a FROM t1)'; + +--error ER_SUBQUERY_NO_1_ROW +EXECUTE s; + +DEALLOCATE PREPARE s; +DROP TABLE t1; + +--echo # diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 367699ea6cb..7d0186c0752 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4434,7 +4434,11 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) return 1; /* purecov: inspected */ thd->send_explain_fields(result); res= mysql_explain_union(thd, &thd->lex->unit, result); - if (lex->describe & DESCRIBE_EXTENDED) + /* + The code which prints the extended description is not robust + against malformed queries, so skip it if we have an error. + */ + if (!res && (lex->describe & DESCRIBE_EXTENDED)) { char buff[1024]; String str(buff,(uint32) sizeof(buff), system_charset_info); From e1fbf1b186134f428c56cdf148dd4ef58bc26665 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Thu, 7 Apr 2011 14:44:26 +0300 Subject: [PATCH 15/43] fixed a missing warning --- mysql-test/r/cast.result | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql-test/r/cast.result b/mysql-test/r/cast.result index 974a6bee63f..44d57055e7f 100644 --- a/mysql-test/r/cast.result +++ b/mysql-test/r/cast.result @@ -455,6 +455,8 @@ DROP TABLE t1; # Bug #11765023: 57934: DOS POSSIBLE SINCE BINARY CASTING # DOESN'T ADHERE TO MAX_ALLOWED_PACKET SET @@GLOBAL.max_allowed_packet=2048; +Warnings: +Warning 1105 The value of 'max_allowed_packet' should be no less than the value of 'net_buffer_length' SELECT CONVERT('a', BINARY(2049)); CONVERT('a', BINARY(2049)) NULL From b4a59e016b0a1987d9e488ad81a4bad9b8f32d88 Mon Sep 17 00:00:00 2001 From: "karen.langford@oracle.com" <> Date: Tue, 12 Apr 2011 01:36:38 +0200 Subject: [PATCH 16/43] Bug#11867664: Fix server crashes on update with join on partitioned table. --- sql/ha_partition.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index f55c48189fe..bd8e0d397c4 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -4317,7 +4317,8 @@ int ha_partition::index_read_idx_map(uchar *buf, uint index, break; } } - m_last_part= part; + if (part <= m_part_spec.end_part) + m_last_part= part; } else { @@ -6237,7 +6238,14 @@ void ha_partition::print_error(int error, myf errflag) { /* In case m_file has not been initialized, like in bug#42438 */ if (m_file) + { + if (m_last_part >= m_tot_parts) + { + DBUG_ASSERT(0); + m_last_part= 0; + } m_file[m_last_part]->print_error(error, errflag); + } else handler::print_error(error, errflag); } From 6593ca560b9867d185743d2b522672c8f973c139 Mon Sep 17 00:00:00 2001 From: "kevin.lewis@oracle.com" <> Date: Tue, 26 Apr 2011 12:55:52 -0500 Subject: [PATCH 17/43] Bug#60309 - Bug#12356829: MYSQL 5.5.9 FOR MAC OSX HAS BUG WITH FOREIGN KEY CONSTRAINTS The innoDB global variable srv_lower_case_table_names is set to the value of lower_case_table_names declared in mysqld.h server in ha_innodb.cc. Since this variable can change at runtime, it is reset for each handler call to ::create, ::open, ::rename_table & ::delete_table. But it is possible for tables to be implicitly opened before an explicit handler call is made when an engine is first started or restarted. I was able to reproduce that with the testcase in this patch on a version of InnoDB from 2 weeks ago. It seemed like the change buffer entries for the secondary key was getting put into pages after the restart. (But I am not sure, I did not write down the call stack while it was reproducing.) In the current code, the implicit open, which is actually a call to dict_load_foreigns(), does not occur with this testcase. The change is to replace srv_lower_case_table_names by an interface function in innodb.cc that retrieves the server global variable when it is needed. --- .../suite/innodb/r/innodb_bug60196.result | 44 ++++++++++++ .../suite/innodb/t/innodb_bug60196.test | 70 +++++++++++++++++++ storage/innobase/dict/dict0dict.c | 5 +- storage/innobase/dict/dict0load.c | 2 +- storage/innobase/dict/dict0mem.c | 17 +++-- storage/innobase/handler/ha_innodb.cc | 21 ++++-- storage/innobase/include/dict0mem.h | 10 ++- storage/innobase/include/ha_prototypes.h | 11 +++ storage/innobase/include/srv0srv.h | 3 - storage/innobase/srv/srv0srv.c | 6 -- 10 files changed, 157 insertions(+), 32 deletions(-) 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_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/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/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 27bf0754d36..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 @@ -6362,8 +6375,6 @@ err_col: col_len); } - srv_lower_case_table_names = lower_case_table_names; - error = row_create_table_for_mysql(table, trx); if (error == DB_DUPLICATE_KEY) { @@ -7223,8 +7234,6 @@ ha_innobase::delete_table( /* Drop the table in InnoDB */ - srv_lower_case_table_names = lower_case_table_names; - error = row_drop_table_for_mysql(norm_name, trx, thd_sql_command(thd) == SQLCOM_DROP_DB); @@ -7354,8 +7363,6 @@ innobase_rename_table( row_mysql_lock_data_dictionary(trx); } - srv_lower_case_table_names = lower_case_table_names; - error = row_rename_table_for_mysql( norm_from, norm_to, trx, lock_and_commit); 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/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/srv/srv0srv.c b/storage/innobase/srv/srv0srv.c index 789fe3bf36a..23e690b1105 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; From 6d7aceeead15eeefe720a2cbb1fe8f955ceb40fd Mon Sep 17 00:00:00 2001 From: Rafal Somla Date: Thu, 28 Apr 2011 21:17:29 +0200 Subject: [PATCH 18/43] Bug#11766631 (59780) - Move the client authentication_windows plugin into the server repository This patch adds client windows authentication plugin code to the client library libmysql (only on Windows platform). The plugin is compiled into the library and added to the list of built-in plugins. This way clients should be able to connect to a server which uses windows authentication plugin even as an SQL user which uses such authentication. Note: this makes the client library to depend on Secur32 Windows system library. When building clients, they must be linked against Secur32. Command mysql_config --libs correctly lists Secur32 as a required dependency. --- libmysql/CMakeLists.txt | 12 +- libmysql/authentication_win/CMakeLists.txt | 31 ++ libmysql/authentication_win/common.cc | 492 ++++++++++++++++++ libmysql/authentication_win/common.h | 290 +++++++++++ libmysql/authentication_win/handshake.cc | 288 ++++++++++ libmysql/authentication_win/handshake.h | 168 ++++++ .../authentication_win/handshake_client.cc | 285 ++++++++++ libmysql/authentication_win/log_client.cc | 55 ++ libmysql/authentication_win/plugin_client.cc | 58 +++ sql-common/client.c | 7 + 10 files changed, 1685 insertions(+), 1 deletion(-) create mode 100644 libmysql/authentication_win/CMakeLists.txt create mode 100644 libmysql/authentication_win/common.cc create mode 100644 libmysql/authentication_win/common.h create mode 100644 libmysql/authentication_win/handshake.cc create mode 100644 libmysql/authentication_win/handshake.h create mode 100644 libmysql/authentication_win/handshake_client.cc create mode 100644 libmysql/authentication_win/log_client.cc create mode 100644 libmysql/authentication_win/plugin_client.cc 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..82c9dffb06e --- /dev/null +++ b/libmysql/authentication_win/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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 + +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 // for ConvertSidToStringSid() +#include // for GetUserNameEx() + + +template <> void error_log_print(const char *fmt, ...); +template <> void error_log_print(const char *fmt, ...); +template <> void error_log_print(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..68f39fe04cc --- /dev/null +++ b/libmysql/authentication_win/common.h @@ -0,0 +1,290 @@ +/* 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 +#include +#include // for CtxtHandle +#include // 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; +}; + +#undef DBUG_ASSERT +#ifndef DBUG_OFF +#define DBUG_ASSERT(X) assert(X) +#else +#define DBUG_ASSERT(X) do {} while (0) +#endif + +extern "C" int opt_auth_win_client_log; + +/* + Note1: Double level of indirection in definition of DBUG_PRINT allows to + temporary redefine or disable DBUG_PRINT macro and then easily return to + the original definition (in terms of DBUG_PRINT_DO). + + Note2: DBUG_PRINT() can use printf-like format string like this: + + DBUG_PRINT(Keyword, ("format string", args)); + + The implementation should handle it correctly. Currently it is passed + to fprintf() (see debug_msg() function). +*/ + +#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) +#else +#define DBUG_PRINT_DO(K, M) do {} while (0) +#endif + +#undef DBUG_PRINT +#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg) + +/* + 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. +*/ +#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 + +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); +} + + +void error_log_vprint(error_log_level::type level, + const char *fmt, va_list args); + +template +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); + + +/** 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(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 + { + return pos < len() ? m_ptr[pos] : 0x00; + } + + bool is_null() const + { + return m_ptr == NULL; + } +}; + + +/** 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..4b33349d0f4 --- /dev/null +++ b/libmysql/authentication_win/handshake.cc @@ -0,0 +1,288 @@ +/* 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(Connection &con) +{ + unsigned round= 1; + + do { + // Read packet send by the peer + DBUG_PRINT("info", ("Waiting for packet")); + Blob packet= con.read(); + if (con.error() || packet.is_null()) + { + ERROR_LOG(ERROR, ("Error reading packet in round %d", 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", round)); + return 1; + } + + /* + If new data has been generated, send it to the peer. Otherwise + handshake must be completed. + */ + + if (!new_data.is_null()) + { + ++round; + DBUG_PRINT("info", ("Round %d started", round)); + + DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); + int ret= con.write(new_data); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing packet in round %d", 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", round)); + return 1; + } + + /* + To protect against malicious clients, break handshake exchange if + too many rounds. + */ + + if (round > MAX_HANDSHAKE_ROUNDS) + { + ERROR_LOG(ERROR, ("Authentication handshake could not be completed" + " after %d rounds", round)); + return 1; + } + + } while(!is_complete()); + + ERROR_LOG(INFO, ("Handshake completed after %d rounds", 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..bb88510a7f8 --- /dev/null +++ b/libmysql/authentication_win/handshake.h @@ -0,0 +1,168 @@ +/* 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(Connection &con); + + 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; + + /// 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; + +#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..e42f055da70 --- /dev/null +++ b/libmysql/authentication_win/handshake_client.cc @@ -0,0 +1,285 @@ +/* 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 // 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]; + +public: + + Handshake_client(const char *target, size_t len); + ~Handshake_client(); + + Blob first_packet(); + Blob process_data(const Blob&); +}; + + +/** + Create authentication handshake context for client. + + @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(const char *target, size_t len) +: Handshake(SSP_NAME, CLIENT), m_service_name(NULL) +{ + 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); +} + + +/** + Generate first packet to be sent to the server during packet exchange. + + This first packet should contain some data. In case of error a null blob + is returned and @c error() gives non-zero error code. + + @return Data to be sent in the first packet or null blob in case of error. +*/ + +Blob Handshake_client::first_packet() +{ + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + NULL, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + NULL, // 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(); +} + + +/** + 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. + + @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) +{ + Security_buffer input(data); + SECURITY_STATUS ret; + + m_output.free(); + + ret= InitializeSecurityContextW( + &m_cred, + &m_sctx, // partial context + m_service_name, // service name + ASC_REQ_ALLOCATE_MEMORY, // requested attributes + 0, // reserved + SECURITY_NETWORK_DREP, // data representation + &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) +{ + /* + 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. + + int ret; + Blob service_name= con.read(); + + if (con.error() || service_name.is_null()) + { + ERROR_LOG(ERROR, ("Error reading initial packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len())); + + // Create authentication handsake context using the given service name. + + Handshake_client hndshk(service_name[0] ? (char *)service_name.ptr() : NULL, + service_name.len()); + if (hndshk.error()) + { + ERROR_LOG(ERROR, ("Could not create authentication handshake context")); + return CR_ERROR; + } + + /* + The following packet exchange always starts with a packet sent by + the client. Send this first packet now. + */ + + { + Blob packet= hndshk.first_packet(); + if (hndshk.error() || packet.is_null()) + { + ERROR_LOG(ERROR, ("Could not generate first packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("Sending first packet of length %d", packet.len())); + + ret= con.write(packet); + if (ret) + { + ERROR_LOG(ERROR, ("Error writing first packet")); + return CR_ERROR; + } + DBUG_PRINT("info", ("First packet sent")); + } + + DBUG_ASSERT(!hndshk.error()); + + /* + If handshake is not yet complete and client expects a reply, + read and process packets from server until handshake is complete. + */ + if (!hndshk.is_complete()) + { + if (hndshk.packet_processing_loop(con)) + return CR_ERROR; + } + + DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); + + 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 +#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 +#include +#include +#include + +#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/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 }; From a6acc73bb18f3a662649c40a12c37063bfae37d6 Mon Sep 17 00:00:00 2001 From: Rafal Somla Date: Thu, 28 Apr 2011 21:39:42 +0200 Subject: [PATCH 19/43] BUG#11879051: FIRST REPLY LENGTH LIMIT (255) CAN BE VIOLATED BEFORE: First packet sent by client-side plugin (generated by Windows function InitializeSecurityContext()) could be longer than 255 bytes violating the limitation imposed by authentication protocol. AFTER: Handshake protocol is changed so that if first client's reply is longer than 254 bytes then it is be sent in 2 parts. However, for replies shorter than 255 bytes nothing changes. ADDITIONAL CHANGES: - The generic packet processing loop (Handshake::packet_processing_loop) has been refactored. Communication with the peer has been abstracted into virtual methods read/write_packet() which are implemented in client and server and transparently do the required splitting and gluing of packets. - Make it possible to optionally use dbug library in the plugin. - Add code for testing splitting of long first client reply. --- libmysql/authentication_win/CMakeLists.txt | 4 +- libmysql/authentication_win/common.h | 130 ++++++---- libmysql/authentication_win/handshake.cc | 29 ++- libmysql/authentication_win/handshake.h | 17 +- .../authentication_win/handshake_client.cc | 245 ++++++++++++------ 5 files changed, 284 insertions(+), 141 deletions(-) diff --git a/libmysql/authentication_win/CMakeLists.txt b/libmysql/authentication_win/CMakeLists.txt index 82c9dffb06e..80cd14780e6 100644 --- a/libmysql/authentication_win/CMakeLists.txt +++ b/libmysql/authentication_win/CMakeLists.txt @@ -18,7 +18,9 @@ # ADD_DEFINITIONS(-DSECURITY_WIN32) -ADD_DEFINITIONS(-DDEBUG_ERRROR_LOG) # no error logging in production builds +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) diff --git a/libmysql/authentication_win/common.h b/libmysql/authentication_win/common.h index 68f39fe04cc..ff0f7153664 100644 --- a/libmysql/authentication_win/common.h +++ b/libmysql/authentication_win/common.h @@ -41,41 +41,6 @@ struct error_log_level typedef enum {INFO, WARNING, ERROR} type; }; -#undef DBUG_ASSERT -#ifndef DBUG_OFF -#define DBUG_ASSERT(X) assert(X) -#else -#define DBUG_ASSERT(X) do {} while (0) -#endif - -extern "C" int opt_auth_win_client_log; - -/* - Note1: Double level of indirection in definition of DBUG_PRINT allows to - temporary redefine or disable DBUG_PRINT macro and then easily return to - the original definition (in terms of DBUG_PRINT_DO). - - Note2: DBUG_PRINT() can use printf-like format string like this: - - DBUG_PRINT(Keyword, ("format string", args)); - - The implementation should handle it correctly. Currently it is passed - to fprintf() (see debug_msg() function). -*/ - -#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) -#else -#define DBUG_PRINT_DO(K, M) do {} while (0) -#endif - -#undef DBUG_PRINT -#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg) /* If DEBUG_ERROR_LOG is defined then error logging happens only @@ -83,24 +48,23 @@ extern "C" int opt_auth_win_client_log; 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 -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); -} - void error_log_vprint(error_log_level::type level, const char *fmt, va_list args); @@ -118,6 +82,70 @@ 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; @@ -158,15 +186,21 @@ public: return m_len; } - byte operator[](unsigned pos) const + byte& operator[](unsigned pos) const { - return pos < len() ? m_ptr[pos] : 0x00; + 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; + } }; diff --git a/libmysql/authentication_win/handshake.cc b/libmysql/authentication_win/handshake.cc index 4b33349d0f4..ec665af9ef9 100644 --- a/libmysql/authentication_win/handshake.cc +++ b/libmysql/authentication_win/handshake.cc @@ -90,17 +90,19 @@ Handshake::~Handshake() @note In case of error, appropriate error message is logged. */ -int Handshake::packet_processing_loop(Connection &con) +int Handshake::packet_processing_loop() { - unsigned round= 1; + m_round= 0; do { + ++m_round; // Read packet send by the peer + DBUG_PRINT("info", ("Waiting for packet")); - Blob packet= con.read(); - if (con.error() || packet.is_null()) + Blob packet= read_packet(); + if (error()) { - ERROR_LOG(ERROR, ("Error reading packet in round %d", round)); + ERROR_LOG(ERROR, ("Error reading packet in round %d", m_round)); return 1; } DBUG_PRINT("info", ("Got packet of length %d", packet.len())); @@ -113,7 +115,7 @@ int Handshake::packet_processing_loop(Connection &con) if (error()) { - ERROR_LOG(ERROR, ("Error processing packet in round %d", round)); + ERROR_LOG(ERROR, ("Error processing packet in round %d", m_round)); return 1; } @@ -124,14 +126,13 @@ int Handshake::packet_processing_loop(Connection &con) if (!new_data.is_null()) { - ++round; - DBUG_PRINT("info", ("Round %d started", round)); + DBUG_PRINT("info", ("Round %d started", m_round)); DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); - int ret= con.write(new_data); + int ret= write_packet(new_data); if (ret) { - ERROR_LOG(ERROR, ("Error writing packet in round %d", round)); + ERROR_LOG(ERROR, ("Error writing packet in round %d", m_round)); return 1; } DBUG_PRINT("info", ("Data sent")); @@ -139,7 +140,7 @@ int Handshake::packet_processing_loop(Connection &con) else if (!is_complete()) { ERROR_LOG(ERROR, ("No data to send in round %d" - " but handshake is not complete", round)); + " but handshake is not complete", m_round)); return 1; } @@ -148,16 +149,16 @@ int Handshake::packet_processing_loop(Connection &con) too many rounds. */ - if (round > MAX_HANDSHAKE_ROUNDS) + if (m_round > MAX_HANDSHAKE_ROUNDS) { ERROR_LOG(ERROR, ("Authentication handshake could not be completed" - " after %d rounds", round)); + " after %d rounds", m_round)); return 1; } } while(!is_complete()); - ERROR_LOG(INFO, ("Handshake completed after %d rounds", round)); + ERROR_LOG(INFO, ("Handshake completed after %d rounds", m_round)); return 0; } diff --git a/libmysql/authentication_win/handshake.h b/libmysql/authentication_win/handshake.h index bb88510a7f8..5292948b583 100644 --- a/libmysql/authentication_win/handshake.h +++ b/libmysql/authentication_win/handshake.h @@ -100,7 +100,7 @@ public: Handshake(const char *ssp, side_t side); virtual ~Handshake(); - int Handshake::packet_processing_loop(Connection &con); + int Handshake::packet_processing_loop(); bool virtual is_complete() const { @@ -126,6 +126,13 @@ protected: /// 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; @@ -152,7 +159,13 @@ protected: @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; + 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 diff --git a/libmysql/authentication_win/handshake_client.cc b/libmysql/authentication_win/handshake_client.cc index e42f055da70..7e89fc92ae7 100644 --- a/libmysql/authentication_win/handshake_client.cc +++ b/libmysql/authentication_win/handshake_client.cc @@ -33,21 +33,27 @@ class Handshake_client: public Handshake /// Buffer for storing service name obtained from server. SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH]; + Connection &m_con; + public: - Handshake_client(const char *target, size_t len); + 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 target name of the target service with which we will authenticate - (can be NULL if not used) + @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 @@ -55,8 +61,9 @@ public: (see @c win_auth_handshake_{server,client}() functions). */ -Handshake_client::Handshake_client(const char *target, size_t len) -: Handshake(SSP_NAME, CLIENT), m_service_name(NULL) +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; @@ -88,43 +95,97 @@ Handshake_client::~Handshake_client() } -/** - Generate first packet to be sent to the server during packet exchange. - - This first packet should contain some data. In case of error a null blob - is returned and @c error() gives non-zero error code. - - @return Data to be sent in the first packet or null blob in case of error. -*/ - -Blob Handshake_client::first_packet() +Blob Handshake_client::read_packet() { - SECURITY_STATUS ret; - - m_output.free(); - - ret= InitializeSecurityContextW( - &m_cred, - NULL, // partial context - m_service_name, // service name - ASC_REQ_ALLOCATE_MEMORY, // requested attributes - 0, // reserved - SECURITY_NETWORK_DREP, // data representation - NULL, // 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)); + /* + 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); } - return m_output.as_blob(); + 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; } @@ -139,12 +200,66 @@ Blob Handshake_client::first_packet() 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; @@ -152,12 +267,12 @@ Blob Handshake_client::process_data(const Blob &data) ret= InitializeSecurityContextW( &m_cred, - &m_sctx, // partial context + 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 - &input, // input data + m_round == 1 ? NULL : &input, // input data 0, // reserved &m_sctx, // context &m_output, // output data @@ -198,6 +313,8 @@ Blob Handshake_client::process_data(const Blob &data) int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { + DBUG_ENTER("win_auth_handshake_client"); + /* Check if we should enable logging. */ @@ -224,62 +341,38 @@ int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) // Read initial packet from server containing service name. - int ret; Blob service_name= con.read(); if (con.error() || service_name.is_null()) { ERROR_LOG(ERROR, ("Error reading initial packet")); - return CR_ERROR; + DBUG_RETURN(CR_ERROR); } DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len())); - // Create authentication handsake context using the given service name. + // Create authentication handshake context using the given service name. - Handshake_client hndshk(service_name[0] ? (char *)service_name.ptr() : NULL, + 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")); - return CR_ERROR; - } - - /* - The following packet exchange always starts with a packet sent by - the client. Send this first packet now. - */ - - { - Blob packet= hndshk.first_packet(); - if (hndshk.error() || packet.is_null()) - { - ERROR_LOG(ERROR, ("Could not generate first packet")); - return CR_ERROR; - } - DBUG_PRINT("info", ("Sending first packet of length %d", packet.len())); - - ret= con.write(packet); - if (ret) - { - ERROR_LOG(ERROR, ("Error writing first packet")); - return CR_ERROR; - } - DBUG_PRINT("info", ("First packet sent")); + DBUG_RETURN(CR_ERROR); } DBUG_ASSERT(!hndshk.error()); /* - If handshake is not yet complete and client expects a reply, - read and process packets from server until handshake is complete. + 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.is_complete()) - { - if (hndshk.packet_processing_loop(con)) - return CR_ERROR; - } + if (hndshk.packet_processing_loop()) + DBUG_RETURN(CR_ERROR); DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); - return CR_OK; + DBUG_RETURN(CR_OK); } From 2e0e518f3901eb9647e08fbc84dc187e7647e3af Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Fri, 29 Apr 2011 14:04:28 +0300 Subject: [PATCH 20/43] Sync 5.1 .inc file with 5.5 due to a missing changeset Add extra codes to wait_until_disconnected.inc that are present in 5.5, but not in 5.1. The missing codes cause innodb_bug59641 to fail in 5.1 on Windows PB2 runs. The addition of those codes in 5.5 was done in luis.soares@sun.com-20090930233215-aup3kxy4j6ltvjfp --- mysql-test/include/wait_until_disconnected.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/include/wait_until_disconnected.inc b/mysql-test/include/wait_until_disconnected.inc index a4362e52d01..8a989becc18 100644 --- a/mysql-test/include/wait_until_disconnected.inc +++ b/mysql-test/include/wait_until_disconnected.inc @@ -7,7 +7,7 @@ let $counter= 500; let $mysql_errno= 0; while (!$mysql_errno) { - --error 0,1053,2002,2006,2013 + --error 0,1040,1053,2002,2003,2006,2013 show status; dec $counter; From 39f71a296a5e2afacbedb556672190e7d0400520 Mon Sep 17 00:00:00 2001 From: Nirbhay Choubey Date: Fri, 29 Apr 2011 18:52:46 +0530 Subject: [PATCH 21/43] Bug#11757855 - 49967: built-in libedit doesn't read .editrc on linux. MySQL client when build with libedit support ignores .editrc at startup. The reason for this regression was the incluison of a safety check, issetugid(), which is not available on some linux platforms. Fixed by adding an equivalent check for platforms which have get[e][u|g]id() set of functions. --- cmd-line-utils/libedit/el.c | 21 ++++++++++++++++----- configure.in | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) 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/configure.in b/configure.in index 5bd823ab879..8ba208b1ef5 100644 --- a/configure.in +++ b/configure.in @@ -1963,7 +1963,7 @@ AC_CHECK_HEADER(vis.h, [AC_DEFINE([HAVE_VIS_H], [1],[Found vis.h and the strvis() function])])]) AC_CHECK_FUNCS(strlcat strlcpy) -AC_CHECK_FUNCS(issetugid) +AC_CHECK_FUNCS(issetugid getuid geteuid getgid getegid) AC_CHECK_FUNCS(fgetln) AC_CHECK_FUNCS(getline flockfile) From 2b5b3e1ea70c4653b17fdfbe5fe17fc213a69a99 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Fri, 29 Apr 2011 18:48:23 -0300 Subject: [PATCH 22/43] FIONREAD is located in sys/filio.h on Solaris. --- config.h.cmake | 1 + configure.cmake | 1 + vio/viosocket.c | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config.h.cmake b/config.h.cmake index fdff4222417..95193730008 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 diff --git a/configure.cmake b/configure.cmake index c0bc1261d15..46714a0a0e7 100644 --- a/configure.cmake +++ b/configure.cmake @@ -487,6 +487,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/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 +#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; From 58ebf8950115aad9d8fa3a8536fc9f9b0982b851 Mon Sep 17 00:00:00 2001 From: Sven Sandberg Date: Mon, 2 May 2011 16:38:25 +0200 Subject: [PATCH 23/43] Marked test experimental. --- mysql-test/collections/default.experimental | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/collections/default.experimental b/mysql-test/collections/default.experimental index c741e4a869c..5ecc8ef8c64 100644 --- a/mysql-test/collections/default.experimental +++ b/mysql-test/collections/default.experimental @@ -17,6 +17,7 @@ main.wait_timeout @solaris # Bug#51244 2010-04-26 alik wait_timeou rpl.rpl_heartbeat_basic # BUG#12403008 2011-04-27 sven fails sporadically rpl.rpl_innodb_bug28430 # Bug#46029 +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 From 4f666e8e8130fe5bac078d4c5715c21fd948aa5f Mon Sep 17 00:00:00 2001 From: Kent Boortz Date: Tue, 3 May 2011 16:02:31 +0200 Subject: [PATCH 24/43] Remove soft links in the build directory, not the source directory (Bug#43312) --- client/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/Makefile.am b/client/Makefile.am index ccd0d8aada0..b455a34a58b 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -116,10 +116,10 @@ link_sources: @LN_CP_F@ $(top_srcdir)/sql/$$f $$f; \ done; \ for f in $(strings_src) ; do \ - rm -f $(srcdir)/$$f; \ + rm -f $$f; \ @LN_CP_F@ $(top_srcdir)/strings/$$f $$f; \ done; \ - rm -f $(srcdir)/my_user.c; \ + rm -f my_user.c; \ @LN_CP_F@ $(top_srcdir)/sql-common/my_user.c my_user.c; echo timestamp > link_sources; From ca6e7781ef47ace73058fd231fd0057e87e86d76 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Wed, 4 May 2011 16:59:24 +0400 Subject: [PATCH 25/43] Patch for Bug#12394306: the sever may crash if mysql.event is corrupted. The problem was that wrong structure of mysql.event was not detected and the server continued to use wrongly-structured data. The fix is to check the structure of mysql.event after opening before any use. That makes operations with events more strict -- some operations that might work before throw errors now. That seems to be Ok. Another side-effect of the patch is that if mysql.event is corrupted, unrelated DROP DATABASE statements issue an SQL warning about inability to open mysql.event table. --- mysql-test/r/events_1.result | 106 ++++++++++++++++++++-------- mysql-test/r/events_restart.result | 3 + mysql-test/t/events_1.test | 109 ++++++++++++++++++++++------- mysql-test/t/events_restart.test | 2 + sql/event_db_repository.cc | 8 +++ 5 files changed, 172 insertions(+), 56 deletions(-) diff --git a/mysql-test/r/events_1.result b/mysql-test/r/events_1.result index e7b645f5556..e0c137ea877 100644 --- a/mysql-test/r/events_1.result +++ b/mysql-test/r/events_1.result @@ -1,3 +1,4 @@ +call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted"); drop database if exists events_test; drop database if exists db_x; drop database if exists mysqltest_db2; @@ -259,33 +260,36 @@ events_test intact_check root@localhost SYSTEM RECURRING NULL 10 # # NULL ENABLE Try to alter mysql.event: the server should fail to load event information after mysql.event was tampered with. -First, let's add a column to the end and make sure everything -works as before +First, let's add a column to the end and check the error is emitted. ALTER TABLE mysql.event ADD dummy INT; SHOW EVENTS; -Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation -events_test intact_check root@localhost SYSTEM RECURRING NULL 10 # # NULL ENABLED 1 latin1 latin1_swedish_ci latin1_swedish_ci +ERROR HY000: Failed to open mysql.event SELECT event_name FROM INFORMATION_SCHEMA.events; -event_name -intact_check +ERROR HY000: Failed to open mysql.event SHOW CREATE EVENT intact_check; -Event sql_mode time_zone Create Event character_set_client collation_connection Database Collation -intact_check SYSTEM CREATE EVENT `intact_check` ON SCHEDULE EVERY 10 HOUR STARTS '#' ON COMPLETION NOT PRESERVE ENABLE DO SELECT "nothing" latin1 latin1_swedish_ci latin1_swedish_ci +ERROR HY000: Failed to open mysql.event DROP EVENT no_such_event; -ERROR HY000: Unknown event 'no_such_event' +ERROR HY000: Failed to open mysql.event CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 RENAME TO intact_check_2; +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_1; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_2; +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check; +ERROR HY000: Failed to open mysql.event DROP DATABASE IF EXISTS mysqltest_no_such_database; Warnings: Note 1008 Can't drop database 'mysqltest_no_such_database'; database doesn't exist CREATE DATABASE mysqltest_db2; DROP DATABASE mysqltest_db2; +Warnings: +Error 1545 Failed to open mysql.event SELECT @@event_scheduler; @@event_scheduler OFF @@ -294,6 +298,7 @@ Variable_name Value event_scheduler OFF SET GLOBAL event_scheduler=OFF; ALTER TABLE mysql.event DROP dummy; +DROP EVENT intact_check; CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing"; Now let's add a column to the first position: the server @@ -301,30 +306,32 @@ expects to see event schema name there ALTER TABLE mysql.event ADD dummy INT FIRST; SHOW EVENTS; -ERROR HY000: Cannot load from mysql.event. The table is probably corrupted +ERROR HY000: Failed to open mysql.event SELECT event_name FROM INFORMATION_SCHEMA.events; -ERROR HY000: Cannot load from mysql.event. The table is probably corrupted +ERROR HY000: Failed to open mysql.event SHOW CREATE EVENT intact_check; -ERROR HY000: Unknown event 'intact_check' +ERROR HY000: Failed to open mysql.event DROP EVENT no_such_event; -ERROR HY000: Unknown event 'no_such_event' +ERROR HY000: Failed to open mysql.event CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; -ERROR HY000: Failed to store event name. Error code 2 from storage engine. +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 RENAME TO intact_check_2; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_1; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_2; -ERROR HY000: Unknown event 'intact_check_2' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check; -ERROR HY000: Unknown event 'intact_check' +ERROR HY000: Failed to open mysql.event DROP DATABASE IF EXISTS mysqltest_no_such_database; Warnings: Note 1008 Can't drop database 'mysqltest_no_such_database'; database doesn't exist CREATE DATABASE mysqltest_db2; DROP DATABASE mysqltest_db2; +Warnings: +Error 1545 Failed to open mysql.event SELECT @@event_scheduler; @@event_scheduler OFF @@ -345,29 +352,32 @@ Drop some columns and try more checks. ALTER TABLE mysql.event DROP comment, DROP starts; SHOW EVENTS; -ERROR HY000: Cannot load from mysql.event. The table is probably corrupted +ERROR HY000: Failed to open mysql.event SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; -ERROR HY000: Cannot load from mysql.event. The table is probably corrupted +ERROR HY000: Failed to open mysql.event SHOW CREATE EVENT intact_check; -ERROR HY000: Cannot load from mysql.event. The table is probably corrupted +ERROR HY000: Failed to open mysql.event DROP EVENT no_such_event; -ERROR HY000: Unknown event 'no_such_event' +ERROR HY000: Failed to open mysql.event CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; -ERROR HY000: Column count of mysql.event is wrong. Expected 22, found 20. The table is probably corrupted +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event ALTER EVENT intact_check_1 RENAME TO intact_check_2; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_1; -ERROR HY000: Unknown event 'intact_check_1' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check_2; -ERROR HY000: Unknown event 'intact_check_2' +ERROR HY000: Failed to open mysql.event DROP EVENT intact_check; +ERROR HY000: Failed to open mysql.event DROP DATABASE IF EXISTS mysqltest_no_such_database; Warnings: Note 1008 Can't drop database 'mysqltest_no_such_database'; database doesn't exist CREATE DATABASE mysqltest_db2; DROP DATABASE mysqltest_db2; +Warnings: +Error 1545 Failed to open mysql.event SELECT @@event_scheduler; @@event_scheduler OFF @@ -425,4 +435,42 @@ CREATE TABLE mysql.event like event_like; DROP TABLE event_like; SHOW EVENTS; Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation + +# +# Bug#12394306: the sever may crash if mysql.event is corrupted +# + +CREATE EVENT ev1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; +ALTER EVENT ev1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; + +CREATE TABLE event_original LIKE mysql.event; +INSERT INTO event_original SELECT * FROM mysql.event; + +ALTER TABLE mysql.event MODIFY modified CHAR(1); +Warnings: +Warning 1265 Data truncated for column 'modified' at row 1 + +SHOW EVENTS; +ERROR HY000: Failed to open mysql.event + +SELECT event_name, created, last_altered FROM information_schema.events; +ERROR HY000: Failed to open mysql.event + +CREATE EVENT ev2 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; +ERROR HY000: Failed to open mysql.event + +ALTER EVENT ev1 ON SCHEDULE EVERY 9 HOUR DO SELECT 9; +ERROR HY000: Failed to open mysql.event + +DROP TABLE mysql.event; +RENAME TABLE event_original TO mysql.event; + +DROP EVENT ev1; + +SHOW EVENTS; +Db Name Definer Time zone Type Execute at Interval value Interval field Starts Ends Status Originator character_set_client collation_connection Database Collation + +# +# End of tests +# drop database events_test; diff --git a/mysql-test/r/events_restart.result b/mysql-test/r/events_restart.result index 4db61d357ce..6a751fa29f8 100644 --- a/mysql-test/r/events_restart.result +++ b/mysql-test/r/events_restart.result @@ -1,3 +1,4 @@ +call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted"); set global event_scheduler=off; drop database if exists events_test; create database events_test; @@ -52,6 +53,8 @@ Warnings: Note 1008 Can't drop database 'mysqltest_database_not_exists'; database doesn't exist create database mysqltest_db1; drop database mysqltest_db1; +Warnings: +Error 1545 Failed to open mysql.event Restore the original mysql.event table drop table mysql.event; rename table event_like to mysql.event; diff --git a/mysql-test/t/events_1.test b/mysql-test/t/events_1.test index ccdeb70d291..7f31e3fc881 100644 --- a/mysql-test/t/events_1.test +++ b/mysql-test/t/events_1.test @@ -4,6 +4,8 @@ # Can't test with embedded server that doesn't support grants -- source include/not_embedded.inc +call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted"); + --disable_warnings drop database if exists events_test; drop database if exists db_x; @@ -270,23 +272,28 @@ SHOW EVENTS; --echo Try to alter mysql.event: the server should fail to load --echo event information after mysql.event was tampered with. --echo ---echo First, let's add a column to the end and make sure everything ---echo works as before +--echo First, let's add a column to the end and check the error is emitted. --echo ALTER TABLE mysql.event ADD dummy INT; ---replace_column 8 # 9 # +--error ER_EVENT_OPEN_TABLE_FAILED SHOW EVENTS; +--error ER_EVENT_OPEN_TABLE_FAILED SELECT event_name FROM INFORMATION_SCHEMA.events; ---replace_regex /STARTS '[^']+'/STARTS '#'/ +--error ER_EVENT_OPEN_TABLE_FAILED SHOW CREATE EVENT intact_check; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT no_such_event; +--error ER_EVENT_OPEN_TABLE_FAILED CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 RENAME TO intact_check_2; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_1; +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_2; +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check; DROP DATABASE IF EXISTS mysqltest_no_such_database; CREATE DATABASE mysqltest_db2; @@ -296,6 +303,7 @@ SHOW VARIABLES LIKE 'event_scheduler'; SET GLOBAL event_scheduler=OFF; # Clean up ALTER TABLE mysql.event DROP dummy; +DROP EVENT intact_check; CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing"; --echo --echo Now let's add a column to the first position: the server @@ -303,24 +311,26 @@ CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing"; --echo ALTER TABLE mysql.event ADD dummy INT FIRST; --error ER_CANNOT_LOAD_FROM_TABLE +--error ER_EVENT_OPEN_TABLE_FAILED SHOW EVENTS; --error ER_CANNOT_LOAD_FROM_TABLE +--error ER_EVENT_OPEN_TABLE_FAILED SELECT event_name FROM INFORMATION_SCHEMA.events; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED SHOW CREATE EVENT intact_check; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT no_such_event; ---error ER_EVENT_STORE_FAILED +--error ER_EVENT_OPEN_TABLE_FAILED CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 RENAME TO intact_check_2; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_1; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_2; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check; # Should work OK DROP DATABASE IF EXISTS mysqltest_no_such_database; @@ -341,25 +351,25 @@ INSERT INTO event_like SELECT * FROM mysql.event; --echo --echo ALTER TABLE mysql.event DROP comment, DROP starts; ---error ER_CANNOT_LOAD_FROM_TABLE +--error ER_EVENT_OPEN_TABLE_FAILED SHOW EVENTS; ---error ER_CANNOT_LOAD_FROM_TABLE +--error ER_EVENT_OPEN_TABLE_FAILED SELECT event_name FROM INFORMATION_SCHEMA.EVENTS; ---error ER_CANNOT_LOAD_FROM_TABLE +--error ER_EVENT_OPEN_TABLE_FAILED SHOW CREATE EVENT intact_check; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT no_such_event; ---error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED +--error ER_EVENT_OPEN_TABLE_FAILED CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED ALTER EVENT intact_check_1 RENAME TO intact_check_2; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_1; ---error ER_EVENT_DOES_NOT_EXIST +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check_2; -# Should succeed +--error ER_EVENT_OPEN_TABLE_FAILED DROP EVENT intact_check; DROP DATABASE IF EXISTS mysqltest_no_such_database; CREATE DATABASE mysqltest_db2; @@ -407,9 +417,54 @@ CREATE TABLE mysql.event like event_like; DROP TABLE event_like; --replace_column 8 # 9 # SHOW EVENTS; -# -# End of tests -# + +--echo +--echo # +--echo # Bug#12394306: the sever may crash if mysql.event is corrupted +--echo # + +--echo +CREATE EVENT ev1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; +ALTER EVENT ev1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8; + +--echo +CREATE TABLE event_original LIKE mysql.event; +INSERT INTO event_original SELECT * FROM mysql.event; + +--echo +ALTER TABLE mysql.event MODIFY modified CHAR(1); + +--echo +--error ER_EVENT_OPEN_TABLE_FAILED +SHOW EVENTS; + +--echo +--error ER_EVENT_OPEN_TABLE_FAILED +SELECT event_name, created, last_altered FROM information_schema.events; + +--echo +--error ER_EVENT_OPEN_TABLE_FAILED +CREATE EVENT ev2 ON SCHEDULE EVERY 5 HOUR DO SELECT 5; + +--echo +--error ER_EVENT_OPEN_TABLE_FAILED +ALTER EVENT ev1 ON SCHEDULE EVERY 9 HOUR DO SELECT 9; + +--echo +DROP TABLE mysql.event; +RENAME TABLE event_original TO mysql.event; + +--echo +DROP EVENT ev1; + +--echo +SHOW EVENTS; + + +--echo +--echo # +--echo # End of tests +--echo # let $wait_condition= select count(*) = 0 from information_schema.processlist diff --git a/mysql-test/t/events_restart.test b/mysql-test/t/events_restart.test index e155fe2ea16..facf2912087 100644 --- a/mysql-test/t/events_restart.test +++ b/mysql-test/t/events_restart.test @@ -1,6 +1,8 @@ # Can't test with embedded server that doesn't support grants -- source include/not_embedded.inc +call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted"); + # # Test that when the server is restarted, it checks mysql.event table, # and disables the scheduler if it's not up to date. diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 7473cf47188..a0765dc6d15 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -582,6 +582,14 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, *table= tables.table; tables.table->use_all_columns(); + + if (table_intact.check(*table, &event_table_def)) + { + close_thread_tables(thd); + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); } From 47ef8f3452bd82324e5459cc5964e31407789e05 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Wed, 4 May 2011 14:07:59 +0100 Subject: [PATCH 26/43] Automerged (cherrypicked) cset from 5.6: zhenxing.he@sun.com-20101202073812-iel8lvhmulyagtsv This takes care of valgrind warnings in 5.5 that exhibit the same trace as in BUG#11763880 (BUG#56650). --- plugin/semisync/semisync_slave_plugin.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/semisync/semisync_slave_plugin.cc b/plugin/semisync/semisync_slave_plugin.cc index 5aa32cdfd5f..cfb04bdd276 100644 --- a/plugin/semisync/semisync_slave_plugin.cc +++ b/plugin/semisync/semisync_slave_plugin.cc @@ -53,7 +53,6 @@ int repl_semi_slave_request_dump(Binlog_relay_IO_param *param, if (mysql_real_query(mysql, query, strlen(query)) || !(res= mysql_store_result(mysql))) { - mysql_free_result(mysql_store_result(mysql)); sql_print_error("Execution failed on master: %s", query); return 1; } @@ -65,8 +64,10 @@ int repl_semi_slave_request_dump(Binlog_relay_IO_param *param, sql_print_warning("Master server does not support semi-sync, " "fallback to asynchronous replication"); rpl_semi_sync_slave_status= 0; + mysql_free_result(res); return 0; } + mysql_free_result(res); /* Tell master dump thread that we want to do semi-sync @@ -76,7 +77,6 @@ int repl_semi_slave_request_dump(Binlog_relay_IO_param *param, if (mysql_real_query(mysql, query, strlen(query))) { sql_print_error("Set 'rpl_semi_sync_slave=1' on master failed"); - mysql_free_result(mysql_store_result(mysql)); return 1; } mysql_free_result(mysql_store_result(mysql)); From 96a41c47d4a74ea5971647f423f1d1dd01db1e92 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Wed, 4 May 2011 14:09:54 +0100 Subject: [PATCH 27/43] Automerged (cherrypicked) cset from 5.6: zhenxing.he@sun.com-20101117085902-n9gfvlkmm44t38y0 This takes care of valgrind warnings in 5.5 that exhibit the same trace as in BUG#11763879 (BUG#56649). --- sql/rpl_handler.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sql/rpl_handler.h b/sql/rpl_handler.h index bf207e53e2d..9a181250efc 100644 --- a/sql/rpl_handler.h +++ b/sql/rpl_handler.h @@ -73,7 +73,10 @@ public: while (info && info->observer != observer) info= iter++; if (info) + { iter.remove(); + delete info; + } else ret= TRUE; unlock(); From e367a789d11fa44f3e2507ff9feed49d50c58b33 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Thu, 5 May 2011 23:48:15 +0100 Subject: [PATCH 28/43] BUG#12354268: MYSQLBINLOG --BASE64-OUTPUT=DECODE-ROWS DOES NOT WORK WITH --START-POSITION If setting --start-position to start after the FD event, mysqlbinlog will output an error stating that it has not found an FD event. However, its not that mysqlbinlog does not find it but rather that it does not processes it in the regular way (i.e., it does not print it). Given that one is using --base64-output=DECODE-ROWS then not printing it is actually fine. To fix this, we make mysqlbinlog not to complain when it has not printed the FD event, is outputing in base64, but is decoding the rows. --- client/mysqlbinlog.cc | 3 ++- mysql-test/r/mysqlbinlog_base64.result | 10 +++++++++ mysql-test/t/mysqlbinlog_base64.test | 29 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index 30a8bddc17c..f451e28de86 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -951,7 +951,8 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, passed --short-form, because --short-form disables printing row events. */ - if (!print_event_info->printed_fd_event && !short_form) + if (!print_event_info->printed_fd_event && !short_form && + opt_base64_output_mode != BASE64_OUTPUT_DECODE_ROWS) { const char* type_str= ev->get_type_str(); if (opt_base64_output_mode == BASE64_OUTPUT_NEVER) diff --git a/mysql-test/r/mysqlbinlog_base64.result b/mysql-test/r/mysqlbinlog_base64.result index c5e1e2f8ca1..72d49c16cc8 100644 --- a/mysql-test/r/mysqlbinlog_base64.result +++ b/mysql-test/r/mysqlbinlog_base64.result @@ -109,3 +109,13 @@ count(*) 35840 drop table t1; drop table t2; +RESET MASTER; +USE test; +SET @old_binlog_format= @@binlog_format; +SET SESSION binlog_format=ROW; +CREATE TABLE t1(c1 INT); +INSERT INTO t1 VALUES (1); +FLUSH LOGS; +DROP TABLE t1; +SET SESSION binlog_format= @old_binlog_format; +RESET MASTER; diff --git a/mysql-test/t/mysqlbinlog_base64.test b/mysql-test/t/mysqlbinlog_base64.test index fb21e28fdcb..3d3444cea1c 100644 --- a/mysql-test/t/mysqlbinlog_base64.test +++ b/mysql-test/t/mysqlbinlog_base64.test @@ -71,3 +71,32 @@ select count(*) from t2; --remove_file $MYSQLTEST_VARDIR/tmp/mysqlbinlog_base64.sql drop table t1; drop table t2; + +# +# BUG#12354268 +# +# This test verifies that using --start-position with DECODE-ROWS +# does not make mysqlbinlog to output an error stating that it +# does not contain any FD event. +# + +RESET MASTER; +USE test; +SET @old_binlog_format= @@binlog_format; +SET SESSION binlog_format=ROW; +CREATE TABLE t1(c1 INT); +--let $master_binlog= query_get_value(SHOW MASTER STATUS, File, 1) +--let $master_pos= query_get_value(SHOW MASTER STATUS, Position, 1) +--let $MYSQLD_DATADIR= `SELECT @@datadir` + +INSERT INTO t1 VALUES (1); + +FLUSH LOGS; + +--disable_result_log +--exec $MYSQL_BINLOG --base64-output=DECODE-ROWS --start-position=$master_pos -v $MYSQLD_DATADIR/$master_binlog +--enable_result_log + +DROP TABLE t1; +SET SESSION binlog_format= @old_binlog_format; +RESET MASTER; From ed6aae83c3dd8e78e024bbb2bae49467a3fc0de3 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Fri, 6 May 2011 00:46:53 +0100 Subject: [PATCH 29/43] BUG#11762616: BUG#55229: 'POSTION' Fix for all "postion" in Oracle files (s/postion/position). Updated the copyright notices where needed. --- client/mysqltest.cc | 4 +-- extra/replace.c | 18 ++++++------ mysql-test/suite/rpl/r/rpl_server_id2.result | 2 +- mysql-test/suite/rpl/t/rpl_row_until.test | 10 +++---- mysql-test/suite/rpl/t/rpl_server_id2.test | 2 +- sql/handler.h | 17 +++++------ sql/slave.cc | 6 ++-- storage/archive/ha_archive.cc | 26 +++++++++-------- storage/ndb/src/kernel/blocks/lgman.cpp | 16 ++++++----- vio/viosocket.c | 30 +++++++++++--------- 10 files changed, 70 insertions(+), 61 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index a1813838a24..c2410b14c19 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -9739,7 +9739,7 @@ int find_set(REP_SETS *sets,REP_SET *find) return i; } } - return i; /* return new postion */ + return i; /* return new position */ } /* find if there is a found_set with same table_offset & found_offset @@ -9759,7 +9759,7 @@ int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) found_set[i].table_offset=table_offset; found_set[i].found_offset=found_offset; found_sets++; - return -i-2; /* return new postion */ + return -i-2; /* return new position */ } /* Return 1 if regexp starts with \b or ends with \b*/ diff --git a/extra/replace.c b/extra/replace.c index fd2d860c212..2df8a58e16a 100644 --- a/extra/replace.c +++ b/extra/replace.c @@ -1,17 +1,19 @@ -/* Copyright (C) 2000 MySQL AB +/* Copyright (c) 2000, 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 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 + 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 */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA */ /* Replace strings in textfile @@ -819,7 +821,7 @@ static short find_set(REP_SETS *sets,REP_SET *find) return (short) i; } } - return (short) i; /* return new postion */ + return (short) i; /* return new position */ } @@ -842,7 +844,7 @@ static short find_found(FOUND_SET *found_set,uint table_offset, found_set[i].table_offset=table_offset; found_set[i].found_offset=found_offset; found_sets++; - return (short) (-i-2); /* return new postion */ + return (short) (-i-2); /* return new position */ } /* Return 1 if regexp starts with \b or ends with \b*/ diff --git a/mysql-test/suite/rpl/r/rpl_server_id2.result b/mysql-test/suite/rpl/r/rpl_server_id2.result index dacb69bc7cb..4f299a1b23b 100644 --- a/mysql-test/suite/rpl/r/rpl_server_id2.result +++ b/mysql-test/suite/rpl/r/rpl_server_id2.result @@ -19,7 +19,7 @@ change master to master_port=MASTER_PORT; start slave until master_log_file='master-bin.000001', master_log_pos=UNTIL_POS; include/wait_for_slave_io_to_start.inc include/wait_for_slave_sql_to_stop.inc -*** checking until postion execution: must be only t1 in the list *** +*** checking until position execution: must be only t1 in the list *** show tables; Tables_in_test t1 diff --git a/mysql-test/suite/rpl/t/rpl_row_until.test b/mysql-test/suite/rpl/t/rpl_row_until.test index afd964ca81a..bf38bd487ea 100644 --- a/mysql-test/suite/rpl/t/rpl_row_until.test +++ b/mysql-test/suite/rpl/t/rpl_row_until.test @@ -9,29 +9,29 @@ connection master; CREATE TABLE t1(n INT NOT NULL AUTO_INCREMENT PRIMARY KEY); INSERT INTO t1 VALUES (1),(2),(3),(4); DROP TABLE t1; -# Save master log postion for query DROP TABLE t1 +# Save master log position for query DROP TABLE t1 save_master_pos; let $master_pos_drop_t1= query_get_value(SHOW BINLOG EVENTS, Pos, 7); let $master_log_file= query_get_value(SHOW BINLOG EVENTS, Log_name, 7); CREATE TABLE t2(n INT NOT NULL AUTO_INCREMENT PRIMARY KEY); -# Save master log postion for query CREATE TABLE t2 +# Save master log position for query CREATE TABLE t2 save_master_pos; let $master_pos_create_t2= query_get_value(SHOW BINLOG EVENTS, Pos, 8); INSERT INTO t2 VALUES (1),(2); save_master_pos; -# Save master log postion for query INSERT INTO t2 VALUES (1),(2); +# Save master log position for query INSERT INTO t2 VALUES (1),(2); let $master_pos_insert1_t2= query_get_value(SHOW BINLOG EVENTS, End_log_pos, 12); sync_slave_with_master; -# Save relay log postion for query INSERT INTO t2 VALUES (1),(2); +# Save relay log position for query INSERT INTO t2 VALUES (1),(2); let $relay_pos_insert1_t2= query_get_value(show slave status, Relay_Log_Pos, 1); connection master; INSERT INTO t2 VALUES (3),(4); DROP TABLE t2; -# Save master log postion for query INSERT INTO t2 VALUES (1),(2); +# Save master log position for query INSERT INTO t2 VALUES (1),(2); let $master_pos_drop_t2= query_get_value(SHOW BINLOG EVENTS, End_log_pos, 17); sync_slave_with_master; diff --git a/mysql-test/suite/rpl/t/rpl_server_id2.test b/mysql-test/suite/rpl/t/rpl_server_id2.test index 32d5e1ec8f2..aeb7292ed17 100644 --- a/mysql-test/suite/rpl/t/rpl_server_id2.test +++ b/mysql-test/suite/rpl/t/rpl_server_id2.test @@ -47,7 +47,7 @@ eval start slave until master_log_file='master-bin.000001', master_log_pos=$unti --source include/wait_for_slave_io_to_start.inc --source include/wait_for_slave_sql_to_stop.inc ---echo *** checking until postion execution: must be only t1 in the list *** +--echo *** checking until position execution: must be only t1 in the list *** show tables; # cleanup diff --git a/sql/handler.h b/sql/handler.h index 9acdac700cd..03b0555ae86 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1,18 +1,19 @@ -/* Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc. +/* Copyright (c) 2000, 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 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 + 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 */ - + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA */ /* Definitions for parameters to do with handler-routines */ @@ -56,7 +57,7 @@ a table with rnd_next() - We will see all rows (including deleted ones) - Row positions are 'table->s->db_record_offset' apart - If this flag is not set, filesort will do a postion() call for each matched + If this flag is not set, filesort will do a position() call for each matched row to be able to find the row later. */ #define HA_REC_NOT_IN_SEQ (1 << 3) diff --git a/sql/slave.cc b/sql/slave.cc index 6d266245460..dd578064f24 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -97,7 +97,7 @@ static const char *reconnect_messages[SLAVE_RECON_ACT_MAX][SLAVE_RECON_MSG_MAX]= registration on master", "Reconnecting after a failed registration on master", "failed registering on master, reconnecting to try again, \ -log '%s' at postion %s", +log '%s' at position %s", "COM_REGISTER_SLAVE", "Slave I/O thread killed during or after reconnect" }, @@ -105,7 +105,7 @@ log '%s' at postion %s", "Waiting to reconnect after a failed binlog dump request", "Slave I/O thread killed while retrying master dump", "Reconnecting after a failed binlog dump request", - "failed dump request, reconnecting to try again, log '%s' at postion %s", + "failed dump request, reconnecting to try again, log '%s' at position %s", "COM_BINLOG_DUMP", "Slave I/O thread killed during or after reconnect" }, @@ -114,7 +114,7 @@ log '%s' at postion %s", "Slave I/O thread killed while waiting to reconnect after a failed read", "Reconnecting after a failed master event read", "Slave I/O thread: Failed reading log event, reconnecting to retry, \ -log '%s' at postion %s", +log '%s' at position %s", "", "Slave I/O thread killed during or after a reconnect done to recover from \ failed read" diff --git a/storage/archive/ha_archive.cc b/storage/archive/ha_archive.cc index 988337ec50e..764ed16e931 100644 --- a/storage/archive/ha_archive.cc +++ b/storage/archive/ha_archive.cc @@ -1,17 +1,19 @@ -/* Copyright (C) 2003 MySQL AB +/* Copyright (c) 2003, 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 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. + 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 */ + 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 */ #ifdef USE_PRAGMA_IMPLEMENTATION #pragma implementation // gcc: Class implementation @@ -864,7 +866,7 @@ int ha_archive::write_row(uchar *buf) */ azflush(&(share->archive_write), Z_SYNC_FLUSH); /* - Set the position of the local read thread to the beginning postion. + Set the position of the local read thread to the beginning position. */ if (read_data_header(&archive)) { diff --git a/storage/ndb/src/kernel/blocks/lgman.cpp b/storage/ndb/src/kernel/blocks/lgman.cpp index 53cb1e113e1..7dc71e7399a 100644 --- a/storage/ndb/src/kernel/blocks/lgman.cpp +++ b/storage/ndb/src/kernel/blocks/lgman.cpp @@ -1,17 +1,19 @@ -/* Copyright (C) 2003 MySQL AB +/* Copyright (c) 2003, 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 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 + 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 */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA */ #include "lgman.hpp" #include "diskpage.hpp" @@ -2501,7 +2503,7 @@ Lgman::init_run_undo_log(Signal* signal) sendSignal(reference(), GSN_CONTINUEB, signal, 2, JBB); /** - * Insert in correct postion in list of logfile_group's + * Insert in correct position in list of logfile_group's */ Ptr pos; for(tmp.first(pos); !pos.isNull(); tmp.next(pos)) diff --git a/vio/viosocket.c b/vio/viosocket.c index f73b890c697..15942fb3e31 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -1,17 +1,19 @@ -/* Copyright (C) 2000 MySQL AB +/* Copyright (c) 2000, 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 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 + 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 */ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA */ /* Note that we can't have assertion on file descriptors; The reason for @@ -548,7 +550,7 @@ size_t vio_read_shared_memory(Vio * vio, uchar* buf, size_t size) { size_t length; size_t remain_local; - char *current_postion; + char *current_position; HANDLE events[2]; DBUG_ENTER("vio_read_shared_memory"); @@ -556,7 +558,7 @@ size_t vio_read_shared_memory(Vio * vio, uchar* buf, size_t size) size)); remain_local = size; - current_postion=buf; + current_position=buf; events[0]= vio->event_server_wrote; events[1]= vio->event_conn_closed; @@ -590,11 +592,11 @@ size_t vio_read_shared_memory(Vio * vio, uchar* buf, size_t size) if (length > remain_local) length = remain_local; - memcpy(current_postion,vio->shared_memory_pos,length); + memcpy(current_position,vio->shared_memory_pos,length); vio->shared_memory_remain-=length; vio->shared_memory_pos+=length; - current_postion+=length; + current_position+=length; remain_local-=length; if (!vio->shared_memory_remain) @@ -614,7 +616,7 @@ size_t vio_write_shared_memory(Vio * vio, const uchar* buf, size_t size) { size_t length, remain, sz; HANDLE pos; - const uchar *current_postion; + const uchar *current_position; HANDLE events[2]; DBUG_ENTER("vio_write_shared_memory"); @@ -622,7 +624,7 @@ size_t vio_write_shared_memory(Vio * vio, const uchar* buf, size_t size) size)); remain = size; - current_postion = buf; + current_position = buf; events[0]= vio->event_server_read; events[1]= vio->event_conn_closed; @@ -640,9 +642,9 @@ size_t vio_write_shared_memory(Vio * vio, const uchar* buf, size_t size) int4store(vio->handle_map,sz); pos = vio->handle_map + 4; - memcpy(pos,current_postion,sz); + memcpy(pos,current_position,sz); remain-=sz; - current_postion+=sz; + current_position+=sz; if (!SetEvent(vio->event_client_wrote)) DBUG_RETURN((size_t) -1); } From ec425cc5ac4281b26ed32b29c06437950357cbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Bl=C3=A5udd?= Date: Fri, 6 May 2011 10:53:42 +0200 Subject: [PATCH 30/43] Merge in patch for bug 12380149 --- sql/sql_partition.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 5c2c0bb95d6..776ce01cc54 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -3979,7 +3979,7 @@ void get_partition_set(const TABLE *table, uchar *buf, const uint index, part_spec->start_part= 0; part_spec->end_part= num_parts - 1; if ((index < MAX_KEY) && - key_spec->flag == (uint)HA_READ_KEY_EXACT && + key_spec && key_spec->flag == (uint)HA_READ_KEY_EXACT && part_info->some_fields_in_PF.is_set(index)) { key_info= table->key_info+index; From b1ff2e68134479dcf5ca609d6915ffc56a8a51b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Bl=C3=A5udd?= Date: Fri, 6 May 2011 11:20:01 +0200 Subject: [PATCH 31/43] Add --with-debug=full support to BUILD/* scripts, this option has been lost between 5.1 and 5.5, it's purpose is to set the compiler flags in a way that does not optimize away the call stack(i.e don't use any -OX flags at all) --- BUILD/SETUP.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BUILD/SETUP.sh b/BUILD/SETUP.sh index 2642788e360..c7f434d1bb3 100755 --- a/BUILD/SETUP.sh +++ b/BUILD/SETUP.sh @@ -31,6 +31,7 @@ Usage: $0 [-h|-n] [configure-options] -h, --help Show this help message. -n, --just-print Don't actually run any commands; just print them. -c, --just-configure Stop after running configure. + --with-debug=full Build with full debug(no optimizations, keep call stack). --warning-mode=[old|pedantic|maintainer] Influences the debug flags. Old is default. --prefix=path Build with prefix 'path'. @@ -46,6 +47,8 @@ parse_options() case "$1" in --prefix=*) prefix=`get_key_value "$1"`;; + --with-debug=full) + full_debug="=full";; --warning-mode=*) warning_mode=`get_key_value "$1"`;; -c | --just-configure) @@ -76,6 +79,7 @@ just_print= just_configure= warning_mode= maintainer_mode= +full_debug= parse_options "$@" @@ -154,7 +158,11 @@ base_cxxflags="-felide-constructors -fno-exceptions -fno-rtti" fast_cflags="-O3 -fno-omit-frame-pointer" debug_configs="--with-debug" -debug_cflags="$debug_cflags $debug_extra_cflags" +if [ -z "$full_debug" ] +then + debug_cflags="$debug_cflags $debug_extra_cflags" +fi + # # Configuration options. From 2593b14ccb4d70d3f3b58f5d3fab886c1c4af7a0 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Fri, 6 May 2011 15:39:40 +0400 Subject: [PATCH 32/43] Preliminary patch for Bug#11848763 / 60025 (SUBSTRING inside a stored function works too slow). Background: - THD classes derives from Query_arena, thus inherits the 'state' attribute and related operations (is_stmt_prepare() & co). - Although these operations are available in THD, they must not be used. THD has its own attribute to point to the active Query_arena -- stmt_arena. - So, instead of using thd->is_stmt_prepare(), thd->stmt_arena->is_stmt_prepare() must be used. This was the root cause of Bug 60025. This patch enforces the proper way of calling those operations. is_stmt_prepare() & co are declared as private operations in THD (thus, they are hidden from being called on THD instance). The patch tries to minimize changes in 5.5. --- sql/item.cc | 6 +++--- sql/item_cmpfunc.cc | 4 ++-- sql/item_func.cc | 2 +- sql/item_row.cc | 2 +- sql/item_strfunc.cc | 2 +- sql/sql_class.h | 18 +++++++++++++----- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index af3917c09c1..788c7744c14 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -581,7 +581,7 @@ void Item::rename(char *new_name) Item* Item::transform(Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); return (this->*transformer)(arg); } @@ -1845,7 +1845,7 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, been created in prepare. In this case register the change for rollback. */ - if (thd->is_stmt_prepare()) + if (thd->stmt_arena->is_stmt_prepare()) *arg= conv; else thd->change_item_tree(arg, conv); @@ -6965,7 +6965,7 @@ int Item_default_value::save_in_field(Field *field_arg, bool no_conversions) Item *Item_default_value::transform(Item_transformer transformer, uchar *args) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); /* If the value of arg is NULL, then this object represents a constant, diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index cf5da5313d9..e0057d1550b 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -4345,7 +4345,7 @@ bool Item_cond::walk(Item_processor processor, bool walk_subquery, uchar *arg) Item *Item_cond::transform(Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); List_iterator li(list); Item *item; @@ -5718,7 +5718,7 @@ bool Item_equal::walk(Item_processor processor, bool walk_subquery, uchar *arg) Item *Item_equal::transform(Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); List_iterator it(fields); Item *item; diff --git a/sql/item_func.cc b/sql/item_func.cc index 1388e0dc479..24d0d94c6c5 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -293,7 +293,7 @@ void Item_func::traverse_cond(Cond_traverser traverser, Item *Item_func::transform(Item_transformer transformer, uchar *argument) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); if (arg_count) { diff --git a/sql/item_row.cc b/sql/item_row.cc index 94515640625..0f5d6f27823 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -170,7 +170,7 @@ bool Item_row::walk(Item_processor processor, bool walk_subquery, uchar *arg) Item *Item_row::transform(Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); for (uint i= 0; i < arg_count; i++) { diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index e5c47c110f4..e1a4fcc8def 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2536,7 +2536,7 @@ String *Item_func_make_set::val_str(String *str) Item *Item_func_make_set::transform(Item_transformer transformer, uchar *arg) { - DBUG_ASSERT(!current_thd->is_stmt_prepare()); + DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare()); Item *new_item= item->transform(transformer, arg); if (!new_item) diff --git a/sql/sql_class.h b/sql/sql_class.h index 35c54b62a03..56d85e7cb6d 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -655,15 +655,10 @@ public: virtual ~Query_arena() {}; inline bool is_stmt_prepare() const { return state == INITIALIZED; } - inline bool is_first_sp_execute() const - { return state == INITIALIZED_FOR_SP; } inline bool is_stmt_prepare_or_first_sp_execute() const { return (int)state < (int)PREPARED; } inline bool is_stmt_prepare_or_first_stmt_execute() const { return (int)state <= (int)PREPARED; } - inline bool is_first_stmt_execute() const { return state == PREPARED; } - inline bool is_stmt_execute() const - { return state == PREPARED || state == EXECUTED; } inline bool is_conventional() const { return state == CONVENTIONAL_EXECUTION; } @@ -1434,6 +1429,19 @@ extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); class THD :public Statement, public Open_tables_state { +private: + inline bool is_stmt_prepare() const + { DBUG_ASSERT(0); return Statement::is_stmt_prepare(); } + + inline bool is_stmt_prepare_or_first_sp_execute() const + { DBUG_ASSERT(0); return Statement::is_stmt_prepare_or_first_sp_execute(); } + + inline bool is_stmt_prepare_or_first_stmt_execute() const + { DBUG_ASSERT(0); return Statement::is_stmt_prepare_or_first_stmt_execute(); } + + inline bool is_conventional() const + { DBUG_ASSERT(0); return Statement::is_conventional(); } + public: MDL_context mdl_context; From bc4095643b955e76f349daaf7472f33da7638597 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Fri, 6 May 2011 15:41:24 +0400 Subject: [PATCH 33/43] Patch for Bug#11848763 / 60025 (SUBSTRING inside a stored function works too slow). The user-visible problem was that the server started to consume memory if a stored-routine of some sort is executed subsequently. The memory was freed only after the corresponding connection was closed. Technically, the problem was that the memory needed for temporary string conversions was allocated on the connection ("persistent") memory root, instead of statement one. The root cause of this problem was the incorrect patch for Bug 55744. That patch wrongly fixed a crash in prepared-statement-mode introduced by another patch. The patch for Bug 55744 used wrong condition to check if prepared statement mode is active (or whether the connection-scoped or statement-scoped memory root should be used). The thing is that for prepared statements such conversions should be done in the connection memory root, so that that the transformations of item-tree were correctly remembered in the PREPARE-phase. The fix is to use proper condition to detect prepared-statement-mode and use proper memory root. --- sql/item.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index 788c7744c14..bd231ec8687 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1781,14 +1781,17 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, } THD *thd= current_thd; - Query_arena *arena, backup; bool res= FALSE; uint i; + /* In case we're in statement prepare, create conversion item in its memory: it will be reused on each execute. */ - arena= thd->activate_stmt_arena_if_needed(&backup); + Query_arena backup; + Query_arena *arena= thd->stmt_arena->is_stmt_prepare() ? + thd->activate_stmt_arena_if_needed(&backup) : + NULL; for (i= 0, arg= args; i < nargs; i++, arg+= item_sep) { From cd501675d8b88698d2a4a0d24ec638ed7e78a95b Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Fri, 6 May 2011 17:39:20 +0400 Subject: [PATCH 34/43] Patch for Bug#12374486 - SEVERE MEMORY LEAK IN PREPARED STATEMENTS THAT CALL STORED PROCEDURES. The bug was introduced by WL#4435. The problem was that if a stored procedure generated a few result sets with different set of columns, a new memory would be allocated after every EXECUTE for every result set. The fix is to introduce a new memory root in scope of MYSQL_STMT, and to store result-set metadata in that memory root. --- include/mysql.h | 4 ++- include/mysql.h.pp | 3 +- libmysql/libmysql.c | 75 ++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/include/mysql.h b/include/mysql.h index d3b24f0198a..1966caefdc1 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -573,6 +573,8 @@ typedef struct st_mysql_bind } MYSQL_BIND; +struct st_mysql_stmt_extension; + /* statement handler */ typedef struct st_mysql_stmt { @@ -618,7 +620,7 @@ typedef struct st_mysql_stmt metadata fields when doing mysql_stmt_store_result. */ my_bool update_max_length; - void *extension; + struct st_mysql_stmt_extension *extension; } MYSQL_STMT; enum enum_stmt_attr_type diff --git a/include/mysql.h.pp b/include/mysql.h.pp index 169a8b30e2b..15ec563dfc2 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -512,6 +512,7 @@ typedef struct st_mysql_bind my_bool is_null_value; void *extension; } MYSQL_BIND; +struct st_mysql_stmt_extension; typedef struct st_mysql_stmt { MEM_ROOT mem_root; @@ -541,7 +542,7 @@ typedef struct st_mysql_stmt unsigned char bind_result_done; my_bool unbuffered_fetch_cancelled; my_bool update_max_length; - void *extension; + struct st_mysql_stmt_extension *extension; } MYSQL_STMT; enum enum_stmt_attr_type { diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index f802387cf9a..ec48720a2f5 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -94,6 +94,11 @@ sig_handler my_pipe_sig_handler(int sig); static my_bool mysql_client_init= 0; static my_bool org_my_init_done= 0; +typedef struct st_mysql_stmt_extension +{ + MEM_ROOT fields_mem_root; +} MYSQL_STMT_EXT; + /* Initialize the MySQL client library @@ -1480,11 +1485,16 @@ mysql_stmt_init(MYSQL *mysql) MYSQL_STMT *stmt; DBUG_ENTER("mysql_stmt_init"); - if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT), + if (!(stmt= + (MYSQL_STMT *) my_malloc(sizeof (MYSQL_STMT), + MYF(MY_WME | MY_ZEROFILL))) || + !(stmt->extension= + (MYSQL_STMT_EXT *) my_malloc(sizeof (MYSQL_STMT_EXT), MYF(MY_WME | MY_ZEROFILL)))) { set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - DBUG_RETURN(0); + my_free(stmt); + DBUG_RETURN(NULL); } init_alloc_root(&stmt->mem_root, 2048, 2048); @@ -1499,6 +1509,8 @@ mysql_stmt_init(MYSQL *mysql) strmov(stmt->sqlstate, not_error_sqlstate); /* The rest of statement members was bzeroed inside malloc */ + init_alloc_root(&stmt->extension->fields_mem_root, 2048, 0); + DBUG_RETURN(stmt); } @@ -1571,6 +1583,7 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) stmt->bind_param_done= stmt->bind_result_done= FALSE; stmt->param_count= stmt->field_count= 0; free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); + free_root(&stmt->extension->fields_mem_root, MYF(0)); int4store(buff, stmt->stmt_id); @@ -1631,21 +1644,21 @@ mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) static void alloc_stmt_fields(MYSQL_STMT *stmt) { MYSQL_FIELD *fields, *field, *end; - MEM_ROOT *alloc= &stmt->mem_root; + MEM_ROOT *fields_mem_root= &stmt->extension->fields_mem_root; MYSQL *mysql= stmt->mysql; - DBUG_ASSERT(mysql->field_count); + DBUG_ASSERT(stmt->field_count); - stmt->field_count= mysql->field_count; + free_root(fields_mem_root, MYF(0)); /* Get the field information for non-select statements like SHOW and DESCRIBE commands */ - if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(alloc, + if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(fields_mem_root, sizeof(MYSQL_FIELD) * stmt->field_count)) || - !(stmt->bind= (MYSQL_BIND *) alloc_root(alloc, + !(stmt->bind= (MYSQL_BIND *) alloc_root(fields_mem_root, sizeof(MYSQL_BIND) * stmt->field_count))) { @@ -1658,18 +1671,36 @@ static void alloc_stmt_fields(MYSQL_STMT *stmt) field && fields < end; fields++, field++) { *field= *fields; /* To copy all numeric parts. */ - field->catalog= strmake_root(alloc, fields->catalog, + field->catalog= strmake_root(fields_mem_root, + fields->catalog, fields->catalog_length); - field->db= strmake_root(alloc, fields->db, fields->db_length); - field->table= strmake_root(alloc, fields->table, fields->table_length); - field->org_table= strmake_root(alloc, fields->org_table, + field->db= strmake_root(fields_mem_root, + fields->db, + fields->db_length); + field->table= strmake_root(fields_mem_root, + fields->table, + fields->table_length); + field->org_table= strmake_root(fields_mem_root, + fields->org_table, fields->org_table_length); - field->name= strmake_root(alloc, fields->name, fields->name_length); - field->org_name= strmake_root(alloc, fields->org_name, + field->name= strmake_root(fields_mem_root, + fields->name, + fields->name_length); + field->org_name= strmake_root(fields_mem_root, + fields->org_name, fields->org_name_length); - field->def= fields->def ? strmake_root(alloc, fields->def, - fields->def_length) : 0; - field->def_length= field->def ? fields->def_length : 0; + if (fields->def) + { + field->def= strmake_root(fields_mem_root, + fields->def, + fields->def_length); + field->def_length= fields->def_length; + } + else + { + field->def= NULL; + field->def_length= 0; + } field->extension= 0; /* Avoid dangling links. */ field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */ } @@ -2387,6 +2418,9 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt) prepared statements can't send result set metadata for these queries on prepare stage. Read it now. */ + + stmt->field_count= stmt->mysql->field_count; + alloc_stmt_fields(stmt); } else @@ -2404,7 +2438,7 @@ static void reinit_result_set_metadata(MYSQL_STMT *stmt) previous branch always works. TODO: send metadata only when it's really necessary and add a warning 'Metadata changed' when it's sent twice. - */ + */ update_stmt_fields(stmt); } } @@ -4605,6 +4639,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) free_root(&stmt->result.alloc, MYF(0)); free_root(&stmt->mem_root, MYF(0)); + free_root(&stmt->extension->fields_mem_root, MYF(0)); if (mysql) { @@ -4639,6 +4674,7 @@ my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) } } + my_free(stmt->extension); my_free(stmt); DBUG_RETURN(test(rc)); @@ -4805,16 +4841,13 @@ int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) stmt->state= MYSQL_STMT_EXECUTE_DONE; stmt->bind_result_done= FALSE; + stmt->field_count= mysql->field_count; if (mysql->field_count) { alloc_stmt_fields(stmt); prepare_to_fetch_result(stmt); } - else - { - stmt->field_count= mysql->field_count; - } DBUG_RETURN(0); } From 75498ef9245a6be70ad725e5b780672a217cf7e0 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Mon, 9 May 2011 12:29:23 +0400 Subject: [PATCH 35/43] Patch for Bug#12362125 (SP INOUT HANDLING IS BROKEN FOR TEXT TYPE). Attempts to assign value to a table column from trigger by using NEW.column_name pseudo-variable might result in garbled data. That happened when: - the column had a BLOB-based type (e.g. TEXT) and - the value being assigned was retrieved from stored routine variable of the same type. The problem was that BLOB values were not copied correctly in this case. Instead of doing a copy of a real value, the value's representation in record buffer was copied. This representation is essentially a pointer to a buffer associated with the virtual table for routine variables where the real value is stored. Since this buffer got freed once trigger was left or could have changed its contents when new value was assigned to corresponding routine variable such a shallow copying resulted in garbled data in NEW.colum_name column. It worked in 5.1 due to a subtle bug in create_virtual_tmp_table(): - in 5.1 create_virtual_tmp_table() returned a table which had db_low_byte_first == false. - in 5.5 and up create_virtual_tmp_table() returns a table which has db_low_byte_first == true. Actually, db_low_byte_first == false only for ISAM storage engine, which was deprecated and removed in 5.0. Having db_low_byte_first == false led to getting false in the complex condition for the 2nd "if" in field_conv(), which in turn led to copy-blob-behavior as a fall-back strategy: - to->table->s->db_low_byte_first was true (correct value) - from->table->s->db_low_byte_first was false (incorrect value) In 5.5 and up that condition is true, which means blob-values are not copied. --- mysql-test/r/trigger.result | 20 +++++++++++++++++++- mysql-test/t/trigger.test | 30 +++++++++++++++++++++++++++++- sql/item.cc | 22 ++++++++++++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 11e0d7313b7..e759153eaaf 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -2208,4 +2208,22 @@ trigger_name # Clean-up. drop temporary table t1; drop table t1; -End of 6.0 tests. + +# +# Bug #12362125: SP INOUT HANDLING IS BROKEN FOR TEXT TYPE. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(c TEXT); +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +BEGIN +DECLARE v TEXT; +SET v = 'aaa'; +SET NEW.c = v; +END| +INSERT INTO t1 VALUES('qazwsxedc'); +SELECT c FROM t1; +c +aaa +DROP TABLE t1; + +End of 5.5 tests. diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index e5039c3ea23..80dbcceb448 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -2583,4 +2583,32 @@ select trigger_name from information_schema.triggers drop temporary table t1; drop table t1; ---echo End of 6.0 tests. + +--echo +--echo # +--echo # Bug #12362125: SP INOUT HANDLING IS BROKEN FOR TEXT TYPE. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(c TEXT); + +delimiter |; +CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW +BEGIN + DECLARE v TEXT; + SET v = 'aaa'; + SET NEW.c = v; +END| +delimiter ;| + +INSERT INTO t1 VALUES('qazwsxedc'); + +SELECT c FROM t1; + +DROP TABLE t1; + +--echo +--echo End of 5.5 tests. diff --git a/sql/item.cc b/sql/item.cc index bd231ec8687..5e5e07203f9 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7134,8 +7134,26 @@ bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it) { Item *item= sp_prepare_func_item(thd, it); - return (!item || (!fixed && fix_fields(thd, 0)) || - (item->save_in_field(field, 0) < 0)); + if (!item) + return true; + + if (!fixed) + { + if (fix_fields(thd, NULL)) + return true; + } + + // NOTE: field->table->copy_blobs should be false here, but let's + // remember the value at runtime to avoid subtle bugs. + bool copy_blobs_saved= field->table->copy_blobs; + + field->table->copy_blobs= true; + + int err_code= item->save_in_field(field, 0); + + field->table->copy_blobs= copy_blobs_saved; + + return err_code < 0; } From e8b54a7ce935a6a66d7abe18d60a6cf4b9ddbac9 Mon Sep 17 00:00:00 2001 From: Serge Kozlov Date: Mon, 9 May 2011 23:14:24 +0400 Subject: [PATCH 36/43] WL#5867 Replaced the error code by error name --- mysql-test/suite/binlog/t/binlog_bug23533.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/binlog/t/binlog_bug23533.test b/mysql-test/suite/binlog/t/binlog_bug23533.test index c05abe788c6..ca610e399e4 100644 --- a/mysql-test/suite/binlog/t/binlog_bug23533.test +++ b/mysql-test/suite/binlog/t/binlog_bug23533.test @@ -35,7 +35,7 @@ connect(default,localhost,root,,test); # Copied data from t1 into t2 large than max_binlog_cache_size START TRANSACTION; ---error 1197 +--error ER_TRANS_CACHE_FULL CREATE TABLE t2 SELECT * FROM t1; COMMIT; SHOW TABLES LIKE 't%'; From 4dde684315e64fb151963c3c54cafa33fd9702c9 Mon Sep 17 00:00:00 2001 From: Serge Kozlov Date: Mon, 9 May 2011 23:26:41 +0400 Subject: [PATCH 37/43] automerge 5.1->5.5 --- mysql-test/suite/binlog/t/binlog_bug23533.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/binlog/t/binlog_bug23533.test b/mysql-test/suite/binlog/t/binlog_bug23533.test index c05abe788c6..ca610e399e4 100644 --- a/mysql-test/suite/binlog/t/binlog_bug23533.test +++ b/mysql-test/suite/binlog/t/binlog_bug23533.test @@ -35,7 +35,7 @@ connect(default,localhost,root,,test); # Copied data from t1 into t2 large than max_binlog_cache_size START TRANSACTION; ---error 1197 +--error ER_TRANS_CACHE_FULL CREATE TABLE t2 SELECT * FROM t1; COMMIT; SHOW TABLES LIKE 't%'; From c9eef1d74af83a8839fe0b47497a496ffbeaa24d Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Tue, 10 May 2011 12:41:09 +0100 Subject: [PATCH 38/43] BUG#12416700: RPL_SHOW_SLAVE_HOSTS FAILS SPORADICALLY (TIMEOUT IN WAIT_SHOW_CONDITION) There was a typo in the name of one of the parameters to the include file wait_show_condition. The parameter name was being set to "connection" instead of "condition". We fix this typo, improve one instruction in the test case and deploy parameter checks inside wait_show_condition.inc. --- mysql-test/include/wait_show_condition.inc | 15 +++++++++++++++ .../suite/rpl/r/rpl_show_slave_hosts.result | 3 +-- mysql-test/suite/rpl/t/rpl_show_slave_hosts.test | 5 ++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/mysql-test/include/wait_show_condition.inc b/mysql-test/include/wait_show_condition.inc index 68e05ce4644..ae1600a7e30 100644 --- a/mysql-test/include/wait_show_condition.inc +++ b/mysql-test/include/wait_show_condition.inc @@ -31,6 +31,21 @@ # Created: 2009-02-18 mleich # +if (!$condition) +{ + --die ERROR IN TEST: the "condition" variable must be set +} + +if (!$field) +{ + --die ERROR IN TEST: the "field" variable must be set +} + +if (!$show_statement) +{ + --die ERROR IN TEST: the "show_statement" variable must be set +} + let $max_run_time= 30; if ($wait_timeout) { diff --git a/mysql-test/suite/rpl/r/rpl_show_slave_hosts.result b/mysql-test/suite/rpl/r/rpl_show_slave_hosts.result index 107cd8f63cc..2ada5670e04 100644 --- a/mysql-test/suite/rpl/r/rpl_show_slave_hosts.result +++ b/mysql-test/suite/rpl/r/rpl_show_slave_hosts.result @@ -8,8 +8,7 @@ SHOW SLAVE HOSTS; Server_id Host Port Master_id 3 slave2 DEFAULT_PORT 1 2 SLAVE_PORT 1 -STOP SLAVE IO_THREAD; -include/wait_for_slave_io_to_stop.inc +include/stop_slave_io.inc SHOW SLAVE HOSTS; Server_id Host Port Master_id 2 SLAVE_PORT 1 diff --git a/mysql-test/suite/rpl/t/rpl_show_slave_hosts.test b/mysql-test/suite/rpl/t/rpl_show_slave_hosts.test index eb2e883847f..105f1873659 100644 --- a/mysql-test/suite/rpl/t/rpl_show_slave_hosts.test +++ b/mysql-test/suite/rpl/t/rpl_show_slave_hosts.test @@ -23,14 +23,13 @@ connection master; let $show_statement= SHOW SLAVE HOSTS; let $field= Server_id; # 3 is server_id of slave2. -let $connection= ='3'; +let $condition= ='3'; source include/wait_show_condition.inc; --replace_result $SLAVE_MYPORT SLAVE_PORT $DEFAULT_MASTER_PORT DEFAULT_PORT SHOW SLAVE HOSTS; connection slave2; -STOP SLAVE IO_THREAD; -source include/wait_for_slave_io_to_stop.inc; +--source include/stop_slave_io.inc connection master; let $show_statement= SHOW SLAVE HOSTS; From f8e86f50e20f189b0061d18b7e8ac04d4ef8d667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Bl=C3=A5udd?= Date: Wed, 11 May 2011 09:49:23 +0200 Subject: [PATCH 39/43] Bug#12384993 EXTRA/RPL_TEST/CHECK_TYPE.INC NEED SUPPORT FOR SPECIFIC ENGINE - add support for choosing the engine of test table(t1) with $engine_type - add primary key to the test table(t1) to support replication of BLOB/TEXT (also with ENGINE=ndb) - change the suppression since the warning printed to error log now says "Column 1" --- mysql-test/extra/rpl_tests/check_type.inc | 14 ++++++++++++-- mysql-test/suite/rpl/r/rpl_typeconv.result | 2 +- mysql-test/suite/rpl/t/rpl_typeconv.test | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/mysql-test/extra/rpl_tests/check_type.inc b/mysql-test/extra/rpl_tests/check_type.inc index 63491d81da4..97300753d38 100644 --- a/mysql-test/extra/rpl_tests/check_type.inc +++ b/mysql-test/extra/rpl_tests/check_type.inc @@ -11,18 +11,28 @@ # on the slave) # $can_convert True if conversion shall work, false if it # shall generate an error +# $engine_type The storage engine to be used for storing table +# on both master and slave +if (!$engine_type) +{ + # Use the default storage engine + let $engine_type=`SELECT @@storage_engine`; +} connection master; disable_warnings; DROP TABLE IF EXISTS t1; enable_warnings; -eval CREATE TABLE t1 (a $source_type); +eval CREATE TABLE t1( + pk INT NOT NULL PRIMARY KEY, + a $source_type +) ENGINE=$engine_type; sync_slave_with_master; eval ALTER TABLE t1 MODIFY a $target_type; connection master; -eval INSERT INTO t1 VALUES($source_value); +eval INSERT INTO t1 VALUES(1, $source_value); if ($can_convert) { sync_slave_with_master; eval SELECT a = $target_value into @compare FROM t1; diff --git a/mysql-test/suite/rpl/r/rpl_typeconv.result b/mysql-test/suite/rpl/r/rpl_typeconv.result index 0d2f3cb26f7..f9d5b50b4e2 100644 --- a/mysql-test/suite/rpl/r/rpl_typeconv.result +++ b/mysql-test/suite/rpl/r/rpl_typeconv.result @@ -534,7 +534,7 @@ BIT(6) BIT(5) ALL_LOSSY,ALL_NON_LOSSY BIT(5) BIT(12) ALL_LOSSY,ALL_NON_LOSSY BIT(12) BIT(5) ALL_LOSSY,ALL_NON_LOSSY DROP TABLE type_conversions; -call mtr.add_suppression("Slave SQL.*Column 0 of table .test.t1. cannot be converted from type.* Error_code: 1677"); +call mtr.add_suppression("Slave SQL.*Column 1 of table .test.t1. cannot be converted from type.* Error_code: 1677"); DROP TABLE t1; set global slave_type_conversions = @saved_slave_type_conversions; include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_typeconv.test b/mysql-test/suite/rpl/t/rpl_typeconv.test index efe3dc15353..efcbe97049f 100644 --- a/mysql-test/suite/rpl/t/rpl_typeconv.test +++ b/mysql-test/suite/rpl/t/rpl_typeconv.test @@ -61,7 +61,7 @@ SELECT RPAD(Source, 15, ' ') AS Source_Type, enable_query_log; DROP TABLE type_conversions; -call mtr.add_suppression("Slave SQL.*Column 0 of table .test.t1. cannot be converted from type.* Error_code: 1677"); +call mtr.add_suppression("Slave SQL.*Column 1 of table .test.t1. cannot be converted from type.* Error_code: 1677"); connection master; DROP TABLE t1; From a914a32191accdce6132486a9bcf71d9d7d4dd70 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Wed, 11 May 2011 14:11:57 +0300 Subject: [PATCH 40/43] Bug #11744875: 4082: integer lengths cause truncation with distinct concat and innodb The 5.5 version of the patch. The server doesn't restrict the data that can be inserted into integer columns with explicitly specified length that's smaller than what the type can handle, e.g. 1234 can be inserted into an INT(2) column just fine. Thus, when calcualting the maximum width of expressions involving such restricted integer columns we need to use the implicit maximum width of the field instead of the explicitly speficied one. Fixed the server to use the implicit maximum in such cases and made sure the implicit maximum is addjusted the same way as the explicit one wrt signedness. Fixed several test case results (ctype_*.result, metadata.result and type_ranges.result) to reflect the extended column widths. Added a regression test case in distinct.test. Note : this is the behavior preserving fix that makes 5.5 behave as 5.1 and earlier. In the mysql trunk we'll add a insert time check for the explict maximum size. --- mysql-test/r/ctype_binary.result | 4 +-- mysql-test/r/ctype_cp1251.result | 4 +-- mysql-test/r/ctype_latin1.result | 4 +-- mysql-test/r/ctype_ucs.result | 4 +-- mysql-test/r/ctype_utf8.result | 4 +-- mysql-test/r/distinct.result | 11 ++++++ mysql-test/r/metadata.result | 2 +- mysql-test/r/type_ranges.result | 2 +- mysql-test/t/distinct.test | 13 +++++++ sql/item.cc | 58 ++++++++++++++++++++++++++++++++ 10 files changed, 94 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/ctype_binary.result b/mysql-test/r/ctype_binary.result index 291dcfdf92a..a80c52ebf9d 100644 --- a/mysql-test/r/ctype_binary.result +++ b/mysql-test/r/ctype_binary.result @@ -2046,7 +2046,7 @@ create table t2 as select concat(a) from t1; show create table t2; Table Create Table t2 CREATE TABLE `t2` ( - `concat(a)` varbinary(2) DEFAULT NULL + `concat(a)` varbinary(4) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1, t2; create table t1 (a year); @@ -2355,7 +2355,7 @@ insert into t1 values (1); create view v1(a) as select concat(a) from t1; show columns from v1; Field Type Null Key Default Extra -a varbinary(2) YES NULL +a varbinary(4) YES NULL select hex(a) from v1; hex(a) 3031 diff --git a/mysql-test/r/ctype_cp1251.result b/mysql-test/r/ctype_cp1251.result index 58f023b2b79..cff4f6b7442 100644 --- a/mysql-test/r/ctype_cp1251.result +++ b/mysql-test/r/ctype_cp1251.result @@ -2438,7 +2438,7 @@ create table t2 as select concat(a) from t1; show create table t2; Table Create Table t2 CREATE TABLE `t2` ( - `concat(a)` varchar(2) CHARACTER SET cp1251 DEFAULT NULL + `concat(a)` varchar(4) CHARACTER SET cp1251 DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1, t2; create table t1 (a year); @@ -2747,7 +2747,7 @@ insert into t1 values (1); create view v1(a) as select concat(a) from t1; show columns from v1; Field Type Null Key Default Extra -a varchar(2) YES NULL +a varchar(4) YES NULL select hex(a) from v1; hex(a) 3031 diff --git a/mysql-test/r/ctype_latin1.result b/mysql-test/r/ctype_latin1.result index 2ecb9d6aeba..12a76302397 100644 --- a/mysql-test/r/ctype_latin1.result +++ b/mysql-test/r/ctype_latin1.result @@ -2465,7 +2465,7 @@ create table t2 as select concat(a) from t1; show create table t2; Table Create Table t2 CREATE TABLE `t2` ( - `concat(a)` varchar(2) DEFAULT NULL + `concat(a)` varchar(4) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1, t2; create table t1 (a year); @@ -2774,7 +2774,7 @@ insert into t1 values (1); create view v1(a) as select concat(a) from t1; show columns from v1; Field Type Null Key Default Extra -a varchar(2) YES NULL +a varchar(4) YES NULL select hex(a) from v1; hex(a) 3031 diff --git a/mysql-test/r/ctype_ucs.result b/mysql-test/r/ctype_ucs.result index e687822c235..dc922f8490a 100644 --- a/mysql-test/r/ctype_ucs.result +++ b/mysql-test/r/ctype_ucs.result @@ -3299,7 +3299,7 @@ create table t2 as select concat(a) from t1; show create table t2; Table Create Table t2 CREATE TABLE `t2` ( - `concat(a)` varchar(2) CHARACTER SET ucs2 DEFAULT NULL + `concat(a)` varchar(4) CHARACTER SET ucs2 DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1, t2; create table t1 (a year); @@ -3608,7 +3608,7 @@ insert into t1 values (1); create view v1(a) as select concat(a) from t1; show columns from v1; Field Type Null Key Default Extra -a varchar(2) YES NULL +a varchar(4) YES NULL select hex(a) from v1; hex(a) 00300031 diff --git a/mysql-test/r/ctype_utf8.result b/mysql-test/r/ctype_utf8.result index db981fbe298..cfbf6cee3a2 100644 --- a/mysql-test/r/ctype_utf8.result +++ b/mysql-test/r/ctype_utf8.result @@ -4177,7 +4177,7 @@ create table t2 as select concat(a) from t1; show create table t2; Table Create Table t2 CREATE TABLE `t2` ( - `concat(a)` varchar(2) CHARACTER SET utf8 DEFAULT NULL + `concat(a)` varchar(4) CHARACTER SET utf8 DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 drop table t1, t2; create table t1 (a year); @@ -4486,7 +4486,7 @@ insert into t1 values (1); create view v1(a) as select concat(a) from t1; show columns from v1; Field Type Null Key Default Extra -a varchar(2) YES NULL +a varchar(4) YES NULL select hex(a) from v1; hex(a) 3031 diff --git a/mysql-test/r/distinct.result b/mysql-test/r/distinct.result index b1cb70fa43c..74f2c19c8fe 100644 --- a/mysql-test/r/distinct.result +++ b/mysql-test/r/distinct.result @@ -794,3 +794,14 @@ DROP TABLE t1; SET @@sort_buffer_size = @old_sort_buffer_size; SET @@max_heap_table_size = @old_max_heap_table_size; End of 5.1 tests +# +# Bug #11744875: 4082: integer lengths cause truncation with distinct concat and innodb +# +CREATE TABLE t1 (a INT(1), b INT(1)); +INSERT INTO t1 VALUES (1111, 2222), (3333, 4444); +SELECT DISTINCT CONCAT(a,b) AS c FROM t1 ORDER BY 1; +c +11112222 +33334444 +DROP TABLE t1; +End of 5.5 tests diff --git a/mysql-test/r/metadata.result b/mysql-test/r/metadata.result index 480cec792c0..3418348854f 100644 --- a/mysql-test/r/metadata.result +++ b/mysql-test/r/metadata.result @@ -126,7 +126,7 @@ renamed 1 select * from v3 where renamed=1 group by renamed; Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr -def v3 v3 renamed renamed 8 11 0 Y 32896 0 63 +def v3 v3 renamed renamed 8 12 0 Y 32896 0 63 renamed drop table t1; drop view v1,v2,v3; diff --git a/mysql-test/r/type_ranges.result b/mysql-test/r/type_ranges.result index ac3d52b9ead..d99c2363d62 100644 --- a/mysql-test/r/type_ranges.result +++ b/mysql-test/r/type_ranges.result @@ -271,7 +271,7 @@ drop table t2; create table t2 (primary key (auto)) select auto+1 as auto,1 as t1, 'a' as t2, repeat('a',256) as t3, binary repeat('b',256) as t4, repeat('a',4096) as t5, binary repeat('b',4096) as t6, '' as t7, binary '' as t8 from t1; show full columns from t2; Field Type Collation Null Key Default Extra Privileges Comment -auto int(6) unsigned NULL NO PRI 0 # +auto int(11) unsigned NULL NO PRI 0 # t1 int(1) NULL NO 0 # t2 varchar(1) latin1_swedish_ci NO # t3 varchar(256) latin1_swedish_ci NO # diff --git a/mysql-test/t/distinct.test b/mysql-test/t/distinct.test index bf4c23562cf..84073d15109 100644 --- a/mysql-test/t/distinct.test +++ b/mysql-test/t/distinct.test @@ -614,3 +614,16 @@ SET @@sort_buffer_size = @old_sort_buffer_size; SET @@max_heap_table_size = @old_max_heap_table_size; --echo End of 5.1 tests + + +--echo # +--echo # Bug #11744875: 4082: integer lengths cause truncation with distinct concat and innodb +--echo # + +CREATE TABLE t1 (a INT(1), b INT(1)); +INSERT INTO t1 VALUES (1111, 2222), (3333, 4444); +SELECT DISTINCT CONCAT(a,b) AS c FROM t1 ORDER BY 1; +DROP TABLE t1; + + +--echo End of 5.5 tests diff --git a/sql/item.cc b/sql/item.cc index 5e5e07203f9..2c0da80b43b 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2011,6 +2011,61 @@ Item_field::Item_field(THD *thd, Item_field *item) collation.set(DERIVATION_IMPLICIT); } + +/** + Calculate the max column length not taking into account the + limitations over integer types. + + When storing data into fields the server currently just ignores the + limits specified on integer types, e.g. 1234 can safely be stored in + an int(2) and will not cause an error. + Thus when creating temporary tables and doing transformations + we must adjust the maximum field length to reflect this fact. + We take the un-restricted maximum length and adjust it similarly to + how the declared length is adjusted wrt unsignedness etc. + TODO: this all needs to go when we disable storing 1234 in int(2). + + @param field_par Original field the use to calculate the lengths + @param max_length Item's calculated explicit max length + @return The adjusted max length +*/ + +inline static uint32 +adjust_max_effective_column_length(Field *field_par, uint32 max_length) +{ + uint32 new_max_length= field_par->max_display_length(); + uint32 sign_length= (field_par->flags & UNSIGNED_FLAG) ? 0 : 1; + + switch (field_par->type()) + { + case MYSQL_TYPE_INT24: + /* + Compensate for MAX_MEDIUMINT_WIDTH being 1 too long (8) + compared to the actual number of digits that can fit into + the column. + */ + new_max_length+= 1; + /* fall through */ + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + + /* Take out the sign and add a conditional sign */ + new_max_length= new_max_length - 1 + sign_length; + break; + + /* BINGINT is always 20 no matter the sign */ + case MYSQL_TYPE_LONGLONG: + /* make gcc happy */ + default: + break; + } + + /* Adjust only if the actual precision based one is bigger than specified */ + return new_max_length > max_length ? new_max_length : max_length; +} + + void Item_field::set_field(Field *field_par) { field=result_field=field_par; // for easy coding with fields @@ -2024,6 +2079,9 @@ void Item_field::set_field(Field *field_par) collation.set(field_par->charset(), field_par->derivation(), field_par->repertoire()); fix_char_length(field_par->char_length()); + + max_length= adjust_max_effective_column_length(field_par, max_length); + fixed= 1; if (field->table->s->tmp_table == SYSTEM_TMP_TABLE) any_privileges= 0; From 94f424652c0244e0efe185c4ddb0177a2d31b81b Mon Sep 17 00:00:00 2001 From: MySQL Build Team Date: Wed, 11 May 2011 13:40:29 +0200 Subject: [PATCH 41/43] Cloning of the 5.5.13 release from Mysql-5.5, increase the version number by two --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3a52d76371e..796544a7013 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=5 -MYSQL_VERSION_PATCH=13 +MYSQL_VERSION_PATCH=15 MYSQL_VERSION_EXTRA= From cbf455cf87a79b368f7456483fc7cf80d7e09aca Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Wed, 11 May 2011 16:45:57 +0400 Subject: [PATCH 42/43] Ignore auto-generated files. --- .bzrignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.bzrignore b/.bzrignore index 05597220950..2369d922ddf 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3129,3 +3129,6 @@ libmysqld/examples/mysql_embedded sql/.empty mysys/thr_lock VERSION.dep +info_macros.cmake +Docs/INFO_BIN +Docs/INFO_SRC From 6fb68a22bd2d176f4fdf123634d1942a6b0b53d3 Mon Sep 17 00:00:00 2001 From: Tatjana Azundris Nuernberg Date: Thu, 12 May 2011 05:56:41 +0100 Subject: [PATCH 43/43] Bug#11902767/Bug#60580: Statement improperly replicated crashes slave SQL thread If LOAD DATA INFILE featured a SET clause, the name=value pairs would be regenerated using item::print. Unfortunately, that code is mostly optimized for EXPLAIN EXTENDED output and such, and can not be relied on to return valid SQL. We now name each value its original, user-supplied form and use that to create LOAD DATA INFILE statements for statement-based replication. --- .../r/binlog_stm_mix_innodb_myisam.result | 4 +- .../suite/rpl/r/rpl_loaddatalocal.result | 27 ++++++++++ mysql-test/suite/rpl/t/rpl_loaddatalocal.test | 51 +++++++++++++++++++ sql/sql_load.cc | 5 +- sql/sql_yacc.yy | 20 +++++++- 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result index 20d82557122..da2e24506fd 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result @@ -698,7 +698,7 @@ master-bin.000001 # Query # # BEGIN master-bin.000001 # Intvar # # INSERT_ID=10 master-bin.000001 # Begin_load_query # # ;file_id=#;block_len=# master-bin.000001 # Intvar # # INSERT_ID=10 -master-bin.000001 # Execute_load_query # # use `test`; LOAD DATA INFILE '../../std_data/rpl_loaddata.dat' INTO TABLE `t4` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`, @b) SET `b`=((@b) + `bug27417`(2)) ;file_id=# +master-bin.000001 # Execute_load_query # # use `test`; LOAD DATA INFILE '../../std_data/rpl_loaddata.dat' INTO TABLE `t4` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`, @b) SET `b`= @b + bug27417(2) ;file_id=# master-bin.000001 # Query # # ROLLBACK /* the output must denote there is the query */; drop trigger trg_del_t2; @@ -950,7 +950,7 @@ master-bin.000001 # User var # # @`b`=_latin1 0x3135 COLLATE latin1_swedish_ci master-bin.000001 # Begin_load_query # # ;file_id=#;block_len=# master-bin.000001 # Intvar # # INSERT_ID=10 master-bin.000001 # User var # # @`b`=_latin1 0x3135 COLLATE latin1_swedish_ci -master-bin.000001 # Execute_load_query # # use `test`; LOAD DATA INFILE '../../std_data/rpl_loaddata.dat' INTO TABLE `t4` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`, @b) SET `b`=((@b) + `bug27417`(2)) ;file_id=# +master-bin.000001 # Execute_load_query # # use `test`; LOAD DATA INFILE '../../std_data/rpl_loaddata.dat' INTO TABLE `t4` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' (`a`, @b) SET `b`= @b + bug27417(2) ;file_id=# master-bin.000001 # Query # # ROLLBACK drop trigger trg_del_t2; drop table t1,t2,t3,t4,t5; diff --git a/mysql-test/suite/rpl/r/rpl_loaddatalocal.result b/mysql-test/suite/rpl/r/rpl_loaddatalocal.result index 37936871993..84748259795 100644 --- a/mysql-test/suite/rpl/r/rpl_loaddatalocal.result +++ b/mysql-test/suite/rpl/r/rpl_loaddatalocal.result @@ -78,4 +78,31 @@ LOAD DATA LOCAL INFILE 'MYSQLD_DATADIR/bug43746.sql' INTO TABLE t1; DROP TABLE t1; SET SESSION sql_mode=@old_mode; [slave] + +Bug #60580/#11902767: +"statement improperly replicated crashes slave sql thread" + +[master] +CREATE TABLE t1(f1 INT, f2 INT); +CREATE TABLE t2(f1 INT, f2 TIMESTAMP); +INSERT INTO t2 VALUES(1, '2011-03-22 21:01:28'); +INSERT INTO t2 VALUES(2, '2011-03-21 21:01:28'); +INSERT INTO t2 VALUES(3, '2011-03-20 21:01:28'); +CREATE TABLE t3 AS SELECT * FROM t2; +CREATE VIEW v1 AS SELECT * FROM t2 +WHERE f1 IN (SELECT f1 FROM t3 WHERE (t3.f2 IS NULL)); +SELECT 1 INTO OUTFILE 'MYSQLD_DATADIR/bug60580.csv' FROM DUAL; +LOAD DATA LOCAL INFILE 'MYSQLD_DATADIR/bug60580.csv' INTO TABLE t1 (@f1) SET f2 = (SELECT f1 FROM v1 WHERE f1=@f1); +SELECT * FROM t1; +f1 f2 +NULL NULL +[slave] +SELECT * FROM t1; +f1 f2 +NULL NULL +[master] +DROP VIEW v1; +DROP TABLE t1, t2, t3; +[slave] include/rpl_end.inc +# End of 5.1 tests diff --git a/mysql-test/suite/rpl/t/rpl_loaddatalocal.test b/mysql-test/suite/rpl/t/rpl_loaddatalocal.test index 8848903a30c..ed6edeb3fbb 100644 --- a/mysql-test/suite/rpl/t/rpl_loaddatalocal.test +++ b/mysql-test/suite/rpl/t/rpl_loaddatalocal.test @@ -163,5 +163,56 @@ SET SESSION sql_mode=@old_mode; --echo [slave] sync_slave_with_master; +connection master; + +--echo +--echo Bug #60580/#11902767: +--echo "statement improperly replicated crashes slave sql thread" +--echo + +--echo [master] +connection master; +let $MYSQLD_DATADIR= `select @@datadir`; + +CREATE TABLE t1(f1 INT, f2 INT); +CREATE TABLE t2(f1 INT, f2 TIMESTAMP); + +INSERT INTO t2 VALUES(1, '2011-03-22 21:01:28'); +INSERT INTO t2 VALUES(2, '2011-03-21 21:01:28'); +INSERT INTO t2 VALUES(3, '2011-03-20 21:01:28'); + +CREATE TABLE t3 AS SELECT * FROM t2; + +CREATE VIEW v1 AS SELECT * FROM t2 + WHERE f1 IN (SELECT f1 FROM t3 WHERE (t3.f2 IS NULL)); + +--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR +eval SELECT 1 INTO OUTFILE '$MYSQLD_DATADIR/bug60580.csv' FROM DUAL; + +--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR +eval LOAD DATA LOCAL INFILE '$MYSQLD_DATADIR/bug60580.csv' INTO TABLE t1 (@f1) SET f2 = (SELECT f1 FROM v1 WHERE f1=@f1); + +SELECT * FROM t1; + +sleep 1; + +--echo [slave] +sync_slave_with_master; + +SELECT * FROM t1; + +--remove_file $MYSQLD_DATADIR/bug60580.csv + +--echo [master] +connection master; + +DROP VIEW v1; +DROP TABLE t1, t2, t3; + +--echo [slave] +sync_slave_with_master; + connection master; --source include/rpl_end.inc + +--echo # End of 5.1 tests diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 5ec6e4a0467..c089ac09b89 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 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 @@ -743,8 +743,7 @@ static bool write_execute_load_query_log_event(THD *thd, sql_exchange* ex, pfields.append("`"); pfields.append(item->name); pfields.append("`"); - pfields.append("="); - val->print(&pfields, QT_ORDINARY); + pfields.append(val->name); } } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9aa938437b1..340ae819bfc 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010 Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 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 @@ -11587,7 +11587,23 @@ field_or_var: opt_load_data_set_spec: /* empty */ {} - | SET insert_update_list {} + | SET load_data_set_list {} + ; + +load_data_set_list: + load_data_set_list ',' load_data_set_elem + | load_data_set_elem + ; + +load_data_set_elem: + simple_ident_nospvar equal remember_name expr_or_default remember_end + { + LEX *lex= Lex; + if (lex->update_list.push_back($1) || + lex->value_list.push_back($4)) + MYSQL_YYABORT; + $4->set_name($3, (uint) ($5 - $3), YYTHD->charset()); + } ; /* Common definitions */