mariadb/sql/sql_binlog.cc
Guilhem Bichot 12c42b980a Fix for BUG#11755168 '46895: test "outfile_loaddata" fails (reproducible)'.
In sql_class.cc, 'row_count', of type 'ha_rows', was used as last argument for
ER_TRUNCATED_WRONG_VALUE_FOR_FIELD which is
"Incorrect %-.32s value: '%-.128s' for column '%.192s' at row %ld".
So 'ha_rows' was used as 'long'.
On SPARC32 Solaris builds, 'long' is 4 bytes and 'ha_rows' is 'longlong' i.e. 8 bytes.
So the printf-like code was reading only the first 4 bytes.
Because the CPU is big-endian, 1LL is 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01
so the first four bytes yield 0. So the warning message had "row 0" instead of
"row 1" in test outfile_loaddata.test:
-Warning	1366	Incorrect string value: '\xE1\xE2\xF7' for column 'b' at row 1
+Warning	1366	Incorrect string value: '\xE1\xE2\xF7' for column 'b' at row 0

All error-messaging functions which internally invoke some printf-life function
are potential candidate for such mistakes.
One apparently easy way to catch such mistakes is to use
ATTRIBUTE_FORMAT (from my_attribute.h).
But this works only when call site has both:
a) the format as a string literal
b) the types of arguments.
So:
  func(ER(ER_BLAH), 10);
will silently not be checked, because ER(ER_BLAH) is not known at
compile time (it is known at run-time, and depends on the chosen
language).
And
  func("%s", a va_list argument);
has the same problem, as the *real* type of arguments is not
known at this site at compile time (it's known in some caller).
Moreover,
  func(ER(ER_BLAH));
though possibly correct (if ER(ER_BLAH) has no '%' markers), will not
compile (gcc says "error: format not a string literal and no format
arguments").

Consequences:
1) ATTRIBUTE_FORMAT is here added only to functions which in practice
take "string literal" formats: "my_error_reporter" and "print_admin_msg".
2) it cannot be added to the other functions: my_error(),
push_warning_printf(), Table_check_intact::report_error(),
general_log_print().

To do a one-time check of functions listed in (2), the following
"static code analysis" has been done:
1) replace
  my_error(ER_xxx, arguments for substitution in format)
with the equivalent
  my_printf_error(ER_xxx,ER(ER_xxx), arguments for substitution in
format),
so that we have ER(ER_xxx) and the arguments *in the same call site*
2) add ATTRIBUTE_FORMAT to push_warning_printf(),
Table_check_intact::report_error(), general_log_print()
3) replace ER(xxx) with the hard-coded English text found in
errmsg.txt (like: ER(ER_UNKNOWN_ERROR) is replaced with
"Unknown error"), so that a call site has the format as string literal
4) this way, ATTRIBUTE_FORMAT can effectively do its job
5) compile, fix errors detected by ATTRIBUTE_FORMAT
6) revert steps 1-2-3.
The present patch has no compiler error when submitted again to the
static code analysis above.
It cannot catch all problems though: see Field::set_warning(), in
which a call to push_warning_printf() has a variable error
(thus, not replacable by a string literal); I checked set_warning() calls
by hand though.

See also WL 5883 for one proposal to avoid such bugs from appearing
again in the future.

The issues fixed in the patch are:
a) mismatch in types (like 'int' passed to '%ld')
b) more arguments passed than specified in the format.
This patch resolves mismatches by changing the type/number of arguments,
not by changing error messages of sql/share/errmsg.txt. The latter would be wrong,
per the following old rule: errmsg.txt must be as stable as possible; no insertions
or deletions of messages, no changes of type or number of printf-like format specifiers,
are allowed, as long as the change impacts a message already released in a GA version.
If this rule is not followed:
- Connectors, which use error message numbers, will be confused (by insertions/deletions
of messages)
- using errmsg.sys of MySQL 5.1.n with mysqld of MySQL 5.1.(n+1)
could produce wrong messages or crash; such usage can easily happen if
installing 5.1.(n+1) while /etc/my.cnf still has --language=/path/to/5.1.n/xxx;
or if copying mysqld from 5.1.(n+1) into a 5.1.n installation.
When fixing b), I have verified that the superfluous arguments were not used in the format
in the first 5.1 GA (5.1.30 'bteam@astra04-20081114162938-z8mctjp6st27uobm').
Had they been used, then passing them today, even if the message doesn't use them
anymore, would have been necessary, as explained above.

include/my_getopt.h:
  this function pointer is used only with "string literal" formats, so we can add
  ATTRIBUTE_FORMAT.
mysql-test/collections/default.experimental:
  test should pass now
sql/derror.cc:
  by having a format as string literal, ATTRIBUTE_FORMAT check becomes effective.
sql/events.cc:
  Change justified by the following excerpt from sql/share/errmsg.txt:
  ER_EVENT_SAME_NAME
          eng "Same old and new event name"
  ER_EVENT_SET_VAR_ERROR
          eng "Error during starting/stopping of the scheduler. Error code %u"
sql/field.cc:
  ER_TOO_BIG_SCALE 42000 S1009
          eng "Too big scale %d specified for column '%-.192s'. Maximum is %lu."
  ER_TOO_BIG_PRECISION 42000 S1009
          eng "Too big precision %d specified for column '%-.192s'. Maximum is %lu."
  ER_TOO_BIG_DISPLAYWIDTH 42000 S1009
          eng "Display width out of range for column '%-.192s' (max = %lu)"
sql/ha_ndbcluster.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
  (sizeof() returns size_t)
sql/ha_ndbcluster_binlog.cc:
  Too many arguments for:
  ER_GET_ERRMSG  
          eng "Got error %d '%-.100s' from %s"
  Patch by Jonas Oreland.
sql/ha_partition.cc:
  print_admin_msg() is used only with a literal as format, so ATTRIBUTE_FORMAT
  works.
sql/handler.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
  (sizeof() returns size_t)
sql/item_create.cc:
  ER_TOO_BIG_SCALE 42000 S1009
          eng "Too big scale %d specified for column '%-.192s'. Maximum is %lu."
  ER_TOO_BIG_PRECISION 42000 S1009
          eng "Too big precision %d specified for column '%-.192s'. Maximum is %lu."
  'c_len' and 'c_dec' are char*, passed as %d !! We don't know their value
  (as strtoul() failed), but they are likely big, so we use INT_MAX.
  'len' is ulong.
sql/item_func.cc:
  ER_WARN_DATA_OUT_OF_RANGE 22003 
          eng "Out of range value for column '%s' at row %ld"
  ER_CANT_FIND_UDF  
          eng "Can't load function '%-.192s'"
sql/item_strfunc.cc:
  ER_TOO_BIG_FOR_UNCOMPRESS  
          eng "Uncompressed data size too large; the maximum size is %d (probably, length of uncompressed data was corrupted)"
  max_allowed_packet is ulong.
sql/mysql_priv.h:
  sql_print_message_func is a function _pointer_.
sql/sp_head.cc:
  ER_SP_RECURSION_LIMIT
          eng "Recursive limit %d (as set by the max_sp_recursion_depth variable) was exceeded for routine %.192s"
  max_sp_recursion_depth is ulong
sql/sql_acl.cc:
  ER_PASSWORD_NO_MATCH 42000 
          eng "Can't find any matching row in the user table"
  ER_CANT_CREATE_USER_WITH_GRANT 42000
          eng "You are not allowed to create a user with GRANT"
sql/sql_base.cc:
  ER_NOT_KEYFILE  
          eng "Incorrect key file for table '%-.200s'; try to repair it"
  ER_TOO_MANY_TABLES  
          eng "Too many tables; MySQL can only use %d tables in a join"
  MAX_TABLES is size_t.
sql/sql_binlog.cc:
  ER_UNKNOWN_ERROR  
          eng "Unknown error"
sql/sql_class.cc:
  ER_TRUNCATED_WRONG_VALUE_FOR_FIELD  
          eng "Incorrect %-.32s value: '%-.128s' for column '%.192s' at row %ld"
  WARN_DATA_TRUNCATED 01000 
          eng "Data truncated for column '%s' at row %ld"
sql/sql_connect.cc:
  ER_HANDSHAKE_ERROR 08S01 
          eng "Bad handshake"
  ER_BAD_HOST_ERROR 08S01 
          eng "Can't get hostname for your address"
sql/sql_insert.cc:
  ER_WRONG_VALUE_COUNT_ON_ROW 21S01 
          eng "Column count doesn't match value count at row %ld"
sql/sql_parse.cc:
  ER_WARN_HOSTNAME_WONT_WORK  
          eng "MySQL is started in --skip-name-resolve mode; you must restart it without this switch for this grant to work"
  ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT
  	eng "Too high level of nesting for select"
  ER_UNKNOWN_ERROR  
          eng "Unknown error"
sql/sql_partition.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
sql/sql_plugin.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
sql/sql_prepare.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
  ER_UNKNOWN_STMT_HANDLER  
          eng "Unknown prepared statement handler (%.*s) given to %s"
  length value (for '%.*s') must be 'int', per the doc of printf()
  and the code of my_vsnprintf().
sql/sql_show.cc:
  ER_OUTOFMEMORY HY001 S1001
          eng "Out of memory; restart server and try again (needed %d bytes)"
sql/sql_table.cc:
  ER_TOO_BIG_FIELDLENGTH 42000 S1009
          eng "Column length too big for column '%-.192s' (max = %lu); use BLOB or TEXT instead"
sql/table.cc:
  ER_NOT_FORM_FILE  
          eng "Incorrect information in file: '%-.200s'"
  ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE
          eng "Column count of mysql.%s is wrong. Expected %d, found %d. Created with MySQL %d, now running %d. Please use mysql_upgrade to fix this error."
  table->s->mysql_version is ulong.
sql/unireg.cc:
  ER_TOO_LONG_TABLE_COMMENT
    eng "Comment for table '%-.64s' is too long (max = %lu)"
  ER_TOO_LONG_FIELD_COMMENT
    eng "Comment for field '%-.64s' is too long (max = %lu)"
  ER_TOO_BIG_ROWSIZE 42000 
          eng "Row size too large. The maximum row size for the used table type, not counting BLOBs, is %ld. You have to change some columns to TEXT or BLOBs"
2011-05-16 22:04:01 +02:00

250 lines
7.4 KiB
C++

/* Copyright (c) 2005, 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 */
#include "mysql_priv.h"
#include "rpl_rli.h"
#include "base64.h"
/**
Execute a BINLOG statement.
To execute the BINLOG command properly the server needs to know
which format the BINLOG command's event is in. Therefore, the first
BINLOG statement seen must be a base64 encoding of the
Format_description_log_event, as outputted by mysqlbinlog. This
Format_description_log_event is cached in
rli->description_event_for_exec.
@param thd Pointer to THD object for the client thread executing the
statement.
*/
void mysql_client_binlog_statement(THD* thd)
{
DBUG_ENTER("mysql_client_binlog_statement");
DBUG_PRINT("info",("binlog base64: '%*s'",
(int) (thd->lex->comment.length < 2048 ?
thd->lex->comment.length : 2048),
thd->lex->comment.str));
if (check_global_access(thd, SUPER_ACL))
DBUG_VOID_RETURN;
size_t coded_len= thd->lex->comment.length;
if (!coded_len)
{
my_error(ER_SYNTAX_ERROR, MYF(0));
DBUG_VOID_RETURN;
}
size_t decoded_len= base64_needed_decoded_length(coded_len);
/*
thd->options will be changed when applying the event. But we don't expect
it be changed permanently after BINLOG statement, so backup it first.
It will be restored at the end of this function.
*/
ulonglong thd_options= thd->options;
/*
Allocation
*/
/*
If we do not have a Format_description_event, we create a dummy
one here. In this case, the first event we read must be a
Format_description_event.
*/
my_bool have_fd_event= TRUE;
int err;
Relay_log_info *rli;
rli= thd->rli_fake;
if (!rli)
{
rli= thd->rli_fake= new Relay_log_info;
#ifdef HAVE_purify
rli->is_fake= TRUE;
#endif
have_fd_event= FALSE;
}
if (rli && !rli->relay_log.description_event_for_exec)
{
rli->relay_log.description_event_for_exec=
new Format_description_log_event(4);
have_fd_event= FALSE;
}
const char *error= 0;
char *buf= (char *) my_malloc(decoded_len, MYF(MY_WME));
Log_event *ev = 0;
/*
Out of memory check
*/
if (!(rli &&
rli->relay_log.description_event_for_exec &&
buf))
{
my_error(ER_OUTOFMEMORY, MYF(0), 1); /* needed 1 bytes */
goto end;
}
rli->sql_thd= thd;
rli->no_storage= TRUE;
for (char const *strptr= thd->lex->comment.str ;
strptr < thd->lex->comment.str + thd->lex->comment.length ; )
{
char const *endptr= 0;
int bytes_decoded= base64_decode(strptr, coded_len, buf, &endptr);
#ifndef HAVE_purify
/*
This debug printout should not be used for valgrind builds
since it will read from unassigned memory.
*/
DBUG_PRINT("info",
("bytes_decoded: %d strptr: 0x%lx endptr: 0x%lx ('%c':%d)",
bytes_decoded, (long) strptr, (long) endptr, *endptr,
*endptr));
#endif
if (bytes_decoded < 0)
{
my_error(ER_BASE64_DECODE_ERROR, MYF(0));
goto end;
}
else if (bytes_decoded == 0)
break; // If no bytes where read, the string contained only whitespace
DBUG_ASSERT(bytes_decoded > 0);
DBUG_ASSERT(endptr > strptr);
coded_len-= endptr - strptr;
strptr= endptr;
/*
Now we have one or more events stored in the buffer. The size of
the buffer is computed based on how much base64-encoded data
there were, so there should be ample space for the data (maybe
even too much, since a statement can consist of a considerable
number of events).
TODO: Switch to use a stream-based base64 encoder/decoder in
order to be able to read exactly what is necessary.
*/
DBUG_PRINT("info",("binlog base64 decoded_len: %lu bytes_decoded: %d",
(ulong) decoded_len, bytes_decoded));
/*
Now we start to read events of the buffer, until there are no
more.
*/
for (char *bufptr= buf ; bytes_decoded > 0 ; )
{
/*
Checking that the first event in the buffer is not truncated.
*/
ulong event_len;
if (bytes_decoded < EVENT_LEN_OFFSET + 4 ||
(event_len= uint4korr(bufptr + EVENT_LEN_OFFSET)) >
(uint) bytes_decoded)
{
my_error(ER_SYNTAX_ERROR, MYF(0));
goto end;
}
DBUG_PRINT("info", ("event_len=%lu, bytes_decoded=%d",
event_len, bytes_decoded));
/*
If we have not seen any Format_description_event, then we must
see one; it is the only statement that can be read in base64
without a prior Format_description_event.
*/
if (!have_fd_event)
{
int type = bufptr[EVENT_TYPE_OFFSET];
if (type == FORMAT_DESCRIPTION_EVENT || type == START_EVENT_V3)
have_fd_event= TRUE;
else
{
my_error(ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT,
MYF(0), Log_event::get_type_str((Log_event_type)type));
goto end;
}
}
ev= Log_event::read_log_event(bufptr, event_len, &error,
rli->relay_log.description_event_for_exec);
DBUG_PRINT("info",("binlog base64 err=%s", error));
if (!ev)
{
/*
This could actually be an out-of-memory, but it is more likely
causes by a bad statement
*/
my_error(ER_SYNTAX_ERROR, MYF(0));
goto end;
}
bytes_decoded -= event_len;
bufptr += event_len;
DBUG_PRINT("info",("ev->get_type_code()=%d", ev->get_type_code()));
ev->thd= thd;
/*
We go directly to the application phase, since we don't need
to check if the event shall be skipped or not.
Neither do we have to update the log positions, since that is
not used at all: the rli_fake instance is used only for error
reporting.
*/
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
err= ev->apply_event(rli);
#else
err= 0;
#endif
/*
Format_description_log_event should not be deleted because it
will be used to read info about the relay log's format; it
will be deleted when the SQL thread does not need it,
i.e. when this thread terminates.
*/
if (ev->get_type_code() != FORMAT_DESCRIPTION_EVENT)
delete ev;
ev= 0;
if (err)
{
/*
TODO: Maybe a better error message since the BINLOG statement
now contains several events.
*/
my_error(ER_UNKNOWN_ERROR, MYF(0));
goto end;
}
}
}
DBUG_PRINT("info",("binlog base64 execution finished successfully"));
my_ok(thd);
end:
thd->options= thd_options;
rli->clear_tables_to_lock();
my_free(buf, MYF(MY_ALLOW_ZERO_PTR));
DBUG_VOID_RETURN;
}