diff options
Diffstat (limited to 'libmariadb/libmariadb')
45 files changed, 32780 insertions, 0 deletions
diff --git a/libmariadb/libmariadb/CMakeLists.txt b/libmariadb/libmariadb/CMakeLists.txt new file mode 100644 index 00000000..43ed67b4 --- /dev/null +++ b/libmariadb/libmariadb/CMakeLists.txt @@ -0,0 +1,526 @@ +INCLUDE_DIRECTORIES(${CC_SOURCE_DIR}/include + ${CC_SOURCE_DIR}/libmariadb) + +ADD_DEFINITIONS(-D HAVE_COMPRESS) +ADD_DEFINITIONS(-D LIBMARIADB) +ADD_DEFINITIONS(-D THREAD) + +IF(APPLE) + ADD_DEFINITIONS(-D _XOPEN_SOURCE) +ENDIF() + +INCLUDE(${CC_SOURCE_DIR}/cmake/sign.cmake) + +SET(MARIADB_LIB_SYMBOLS + mariadb_cancel + mariadb_connection + mariadb_convert_string + ma_pvio_register_callback + mariadb_get_charset_by_name + mariadb_stmt_execute_direct + mariadb_get_charset_by_nr + mariadb_get_info + mariadb_get_infov + mysql_get_timeout_value + mysql_get_timeout_value_ms + mysql_optionsv + mysql_ps_fetch_functions + mariadb_reconnect + mysql_stmt_warning_count + mariadb_stmt_fetch_fields + mariadb_rpl_open + mariadb_rpl_close + mariadb_rpl_fetch + mariadb_rpl_optionsv + mariadb_rpl_get_optionsv + mariadb_rpl_init_ex + mariadb_free_rpl_event + mariadb_field_attr +) + +SET(MARIADB_LIB_3_3_5_SYMBOLS + mariadb_rpl_extract_rows + mariadb_rpl_error + mariadb_rpl_errno) +IF(WITH_SSL) + SET(MARIADB_LIB_SYMBOLS ${MARIADB_LIB_SYMBOLS} mariadb_deinitialize_ssl) +ENDIF() + +SET(MYSQL_LIB_SYMBOLS + mysql_affected_rows + mysql_autocommit + mysql_change_user + mysql_character_set_name + mysql_client_find_plugin + mysql_client_register_plugin + mysql_close + mysql_commit + mysql_data_seek + mysql_debug + mysql_dump_debug_info + mysql_embedded + mysql_eof + mysql_errno + mysql_error + mysql_escape_string + mysql_fetch_field + mysql_fetch_field_direct + mysql_fetch_fields + mysql_fetch_lengths + mysql_fetch_row + mysql_field_count + mysql_field_seek + mysql_field_tell + mysql_free_result + mysql_get_character_set_info + mysql_get_charset_by_name + mysql_get_charset_by_nr + mysql_get_client_info + mysql_get_client_version + mysql_get_host_info + mysql_get_option + mysql_get_optionv + mysql_get_parameters + mysql_get_proto_info + mysql_get_server_info + mysql_get_server_name + mysql_get_server_version + mysql_get_socket + mysql_get_ssl_cipher + mysql_hex_string + mysql_info + mysql_init + mysql_insert_id + mysql_kill + mysql_list_dbs + mysql_list_fields + mysql_list_processes + mysql_list_tables + mysql_load_plugin + mysql_load_plugin_v + mysql_more_results + mysql_net_field_length + mysql_net_read_packet + mysql_next_result + mysql_num_fields + mysql_num_rows + mysql_options + mysql_options4 + mysql_ping + mysql_query + mysql_read_query_result + mysql_real_connect + mysql_real_escape_string + mysql_real_query + mysql_refresh + mysql_reset_connection + mysql_rollback + mysql_row_seek + mysql_row_tell + mysql_select_db + mysql_send_query + mysql_server_end + mysql_server_init + mysql_session_track_get_next + mysql_session_track_get_first + mysql_set_character_set + mysql_set_local_infile_default + mysql_set_local_infile_handler + mysql_set_server_option + mysql_shutdown + mysql_sqlstate + mysql_ssl_set + mysql_stat + mysql_stmt_affected_rows + mysql_stmt_attr_get + mysql_stmt_attr_set + mysql_stmt_bind_param + mysql_stmt_bind_result + mysql_stmt_close + mysql_stmt_data_seek + mysql_stmt_errno + mysql_stmt_error + mysql_stmt_execute + mysql_stmt_fetch + mysql_stmt_fetch_column + mysql_stmt_field_count + mysql_stmt_free_result + mysql_stmt_init + mysql_stmt_insert_id + mysql_stmt_more_results + mysql_stmt_next_result + mysql_stmt_num_rows + mysql_stmt_param_count + mysql_stmt_param_metadata + mysql_stmt_prepare + mysql_stmt_reset + mysql_stmt_result_metadata + mysql_stmt_row_seek + mysql_stmt_row_tell + mysql_stmt_send_long_data + mysql_stmt_sqlstate + mysql_stmt_store_result + mysql_store_result + mysql_thread_end + mysql_thread_id + mysql_thread_init + mysql_thread_safe + mysql_use_result + mysql_warning_count) + +# some gcc versions fail to compile asm parts of my_context.c, +# if build type is "Release" (see CONC-133), so we need to add -g flag +IF(CMAKE_COMPILER_IS_GNUCC AND CMAKE_BUILD_TYPE MATCHES "Release") + SET_SOURCE_FILES_PROPERTIES(my_context.c PROPERTIES COMPILE_FLAGS -g) +ENDIF() + +IF(ZLIB_FOUND AND WITH_EXTERNAL_ZLIB) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) +ELSE() + SET(ZLIB_SOURCES + ${CC_SOURCE_DIR}/external/zlib/adler32.c + ${CC_SOURCE_DIR}/external/zlib/compress.c + ${CC_SOURCE_DIR}/external/zlib/crc32.c + ${CC_SOURCE_DIR}/external/zlib/deflate.c + ${CC_SOURCE_DIR}/external/zlib/gzclose.c + ${CC_SOURCE_DIR}/external/zlib/gzlib.c + ${CC_SOURCE_DIR}/external/zlib/gzread.c + ${CC_SOURCE_DIR}/external/zlib/gzwrite.c + ${CC_SOURCE_DIR}/external/zlib/infback.c + ${CC_SOURCE_DIR}/external/zlib/inffast.c + ${CC_SOURCE_DIR}/external/zlib/inflate.c + ${CC_SOURCE_DIR}/external/zlib/inftrees.c + ${CC_SOURCE_DIR}/external/zlib/trees.c + ${CC_SOURCE_DIR}/external/zlib/uncompr.c + ${CC_SOURCE_DIR}/external/zlib/zutil.c + ) + INCLUDE_DIRECTORIES(${CC_SOURCE_DIR}/external/zlib ${CC_BINARY_DIR}/external/zlib) +ENDIF() + + +SET(MARIADB_DYNCOL_SYMBOLS + mariadb_dyncol_check + mariadb_dyncol_column_cmp_named + mariadb_dyncol_column_count + mariadb_dyncol_create_many_named + mariadb_dyncol_create_many_num + mariadb_dyncol_exists_named + mariadb_dyncol_exists_num + mariadb_dyncol_free + mariadb_dyncol_get_named + mariadb_dyncol_get_num + mariadb_dyncol_has_names + mariadb_dyncol_json + mariadb_dyncol_list_named + mariadb_dyncol_list_num + mariadb_dyncol_unpack + mariadb_dyncol_update_many_named + mariadb_dyncol_update_many_num + mariadb_dyncol_val_double + mariadb_dyncol_val_long + mariadb_dyncol_val_str) + +SET(MARIADB_NONBLOCK_SYMBOLS + mysql_autocommit_cont + mysql_autocommit_start + mysql_change_user_cont + mysql_change_user_start + mysql_close_cont + mysql_close_start + mysql_commit_cont + mysql_commit_start + mysql_dump_debug_info_cont + mysql_dump_debug_info_start + mysql_fetch_row_cont + mysql_fetch_row_start + mysql_free_result_cont + mysql_free_result_start + mysql_kill_cont + mysql_kill_start + mysql_list_fields_cont + mysql_list_fields_start + mysql_next_result_cont + mysql_next_result_start + mysql_ping_cont + mysql_ping_start + mysql_reset_connection_start + mysql_reset_connection_cont + mysql_query_cont + mysql_query_start + mysql_read_query_result_cont + mysql_read_query_result_start + mysql_real_connect_cont + mysql_real_connect_start + mysql_real_query_cont + mysql_real_query_start + mysql_refresh_cont + mysql_refresh_start + mysql_rollback_cont + mysql_rollback_start + mysql_select_db_cont + mysql_select_db_start + mysql_send_query_cont + mysql_send_query_start + mysql_set_character_set_cont + mysql_set_character_set_start + mysql_set_server_option_cont + mysql_set_server_option_start + mysql_shutdown_cont + mysql_shutdown_start + mysql_stat_cont + mysql_stat_start + mysql_stmt_close_cont + mysql_stmt_close_start + mysql_stmt_execute_cont + mysql_stmt_execute_start + mysql_stmt_fetch_cont + mysql_stmt_fetch_start + mysql_stmt_free_result_cont + mysql_stmt_free_result_start + mysql_stmt_next_result_cont + mysql_stmt_next_result_start + mysql_stmt_prepare_cont + mysql_stmt_prepare_start + mysql_stmt_reset_cont + mysql_stmt_reset_start + mysql_stmt_send_long_data_cont + mysql_stmt_send_long_data_start + mysql_stmt_store_result_cont + mysql_stmt_store_result_start + mysql_store_result_cont + mysql_store_result_start +) + +# handle static plugins +SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} ${LIBMARIADB_PLUGIN_SOURCES}) +SET(SYSTEM_LIBS ${SYSTEM_LIBS} ${LIBMARIADB_PLUGIN_LIBS} ${ZSTD_LIBRARY} ${ZLIB_LIBRARY}) +MESSAGE(STATUS "SYSTEM_LIBS: ${SYSTEM_LIBS}") +INCLUDE_DIRECTORIES(${LIBMARIADB_PLUGIN_INCLUDES}) +ADD_DEFINITIONS(${LIBMARIADB_PLUGIN_DEFS}) +FOREACH(plugin ${PLUGINS_STATIC}) + SET(EXTERNAL_PLUGINS "${EXTERNAL_PLUGINS} extern struct st_mysql_client_plugin ${plugin}_client_plugin;\n") + SET(BUILTIN_PLUGINS "${BUILTIN_PLUGINS} (struct st_mysql_client_plugin *)&${plugin}_client_plugin,\n") +ENDFOREACH() +CONFIGURE_FILE(${CC_SOURCE_DIR}/libmariadb/ma_client_plugin.c.in + ${CC_BINARY_DIR}/libmariadb/ma_client_plugin.c) + +SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} +${CC_SOURCE_DIR}/plugins/auth/my_auth.c +ma_array.c +ma_charset.c +ma_decimal.c +ma_hashtbl.c +ma_net.c +mariadb_charset.c +ma_time.c +ma_default.c +ma_errmsg.c +mariadb_lib.c +ma_list.c +ma_pvio.c +ma_tls.c +ma_alloc.c +ma_compress.c +ma_init.c +ma_password.c +ma_ll2str.c +ma_sha1.c +mariadb_stmt.c +ma_loaddata.c +ma_stmt_codec.c +ma_string.c +ma_dtoa.c +mariadb_rpl.c +${CC_BINARY_DIR}/libmariadb/ma_client_plugin.c +ma_io.c +${SSL_SOURCES} +) + +IF(WIN32) + ADD_DEFINITIONS(-DSIZEOF_CHARP=${CMAKE_SIZEOF_VOID_P}) + INCLUDE_DIRECTORIES(${CC_SOURCE_DIR}/win-iconv) + SET(LIBMARIADB_SOURCES + ${LIBMARIADB_SOURCES} + ${CC_SOURCE_DIR}/win-iconv/win_iconv.c + win32_errmsg.c + win32_errmsg.h) +ELSE() + IF(ICONV_INCLUDE_DIR) + INCLUDE_DIRECTORIES(BEFORE ${ICONV_INCLUDE_DIR}) + ENDIF() + IF(NOT CMAKE_SYSTEM_NAME MATCHES AIX) + ADD_DEFINITIONS(-DLIBICONV_PLUG) + ENDIF() + IF(APPLE) + ADD_DEFINITIONS(-Wno-deprecated-declarations) + ENDIF() +ENDIF() + +IF(ZLIB_FOUND AND WITH_EXTERNAL_ZLIB) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) +ELSE() + SET(ZLIB_SOURCES + ${CC_SOURCE_DIR}/external/zlib/adler32.c + ${CC_SOURCE_DIR}/external/zlib/compress.c + ${CC_SOURCE_DIR}/external/zlib/crc32.c + ${CC_SOURCE_DIR}/external/zlib/deflate.c + ${CC_SOURCE_DIR}/external/zlib/gzclose.c + ${CC_SOURCE_DIR}/external/zlib/gzlib.c + ${CC_SOURCE_DIR}/external/zlib/gzread.c + ${CC_SOURCE_DIR}/external/zlib/gzwrite.c + ${CC_SOURCE_DIR}/external/zlib/infback.c + ${CC_SOURCE_DIR}/external/zlib/inffast.c + ${CC_SOURCE_DIR}/external/zlib/inflate.c + ${CC_SOURCE_DIR}/external/zlib/inftrees.c + ${CC_SOURCE_DIR}/external/zlib/trees.c + ${CC_SOURCE_DIR}/external/zlib/uncompr.c + ${CC_SOURCE_DIR}/external/zlib/zutil.c + ) + IF(MSVC) + SET_SOURCE_FILES_PROPERTIES(${ZLIB_SOURCES} PROPERTIES COMPILE_FLAGS -W0) + ENDIF() + SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} ${ZLIB_SOURCES}) + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/external/zlib) +ENDIF() + +IF(WITH_DYNCOL) + MESSAGE1(WITH_DYNCOL "Dynamic column API support: ON") + SET(MARIADB_LIB_SYMBOLS ${MARIADB_LIB_SYMBOLS} ${MARIADB_DYNCOL_SYMBOLS}) + SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} mariadb_dyncol.c) +ENDIF() + +SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} mariadb_async.c ma_context.c) +SET(MARIADB_LIB_SYMBOLS ${MARIADB_LIB_SYMBOLS} ${MARIADB_NONBLOCK_SYMBOLS}) + +INCLUDE(${CC_SOURCE_DIR}/cmake/export.cmake) +IF(NOT WIN32) + CREATE_EXPORT_FILE(WRITE mariadbclient.def + "libmysqlclient_18" + "${MYSQL_LIB_SYMBOLS}" + "libmariadbclient_18") + CREATE_EXPORT_FILE(APPEND mariadbclient.def + "libmariadb_3" + "${MARIADB_LIB_SYMBOLS}" + "") + CREATE_EXPORT_FILE(APPEND mariadbclient.def + "libmariadb_3_3_5" + "${MARIADB_LIB_3_3_5_SYMBOLS}" + "") +ELSE() + CREATE_EXPORT_FILE(WRITE mariadbclient.def + "libmariadb_3" + "${MARIADB_LIB_SYMBOLS};${MYSQL_LIB_SYMBOLS};${MARIADB_LIB_3_3_5_SYMBOLS}" + "") +ENDIF() + + +IF(CMAKE_VERSION VERSION_GREATER 2.8.7) + # CREATE OBJECT LIBRARY + ADD_LIBRARY(mariadb_obj OBJECT ${LIBMARIADB_SOURCES}) + IF(UNIX) + SET_TARGET_PROPERTIES(mariadb_obj PROPERTIES COMPILE_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") + ENDIF() + SET (MARIADB_OBJECTS $<TARGET_OBJECTS:mariadb_obj>) +ELSE() + SET (MARIADB_OBJECTS ${LIBMARIADB_SOURCES}) +ENDIF() + +# Xcode doesn't support targets that have only object files, +# so let's add an empty file to keep Xcode happy +IF(CMAKE_GENERATOR MATCHES Xcode) + FILE(WRITE ${CC_BINARY_DIR}/libmariadb/empty.c "") + SET(EMPTY_FILE ${CC_BINARY_DIR}/libmariadb/empty.c) +ENDIF() + +#* create file with list of functions */ +FILE(WRITE ${CC_BINARY_DIR}/manpages.list "${MARIADB_LIB_SYMBOLS};${MYSQL_LIB_SYMBOLS}") + +IF(WIN32) + SET_VERSION_INFO("TARGET:libmariadb" + "FILE_TYPE:VFT_DLL" + "SOURCE_FILE:libmariadb/libmariadb.c" + "ORIGINAL_FILE_NAME:libmariadb.dll" + "FILE_DESCRIPTION:Dynamic lib for client/server communication") +ENDIF() + +ADD_LIBRARY(mariadbclient STATIC ${MARIADB_OBJECTS} ${EMPTY_FILE}) +TARGET_LINK_LIBRARIES(mariadbclient ${SYSTEM_LIBS} ${CRYPTO_LIBS}) + +IF(UNIX) + ADD_LIBRARY(libmariadb SHARED ${libmariadb_RC} ${MARIADB_OBJECTS} ${EMPTY_FILE}) + SET_TARGET_PROPERTIES(libmariadb PROPERTIES COMPILE_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") +ELSE() + ADD_LIBRARY(libmariadb SHARED ${libmariadb_RC} ${MARIADB_OBJECTS} mariadbclient.def) + SET_TARGET_PROPERTIES(libmariadb PROPERTIES LINKER_LANGUAGE C) +ENDIF() + +TARGET_LINK_LIBRARIES(libmariadb LINK_PRIVATE ${SYSTEM_LIBS} ${CRYPTO_LIBS}) + +SIGN_TARGET(libmariadb) + +IF(CMAKE_SYSTEM_NAME MATCHES "Linux" OR + CMAKE_SYSTEM_NAME MATCHES "kFreeBSD" OR + CMAKE_SYSTEM_NAME MATCHES "GNU") + IF (NOT WITH_ASAN AND NOT WITH_TSAN AND NOT WITH_UBSAN AND NOT WITH_MSAN) + TARGET_LINK_LIBRARIES (libmariadb LINK_PRIVATE "-Wl,--no-undefined") + ENDIF() + SET_TARGET_PROPERTIES(libmariadb PROPERTIES LINK_FLAGS "${CC_BINARY_DIR}/libmariadb/mariadbclient.def") +ENDIF() + +SET_TARGET_PROPERTIES(mariadbclient PROPERTIES IMPORTED_INTERFACE_LINK_LIBRARIES "${SYSTEM_LIBS}") +SET_TARGET_PROPERTIES(libmariadb PROPERTIES IMPORTED_INTERFACE_LINK_LIBRARIES "${SYSTEM_LIBS}") + +SET_TARGET_PROPERTIES(libmariadb PROPERTIES PREFIX "") + +# +# Installation +# +INCLUDE(${CC_SOURCE_DIR}/cmake/symlink.cmake) + +# There are still several projects which don't make use +# of the config program. To make sure these programs can +# use mariadb client library we provide libmysql symlinks + +IF(WITH_MYSQLCOMPAT) + create_symlink(libmysqlclient${CMAKE_SHARED_LIBRARY_SUFFIX} libmariadb ${INSTALL_LIBDIR}) + create_symlink(libmysqlclient_r${CMAKE_SHARED_LIBRARY_SUFFIX} libmariadb ${INSTALL_LIBDIR}) + IF(NOT CMAKE_SYSTEM_NAME MATCHES AIX) + create_symlink(libmysqlclient${CMAKE_STATIC_LIBRARY_SUFFIX} mariadbclient ${INSTALL_LIBDIR}) + create_symlink(libmysqlclient_r${CMAKE_STATIC_LIBRARY_SUFFIX} mariadbclient ${INSTALL_LIBDIR}) + ENDIF() +ENDIF() + +create_symlink(libmariadb${CMAKE_STATIC_LIBRARY_SUFFIX} mariadbclient ${INSTALL_LIBDIR}) + +SET_TARGET_PROPERTIES(libmariadb PROPERTIES VERSION + ${CPACK_PACKAGE_VERSION_MAJOR} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}) + +IF(NOT WIN32) + SET_TARGET_PROPERTIES(mariadbclient PROPERTIES OUTPUT_NAME "${LIBMARIADB_STATIC_NAME}") +ENDIF() + +INSTALL(TARGETS mariadbclient + COMPONENT Development + DESTINATION ${INSTALL_LIBDIR}) +IF(WIN32) +INSTALL(TARGETS libmariadb + COMPONENT SharedLibraries + DESTINATION ${INSTALL_LIBDIR}) +ELSE() +# in cmake 3.12+ we can use +#INSTALL(TARGETS libmariadb LIBRARY DESTINATION ${INSTALL_LIBDIR} +# COMPONENT SharedLibraries NAMELINK_COMPONENT Development) +# but as long as we build on CentOS 7 with its cmake 2.8.12.2 we have to use +INSTALL(TARGETS libmariadb LIBRARY DESTINATION ${INSTALL_LIBDIR} + COMPONENT SharedLibraries NAMELINK_SKIP) +INSTALL(TARGETS libmariadb LIBRARY DESTINATION ${INSTALL_LIBDIR} + COMPONENT Development NAMELINK_ONLY) +ENDIF() + +IF(MSVC) + # On Windows, install PDB + INSTALL(FILES $<TARGET_PDB_FILE:libmariadb> DESTINATION "${INSTALL_LIBDIR}" + CONFIGURATIONS Debug RelWithDebInfo + COMPONENT Development) +ENDIF() diff --git a/libmariadb/libmariadb/bmove_upp.c b/libmariadb/libmariadb/bmove_upp.c new file mode 100644 index 00000000..599b38be --- /dev/null +++ b/libmariadb/libmariadb/bmove_upp.c @@ -0,0 +1,33 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* File : bmove.c + Author : Michael widenius + Updated: 1987-03-20 + Defines: bmove_upp() + + bmove_upp(dst, src, len) moves exactly "len" bytes from the source + "src-len" to the destination "dst-len" counting downwards. +*/ + +#include <ma_global.h> +#include "ma_string.h" + +void ma_bmove_upp(register char *dst, register const char *src, register size_t len) +{ + while (len-- != 0) *--dst = *--src; +} diff --git a/libmariadb/libmariadb/get_password.c b/libmariadb/libmariadb/get_password.c new file mode 100644 index 00000000..4a93a738 --- /dev/null +++ b/libmariadb/libmariadb/get_password.c @@ -0,0 +1,172 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ +#include <ma_global.h> +#include <ma_sys.h> +#include "mysql.h" +#include <ma_string.h> +#include <mariadb_ctype.h> +#include <stdio.h> +#include <memory.h> + +#ifndef _WIN32 +#include <termios.h> +#else +#include <conio.h> +#endif /* _WIN32 */ + +/* {{{ static char *get_password() */ +/* + read password from device + + SYNOPSIS + get_password + Hdl/file file handle + buffer input buffer + length buffer length + + RETURN + buffer zero terminated input buffer +*/ +#ifdef _WIN32 +static char *get_password(HANDLE Hdl, char *buffer, DWORD length) +#else +static char *get_password(FILE *file, char *buffer, int length) +#endif +{ + char inChar; + int CharsProcessed= 1; +#ifdef _WIN32 + DWORD Offset= 0; +#else + int Offset= 0; +#endif + memset(buffer, 0, length); + + do + { +#ifdef _WIN32 + if (!ReadConsole(Hdl, &inChar, 1, (DWORD *)&CharsProcessed, NULL) || + !CharsProcessed) + break; +#else + inChar= (char)fgetc(file); +#endif + + switch(inChar) { + case '\b': /* backslash */ + if (Offset) + { + /* cursor is always at the end */ + Offset--; + buffer[Offset]= 0; +#ifdef _WIN32 + _cputs("\b \b"); +#endif + } + break; + case '\n': + case '\r': + break; + default: + buffer[Offset]= inChar; + if (Offset < length - 2) + Offset++; +#ifdef _WIN32 + _cputs("*"); +#endif + break; + } + } while (CharsProcessed && inChar != '\n' && inChar != '\r'); + return buffer; +} +/* }}} */ + +/* {{{ static char* get_tty_password */ +/* + reads password from tty/console + + SYNOPSIS + get_tty_password() + buffer input buffer + length length of input buffer + + DESCRIPTION + reads a password from console (Windows) or tty without echoing + it's characters. Input buffer must be allocated by calling function. + + RETURNS + buffer pointer to input buffer +*/ +char* get_tty_password(char *prompt, char *buffer, int length) +{ +#ifdef _WIN32 + DWORD SaveState; + HANDLE Hdl; + + if (prompt) + fprintf(stderr, "%s", prompt); + + if (!(Hdl= CreateFile("CONIN$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, 0, NULL))) + { + /* todo: provide a graphical dialog */ + return buffer; + } + /* Save ConsoleMode and set ENABLE_PROCESSED_INPUT: + CTRL+C is processed by the system and is not placed in the input buffer */ + GetConsoleMode(Hdl, &SaveState); + SetConsoleMode(Hdl, ENABLE_PROCESSED_INPUT); + + buffer= get_password(Hdl, buffer, length); + SetConsoleMode(Hdl, SaveState); + CloseHandle(Hdl); + return buffer; +#else + struct termios term_old, + term_new; + FILE *readfrom; + + if (prompt && isatty(fileno(stderr))) + fputs(prompt, stderr); + + if (!(readfrom= fopen("/dev/tty", "r"))) + readfrom= stdin; + + /* try to disable echo */ + tcgetattr(fileno(readfrom), &term_old); + term_new= term_old; + term_new.c_cc[VMIN] = 1; + term_new.c_cc[VTIME]= 0; + term_new.c_lflag&= ~(ECHO | ISIG | ICANON | ECHONL); + tcsetattr(fileno(readfrom), TCSADRAIN, &term_new); + + buffer= get_password(readfrom, buffer, length); + + if (isatty(fileno(readfrom))) + tcsetattr(fileno(readfrom), TCSADRAIN, &term_old); + + fclose(readfrom); + + return buffer; +#endif +} +/* }}} */ diff --git a/libmariadb/libmariadb/ma_alloc.c b/libmariadb/libmariadb/ma_alloc.c new file mode 100644 index 00000000..d6db279e --- /dev/null +++ b/libmariadb/libmariadb/ma_alloc.c @@ -0,0 +1,196 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* Routines to handle mallocing of results which will be freed the same time */ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_string.h> + +#define INIT_BLOCK_NUM 4 +void ma_init_alloc_root(MA_MEM_ROOT *mem_root, size_t block_size, size_t pre_alloc_size) +{ + mem_root->free= mem_root->used= mem_root->pre_alloc= 0; + mem_root->min_malloc=32; + mem_root->block_size= (block_size-MALLOC_OVERHEAD-sizeof(MA_USED_MEM)+8); + mem_root->error_handler=0; + mem_root->block_num= INIT_BLOCK_NUM; + mem_root->first_block_usage= 0; +#if !(defined(HAVE_purify) && defined(EXTRA_DEBUG)) + if (pre_alloc_size) + { + if ((mem_root->free = mem_root->pre_alloc= + (MA_USED_MEM*) malloc(pre_alloc_size+ ALIGN_SIZE(sizeof(MA_USED_MEM))))) + { + mem_root->free->size=pre_alloc_size+ALIGN_SIZE(sizeof(MA_USED_MEM)); + mem_root->free->left=pre_alloc_size; + mem_root->free->next=0; + } + } +#endif +} + +void * ma_alloc_root(MA_MEM_ROOT *mem_root, size_t Size) +{ +#if defined(HAVE_purify) && defined(EXTRA_DEBUG) + reg1 MA_USED_MEM *next; + Size+=ALIGN_SIZE(sizeof(MA_USED_MEM)); + + if (!(next = (MA_USED_MEM*) malloc(Size))) + { + if (mem_root->error_handler) + (*mem_root->error_handler)(); + return((void *) 0); /* purecov: inspected */ + } + next->next=mem_root->used; + mem_root->used=next; + return (void *) (((char*) next)+ALIGN_SIZE(sizeof(MA_USED_MEM))); +#else + size_t get_size; + void * point; + reg1 MA_USED_MEM *next= 0; + reg2 MA_USED_MEM **prev; + + Size= ALIGN_SIZE(Size); + + if ((*(prev= &mem_root->free))) + { + if ((*prev)->left < Size && + mem_root->first_block_usage++ >= 16 && + (*prev)->left < 4096) + { + next= *prev; + *prev= next->next; + next->next= mem_root->used; + mem_root->used= next; + mem_root->first_block_usage= 0; + } + for (next= *prev; next && next->left < Size; next= next->next) + prev= &next->next; + } + + if (! next) + { /* Time to alloc new block */ + get_size= MAX(Size+ALIGN_SIZE(sizeof(MA_USED_MEM)), + (mem_root->block_size & ~1) * (mem_root->block_num >> 2)); + + if (!(next = (MA_USED_MEM*) malloc(get_size))) + { + if (mem_root->error_handler) + (*mem_root->error_handler)(); + return((void *) 0); /* purecov: inspected */ + } + mem_root->block_num++; + next->next= *prev; + next->size= get_size; + next->left= get_size-ALIGN_SIZE(sizeof(MA_USED_MEM)); + *prev=next; + } + point= (void *) ((char*) next+ (next->size-next->left)); + if ((next->left-= Size) < mem_root->min_malloc) + { /* Full block */ + *prev=next->next; /* Remove block from list */ + next->next=mem_root->used; + mem_root->used=next; + mem_root->first_block_usage= 0; + } + return(point); +#endif +} + + /* deallocate everything used by alloc_root */ + +void ma_free_root(MA_MEM_ROOT *root, myf MyFlags) +{ + reg1 MA_USED_MEM *next,*old; + + if (!root) + return; /* purecov: inspected */ + if (!(MyFlags & MY_KEEP_PREALLOC)) + root->pre_alloc=0; + + for ( next=root->used; next ;) + { + old=next; next= next->next ; + if (old != root->pre_alloc) + free(old); + } + for (next= root->free ; next ; ) + { + old=next; next= next->next ; + if (old != root->pre_alloc) + free(old); + } + root->used=root->free=0; + if (root->pre_alloc) + { + root->free=root->pre_alloc; + root->free->left=root->pre_alloc->size-ALIGN_SIZE(sizeof(MA_USED_MEM)); + root->free->next=0; + } + root->block_num= INIT_BLOCK_NUM; + root->first_block_usage= 0; +} + + +char *ma_strdup_root(MA_MEM_ROOT *root,const char *str) +{ + size_t len= strlen(str)+1; + char *pos; + if ((pos=ma_alloc_root(root,len))) + memcpy(pos,str,len); + return pos; +} + + +char *ma_memdup_root(MA_MEM_ROOT *root, const char *str, size_t len) +{ + char *pos; + if ((pos= ma_alloc_root(root,len))) + memcpy(pos,str,len); + return pos; +} + +void *ma_multi_malloc(myf myFlags, ...) +{ + va_list args; + char **ptr,*start,*res; + size_t tot_length,length; + + va_start(args,myFlags); + tot_length=0; + while ((ptr=va_arg(args, char **))) + { + length=va_arg(args, size_t); + tot_length+=ALIGN_SIZE(length); + } + va_end(args); + + if (!(start=(char *)malloc(tot_length))) + return 0; + + va_start(args,myFlags); + res=start; + while ((ptr=va_arg(args, char **))) + { + *ptr=res; + length=va_arg(args,size_t); + res+=ALIGN_SIZE(length); + } + va_end(args); + return start; +} diff --git a/libmariadb/libmariadb/ma_array.c b/libmariadb/libmariadb/ma_array.c new file mode 100644 index 00000000..d067aa4a --- /dev/null +++ b/libmariadb/libmariadb/ma_array.c @@ -0,0 +1,172 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* Handling of arrays that can grow dynamically. */ + +#undef SAFEMALLOC /* Problems with threads */ + +#include <ma_global.h> +#include <ma_sys.h> +#include "ma_string.h" +#include <memory.h> + +/* + Initiate array and alloc space for init_alloc elements. Array is usable + even if space allocation failed +*/ + +my_bool ma_init_dynamic_array(DYNAMIC_ARRAY *array, uint element_size, + uint init_alloc, uint alloc_increment CALLER_INFO_PROTO) +{ + if (!alloc_increment) + { + alloc_increment=max((8192-MALLOC_OVERHEAD)/element_size,16); + if (init_alloc > 8 && alloc_increment > init_alloc * 2) + alloc_increment=init_alloc*2; + } + + if (!init_alloc) + init_alloc=alloc_increment; + array->elements=0; + array->max_element=init_alloc; + array->alloc_increment=alloc_increment; + array->size_of_element=element_size; + if (!(array->buffer=(char*) malloc(element_size*init_alloc))) + { + array->max_element=0; + return(TRUE); + } + return(FALSE); +} + + +my_bool ma_insert_dynamic(DYNAMIC_ARRAY *array, void *element) +{ + void *buffer; + if (array->elements == array->max_element) + { /* Call only when necessary */ + if (!(buffer=ma_alloc_dynamic(array))) + return TRUE; + } + else + { + buffer=array->buffer+(array->elements * array->size_of_element); + array->elements++; + } + memcpy(buffer,element,(size_t) array->size_of_element); + return FALSE; +} + + + /* Alloc room for one element */ + +unsigned char *ma_alloc_dynamic(DYNAMIC_ARRAY *array) +{ + if (array->elements == array->max_element) + { + char *new_ptr; + if (!(new_ptr=(char*) realloc(array->buffer,(array->max_element+ + array->alloc_increment)* + array->size_of_element))) + return 0; + array->buffer=new_ptr; + array->max_element+=array->alloc_increment; + } + return (unsigned char *)array->buffer+(array->elements++ * array->size_of_element); +} + + + /* remove last element from array and return it */ + +unsigned char *ma_pop_dynamic(DYNAMIC_ARRAY *array) +{ + if (array->elements) + return (unsigned char *)array->buffer+(--array->elements * array->size_of_element); + return 0; +} + + +my_bool ma_set_dynamic(DYNAMIC_ARRAY *array, void * element, uint idx) +{ + if (idx >= array->elements) + { + if (idx >= array->max_element) + { + uint size; + char *new_ptr; + size=(idx+array->alloc_increment)/array->alloc_increment; + size*= array->alloc_increment; + if (!(new_ptr=(char*) realloc(array->buffer,size* + array->size_of_element))) + return TRUE; + array->buffer=new_ptr; + array->max_element=size; + } + memset((array->buffer+array->elements*array->size_of_element), 0, + (idx - array->elements)*array->size_of_element); + array->elements=idx+1; + } + memcpy(array->buffer+(idx * array->size_of_element),element, + (size_t) array->size_of_element); + return FALSE; +} + + +void ma_get_dynamic(DYNAMIC_ARRAY *array, void * element, uint idx) +{ + if (idx >= array->elements) + { + memset(element, 0, array->size_of_element); + return; + } + memcpy(element,array->buffer+idx*array->size_of_element, + (size_t) array->size_of_element); +} + + +void ma_delete_dynamic(DYNAMIC_ARRAY *array) +{ + if (array->buffer) + { + free(array->buffer); + array->buffer=0; + array->elements=array->max_element=0; + } +} + + +void ma_delete_dynamic_element(DYNAMIC_ARRAY *array, uint idx) +{ + char *ptr=array->buffer+array->size_of_element*idx; + array->elements--; + memmove(ptr,ptr+array->size_of_element, + (array->elements-idx)*array->size_of_element); +} + + +void ma_freeze_size(DYNAMIC_ARRAY *array) +{ + uint elements=max(array->elements,1); + + if (array->buffer && array->max_element != elements) + { + array->buffer=(char*) realloc(array->buffer, + elements*array->size_of_element); + array->max_element=elements; + } +} diff --git a/libmariadb/libmariadb/ma_charset.c b/libmariadb/libmariadb/ma_charset.c new file mode 100644 index 00000000..f6ed6f80 --- /dev/null +++ b/libmariadb/libmariadb/ma_charset.c @@ -0,0 +1,1603 @@ +/**************************************************************************** + Copyright (C) 2012, 2022, MariaDB Corporation. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*****************************************************************************/ + +/* The implementation for character set support was ported from PHP's mysqlnd + extension, written by Andrey Hristov, Georg Richter and Ulf Wendel + + Original file header: + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef _WIN32 +#include <strings.h> +#include <string.h> +#else +#include <string.h> +#endif +#include <ma_global.h> +#include <mariadb_ctype.h> +#include <ma_string.h> + +#ifdef HAVE_ICONV +#ifdef _WIN32 +#include "../win-iconv/iconv.h" +#else +#include <iconv.h> +#endif +#endif + + +#if defined(HAVE_NL_LANGINFO) && defined(HAVE_SETLOCALE) +#include <locale.h> +#include <langinfo.h> +#endif + +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* {{{ utf8 functions */ +static unsigned int check_mb_utf8mb3_sequence(const char *start, const char *end) +{ + uchar c; + + if (start >= end) { + return 0; + } + + c = (uchar) start[0]; + + if (c < 0x80) { + return 1; /* single byte character */ + } + if (c < 0xC2) { + return 0; /* invalid mb character */ + } + if (c < 0xE0) { + if (start + 2 > end) { + return 0; /* too small */ + } + if (!(((uchar)start[1] ^ 0x80) < 0x40)) { + return 0; + } + return 2; + } + if (c < 0xF0) { + if (start + 3 > end) { + return 0; /* too small */ + } + if (!(((uchar)start[1] ^ 0x80) < 0x40 && ((uchar)start[2] ^ 0x80) < 0x40 && + (c >= 0xE1 || (uchar)start[1] >= 0xA0))) { + return 0; /* invalid utf8 character */ + } + return 3; + } + return 0; +} + + +static unsigned int check_mb_utf8_sequence(const char *start, const char *end) +{ + uchar c; + + if (start >= end) { + return 0; + } + + c = (uchar) start[0]; + + if (c < 0x80) { + return 1; /* single byte character */ + } + if (c < 0xC2) { + return 0; /* invalid mb character */ + } + if (c < 0xE0) { + if (start + 2 > end) { + return 0; /* too small */ + } + if (!(((uchar)start[1] ^ 0x80) < 0x40)) { + return 0; + } + return 2; + } + if (c < 0xF0) { + if (start + 3 > end) { + return 0; /* too small */ + } + if (!(((uchar)start[1] ^ 0x80) < 0x40 && ((uchar)start[2] ^ 0x80) < 0x40 && + (c >= 0xE1 || (uchar)start[1] >= 0xA0))) { + return 0; /* invalid utf8 character */ + } + return 3; + } + if (c < 0xF5) { + if (start + 4 > end) { /* We need 4 characters */ + return 0; /* too small */ + } + + /* + UTF-8 quick four-byte mask: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + Encoding allows to encode U+00010000..U+001FFFFF + + The maximum character defined in the Unicode standard is U+0010FFFF. + Higher characters U+00110000..U+001FFFFF are not used. + + 11110000.10010000.10xxxxxx.10xxxxxx == F0.90.80.80 == U+00010000 (min) + 11110100.10001111.10111111.10111111 == F4.8F.BF.BF == U+0010FFFF (max) + + Valid codes: + [F0][90..BF][80..BF][80..BF] + [F1][80..BF][80..BF][80..BF] + [F2][80..BF][80..BF][80..BF] + [F3][80..BF][80..BF][80..BF] + [F4][80..8F][80..BF][80..BF] + */ + + if (!(((uchar)start[1] ^ 0x80) < 0x40 && + ((uchar)start[2] ^ 0x80) < 0x40 && + ((uchar)start[3] ^ 0x80) < 0x40 && + (c >= 0xf1 || (uchar)start[1] >= 0x90) && + (c <= 0xf3 || (uchar)start[1] <= 0x8F))) + { + return 0; /* invalid utf8 character */ + } + return 4; + } + return 0; +} + +static unsigned int check_mb_utf8mb3_valid(const char *start, const char *end) +{ + unsigned int len = check_mb_utf8mb3_sequence(start, end); + return (len > 1)? len:0; +} + +static unsigned int check_mb_utf8_valid(const char *start, const char *end) +{ + unsigned int len = check_mb_utf8_sequence(start, end); + return (len > 1)? len:0; +} + + +static unsigned int mysql_mbcharlen_utf8mb3(unsigned int utf8) +{ + if (utf8 < 0x80) { + return 1; /* single byte character */ + } + if (utf8 < 0xC2) { + return 0; /* invalid multibyte header */ + } + if (utf8 < 0xE0) { + return 2; /* double byte character */ + } + if (utf8 < 0xF0) { + return 3; /* triple byte character */ + } + return 0; +} + + +static unsigned int mysql_mbcharlen_utf8(unsigned int utf8) +{ + if (utf8 < 0x80) { + return 1; /* single byte character */ + } + if (utf8 < 0xC2) { + return 0; /* invalid multibyte header */ + } + if (utf8 < 0xE0) { + return 2; /* double byte character */ + } + if (utf8 < 0xF0) { + return 3; /* triple byte character */ + } + if (utf8 < 0xF8) { + return 4; /* four byte character */ + } + return 0; +} +/* }}} */ + + +/* {{{ big5 functions */ +#define valid_big5head(c) (0xA1 <= (unsigned int)(c) && (unsigned int)(c) <= 0xF9) +#define valid_big5tail(c) ((0x40 <= (unsigned int)(c) && (unsigned int)(c) <= 0x7E) || \ + (0xA1 <= (unsigned int)(c) && (unsigned int)(c) <= 0xFE)) + +#define isbig5code(c,d) (isbig5head(c) && isbig5tail(d)) + +static unsigned int check_mb_big5(const char *start, const char *end) +{ + return (valid_big5head(*((const uchar*) start)) && (end - start) > 1 && valid_big5tail(*((const uchar*) start + 1)) ? 2 : 0); +} + + +static unsigned int mysql_mbcharlen_big5(unsigned int big5) +{ + return (valid_big5head(big5)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ cp932 functions */ +#define valid_cp932head(c) ((0x81 <= (c) && (c) <= 0x9F) || (0xE0 <= (c) && c <= 0xFC)) +#define valid_cp932tail(c) ((0x40 <= (c) && (c) <= 0x7E) || (0x80 <= (c) && c <= 0xFC)) + + +static unsigned int check_mb_cp932(const char *start, const char *end) +{ + return (valid_cp932head((uchar)start[0]) && (end - start > 1) && + valid_cp932tail((uchar)start[1])) ? 2 : 0; +} + + +static unsigned int mysql_mbcharlen_cp932(unsigned int cp932) +{ + return (valid_cp932head((uchar)cp932)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ euckr functions */ +#define valid_euckr(c) ((0xA1 <= (uchar)(c) && (uchar)(c) <= 0xFE)) + +static unsigned int check_mb_euckr(const char *start, const char *end) +{ + if (end - start <= 1) { + return 0; /* invalid length */ + } + if (*(uchar *)start < 0x80) { + return 0; /* invalid euckr character */ + } + if (valid_euckr(start[1])) { + return 2; + } + return 0; +} + + +static unsigned int mysql_mbcharlen_euckr(unsigned int kr) +{ + return (valid_euckr(kr)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ eucjpms functions */ +#define valid_eucjpms(c) (((c) & 0xFF) >= 0xA1 && ((c) & 0xFF) <= 0xFE) +#define valid_eucjpms_kata(c) (((c) & 0xFF) >= 0xA1 && ((c) & 0xFF) <= 0xDF) +#define valid_eucjpms_ss2(c) (((c) & 0xFF) == 0x8E) +#define valid_eucjpms_ss3(c) (((c) & 0xFF) == 0x8F) + +static unsigned int check_mb_eucjpms(const char *start, const char *end) +{ + if (*((uchar *)start) < 0x80) { + return 0; /* invalid eucjpms character */ + } + if (valid_eucjpms(start[0]) && (end - start) > 1 && valid_eucjpms(start[1])) { + return 2; + } + if (valid_eucjpms_ss2(start[0]) && (end - start) > 1 && valid_eucjpms_kata(start[1])) { + return 2; + } + if (valid_eucjpms_ss3(start[0]) && (end - start) > 2 && valid_eucjpms(start[1]) && + valid_eucjpms(start[2])) { + return 2; + } + return 0; +} + + +static unsigned int mysql_mbcharlen_eucjpms(unsigned int jpms) +{ + if (valid_eucjpms(jpms) || valid_eucjpms_ss2(jpms)) { + return 2; + } + if (valid_eucjpms_ss3(jpms)) { + return 3; + } + return 1; +} +/* }}} */ + + +/* {{{ gb2312 functions */ +#define valid_gb2312_head(c) (0xA1 <= (uchar)(c) && (uchar)(c) <= 0xF7) +#define valid_gb2312_tail(c) (0xA1 <= (uchar)(c) && (uchar)(c) <= 0xFE) + + +static unsigned int check_mb_gb2312(const char *start, const char *end) +{ + return (valid_gb2312_head((unsigned int)start[0]) && end - start > 1 && + valid_gb2312_tail((unsigned int)start[1])) ? 2 : 0; +} + + +static unsigned int mysql_mbcharlen_gb2312(unsigned int gb) +{ + return (valid_gb2312_head(gb)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ gbk functions */ +#define valid_gbk_head(c) (0x81<=(uchar)(c) && (uchar)(c)<=0xFE) +#define valid_gbk_tail(c) ((0x40<=(uchar)(c) && (uchar)(c)<=0x7E) || (0x80<=(uchar)(c) && (uchar)(c)<=0xFE)) + +static unsigned int check_mb_gbk(const char *start, const char *end) +{ + return (valid_gbk_head(start[0]) && (end) - (start) > 1 && valid_gbk_tail(start[1])) ? 2 : 0; +} + +static unsigned int mysql_mbcharlen_gbk(unsigned int gbk) +{ + return (valid_gbk_head(gbk) ? 2 : 1); +} +/* }}} */ + + +/* {{{ sjis functions */ +#define valid_sjis_head(c) ((0x81 <= (c) && (c) <= 0x9F) || (0xE0 <= (c) && (c) <= 0xFC)) +#define valid_sjis_tail(c) ((0x40 <= (c) && (c) <= 0x7E) || (0x80 <= (c) && (c) <= 0xFC)) + + +static unsigned int check_mb_sjis(const char *start, const char *end) +{ + return (valid_sjis_head((uchar)start[0]) && (end - start) > 1 && valid_sjis_tail((uchar)start[1])) ? 2 : 0; +} + + +static unsigned int mysql_mbcharlen_sjis(unsigned int sjis) +{ + return (valid_sjis_head((uchar)sjis)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ ucs2 functions */ +static unsigned int check_mb_ucs2(const char *start __attribute((unused)), const char *end __attribute((unused))) +{ + return 2; /* always 2 */ +} + +static unsigned int mysql_mbcharlen_ucs2(unsigned int ucs2 __attribute((unused))) +{ + return 2; /* always 2 */ +} +/* }}} */ + + +/* {{{ ujis functions */ +#define valid_ujis(c) ((0xA1 <= ((c)&0xFF) && ((c)&0xFF) <= 0xFE)) +#define valid_ujis_kata(c) ((0xA1 <= ((c)&0xFF) && ((c)&0xFF) <= 0xDF)) +#define valid_ujis_ss2(c) (((c)&0xFF) == 0x8E) +#define valid_ujis_ss3(c) (((c)&0xFF) == 0x8F) + +static unsigned int check_mb_ujis(const char *start, const char *end) +{ + if (*(uchar*)start < 0x80) { + return 0; /* invalid ujis character */ + } + if (valid_ujis(*(start)) && valid_ujis(*((start)+1))) { + return 2; + } + if (valid_ujis_ss2(*(start)) && valid_ujis_kata(*((start)+1))) { + return 2; + } + if (valid_ujis_ss3(*(start)) && (end-start) > 2 && valid_ujis(*((start)+1)) && valid_ujis(*((start)+2))) { + return 3; + } + return 0; +} + + +static unsigned int mysql_mbcharlen_ujis(unsigned int ujis) +{ + return (valid_ujis(ujis)? 2: valid_ujis_ss2(ujis)? 2: valid_ujis_ss3(ujis)? 3: 1); +} +/* }}} */ + + + +/* {{{ utf16 functions */ +#define UTF16_HIGH_HEAD(x) ((((uchar) (x)) & 0xFC) == 0xD8) +#define UTF16_LOW_HEAD(x) ((((uchar) (x)) & 0xFC) == 0xDC) + +static unsigned int check_mb_utf16(const char *start, const char *end) +{ + if (start + 2 > end) { + return 0; + } + + if (UTF16_HIGH_HEAD(*start)) { + return (start + 4 <= end) && UTF16_LOW_HEAD(start[2]) ? 4 : 0; + } + + if (UTF16_LOW_HEAD(*start)) { + return 0; + } + return 2; +} + + +static uint mysql_mbcharlen_utf16(unsigned int utf16) +{ + return UTF16_HIGH_HEAD(utf16) ? 4 : 2; +} +/* }}} */ + + +/* {{{ utf32 functions */ +static uint +check_mb_utf32(const char *start __attribute((unused)), const char *end __attribute((unused))) +{ + return 4; +} + + +static uint +mysql_mbcharlen_utf32(unsigned int utf32 __attribute((unused))) +{ + return 4; +} +/* }}} */ + +/* {{{ gb18030 functions */ +#define is_gb18030_odd(c) (0x81 <= (unsigned char) (c) && (unsigned char) (c) <= 0xFE) +#define is_gb18030_even_2(c) ((0x40 <= (unsigned char) (c) && (unsigned char) (c) <= 0x7E) || (0x80 <= (unsigned char) (c) && (unsigned char) (c) <= 0xFE)) +#define is_gb18030_even_4(c) (0x30 <= (unsigned char) (c) && (unsigned char) (c) <= 0x39) + + +static unsigned int mysql_mbcharlen_gb18030(unsigned int c) +{ + if (c <= 0xFF) { + return !is_gb18030_odd(c); + } + if (c > 0xFFFF || !is_gb18030_odd((c >> 8) & 0xFF)) { + return 0; + } + if (is_gb18030_even_2((c & 0xFF))) { + return 2; + } + if (is_gb18030_even_4((c & 0xFF))) { + return 4; + } + + return 0; +} + +static unsigned int check_mb_gb18030_valid(const char * start, const char * end) +{ + if (end - start <= 1 || !is_gb18030_odd(start[0])) { + return 0; + } + + if (is_gb18030_even_2(start[1])) { + return 2; + } else if (end - start > 3 && is_gb18030_even_4(start[1]) && is_gb18030_odd(start[2]) && is_gb18030_even_4(start[3])) { + return 4; + } + + return 0; +} +/* }}} */ + +/* + The server compiles sometimes the full utf-8 (the mb4) as utf8mb4, and the old as utf8, + for BC reasons. Sometimes, utf8mb4 is just utf8 but the old charsets are utf8mb3. + Change easily now, with a macro, could be made compilation dependable. +*/ + +#define UTF8_MB4 "utf8mb4" +#define UTF8_MB3 "utf8mb3" + +/* {{{ mysql_charsets */ +const MARIADB_CHARSET_INFO mariadb_compiled_charsets[] = +{ + { 1, 1, "big5","big5_chinese_ci", "", 950, "BIG5", 1, 2, mysql_mbcharlen_big5, check_mb_big5}, + { 3, 1, "dec8", "dec8_swedish_ci", "", 0, "DEC", 1, 1, NULL, NULL}, + { 4, 1, "cp850", "cp850_general_ci", "", 850, "CP850", 1, 1, NULL, NULL}, + { 6, 1, "hp8", "hp8_english_ci", "", 0, "HP-ROMAN8", 1, 1, NULL, NULL}, + { 7, 1, "koi8r", "koi8r_general_ci", "", 20866, "KOI8R", 1, 1, NULL, NULL}, + { 8, 1, "latin1", "latin1_swedish_ci", "", 1252, "LATIN1", 1, 1, NULL, NULL}, + { 9, 1, "latin2", "latin2_general_ci", "", 852, "LATIN2", 1, 1, NULL, NULL}, + { 10, 1, "swe7", "swe7_swedish_ci", "", 20107, "", 1, 1, NULL, NULL}, + { 11, 1, "ascii", "ascii_general_ci", "", 1252, "ASCII", 1, 1, NULL, NULL}, + { 12, 1, "ujis", "ujis_japanese_ci", "", 20932, "UJIS", 1, 3, mysql_mbcharlen_ujis, check_mb_ujis}, + { 13, 1, "sjis", "sjis_japanese_ci", "", 932, "SJIS", 1, 2, mysql_mbcharlen_sjis, check_mb_sjis}, + { 16, 1, "hebrew", "hebrew_general_ci", "", 1255, "HEBREW", 1, 1, NULL, NULL}, + { 18, 1, "tis620", "tis620_thai_ci", "", 874, "TIS620", 1, 1, NULL, NULL}, + { 19, 1, "euckr", "euckr_korean_ci", "", 51949, "EUCKR", 1, 2, mysql_mbcharlen_euckr, check_mb_euckr}, + { 22, 1, "koi8u", "koi8u_general_ci", "", 21866, "KOI8U", 1, 1, NULL, NULL}, + { 24, 1, "gb2312", "gb2312_chinese_ci", "", 936, "GB2312", 1, 2, mysql_mbcharlen_gb2312, check_mb_gb2312}, + { 25, 1, "greek", "greek_general_ci", "", 28597, "GREEK", 1, 1, NULL, NULL}, + { 26, 1, "cp1250", "cp1250_general_ci", "", 1250, "CP1250", 1, 1, NULL, NULL}, + { 28, 1, "gbk", "gbk_chinese_ci", "", 936, "GBK", 1, 2, mysql_mbcharlen_gbk, check_mb_gbk}, + { 30, 1, "latin5", "latin5_turkish_ci", "", 1254, "LATIN5", 1, 1, NULL, NULL}, + { 32, 1, "armscii8", "armscii8_general_ci", "", 0, "ARMSCII-8", 1, 1, NULL, NULL}, + { 33, 1, UTF8_MB3, UTF8_MB3"_general_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 35, 1, "ucs2", "ucs2_general_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 36, 1, "cp866", "cp866_general_ci", "", 866, "CP866", 1, 1, NULL, NULL}, + { 37, 1, "keybcs2", "keybcs2_general_ci", "", 0, "", 1, 1, NULL, NULL}, + { 38, 1, "macce", "macce_general_ci", "", 10029, "CP1282", 1, 1, NULL, NULL}, + { 39, 1, "macroman", "macroman_general_ci", "", 10000, "MACINTOSH", 1, 1, NULL, NULL}, + { 40, 1, "cp852", "cp852_general_ci", "", 852, "CP852", 1, 1, NULL, NULL}, + { 41, 1, "latin7", "latin7_general_ci", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + { 51, 1, "cp1251", "cp1251_general_ci", "", 1251, "CP1251", 1, 1, NULL, NULL}, + { 57, 1, "cp1256", "cp1256_general_ci", "", 1256, "CP1256", 1, 1, NULL, NULL}, + { 59, 1, "cp1257", "cp1257_general_ci", "", 1257, "CP1257", 1, 1, NULL, NULL}, + { 63, 1, "binary", "binary", "", 0, "ASCII", 1, 1, NULL, NULL}, + { 64, 1, "armscii8", "armscii8_bin", "", 0, "ARMSCII-8", 1, 1, NULL, NULL}, + { 92, 1, "geostd8", "geostd8_general_ci", "", 0, "GEORGIAN-PS", 1, 1, NULL, NULL}, + { 95, 1, "cp932", "cp932_japanese_ci", "", 932, "CP932", 1, 2, mysql_mbcharlen_cp932, check_mb_cp932}, + { 97, 1, "eucjpms", "eucjpms_japanese_ci", "", 932, "EUC-JP-MS", 1, 3, mysql_mbcharlen_eucjpms, check_mb_eucjpms}, + { 2, 1, "latin2", "latin2_czech_cs", "", 852, "LATIN2", 1, 1, NULL, NULL}, + { 5, 1, "latin1", "latin1_german1_ci", "", 1252, "LATIN1", 1, 1, NULL, NULL}, + { 14, 1, "cp1251", "cp1251_bulgarian_ci", "", 1251, "CP1251", 1, 1, NULL, NULL}, + { 15, 1, "latin1", "latin1_danish_ci", "", 1252, "LATIN1", 1, 1, NULL, NULL}, + { 17, 1, "filename", "filename", "", 0, "", 1, 5, NULL, NULL}, + { 20, 1, "latin7", "latin7_estonian_cs", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + { 21, 1, "latin2", "latin2_hungarian_ci", "", 852, "LATIN2", 1, 1, NULL, NULL}, + { 23, 1, "cp1251", "cp1251_ukrainian_ci", "", 1251, "CP1251", 1, 1, NULL, NULL}, + { 27, 1, "latin2", "latin2_croatian_ci", "", 852, "LATIN2", 1, 1, NULL, NULL}, + { 29, 1, "cp1257", "cp1257_lithuanian_ci", "", 1257, "CP1257", 1, 1, NULL, NULL}, + { 31, 1, "latin1", "latin1_german2_ci", "", 1252, "LATIN1", 1, 1, NULL, NULL}, + { 34, 1, "cp1250", "cp1250_czech_cs", "", 1250, "CP1250", 1, 1, NULL, NULL}, + { 42, 1, "latin7", "latin7_general_cs", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + { 43, 1, "macce", "macce_bin", "", 10029, "CP1282", 1, 1, NULL, NULL}, + { 44, 1, "cp1250", "cp1250_croatian_ci", "", 1250, "CP1250", 1, 1, NULL, NULL}, + { 45, 1, UTF8_MB4, UTF8_MB4"_general_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 46, 1, UTF8_MB4, UTF8_MB4"_bin", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 47, 1, "latin1", "latin1_bin", "", 1250, "LATIN1", 1, 1, NULL, NULL}, + { 48, 1, "latin1", "latin1_general_ci", "", 1250, "LATIN1", 1, 1, NULL, NULL}, + { 49, 1, "latin1", "latin1_general_cs", "", 1250, "LATIN1", 1, 1, NULL, NULL}, + { 50, 1, "cp1251", "cp1251_bin", "", 1251, "CP1251", 1, 1, NULL, NULL}, + { 52, 1, "cp1251", "cp1251_general_cs", "", 1251, "CP1251", 1, 1, NULL, NULL}, + { 53, 1, "macroman", "macroman_bin", "", 10000, "MACINTOSH", 1, 1, NULL, NULL}, + { 54, 1, "utf16", "utf16_general_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 55, 1, "utf16", "utf16_bin", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 56, 1, "utf16le", "utf16_general_ci", "", 1200, "UTF16LE", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 58, 1, "cp1257", "cp1257_bin", "", 1257, "CP1257", 1, 1, NULL, NULL}, +#ifdef USED_TO_BE_SO_BEFORE_MYSQL_5_5 + { 60, 1, "armascii8", "armascii8_bin", "", 0, "ARMSCII-8", 1, 1, NULL, NULL}, +#endif + { 60, 1, "utf32", "utf32_general_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 61, 1, "utf32", "utf32_bin", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 62, 1, "utf16le", "utf16_bin", "", 1200, "UTF16LE", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 65, 1, "ascii", "ascii_bin", "", 1252, "ASCII", 1, 1, NULL, NULL}, + { 66, 1, "cp1250", "cp1250_bin", "", 1250, "CP1250", 1, 1, NULL, NULL}, + { 67, 1, "cp1256", "cp1256_bin", "", 1256, "CP1256", 1, 1, NULL, NULL}, + { 68, 1, "cp866", "cp866_bin", "", 866, "CP866", 1, 1, NULL, NULL}, + { 69, 1, "dec8", "dec8_bin", "", 0, "DEC", 1, 1, NULL, NULL}, + { 70, 1, "greek", "greek_bin", "", 28597, "GREEK", 1, 1, NULL, NULL}, + { 71, 1, "hebrew", "hebrew_bin", "", 1255, "hebrew", 1, 1, NULL, NULL}, + { 72, 1, "hp8", "hp8_bin", "", 0, "HPROMAN-8", 1, 1, NULL, NULL}, + { 73, 1, "keybcs2", "keybcs2_bin", "", 0, "", 1, 1, NULL, NULL}, + { 74, 1, "koi8r", "koi8r_bin", "", 20866, "KOI8R", 1, 1, NULL, NULL}, + { 75, 1, "koi8u", "koi8u_bin", "", 21866, "KOI8U", 1, 1, NULL, NULL}, + { 76, 1, UTF8_MB3, UTF8_MB3"_tolower_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 77, 1, "latin2", "latin2_bin", "", 28592, "LATIN2", 1, 1, NULL, NULL}, + { 78, 1, "latin5", "latin5_bin", "", 1254, "LATIN5", 1, 1, NULL, NULL}, + { 79, 1, "latin7", "latin7_bin", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + { 80, 1, "cp850", "cp850_bin", "", 850, "CP850", 1, 1, NULL, NULL}, + { 81, 1, "cp852", "cp852_bin", "", 852, "CP852", 1, 1, NULL, NULL}, + { 82, 1, "swe7", "swe7_bin", "", 0, "", 1, 1, NULL, NULL}, + { 93, 1, "geostd8", "geostd8_bin", "", 0, "GEORGIAN-PS", 1, 1, NULL, NULL}, + { 83, 1, UTF8_MB3, UTF8_MB3"_bin", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 84, 1, "big5", "big5_bin", "", 65000, "BIG5", 1, 2, mysql_mbcharlen_big5, check_mb_big5}, + { 85, 1, "euckr", "euckr_bin", "", 51949, "EUCKR", 1, 2, mysql_mbcharlen_euckr, check_mb_euckr}, + { 86, 1, "gb2312", "gb2312_bin", "", 936, "GB2312", 1, 2, mysql_mbcharlen_gb2312, check_mb_gb2312}, + { 87, 1, "gbk", "gbk_bin", "", 936, "GBK", 1, 2, mysql_mbcharlen_gbk, check_mb_gbk}, + { 88, 1, "sjis", "sjis_bin", "", 932, "SJIS", 1, 2, mysql_mbcharlen_sjis, check_mb_sjis}, + { 89, 1, "tis620", "tis620_bin", "", 874, "TIS620", 1, 1, NULL, NULL}, + { 90, 1, "ucs2", "ucs2_bin", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 91, 1, "ujis", "ujis_bin", "", 20932, "UJIS", 1, 3, mysql_mbcharlen_ujis, check_mb_ujis}, + { 94, 1, "latin1", "latin1_spanish_ci", "", 1252, "LATIN1", 1, 1, NULL, NULL}, + { 96, 1, "cp932", "cp932_bin", "", 932, "CP932", 1, 2, mysql_mbcharlen_cp932, check_mb_cp932}, + { 99, 1, "cp1250", "cp1250_polish_ci", "", 1250, "CP1250", 1, 1, NULL, NULL}, + { 98, 1, "eucjpms", "eucjpms_bin", "", 932, "EUCJP-MS", 1, 3, mysql_mbcharlen_eucjpms, check_mb_eucjpms}, + { 101, 1, "utf16", "utf16_unicode_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 102, 1, "utf16", "utf16_icelandic_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 103, 1, "utf16", "utf16_latvian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 104, 1, "utf16", "utf16_romanian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 105, 1, "utf16", "utf16_slovenian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 106, 1, "utf16", "utf16_polish_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 107, 1, "utf16", "utf16_estonian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 108, 1, "utf16", "utf16_spanish_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 109, 1, "utf16", "utf16_swedish_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 110, 1, "utf16", "utf16_turkish_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 111, 1, "utf16", "utf16_czech_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 112, 1, "utf16", "utf16_danish_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 113, 1, "utf16", "utf16_lithunian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 114, 1, "utf16", "utf16_slovak_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 115, 1, "utf16", "utf16_spanish2_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 116, 1, "utf16", "utf16_roman_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 117, 1, "utf16", "utf16_persian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 118, 1, "utf16", "utf16_esperanto_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 120, 1, "utf16", "utf16_sinhala_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 121, 1, "utf16", "utf16_german2_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 122, 1, "utf16", "utf16_croatian_mysql561_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 123, 1, "utf16", "utf16_unicode_520_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 124, 1, "utf16", "utf16_vietnamese_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 128, 1, "ucs2", "ucs2_unicode_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 129, 1, "ucs2", "ucs2_icelandic_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 130, 1, "ucs2", "ucs2_latvian_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 131, 1, "ucs2", "ucs2_romanian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 132, 1, "ucs2", "ucs2_slovenian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 133, 1, "ucs2", "ucs2_polish_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 134, 1, "ucs2", "ucs2_estonian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 135, 1, "ucs2", "ucs2_spanish_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 136, 1, "ucs2", "ucs2_swedish_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 137, 1, "ucs2", "ucs2_turkish_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 138, 1, "ucs2", "ucs2_czech_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 139, 1, "ucs2", "ucs2_danish_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 140, 1, "ucs2", "ucs2_lithuanian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 141, 1, "ucs2", "ucs2_slovak_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 142, 1, "ucs2", "ucs2_spanish2_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 143, 1, "ucs2", "ucs2_roman_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 144, 1, "ucs2", "ucs2_persian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 145, 1, "ucs2", "ucs2_esperanto_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 146, 1, "ucs2", "ucs2_hungarian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 147, 1, "ucs2", "ucs2_sinhala_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 148, 1, "ucs2", "ucs2_german2_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 149, 1, "ucs2", "ucs2_croatian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, /* MDB */ + { 150, 1, "ucs2", "ucs2_unicode_520_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, /* MDB */ + { 151, 1, "ucs2", "ucs2_vietnamese_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, /* MDB */ + { 159, 1, "ucs2", "ucs2_general_mysql500_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, /* MDB */ + { 160, 1, "utf32", "utf32_unicode_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 161, 1, "utf32", "utf32_icelandic_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 162, 1, "utf32", "utf32_latvian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 163, 1, "utf32", "utf32_romanian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 164, 1, "utf32", "utf32_slovenian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 165, 1, "utf32", "utf32_polish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 166, 1, "utf32", "utf32_estonian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 167, 1, "utf32", "utf32_spanish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 168, 1, "utf32", "utf32_swedish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 169, 1, "utf32", "utf32_turkish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 170, 1, "utf32", "utf32_czech_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 171, 1, "utf32", "utf32_danish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 172, 1, "utf32", "utf32_lithuanian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 173, 1, "utf32", "utf32_slovak_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 174, 1, "utf32", "utf32_spanish_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 175, 1, "utf32", "utf32_roman_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 176, 1, "utf32", "utf32_persian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 177, 1, "utf32", "utf32_esperanto_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 178, 1, "utf32", "utf32_hungarian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 179, 1, "utf32", "utf32_sinhala_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 180, 1, "utf32", "utf32_german2_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 181, 1, "utf32", "utf32_croatian_mysql561_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 182, 1, "utf32", "utf32_unicode_520_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 183, 1, "utf32", "utf32_vietnamese_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + + { 192, 1, UTF8_MB3, UTF8_MB3"_general_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 193, 1, UTF8_MB3, UTF8_MB3"_icelandic_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 194, 1, UTF8_MB3, UTF8_MB3"_latvian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 195, 1, UTF8_MB3, UTF8_MB3"_romanian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 196, 1, UTF8_MB3, UTF8_MB3"_slovenian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 197, 1, UTF8_MB3, UTF8_MB3"_polish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 198, 1, UTF8_MB3, UTF8_MB3"_estonian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 199, 1, UTF8_MB3, UTF8_MB3"_spanish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 119, 1, UTF8_MB3, UTF8_MB3"_spanish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 200, 1, UTF8_MB3, UTF8_MB3"_swedish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 201, 1, UTF8_MB3, UTF8_MB3"_turkish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 202, 1, UTF8_MB3, UTF8_MB3"_czech_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 203, 1, UTF8_MB3, UTF8_MB3"_danish_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid }, + { 204, 1, UTF8_MB3, UTF8_MB3"_lithuanian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid }, + { 205, 1, UTF8_MB3, UTF8_MB3"_slovak_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 206, 1, UTF8_MB3, UTF8_MB3"_spanish2_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 207, 1, UTF8_MB3, UTF8_MB3"_roman_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 208, 1, UTF8_MB3, UTF8_MB3"_persian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 209, 1, UTF8_MB3, UTF8_MB3"_esperanto_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 210, 1, UTF8_MB3, UTF8_MB3"_hungarian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 211, 1, UTF8_MB3, UTF8_MB3"_sinhala_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 212, 1, UTF8_MB3, UTF8_MB3"_german2_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 214, 1, UTF8_MB3, UTF8_MB3"_unicode_520_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 215, 1, UTF8_MB3, UTF8_MB3"_vietnamese_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 213, 1, UTF8_MB3, UTF8_MB3"_croatian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, /*MDB*/ + { 223, 1, UTF8_MB3, UTF8_MB3"_general_mysql500_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, /*MDB*/ + + { 224, 1, UTF8_MB4, UTF8_MB4"_unicode_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 225, 1, UTF8_MB4, UTF8_MB4"_icelandic_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 226, 1, UTF8_MB4, UTF8_MB4"_latvian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 227, 1, UTF8_MB4, UTF8_MB4"_romanian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 228, 1, UTF8_MB4, UTF8_MB4"_slovenian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 229, 1, UTF8_MB4, UTF8_MB4"_polish_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 230, 1, UTF8_MB4, UTF8_MB4"_estonian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 231, 1, UTF8_MB4, UTF8_MB4"_spanish_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 232, 1, UTF8_MB4, UTF8_MB4"_swedish_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 233, 1, UTF8_MB4, UTF8_MB4"_turkish_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 234, 1, UTF8_MB4, UTF8_MB4"_czech_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 235, 1, UTF8_MB4, UTF8_MB4"_danish_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 236, 1, UTF8_MB4, UTF8_MB4"_lithuanian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 237, 1, UTF8_MB4, UTF8_MB4"_slovak_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 238, 1, UTF8_MB4, UTF8_MB4"_spanish2_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 239, 1, UTF8_MB4, UTF8_MB4"_roman_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 240, 1, UTF8_MB4, UTF8_MB4"_persian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 241, 1, UTF8_MB4, UTF8_MB4"_esperanto_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 242, 1, UTF8_MB4, UTF8_MB4"_hungarian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 243, 1, UTF8_MB4, UTF8_MB4"_sinhala_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 244, 1, UTF8_MB4, UTF8_MB4"_german2_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 245, 1, UTF8_MB4, UTF8_MB4"_croatian_mysql561_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 246, 1, UTF8_MB4, UTF8_MB4"_unicode_520_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 247, 1, UTF8_MB4, UTF8_MB4"_vietnamese_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 248, 1, "gb18030", "gb18030_chinese_ci", "", 54936, "GB18030", 1, 4, mysql_mbcharlen_gb18030, check_mb_gb18030_valid}, + { 249, 1, "gb18030", "gb18030_bin", "", 54936, "GB18030", 1, 4, mysql_mbcharlen_gb18030, check_mb_gb18030_valid}, + { 250, 1, "gb18030", "gb18030_unicode_520_ci", "", 54936, "GB18030", 1, 4, mysql_mbcharlen_gb18030, check_mb_gb18030_valid}, + + + { 254, 1, UTF8_MB3, UTF8_MB3"_general_cs", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + + { 255, 1, UTF8_MB4, UTF8_MB4"_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 256, 1, UTF8_MB4, UTF8_MB4"_de_pb_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 257, 1, UTF8_MB4, UTF8_MB4"_is_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 258, 1, UTF8_MB4, UTF8_MB4"_lv_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 259, 1, UTF8_MB4, UTF8_MB4"_ro_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 260, 1, UTF8_MB4, UTF8_MB4"_sl_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 261, 1, UTF8_MB4, UTF8_MB4"_pl_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 262, 1, UTF8_MB4, UTF8_MB4"_et_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 263, 1, UTF8_MB4, UTF8_MB4"_es_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 264, 1, UTF8_MB4, UTF8_MB4"_sv_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 265, 1, UTF8_MB4, UTF8_MB4"_tr_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 266, 1, UTF8_MB4, UTF8_MB4"_cs_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 267, 1, UTF8_MB4, UTF8_MB4"_da_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 268, 1, UTF8_MB4, UTF8_MB4"_lt_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 269, 1, UTF8_MB4, UTF8_MB4"_sk_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 270, 1, UTF8_MB4, UTF8_MB4"_es_trad_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 271, 1, UTF8_MB4, UTF8_MB4"_la_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 273, 1, UTF8_MB4, UTF8_MB4"_eo_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 274, 1, UTF8_MB4, UTF8_MB4"_hu_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 275, 1, UTF8_MB4, UTF8_MB4"_hr_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 277, 1, UTF8_MB4, UTF8_MB4"_vi_0900_ai_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 278, 1, UTF8_MB4, UTF8_MB4"_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 279, 1, UTF8_MB4, UTF8_MB4"_de_pb__0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 280, 1, UTF8_MB4, UTF8_MB4"_is_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 281, 1, UTF8_MB4, UTF8_MB4"_lv_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 282, 1, UTF8_MB4, UTF8_MB4"_ro_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 283, 1, UTF8_MB4, UTF8_MB4"_sl_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 284, 1, UTF8_MB4, UTF8_MB4"_pl_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 285, 1, UTF8_MB4, UTF8_MB4"_et_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 286, 1, UTF8_MB4, UTF8_MB4"_es_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 287, 1, UTF8_MB4, UTF8_MB4"_sv_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 288, 1, UTF8_MB4, UTF8_MB4"_tr_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 289, 1, UTF8_MB4, UTF8_MB4"_cs_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 290, 1, UTF8_MB4, UTF8_MB4"_da_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 291, 1, UTF8_MB4, UTF8_MB4"_lt_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 292, 1, UTF8_MB4, UTF8_MB4"_sk_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 293, 1, UTF8_MB4, UTF8_MB4"_es_trad_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 294, 1, UTF8_MB4, UTF8_MB4"_la_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 296, 1, UTF8_MB4, UTF8_MB4"_eo_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 297, 1, UTF8_MB4, UTF8_MB4"_hu_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 298, 1, UTF8_MB4, UTF8_MB4"_hr_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 300, 1, UTF8_MB4, UTF8_MB4"_vi_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 303, 1, UTF8_MB4, UTF8_MB4"_ja_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 304, 1, UTF8_MB4, UTF8_MB4"_ja_0900_as_cs_ks", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 305, 1, UTF8_MB4, UTF8_MB4"_0900_as_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 306, 1, UTF8_MB4, UTF8_MB4"_ru_0900_as_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 307, 1, UTF8_MB4, UTF8_MB4"_ru_0900_as_cs", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 576, 1, UTF8_MB3, UTF8_MB3"_croatian_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, /*MDB*/ + { 577, 1, UTF8_MB3, UTF8_MB3"_myanmar_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, /*MDB*/ + { 578, 1, UTF8_MB3, UTF8_MB3"_thai_520_w2", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, /*MDB*/ + { 608, 1, UTF8_MB4, UTF8_MB4"_croatian_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 609, 1, UTF8_MB4, UTF8_MB4"_myanmar_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 610, 1, UTF8_MB4, UTF8_MB4"_thai_520_w2", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 640, 1, "ucs2", "ucs2_croatian_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 641, 1, "ucs2", "ucs2_myanmar_ci", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 642, 1, "ucs2", "ucs2_thai_520_w2", "", 1200, "UCS2-BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + { 672, 1, "utf16", "utf16_croatian_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 673, 1, "utf16", "utf16_myanmar_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 674, 1, "utf16", "utf16_thai_520_w2", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + { 736, 1, "utf32", "utf32_croatian_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 737, 1, "utf32", "utf32_myanmar_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + { 738, 1, "utf32", "utf32_thai_520_w2", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + {1025, 1, "big5","big5_chinese_nopad_ci", "", 950, "BIG5", 1, 2, mysql_mbcharlen_big5, check_mb_big5}, + {1027, 1, "dec8", "dec8_swedisch_nopad_ci", "", 0, "DEC", 1, 1, NULL, NULL}, + {1028, 1, "cp850", "cp850_general_nopad_ci", "", 850, "CP850", 1, 1, NULL, NULL}, + {1030, 1, "hp8", "hp8_english_nopad_ci", "", 0, "HP-ROMAN8", 1, 1, NULL, NULL}, + {1031, 1, "koi8r", "koi8r_general_nopad_ci", "", 878, "KOI8R", 1, 1, NULL, NULL}, + {1032, 1, "latin1", "latin1_swedish_nopad_ci", "", 850, "LATIN1", 1, 1, NULL, NULL}, + {1033, 1, "latin2", "latin2_general_nopad_ci", "", 852, "LATIN2", 1, 1, NULL, NULL}, + {1034, 1, "swe7", "swe7_swedish_nopad_ci", "", 20107, "", 1, 1, NULL, NULL}, + {1035, 1, "ascii", "ascii_general_nopad_ci", "", 1252, "ASCII", 1, 1, NULL, NULL}, + {1036, 1, "ujis", "ujis_japanese_nopad_ci", "", 20932, "UJIS", 1, 3, mysql_mbcharlen_ujis, check_mb_ujis}, + {1037, 1, "sjis", "sjis_japanese_nopad_ci", "", 932, "SJIS", 1, 2, mysql_mbcharlen_sjis, check_mb_sjis}, + {1040, 1, "hebrew", "hebrew_general_nopad_ci", "", 1255, "HEBREW", 1, 1, NULL, NULL}, + {1042, 1, "tis620", "tis620_thai_nopad_ci", "", 874, "TIS620", 1, 1, NULL, NULL}, + {1043, 1, "euckr", "euckr_korean_nopad_ci", "", 51949, "EUCKR", 1, 2, mysql_mbcharlen_euckr, check_mb_euckr}, + {1046, 1, "koi8u", "koi8u_general_nopad_ci", "", 20866, "KOI8U", 1, 1, NULL, NULL}, + {1048, 1, "gb2312", "gb2312_chinese_nopad_ci", "", 936, "GB2312", 1, 2, mysql_mbcharlen_gb2312, check_mb_gb2312}, + {1049, 1, "greek", "greek_general_nopad_ci", "", 28597, "GREEK", 1, 1, NULL, NULL}, + {1050, 1, "cp1250", "cp1250_general_nopad_ci", "", 1250, "CP1250", 1, 1, NULL, NULL}, + {1052, 1, "gbk", "gbk_chinese_nopad_ci", "", 936, "GBK", 1, 2, mysql_mbcharlen_gbk, check_mb_gbk}, + {1054, 1, "latin5", "latin5_turkish_nopad_ci", "", 1254, "LATIN5", 1, 1, NULL, NULL}, + {1056, 1, "armscii8", "armscii8_general_nopad_ci", "", 0, "ARMSCII-8", 1, 1, NULL, NULL}, + {1057, 1, UTF8_MB3, UTF8_MB3"_general_nopad_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + {1059, 1, "ucs2", "ucs2_general_nopad_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + {1060, 1, "cp866", "cp866_general_nopad_ci", "", 866, "CP866", 1, 1, NULL, NULL}, + {1061, 1, "keybcs2", "keybcs2_general_nopad_ci", "", 0, "", 1, 1, NULL, NULL}, + {1062, 1, "macce", "macce_general_nopad_ci", "", 10029, "CP1282", 1, 1, NULL, NULL}, + {1063, 1, "macroman", "macroman_general_nopad_ci", "", 10000, "MACINTOSH", 1, 1, NULL, NULL}, + {1064, 1, "cp852", "cp852_general_nopad_ci", "", 852, "CP852", 1, 1, NULL, NULL}, + {1065, 1, "latin7", "latin7_general_nopad_ci", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + {1067, 1, "macce", "macce_nopad_bin", "", 10029, "CP1282", 1, 1, NULL, NULL}, + {1069, 1, UTF8_MB4, UTF8_MB4"_general_nopad_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + {1070, 1, UTF8_MB4, UTF8_MB4"_general_nopad_bin", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + {1071, 1, "latin1", "latin1_nopad_bin", "", 850, "LATIN1", 1, 1, NULL, NULL}, + {1074, 1, "cp1251", "cp1251_nopad_bin", "", 1251, "CP1251", 1, 1, NULL, NULL}, + {1075, 1, "cp1251", "cp1251_general_nopad_ci", "", 1251, "CP1251", 1, 1, NULL, NULL}, + {1077, 1, "macroman", "macroman_nopad_bin", "", 10000, "MACINTOSH", 1, 1, NULL, NULL}, + {1078, 1, "utf16", "utf16_general_nopad_ci", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1079, 1, "utf16", "utf16_nopad_bin", "", 0, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1080, 1, "utf16le", "utf16le_general_nopad_ci", "", 1200, "UTF16LE", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1081, 1, "cp1256", "cp1256_general_nopad_ci", "", 1256, "CP1256", 1, 1, NULL, NULL}, + {1082, 1, "cp1257", "cp1257_nopad_bin", "", 1257, "CP1257", 1, 1, NULL, NULL}, + {1083, 1, "cp1257", "cp1257_general_nopad_ci", "", 1257, "CP1257", 1, 1, NULL, NULL}, + {1084, 1, "utf32", "utf32_general_nopad_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + {1085, 1, "utf32", "utf32_nopad_bin", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + {1086, 1, "utf16le", "utf16le_nopad_bin", "", 1200, "UTF16LE", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1088, 1, "armscii8", "armscii8_nopad_bin", "", 0, "ARMSCII-8", 1, 1, NULL, NULL}, + {1089, 1, "ascii", "ascii_nopad_bin", "", 1252, "ASCII", 1, 1, NULL, NULL}, + {1090, 1, "cp1250", "cp1250_nopad_bin", "", 1250, "CP1250", 1, 1, NULL, NULL}, + {1091, 1, "cp1256", "cp1256_nopad_bin", "", 1256, "CP1256", 1, 1, NULL, NULL}, + {1092, 1, "cp866", "cp866_nopad_bin", "", 866, "CP866", 1, 1, NULL, NULL}, + {1093, 1, "dec8", "dec8_nopad_bin", "", 0, "DEC", 1, 1, NULL, NULL}, + {1094, 1, "greek", "greek_nopad_bin", "", 28597, "GREEK", 1, 1, NULL, NULL}, + {1095, 1, "hebrew", "hebrew_nopad_bin", "", 1255, "HEBREW", 1, 1, NULL, NULL}, + {1096, 1, "hp8", "hp8_nopad_bin", "", 0, "HP-ROMAN8", 1, 1, NULL, NULL}, + {1097, 1, "keybcs2", "keybcs2_nopad_bin", "", 0, "", 1, 1, NULL, NULL}, + {1098, 1, "koi8r", "koi8r_nopad_bin", "", 878, "KOI8R", 1, 1, NULL, NULL}, + {1099, 1, "koi8u", "koi8u_nopad_bin", "", 20866, "KOI8U", 1, 1, NULL, NULL}, + {1101, 1, "latin2", "latin2_nopad_bin", "", 852, "LATIN2", 1, 1, NULL, NULL}, + {1102, 1, "latin5", "latin5_nopad_bin", "", 1254, "LATIN5", 1, 1, NULL, NULL}, + {1103, 1, "latin7", "latin7_nopad_bin", "", 28603, "LATIN7", 1, 1, NULL, NULL}, + {1104, 1, "cp850", "cp850_nopad_bin", "", 850, "CP850", 1, 1, NULL, NULL}, + {1105, 1, "cp852", "cp852_nopad_bin", "", 852, "CP852", 1, 1, NULL, NULL}, + {1106, 1, "swe7", "swe7_nopad_bin", "", 20107, "", 1, 1, NULL, NULL}, + {1107, 1, UTF8_MB3, UTF8_MB3"_nopad_bin", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + {1108, 1, "big5","big5_nopad_bin", "", 950, "BIG5", 1, 2, mysql_mbcharlen_big5, check_mb_big5}, + {1109, 1, "euckr", "euckr_nopad_bin", "", 51949, "EUCKR", 1, 2, mysql_mbcharlen_euckr, check_mb_euckr}, + {1110, 1, "gb2312", "gb2312_nopad_bin", "", 936, "GB2312", 1, 2, mysql_mbcharlen_gb2312, check_mb_gb2312}, + {1111, 1, "gbk", "gbk_nopad_bin", "", 936, "GBK", 1, 2, mysql_mbcharlen_gbk, check_mb_gbk}, + {1112, 1, "sjis", "sjis_nopad_bin", "", 932, "SJIS", 1, 2, mysql_mbcharlen_sjis, check_mb_sjis}, + {1113, 1, "tis620", "tis620_nopad_bin", "", 874, "TIS620", 1, 1, NULL, NULL}, + {1114, 1, "ucs2", "ucs2_nopad_bin", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + {1115, 1, "ujis", "ujis_nopad_bin", "", 20932, "UJIS", 1, 3, mysql_mbcharlen_ujis, check_mb_ujis}, + {1116, 1, "geostd8", "geostd8_general_nopad_ci", "", 0, "GEORGIAN-PS", 1, 1, NULL, NULL}, + {1117, 1, "geostd8", "geostd8_nopad_bin", "", 0, "GEORGIAN-PS", 1, 1, NULL, NULL}, + {1119, 1, "cp932", "cp932_japanese_nopad_ci", "", 932, "CP932", 1, 2, mysql_mbcharlen_cp932, check_mb_cp932}, + {1120, 1, "cp932", "cp932_nopad_bin", "", 932, "CP932", 1, 2, mysql_mbcharlen_cp932, check_mb_cp932}, + {1121, 1, "eucjpms", "eucjpms_japanese_nopad_ci", "", 932, "EUCJP-MS", 1, 3, mysql_mbcharlen_eucjpms, check_mb_eucjpms}, + {1122, 1, "eucjpms", "eucjpms_nopad_bin", "", 932, "EUCJP-MS", 1, 3, mysql_mbcharlen_eucjpms, check_mb_eucjpms}, + {1125, 1, "utf16", "utf16_unicode_nopad_ci", "", 1200, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1147, 1, "utf16", "utf16_unicode_520_nopad_ci", "", 1200, "UTF16", 2, 4, mysql_mbcharlen_utf16, check_mb_utf16}, + {1152, 1, "ucs2", "ucs2_unicode_nopad_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + {1174, 1, "ucs2", "ucs2_unicode_520_nopad_ci", "", 1200, "UCS-2BE", 2, 2, mysql_mbcharlen_ucs2, check_mb_ucs2}, + {1184, 1, "utf32", "utf32_unicode_nopad_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + {1206, 1, "utf32", "utf32_unicode_520_nopad_ci", "", 0, "UTF32", 4, 4, mysql_mbcharlen_utf32, check_mb_utf32}, + {1216, 1, UTF8_MB3, UTF8_MB3"_unicode_nopad_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + {1238, 1, UTF8_MB3, UTF8_MB3"_unicode_520_nopad_ci", "", 65001, "UTF-8", 1, 3, mysql_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + {1248, 1, UTF8_MB4, UTF8_MB4"_unicode_nopad_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + {1270, 1, UTF8_MB4, UTF8_MB4"_unicode_520_nopad_ci", "", 65001, "UTF-8", 1, 4, mysql_mbcharlen_utf8, check_mb_utf8_valid}, + { 0, 0, NULL, NULL, NULL, 0, NULL, 0, 0, NULL, NULL} +}; +/* }}} */ + + +typedef enum my_cs_encoding_enum +{ + MY_CS_ENCODING_UTF8MB3= 0, + MY_CS_ENCODING_UTF8MB4= 1, + MY_CS_ENCODING_UCS2= 2, + MY_CS_ENCODING_UTF16= 3, + MY_CS_ENCODING_UTF32= 4, +} my_cs_encoding_t; + +#define MY_CS_ENCODING_LAST MY_CS_ENCODING_UTF32 + + +typedef struct my_uca1400_collation_definition_st +{ + const char * tailoring; + const char * name; + uint16 id_utf8mb3; + uint16 id_utf8mb4; + uint16 id_ucs2; + uint16 id_utf16; + uint16 id_utf32; +} MY_UCA1400_COLLATION_DEFINITION; + + +#define MY_UCA1400_COLLATION_DEFINITION_COUNT 26 +/* + UCA1400 collation definitions in the order of their UCA400 counterparts, + with IDs of their closest UCA1400 counterparts, for character sets + utf8mb3, utf8mb4, ucs2, utf16, utf32. +*/ +static MY_UCA1400_COLLATION_DEFINITION +my_uca1400_collation_definitions[MY_UCA1400_COLLATION_DEFINITION_COUNT]= +{ +#define COLDEF(tl,name,id_utf8mb3,id_utf8mb4,id_ucs2,id_utf16,id_utf32) \ + {(""), (name), (id_utf8mb3), (id_utf8mb4), (id_ucs2), (id_utf16), (id_utf32)} + /* Block N1 */ + COLDEF("", "", 192, 224, 128, 101, 160), + COLDEF(icelandic, "icelandic", 193, 225, 129, 102, 161), + COLDEF(latvian, "latvian", 194, 226, 130, 103, 162), + COLDEF(romanian, "romanian", 195, 227, 131, 104, 163), + COLDEF(slovenian, "slovenian", 196, 228, 132, 105, 164), + COLDEF(polish, "polish", 197, 229, 133, 106, 165), + COLDEF(estonian, "estonian", 198, 230, 134, 107, 166), + COLDEF(spanish, "spanish", 199, 231, 135, 108, 167), + COLDEF(swedish, "swedish", 200, 232, 136, 109, 168), + COLDEF(turkish, "turkish", 201, 233, 137, 110, 169), + COLDEF(czech, "czech", 202, 234, 138, 111, 170), + COLDEF(danish, "danish", 203, 235, 139, 112, 171), + COLDEF(lithuanian, "lithuanian", 204, 236, 140, 113, 172), + COLDEF(slovak, "slovak", 205, 237, 141, 114, 173), + COLDEF(spanish2, "spanish2", 206, 238, 142, 115, 174), + COLDEF(roman, "roman", 207, 239, 143, 116, 175), + COLDEF(persian, "persian", 208, 240, 144, 117, 176), + COLDEF(esperanto, "esperanto", 209, 241, 145, 118, 177), + COLDEF(hungarian, "hungarian", 210, 242, 146, 119, 178), + COLDEF(sinhala, "sinhala", 211, 243, 147, 120, 179), + COLDEF(german2, "german2", 212, 244, 148, 121, 180), + /* + Don't add "croatian_mysql561", as its UCA-4.0.0 version was added for + compatibility only, to open MySQL tables. + The "croatian" version is closer to CLDR. Checked with CLDR-40. + */ + COLDEF(NULL,/*croatian_mysql561*/ NULL, 213, 245, 149, 122, 181), + COLDEF(NULL,/*unicode_520_ci*/ NULL, 214, 246, 150, 123, 182), + COLDEF(vietnamese, "vietnamese", 215, 247, 151, 124, 183), + /* Block N2 */ + COLDEF(croatian_mariadb, "croatian", 576, 608, 640, 672, 736), + /* Don't add myanmar yet. It causes too long weights on the tertiary level.*/ + COLDEF(NULL,/*myanmar*/ NULL, 577, 609, 641, 673, 737) + +#undef COLDEF +}; + + +#define MY_UCA1400_COLLATION_ID_POSSIBLE_MIN 2048 +#define MY_UCA1400_COLLATION_ID_POSSIBLE_MAX 4095 + +static inline my_bool +my_collation_id_is_uca1400(uint id) +{ + return (my_bool) (id >= MY_UCA1400_COLLATION_ID_POSSIBLE_MIN && + id <= MY_UCA1400_COLLATION_ID_POSSIBLE_MAX); +} + + +static inline uint +my_uca1400_collation_id_to_tailoring_id(uint id) +{ + return (id >> 3) & 0x1F; +} + + +static inline my_cs_encoding_t +my_uca1400_collation_id_to_charset_id(uint id) +{ + return (my_cs_encoding_t) ((id >> 8) & 0x07); +} + + +static uint my_uca1400_collation_id_uca400_compat(uint id) +{ + uint tlid= my_uca1400_collation_id_to_tailoring_id(id); + my_cs_encoding_t csid= my_uca1400_collation_id_to_charset_id(id); + MY_UCA1400_COLLATION_DEFINITION *def; + DBUG_ASSERT(my_collation_id_is_uca1400(id)); + if (!(def= &my_uca1400_collation_definitions[tlid])->name) + return id; + switch (csid) { + case MY_CS_ENCODING_UTF8MB3: return def->id_utf8mb3; + case MY_CS_ENCODING_UTF8MB4: return def->id_utf8mb4; + case MY_CS_ENCODING_UCS2: return def->id_ucs2; + case MY_CS_ENCODING_UTF16: return def->id_utf16; + case MY_CS_ENCODING_UTF32: return def->id_utf32; + } + return id; +} + + +/* {{{ mysql_find_charset_nr */ +const MARIADB_CHARSET_INFO * mysql_find_charset_nr(unsigned int charsetnr) +{ + const MARIADB_CHARSET_INFO * c = mariadb_compiled_charsets; + + if (my_collation_id_is_uca1400(charsetnr)) + charsetnr= my_uca1400_collation_id_uca400_compat(charsetnr); + + do { + if (c->nr == charsetnr) { + return(c); + } + ++c; + } while (c[0].nr != 0); + return(NULL); +} +/* }}} */ + + +/* {{{ mysql_find_charset_name */ +MARIADB_CHARSET_INFO * mysql_find_charset_name(const char *name) +{ + MARIADB_CHARSET_INFO *c = (MARIADB_CHARSET_INFO *)mariadb_compiled_charsets; + const char *csname; + + if (!strcasecmp(name, MADB_AUTODETECT_CHARSET_NAME)) + csname= madb_get_os_character_set(); + else + csname= (char *)name; + + if (!strcasecmp("utf8",csname)) + csname= "utf8mb3"; + + do { + if (!strcasecmp(c->csname, csname)) { + return(c); + } + ++c; + } while (c[0].nr != 0); + return(NULL); +} +/* }}} */ + + +/* {{{ mysql_cset_escape_quotes */ +size_t mysql_cset_escape_quotes(const MARIADB_CHARSET_INFO *cset, char *newstr, + const char * escapestr, size_t escapestr_len ) +{ + const char *newstr_s = newstr; + const char *newstr_e = newstr + 2 * escapestr_len; + const char *end = escapestr + escapestr_len; + my_bool escape_overflow = FALSE; + + for (;escapestr < end; escapestr++) { + unsigned int len = 0; + /* check unicode characters */ + + if (cset->char_maxlen > 1 && (len = cset->mb_valid(escapestr, end))) { + + /* check possible overflow */ + if ((newstr + len) > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy mb char without escaping it */ + while (len--) { + *newstr++ = *escapestr++; + } + escapestr--; + continue; + } + if (*escapestr == '\'') { + if (newstr + 2 > newstr_e) { + escape_overflow = TRUE; + break; + } + *newstr++ = '\''; + *newstr++ = '\''; + } else { + if (newstr + 1 > newstr_e) { + escape_overflow = TRUE; + break; + } + *newstr++ = *escapestr; + } + } + *newstr = '\0'; + + if (escape_overflow) { + return((size_t)~0); + } + return((size_t)(newstr - newstr_s)); +} +/* }}} */ + + +/* {{{ mysql_cset_escape_slashes */ +size_t mysql_cset_escape_slashes(const MARIADB_CHARSET_INFO * cset, char *newstr, + const char * escapestr, size_t escapestr_len ) +{ + const char *newstr_s = newstr; + const char *newstr_e = newstr + 2 * escapestr_len; + const char *end = escapestr + escapestr_len; + my_bool escape_overflow = FALSE; + + for (;escapestr < end; escapestr++) { + char esc = '\0'; + unsigned int len = 0; + + /* check unicode characters */ + if (cset->char_maxlen > 1 && (len = cset->mb_valid(escapestr, end))) { + /* check possible overflow */ + if ((newstr + len) > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy mb char without escaping it */ + while (len--) { + *newstr++ = *escapestr++; + } + escapestr--; + continue; + } + if (cset->char_maxlen > 1 && cset->mb_charlen(*escapestr) > 1) { + esc = *escapestr; + } else { + switch (*escapestr) { + case 0: + esc = '0'; + break; + case '\n': + esc = 'n'; + break; + case '\r': + esc = 'r'; + break; + case '\\': + case '\'': + case '"': + esc = *escapestr; + break; + case '\032': + esc = 'Z'; + break; + } + } + if (esc) { + if (newstr + 2 > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy escaped character */ + *newstr++ = '\\'; + *newstr++ = esc; + } else { + if (newstr + 1 > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy non escaped character */ + *newstr++ = *escapestr; + } + } + *newstr = '\0'; + + if (escape_overflow) { + return((size_t)~0); + } + return((size_t)(newstr - newstr_s)); +} +/* }}} */ + +/* {{{ MADB_OS_CHARSET */ +struct st_madb_os_charset { + const char *identifier; + const char *description; + const char *charset; + const char *iconv_cs; + unsigned char supported; +}; + +#define MADB_CS_UNSUPPORTED 0 +#define MADB_CS_APPROX 1 +#define MADB_CS_EXACT 2 + +/* Please add new character sets at the end. */ +struct st_madb_os_charset MADB_OS_CHARSET[]= +{ +#ifdef _WIN32 + /* Windows code pages */ + {"037", "IBM EBCDIC US-Canada", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"437", "OEM United States", "cp850", NULL, MADB_CS_APPROX}, + {"500", "IBM EBCDIC International", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"708", "Arabic (ASMO 708)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"709", "Arabic (ASMO-449+, BCON V4)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"710", "Transparent Arabic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"720", "Arabic (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"737", "Greek (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"775", "Baltic (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"850", "Western European (DOS)", "cp850", NULL, MADB_CS_EXACT}, + {"852", "Central European (DOS)", "cp852", NULL, MADB_CS_EXACT}, + {"855", "Cyrillic (primarily Russian)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"857", "Turkish (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"858", "OEM Multilingual Latin 1 + Euro symbol", "cp850", NULL, MADB_CS_EXACT}, + {"860", "Portuguese (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"861", "Icelandic (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"862", "Hebrew (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"863", "French Canadian (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"864", "Arabic (864)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"865", "Nordic (DOS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"866", "Cyrillic (DOS)", "cp866", NULL, MADB_CS_EXACT}, + {"869", "Greek, Modern (DOS)", "greek", NULL, MADB_CS_EXACT}, + {"870", "IBM EBCDIC Multilingual Latin 2", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"874", "Thai (Windows)", "tis620", NULL, MADB_CS_UNSUPPORTED}, + {"875", "Greek Modern", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"932", "Japanese (Shift-JIS)", "cp932", NULL, MADB_CS_EXACT}, + {"936", "Chinese Simplified (GB2312)", "gbk", NULL, MADB_CS_EXACT}, + {"949", "ANSI/OEM Korean (Unified Hangul Code)", "euckr", NULL, MADB_CS_EXACT}, + {"950", "Chinese Traditional (Big5)", "big5", NULL, MADB_CS_EXACT}, + {"1026", "EBCDIC Turkish (Latin 5)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1047", "EBCDIC Latin 1/Open System", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1140", "IBM EBCDIC (US-Canada-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1141", "IBM EBCDIC (Germany-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1142", "IBM EBCDIC (Denmark-Norway-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1143", "IBM EBCDIC (Finland-Sweden-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1144", "IBM EBCDIC (Italy-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1145", "IBM EBCDIC (Spain-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1146", "IBM EBCDIC (UK-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1147", "IBM EBCDIC (France-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1148", "IBM EBCDIC (International-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1149", "IBM EBCDIC (Icelandic-Euro)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1200", "UTF-16, little endian byte order", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1201", "UTF-16, big endian byte order", "utf16", NULL, MADB_CS_UNSUPPORTED}, + {"1250", "Central European (Windows)", "cp1250", NULL, MADB_CS_EXACT}, + {"1251", "Cyrillic (Windows)", "cp1251", NULL, MADB_CS_EXACT}, + {"1252", "Western European (Windows)", "latin1", NULL, MADB_CS_EXACT}, + {"1253", "Greek (Windows)", "greek", NULL, MADB_CS_EXACT}, + {"1254", "Turkish (Windows)", "latin5", NULL, MADB_CS_EXACT}, + {"1255", "Hebrew (Windows)", "hebrew", NULL, MADB_CS_EXACT}, + {"1256", "Arabic (Windows)", "cp1256", NULL, MADB_CS_EXACT}, + {"1257", "Baltic (Windows)","cp1257", NULL, MADB_CS_EXACT}, + {"1258", "Vietnamese (Windows)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"1361", "Korean (Johab)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10000", "Western European (Mac)", "macroman", NULL, MADB_CS_EXACT}, + {"10001", "Japanese (Mac)", "sjis", NULL, MADB_CS_EXACT}, + {"10002", "Chinese Traditional (Mac)", "big5", NULL, MADB_CS_EXACT}, + {"10003", "Korean (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10004", "Arabic (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10005", "Hebrew (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10006", "Greek (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10007", "Cyrillic (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10008", "Chinese Simplified (Mac)", "gb2312", NULL, MADB_CS_EXACT}, + {"10010", "Romanian (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10017", "Ukrainian (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10021", "Thai (Mac)", "tis620", NULL, MADB_CS_EXACT}, + {"10029", "Central European (Mac)", "macce", NULL, MADB_CS_EXACT}, + {"10079", "Icelandic (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10081", "Turkish (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"10082", "Croatian (Mac)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"12000", "Unicode UTF-32, little endian byte order", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"12001", "Unicode UTF-32, big endian byte order", "utf32", NULL, MADB_CS_UNSUPPORTED}, + {"20000", "Chinese Traditional (CNS)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20001", "TCA Taiwan", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20002", "Chinese Traditional (Eten)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20003", "IBM5550 Taiwan", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20004", "TeleText Taiwan", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20005", "Wang Taiwan", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20105", "Western European (IA5)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20106", "IA5 German (7-bit)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20107", "Swedish (7-bit)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20108", "Norwegian (7-bit)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20127", "US-ASCII (7-bit)", "ascii", NULL, MADB_CS_EXACT}, + {"20261", "T.61", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20269", "Non-Spacing Accent", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20273", "EBCDIC Germany", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20277", "EBCDIC Denmark-Norway", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20278", "EBCDIC Finland-Sweden", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20280", "EBCDIC Italy", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20284", "EBCDIC Latin America-Spain", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20285", "EBCDIC United Kingdom", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20290", "EBCDIC Japanese Katakana Extended", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20297", "EBCDIC France", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20420", "EBCDIC Arabic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20423", "EBCDIC Greek", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20424", "EBCDIC Hebrew", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20833", "EBCDIC Korean Extended", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20838", "EBCDIC Thai", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20866", "Cyrillic (KOI8-R)", "koi8r", NULL, MADB_CS_EXACT}, + {"20871", "EBCDIC Icelandic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20880", "EBCDIC Cyrillic Russian", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20905", "EBCDIC Turkish", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20924", "EBCDIC Latin 1/Open System (1047 + Euro symbol)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"20932", "Japanese (JIS 0208-1990 and 0121-1990)", "ujis", NULL, MADB_CS_EXACT}, + {"20936", "Chinese Simplified (GB2312-80)", "gb2312", NULL, MADB_CS_APPROX}, + {"20949", "Korean Wansung", "euckr", NULL, MADB_CS_APPROX}, + {"21025", "EBCDIC Cyrillic Serbian-Bulgarian", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"21866", "Cyrillic (KOI8-U)", "koi8u", NULL, MADB_CS_EXACT}, + {"28591", "Western European (ISO)", "latin1", NULL, MADB_CS_APPROX}, + {"28592", "Central European (ISO)", "latin2", NULL, MADB_CS_EXACT}, + {"28593", "Latin 3", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"28594", "Baltic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"28595", "ISO 8859-5 Cyrillic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"28596", "ISO 8859-6 Arabic", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"28597", "ISO 8859-7 Greek", "greek", NULL, MADB_CS_EXACT}, + {"28598", "Hebrew (ISO-Visual)", "hebrew", NULL, MADB_CS_EXACT}, + {"28599", "ISO 8859-9 Turkish", "latin5", NULL, MADB_CS_EXACT}, + {"28603", "ISO 8859-13 Estonian", "latin7", NULL, MADB_CS_EXACT}, + {"28605", "8859-15 Latin 9", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"29001", "Europa 3", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"38598", "ISO 8859-8 Hebrew; Hebrew (ISO-Logical)", "hebrew", NULL, MADB_CS_EXACT}, + {"50220", "ISO 2022 Japanese with no halfwidth Katakana", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50221", "ISO 2022 Japanese with halfwidth Katakana", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50222", "ISO 2022 Japanese JIS X 0201-1989", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50225", "ISO 2022 Korean", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50227", "ISO 2022 Simplified Chinese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50229", "ISO 2022 Traditional Chinese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50930", "EBCDIC Japanese (Katakana) Extended", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50931", "EBCDIC US-Canada and Japanese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50933", "EBCDIC Korean Extended and Korean", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50935", "EBCDIC Simplified Chinese Extended and Simplified Chinese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50936", "EBCDIC Simplified Chinese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50937", "EBCDIC US-Canada and Traditional Chinese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"50939", "EBCDIC Japanese (Latin) Extended and Japanese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"51932", "EUC Japanese", "ujis", NULL, MADB_CS_EXACT}, + {"51936", "EUC Simplified Chinese; Chinese Simplified (EUC)", "gb2312", NULL, MADB_CS_EXACT}, + {"51949", "EUC Korean", "euckr", NULL, MADB_CS_EXACT}, + {"51950", "EUC Traditional Chinese", "big5", NULL, MADB_CS_EXACT}, + {"52936", "Chinese Simplified (HZ)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"54936", "Chinese Simplified (GB18030)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57002", "ISCII Devanagari", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57003", "ISCII Bengali", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57004", "ISCII Tamil", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57005", "ISCII Telugu", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57006", "ISCII Assamese", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57007", "ISCII Oriya", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57008", "ISCII Kannada", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57009", "ISCII Malayalam", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57010", "ISCII Gujarati", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"57011", "ISCII Punjabi", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"65000", "utf-7 Unicode (UTF-7)", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"65001", "utf-8 Unicode (UTF-8)", "utf8", NULL, MADB_CS_EXACT}, + /* non Windows */ +#else + /* iconv encodings */ + {"ASCII", "US-ASCII", "ascii", "ASCII", MADB_CS_APPROX}, + {"US-ASCII", "US-ASCII", "ascii", "ASCII", MADB_CS_APPROX}, + {"Big5", "Chinese for Taiwan Multi-byte set", "big5", "BIG5", MADB_CS_EXACT}, + {"CP866", "IBM 866", "cp866", "CP866", MADB_CS_EXACT}, + {"IBM-1252", "Catalan Spain", "cp1252", "CP1252", MADB_CS_EXACT}, + {"ISCII-DEV", "Hindi", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"ISO-8859-1", "ISO-8859-1", "latin1", "ISO_8859-1", MADB_CS_APPROX}, + {"ISO8859-1", "ISO-8859-1", "latin1", "ISO_8859-1", MADB_CS_APPROX}, + {"ISO_8859-1", "ISO-8859-1", "latin1", "ISO_8859-1", MADB_CS_APPROX}, + {"ISO88591", "ISO-8859-1", "latin1", "ISO_8859-1", MADB_CS_APPROX}, + {"ISO-8859-13", "ISO-8859-13", "latin7", "ISO_8859-13", MADB_CS_EXACT}, + {"ISO8859-13", "ISO-8859-13", "latin7", "ISO_8859-13", MADB_CS_EXACT}, + {"ISO_8859-13", "ISO-8859-13", "latin7", "ISO_8859-13", MADB_CS_EXACT}, + {"ISO885913", "ISO-8859-13", "latin7", "ISO_8859-13", MADB_CS_EXACT}, + {"ISO-8859-15", "ISO-8859-15", "latin9", "ISO_8859-15", MADB_CS_UNSUPPORTED}, + {"ISO8859-15", "ISO-8859-15", "latin9", "ISO_8859-15", MADB_CS_UNSUPPORTED}, + {"ISO_8859-15", "ISO-8859-15", "latin9", "ISO_8859-15", MADB_CS_UNSUPPORTED}, + {"ISO885915", "ISO-8859-15", "latin9", "ISO_8859-15", MADB_CS_UNSUPPORTED}, + {"ISO-8859-2", "ISO-8859-2", "latin2", "ISO_8859-2", MADB_CS_EXACT}, + {"ISO8859-2", "ISO-8859-2", "latin2", "ISO_8859-2", MADB_CS_EXACT}, + {"ISO_8859-2", "ISO-8859-2", "latin2", "ISO_8859-2", MADB_CS_EXACT}, + {"ISO88592", "ISO-8859-2", "latin2", "ISO_8859-2", MADB_CS_EXACT}, + {"ISO-8859-7", "ISO-8859-7", "greek", "ISO_8859-7", MADB_CS_EXACT}, + {"ISO8859-7", "ISO-8859-7", "greek", "ISO_8859-7", MADB_CS_EXACT}, + {"ISO_8859-7", "ISO-8859-7", "greek", "ISO_8859-7", MADB_CS_EXACT}, + {"ISO88597", "ISO-8859-7", "greek", "ISO_8859-7", MADB_CS_EXACT}, + {"ISO-8859-8", "ISO-8859-8", "hebrew", "ISO_8859-8", MADB_CS_EXACT}, + {"ISO8859-8", "ISO-8859-8", "hebrew", "ISO_8859-8", MADB_CS_EXACT}, + {"ISO_8859-8", "ISO-8859-8", "hebrew", "ISO_8859-8", MADB_CS_EXACT}, + {"ISO88598", "ISO-8859-8", "hebrew", "ISO_8859-8", MADB_CS_EXACT}, + {"ISO-8859-9", "ISO-8859-9", "latin5", "ISO_8859-9", MADB_CS_EXACT}, + {"ISO8859-9", "ISO-8859-9", "latin5", "ISO_8859-9", MADB_CS_EXACT}, + {"ISO_8859-9", "ISO-8859-9", "latin5", "ISO_8859-9", MADB_CS_EXACT}, + {"ISO88599", "ISO-8859-9", "latin5", "ISO_8859-9", MADB_CS_EXACT}, + {"ISO-8859-4", "ISO-8859-4", NULL, "ISO_8859-4", MADB_CS_UNSUPPORTED}, + {"ISO8859-4", "ISO-8859-4", NULL, "ISO_8859-4", MADB_CS_UNSUPPORTED}, + {"ISO_8859-4", "ISO-8859-4", NULL, "ISO_8859-4", MADB_CS_UNSUPPORTED}, + {"ISO88594", "ISO-8859-4", NULL, "ISO_8859-4", MADB_CS_UNSUPPORTED}, + {"ISO-8859-5", "ISO-8859-5", NULL, "ISO_8859-5", MADB_CS_UNSUPPORTED}, + {"ISO8859-5", "ISO-8859-5", NULL, "ISO_8859-5", MADB_CS_UNSUPPORTED}, + {"ISO_8859-5", "ISO-8859-5", NULL, "ISO_8859-5", MADB_CS_UNSUPPORTED}, + {"ISO88595", "ISO-8859-5", NULL, "ISO_8859-5", MADB_CS_UNSUPPORTED}, + {"KOI8-R", "KOI8-R", "koi8r", "KOI8R", MADB_CS_EXACT}, + {"koi8r", "KOI8-R", "koi8r", "KOI8R", MADB_CS_EXACT}, + {"KOI8-U", "KOI8-U", "koi8u", "KOI8U", MADB_CS_EXACT}, + {"koi8u", "KOI8-U", "koi8u", "KOI8U", MADB_CS_EXACT}, + {"koi8t", "KOI8-T", NULL, "KOI8-T", MADB_CS_UNSUPPORTED}, + {"KOI8-T", "KOI8-T", NULL, "KOI8-T", MADB_CS_UNSUPPORTED}, + {"SJIS", "SHIFT_JIS", "sjis", "SJIS", MADB_CS_EXACT}, + {"Shift-JIS", "SHIFT_JIS", "sjis", "SJIS", MADB_CS_EXACT}, + {"ansi1251", "Cyrillic", "cp1251", "CP1251", MADB_CS_EXACT}, + {"cp1251", "Cyrillic", "cp1251", "CP1251", MADB_CS_EXACT}, + {"armscii8", "Armenian", "armscii8", "ASMSCII-8", MADB_CS_EXACT}, + {"armscii-8", "Armenian", "armscii8", "ASMSCII-8", MADB_CS_EXACT}, + {"big5hkscs", "Big5-HKSCS", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"cp1255", "Hebrew", "cp1255", "CP1255", MADB_CS_EXACT}, + {"eucCN", "GB-2312", "gb2312", "GB2312", MADB_CS_EXACT}, + {"eucJP", "UJIS", "ujis", "UJIS", MADB_CS_EXACT}, + {"eucKR", "EUC-KR", "euckr", "EUCKR", MADB_CS_EXACT}, + {"euctw", "EUC-TW", NULL, NULL, MADB_CS_UNSUPPORTED}, + {"gb18030", "GB 18030-2000", "gb18030", "GB18030", MADB_CS_UNSUPPORTED}, + {"gb2312", "GB2312", "gb2312", "GB2312", MADB_CS_EXACT}, + {"gbk", "GBK", "gbk", "GBK", MADB_CS_EXACT}, + {"georgianps", "Georgian", "geostd8", "GEORGIAN-PS", MADB_CS_EXACT}, + {"utf8", "UTF8", "utf8", "UTF-8", MADB_CS_EXACT}, + {"utf-8", "UTF8", "utf8", "UTF-8", MADB_CS_EXACT}, +#endif + {NULL, NULL, NULL, NULL, 0} +}; +/* }}} */ + +/* {{{ madb_get_os_character_set */ +const char *madb_get_os_character_set() +{ + unsigned int i= 0; + char *p= NULL; +#ifdef _WIN32 + char codepage[FN_REFLEN]; + snprintf(codepage, FN_REFLEN, "%u", GetConsoleCP() ? GetConsoleCP() : GetACP()); + p= codepage; +#elif defined(HAVE_NL_LANGINFO) && defined(HAVE_SETLOCALE) + if (setlocale(LC_CTYPE, "")) + p= nl_langinfo(CODESET); +#endif + if (!p) + return MADB_DEFAULT_CHARSET_NAME; + while (MADB_OS_CHARSET[i].identifier) + { + if (MADB_OS_CHARSET[i].supported > MADB_CS_UNSUPPORTED && + strcasecmp(MADB_OS_CHARSET[i].identifier, p) == 0) + return MADB_OS_CHARSET[i].charset; + i++; + } + return MADB_DEFAULT_CHARSET_NAME; +} +/* }}} */ + +/* {{{ madb_get_code_page */ +#ifdef _WIN32 +int madb_get_windows_cp(const char *charset) +{ + unsigned int i= 0; + while (MADB_OS_CHARSET[i].identifier) + { + if (MADB_OS_CHARSET[i].supported > MADB_CS_UNSUPPORTED && + strcmp(MADB_OS_CHARSET[i].charset, charset) == 0) + return atoi(MADB_OS_CHARSET[i].identifier); + i++; + } + return -1; +} +#endif +/* }}} */ + +#ifdef HAVE_ICONV +/* {{{ map_charset_name + Changing charset name into something iconv understands, if necessary. + Another purpose it to avoid BOMs in result string, adding BE if necessary + e.g.UTF16 does not work form iconv, while UTF-16 does. + */ +static void map_charset_name(const char *cs_name, my_bool target_cs, char *buffer, size_t buff_len) +{ + char digits[3], endianness[3]= "BE"; + + if (sscanf(cs_name, "UTF%2[0-9]%2[LBE]", digits, endianness)) + { + /* We should have at least digits. Endianness we write either default(BE), or what we found in the string */ + snprintf(buffer, buff_len, "UTF-%s%s", digits, endianness); + } + else + { + /* Not our client - copy as is*/ + strncpy(buffer, cs_name, buff_len - 1); + buffer[buff_len - 1]= '\0'; + } + + if (target_cs) + { + strncat(buffer, "//TRANSLIT", buff_len - strlen(buffer)); + } +} +/* }}} */ +#endif + +/* {{{ mariadb_convert_string + Converts string from one charset to another, and writes converted string to given buffer + @param[in] from + @param[in/out] from_len + @param[in] from_cs + @param[out] to + @param[in/out] to_len + @param[in] to_cs + @param[out] errorcode + + @return -1 in case of error, bytes used in the "to" buffer, otherwise + */ +size_t STDCALL mariadb_convert_string(const char *from __attribute__((unused)), + size_t *from_len __attribute__((unused)), + MARIADB_CHARSET_INFO *from_cs __attribute__((unused)), + char *to __attribute__((unused)), + size_t *to_len __attribute__((unused)), + MARIADB_CHARSET_INFO *to_cs __attribute__((unused)), int *errorcode) +{ +#ifndef HAVE_ICONV + *errorcode= ENOTSUP; + return -1; +#else + iconv_t conv= 0; + size_t rc= -1; + size_t save_len= *to_len; + char to_encoding[128], from_encoding[128]; + + *errorcode= 0; + + /* check if conversion is supported */ + if (!from_cs || !from_cs->encoding || !from_cs->encoding[0] || + !to_cs || !to_cs->encoding || !to_cs->encoding[0]) + { + *errorcode= EINVAL; + return rc; + } + + map_charset_name(to_cs->encoding, 1, to_encoding, sizeof(to_encoding)); + map_charset_name(from_cs->encoding, 0, from_encoding, sizeof(from_encoding)); + + if ((conv= iconv_open(to_encoding, from_encoding)) == (iconv_t)-1) + { + *errorcode= errno; + goto error; + } + if ((rc= iconv(conv, IF_WIN(,IF_SOLARIS(,(char **)))&from, from_len, &to, to_len)) == (size_t)-1) + { + *errorcode= errno; + goto error; + } + rc= save_len - *to_len; +error: + if (conv != (iconv_t)-1) + iconv_close(conv); + return rc; +#endif +} +/* }}} */ + diff --git a/libmariadb/libmariadb/ma_client_plugin.c.in b/libmariadb/libmariadb/ma_client_plugin.c.in new file mode 100644 index 00000000..6e0433b3 --- /dev/null +++ b/libmariadb/libmariadb/ma_client_plugin.c.in @@ -0,0 +1,503 @@ +/* Copyright (C) 2010 - 2012 Sergei Golubchik and Monty Program Ab + 2015-2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA */ + +/** + @file + + Support code for the client side (libmariadb) plugins + + Client plugins are somewhat different from server plugins, they are simpler. + + They do not need to be installed or in any way explicitly loaded on the + client, they are loaded automatically on demand. + One client plugin per shared object, soname *must* match the plugin name. + + There is no reference counting and no unloading either. +*/ + +/* Silence warnings about variable 'unused' being used. */ +#define FORCE_INIT_OF_VARS 1 + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <ma_string.h> +#include <ma_pthread.h> + +#include "errmsg.h" +#include <mysql/client_plugin.h> + +#ifndef WIN32 +#include <dlfcn.h> +#endif + +struct st_client_plugin_int { + struct st_client_plugin_int *next; + void *dlhandle; + struct st_mysql_client_plugin *plugin; +}; + +static my_bool initialized= 0; +static MA_MEM_ROOT mem_root; + +static uint valid_plugins[][2]= { + {MYSQL_CLIENT_AUTHENTICATION_PLUGIN, MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_PVIO_PLUGIN, MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_TRACE_PLUGIN, MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_REMOTEIO_PLUGIN, MARIADB_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_CONNECTION_PLUGIN, MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION}, + {MARIADB_CLIENT_COMPRESSION_PLUGIN, MARIADB_CLIENT_COMPRESSION_PLUGIN_INTERFACE_VERSION}, + {0, 0} +}; + +/* + Loaded plugins are stored in a linked list. + The list is append-only, the elements are added to the head (like in a stack). + The elements are added under a mutex, but the list can be read and traversed + without any mutex because once an element is added to the list, it stays + there. The main purpose of a mutex is to prevent two threads from + loading the same plugin twice in parallel. +*/ + + +struct st_client_plugin_int *plugin_list[MYSQL_CLIENT_MAX_PLUGINS + MARIADB_CLIENT_MAX_PLUGINS]; +#ifdef THREAD +static pthread_mutex_t LOCK_load_client_plugin; +#endif + +@EXTERNAL_PLUGINS@ + +struct st_mysql_client_plugin *mysql_client_builtins[]= +{ + @BUILTIN_PLUGINS@ + 0 +}; + + +static int is_not_initialized(MYSQL *mysql, const char *name) +{ + if (initialized) + return 0; + + my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, + SQLSTATE_UNKNOWN, ER(CR_AUTH_PLUGIN_CANNOT_LOAD), + name, "not initialized"); + return 1; +} + +static int get_plugin_nr(uint type) +{ + uint i= 0; + for(; valid_plugins[i][1]; i++) + if (valid_plugins[i][0] == type) + return i; + return -1; +} + +static const char *check_plugin_version(struct st_mysql_client_plugin *plugin, unsigned int version) +{ + if (plugin->interface_version < version || + (plugin->interface_version >> 8) > (version >> 8)) + return "Incompatible client plugin interface"; + return 0; +} + +/** + finds a plugin in the list + + @param name plugin name to search for + @param type plugin type + + @note this does NOT necessarily need a mutex, take care! + + @retval a pointer to a found plugin or 0 +*/ +static struct st_mysql_client_plugin *find_plugin(const char *name, int type) +{ + struct st_client_plugin_int *p; + int plugin_nr= get_plugin_nr(type); + + DBUG_ASSERT(initialized); + if (plugin_nr == -1) + return 0; + + if (!name) + return plugin_list[plugin_nr]->plugin; + + for (p= plugin_list[plugin_nr]; p; p= p->next) + { + if (strcmp(p->plugin->name, name) == 0) + return p->plugin; + } + return NULL; +} + + +/** + verifies the plugin and adds it to the list + + @param mysql MYSQL structure (for error reporting) + @param plugin plugin to install + @param dlhandle a handle to the shared object (returned by dlopen) + or 0 if the plugin was not dynamically loaded + @param argc number of arguments in the 'va_list args' + @param args arguments passed to the plugin initialization function + + @retval a pointer to an installed plugin or 0 +*/ + +static struct st_mysql_client_plugin * +add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle, + int argc, va_list args) +{ + const char *errmsg; + struct st_client_plugin_int plugin_int, *p; + char errbuf[1024]; + int plugin_nr; + + DBUG_ASSERT(initialized); + + plugin_int.plugin= plugin; + plugin_int.dlhandle= dlhandle; + + if ((plugin_nr= get_plugin_nr(plugin->type)) == -1) + { + errmsg= "Unknown client plugin type"; + goto err1; + } + if ((errmsg= check_plugin_version(plugin, valid_plugins[plugin_nr][1]))) + goto err1; + + /* Call the plugin initialization function, if any */ + if (plugin->init && plugin->init(errbuf, sizeof(errbuf), argc, args)) + { + errmsg= errbuf; + goto err1; + } + + p= (struct st_client_plugin_int *) + ma_memdup_root(&mem_root, (char *)&plugin_int, sizeof(plugin_int)); + + if (!p) + { + errmsg= "Out of memory"; + goto err2; + } + + + p->next= plugin_list[plugin_nr]; + plugin_list[plugin_nr]= p; + + return plugin; + +err2: + if (plugin->deinit) + plugin->deinit(); +err1: + my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), plugin->name, errmsg); + if (dlhandle) + (void)dlclose(dlhandle); + return NULL; +} + + +/** + Loads plugins which are specified in the environment variable + LIBMYSQL_PLUGINS. + + Multiple plugins must be separated by semicolon. This function doesn't + return or log an error. + + The function is be called by mysql_client_plugin_init + + @todo + Support extended syntax, passing parameters to plugins, for example + LIBMYSQL_PLUGINS="plugin1(param1,param2);plugin2;..." + or + LIBMYSQL_PLUGINS="plugin1=int:param1,str:param2;plugin2;..." +*/ + +static void load_env_plugins(MYSQL *mysql) +{ + char *plugs, *free_env, *s= getenv("LIBMYSQL_PLUGINS"); + + if (ma_check_env_str(s)) + return; + + free_env= strdup(s); + plugs= s= free_env; + + do { + if ((s= strchr(plugs, ';'))) + *s= '\0'; + mysql_load_plugin(mysql, plugs, -1, 0); + plugs= s + 1; + } while (s); + + free(free_env); +} + +/********** extern functions to be used by libmariadb *********************/ + +/** + Initializes the client plugin layer. + + This function must be called before any other client plugin function. + + @retval 0 successful + @retval != 0 error occurred +*/ + +int mysql_client_plugin_init() +{ + MYSQL mysql; + struct st_mysql_client_plugin **builtin; + va_list unused; + LINT_INIT_STRUCT(unused); + + if (initialized) + return 0; + + memset(&mysql, 0, sizeof(mysql)); /* dummy mysql for set_mysql_extended_error */ + + pthread_mutex_init(&LOCK_load_client_plugin, NULL); + ma_init_alloc_root(&mem_root, 128, 128); + + memset(&plugin_list, 0, sizeof(plugin_list)); + + initialized= 1; + + pthread_mutex_lock(&LOCK_load_client_plugin); + for (builtin= mysql_client_builtins; *builtin; builtin++) + add_plugin(&mysql, *builtin, 0, 0, unused); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + + load_env_plugins(&mysql); + + return 0; +} + + +/** + Deinitializes the client plugin layer. + + Unloades all client plugins and frees any associated resources. +*/ + +void mysql_client_plugin_deinit() +{ + int i; + struct st_client_plugin_int *p; + + if (!initialized) + return; + + for (i=0; i < MYSQL_CLIENT_MAX_PLUGINS; i++) + for (p= plugin_list[i]; p; p= p->next) + { + if (p->plugin->deinit) + p->plugin->deinit(); + if (p->dlhandle) + (void)dlclose(p->dlhandle); + } + + memset(&plugin_list, 0, sizeof(plugin_list)); + initialized= 0; + ma_free_root(&mem_root, MYF(0)); + pthread_mutex_destroy(&LOCK_load_client_plugin); +} + +/************* public facing functions, for client consumption *********/ + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * STDCALL +mysql_client_register_plugin(MYSQL *mysql, + struct st_mysql_client_plugin *plugin) +{ + struct st_mysql_client_plugin *found_plugin= NULL; + va_list unused; + LINT_INIT_STRUCT(unused); + + if (is_not_initialized(mysql, plugin->name)) + return NULL; + + pthread_mutex_lock(&LOCK_load_client_plugin); + + /* make sure the plugin wasn't loaded meanwhile */ + if (!(found_plugin= find_plugin(plugin->name, plugin->type))) + found_plugin= add_plugin(mysql, plugin, 0, 0, unused); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + return found_plugin; +} + + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * STDCALL +mysql_load_plugin_v(MYSQL *mysql, const char *name, int type, + int argc, va_list args) +{ + const char *errmsg; +#ifdef _WIN32 + char errbuf[1024]; +#endif + char dlpath[FN_REFLEN+1]; + void *sym, *dlhandle = NULL; + struct st_mysql_client_plugin *plugin; + char *env_plugin_dir= getenv("MARIADB_PLUGIN_DIR"); + + CLEAR_CLIENT_ERROR(mysql); + if (is_not_initialized(mysql, name)) + return NULL; + + pthread_mutex_lock(&LOCK_load_client_plugin); + + /* make sure the plugin wasn't loaded meanwhile */ + if (type >= 0 && find_plugin(name, type)) + { + errmsg= "it is already loaded"; + goto err; + } + + /* Compile dll path */ +#ifndef WIN32 + snprintf(dlpath, sizeof(dlpath) - 1, "%s/%s%s", + mysql->options.extension && mysql->options.extension->plugin_dir ? + mysql->options.extension->plugin_dir : (env_plugin_dir) ? env_plugin_dir : + MARIADB_PLUGINDIR, name, SO_EXT); +#else + { + char *p= (mysql->options.extension && mysql->options.extension->plugin_dir) ? + mysql->options.extension->plugin_dir : env_plugin_dir; + snprintf(dlpath, sizeof(dlpath), "%s%s%s%s", p ? p : "", p ? "\\" : "", name, SO_EXT); + } +#endif + + if (strpbrk(name, "()[]!@#$%^&/*;.,'?\\")) + { + errmsg= "invalid plugin name"; + goto err; + } + + + /* Open new dll handle */ + if (!(dlhandle= dlopen((const char *)dlpath, RTLD_NOW))) + { +#ifdef _WIN32 + char winmsg[255]; + size_t len; + winmsg[0] = 0; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + winmsg, 255, NULL); + len= strlen(winmsg); + while (len > 0 && (winmsg[len - 1] == '\n' || winmsg[len - 1] == '\r')) + len--; + if (len) + winmsg[len] = 0; + snprintf(errbuf, sizeof(errbuf), "%s Library path is '%s'", winmsg, dlpath); + errmsg= errbuf; +#else + errmsg= dlerror(); +#endif + goto err; + } + + + if (!(sym= dlsym(dlhandle, plugin_declarations_sym))) + { + errmsg= "not a plugin"; + (void)dlclose(dlhandle); + goto err; + } + + plugin= (struct st_mysql_client_plugin*)sym; + + if (type >=0 && type != plugin->type) + { + errmsg= "type mismatch"; + goto err; + } + + if (strcmp(name, plugin->name)) + { + errmsg= "name mismatch"; + goto err; + } + + if (type < 0 && find_plugin(name, plugin->type)) + { + errmsg= "it is already loaded"; + goto err; + } + + plugin= add_plugin(mysql, plugin, dlhandle, argc, args); + + pthread_mutex_unlock(&LOCK_load_client_plugin); + + return plugin; + +err: + if (dlhandle) + dlclose(dlhandle); + pthread_mutex_unlock(&LOCK_load_client_plugin); + my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, errmsg); + return NULL; +} + + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * STDCALL +mysql_load_plugin(MYSQL *mysql, const char *name, int type, int argc, ...) +{ + struct st_mysql_client_plugin *p; + va_list args; + va_start(args, argc); + p= mysql_load_plugin_v(mysql, name, type, argc, args); + va_end(args); + return p; +} + +/* see <mysql/client_plugin.h> for a full description */ +struct st_mysql_client_plugin * STDCALL +mysql_client_find_plugin(MYSQL *mysql, const char *name, int type) +{ + struct st_mysql_client_plugin *p; + int plugin_nr= get_plugin_nr(type); + + if (is_not_initialized(mysql, name)) + return NULL; + + if (plugin_nr == -1) + { + my_set_error(mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, SQLSTATE_UNKNOWN, + ER(CR_AUTH_PLUGIN_CANNOT_LOAD), name, "invalid type"); + } + + if ((p= find_plugin(name, type))) + return p; + + /* not found, load it */ + return mysql_load_plugin(mysql, name, type, 0); +} + diff --git a/libmariadb/libmariadb/ma_compress.c b/libmariadb/libmariadb/ma_compress.c new file mode 100644 index 00000000..002246e9 --- /dev/null +++ b/libmariadb/libmariadb/ma_compress.c @@ -0,0 +1,113 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016, 2022 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* Initially Written by Sinisa Milivojevic <sinisa@coresinc.com> */ + +#include <ma_global.h> +#ifdef HAVE_COMPRESS +#include <mysql.h> +#include <ma_common.h> +#include <ma_sys.h> +#include <ma_string.h> + +const char *compression_algorithms[] = +{ + "none", + "zlib", + "zstd", + "unknown" +}; + +const char *_mariadb_compression_algorithm_str(enum enum_ma_compression_algorithm algorithm) +{ + switch(algorithm) { + case COMPRESSION_NONE: + case COMPRESSION_ZLIB: + case COMPRESSION_ZSTD: + return compression_algorithms[algorithm] ; + default: + return compression_algorithms[COMPRESSION_UNKNOWN]; + } +} +/* +** This replaces the packet with a compressed packet +** Returns 1 on error +** *complen is 0 if the packet wasn't compressed +*/ + +my_bool _mariadb_compress(NET *net, unsigned char *packet, size_t *len, size_t *complen) +{ + if (*len < MIN_COMPRESS_LENGTH || + !compression_plugin(net)) + *complen=0; + else + { + unsigned char *compbuf=_mariadb_compress_alloc(net,packet,len,complen); + if (!compbuf) + return *complen ? 0 : 1; + memcpy(packet,compbuf,*len); + free(compbuf); + } + return 0; +} + +unsigned char *_mariadb_compress_alloc(NET *net, const unsigned char *packet, size_t *len, size_t *complen) +{ + unsigned char *compbuf; + *complen = *len * 120 / 100 + 12; + + if (!(compbuf = (unsigned char *) malloc(*complen))) + return 0; /* Not enough memory */ + + if (compression_plugin(net)->compress(compression_ctx(net), (void *)compbuf, complen, (void *)packet, *len)) + { + free(compbuf); + return 0; + } + + if (*complen >= *len) + { + *complen=0; + free(compbuf); + return 0; + } + + swap(size_t,*len,*complen); /* *len is now packet length */ + return compbuf; +} + +my_bool _mariadb_uncompress (NET *net, unsigned char *packet, size_t *len, size_t *complen) +{ + if (*complen) /* If compressed */ + { + unsigned char *compbuf = (unsigned char *) malloc (*complen); + if (!compbuf) + return 1; /* Not enough memory */ + if (compression_plugin(net)->decompress(compression_ctx(net), compbuf, complen, packet, len)) + { /* Probably wrong packet */ + free(compbuf); + return 1; + } + *len = *complen; + memcpy(packet,compbuf,*len); + free(compbuf); + } + else *complen= *len; + return 0; +} +#endif /* HAVE_COMPRESS */ diff --git a/libmariadb/libmariadb/ma_context.c b/libmariadb/libmariadb/ma_context.c new file mode 100644 index 00000000..222dbe28 --- /dev/null +++ b/libmariadb/libmariadb/ma_context.c @@ -0,0 +1,728 @@ +/* + Copyright 2011, 2012 Kristian Nielsen and Monty Program Ab + 2016 MariaDB Corporation AB + + This file is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + Implementation of async context spawning using Posix ucontext and + swapcontext(). +*/ + +#include "ma_global.h" +#include "ma_string.h" +#include "ma_context.h" + +#ifdef HAVE_VALGRIND +#include <valgrind/valgrind.h> +#endif + +#ifdef MY_CONTEXT_USE_UCONTEXT + +typedef void (*uc_func_t)(void); + +/* + The makecontext() only allows to pass integers into the created context :-( + We want to pass pointers, so we do it this kinda hackish way. + Anyway, it should work everywhere, and at least it does not break strict + aliasing. +*/ +union pass_void_ptr_as_2_int { + int a[2]; + void *p; +}; + +/* + We use old-style function definition here, as this is passed to + makecontext(). And the type of the makecontext() argument does not match + the actual type (as the actual type can differ from call to call). +*/ +static void +my_context_spawn_internal(int i0, int i1) +{ + int err; + struct my_context *c; + union pass_void_ptr_as_2_int u; + + u.a[0]= i0; + u.a[1]= i1; + c= (struct my_context *)u.p; + + (*c->user_func)(c->user_data); + c->active= 0; + err= setcontext(&c->base_context); + fprintf(stderr, "Aieie, setcontext() failed: %d (errno=%d)\n", err, errno); +} + + +int +my_context_continue(struct my_context *c) +{ + int err; + + if (!c->active) + return 0; + + err= swapcontext(&c->base_context, &c->spawned_context); + if (err) + { + fprintf(stderr, "Aieie, swapcontext() failed: %d (errno=%d)\n", + err, errno); + return -1; + } + + return c->active; +} + + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + int err; + union pass_void_ptr_as_2_int u; + + err= getcontext(&c->spawned_context); + if (err) + return -1; + c->spawned_context.uc_stack.ss_sp= c->stack; + c->spawned_context.uc_stack.ss_size= c->stack_size; + c->spawned_context.uc_link= NULL; + c->user_func= f; + c->user_data= d; + c->active= 1; + u.p= c; + makecontext(&c->spawned_context, (uc_func_t)my_context_spawn_internal, 2, + u.a[0], u.a[1]); + + return my_context_continue(c); +} + + +int +my_context_yield(struct my_context *c) +{ + int err; + + if (!c->active) + return -1; + + err= swapcontext(&c->spawned_context, &c->base_context); + if (err) + return -1; + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ +#if SIZEOF_CHARP > SIZEOF_INT*2 +#error Error: Unable to store pointer in 2 ints on this architecture +#endif + + memset(c, 0, sizeof(*c)); + if (!(c->stack= malloc(stack_size))) + return -1; /* Out of memory */ + c->stack_size= stack_size; +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack, ((unsigned char *)(c->stack))+stack_size); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack) + { +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + free(c->stack); + } +} + +#endif /* MY_CONTEXT_USE_UCONTEXT */ + + +#ifdef MY_CONTEXT_USE_X86_64_GCC_ASM +/* + GCC-amd64 implementation of my_context. + + This is slightly optimized in the common case where we never yield + (eg. fetch next row and it is already fully received in buffer). In this + case we do not need to restore registers at return (though we still need to + save them as we cannot know if we will yield or not in advance). +*/ + +#include <stdint.h> +#include <stdlib.h> + +/* + Layout of saved registers etc. + Since this is accessed through gcc inline assembler, it is simpler to just + use numbers than to try to define nice constants or structs. + + 0 0 %rsp + 1 8 %rbp + 2 16 %rbx + 3 24 %r12 + 4 32 %r13 + 5 40 %r14 + 6 48 %r15 + 7 56 %rip for done + 8 64 %rip for yield/continue +*/ + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + int ret; + + /* + There are 6 callee-save registers we need to save and restore when + suspending and continuing, plus stack pointer %rsp and instruction pointer + %rip. + + However, if we never suspend, the user-supplied function will in any case + restore the 6 callee-save registers, so we can avoid restoring them in + this case. + */ + __asm__ __volatile__ + ( + "movq %%rsp, (%[save])\n\t" + "movq %[stack], %%rsp\n\t" +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 4 && !defined(__INTEL_COMPILER) + /* + This emits a DWARF DW_CFA_undefined directive to make the return address + undefined. This indicates that this is the top of the stack frame, and + helps tools that use DWARF stack unwinding to obtain stack traces. + (I use numeric constant to avoid a dependency on libdwarf includes). + */ + ".cfi_escape 0x07, 16\n\t" +#endif + "movq %%rbp, 8(%[save])\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%r15, 48(%[save])\n\t" + "leaq 1f(%%rip), %%rax\n\t" + "leaq 2f(%%rip), %%rcx\n\t" + "movq %%rax, 56(%[save])\n\t" + "movq %%rcx, 64(%[save])\n\t" + /* + Constraint below puts the argument to the user function into %rdi, as + needed for the calling convention. + */ + "callq *%[f]\n\t" + "jmpq *56(%[save])\n" + /* + Come here when operation is done. + We do not need to restore callee-save registers, as the called function + will do this for us if needed. + */ + "1:\n\t" + "movq (%[save]), %%rsp\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 3f\n" + /* Come here when operation was suspended. */ + "2:\n\t" + "movl $1, %[ret]\n" + "3:\n" + : [ret] "=a" (ret), + [f] "+S" (f), + /* Need this in %rdi to follow calling convention. */ + [d] "+D" (d) + : [stack] "a" (c->stack_top), + /* Need this in callee-save register to preserve in function call. */ + [save] "b" (&c->save[0]) + : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc" + ); + + return ret; +} + +int +my_context_continue(struct my_context *c) +{ + int ret; + + __asm__ __volatile__ + ( + "movq (%[save]), %%rax\n\t" + "movq %%rsp, (%[save])\n\t" + "movq %%rax, %%rsp\n\t" + "movq 8(%[save]), %%rax\n\t" + "movq %%rbp, 8(%[save])\n\t" + "movq %%rax, %%rbp\n\t" + "movq 24(%[save]), %%rax\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%rax, %%r12\n\t" + "movq 32(%[save]), %%rax\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%rax, %%r13\n\t" + "movq 40(%[save]), %%rax\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%rax, %%r14\n\t" + "movq 48(%[save]), %%rax\n\t" + "movq %%r15, 48(%[save])\n\t" + "movq %%rax, %%r15\n\t" + + "leaq 1f(%%rip), %%rax\n\t" + "leaq 2f(%%rip), %%rcx\n\t" + "movq %%rax, 56(%[save])\n\t" + "movq 64(%[save]), %%rax\n\t" + "movq %%rcx, 64(%[save])\n\t" + + "movq 16(%[save]), %%rcx\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%rcx, %%rbx\n\t" + + "jmpq *%%rax\n" + /* + Come here when operation is done. + Be sure to use the same callee-save register for %[save] here and in + my_context_spawn(), so we preserve the value correctly at this point. + */ + "1:\n\t" + "movq (%[save]), %%rsp\n\t" + "movq 8(%[save]), %%rbp\n\t" + /* %rbx is preserved from my_context_spawn() in this case. */ + "movq 24(%[save]), %%r12\n\t" + "movq 32(%[save]), %%r13\n\t" + "movq 40(%[save]), %%r14\n\t" + "movq 48(%[save]), %%r15\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 3f\n" + /* Come here when operation is suspended. */ + "2:\n\t" + "movl $1, %[ret]\n" + "3:\n" + : [ret] "=a" (ret) + : /* Need this in callee-save register to preserve in function call. */ + [save] "b" (&c->save[0]) + : "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory", "cc" + ); + + return ret; +} + +int +my_context_yield(struct my_context *c) +{ + uint64_t *save= &c->save[0]; + __asm__ __volatile__ + ( + "movq (%[save]), %%rax\n\t" + "movq %%rsp, (%[save])\n\t" + "movq %%rax, %%rsp\n\t" + "movq 8(%[save]), %%rax\n\t" + "movq %%rbp, 8(%[save])\n\t" + "movq %%rax, %%rbp\n\t" + "movq 16(%[save]), %%rax\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%rax, %%rbx\n\t" + "movq 24(%[save]), %%rax\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%rax, %%r12\n\t" + "movq 32(%[save]), %%rax\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%rax, %%r13\n\t" + "movq 40(%[save]), %%rax\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%rax, %%r14\n\t" + "movq 48(%[save]), %%rax\n\t" + "movq %%r15, 48(%[save])\n\t" + "movq %%rax, %%r15\n\t" + "movq 64(%[save]), %%rax\n\t" + "leaq 1f(%%rip), %%rcx\n\t" + "movq %%rcx, 64(%[save])\n\t" + + "jmpq *%%rax\n" + + "1:\n" + : [save] "+D" (save) + : + : "rax", "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc" + ); + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + memset(c, 0, sizeof(*c)); + + if (!(c->stack_bot= malloc(stack_size))) + return -1; /* Out of memory */ + /* + The x86_64 ABI specifies 16-byte stack alignment. + Also put two zero words at the top of the stack. + */ + c->stack_top= (void *) + (( ((intptr)c->stack_bot + stack_size) & ~(intptr)0xf) - 16); + memset(c->stack_top, 0, 16); + +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack_bot, c->stack_top); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack_bot) + { + free(c->stack_bot); +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + } +} + +#endif /* MY_CONTEXT_USE_X86_64_GCC_ASM */ + + +#ifdef MY_CONTEXT_USE_I386_GCC_ASM +/* + GCC-i386 implementation of my_context. + + This is slightly optimized in the common case where we never yield + (eg. fetch next row and it is already fully received in buffer). In this + case we do not need to restore registers at return (though we still need to + save them as we cannot know if we will yield or not in advance). +*/ + +#include <stdint.h> +#include <stdlib.h> + +/* + Layout of saved registers etc. + Since this is accessed through gcc inline assembler, it is simpler to just + use numbers than to try to define nice constants or structs. + + 0 0 %esp + 1 4 %ebp + 2 8 %ebx + 3 12 %esi + 4 16 %edi + 5 20 %eip for done + 6 24 %eip for yield/continue +*/ + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + int ret; + + /* + There are 4 callee-save registers we need to save and restore when + suspending and continuing, plus stack pointer %esp and instruction pointer + %eip. + + However, if we never suspend, the user-supplied function will in any case + restore the 4 callee-save registers, so we can avoid restoring them in + this case. + */ + __asm__ __volatile__ + ( + "movl %%esp, (%[save])\n\t" + "movl %[stack], %%esp\n\t" +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 4 && !defined(__INTEL_COMPILER) + /* + This emits a DWARF DW_CFA_undefined directive to make the return address + undefined. This indicates that this is the top of the stack frame, and + helps tools that use DWARF stack unwinding to obtain stack traces. + (I use numeric constant to avoid a dependency on libdwarf includes). + */ + ".cfi_escape 0x07, 8\n\t" +#endif + /* Push the parameter on the stack. */ + "pushl %[d]\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%edi, 16(%[save])\n\t" + /* Get label addresses in -fPIC-compatible way (no pc-relative on 32bit) */ + "call 1f\n" + "1:\n\t" + "popl %%eax\n\t" + "addl $(2f-1b), %%eax\n\t" + "movl %%eax, 20(%[save])\n\t" + "addl $(3f-2f), %%eax\n\t" + "movl %%eax, 24(%[save])\n\t" + "call *%[f]\n\t" + "jmp *20(%[save])\n" + /* + Come here when operation is done. + We do not need to restore callee-save registers, as the called function + will do this for us if needed. + */ + "2:\n\t" + "movl (%[save]), %%esp\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 4f\n" + /* Come here when operation was suspended. */ + "3:\n\t" + "movl $1, %[ret]\n" + "4:\n" + : [ret] "=a" (ret), + [f] "+c" (f), + [d] "+d" (d) + : [stack] "a" (c->stack_top), + /* Need this in callee-save register to preserve across function call. */ + [save] "D" (&c->save[0]) + : "memory", "cc" + ); + + return ret; +} + +int +my_context_continue(struct my_context *c) +{ + int ret; + + __asm__ __volatile__ + ( + "movl (%[save]), %%eax\n\t" + "movl %%esp, (%[save])\n\t" + "movl %%eax, %%esp\n\t" + "movl 4(%[save]), %%eax\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%eax, %%ebp\n\t" + "movl 8(%[save]), %%eax\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%eax, %%ebx\n\t" + "movl 12(%[save]), %%eax\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%eax, %%esi\n\t" + + "movl 24(%[save]), %%eax\n\t" + "call 1f\n" + "1:\n\t" + "popl %%ecx\n\t" + "addl $(2f-1b), %%ecx\n\t" + "movl %%ecx, 20(%[save])\n\t" + "addl $(3f-2f), %%ecx\n\t" + "movl %%ecx, 24(%[save])\n\t" + + /* Must restore %edi last as it is also our %[save] register. */ + "movl 16(%[save]), %%ecx\n\t" + "movl %%edi, 16(%[save])\n\t" + "movl %%ecx, %%edi\n\t" + + "jmp *%%eax\n" + /* + Come here when operation is done. + Be sure to use the same callee-save register for %[save] here and in + my_context_spawn(), so we preserve the value correctly at this point. + */ + "2:\n\t" + "movl (%[save]), %%esp\n\t" + "movl 4(%[save]), %%ebp\n\t" + "movl 8(%[save]), %%ebx\n\t" + "movl 12(%[save]), %%esi\n\t" + "movl 16(%[save]), %%edi\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 4f\n" + /* Come here when operation is suspended. */ + "3:\n\t" + "movl $1, %[ret]\n" + "4:\n" + : [ret] "=a" (ret) + : /* Need this in callee-save register to preserve in function call. */ + [save] "D" (&c->save[0]) + : "ecx", "edx", "memory", "cc" + ); + + return ret; +} + +int +my_context_yield(struct my_context *c) +{ + uint64_t *save= &c->save[0]; + __asm__ __volatile__ + ( + "movl (%[save]), %%eax\n\t" + "movl %%esp, (%[save])\n\t" + "movl %%eax, %%esp\n\t" + "movl 4(%[save]), %%eax\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%eax, %%ebp\n\t" + "movl 8(%[save]), %%eax\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%eax, %%ebx\n\t" + "movl 12(%[save]), %%eax\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%eax, %%esi\n\t" + "movl 16(%[save]), %%eax\n\t" + "movl %%edi, 16(%[save])\n\t" + "movl %%eax, %%edi\n\t" + + "movl 24(%[save]), %%eax\n\t" + "call 1f\n" + "1:\n\t" + "popl %%ecx\n\t" + "addl $(2f-1b), %%ecx\n\t" + "movl %%ecx, 24(%[save])\n\t" + + "jmp *%%eax\n" + + "2:\n" + : [save] "+d" (save) + : + : "eax", "ecx", "memory", "cc" + ); + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + memset(c, 0, sizeof(*c)); + if (!(c->stack_bot= malloc(stack_size))) + return -1; /* Out of memory */ + c->stack_top= (void *) + (( ((intptr)c->stack_bot + stack_size) & ~(intptr)0xf) - 16); + memset(c->stack_top, 0, 16); + +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack_bot, c->stack_top); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack_bot) + { + free(c->stack_bot); +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + } +} + +#endif /* MY_CONTEXT_USE_I386_GCC_ASM */ + + +#ifdef MY_CONTEXT_USE_WIN32_FIBERS +int +my_context_yield(struct my_context *c) +{ + c->return_value= 1; + SwitchToFiber(c->app_fiber); + return 0; +} + + +static void WINAPI +my_context_trampoline(void *p) +{ + struct my_context *c= (struct my_context *)p; + /* + Reuse the Fiber by looping infinitely, each time we are scheduled we + spawn the appropriate function and switch back when it is done. + + This way we avoid the overhead of CreateFiber() for every asynchronous + operation. + */ + for(;;) + { + (*(c->user_func))(c->user_arg); + c->return_value= 0; + SwitchToFiber(c->app_fiber); + } +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + memset(c, 0, sizeof(*c)); + c->lib_fiber= CreateFiber(stack_size, my_context_trampoline, c); + if (c->lib_fiber) + return 0; + return -1; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->lib_fiber) + { + DeleteFiber(c->lib_fiber); + c->lib_fiber= NULL; + } +} + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + c->user_func= f; + c->user_arg= d; + return my_context_continue(c); +} + +int +my_context_continue(struct my_context *c) +{ + void *current_fiber= IsThreadAFiber() ? GetCurrentFiber() : ConvertThreadToFiber(c); + c->app_fiber= current_fiber; + SwitchToFiber(c->lib_fiber); + return c->return_value; +} + +#endif /* MY_CONTEXT_USE_WIN32_FIBERS */ + +#ifdef MY_CONTEXT_DISABLE +int +my_context_continue(struct my_context *c) +{ + return -1; +} + + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + return -1; +} + + +int +my_context_yield(struct my_context *c) +{ + return -1; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + return -1; /* Out of memory */ +} + +void +my_context_destroy(struct my_context *c) +{ +} + +#endif diff --git a/libmariadb/libmariadb/ma_decimal.c b/libmariadb/libmariadb/ma_decimal.c new file mode 100644 index 00000000..de6062df --- /dev/null +++ b/libmariadb/libmariadb/ma_decimal.c @@ -0,0 +1,479 @@ +/* Copyright (C) 2004 Sergei Golubchik + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ + +/* +======================================================================= + NOTE: this library implements SQL standard "exact numeric" type + and is not at all generic, but rather intentinally crippled to + follow the standard :) +======================================================================= + Quoting the standard + (SQL:2003, Part 2 Foundations, aka ISO/IEC 9075-2:2003) + +4.4.2 Characteristics of numbers, page 27: + + An exact numeric type has a precision P and a scale S. P is a positive + integer that determines the number of significant digits in a + particular radix R, where R is either 2 or 10. S is a non-negative + integer. Every value of an exact numeric type of scale S is of the + form n*10^{-S}, where n is an integer such that ?-R^P <= n <= R^P. + + [...] + + If an assignment of some number would result in a loss of its most + significant digit, an exception condition is raised. If least + significant digits are lost, implementation-defined rounding or + truncating occurs, with no exception condition being raised. + + [...] + + Whenever an exact or approximate numeric value is assigned to an exact + numeric value site, an approximation of its value that preserves + leading significant digits after rounding or truncating is represented + in the declared type of the target. The value is converted to have the + precision and scale of the target. The choice of whether to truncate + or round is implementation-defined. + + [...] + + All numeric values between the smallest and the largest value, + inclusive, in a given exact numeric type have an approximation + obtained by rounding or truncation for that type; it is + implementation-defined which other numeric values have such + approximations. + +5.3 <literal>, page 143 + + <exact numeric literal> ::= + <unsigned integer> [ <period> [ <unsigned integer> ] ] + | <period> <unsigned integer> + +6.1 <data type>, page 165: + + 19) The <scale> of an <exact numeric type> shall not be greater than + the <precision> of the <exact numeric type>. + + 20) For the <exact numeric type>s DECIMAL and NUMERIC: + + a) The maximum value of <precision> is implementation-defined. + <precision> shall not be greater than this value. + b) The maximum value of <scale> is implementation-defined. <scale> + shall not be greater than this maximum value. + + 21) NUMERIC specifies the data type exact numeric, with the decimal + precision and scale specified by the <precision> and <scale>. + + 22) DECIMAL specifies the data type exact numeric, with the decimal + scale specified by the <scale> and the implementation-defined + decimal precision equal to or greater than the value of the + specified <precision>. + +6.26 <numeric value expression>, page 241: + + 1) If the declared type of both operands of a dyadic arithmetic + operator is exact numeric, then the declared type of the result is + an implementation-defined exact numeric type, with precision and + scale determined as follows: + + a) Let S1 and S2 be the scale of the first and second operands + respectively. + b) The precision of the result of addition and subtraction is + implementation-defined, and the scale is the maximum of S1 and S2. + c) The precision of the result of multiplication is + implementation-defined, and the scale is S1 + S2. + d) The precision and scale of the result of division are + implementation-defined. +*/ + +#include <ma_global.h> +#include <ma_sys.h> /* for my_alloca */ +#include <ma_decimal.h> +#include <mysql.h> +#include <mariadb_rpl.h> +#include <string.h> + +#ifdef WIN32 +#include <malloc.h> +#endif + +typedef decimal_digit dec1; +typedef longlong dec2; + +#define unlikely(A) (A) +#define DIG_PER_DEC1 9 +#define DIG_MASK 100000000 +#define DIG_BASE 1000000000 +#define DIG_BASE2 LL(1000000000000000000) +#define ROUND_UP(X) (((X)+DIG_PER_DEC1-1)/DIG_PER_DEC1) +static const dec1 powers10[DIG_PER_DEC1+1]={ + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; +static const int dig2bytes[DIG_PER_DEC1+1]={0, 1, 1, 2, 2, 3, 3, 4, 4, 4}; + +#define sanity(d) DBUG_ASSERT((d)->len >0 && ((d)->buf[0] | \ + (d)->buf[(d)->len-1] | 1)) + +#define FIX_INTG_FRAC_ERROR(len, intg1, frac1, error) \ + do \ + { \ + if (unlikely(intg1+frac1 > (len))) \ + { \ + if (unlikely(intg1 > (len))) \ + { \ + intg1=(len); \ + frac1=0; \ + error=E_DEC_OVERFLOW; \ + } \ + else \ + { \ + frac1=(len)-intg1; \ + error=E_DEC_TRUNCATED; \ + } \ + } \ + else \ + error=E_DEC_OK; \ + } while(0) + +#define ADD(to, from1, from2, carry) /* assume carry <= 1 */ \ + do \ + { \ + dec1 a=(from1)+(from2)+(carry); \ + if (((carry)= a >= DIG_BASE)) /* no division here! */ \ + a-=DIG_BASE; \ + (to)=a; \ + } while(0) + +#define ADD2(to, from1, from2, carry) \ + do \ + { \ + dec1 a=(from1)+(from2)+(carry); \ + if (((carry)= a >= DIG_BASE)) \ + a-=DIG_BASE; \ + if (unlikely(a >= DIG_BASE)) \ + { \ + a-=DIG_BASE; \ + carry++; \ + } \ + (to)=a; \ + } while(0) + +#define SUB(to, from1, from2, carry) /* to=from1-from2 */ \ + do \ + { \ + dec1 a=(from1)-(from2)-(carry); \ + if (((carry)= a < 0)) \ + a+=DIG_BASE; \ + (to)=a; \ + } while(0) + +#define SUB2(to, from1, from2, carry) /* to=from1-from2 */ \ + do \ + { \ + dec1 a=(from1)-(from2)-(carry); \ + if (((carry)= a < 0)) \ + a+=DIG_BASE; \ + if (unlikely(a < 0)) \ + { \ + a+=DIG_BASE; \ + carry++; \ + } \ + (to)=a; \ + } while(0) + +/* + Convert decimal to its printable string representation + + SYNOPSIS + decimal2string() + 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 + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW +*/ + +int decimal2string(decimal *from, char *to, int *to_len) +{ + int len, intg=from->intg, frac=from->frac, i; + int error=E_DEC_OK; + char *s=to; + dec1 *buf, *buf0=from->buf, tmp; + + DBUG_ASSERT(*to_len >= 2+from->sign); + + /* removing leading zeroes */ + i=((intg-1) % DIG_PER_DEC1)+1; + while (intg > 0 && *buf0 == 0) + { + intg-=i; + i=DIG_PER_DEC1; + buf0++; + } + if (intg > 0) + { + for (i=(intg-1) % DIG_PER_DEC1; *buf0 < powers10[i--]; intg--) {} + DBUG_ASSERT(intg > 0); + } + else + intg=0; + if (unlikely(intg+frac==0)) + { + intg=1; + tmp=0; + buf0=&tmp; + } + + len= from->sign + intg + test(frac) + frac; + if (unlikely(len > --*to_len)) /* reserve one byte for \0 */ + { + int i=len-*to_len; + error= (frac && i <= frac + 1) ? E_DEC_TRUNCATED : E_DEC_OVERFLOW; + if (frac && i >= frac + 1) i--; + if (i > frac) + { + intg-= i-frac; + frac= 0; + } + else + frac-=i; + len= from->sign + intg + test(frac) + frac; + } + *to_len=len; + s[len]=0; + + if (from->sign) + *s++='-'; + + if (frac) + { + char *s1=s+intg; + buf=buf0+ROUND_UP(intg); + *s1++='.'; + for (; frac>0; frac-=DIG_PER_DEC1) + { + dec1 x=*buf++; + for (i=min(frac, DIG_PER_DEC1); i; i--) + { + dec1 y=x/DIG_MASK; + *s1++='0'+(uchar)y; + x-=y*DIG_MASK; + x*=10; + } + } + } + + s+=intg; + for (buf=buf0+ROUND_UP(intg); intg>0; intg-=DIG_PER_DEC1) + { + dec1 x=*--buf; + for (i=min(intg, DIG_PER_DEC1); i; i--) + { + dec1 y=x/10; + *--s='0'+(uchar)(x-y*10); + x=y; + } + } + return error; +} + +/* + Convert string to decimal + + SYNOPSIS + str2decl() + from - value to convert + to - decimal where where the result will be stored + to->buf and to->len must be set. + end - if not NULL, *end will be set to the char where + conversion ended + fixed - use to->intg, to->frac as limits for input number + + NOTE + to->intg and to->frac can be modified even when fixed=1 + (but only decreased, in this case) + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW/E_DEC_BAD_NUM/E_DEC_OOM +*/ + +/* + Convert decimal to its binary fixed-length representation + two representations of the same length can be compared with memcmp + with the correct -1/0/+1 result + + SYNOPSIS + decimal2bin() + from - value to convert + to - points to buffer where string representation should be stored + precision/scale - see decimal_bin_size() below + + NOTE + the buffer is assumed to be of the size decimal_bin_size(precision, scale) + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW +*/ + +/* + Restores decimal from its binary fixed-length representation + + SYNOPSIS + bin2decimal() + from - value to convert + to - result + precision/scale - see decimal_bin_size() below + + NOTE + see decimal2bin() + the buffer is assumed to be of the size decimal_bin_size(precision, scale) + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW +*/ + +int bin2decimal(const char *from, decimal *to, int precision, int scale) +{ + int error=E_DEC_OK, + intg= precision - scale, + intg0= intg / DIG_PER_DEC1, + frac0= scale / DIG_PER_DEC1, + intg0x= intg - intg0 * DIG_PER_DEC1, + frac0x= scale - frac0*DIG_PER_DEC1, + intg1= intg0 + (intg0x > 0), + frac1= frac0 + (frac0x > 0), + tmp_size= decimal_bin_size(precision, scale); + char *tmp; + dec1 *buf= to->buf, + mask=(*from & 0x80) ? 0 : -1; + char *stop; + + /* Initial implementation from Sergei modified "from" buffer, (which errored + in binlog api when verifying checksum), so we declare from as read only and use + a stack buffer instead */ + tmp= (char *)alloca(tmp_size); + memcpy(tmp, from, tmp_size); + *tmp^= 0x80; /* remove sign bit */ + from= tmp; + + sanity(to); + + FIX_INTG_FRAC_ERROR(to->len, intg1, frac1, error); + if (unlikely(error)) + { + if (intg1 < intg0+(intg0x>0)) + { + from+= dig2bytes[intg0x] + sizeof(dec1)*(intg0 - intg1); + frac0= frac0x= intg0x= 0; + intg0= intg1; + } + else + { + frac0x= 0; + frac0= frac1; + } + } + + to->sign= (mask != 0); + to->intg= intg0 * DIG_PER_DEC1 + intg0x; + to->frac= frac0 * DIG_PER_DEC1 + frac0x; + + if (intg0x) + { + int i= dig2bytes[intg0x]; + dec1 x= 0; + switch (i) + { + case 1: x=myisam_sint1korr(from); break; + case 2: x=myisam_sint2korr(from); break; + case 3: x=myisam_sint3korr(from); break; + case 4: x=myisam_sint4korr(from); break; + default: DBUG_ASSERT(0); x= 0; + } + from+=i; + *buf=x ^ mask; + if (buf > to->buf || *buf != 0) + buf++; + else + to->intg-=intg0x; + } + for (stop=(char *)from+intg0*sizeof(dec1); from < stop; from+=sizeof(dec1)) + { + DBUG_ASSERT(sizeof(dec1) == 4); + *buf=myisam_sint4korr(from) ^ mask; + if (buf > to->buf || *buf != 0) + buf++; + else + to->intg-=DIG_PER_DEC1; + } + DBUG_ASSERT(to->intg >=0); + for (stop=(char *)from+frac0*sizeof(dec1); from < stop; from+=sizeof(dec1)) + { + DBUG_ASSERT(sizeof(dec1) == 4); + *buf=myisam_sint4korr(from) ^ mask; + buf++; + } + if (frac0x) + { + int i=dig2bytes[frac0x]; + dec1 x= 0; + switch (i) + { + case 1: x=myisam_sint1korr(from); break; + case 2: x=myisam_sint2korr(from); break; + case 3: x=myisam_sint3korr(from); break; + case 4: x=myisam_sint4korr(from); break; + default: DBUG_ASSERT(0); x= 0; + } + *buf= (x ^ mask) * powers10[DIG_PER_DEC1 - frac0x]; + buf++; + } + return error; +} + +/* + Returns the size of array to hold a decimal with given precision and scale + + RETURN VALUE + size in dec1 + (multiply by sizeof(dec1) to get the size if bytes) +*/ + +int decimal_size(int precision, int scale) +{ + DBUG_ASSERT(scale >= 0 && precision > 0 && scale <= precision); + return ROUND_UP(precision-scale)+ROUND_UP(scale); +} + +/* + Returns the size of array to hold a binary representation of a decimal + + RETURN VALUE + size in bytes +*/ + +int decimal_bin_size(int precision, int scale) +{ + int intg=precision-scale, + intg0=intg/DIG_PER_DEC1, frac0=scale/DIG_PER_DEC1, + intg0x=intg-intg0*DIG_PER_DEC1, frac0x=scale-frac0*DIG_PER_DEC1; + + DBUG_ASSERT(scale >= 0 && precision > 0 && scale <= precision); + return intg0*sizeof(dec1)+dig2bytes[intg0x]+ + frac0*sizeof(dec1)+dig2bytes[frac0x]; +} diff --git a/libmariadb/libmariadb/ma_default.c b/libmariadb/libmariadb/ma_default.c new file mode 100644 index 00000000..0f6921c1 --- /dev/null +++ b/libmariadb/libmariadb/ma_default.c @@ -0,0 +1,370 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +#include <ma_global.h> +#include <ma_sys.h> +#include "ma_string.h" +#include <ctype.h> +#include "mariadb_ctype.h" +#include <mysql.h> +#include <ma_common.h> +#include <mariadb/ma_io.h> + +#ifdef _WIN32 +#include <io.h> +#include "shlwapi.h" + +#define access _access + +static const char *ini_exts[]= {"ini", "cnf", 0}; +#define R_OK 4 +#else +#include <unistd.h> +static const char *ini_exts[]= {"cnf", 0}; +#endif + +char **configuration_dirs= NULL; +#define MAX_CONFIG_DIRS 6 + +my_bool _mariadb_read_options(MYSQL *mysql, + const char *config_dir, + const char *config_file, + const char *group, + unsigned int recursion); + +static int add_cfg_dir(char **cfg_dirs, const char *directory) +{ + int i; + + for (i = 0; i < MAX_CONFIG_DIRS && cfg_dirs[i]; i++) + if (!strcmp(cfg_dirs[i], directory)) /* already present */ + return 0; + + if (i < MAX_CONFIG_DIRS) { + cfg_dirs[i]= strdup(directory); + return 0; + } + return 1; +} + +void release_configuration_dirs() +{ + if (configuration_dirs) + { + int i= 0; + while (configuration_dirs[i]) + free(configuration_dirs[i++]); + free(configuration_dirs); + } +} + +char **get_default_configuration_dirs() +{ +#ifdef _WIN32 + char dirname[FN_REFLEN]; +#endif + char *env; + + configuration_dirs= (char **)calloc(1, (MAX_CONFIG_DIRS + 1) * sizeof(char *)); + if (!configuration_dirs) + goto end; + +#ifdef _WIN32 + /* On Windows operating systems configuration files are stored in + 1. System Windows directory + 2. System directory + 3. Windows directory + 4. C:\ + */ + + if (!GetSystemWindowsDirectory(dirname, FN_REFLEN) || + add_cfg_dir(configuration_dirs, dirname)) + goto error; + + if (!GetWindowsDirectory(dirname, FN_REFLEN) || + add_cfg_dir(configuration_dirs, dirname)) + goto error; + + if (add_cfg_dir(configuration_dirs, "C:")) + goto error; + + if (GetModuleFileName(NULL, dirname, FN_REFLEN)) + { + PathRemoveFileSpec(dirname); + if (add_cfg_dir(configuration_dirs, dirname)) + goto error; + } +#else + /* on *nix platforms configuration files are stored in + 1. SYSCONFDIR (if build happens inside server package, or + -DDEFAULT_SYSCONFDIR was specified + 2. /etc + 3. /etc/mysql + */ +#ifdef DEFAULT_SYSCONFDIR + if (add_cfg_dir(configuration_dirs, DEFAULT_SYSCONFDIR)) + goto error; +#else + if (add_cfg_dir(configuration_dirs, "/etc")) + goto error; + if (add_cfg_dir(configuration_dirs, "/etc/mysql")) + goto error; +#endif +#endif + /* CONC-537: Read configuration files from MYSQL_HOME directory only if + MARIADB_HOME was not set */ + if (!(env= getenv("MARIADB_HOME"))) + env= getenv("MYSQL_HOME"); + if (env && add_cfg_dir(configuration_dirs, env)) + goto error; +end: + return configuration_dirs; +error: + return NULL; +} + +extern my_bool _mariadb_set_conf_option(MYSQL *mysql, const char *config_option, const char *config_value); + +static my_bool is_group(char *ptr, const char **groups) +{ + while (*groups) + { + if (!strcmp(ptr, *groups)) + return 1; + groups++; + } + return 0; +} + +static my_bool _mariadb_read_options_from_file(MYSQL *mysql, + const char *config_file, + const char *group, + unsigned int recursion) +{ + my_bool read_values= 0, found_group= 0, is_escaped= 0, is_quoted= 0; + char buff[4096],*ptr,*end,*value, *key= 0, *optval; + MA_FILE *file= NULL; + my_bool rc= 1; + const char *groups[5]= {"client", + "client-server", + "client-mariadb", + group, + NULL}; + my_bool (*set_option)(MYSQL *mysql, const char *config_option, const char *config_value); + + + /* if a plugin registered a hook we will call this hook, otherwise + * default (_mariadb_set_conf_option) will be called */ + if (mysql->options.extension && mysql->options.extension->set_option) + set_option= mysql->options.extension->set_option; + else + set_option= _mariadb_set_conf_option; + + if (!(file = ma_open(config_file, "r", NULL))) + goto err; + + while (ma_gets(buff,sizeof(buff)-1,file)) + { + key= 0; + /* Ignore comment and empty lines */ + for (ptr=buff ; isspace(*ptr) ; ptr++ ); + if (!is_escaped && (*ptr == '\"' || *ptr== '\'')) + { + is_quoted= !is_quoted; + continue; + } + /* CONC- 327: !includedir and !include */ + if (*ptr == '!') + { + char *val; + ptr++; + if (!(val= strchr(ptr, ' '))) + continue; + *val++= 0; + end= strchr(val, 0); + for ( ; isspace(end[-1]) ; end--) ; /* Remove end space */ + *end= 0; + if (!strcmp(ptr, "includedir")) + _mariadb_read_options(mysql, (const char *)val, NULL, group, recursion + 1); + else if (!strcmp(ptr, "include")) + _mariadb_read_options(mysql, NULL, (const char *)val, group, recursion + 1); + continue; + } + if (*ptr == '#' || *ptr == ';' || !*ptr) + continue; + is_escaped= (*ptr == '\\'); + if (*ptr == '[') /* Group name */ + { + found_group=1; + if (!(end=(char *) strchr(++ptr,']'))) + { + /* todo: set error */ + goto err; + } + for ( ; isspace(end[-1]) ; end--) ; /* Remove end space */ + end[0]=0; + read_values= is_group(ptr, groups); + continue; + } + if (!found_group) + { + /* todo: set error */ + goto err; + } + if (!read_values) + continue; + if (!(end=value=strchr(ptr,'='))) + { + end=strchr(ptr, '\0'); /* Option without argument */ + set_option(mysql, ptr, NULL); + } + if (!key) + key= ptr; + for ( ; isspace(end[-1]) ; end--) ; + *end= 0; + if (value) + { + /* Remove pre- and end space */ + char *value_end; + *value= 0; + value++; + ptr= value; + for ( ; isspace(*value); value++) ; + value_end=strchr(value, '\0'); + *value_end= 0; + optval= ptr; + for ( ; isspace(value_end[-1]) ; value_end--) ; + /* remove possible quotes */ + if (*value == '\'' || *value == '\"') + { + value++; + if (value_end[-1] == '\'' || value_end[-1] == '\"') + value_end--; + } + if (value_end < value) /* Empty string */ + value_end=value; + for ( ; value != value_end; value++) + { + if (*value == '\\' && value != value_end-1) + { + switch(*++value) { + case 'n': + *ptr++='\n'; + break; + case 't': + *ptr++= '\t'; + break; + case 'r': + *ptr++ = '\r'; + break; + case 'b': + *ptr++ = '\b'; + break; + case 's': + *ptr++= ' '; /* space */ + break; + case '\"': + *ptr++= '\"'; + break; + case '\'': + *ptr++= '\''; + break; + case '\\': + *ptr++= '\\'; + break; + default: /* Unknown; Keep '\' */ + *ptr++= '\\'; + *ptr++= *value; + break; + } + } + else + *ptr++= *value; + } + *ptr=0; + set_option(mysql, key, optval); + key= optval= 0; + } + } + rc= 0; + +err: + if (file) + ma_close(file); + return rc; +} + + +my_bool _mariadb_read_options(MYSQL *mysql, + const char *config_dir, + const char *config_file, + const char *group, + unsigned int recursion) +{ + int i= 0, + exts, + errors= 0; + char filename[FN_REFLEN + 1]; + unsigned int recursion_stop= 64; +#ifndef _WIN32 + char *env; +#endif + + if (recursion >= recursion_stop) + return 1; + + if (config_file && config_file[0]) + return _mariadb_read_options_from_file(mysql, config_file, group, recursion); + + if (config_dir && config_dir[0]) + { + for (exts= 0; ini_exts[exts]; exts++) + { + snprintf(filename, FN_REFLEN, + "%s%cmy.%s", config_dir, FN_LIBCHAR, ini_exts[exts]); + if (!access(filename, R_OK)) + errors+= _mariadb_read_options_from_file(mysql, filename, group, recursion); + } + return errors; + } + + for (i=0; i < MAX_CONFIG_DIRS && configuration_dirs[i]; i++) + { + for (exts= 0; ini_exts[exts]; exts++) + { + snprintf(filename, FN_REFLEN, + "%s%cmy.%s", configuration_dirs[i], FN_LIBCHAR, ini_exts[exts]); + if (!access(filename, R_OK)) + errors+= _mariadb_read_options_from_file(mysql, filename, group, recursion); + } + } +#ifndef _WIN32 + /* special case: .my.cnf in Home directory */ + if ((env= getenv("HOME"))) + { + for (exts= 0; ini_exts[exts]; exts++) + { + snprintf(filename, FN_REFLEN, + "%s%c.my.%s", env, FN_LIBCHAR, ini_exts[exts]); + if (!access(filename, R_OK)) + errors+= _mariadb_read_options_from_file(mysql, filename, group, recursion); + } + } +#endif + return errors; +} diff --git a/libmariadb/libmariadb/ma_dtoa.c b/libmariadb/libmariadb/ma_dtoa.c new file mode 100644 index 00000000..05ab0f0b --- /dev/null +++ b/libmariadb/libmariadb/ma_dtoa.c @@ -0,0 +1,1924 @@ +/* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. + 2016,2022 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 */ + +/**************************************************************** + + This file incorporates work covered by the following copyright and + permission notice: + + The author of this software is David M. Gay. + + Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + + Permission to use, copy, modify, and distribute this software for any + purpose without fee is hereby granted, provided that this entire notice + is included in all copies of any software which is or includes a copy + or modification of this software and in all copies of the supporting + documentation for such software. + + THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + + ***************************************************************/ + +//#include "strings_def.h" +//#include <my_base.h> /* for EOVERFLOW on Windows */ +#include <ma_global.h> +#include <memory.h> +#include "ma_string.h" + +/** + Appears to suffice to not call malloc() in most cases. + @todo + see if it is possible to get rid of malloc(). + this constant is sufficient to avoid malloc() on all inputs I have tried. +*/ +#define DTOA_BUFF_SIZE (460 * sizeof(void *)) + +/* Magic value returned by dtoa() to indicate overflow */ +#define DTOA_OVERFLOW 9999 + +static char *dtoa(double, int, int, int *, int *, char **, char *, size_t); +static void dtoa_free(char *, char *, size_t); + +/** + @brief + Converts a given floating point number to a zero-terminated string + representation using the 'f' format. + + @details + This function is a wrapper around dtoa() to do the same as + sprintf(to, "%-.*f", precision, x), though the conversion is usually more + precise. The only difference is in handling [-,+]infinity and nan values, + in which case we print '0\0' to the output string and indicate an overflow. + + @param x the input floating point number. + @param precision the number of digits after the decimal point. + All properties of sprintf() apply: + - if the number of significant digits after the decimal + point is less than precision, the resulting string is + right-padded with zeros + - if the precision is 0, no decimal point appears + - if a decimal point appears, at least one digit appears + before it + @param to pointer to the output buffer. The longest string which + my_fcvt() can return is FLOATING_POINT_BUFFER bytes + (including the terminating '\0'). + @param error if not NULL, points to a location where the status of + conversion is stored upon return. + FALSE successful conversion + TRUE the input number is [-,+]infinity or nan. + The output string in this case is always '0'. + @return number of written characters (excluding terminating '\0') +*/ + +size_t ma_fcvt(double x, int precision, char *to, my_bool *error) +{ + int decpt, sign, len, i; + char *res, *src, *end, *dst= to; + char buf[DTOA_BUFF_SIZE]; + DBUG_ASSERT(precision >= 0 && precision < NOT_FIXED_DEC && to != NULL); + + res= dtoa(x, 5, precision, &decpt, &sign, &end, buf, sizeof(buf)); + + if (decpt == DTOA_OVERFLOW) + { + dtoa_free(res, buf, sizeof(buf)); + *to++= '0'; + *to= '\0'; + if (error != NULL) + *error= TRUE; + return 1; + } + + src= res; + len= (int)(end - src); + + if (sign) + *dst++= '-'; + + if (decpt <= 0) + { + *dst++= '0'; + *dst++= '.'; + for (i= decpt; i < 0; i++) + *dst++= '0'; + } + + for (i= 1; i <= len; i++) + { + *dst++= *src++; + if (i == decpt && i < len) + *dst++= '.'; + } + while (i++ <= decpt) + *dst++= '0'; + + if (precision > 0) + { + if (len <= decpt) + *dst++= '.'; + + for (i= precision - MAX(0, (len - decpt)); i > 0; i--) + *dst++= '0'; + } + + *dst= '\0'; + if (error != NULL) + *error= FALSE; + + dtoa_free(res, buf, sizeof(buf)); + + return dst - to; +} + +/** + @brief + Converts a given floating point number to a zero-terminated string + representation with a given field width using the 'e' format + (aka scientific notation) or the 'f' one. + + @details + The format is chosen automatically to provide the most number of significant + digits (and thus, precision) with a given field width. In many cases, the + result is similar to that of sprintf(to, "%g", x) with a few notable + differences: + - the conversion is usually more precise than C library functions. + - there is no 'precision' argument. instead, we specify the number of + characters available for conversion (i.e. a field width). + - the result never exceeds the specified field width. If the field is too + short to contain even a rounded decimal representation, ma_gcvt() + indicates overflow and truncates the output string to the specified width. + - float-type arguments are handled differently than double ones. For a + float input number (i.e. when the 'type' argument is MY_GCVT_ARG_FLOAT) + we deliberately limit the precision of conversion by FLT_DIG digits to + avoid garbage past the significant digits. + - unlike sprintf(), in cases where the 'e' format is preferred, we don't + zero-pad the exponent to save space for significant digits. The '+' sign + for a positive exponent does not appear for the same reason. + + @param x the input floating point number. + @param type is either MY_GCVT_ARG_FLOAT or MY_GCVT_ARG_DOUBLE. + Specifies the type of the input number (see notes above). + @param width field width in characters. The minimal field width to + hold any number representation (albeit rounded) is 7 + characters ("-Ne-NNN"). + @param to pointer to the output buffer. The result is always + zero-terminated, and the longest returned string is thus + 'width + 1' bytes. + @param error if not NULL, points to a location where the status of + conversion is stored upon return. + FALSE successful conversion + TRUE the input number is [-,+]infinity or nan. + The output string in this case is always '0'. + @return number of written characters (excluding terminating '\0') + + @todo + Check if it is possible and makes sense to do our own rounding on top of + dtoa() instead of calling dtoa() twice in (rare) cases when the resulting + string representation does not fit in the specified field width and we want + to re-round the input number with fewer significant digits. Examples: + + ma_gcvt(-9e-3, ..., 4, ...); + ma_gcvt(-9e-3, ..., 2, ...); + ma_gcvt(1.87e-3, ..., 4, ...); + ma_gcvt(55, ..., 1, ...); + + We do our best to minimize such cases by: + + - passing to dtoa() the field width as the number of significant digits + + - removing the sign of the number early (and decreasing the width before + passing it to dtoa()) + + - choosing the proper format to preserve the most number of significant + digits. +*/ + +size_t ma_gcvt(double x, my_gcvt_arg_type type, int width, char *to, + my_bool *error) +{ + int decpt, sign, len, exp_len; + char *res, *src, *end, *dst= to, *dend= dst + width; + char buf[DTOA_BUFF_SIZE]; + my_bool have_space, force_e_format; + DBUG_ASSERT(width > 0 && to != NULL); + + /* We want to remove '-' from equations early */ + if (x < 0.) + width--; + + res= dtoa(x, 4, type == MY_GCVT_ARG_DOUBLE ? width : MIN(width, FLT_DIG), + &decpt, &sign, &end, buf, sizeof(buf)); + if (decpt == DTOA_OVERFLOW) + { + dtoa_free(res, buf, sizeof(buf)); + *to++= '0'; + *to= '\0'; + if (error != NULL) + *error= TRUE; + return 1; + } + + if (error != NULL) + *error= FALSE; + + src= res; + len= (int)(end - res); + + /* + Number of digits in the exponent from the 'e' conversion. + The sign of the exponent is taken into account separately, we don't need + to count it here. + */ + exp_len= 1 + (decpt >= 101 || decpt <= -99) + (decpt >= 11 || decpt <= -9); + + /* + Do we have enough space for all digits in the 'f' format? + Let 'len' be the number of significant digits returned by dtoa, + and F be the length of the resulting decimal representation. + Consider the following cases: + 1. decpt <= 0, i.e. we have "0.NNN" => F = len - decpt + 2 + 2. 0 < decpt < len, i.e. we have "NNN.NNN" => F = len + 1 + 3. len <= decpt, i.e. we have "NNN00" => F = decpt + */ + have_space= (decpt <= 0 ? len - decpt + 2 : + decpt > 0 && decpt < len ? len + 1 : + decpt) <= width; + /* + The following is true when no significant digits can be placed with the + specified field width using the 'f' format, and the 'e' format + will not be truncated. + */ + force_e_format= (decpt <= 0 && width <= 2 - decpt && width >= 3 + exp_len); + /* + Assume that we don't have enough space to place all significant digits in + the 'f' format. We have to choose between the 'e' format and the 'f' one + to keep as many significant digits as possible. + Let E and F be the lengths of decimal representation in the 'e' and 'f' + formats, respectively. We want to use the 'f' format if, and only if F <= E. + Consider the following cases: + 1. decpt <= 0. + F = len - decpt + 2 (see above) + E = len + (len > 1) + 1 + 1 (decpt <= -99) + (decpt <= -9) + 1 + ("N.NNe-MMM") + (F <= E) <=> (len == 1 && decpt >= -1) || (len > 1 && decpt >= -2) + We also need to ensure that if the 'f' format is chosen, + the field width allows us to place at least one significant digit + (i.e. width > 2 - decpt). If not, we prefer the 'e' format. + 2. 0 < decpt < len + F = len + 1 (see above) + E = len + 1 + 1 + ... ("N.NNeMMM") + F is always less than E. + 3. len <= decpt <= width + In this case we have enough space to represent the number in the 'f' + format, so we prefer it with some exceptions. + 4. width < decpt + The number cannot be represented in the 'f' format at all, always use + the 'e' 'one. + */ + if ((have_space || + /* + Not enough space, let's see if the 'f' format provides the most number + of significant digits. + */ + ((decpt <= width && (decpt >= -1 || (decpt == -2 && + (len > 1 || !force_e_format)))) && + !force_e_format)) && + + /* + Use the 'e' format in some cases even if we have enough space for the + 'f' one. See comment for DBL_DIG. + */ + (!have_space || (decpt >= -DBL_DIG + 1 && + (decpt <= DBL_DIG || len > decpt)))) + { + /* 'f' format */ + int i; + + width-= (decpt < len) + (decpt <= 0 ? 1 - decpt : 0); + + /* Do we have to truncate any digits? */ + if (width < len) + { + if (width < decpt) + { + if (error != NULL) + *error= TRUE; + width= decpt; + } + + /* + We want to truncate (len - width) least significant digits after the + decimal point. For this we are calling dtoa with mode=5, passing the + number of significant digits = (len-decpt) - (len-width) = width-decpt + */ + dtoa_free(res, buf, sizeof(buf)); + res= dtoa(x, 5, width - decpt, &decpt, &sign, &end, buf, sizeof(buf)); + src= res; + len= (int)(end - res); + } + + if (len == 0) + { + /* Underflow. Just print '0' and exit */ + *dst++= '0'; + goto end; + } + + /* + At this point we are sure we have enough space to put all digits + returned by dtoa + */ + if (sign && dst < dend) + *dst++= '-'; + if (decpt <= 0) + { + if (dst < dend) + *dst++= '0'; + if (len > 0 && dst < dend) + *dst++= '.'; + for (; decpt < 0 && dst < dend; decpt++) + *dst++= '0'; + } + + for (i= 1; i <= len && dst < dend; i++) + { + *dst++= *src++; + if (i == decpt && i < len && dst < dend) + *dst++= '.'; + } + while (i++ <= decpt && dst < dend) + *dst++= '0'; + } + else + { + /* 'e' format */ + int decpt_sign= 0; + + if (--decpt < 0) + { + decpt= -decpt; + width--; + decpt_sign= 1; + } + width-= 1 + exp_len; /* eNNN */ + + if (len > 1) + width--; + + if (width <= 0) + { + /* Overflow */ + if (error != NULL) + *error= TRUE; + width= 0; + } + + /* Do we have to truncate any digits? */ + if (width < len) + { + /* Yes, re-convert with a smaller width */ + dtoa_free(res, buf, sizeof(buf)); + res= dtoa(x, 4, width, &decpt, &sign, &end, buf, sizeof(buf)); + src= res; + len= (int)(end - res); + if (--decpt < 0) + decpt= -decpt; + } + /* + At this point we are sure we have enough space to put all digits + returned by dtoa + */ + if (sign && dst < dend) + *dst++= '-'; + if (dst < dend) + *dst++= *src++; + if (len > 1 && dst < dend) + { + *dst++= '.'; + while (src < end && dst < dend) + *dst++= *src++; + } + if (dst < dend) + *dst++= 'e'; + if (decpt_sign && dst < dend) + *dst++= '-'; + + if (decpt >= 100 && dst < dend) + { + *dst++= decpt / 100 + '0'; + decpt%= 100; + if (dst < dend) + *dst++= decpt / 10 + '0'; + } + else if (decpt >= 10 && dst < dend) + *dst++= decpt / 10 + '0'; + if (dst < dend) + *dst++= decpt % 10 + '0'; + + } + +end: + dtoa_free(res, buf, sizeof(buf)); + *dst= '\0'; + + return dst - to; +} + +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* + Original copy of the software is located at http://www.netlib.org/fp/dtoa.c + It was adjusted to serve MySQL server needs: + * strtod() was modified to not expect a zero-terminated string. + It now honors 'se' (end of string) argument as the input parameter, + not just as the output one. + * in dtoa(), in case of overflow/underflow/NaN result string now contains "0"; + decpt is set to DTOA_OVERFLOW to indicate overflow. + * support for VAX, IBM mainframe and 16-bit hardware removed + * we always assume that 64-bit integer type is available + * support for Kernigan-Ritchie style headers (pre-ANSI compilers) + removed + * all gcc warnings ironed out + * we always assume multithreaded environment, so we had to change + memory allocation procedures to use stack in most cases; + malloc is used as the last resort. + * pow5mult rewritten to use pre-calculated pow5 list instead of + the one generated on the fly. +*/ + + +/* + On a machine with IEEE extended-precision registers, it is + necessary to specify double-precision (53-bit) rounding precision + before invoking strtod or dtoa. If the machine uses (the equivalent + of) Intel 80x87 arithmetic, the call + _control87(PC_53, MCW_PC); + does this with many compilers. Whether this or another call is + appropriate depends on the compiler; for this to work, it may be + necessary to #include "float.h" or another system-dependent header + file. +*/ + +/* + #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + and dtoa should round accordingly. + #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + and Honor_FLT_ROUNDS is not #defined. + + TODO: check if we can get rid of the above two +*/ + +typedef int32 Long; +typedef uint32 ULong; +typedef int64 LLong; +typedef uint64 ULLong; + +typedef union { double d; ULong L[2]; } U; + +#if defined(HAVE_BIGENDIAN) || \ + (defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)) +#define word0(x) ((x)->L[0]) +#define word1(x) ((x)->L[1]) +#else +#define word0(x) ((x)->L[1]) +#define word1(x) ((x)->L[0]) +#endif + +#define dval(x) ((x)->d) + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax= floor(P*log(2)/log(5)) */ +/* Bletch= (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max= floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max= floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Bias 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#define Rounding rounding +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#define rounded_product(a,b) ((a)*= (b)) +#define rounded_quotient(a,b) ((a)/= (b)) + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff +#define FFFFFFFF 0xffffffffUL + +/* This is tested to be enough for dtoa */ + +#define Kmax 15 + +#define Bcopy(x,y) memcpy((char *)&(x)->sign, (char *)&(y)->sign, \ + 2*sizeof(int) + (y)->wds*sizeof(ULong)) + +/* Arbitrary-length integer */ + +typedef struct Bigint +{ + union { + ULong *x; /* points right after this Bigint object */ + struct Bigint *next; /* to maintain free lists */ + } p; + int k; /* 2^k = maxwds */ + int maxwds; /* maximum length in 32-bit words */ + int sign; /* not zero if number is negative */ + int wds; /* current length in 32-bit words */ +} Bigint; + + +/* A simple stack-memory based allocator for Bigints */ + +typedef struct Stack_alloc +{ + char *begin; + char *free; + char *end; + /* + Having list of free blocks lets us reduce maximum required amount + of memory from ~4000 bytes to < 1680 (tested on x86). + */ + Bigint *freelist[Kmax+1]; +} Stack_alloc; + + +/* + Try to allocate object on stack, and resort to malloc if all + stack memory is used. Ensure allocated objects to be aligned by the pointer + size in order to not break the alignment rules when storing a pointer to a + Bigint. +*/ + +static Bigint *Balloc(int k, Stack_alloc *alloc) +{ + Bigint *rv; + DBUG_ASSERT(k <= Kmax); + if (k <= Kmax && alloc->freelist[k]) + { + rv= alloc->freelist[k]; + alloc->freelist[k]= rv->p.next; + } + else + { + int x, len; + + x= 1 << k; + len= MY_ALIGN(sizeof(Bigint) + x * sizeof(ULong), SIZEOF_CHARP); + + if (alloc->free + len <= alloc->end) + { + rv= (Bigint*) alloc->free; + alloc->free+= len; + } + else + rv= (Bigint*) malloc(len); + + rv->k= k; + rv->maxwds= x; + } + rv->sign= rv->wds= 0; + rv->p.x= (ULong*) (rv + 1); + return rv; +} + + +/* + If object was allocated on stack, try putting it to the free + list. Otherwise call free(). +*/ + +static void Bfree(Bigint *v, Stack_alloc *alloc) +{ + char *gptr= (char*) v; /* generic pointer */ + if (gptr < alloc->begin || gptr >= alloc->end) + free(gptr); + else if (v->k <= Kmax) + { + /* + Maintain free lists only for stack objects: this way we don't + have to bother with freeing lists in the end of dtoa; + heap should not be used normally anyway. + */ + v->p.next= alloc->freelist[v->k]; + alloc->freelist[v->k]= v; + } +} + + +/* + This is to place return value of dtoa in: tries to use stack + as well, but passes by free lists management and just aligns len by + the pointer size in order to not break the alignment rules when storing a + pointer to a Bigint. +*/ + +static char *dtoa_alloc(int i, Stack_alloc *alloc) +{ + char *rv; + int aligned_size= MY_ALIGN(i, SIZEOF_CHARP); + if (alloc->free + aligned_size <= alloc->end) + { + rv= alloc->free; + alloc->free+= aligned_size; + } + else + rv= malloc(i); + return rv; +} + + +/* + dtoa_free() must be used to free values s returned by dtoa() + This is the counterpart of dtoa_alloc() +*/ + +static void dtoa_free(char *gptr, char *buf, size_t buf_size) +{ + if (gptr < buf || gptr >= buf + buf_size) + free(gptr); +} + + +/* Bigint arithmetic functions */ + +/* Multiply by m and add a */ + +static Bigint *multadd(Bigint *b, int m, int a, Stack_alloc *alloc) +{ + int i, wds; + ULong *x; + ULLong carry, y; + Bigint *b1; + + wds= b->wds; + x= b->p.x; + i= 0; + carry= a; + do + { + y= *x * (ULLong)m + carry; + carry= y >> 32; + *x++= (ULong)(y & FFFFFFFF); + } + while (++i < wds); + if (carry) + { + if (wds >= b->maxwds) + { + b1= Balloc(b->k+1, alloc); + Bcopy(b1, b); + Bfree(b, alloc); + b= b1; + } + b->p.x[wds++]= (ULong) carry; + b->wds= wds; + } + return b; +} + + +static int hi0bits(register ULong x) +{ + register int k= 0; + + if (!(x & 0xffff0000)) + { + k= 16; + x<<= 16; + } + if (!(x & 0xff000000)) + { + k+= 8; + x<<= 8; + } + if (!(x & 0xf0000000)) + { + k+= 4; + x<<= 4; + } + if (!(x & 0xc0000000)) + { + k+= 2; + x<<= 2; + } + if (!(x & 0x80000000)) + { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; +} + + +static int lo0bits(ULong *y) +{ + register int k; + register ULong x= *y; + + if (x & 7) + { + if (x & 1) + return 0; + if (x & 2) + { + *y= x >> 1; + return 1; + } + *y= x >> 2; + return 2; + } + k= 0; + if (!(x & 0xffff)) + { + k= 16; + x>>= 16; + } + if (!(x & 0xff)) + { + k+= 8; + x>>= 8; + } + if (!(x & 0xf)) + { + k+= 4; + x>>= 4; + } + if (!(x & 0x3)) + { + k+= 2; + x>>= 2; + } + if (!(x & 1)) + { + k++; + x>>= 1; + if (!x) + return 32; + } + *y= x; + return k; +} + + +/* Convert integer to Bigint number */ + +static Bigint *i2b(int i, Stack_alloc *alloc) +{ + Bigint *b; + + b= Balloc(1, alloc); + b->p.x[0]= i; + b->wds= 1; + return b; +} + + +/* Multiply two Bigint numbers */ + +static Bigint *mult(Bigint *a, Bigint *b, Stack_alloc *alloc) +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; + ULLong carry, z; + + if (a->wds < b->wds) + { + c= a; + a= b; + b= c; + } + k= a->k; + wa= a->wds; + wb= b->wds; + wc= wa + wb; + if (wc > a->maxwds) + k++; + c= Balloc(k, alloc); + for (x= c->p.x, xa= x + wc; x < xa; x++) + *x= 0; + xa= a->p.x; + xae= xa + wa; + xb= b->p.x; + xbe= xb + wb; + xc0= c->p.x; + for (; xb < xbe; xc0++) + { + if ((y= *xb++)) + { + x= xa; + xc= xc0; + carry= 0; + do + { + z= *x++ * (ULLong)y + *xc + carry; + carry= z >> 32; + *xc++= (ULong) (z & FFFFFFFF); + } + while (x < xae); + *xc= (ULong) carry; + } + } + for (xc0= c->p.x, xc= xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds= wc; + return c; +} + + +/* + Precalculated array of powers of 5: tested to be enough for + vasting majority of dtoa_r cases. +*/ + +static ULong powers5[]= +{ + 625UL, + + 390625UL, + + 2264035265UL, 35UL, + + 2242703233UL, 762134875UL, 1262UL, + + 3211403009UL, 1849224548UL, 3668416493UL, 3913284084UL, 1593091UL, + + 781532673UL, 64985353UL, 253049085UL, 594863151UL, 3553621484UL, + 3288652808UL, 3167596762UL, 2788392729UL, 3911132675UL, 590UL, + + 2553183233UL, 3201533787UL, 3638140786UL, 303378311UL, 1809731782UL, + 3477761648UL, 3583367183UL, 649228654UL, 2915460784UL, 487929380UL, + 1011012442UL, 1677677582UL, 3428152256UL, 1710878487UL, 1438394610UL, + 2161952759UL, 4100910556UL, 1608314830UL, 349175UL +}; + + +static Bigint p5_a[]= +{ + /* { x } - k - maxwds - sign - wds */ + { { powers5 }, 1, 1, 0, 1 }, + { { powers5 + 1 }, 1, 1, 0, 1 }, + { { powers5 + 2 }, 1, 2, 0, 2 }, + { { powers5 + 4 }, 2, 3, 0, 3 }, + { { powers5 + 7 }, 3, 5, 0, 5 }, + { { powers5 + 12 }, 4, 10, 0, 10 }, + { { powers5 + 22 }, 5, 19, 0, 19 } +}; + +#define P5A_MAX (sizeof(p5_a)/sizeof(*p5_a) - 1) + +static Bigint *pow5mult(Bigint *b, int k, Stack_alloc *alloc) +{ + Bigint *b1, *p5, *p51=NULL; + int i; + static int p05[3]= { 5, 25, 125 }; + my_bool overflow= FALSE; + + if ((i= k & 3)) + b= multadd(b, p05[i-1], 0, alloc); + + if (!(k>>= 2)) + return b; + p5= p5_a; + for (;;) + { + if (k & 1) + { + b1= mult(b, p5, alloc); + Bfree(b, alloc); + b= b1; + } + if (!(k>>= 1)) + break; + /* Calculate next power of 5 */ + if (overflow) + { + p51= mult(p5, p5, alloc); + Bfree(p5, alloc); + p5= p51; + } + else if (p5 < p5_a + P5A_MAX) + ++p5; + else if (p5 == p5_a + P5A_MAX) + { + p5= mult(p5, p5, alloc); + overflow= TRUE; + } + } + if (p51) + Bfree(p51, alloc); + return b; +} + + +static Bigint *lshift(Bigint *b, int k, Stack_alloc *alloc) +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + + n= k >> 5; + k1= b->k; + n1= n + b->wds + 1; + for (i= b->maxwds; n1 > i; i<<= 1) + k1++; + b1= Balloc(k1, alloc); + x1= b1->p.x; + for (i= 0; i < n; i++) + *x1++= 0; + x= b->p.x; + xe= x + b->wds; + if (k&= 0x1f) + { + k1= 32 - k; + z= 0; + do + { + *x1++= *x << k | z; + z= *x++ >> k1; + } + while (x < xe); + if ((*x1= z)) + ++n1; + } + else + do + *x1++= *x++; + while (x < xe); + b1->wds= n1 - 1; + Bfree(b, alloc); + return b1; +} + + +static int cmp(Bigint *a, Bigint *b) +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i= a->wds; + j= b->wds; + if (i-= j) + return i; + xa0= a->p.x; + xa= xa0 + j; + xb0= b->p.x; + xb= xb0 + j; + for (;;) + { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; +} + + +static Bigint *diff(Bigint *a, Bigint *b, Stack_alloc *alloc) +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; + ULLong borrow, y; + + i= cmp(a,b); + if (!i) + { + c= Balloc(0, alloc); + c->wds= 1; + c->p.x[0]= 0; + return c; + } + if (i < 0) + { + c= a; + a= b; + b= c; + i= 1; + } + else + i= 0; + c= Balloc(a->k, alloc); + c->sign= i; + wa= a->wds; + xa= a->p.x; + xae= xa + wa; + wb= b->wds; + xb= b->p.x; + xbe= xb + wb; + xc= c->p.x; + borrow= 0; + do + { + y= (ULLong)*xa++ - *xb++ - borrow; + borrow= y >> 32 & (ULong)1; + *xc++= (ULong) (y & FFFFFFFF); + } + while (xb < xbe); + while (xa < xae) + { + y= *xa++ - borrow; + borrow= y >> 32 & (ULong)1; + *xc++= (ULong) (y & FFFFFFFF); + } + while (!*--xc) + wa--; + c->wds= wa; + return c; +} + + +static Bigint *d2b(U *d, int *e, int *bits, Stack_alloc *alloc) +{ + Bigint *b; + int de, k; + ULong *x, y, z; + int i; +#define d0 word0(d) +#define d1 word1(d) + + b= Balloc(1, alloc); + x= b->p.x; + + z= d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ + if ((de= (int)(d0 >> Exp_shift))) + z|= Exp_msk1; + if ((y= d1)) + { + if ((k= lo0bits(&y))) + { + x[0]= y | z << (32 - k); + z>>= (k == 32) ? (--k) : k; + } + else + x[0]= y; + i= b->wds= (x[1]= z) ? 2 : 1; + } + else + { + k= lo0bits(&z); + x[0]= z; + i= b->wds= 1; + k+= 32; + } + if (de) + { + *e= de - Bias - (P-1) + k; + *bits= P - k; + } + else + { + *e= de - Bias - (P-1) + 1 + k; + *bits= 32*i - hi0bits(x[i-1]); + } + return b; +#undef d0 +#undef d1 +} + + +static const double tens[] = +{ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +}; + +static const double bigtens[]= { 1e16, 1e32, 1e64, 1e128, 1e256 }; +/* + The factor of 2^53 in tinytens[4] helps us avoid setting the underflow + flag unnecessarily. It leads to a song and dance at the end of strtod. +*/ +#define Scale_Bit 0x10 +#define n_bigtens 5 + + +static int quorem(Bigint *b, Bigint *S) +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; + ULLong borrow, carry, y, ys; + + n= S->wds; + if (b->wds < n) + return 0; + sx= S->p.x; + sxe= sx + --n; + bx= b->p.x; + bxe= bx + n; + q= *bxe / (*sxe + 1); /* ensure q <= true quotient */ + if (q) + { + borrow= 0; + carry= 0; + do + { + ys= *sx++ * (ULLong)q + carry; + carry= ys >> 32; + y= *bx - (ys & FFFFFFFF) - borrow; + borrow= y >> 32 & (ULong)1; + *bx++= (ULong) (y & FFFFFFFF); + } + while (sx <= sxe); + if (!*bxe) + { + bx= b->p.x; + while (--bxe > bx && !*bxe) + --n; + b->wds= n; + } + } + if (cmp(b, S) >= 0) + { + q++; + borrow= 0; + carry= 0; + bx= b->p.x; + sx= S->p.x; + do + { + ys= *sx++ + carry; + carry= ys >> 32; + y= *bx - (ys & FFFFFFFF) - borrow; + borrow= y >> 32 & (ULong)1; + *bx++= (ULong) (y & FFFFFFFF); + } + while (sx <= sxe); + bx= b->p.x; + bxe= bx + n; + if (!*bxe) + { + while (--bxe > bx && !*bxe) + --n; + b->wds= n; + } + } + return q; +} + + +/* + dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + + Inspired by "How to Print Floating-Point Numbers Accurately" by + Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + + Modifications: + 1. Rather than iterating, we use a simple numeric overestimate + to determine k= floor(log10(d)). We scale relevant + quantities using O(log2(k)) rather than O(k) multiplications. + 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + try to generate digits strictly left to right. Instead, we + compute with fewer bits and propagate the carry if necessary + when rounding the final digit up. This is often faster. + 3. Under the assumption that input will be rounded nearest, + mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + That is, we allow equality in stopping tests when the + round-nearest rule will give the same floating-point value + as would satisfaction of the stopping test with strict + inequality. + 4. We remove common factors of powers of 2 from relevant + quantities. + 5. When converting floating-point integers less than 1e16, + we use floating-point arithmetic rather than resorting + to multiple-precision integers. + 6. When asked to produce fewer than 15 digits, we first try + to get by with floating-point arithmetic; we resort to + multiple-precision integer arithmetic only if we cannot + guarantee that the floating-point calculation has given + the correctly rounded result. For k requested digits and + "uniformly" distributed input, the probability is + something like 10^(k-15) that we must resort to the Long + calculation. + */ + +static char *dtoa(double dd, int mode, int ndigits, int *decpt, int *sign, + char **rve, char *buf, size_t buf_size) +{ + /* + Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to DTOA_OVERFLOW. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> MAX(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, UNINIT_VAR(ilim), ilim0, + UNINIT_VAR(ilim1), j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick; + Long L; + int denorm; + ULong x; + Bigint *b, *b1, *delta, *mlo, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0; +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif + Stack_alloc alloc; + + alloc.begin= alloc.free= buf; + alloc.end= buf + buf_size; + memset(alloc.freelist, 0, sizeof(alloc.freelist)); + + u.d= dd; + if (word0(&u) & Sign_bit) + { + /* set sign for everything, including 0's and NaNs */ + *sign= 1; + word0(&u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign= 0; + + /* If infinity, set decpt to DTOA_OVERFLOW, if 0 set it to 1 */ + /* coverity[assign_where_compare_meant] */ + if (((word0(&u) & Exp_mask) == Exp_mask && (*decpt= DTOA_OVERFLOW)) || + /* coverity[assign_where_compare_meant] */ + (!dval(&u) && (*decpt= 1))) + { + /* Infinity, NaN, 0 */ + char *res= (char*) dtoa_alloc(2, &alloc); + res[0]= '0'; + res[1]= '\0'; + if (rve) + *rve= res + 1; + return res; + } + +#ifdef Honor_FLT_ROUNDS + if ((rounding= Flt_Rounds) >= 2) + { + if (*sign) + rounding= rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding= 0; + } +#endif + + b= d2b(&u, &be, &bbits, &alloc); + if ((i= (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)))) + { + dval(&d2)= dval(&u); + word0(&d2) &= Frac_mask1; + word0(&d2) |= Exp_11; + + /* + log(x) ~=~ log(1.5) + (x-1.5)/1.5 + log10(x) = log(x) / log(10) + ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + log10(d)= (i-Bias)*log(2)/log(10) + log10(d2) + + This suggests computing an approximation k to log10(d) by + + k= (i - Bias)*0.301029995663981 + + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + + We want k to be too large rather than too small. + The error in the first-order Taylor series approximation + is in our favor, so we just round up the constant enough + to compensate for any error in the multiplication of + (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + adding 1e-13 to the constant term more than suffices. + Hence we adjust the constant term to 0.1760912590558. + (We could get a more accurate k by invoking log10, + but this is probably not worthwhile.) + */ + + i-= Bias; + denorm= 0; + } + else + { + /* d is denormalized */ + + i= bbits + be + (Bias + (P-1) - 1); + x= i > 32 ? word0(&u) << (64 - i) | word1(&u) >> (i - 32) + : word1(&u) << (32 - i); + dval(&d2)= x; + word0(&d2)-= 31*Exp_msk1; /* adjust exponent */ + i-= (Bias + (P-1) - 1) + 1; + denorm= 1; + } + ds= (dval(&d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k= (int)ds; + if (ds < 0. && ds != k) + k--; /* want k= floor(ds) */ + k_check= 1; + if (k >= 0 && k <= Ten_pmax) + { + if (dval(&u) < tens[k]) + k--; + k_check= 0; + } + j= bbits - i - 1; + if (j >= 0) + { + b2= 0; + s2= j; + } + else + { + b2= -j; + s2= 0; + } + if (k >= 0) + { + b5= 0; + s5= k; + s2+= k; + } + else + { + b2-= k; + b5= -k; + s5= 0; + } + if (mode < 0 || mode > 9) + mode= 0; + +#ifdef Check_FLT_ROUNDS + try_quick= Rounding == 1; +#else + try_quick= 1; +#endif + + if (mode > 5) + { + mode-= 4; + try_quick= 0; + } + leftright= 1; + switch (mode) { + case 0: + case 1: + ilim= ilim1= -1; + i= 18; + ndigits= 0; + break; + case 2: + leftright= 0; + /* fall through */ + case 4: + if (ndigits <= 0) + ndigits= 1; + ilim= ilim1= i= ndigits; + break; + case 3: + leftright= 0; + /* fall through */ + case 5: + i= ndigits + k + 1; + ilim= i; + ilim1= i - 1; + if (i <= 0) + i= 1; + } + s= s0= dtoa_alloc(i, &alloc); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && rounding != 1) + leftright= 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) + { + /* Try to get by with floating-point arithmetic. */ + i= 0; + dval(&d2)= dval(&u); + k0= k; + ilim0= ilim; + ieps= 2; /* conservative */ + if (k > 0) + { + ds= tens[k&0xf]; + j= k >> 4; + if (j & Bletch) + { + /* prevent overflows */ + j&= Bletch - 1; + dval(&u)/= bigtens[n_bigtens-1]; + ieps++; + } + for (; j; j>>= 1, i++) + { + if (j & 1) + { + ieps++; + ds*= bigtens[i]; + } + } + dval(&u)/= ds; + } + else if ((j1= -k)) + { + dval(&u)*= tens[j1 & 0xf]; + for (j= j1 >> 4; j; j>>= 1, i++) + { + if (j & 1) + { + ieps++; + dval(&u)*= bigtens[i]; + } + } + } + if (k_check && dval(&u) < 1. && ilim > 0) + { + if (ilim1 <= 0) + goto fast_failed; + ilim= ilim1; + k--; + dval(&u)*= 10.; + ieps++; + } + dval(&eps)= ieps*dval(&u) + 7.; + word0(&eps)-= (P-1)*Exp_msk1; + if (ilim == 0) + { + S= mhi= 0; + dval(&u)-= 5.; + if (dval(&u) > dval(&eps)) + goto one_digit; + if (dval(&u) < -dval(&eps)) + goto no_digits; + goto fast_failed; + } + if (leftright) + { + /* Use Steele & White method of only generating digits needed. */ + dval(&eps)= 0.5/tens[ilim-1] - dval(&eps); + for (i= 0;;) + { + L= (Long) dval(&u); + dval(&u)-= L; + *s++= '0' + (int)L; + if (dval(&u) < dval(&eps)) + goto ret1; + if (1. - dval(&u) < dval(&eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(&eps)*= 10.; + dval(&u)*= 10.; + } + } + else + { + /* Generate ilim digits, then fix them up. */ + dval(&eps)*= tens[ilim-1]; + for (i= 1;; i++, dval(&u)*= 10.) + { + L= (Long)(dval(&u)); + if (!(dval(&u)-= L)) + ilim= i; + *s++= '0' + (int)L; + if (i == ilim) + { + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) + { + while (*--s == '0'); + s++; + goto ret1; + } + break; + } + } + } + fast_failed: + s= s0; + dval(&u)= dval(&d2); + k= k0; + ilim= ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) + { + /* Yes. */ + ds= tens[k]; + if (ndigits < 0 && ilim <= 0) + { + S= mhi= 0; + if (ilim < 0 || dval(&u) <= 5*ds) + goto no_digits; + goto one_digit; + } + for (i= 1;; i++, dval(&u)*= 10.) + { + L= (Long)(dval(&u) / ds); + dval(&u)-= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(&u) < 0) + { + L--; + dval(&u)+= ds; + } +#endif + *s++= '0' + (int)L; + if (!dval(&u)) + { + break; + } + if (i == ilim) + { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + { + switch (rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } + } +#endif + dval(&u)+= dval(&u); + if (dval(&u) > ds || (dval(&u) == ds && L & 1)) + { +bump_up: + while (*--s == '9') + if (s == s0) + { + k++; + *s= '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2= b2; + m5= b5; + mhi= mlo= 0; + if (leftright) + { + i = denorm ? be + (Bias + (P-1) - 1 + 1) : 1 + P - bbits; + b2+= i; + s2+= i; + mhi= i2b(1, &alloc); + } + if (m2 > 0 && s2 > 0) + { + i= m2 < s2 ? m2 : s2; + b2-= i; + m2-= i; + s2-= i; + } + if (b5 > 0) + { + if (leftright) + { + if (m5 > 0) + { + mhi= pow5mult(mhi, m5, &alloc); + b1= mult(mhi, b, &alloc); + Bfree(b, &alloc); + b= b1; + } + if ((j= b5 - m5)) + b= pow5mult(b, j, &alloc); + } + else + b= pow5mult(b, b5, &alloc); + } + S= i2b(1, &alloc); + if (s5 > 0) + S= pow5mult(S, s5, &alloc); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case= 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && rounding == 1 +#endif + ) + { + if (!word1(&u) && !(word0(&u) & Bndry_mask) && + word0(&u) & (Exp_mask & ~Exp_msk1) + ) + { + /* The special case */ + b2+= Log2P; + s2+= Log2P; + spec_case= 1; + } + } + + /* + Arrange for convenient computation of quotients: + shift left if necessary so divisor has 4 leading 0 bits. + + Perhaps we should just compute leading 28 bits of S once + a nd for all and pass them and a shift to quorem, so it + can do shifts and ors to compute the numerator for q. + */ + if ((i= ((s5 ? 32 - hi0bits(S->p.x[S->wds-1]) : 1) + s2) & 0x1f)) + i= 32 - i; + if (i > 4) + { + i-= 4; + b2+= i; + m2+= i; + s2+= i; + } + else if (i < 4) + { + i+= 28; + b2+= i; + m2+= i; + s2+= i; + } + if (b2 > 0) + b= lshift(b, b2, &alloc); + if (s2 > 0) + S= lshift(S, s2, &alloc); + if (k_check) + { + if (cmp(b,S) < 0) + { + k--; + /* we botched the k estimate */ + b= multadd(b, 10, 0, &alloc); + if (leftright) + mhi= multadd(mhi, 10, 0, &alloc); + ilim= ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) + { + if (ilim < 0 || cmp(b,S= multadd(S,5,0, &alloc)) <= 0) + { + /* no digits, fcvt style */ +no_digits: + k= -1 - ndigits; + goto ret; + } +one_digit: + *s++= '1'; + k++; + goto ret; + } + if (leftright) + { + if (m2 > 0) + mhi= lshift(mhi, m2, &alloc); + + /* + Compute mlo -- check for special case that d is a normalized power of 2. + */ + + mlo= mhi; + if (spec_case) + { + mhi= Balloc(mhi->k, &alloc); + Bcopy(mhi, mlo); + mhi= lshift(mhi, Log2P, &alloc); + } + + for (i= 1;;i++) + { + dig= quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string that will round to d? */ + j= cmp(b, mlo); + delta= diff(S, mhi, &alloc); + j1= delta->sign ? 1 : cmp(b, delta); + Bfree(delta, &alloc); + if (j1 == 0 && mode != 1 && !(word1(&u) & 1) +#ifdef Honor_FLT_ROUNDS + && rounding >= 1 +#endif + ) + { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; + *s++= dig; + goto ret; + } + if (j < 0 || (j == 0 && mode != 1 && !(word1(&u) & 1))) + { + if (!b->p.x[0] && b->wds <= 1) + { + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) + { + b= lshift(b, 1, &alloc); + j1= cmp(b, S); + if ((j1 > 0 || (j1 == 0 && dig & 1)) + && dig++ == '9') + goto round_9_up; + } +accept_dig: + *s++= dig; + goto ret; + } + if (j1 > 0) + { +#ifdef Honor_FLT_ROUNDS + if (!rounding) + goto accept_dig; +#endif + if (dig == '9') + { /* possible if i == 1 */ +round_9_up: + *s++= '9'; + goto roundoff; + } + *s++= dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS +keep_dig: +#endif + *s++= dig; + if (i == ilim) + break; + b= multadd(b, 10, 0, &alloc); + if (mlo == mhi) + mlo= mhi= multadd(mhi, 10, 0, &alloc); + else + { + mlo= multadd(mlo, 10, 0, &alloc); + mhi= multadd(mhi, 10, 0, &alloc); + } + } + } + else + for (i= 1;; i++) + { + *s++= dig= quorem(b,S) + '0'; + if (!b->p.x[0] && b->wds <= 1) + { + goto ret; + } + if (i >= ilim) + break; + b= multadd(b, 10, 0, &alloc); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b= lshift(b, 1, &alloc); + j= cmp(b, S); + if (j > 0 || (j == 0 && dig & 1)) + { +roundoff: + while (*--s == '9') + if (s == s0) + { + k++; + *s++= '1'; + goto ret; + } + ++*s++; + } + else + { +#ifdef Honor_FLT_ROUNDS +trimzeros: +#endif + while (*--s == '0'); + s++; + } +ret: + if (S != NULL) + Bfree(S, &alloc); + if (mhi) + { + if (mlo && mlo != mhi) + Bfree(mlo, &alloc); + Bfree(mhi, &alloc); + } +ret1: + Bfree(b, &alloc); + *s= 0; + *decpt= k + 1; + if (rve) + *rve= s; + return s0; +} diff --git a/libmariadb/libmariadb/ma_errmsg.c b/libmariadb/libmariadb/ma_errmsg.c new file mode 100644 index 00000000..888a41e3 --- /dev/null +++ b/libmariadb/libmariadb/ma_errmsg.c @@ -0,0 +1,131 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* Error messages for clients */ +/* error messages for the demon is in share/language/errmsg.sys */ + +#include <ma_global.h> +#include <ma_sys.h> +#include "errmsg.h" +#include <stdarg.h> + +const char *SQLSTATE_UNKNOWN= "HY000"; + +const char *client_errors[]= +{ +/* 2000 */ "Unknown error", +/* 2001 */ "Can't create UNIX socket (%d)", +/* 2002 */ "Can't connect to local server through socket '%-.64s' (%d)", +/* 2003 */ "Can't connect to server on '%-.64s' (%d)", +/* 2004 */ "Can't create TCP/IP socket (%d)", +/* 2005 */ "Unknown server host '%-.100s' (%d)", +/* 2006 */ "Server has gone away", +/* 2007 */ "Protocol mismatch. Server Version = %d Client Version = %d", +/* 2008 */ "Client run out of memory", +/* 2009 */ "Wrong host info", +/* 2010 */ "Localhost via UNIX socket", +/* 2011 */ "%-.64s via TCP/IP", +/* 2012 */ "Error in server handshake", +/* 2013 */ "Lost connection to server during query", +/* 2014 */ "Commands out of sync; you can't run this command now", +/* 2015 */ "%-.64s via named pipe", +/* 2016 */ "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)", +/* 2017 */ "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)", +/* 2018 */ "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)", +/* 2019 */ "Can't initialize character set %-.64s (path: %-.64s)", +/* 2020 */ "Got packet bigger than 'max_allowed_packet'", +/* 2021 */ "", +/* 2022 */ "", +/* 2023 */ "", +/* 2024 */ "", +/* 2025 */ "", +/* 2026 */ "TLS/SSL error: %-.100s", +/* 2027 */ "Received malformed packet", +/* 2028 */ "", +/* 2029 */ "", +/* 2030 */ "Statement is not prepared", +/* 2031 */ "No data supplied for parameters in prepared statement", +/* 2032 */ "Data truncated", +/* 2033 */ "", +/* 2034 */ "Invalid parameter number", +/* 2035 */ "Invalid buffer type: %d (parameter: %d)", +/* 2036 */ "Buffer type is not supported", +/* 2037 */ "Shared memory: %-.64s", +/* 2038 */ "Shared memory connection failed during %s. (%lu)", +/* 2039 */ "", +/* 2040 */ "", +/* 2041 */ "", +/* 2042 */ "", +/* 2043 */ "", +/* 2044 */ "", +/* 2045 */ "", +/* 2046 */ "", +/* 2047 */ "Wrong or unknown protocol", +/* 2048 */ "", +/* 2049 */ "Connection with old authentication protocol refused.", +/* 2050 */ "", +/* 2051 */ "", +/* 2052 */ "Prepared statement contains no metadata", +/* 2053 */ "", +/* 2054 */ "This feature is not implemented or disabled", +/* 2055 */ "Lost connection to server at '%s', system error: %d", +/* 2056 */ "Server closed statement due to a prior %s function call", +/* 2057 */ "The number of parameters in bound buffers differs from number of columns in resultset", +/* 2058 */ "Can't connect twice. Already connected", +/* 2059 */ "Plugin %s could not be loaded: %s", +/* 2060 */ "An attribute with same name already exists", +/* 2061 */ "Plugin doesn't support this function", + "" +}; + +const char *mariadb_client_errors[] = +{ + /* 5000 */ "Creating an event failed (Errorcode: %d)", + /* 5001 */ "Bind to local interface '-.%64s' failed (Errorcode: %d)", + /* 5002 */ "Connection type doesn't support asynchronous IO operations", + /* 5003 */ "Server doesn't support function '%s'", + /* 5004 */ "File '%s' not found (Errcode: %d)", + /* 5005 */ "Error reading file '%s' (Errcode: %d)", + /* 5006 */ "Bulk operation without parameters is not supported", + /* 5007 */ "Invalid statement handle", + /* 5008 */ "Unsupported version %d. Supported versions are in the range %d - %d", + /* 5009 */ "Invalid or missing parameter '%s'.", + /* 5010 */ "Authentication plugin '%s' couldn't be found in restricted_auth plugin list.", + /* 5011 */ "Parse error in connection string (offset %d)", + /* 5012 */ "Error while loading plugin '%s'", + /* 5013 */ "Read error: %s (%d)", + /* 5014 */ "Write error: %s (%d)", + /* 5015 */ "Error while uncompressing packet", + /* 5016 */ "Error while retrieving parameter from callback function", + /* 5017 */ "Error while uncompressing packet (File: %.*s start_pos=%ld).", + /* 5018 */ "Checksum verification failed (File: %.*s start_pos=%ld). Reported checksum is %x, while calculated checksum is %x.", + /* 5019 */ "Binary log protocol error (File: %.*s start_pos=%ld): Log format %d not supported.", + /* 5020 */ "Binary log error (File: %.*s start_pos=%ld): Unknown event type (%d) with flag 'not_ignorable'.", + /* 5021 */ "Binary log error (File: %.*s start_pos=%ld): %s.", + /* 5022 */ "File '%s' is not a binary log file", + /* 5023 */ "Semi sync request error: %s", + "" +}; + +const char ** NEAR my_errmsg[MAXMAPS]={0,0,0,0}; +char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE]; + +void init_client_errs(void) +{ + my_errmsg[CLIENT_ERRMAP] = &client_errors[0]; +} + diff --git a/libmariadb/libmariadb/ma_hashtbl.c b/libmariadb/libmariadb/ma_hashtbl.c new file mode 100644 index 00000000..4805dee0 --- /dev/null +++ b/libmariadb/libmariadb/ma_hashtbl.c @@ -0,0 +1,580 @@ +/************************************************************************************ + Copyright (C) 2000, 2012 MySQL AB & MySQL Finland AB & TCX DataKonsult AB, + Monty Program AB, 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ + +/* The hash functions used for saving keys */ +/* One of key_length or key_length_offset must be given */ +/* Key length of 0 isn't allowed */ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_string.h> +#include <mariadb_ctype.h> +#include "ma_hashtbl.h" + +#define NO_RECORD ((uint) -1) +#define LOWFIND 1 +#define LOWUSED 2 +#define HIGHFIND 4 +#define HIGHUSED 8 + +static uint hash_mask(uint hashnr,uint buffmax,uint maxlength); +static void movelink(MA_HASHTBL_LINK *array,uint pos,uint next_link,uint newlink); +static uint calc_hashnr(const uchar *key,uint length); +static uint calc_hashnr_caseup(const uchar *key,uint length); +static int hashcmp(MA_HASHTBL *hash,MA_HASHTBL_LINK *pos,const uchar *key,uint length); + + +my_bool _ma_hashtbl_init(MA_HASHTBL *hash,uint size,uint key_offset,uint key_length, + hash_get_key get_key, + void (*free_element)(void*),uint flags CALLER_INFO_PROTO) +{ + hash->records=0; + if (ma_init_dynamic_array_ci(&hash->array,sizeof(MA_HASHTBL_LINK),size,0)) + { + hash->free=0; /* Allow call to hash_free */ + return(TRUE); + } + hash->key_offset=key_offset; + hash->key_length=key_length; + hash->blength=1; + hash->current_record= NO_RECORD; /* For the future */ + hash->get_key=get_key; + hash->free=free_element; + hash->flags=flags; + if (flags & MA_HASHTBL_CASE_INSENSITIVE) + hash->calc_hashnr=calc_hashnr_caseup; + else + hash->calc_hashnr=calc_hashnr; + return(0); +} + + +void ma_hashtbl_free(MA_HASHTBL *hash) +{ + if (hash->free) + { + uint i,records; + MA_HASHTBL_LINK *data=dynamic_element(&hash->array,0,MA_HASHTBL_LINK*); + for (i=0,records=hash->records ; i < records ; i++) + (*hash->free)(data[i].data); + hash->free=0; + } + ma_delete_dynamic(&hash->array); + hash->records=0; + return; +} + + /* some helper functions */ + +/* + This function is char* instead of uchar* as HPUX11 compiler can't + handle inline functions that are not defined as native types +*/ + +static inline char* +hash_key(MA_HASHTBL *hash,const uchar *record,uint *length,my_bool first) +{ + if (hash->get_key) + return (char *)(*hash->get_key)(record,(uint *)length,first); + *length=hash->key_length; + return (char*) record+hash->key_offset; +} + + /* Calculate pos according to keys */ + +static uint hash_mask(uint hashnr,uint buffmax,uint maxlength) +{ + if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1)); + return (hashnr & ((buffmax >> 1) -1)); +} + +static uint hash_rec_mask(MA_HASHTBL *hash,MA_HASHTBL_LINK *pos,uint buffmax, + uint maxlength) +{ + uint length; + uchar *key= (uchar*) hash_key(hash,pos->data,&length,0); + return hash_mask((*hash->calc_hashnr)(key,length),buffmax,maxlength); +} + +#ifndef NEW_MA_HASHTBL_FUNCTION + + /* Calc hashvalue for a key */ + +static uint calc_hashnr(const uchar *key,uint length) +{ + register uint nr=1, nr2=4; + while (length--) + { + nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8); + nr2+=3; + } + return((uint) nr); +} + + /* Calc hashvalue for a key, case independently */ + +static uint calc_hashnr_caseup(const uchar *key,uint length) +{ + register uint nr=1, nr2=4; + while (length--) + { + nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8); + nr2+=3; + } + return((uint) nr); +} + +#else + +/* + * Fowler/Noll/Vo hash + * + * The basis of the hash algorithm was taken from an idea sent by email to the + * IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and + * Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com) + * later improved on their algorithm. + * + * The magic is in the interesting relationship between the special prime + * 16777619 (2^24 + 403) and 2^32 and 2^8. + * + * This hash produces the fewest collisions of any function that we've seen so + * far, and works well on both numbers and strings. + */ + +uint calc_hashnr(const uchar *key, uint len) +{ + const uchar *end=key+len; + uint hash; + for (hash = 0; key < end; key++) + { + hash *= 16777619; + hash ^= (uint) *(uchar*) key; + } + return (hash); +} + +uint calc_hashnr_caseup(const uchar *key, uint len) +{ + const uchar *end=key+len; + uint hash; + for (hash = 0; key < end; key++) + { + hash *= 16777619; + hash ^= (uint) (uchar) toupper(*key); + } + return (hash); +} + +#endif + + +#ifndef __SUNPRO_C /* SUNPRO can't handle this */ +static inline +#endif +unsigned int rec_hashnr(MA_HASHTBL *hash,const uchar *record) +{ + uint length; + uchar *key= (uchar*) hash_key(hash,record,&length,0); + return (*hash->calc_hashnr)(key,length); +} + + + /* Search after a record based on a key */ + /* Sets info->current_ptr to found record */ + +void* ma_hashtbl_search(MA_HASHTBL *hash,const uchar *key,uint length) +{ + MA_HASHTBL_LINK *pos; + uint flag,idx; + + flag=1; + if (hash->records) + { + idx=hash_mask((*hash->calc_hashnr)(key,length ? length : + hash->key_length), + hash->blength,hash->records); + do + { + pos= dynamic_element(&hash->array,idx,MA_HASHTBL_LINK*); + if (!hashcmp(hash,pos,key,length)) + { + hash->current_record= idx; + return (pos->data); + } + if (flag) + { + flag=0; /* Reset flag */ + if (hash_rec_mask(hash,pos,hash->blength,hash->records) != idx) + break; /* Wrong link */ + } + } + while ((idx=pos->next) != NO_RECORD); + } + hash->current_record= NO_RECORD; + return(0); +} + + /* Get next record with identical key */ + /* Can only be called if previous calls was hash_search */ + +void *ma_hashtbl_next(MA_HASHTBL *hash,const uchar *key,uint length) +{ + MA_HASHTBL_LINK *pos; + uint idx; + + if (hash->current_record != NO_RECORD) + { + MA_HASHTBL_LINK *data=dynamic_element(&hash->array,0,MA_HASHTBL_LINK*); + for (idx=data[hash->current_record].next; idx != NO_RECORD ; idx=pos->next) + { + pos=data+idx; + if (!hashcmp(hash,pos,key,length)) + { + hash->current_record= idx; + return pos->data; + } + } + hash->current_record=NO_RECORD; + } + return 0; +} + + + /* Change link from pos to new_link */ + +static void movelink(MA_HASHTBL_LINK *array,uint find,uint next_link,uint newlink) +{ + MA_HASHTBL_LINK *old_link; + do + { + old_link=array+next_link; + } + while ((next_link=old_link->next) != find); + old_link->next= newlink; + return; +} + + /* Compare a key in a record to a whole key. Return 0 if identical */ + +static int hashcmp(MA_HASHTBL *hash,MA_HASHTBL_LINK *pos,const uchar *key,uint length) +{ + uint rec_keylength; + uchar *rec_key= (uchar*) hash_key(hash,pos->data,&rec_keylength,1); + return (length && length != rec_keylength) || + memcmp(rec_key,key,rec_keylength); +} + + + /* Write a hash-key to the hash-index */ + +my_bool ma_hashtbl_insert(MA_HASHTBL *info,const uchar *record) +{ + int flag; + uint halfbuff,hash_nr,first_index,idx; + uchar *ptr_to_rec= NULL,*ptr_to_rec2= NULL; + MA_HASHTBL_LINK *data,*empty,*gpos= NULL,*gpos2 = NULL,*pos; + + LINT_INIT(gpos); LINT_INIT(gpos2); + LINT_INIT(ptr_to_rec); LINT_INIT(ptr_to_rec2); + + flag=0; + if (!(empty=(MA_HASHTBL_LINK*) ma_alloc_dynamic(&info->array))) + return(TRUE); /* No more memory */ + + info->current_record= NO_RECORD; + data=dynamic_element(&info->array,0,MA_HASHTBL_LINK*); + halfbuff= info->blength >> 1; + + idx=first_index=info->records-halfbuff; + if (idx != info->records) /* If some records */ + { + do + { + pos=data+idx; + hash_nr=rec_hashnr(info,pos->data); + if (flag == 0) /* First loop; Check if ok */ + if (hash_mask(hash_nr,info->blength,info->records) != first_index) + break; + if (!(hash_nr & halfbuff)) + { /* Key will not move */ + if (!(flag & LOWFIND)) + { + if (flag & HIGHFIND) + { + flag=LOWFIND | HIGHFIND; + /* key shall be moved to the current empty position */ + gpos=empty; + ptr_to_rec=pos->data; + empty=pos; /* This place is now free */ + } + else + { + flag=LOWFIND | LOWUSED; /* key isn't changed */ + gpos=pos; + ptr_to_rec=pos->data; + } + } + else + { + if (!(flag & LOWUSED)) + { + /* Change link of previous LOW-key */ + gpos->data=ptr_to_rec; + gpos->next=(uint) (pos-data); + flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED); + } + gpos=pos; + ptr_to_rec=pos->data; + } + } + else + { /* key will be moved */ + if (!(flag & HIGHFIND)) + { + flag= (flag & LOWFIND) | HIGHFIND; + /* key shall be moved to the last (empty) position */ + gpos2 = empty; empty=pos; + ptr_to_rec2=pos->data; + } + else + { + if (!(flag & HIGHUSED)) + { + /* Change link of previous hash-key and save */ + gpos2->data=ptr_to_rec2; + gpos2->next=(uint) (pos-data); + flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED); + } + gpos2=pos; + ptr_to_rec2=pos->data; + } + } + } + while ((idx=pos->next) != NO_RECORD); + + if ((flag & (LOWFIND | LOWUSED)) == LOWFIND) + { + gpos->data=ptr_to_rec; + gpos->next=NO_RECORD; + } + if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND) + { + gpos2->data=ptr_to_rec2; + gpos2->next=NO_RECORD; + } + } + /* Check if we are at the empty position */ + + idx=hash_mask(rec_hashnr(info,record),info->blength,info->records+1); + pos=data+idx; + if (pos == empty) + { + pos->data=(uchar*) record; + pos->next=NO_RECORD; + } + else + { + /* Check if more records in same hash-nr family */ + empty[0]=pos[0]; + gpos=data+hash_rec_mask(info,pos,info->blength,info->records+1); + if (pos == gpos) + { + pos->data=(uchar*) record; + pos->next=(uint) (empty - data); + } + else + { + pos->data=(uchar*) record; + pos->next=NO_RECORD; + movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data)); + } + } + if (++info->records == info->blength) + info->blength+= info->blength; + return(0); +} + + +/****************************************************************************** +** Remove one record from hash-table. The record with the same record +** ptr is removed. +** if there is a free-function it's called for record if found +******************************************************************************/ + +my_bool ma_hashtbl_delete(MA_HASHTBL *hash,uchar *record) +{ + uint blength,pos2,pos_hashnr,lastpos_hashnr,idx,empty_index; + MA_HASHTBL_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty; + if (!hash->records) + return(1); + + blength=hash->blength; + data=dynamic_element(&hash->array,0,MA_HASHTBL_LINK*); + /* Search after record with key */ + pos=data+ hash_mask(rec_hashnr(hash,record),blength,hash->records); + gpos = 0; + + while (pos->data != record) + { + gpos=pos; + if (pos->next == NO_RECORD) + return(1); /* Key not found */ + pos=data+pos->next; + } + + if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1; + hash->current_record= NO_RECORD; + lastpos=data+hash->records; + + /* Remove link to record */ + empty=pos; empty_index=(uint) (empty-data); + if (gpos) + gpos->next=pos->next; /* unlink current ptr */ + else if (pos->next != NO_RECORD) + { + empty=data+(empty_index=pos->next); + pos->data=empty->data; + pos->next=empty->next; + } + + if (empty == lastpos) /* last key at wrong pos or no next link */ + goto exit; + + /* Move the last key (lastpos) */ + lastpos_hashnr=rec_hashnr(hash,lastpos->data); + /* pos is where lastpos should be */ + pos=data+hash_mask(lastpos_hashnr,hash->blength,hash->records); + if (pos == empty) /* Move to empty position. */ + { + empty[0]=lastpos[0]; + goto exit; + } + pos_hashnr=rec_hashnr(hash,pos->data); + /* pos3 is where the pos should be */ + pos3= data+hash_mask(pos_hashnr,hash->blength,hash->records); + if (pos != pos3) + { /* pos is on wrong posit */ + empty[0]=pos[0]; /* Save it here */ + pos[0]=lastpos[0]; /* This should be here */ + movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index); + goto exit; + } + pos2= hash_mask(lastpos_hashnr,blength,hash->records+1); + if (pos2 == hash_mask(pos_hashnr,blength,hash->records+1)) + { /* Identical key-positions */ + if (pos2 != hash->records) + { + empty[0]=lastpos[0]; + movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index); + goto exit; + } + idx= (uint) (pos-data); /* Link pos->next after lastpos */ + } + else idx= NO_RECORD; /* Different positions merge */ + + empty[0]=lastpos[0]; + movelink(data,idx,empty_index,pos->next); + pos->next=empty_index; + +exit: + ma_pop_dynamic(&hash->array); + if (hash->free) + (*hash->free)((uchar*) record); + return(0); +} + + /* + Update keys when record has changed. + This is much more efficient than using a delete & insert. + */ + +my_bool ma_hashtbl_update(MA_HASHTBL *hash,uchar *record,uchar *old_key,uint old_key_length) +{ + uint idx,new_index,new_pos_index,blength,records,empty; + MA_HASHTBL_LINK org_link,*data,*previous,*pos; + + data=dynamic_element(&hash->array,0,MA_HASHTBL_LINK*); + blength=hash->blength; records=hash->records; + + /* Search after record with key */ + + idx=hash_mask((*hash->calc_hashnr)(old_key,(old_key_length ? + old_key_length : + hash->key_length)), + blength,records); + new_index=hash_mask(rec_hashnr(hash,record),blength,records); + if (idx == new_index) + return(0); /* Nothing to do (No record check) */ + previous=0; + for (;;) + { + + if ((pos= data+idx)->data == record) + break; + previous=pos; + if ((idx=pos->next) == NO_RECORD) + return(1); /* Not found in links */ + } + hash->current_record= NO_RECORD; + org_link= *pos; + empty=idx; + + /* Relink record from current chain */ + + if (!previous) + { + if (pos->next != NO_RECORD) + { + empty=pos->next; + *pos= data[pos->next]; + } + } + else + previous->next=pos->next; /* unlink pos */ + + /* Move data to correct position */ + pos=data+new_index; + new_pos_index=hash_rec_mask(hash,pos,blength,records); + if (new_index != new_pos_index) + { /* Other record in wrong position */ + data[empty] = *pos; + movelink(data,new_index,new_pos_index,empty); + org_link.next=NO_RECORD; + data[new_index]= org_link; + } + else + { /* Link in chain at right position */ + org_link.next=data[new_index].next; + data[empty]=org_link; + data[new_index].next=empty; + } + return(0); +} + + +uchar *ma_hashtbl_element(MA_HASHTBL *hash,uint idx) +{ + if (idx < hash->records) + return dynamic_element(&hash->array,idx,MA_HASHTBL_LINK*)->data; + return 0; +} diff --git a/libmariadb/libmariadb/ma_init.c b/libmariadb/libmariadb/ma_init.c new file mode 100644 index 00000000..008aec54 --- /dev/null +++ b/libmariadb/libmariadb/ma_init.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +#include <ma_global.h> +#include <ma_sys.h> +#include "mariadb_ctype.h" +#include <ma_string.h> +#include <mariadb_ctype.h> +#include <signal.h> +#ifdef _WIN32 +#ifdef _MSC_VER +#include <locale.h> +#include <crtdbg.h> +#endif +static my_bool my_win_init(void); +#else +#define my_win_init() +#endif + +my_bool ma_init_done=0; + + + +/* Init ma_sys functions and ma_sys variabels */ + +void ma_init(void) +{ + if (ma_init_done) + return; + ma_init_done=1; + { +#ifdef _WIN32 + my_win_init(); +#endif + return; + } +} /* ma_init */ + + + +void ma_end(int infoflag __attribute__((unused))) +{ +#ifdef _WIN32 + WSACleanup( ); +#endif /* _WIN32 */ + ma_init_done=0; +} /* ma_end */ + +#ifdef _WIN32 +static my_bool my_win_init() +{ + WORD VersionRequested; + int err; + WSADATA WsaData; + const unsigned int MajorVersion=2, + MinorVersion=2; + VersionRequested= MAKEWORD(MajorVersion, MinorVersion); + /* Load WinSock library */ + if ((err= WSAStartup(VersionRequested, &WsaData))) + { + return 0; + } + /* make sure 2.2 or higher is supported */ + if ((LOBYTE(WsaData.wVersion) * 10 + HIBYTE(WsaData.wVersion)) < 22) + { + WSACleanup(); + return 1; + } + return 0; +} +#endif + diff --git a/libmariadb/libmariadb/ma_io.c b/libmariadb/libmariadb/ma_io.c new file mode 100644 index 00000000..178ffe9f --- /dev/null +++ b/libmariadb/libmariadb/ma_io.c @@ -0,0 +1,224 @@ +/* + Copyright (C) 2015 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA +*/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <errmsg.h> +#include <mysql.h> +#include <mysql/client_plugin.h> +#include <mariadb/ma_io.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_REMOTEIO +struct st_mysql_client_plugin_REMOTEIO *rio_plugin= NULL; +#endif + +/* {{{ ma_open */ +MA_FILE *ma_open(const char *location, const char *mode, MYSQL *mysql) +{ + int CodePage= -1; + FILE *fp= NULL; + MA_FILE *ma_file= NULL; + + if (!location || !location[0]) + return NULL; +#ifdef HAVE_REMOTEIO + if (strstr(location, "://")) + goto remote; +#endif + +#ifdef _WIN32 + if (mysql && mysql->charset) + CodePage= madb_get_windows_cp(mysql->charset->csname); +#endif + if (CodePage == -1) + { + if (!(fp= fopen(location, mode))) + { + return NULL; + } + } +#ifdef _WIN32 + /* See CONC-44: we need to support non ascii filenames too, so we convert + current character set to wchar_t and try to open the file via _wsopen */ + else + { + wchar_t *w_filename= NULL; + wchar_t *w_mode= NULL; + int len; + DWORD Length; + + len= MultiByteToWideChar(CodePage, 0, location, (int)strlen(location), NULL, 0); + if (!len) + return NULL; + if (!(w_filename= (wchar_t *)calloc(1, (len + 1) * sizeof(wchar_t)))) + { + my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + Length= len; + len= MultiByteToWideChar(CodePage, 0, location, (int)strlen(location), w_filename, (int)Length); + if (!len) + { + /* todo: error handling */ + free(w_filename); + return NULL; + } + len= (int)strlen(mode); + if (!(w_mode= (wchar_t *)calloc(1, (len + 1) * sizeof(wchar_t)))) + { + my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + free(w_filename); + return NULL; + } + Length= len; + len= MultiByteToWideChar(CodePage, 0, mode, (int)strlen(mode), w_mode, (int)Length); + if (!len) + { + /* todo: error handling */ + free(w_filename); + free(w_mode); + return NULL; + } + fp= _wfopen(w_filename, w_mode); + free(w_filename); + free(w_mode); + } + +#endif + if (fp) + { + ma_file= (MA_FILE *)malloc(sizeof(MA_FILE)); + if (!ma_file) + { + fclose(fp); + my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + ma_file->type= MA_FILE_LOCAL; + ma_file->ptr= (void *)fp; + } + return ma_file; +#ifdef HAVE_REMOTEIO +remote: + /* check if plugin for remote io is available and try + * to open location */ + { + MYSQL mysql; + if (rio_plugin ||(rio_plugin= (struct st_mysql_client_plugin_REMOTEIO *) + mysql_client_find_plugin(&mysql, NULL, MARIADB_CLIENT_REMOTEIO_PLUGIN))) + return rio_plugin->methods->mopen(location, mode); + return NULL; + } +#endif +} +/* }}} */ + +/* {{{ ma_close */ +int ma_close(MA_FILE *file) +{ + int rc; + if (!file) + return -1; + + switch (file->type) { + case MA_FILE_LOCAL: + rc= fclose((FILE *)file->ptr); + free(file); + break; +#ifdef HAVE_REMOTEIO + case MA_FILE_REMOTE: + rc= rio_plugin->methods->mclose(file); + break; +#endif + default: + return -1; + } + return rc; +} +/* }}} */ + + +/* {{{ ma_feof */ +int ma_feof(MA_FILE *file) +{ + if (!file) + return -1; + + switch (file->type) { + case MA_FILE_LOCAL: + return feof((FILE *)file->ptr); + break; +#ifdef HAVE_REMOTEIO + case MA_FILE_REMOTE: + return rio_plugin->methods->mfeof(file); + break; +#endif + default: + return -1; + } +} +/* }}} */ + +/* {{{ ma_read */ +size_t ma_read(void *ptr, size_t size, size_t nmemb, MA_FILE *file) +{ + size_t s= 0; + if (!file) + return -1; + + switch (file->type) { + case MA_FILE_LOCAL: + s= fread(ptr, size, nmemb, (FILE *)file->ptr); + return s; + break; +#ifdef HAVE_REMOTEIO + case MA_FILE_REMOTE: + return rio_plugin->methods->mread(ptr, size, nmemb, file); + break; +#endif + default: + return -1; + } +} +/* }}} */ + +/* {{{ ma_gets */ +char *ma_gets(char *ptr, size_t size, MA_FILE *file) +{ + if (!file) + return NULL; + + switch (file->type) { + case MA_FILE_LOCAL: + return fgets(ptr, (int)size, (FILE *)file->ptr); + break; +#ifdef HAVE_REMOTEIO + case MA_FILE_REMOTE: + return rio_plugin->methods->mgets(ptr, size, file); + break; +#endif + default: + return NULL; + } +} +/* }}} */ + + diff --git a/libmariadb/libmariadb/ma_list.c b/libmariadb/libmariadb/ma_list.c new file mode 100644 index 00000000..63c526f1 --- /dev/null +++ b/libmariadb/libmariadb/ma_list.c @@ -0,0 +1,114 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* + Code for handling dubble-linked lists in C +*/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_list.h> + + /* Add a element to start of list */ + +LIST *list_add(LIST *root, LIST *element) +{ + if (root) + { + if (root->prev) /* If add in mid of list */ + root->prev->next= element; + element->prev=root->prev; + root->prev=element; + } + else + element->prev=0; + element->next=root; + return(element); /* New root */ +} + + +LIST *list_delete(LIST *root, LIST *element) +{ + if (element->prev) + element->prev->next=element->next; + else + root=element->next; + if (element->next) + element->next->prev=element->prev; + return root; +} + + +void list_free(LIST *root, unsigned int free_data) +{ + LIST *next; + while (root) + { + next=root->next; + if (free_data) + free(root->data); + free(root); + root=next; + } +} + + +LIST *list_cons(void *data, LIST *list) +{ + LIST *new_charset=(LIST*) malloc(sizeof(LIST)); + if (!new_charset) + return 0; + new_charset->data=data; + return list_add(list,new_charset); +} + + +LIST *list_reverse(LIST *root) +{ + LIST *last; + + last=root; + while (root) + { + last=root; + root=root->next; + last->next=last->prev; + last->prev=root; + } + return last; +} + +uint list_length(LIST *list) +{ + uint count; + for (count=0 ; list ; list=list->next, count++) ; + return count; +} + + +int list_walk(LIST *list, list_walk_action action, gptr argument) +{ + int error=0; + while (list) + { + if ((error = (*action)(list->data,argument))) + return error; + list= list_rest(list); + } + return 0; +} diff --git a/libmariadb/libmariadb/ma_ll2str.c b/libmariadb/libmariadb/ma_ll2str.c new file mode 100644 index 00000000..6938d37f --- /dev/null +++ b/libmariadb/libmariadb/ma_ll2str.c @@ -0,0 +1,70 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016,2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +#include <ma_global.h> +#include "ma_string.h" +#include <ctype.h> + +static char NEAR _dig_vec[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +char *ma_ll2str(long long val,char *dst,int radix) +{ + char buffer[65]; + register char *p; + long long_val; + + if (radix < 0) + { + if (radix < -36 || radix > -2) return (char*) 0; + if (val < 0) { + *dst++ = '-'; + val = 0ULL - val; + } + radix = -radix; + } + else + { + if (radix > 36 || radix < 2) return (char*) 0; + } + if (val == 0) + { + *dst++='0'; + *dst='\0'; + return dst; + } + p = &buffer[sizeof(buffer)-1]; + *p = '\0'; + + while ((ulonglong) val > (ulonglong) LONG_MAX) + { + ulonglong quo=(ulonglong) val/(uint) radix; + uint rem= (uint) (val- quo* (uint) radix); + *--p = _dig_vec[rem]; + val= quo; + } + long_val= (long) val; + while (long_val != 0) + { + long quo= long_val/radix; + *--p = _dig_vec[(uchar) (long_val - quo*radix)]; + long_val= quo; + } + while ((*dst++ = *p++) != 0) ; + return dst-1; +} diff --git a/libmariadb/libmariadb/ma_loaddata.c b/libmariadb/libmariadb/ma_loaddata.c new file mode 100644 index 00000000..c59285ec --- /dev/null +++ b/libmariadb/libmariadb/ma_loaddata.c @@ -0,0 +1,265 @@ +/************************************************************************************ + Copyright (C) 2000, 2011 MySQL AB & MySQL Finland AB & TCX DataKonsult AB, + Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +#include "ma_global.h" +#include <ma_sys.h> +#include <ma_string.h> +#include "errmsg.h" +#include "mysql.h" +#include <mariadb/ma_io.h> +#include <string.h> +#ifdef _WIN32 +#include <share.h> +#endif +#include <ma_common.h> + +typedef struct st_mysql_infile_info +{ + MA_FILE *fp; + int error_no; + char error_msg[MYSQL_ERRMSG_SIZE + 1]; + const char *filename; +} MYSQL_INFILE_INFO; + +/* {{{ mysql_local_infile_init */ +static +int mysql_local_infile_init(void **ptr, const char *filename, void *userdata) +{ + MYSQL_INFILE_INFO *info; + MYSQL *mysql= (MYSQL *)userdata; + + info = (MYSQL_INFILE_INFO *)malloc(sizeof(MYSQL_INFILE_INFO)); + if (!info) { + return(1); + } + memset(info, 0, sizeof(MYSQL_INFILE_INFO)); + *ptr = info; + + info->filename = filename; + + info->fp= ma_open(filename, "rb", mysql); + + if (!info->fp) + { + /* error handling is done via mysql_local_infile_error function, so we + need to copy error to info */ + if (mysql_errno(mysql) && !info->error_no) + { + info->error_no= mysql_errno(mysql); + ma_strmake(info->error_msg, mysql_error(mysql), MYSQL_ERRMSG_SIZE); + } + else + { + info->error_no = errno; + snprintf((char *)info->error_msg, sizeof(info->error_msg), + CER(CR_FILE_NOT_FOUND), filename, info->error_no); + } + return(1); + } + + return(0); +} +/* }}} */ + + +/* {{{ mysql_local_infile_read */ +static +int mysql_local_infile_read(void *ptr, char * buf, unsigned int buf_len) +{ + MYSQL_INFILE_INFO *info = (MYSQL_INFILE_INFO *)ptr; + size_t count; + + count= ma_read((void *)buf, 1, (size_t)buf_len, info->fp); + + if (count == (size_t)-1) + { + info->error_no = errno; + snprintf((char *)info->error_msg, sizeof(info->error_msg), + CER(CR_FILE_READ), info->filename, info->error_no); + } + return((int)count); +} +/* }}} */ + + +/* {{{ mysql_local_infile_error */ +static +int mysql_local_infile_error(void *ptr, char *error_buf, unsigned int error_buf_len) +{ + MYSQL_INFILE_INFO *info = (MYSQL_INFILE_INFO *)ptr; + + if (info) { + ma_strmake(error_buf, info->error_msg, error_buf_len); + return(info->error_no); + } + + ma_strmake(error_buf, "Unknown error", error_buf_len); + return(CR_UNKNOWN_ERROR); +} +/* }}} */ + + +/* {{{ mysql_local_infile_end */ +static +void mysql_local_infile_end(void *ptr) +{ + MYSQL_INFILE_INFO *info = (MYSQL_INFILE_INFO *)ptr; + + if (info) + { + if (info->fp) + ma_close(info->fp); + free(ptr); + } + return; +} +/* }}} */ + + +/* {{{ mysql_local_infile_default */ +void mysql_set_local_infile_default(MYSQL *conn) +{ + conn->options.local_infile_init = mysql_local_infile_init; + conn->options.local_infile_read = mysql_local_infile_read; + conn->options.local_infile_error = mysql_local_infile_error; + conn->options.local_infile_end = mysql_local_infile_end; + return; +} +/* }}} */ + +/* {{{ mysql_set_local_infile_handler */ +void STDCALL mysql_set_local_infile_handler(MYSQL *conn, + int (*local_infile_init)(void **, const char *, void *), + int (*local_infile_read)(void *, char *, uint), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char *, uint), + void *userdata) +{ + conn->options.local_infile_init= local_infile_init; + conn->options.local_infile_read= local_infile_read; + conn->options.local_infile_end= local_infile_end; + conn->options.local_infile_error= local_infile_error; + conn->options.local_infile_userdata = userdata; + return; +} +/* }}} */ + +/* {{{ mysql_handle_local_infile */ +my_bool mysql_handle_local_infile(MYSQL *conn, const char *filename, my_bool can_local_infile) +{ + unsigned int buflen= 4096; + int bufread; + unsigned char *buf= NULL; + void *info= NULL; + my_bool result= 1; + + /* check if all callback functions exist */ + if (!conn->options.local_infile_init || !conn->options.local_infile_end || + !conn->options.local_infile_read || !conn->options.local_infile_error) + { + conn->options.local_infile_userdata= conn; + mysql_set_local_infile_default(conn); + } + + if (!(conn->options.client_flag & CLIENT_LOCAL_FILES) || + !can_local_infile) + { + my_set_error(conn, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, "Load data local infile forbidden"); + /* write empty packet to server */ + ma_net_write(&conn->net, (unsigned char *)"", 0); + ma_net_flush(&conn->net); + goto infile_error; + } + + /* allocate buffer for reading data */ + buf = (uchar *)malloc(buflen); + + /* init handler: allocate read buffer and open file */ + if (conn->options.local_infile_init(&info, filename, + conn->options.local_infile_userdata)) + { + char tmp_buf[MYSQL_ERRMSG_SIZE]; + int tmp_errno; + + tmp_errno= conn->options.local_infile_error(info, tmp_buf, sizeof(tmp_buf)); + my_set_error(conn, tmp_errno, SQLSTATE_UNKNOWN, tmp_buf); + ma_net_write(&conn->net, (unsigned char *)"", 0); + ma_net_flush(&conn->net); + goto infile_error; + } + + /* read data */ + while ((bufread= conn->options.local_infile_read(info, (char *)buf, buflen)) > 0) + { + if (ma_net_write(&conn->net, (unsigned char *)buf, bufread)) + { + my_set_error(conn, CR_SERVER_LOST, SQLSTATE_UNKNOWN, NULL); + goto infile_error; + } + } + + /* send empty packet for eof */ + if (ma_net_write(&conn->net, (unsigned char *)"", 0) || + ma_net_flush(&conn->net)) + { + my_set_error(conn, CR_SERVER_LOST, SQLSTATE_UNKNOWN, NULL); + goto infile_error; + } + + /* error during read occurred */ + if (bufread < 0) + { + char tmp_buf[MYSQL_ERRMSG_SIZE]; + int tmp_errno= conn->options.local_infile_error(info, tmp_buf, sizeof(tmp_buf)); + my_set_error(conn, tmp_errno, SQLSTATE_UNKNOWN, tmp_buf); + goto infile_error; + } + + result = 0; + +infile_error: + conn->options.local_infile_end(info); + free(buf); + return(result); +} +/* }}} */ + diff --git a/libmariadb/libmariadb/ma_net.c b/libmariadb/libmariadb/ma_net.c new file mode 100644 index 00000000..2e932b17 --- /dev/null +++ b/libmariadb/libmariadb/ma_net.c @@ -0,0 +1,595 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2012-2016 SkySQL AB, MariaDB Corporation AB + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* Write and read of logical packets to/from socket + ** Writes are cached into net_buffer_length big packets. + ** Read packets are reallocated dynamically when reading big packets. + ** Each logical packet has the following pre-info: + ** 3 byte length & 1 byte package-number. + */ + + +#include <ma_global.h> +#include <mysql.h> +#include <ma_pvio.h> +#include <ma_sys.h> +#include <ma_string.h> +#include "mysql.h" +#include "errmsg.h" +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <ma_pvio.h> +#include <ma_common.h> +#ifndef _WIN32 +#include <poll.h> +#endif + +#define MAX_PACKET_LENGTH (256L*256L*256L-1) + +/* net_buffer_length and max_allowed_packet are defined in mysql.h + See bug conc-57 + */ +#undef net_buffer_length + +#undef max_allowed_packet +ulong max_allowed_packet=1024L * 1024L * 1024L; +ulong net_read_timeout= NET_READ_TIMEOUT; +ulong net_write_timeout= NET_WRITE_TIMEOUT; +ulong net_buffer_length= 8192; /* Default length. Enlarged if necessary */ + +#if !defined(_WIN32) +#include <sys/socket.h> +#else +#undef MYSQL_SERVER /* Win32 can't handle interrupts */ +#endif +#if !defined(_WIN32) && !defined(HAVE_BROKEN_NETINET_INCLUDES) && !defined(__BEOS__) +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#if !defined(alpha_linux_port) +#include <netinet/tcp.h> +#endif +#endif + + +/* + ** Give error if a too big packet is found + ** The server can change this with the -O switch, but because the client + ** can't normally do this the client should have a bigger max-buffer. + */ + +static int ma_net_write_buff(NET *net,const char *packet, size_t len); + + +/* Init with packet info */ + +int ma_net_init(NET *net, MARIADB_PVIO* pvio) +{ + if (!(net->buff=(uchar*) malloc(net_buffer_length))) + return 1; + if (!net->extension) + return 1; + + memset(net->buff, 0, net_buffer_length); + + net->max_packet_size= MAX(net_buffer_length, max_allowed_packet); + net->buff_end=net->buff+(net->max_packet=net_buffer_length); + net->pvio = pvio; + net->error=0; net->return_status=0; + net->read_timeout=(uint) net_read_timeout; /* Timeout for read */ + net->compress_pkt_nr= net->pkt_nr= 0; + net->write_pos=net->read_pos = net->buff; + net->last_error[0]= net->sqlstate[0] =0; + + net->compress=0; net->reading_or_writing=0; + net->where_b = net->remain_in_buf=0; + net->last_errno=0; + + if (pvio != 0) /* If real connection */ + { + ma_pvio_get_handle(pvio, &net->fd); + ma_pvio_blocking(pvio, 1, 0); + ma_pvio_fast_send(pvio); + } + return 0; +} + +void ma_net_end(NET *net) +{ + free(net->buff); + net->buff=0; +} + +/* Realloc the packet buffer */ + +static my_bool net_realloc(NET *net, size_t length) +{ + uchar *buff; + size_t pkt_length; + + if (length >= net->max_packet_size) + { + net->error=1; + net->pvio->set_error(net->pvio->mysql, CR_NET_PACKET_TOO_LARGE, SQLSTATE_UNKNOWN, 0); + return(1); + } + pkt_length = (length+IO_SIZE-1) & ~(IO_SIZE-1); + /* reallocate buffer: + size= pkt_length + NET_HEADER_SIZE + COMP_HEADER_SIZE */ + if (!(buff=(uchar*) realloc(net->buff, + pkt_length + NET_HEADER_SIZE + COMP_HEADER_SIZE))) + { + net->error=1; + return(1); + } + net->buff=net->write_pos=buff; + net->buff_end=buff+(net->max_packet=(unsigned long)pkt_length); + return(0); +} + +/* Remove unwanted characters from connection */ +void ma_net_clear(NET *net) +{ + if (net->extension->multi_status > COM_MULTI_OFF) + return; + net->compress_pkt_nr= net->pkt_nr=0; /* Ready for new command */ + net->write_pos=net->buff; + return; +} + +/* Flush write_buffer if not empty. */ +int ma_net_flush(NET *net) +{ + int error=0; + + /* don't flush if pipelined query is in progress */ + if (net->extension->multi_status > COM_MULTI_OFF) + return 0; + + if (net->buff != net->write_pos) + { + error=ma_net_real_write(net,(char*) net->buff, + (size_t) (net->write_pos - net->buff)); + net->write_pos=net->buff; + } + if (net->compress) + net->pkt_nr= net->compress_pkt_nr; + return(error); +} + +/***************************************************************************** + ** Write something to server/client buffer + *****************************************************************************/ + +/* + ** Write a logical packet with packet header + ** Format: Packet length (3 bytes), packet number(1 byte) + ** When compression is used a 3 byte compression length is added + ** NOTE: If compression is used the original package is destroyed! + */ + +int ma_net_write(NET *net, const uchar *packet, size_t len) +{ + uchar buff[NET_HEADER_SIZE]; + while (len >= MAX_PACKET_LENGTH) + { + const ulong max_len= MAX_PACKET_LENGTH; + int3store(buff,max_len); + buff[3]= (uchar)net->pkt_nr++; + if (ma_net_write_buff(net,(char*) buff,NET_HEADER_SIZE) || + ma_net_write_buff(net, (char *)packet, max_len)) + return 1; + packet+= max_len; + len-= max_len; + } + /* write last remaining packet, size can be zero */ + int3store(buff, len); + buff[3]= (uchar)net->pkt_nr++; + if (ma_net_write_buff(net,(char*) buff,NET_HEADER_SIZE) || + ma_net_write_buff(net, (char *)packet, len)) + return 1; + return 0; +} + +int ma_net_write_command(NET *net, uchar command, + const char *packet, size_t len, + my_bool disable_flush) +{ + uchar buff[NET_HEADER_SIZE+1]; + size_t buff_size= NET_HEADER_SIZE + 1; + size_t length= 1 + len; /* 1 extra byte for command */ + int rc; + + buff[NET_HEADER_SIZE]= 0; + buff[4]=command; + + if (length >= MAX_PACKET_LENGTH) + { + len= MAX_PACKET_LENGTH - 1; + do + { + int3store(buff, MAX_PACKET_LENGTH); + buff[3]= (net->compress) ? 0 : (uchar) (net->pkt_nr++); + + if (ma_net_write_buff(net, (char *)buff, buff_size) || + ma_net_write_buff(net, packet, len)) + return(1); + packet+= len; + length-= MAX_PACKET_LENGTH; + len= MAX_PACKET_LENGTH; + buff_size= NET_HEADER_SIZE; /* don't send command for further packets */ + } while (length >= MAX_PACKET_LENGTH); + len= length; + } + int3store(buff,length); + buff[3]= (net->compress) ? 0 :(uchar) (net->pkt_nr++); + rc= test (ma_net_write_buff(net,(char *)buff, buff_size) || + ma_net_write_buff(net,packet,len)); + if (!rc && !disable_flush) + return test(ma_net_flush(net)); + return rc; +} + + +static int ma_net_write_buff(NET *net,const char *packet, size_t len) +{ + size_t left_length; + + if (!len) + return 0; + + if (net->max_packet > MAX_PACKET_LENGTH && + net->compress) + left_length= (size_t)(MAX_PACKET_LENGTH - (net->write_pos - net->buff)); + else + left_length=(size_t) (net->buff_end - net->write_pos); + + if (len > left_length) + { + if (net->write_pos != net->buff) + { + memcpy((char*) net->write_pos,packet,left_length); + if (ma_net_real_write(net,(char*) net->buff, + (size_t)(net->write_pos - net->buff) + left_length)) + return 1; + packet+=left_length; + len-=left_length; + net->write_pos= net->buff; + } + if (net->compress) + { + /* uncompressed length is stored in 3 bytes,so + packet can't be > 0xFFFFFF */ + left_length= MAX_PACKET_LENGTH; + while (len > left_length) + { + if (ma_net_real_write(net, packet, left_length)) + return 1; + packet+= left_length; + len-= left_length; + } + } + if (len > net->max_packet) + return(test(ma_net_real_write(net, packet, len))); + } + memcpy((char*) net->write_pos,packet,len); + net->write_pos+=len; + return 0; +} + +unsigned char *mysql_net_store_length(unsigned char *packet, ulonglong length); + +/* Read and write using timeouts */ + +int ma_net_real_write(NET *net, const char *packet, size_t len) +{ + ssize_t length; + char *pos,*end; + + if (net->error == 2) + return(-1); /* socket can't be used */ + + net->reading_or_writing=2; +#ifdef HAVE_COMPRESS + if (net->compress) + { + size_t complen; + uchar *b; + uint header_length=NET_HEADER_SIZE+COMP_HEADER_SIZE; + if (!(b=(uchar*) malloc(len + NET_HEADER_SIZE + COMP_HEADER_SIZE + 1))) + { + net->pvio->set_error(net->pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + net->error=2; + net->reading_or_writing=0; + return(1); + } + memcpy(b+header_length,packet,len); + + if (_mariadb_compress(net, (unsigned char*) b+header_length,&len,&complen)) + { + complen=0; + } + int3store(&b[NET_HEADER_SIZE],complen); + int3store(b,len); + b[3]=(uchar) (net->compress_pkt_nr++); + len+= header_length; + packet= (char*) b; + } +#endif /* HAVE_COMPRESS */ + + pos=(char*) packet; end=pos+len; + while (pos != end) + { + if ((length=ma_pvio_write(net->pvio,(uchar *)pos,(size_t) (end-pos))) <= 0) + { + int save_errno= errno; + char errmsg[100]; + + net->error=2; /* Close socket */ + strerror_r(save_errno, errmsg, 100); + net->pvio->set_error(net->pvio->mysql, CR_ERR_NET_WRITE, SQLSTATE_UNKNOWN, 0, + errmsg, save_errno); + net->reading_or_writing=0; +#ifdef HAVE_COMPRESS + if (net->compress) + free((char*) packet); +#endif + return(1); + } + pos+=length; + } +#ifdef HAVE_COMPRESS + if (net->compress) + free((char*) packet); +#endif + net->reading_or_writing=0; + return(((int) (pos != end))); +} + +/***************************************************************************** + ** Read something from server/clinet + *****************************************************************************/ +static ulong ma_real_read(NET *net, size_t *complen) +{ + uchar *pos; + ssize_t length; + uint i; + ulong len=packet_error; + size_t remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE : + NET_HEADER_SIZE); + *complen = 0; + + net->reading_or_writing=1; + + pos = net->buff + net->where_b; /* net->packet -4 */ + for (i=0 ; i < 2 ; i++) + { + while (remain > 0) + { + /* First read is done with non blocking mode */ + if ((length=ma_pvio_cache_read(net->pvio, pos,remain)) <= 0L) + { + len= packet_error; + net->error=2; /* Close socket */ + goto end; + } + remain -= (ulong) length; + pos+= (ulong) length; + } + + if (i == 0) + { /* First parts is packet length */ + ulong helping; + net->pkt_nr= net->buff[net->where_b + 3]; + net->compress_pkt_nr= ++net->pkt_nr; +#ifdef HAVE_COMPRESS + if (net->compress) + { + /* complen is > 0 if package is really compressed */ + *complen=uint3korr(&(net->buff[net->where_b + NET_HEADER_SIZE])); + } +#endif + + len=uint3korr(net->buff+net->where_b); + if (!len) + goto end; + helping = max(len,(ulong)*complen) + net->where_b; + /* The necessary size of net->buff */ + if (helping >= net->max_packet) + { + if (net_realloc(net, helping)) + { + len= packet_error; /* Return error */ + goto end; + } + } + pos=net->buff + net->where_b; + remain = len; + } + } + +end: + net->reading_or_writing=0; + return(len); +} + +ulong ma_net_read(NET *net) +{ + size_t len,complen; + +#ifdef HAVE_COMPRESS + if (!net->compress) + { +#endif + len = ma_real_read (net,(size_t *)&complen); + if (len == MAX_PACKET_LENGTH) + { + /* multi packet read */ + size_t length= 0; + ulong last_pos= net->where_b; + + do + { + length+= len; + net->where_b+= (unsigned long)len; + len= ma_real_read(net, &complen); + } while (len == MAX_PACKET_LENGTH); + net->where_b= last_pos; + if (len != packet_error) + len+= length; + } + net->read_pos = net->buff + net->where_b; + if (len != packet_error) + net->read_pos[len]=0; /* Safeguard for mysql_use_result */ + return (ulong)len; +#ifdef HAVE_COMPRESS + } + else + { + /* + compressed protocol: + + -------------------------------------- + packet_length 3 + sequence_id 1 + uncompressed_length 3 + -------------------------------------- + compressed data packet_length - 7 + -------------------------------------- + + Another packet will follow if: + packet_length == MAX_PACKET_LENGTH + + Last package will be identified by + - packet_length is zero (special case) + - packet_length < MAX_PACKET_LENGTH + */ + + size_t packet_length, + buffer_length; + size_t current= 0, start= 0; + my_bool is_multi_packet= 0; + + /* check if buffer is empty */ + if (!net->remain_in_buf) + { + buffer_length= 0; + } + else + { + /* save position and restore \0 character */ + buffer_length= net->buf_length; + current= net->buf_length - net->remain_in_buf; + start= current; + net->buff[net->buf_length - net->remain_in_buf]=net->save_char; + } + for (;;) + { + if (buffer_length - current >= 4) + { + uchar *pos= net->buff + current; + packet_length= uint3korr(pos); + + /* check if we have last package (special case: zero length) */ + if (!packet_length) + { + current+= 4; /* length + sequence_id, + no more data will follow */ + break; + } + if (packet_length + 4 <= buffer_length - current) + { + if (!is_multi_packet) + { + current= current + packet_length + 4; + } + else + { + /* remove packet_header */ + memmove(net->buff + current, + net->buff + current + 4, + buffer_length - current); + buffer_length-= 4; + current+= packet_length; + } + /* do we have last packet ? */ + if (packet_length != MAX_PACKET_LENGTH) + { + is_multi_packet= 0; + break; + } + else + is_multi_packet= 1; + if (start) + { + memmove(net->buff, net->buff + start, + buffer_length - start); + /* decrease buflen*/ + buffer_length-= start; + start= 0; + } + continue; + } + } + if (start) + { + memmove(net->buff, net->buff + start, buffer_length - start); + /* decrease buflen and current */ + current -= start; + buffer_length-= start; + start= 0; + } + + net->where_b=(unsigned long)buffer_length; + + if ((packet_length = ma_real_read(net,(size_t *)&complen)) == packet_error) + return packet_error; + if (_mariadb_uncompress(net, (unsigned char*) net->buff + net->where_b, &packet_length, &complen)) + { + net->error=2; /* caller will close socket */ + net->pvio->set_error(net->pvio->mysql, CR_ERR_NET_UNCOMPRESS, SQLSTATE_UNKNOWN, 0); + return packet_error; + } + buffer_length+= complen; + } + /* set values */ + net->buf_length= (unsigned long)buffer_length; + net->remain_in_buf= (unsigned long)(buffer_length - current); + net->read_pos= net->buff + start + 4; + len= current - start - 4; + if (is_multi_packet) + len-= 4; + net->save_char= net->read_pos[len]; /* Must be saved */ + net->read_pos[len]=0; /* Safeguard for mysql_use_result */ + } +#endif + return (ulong)len; +} + +int net_add_multi_command(NET *net, uchar command, const uchar *packet, + size_t length) +{ + if (net->extension->multi_status == COM_MULTI_OFF) + { + return(1); + } + /* don't increase packet number */ + net->compress_pkt_nr= net->pkt_nr= 0; + return ma_net_write_command(net, command, (const char *)packet, length, 1); +} + diff --git a/libmariadb/libmariadb/ma_password.c b/libmariadb/libmariadb/ma_password.c new file mode 100644 index 00000000..eb6fe6a8 --- /dev/null +++ b/libmariadb/libmariadb/ma_password.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016,2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* password checking routines */ +/***************************************************************************** + The main idea is that no password are sent between client & server on + connection and that no password are saved in mysql in a decodable form. + + On connection a random string is generated and sent to the client. + The client generates a new string with a random generator inited with + the hash values from the password and the sent string. + This 'check' string is sent to the server where it is compared with + a string generated from the stored hash_value of the password and the + random string. + + The password is saved (in user.password) by using the PASSWORD() function in + mysql. + + Example: + update user set password=PASSWORD("hello") where user="test" + This saves a hashed number as a string in the password field. +*****************************************************************************/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_string.h> +#include <ma_sha1.h> +#include "mysql.h" + + +void ma_randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) +{ /* For mysql 3.21.# */ +#ifdef HAVE_purify + memset((char*) rand_st, 0m sizeof(*rand_st)); /* Avoid UMC warnings */ +#endif + rand_st->max_value= 0x3FFFFFFFL; + rand_st->max_value_dbl=(double) rand_st->max_value; + rand_st->seed1=seed1%rand_st->max_value ; + rand_st->seed2=seed2%rand_st->max_value; +} + +double rnd(struct rand_struct *rand_st) +{ + rand_st->seed1=(rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; + rand_st->seed2=(rand_st->seed1+rand_st->seed2+33) % rand_st->max_value; + return (((double) rand_st->seed1)/rand_st->max_value_dbl); +} + +void ma_hash_password(ulong *result, const char *password, size_t len) +{ + register ulong nr=1345345333L, add=7, nr2=0x12345671L; + ulong tmp; + const char *password_end= password + len; + for (; password < password_end; password++) + { + if (*password == ' ' || *password == '\t') + continue; /* skip space in password */ + tmp= (ulong) (uchar) *password; + nr^= (((nr & 63)+add)*tmp)+ (nr << 8); + nr2+=(nr2 << 8) ^ nr; + add+=tmp; + } + result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */; + result[1]=nr2 & (((ulong) 1L << 31) -1L); + return; +} + +/* + * Generate a new message based on message and password + * The same thing is done in client and server and the results are checked. + */ + +/* scramble for 4.1 servers + * Code based on php_nysqlnd_scramble function from PHP's mysqlnd extension, + * written by Andrey Hristov (andrey@php.net) + * License: PHP License 3.0 + */ +void my_crypt(unsigned char *buffer, const unsigned char *s1, const unsigned char *s2, size_t len) +{ + const unsigned char *s1_end= s1 + len; + while (s1 < s1_end) { + *buffer++= *s1++ ^ *s2++; + } +} + +void ma_scramble_41(const unsigned char *buffer, const char *scramble, const char *password) +{ + _MA_SHA1_CTX context; + unsigned char sha1[SHA1_MAX_LENGTH]; + unsigned char sha2[SHA1_MAX_LENGTH]; + + + /* Phase 1: hash password */ + ma_SHA1Init(&context); + ma_SHA1Update(&context, (unsigned char *)password, strlen((char *)password)); + ma_SHA1Final(sha1, &context); + + /* Phase 2: hash sha1 */ + ma_SHA1Init(&context); + ma_SHA1Update(&context, (unsigned char*)sha1, SHA1_MAX_LENGTH); + ma_SHA1Final(sha2, &context); + + /* Phase 3: hash scramble + sha2 */ + ma_SHA1Init(&context); + ma_SHA1Update(&context, (unsigned char *)scramble, SCRAMBLE_LENGTH); + ma_SHA1Update(&context, (unsigned char*)sha2, SHA1_MAX_LENGTH); + ma_SHA1Final((unsigned char *)buffer, &context); + + /* let's crypt buffer now */ + my_crypt((uchar *)buffer, (const unsigned char *)buffer, (const unsigned char *)sha1, SHA1_MAX_LENGTH); +} +/* }}} */ + +void ma_make_scrambled_password(char *to,const char *password) +{ + ulong hash_res[2]; + ma_hash_password(hash_res,password, strlen(password)); + sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); +} + +/* + * Generate a new message based on message and password + * The same thing is done in client and server and the results are checked. + */ +char *ma_scramble_323(char *to, const char *message, const char *password) +{ + struct rand_struct rand_st; + ulong hash_pass[2], hash_message[2]; + + if (password && password[0]) + { + char extra, *to_start=to; + const char *end_scramble323= message + SCRAMBLE_LENGTH_323; + ma_hash_password(hash_pass,password, (uint) strlen(password)); + /* Don't use strlen, could be > SCRAMBLE_LENGTH_323 ! */ + ma_hash_password(hash_message, message, SCRAMBLE_LENGTH_323); + ma_randominit(&rand_st, hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + for (; message < end_scramble323; message++) + *to++= (char) (floor(rnd(&rand_st) * 31) + 64); + extra=(char) (floor(rnd(&rand_st) * 31)); + while (to_start != to) + *(to_start++)^= extra; + } + *to= 0; + return to; +} diff --git a/libmariadb/libmariadb/ma_pvio.c b/libmariadb/libmariadb/ma_pvio.c new file mode 100644 index 00000000..a8a653b6 --- /dev/null +++ b/libmariadb/libmariadb/ma_pvio.c @@ -0,0 +1,598 @@ +/************************************************************************************ + Copyright (C) 2015, 2022 MariaDB Corporation AB, + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ + +/* MariaDB Communication IO (PVIO) interface + + PVIO is the interface for client server communication and replaces former vio + component of the client library. + + PVIO support various protocols like sockets, pipes and shared memory, which are + implemented as plugins and can be extended therefore easily. + + Interface function description: + + ma_pvio_init allocates a new PVIO object which will be used + for the current connection + + ma_pvio_close frees all resources of previously allocated PVIO object + and closes open connections + + ma_pvio_read reads data from server + + ma_pvio_write sends data to server + + ma_pvio_set_timeout sets timeout for connection, read and write + + ma_pvio_register_callback + register callback functions for read and write + */ + +#include <ma_global.h> +#include <ma_sys.h> +#include <mysql.h> +#include <errmsg.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <ma_common.h> +#include <ma_pvio.h> +#include <mariadb_async.h> +#include <ma_context.h> + +/* callback functions for read/write */ +LIST *pvio_callback= NULL; + +#define IS_BLOCKING_ERROR() \ + IF_WIN(WSAGetLastError() != WSAEWOULDBLOCK, \ + (errno != EAGAIN && errno != EINTR)) + +/* {{{ MARIADB_PVIO *ma_pvio_init */ +MARIADB_PVIO *ma_pvio_init(MA_PVIO_CINFO *cinfo) +{ + /* check connection type and load the required plugin. + * Currently we support the following pvio types: + * pvio_socket + * pvio_namedpipe + * pvio_sharedmed + */ + const char *pvio_plugins[] = {"pvio_socket", "pvio_npipe", "pvio_shmem"}; + int type; + MARIADB_PVIO_PLUGIN *pvio_plugin; + MARIADB_PVIO *pvio= NULL; + + switch (cinfo->type) + { + case PVIO_TYPE_UNIXSOCKET: + case PVIO_TYPE_SOCKET: + type= 0; + break; +#ifdef _WIN32 + case PVIO_TYPE_NAMEDPIPE: + type= 1; + break; + case PVIO_TYPE_SHAREDMEM: + type= 2; + break; +#endif + default: + return NULL; + } + + if (!(pvio_plugin= (MARIADB_PVIO_PLUGIN *) + mysql_client_find_plugin(cinfo->mysql, + pvio_plugins[type], + MARIADB_CLIENT_PVIO_PLUGIN))) + { + /* error already set in mysql_client_find_plugin */ + return NULL; + } + +/* coverity[var_deref_op] */ + if (!(pvio= (MARIADB_PVIO *)calloc(1, sizeof(MARIADB_PVIO)))) + { + my_set_error(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + return NULL; + } + + /* register error routine and methods */ + pvio->methods= pvio_plugin->methods; + pvio->set_error= my_set_error; + pvio->type= cinfo->type; + + /* set timeout to connect timeout - after successful connect we will set + * correct values for read and write */ + if (pvio->methods->set_timeout) + { + pvio->methods->set_timeout(pvio, PVIO_CONNECT_TIMEOUT, cinfo->mysql->options.connect_timeout); + pvio->methods->set_timeout(pvio, PVIO_READ_TIMEOUT, cinfo->mysql->options.connect_timeout); + pvio->methods->set_timeout(pvio, PVIO_WRITE_TIMEOUT, cinfo->mysql->options.connect_timeout); + } + + if (!(pvio->cache= calloc(1, PVIO_READ_AHEAD_CACHE_SIZE))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + free(pvio); + return NULL; + } + pvio->cache_size= 0; + pvio->cache_pos= pvio->cache; + + return pvio; +} +/* }}} */ + +/* {{{ my_bool ma_pvio_is_alive */ +my_bool ma_pvio_is_alive(MARIADB_PVIO *pvio) +{ + if (!pvio) + return FALSE; + if (pvio->methods->is_alive) + return pvio->methods->is_alive(pvio); + return TRUE; +} +/* }}} */ + +/* {{{ int ma_pvio_fast_send */ +int ma_pvio_fast_send(MARIADB_PVIO *pvio) +{ + if (!pvio || !pvio->methods->fast_send) + return 1; + return pvio->methods->fast_send(pvio); +} +/* }}} */ + +/* {{{ int ma_pvio_keepalive */ +int ma_pvio_keepalive(MARIADB_PVIO *pvio) +{ + if (!pvio || !pvio->methods->keepalive) + return 1; + return pvio->methods->keepalive(pvio); +} +/* }}} */ + +/* {{{ my_bool ma_pvio_set_timeout */ +my_bool ma_pvio_set_timeout(MARIADB_PVIO *pvio, + enum enum_pvio_timeout type, + int timeout) +{ + if (!pvio) + return 1; + + if (pvio->methods->set_timeout) + return pvio->methods->set_timeout(pvio, type, timeout); + return 1; +} +/* }}} */ + +/* {{{ size_t ma_pvio_read_async */ +static size_t ma_pvio_read_async(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + ssize_t res= 0; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + int timeout= pvio->timeout[PVIO_READ_TIMEOUT]; + + if (!pvio->methods->async_read) + { + PVIO_SET_ERROR(pvio->mysql, CR_ASYNC_NOT_SUPPORTED, unknown_sqlstate, 0); + return -1; + } + + for (;;) + { + if (pvio->methods->async_read) + res= pvio->methods->async_read(pvio, buffer, length); + if (res >= 0 || IS_BLOCKING_ERROR()) + return res; + b->events_to_wait_for= MYSQL_WAIT_READ; + if (timeout >= 0) + { + b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; + b->timeout_value= timeout; + } + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) + return -1; + } +} +/* }}} */ + +/* {{{ size_t ma_pvio_read */ +ssize_t ma_pvio_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + ssize_t r= -1; + if (!pvio) + return -1; + if (IS_PVIO_ASYNC_ACTIVE(pvio)) + { + r= +#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL) + (pvio->ctls) ? ma_tls_read_async(pvio, buffer, length) : +#endif + (ssize_t)ma_pvio_read_async(pvio, buffer, length); + goto end; + } + else + { + if (IS_PVIO_ASYNC(pvio)) + { + /* + If switching from non-blocking to blocking API usage, set the socket + back to blocking mode. + */ + my_bool old_mode; + ma_pvio_blocking(pvio, TRUE, &old_mode); + } + } + + /* secure connection */ +#ifdef HAVE_TLS + if (pvio->ctls) + { + r= ma_pvio_tls_read(pvio->ctls, buffer, length); + goto end; + } +#endif + if (pvio->methods->read) + r= pvio->methods->read(pvio, buffer, length); +end: + if (pvio_callback) + { + void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length); + LIST *p= pvio_callback; + while (p) + { + callback= p->data; + callback(0, pvio->mysql, buffer, r); + p= p->next; + } + } + if (r > 0) + pvio->bytes_read+= r; + return r; +} +/* }}} */ + +/* {{{ size_t ma_pvio_cache_read */ +ssize_t ma_pvio_cache_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + ssize_t r; + + if (!pvio) + return -1; + + if (!pvio->cache) + return ma_pvio_read(pvio, buffer, length); + + if (pvio->cache + pvio->cache_size > pvio->cache_pos) + { + ssize_t remaining = pvio->cache + pvio->cache_size - pvio->cache_pos; + assert(remaining > 0); + r= MIN((ssize_t)length, remaining); + memcpy(buffer, pvio->cache_pos, r); + pvio->cache_pos+= r; + } + else if (length >= PVIO_READ_AHEAD_CACHE_MIN_SIZE) + { + r= ma_pvio_read(pvio, buffer, length); + } + else + { + r= ma_pvio_read(pvio, pvio->cache, PVIO_READ_AHEAD_CACHE_SIZE); + if (r > 0) + { + if (length < (size_t)r) + { + pvio->cache_size= r; + pvio->cache_pos= pvio->cache + length; + r= length; + } + memcpy(buffer, pvio->cache, r); + } + } + return r; +} +/* }}} */ + +/* {{{ size_t ma_pvio_write_async */ +static ssize_t ma_pvio_write_async(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t res; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + int timeout= pvio->timeout[PVIO_WRITE_TIMEOUT]; + + for (;;) + { + res= pvio->methods->async_write(pvio, buffer, length); + if (res >= 0 || IS_BLOCKING_ERROR()) + return res; + b->events_to_wait_for= MYSQL_WAIT_WRITE; + if (timeout >= 0) + { + b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; + b->timeout_value= timeout; + } + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) + return -1; + } +} +/* }}} */ + +/* {{{ size_t ma_pvio_write */ +ssize_t ma_pvio_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t r= 0; + + if (!pvio) + return -1; + + if (IS_PVIO_ASYNC_ACTIVE(pvio)) + { + r= +#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL) + (pvio->ctls) ? ma_tls_write_async(pvio, buffer, length) : +#endif + ma_pvio_write_async(pvio, buffer, length); + goto end; + } + else + { + if (IS_PVIO_ASYNC(pvio)) + { + /* + If switching from non-blocking to blocking API usage, set the socket + back to blocking mode. + */ + my_bool old_mode; + ma_pvio_blocking(pvio, TRUE, &old_mode); + } + } + /* secure connection */ +#ifdef HAVE_TLS + if (pvio->ctls) + { + r= ma_pvio_tls_write(pvio->ctls, buffer, length); + goto end; + } +#endif + + if (pvio->methods->write) + r= pvio->methods->write(pvio, buffer, length); +end: + if (pvio_callback) + { + void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length); + LIST *p= pvio_callback; + while (p) + { + callback= p->data; + callback(1, pvio->mysql, buffer, r); + p= p->next; + } + } + if (r > 0) + pvio->bytes_sent+= r; + return r; +} +/* }}} */ + +/* {{{ void ma_pvio_close */ +void ma_pvio_close(MARIADB_PVIO *pvio) +{ + /* free internal structures and close connection */ + if (pvio) + { +#ifdef HAVE_TLS + if (pvio->ctls) + { + ma_pvio_tls_close(pvio->ctls); + free(pvio->ctls); + } +#endif + if (pvio && pvio->methods->close) + pvio->methods->close(pvio); + + if (pvio->cache) + free(pvio->cache); + + free(pvio); + } +} +/* }}} */ + +/* {{{ my_bool ma_pvio_get_handle */ +my_bool ma_pvio_get_handle(MARIADB_PVIO *pvio, void *handle) +{ + if (pvio && pvio->methods->get_handle) + return pvio->methods->get_handle(pvio, handle); + return 1; +} +/* }}} */ + +/* {{{ ma_pvio_wait_async */ +static my_bool +ma_pvio_wait_async(struct mysql_async_context *b, enum enum_pvio_io_event event, + int timeout) +{ + switch (event) + { + case VIO_IO_EVENT_READ: + b->events_to_wait_for = MYSQL_WAIT_READ; + break; + case VIO_IO_EVENT_WRITE: + b->events_to_wait_for = MYSQL_WAIT_WRITE; + break; + case VIO_IO_EVENT_CONNECT: + b->events_to_wait_for = MYSQL_WAIT_WRITE | IF_WIN(0, MYSQL_WAIT_EXCEPT); + break; + } + + if (timeout >= 0) + { + b->events_to_wait_for |= MYSQL_WAIT_TIMEOUT; + b->timeout_value= timeout; + } + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + return (b->events_occurred & MYSQL_WAIT_TIMEOUT) ? 0 : 1; +} +/* }}} */ + +/* {{{ ma_pvio_wait_io_or_timeout */ +int ma_pvio_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout) +{ + if (pvio) + { + if (IS_PVIO_ASYNC_ACTIVE(pvio)) + return ma_pvio_wait_async(pvio->mysql->options.extension->async_context, + (is_read) ? VIO_IO_EVENT_READ : VIO_IO_EVENT_WRITE, + timeout); + + if (pvio && pvio->methods->wait_io_or_timeout) + return pvio->methods->wait_io_or_timeout(pvio, is_read, timeout); + } + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_pvio_connect */ +my_bool ma_pvio_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo) +{ + if (pvio && pvio->methods->connect) + return pvio->methods->connect(pvio, cinfo); + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_pvio_blocking */ +my_bool ma_pvio_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode) +{ + if (pvio && pvio->methods->blocking) + return pvio->methods->blocking(pvio, block, previous_mode) != 0; + return 1; +} +/* }}} */ + +/* {{{ my_bool ma_pvio_is_blocking */ +my_bool ma_pvio_is_blocking(MARIADB_PVIO *pvio) +{ + if (pvio && pvio->methods->is_blocking) + return pvio->methods->is_blocking(pvio); + return 1; +} +/* }}} */ + +/* {{{ ma_pvio_has_data */ +my_bool ma_pvio_has_data(MARIADB_PVIO *pvio, ssize_t *data_len) +{ + /* check if we still have unread data in cache */ + if (pvio && pvio->cache) + if (pvio->cache_pos > pvio->cache) + return test(pvio->cache_pos - pvio->cache); + if (pvio && pvio->methods->has_data) + return pvio->methods->has_data(pvio, data_len); + return 1; +} +/* }}} */ + +#ifdef HAVE_TLS +/* {{{ my_bool ma_pvio_start_ssl */ +my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio) +{ + if (!pvio || !pvio->mysql) + return 1; + CLEAR_CLIENT_ERROR(pvio->mysql); + if (!(pvio->ctls= ma_pvio_tls_init(pvio->mysql))) + { + return 1; + } + if (ma_pvio_tls_connect(pvio->ctls)) + { + free(pvio->ctls); + pvio->ctls= NULL; + return 1; + } + + /* default behaviour: + 1. peer certificate verification + 2. verify CN (requires option ssl_verify_check) + 3. verrify finger print + */ + if (pvio->mysql->options.extension->tls_verify_server_cert && + ma_pvio_tls_verify_server_cert(pvio->ctls)) + return 1; + + if (pvio->mysql->options.extension && + ((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) || + (pvio->mysql->options.extension->tls_fp_list && pvio->mysql->options.extension->tls_fp_list[0]))) + { + if (ma_pvio_tls_check_fp(pvio->ctls, + pvio->mysql->options.extension->tls_fp, + pvio->mysql->options.extension->tls_fp_list)) + return 1; + } + + return 0; +} +/* }}} */ +#endif + +/* {{{ ma_pvio_register_callback */ +int ma_pvio_register_callback(my_bool register_callback, + void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length)) +{ + LIST *list; + + if (!callback_function) + return 1; + + /* plugin will unregister in it's deinit function */ + if (register_callback) + { + list= (LIST *)malloc(sizeof(LIST)); + + list->data= (void *)callback_function; + pvio_callback= list_add(pvio_callback, list); + } + else /* unregister callback function */ + { + LIST *p= pvio_callback; + while (p) + { + if (p->data == callback_function) + { + list_delete(pvio_callback, p); + break; + } + p= p->next; + } + } + return 0; +} +/* }}} */ diff --git a/libmariadb/libmariadb/ma_sha1.c b/libmariadb/libmariadb/ma_sha1.c new file mode 100644 index 00000000..04c5760e --- /dev/null +++ b/libmariadb/libmariadb/ma_sha1.c @@ -0,0 +1,326 @@ +/**************************************************************************** + Copyright (C) 2012 Monty Program AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + *****************************************************************************/ + +/* This code came from the PHP project, initially written by + Stefan Esser */ + + +#include "ma_global.h" +#include "string.h" + +/* This code is heavily based on the PHP md5 implementation */ + +#include "ma_sha1.h" + + +static void ma_SHA1Transform(uint32[5], const unsigned char[64]); +static void ma_SHA1Encode(unsigned char *, uint32 *, unsigned int); +static void ma_SHA1Decode(uint32 *, const unsigned char *, unsigned int); + +static unsigned char PADDING[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic SHA1 functions. +*/ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((x) ^ (y) ^ (z)) +#define H(x, y, z) (((x) & (y)) | ((z) & ((x) | (y)))) +#define I(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. +*/ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* W[i] +*/ +#define W(i) ( tmp=x[(i-3)&15]^x[(i-8)&15]^x[(i-14)&15]^x[i&15], \ + (x[i&15]=ROTATE_LEFT(tmp, 1)) ) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +*/ +#define FF(a, b, c, d, e, w) { \ + (e) += F ((b), (c), (d)) + (w) + (uint32)(0x5A827999); \ + (e) += ROTATE_LEFT ((a), 5); \ + (b) = ROTATE_LEFT((b), 30); \ +} +#define GG(a, b, c, d, e, w) { \ + (e) += G ((b), (c), (d)) + (w) + (uint32)(0x6ED9EBA1); \ + (e) += ROTATE_LEFT ((a), 5); \ + (b) = ROTATE_LEFT((b), 30); \ +} +#define HH(a, b, c, d, e, w) { \ + (e) += H ((b), (c), (d)) + (w) + (uint32)(0x8F1BBCDC); \ + (e) += ROTATE_LEFT ((a), 5); \ + (b) = ROTATE_LEFT((b), 30); \ +} +#define II(a, b, c, d, e, w) { \ + (e) += I ((b), (c), (d)) + (w) + (uint32)(0xCA62C1D6); \ + (e) += ROTATE_LEFT ((a), 5); \ + (b) = ROTATE_LEFT((b), 30); \ +} + + +/* {{{ ma_SHA1Init + * SHA1 initialization. Begins an SHA1 operation, writing a new context. + */ +void ma_SHA1Init(_MA_SHA1_CTX * context) +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. + */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; + context->state[4] = 0xc3d2e1f0; +} +/* }}} */ + +/* {{{ ma_SHA1Update + SHA1 block update operation. Continues an SHA1 message-digest + operation, processing another message block, and updating the + context. + */ +void ma_SHA1Update(_MA_SHA1_CTX * context, const unsigned char *input, + size_t inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((uint32) inputLen << 3)) + < ((uint32) inputLen << 3)) + context->count[1]++; + context->count[1] += ((uint32) inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + memcpy + ((unsigned char*) & context->buffer[index], (unsigned char*) input, partLen); + ma_SHA1Transform(context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + ma_SHA1Transform(context->state, &input[i]); + + index = 0; + } else + i = 0; + + /* Buffer remaining input */ + memcpy + ((unsigned char*) & context->buffer[index], (unsigned char*) & input[i], + inputLen - i); +} +/* }}} */ + +/* {{{ ma_SHA1Final + SHA1 finalization. Ends an SHA1 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void ma_SHA1Final(unsigned char digest[20], _MA_SHA1_CTX * context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + bits[7] = context->count[0] & 0xFF; + bits[6] = (context->count[0] >> 8) & 0xFF; + bits[5] = (context->count[0] >> 16) & 0xFF; + bits[4] = (context->count[0] >> 24) & 0xFF; + bits[3] = context->count[1] & 0xFF; + bits[2] = (context->count[1] >> 8) & 0xFF; + bits[1] = (context->count[1] >> 16) & 0xFF; + bits[0] = (context->count[1] >> 24) & 0xFF; + + /* Pad out to 56 mod 64. + */ + index = (unsigned int) ((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + ma_SHA1Update(context, PADDING, padLen); + + /* Append length (before padding) */ + ma_SHA1Update(context, bits, 8); + + /* Store state in digest */ + ma_SHA1Encode(digest, context->state, 20); + + /* Zeroize sensitive information. + */ + memset((unsigned char*) context, 0, sizeof(*context)); +} +/* }}} */ + +/* {{{ ma_SHA1Transform + * SHA1 basic transformation. Transforms state based on block. + */ +static void ma_SHA1Transform(uint32 state[5], const unsigned char block[64]) +{ + uint32 a = state[0], b = state[1], c = state[2]; + uint32 d = state[3], e = state[4], x[16], tmp; + + ma_SHA1Decode(x, block, 64); + + /* Round 1 */ + FF(a, b, c, d, e, x[0]); /* 1 */ + FF(e, a, b, c, d, x[1]); /* 2 */ + FF(d, e, a, b, c, x[2]); /* 3 */ + FF(c, d, e, a, b, x[3]); /* 4 */ + FF(b, c, d, e, a, x[4]); /* 5 */ + FF(a, b, c, d, e, x[5]); /* 6 */ + FF(e, a, b, c, d, x[6]); /* 7 */ + FF(d, e, a, b, c, x[7]); /* 8 */ + FF(c, d, e, a, b, x[8]); /* 9 */ + FF(b, c, d, e, a, x[9]); /* 10 */ + FF(a, b, c, d, e, x[10]); /* 11 */ + FF(e, a, b, c, d, x[11]); /* 12 */ + FF(d, e, a, b, c, x[12]); /* 13 */ + FF(c, d, e, a, b, x[13]); /* 14 */ + FF(b, c, d, e, a, x[14]); /* 15 */ + FF(a, b, c, d, e, x[15]); /* 16 */ + FF(e, a, b, c, d, W(16)); /* 17 */ + FF(d, e, a, b, c, W(17)); /* 18 */ + FF(c, d, e, a, b, W(18)); /* 19 */ + FF(b, c, d, e, a, W(19)); /* 20 */ + + /* Round 2 */ + GG(a, b, c, d, e, W(20)); /* 21 */ + GG(e, a, b, c, d, W(21)); /* 22 */ + GG(d, e, a, b, c, W(22)); /* 23 */ + GG(c, d, e, a, b, W(23)); /* 24 */ + GG(b, c, d, e, a, W(24)); /* 25 */ + GG(a, b, c, d, e, W(25)); /* 26 */ + GG(e, a, b, c, d, W(26)); /* 27 */ + GG(d, e, a, b, c, W(27)); /* 28 */ + GG(c, d, e, a, b, W(28)); /* 29 */ + GG(b, c, d, e, a, W(29)); /* 30 */ + GG(a, b, c, d, e, W(30)); /* 31 */ + GG(e, a, b, c, d, W(31)); /* 32 */ + GG(d, e, a, b, c, W(32)); /* 33 */ + GG(c, d, e, a, b, W(33)); /* 34 */ + GG(b, c, d, e, a, W(34)); /* 35 */ + GG(a, b, c, d, e, W(35)); /* 36 */ + GG(e, a, b, c, d, W(36)); /* 37 */ + GG(d, e, a, b, c, W(37)); /* 38 */ + GG(c, d, e, a, b, W(38)); /* 39 */ + GG(b, c, d, e, a, W(39)); /* 40 */ + + /* Round 3 */ + HH(a, b, c, d, e, W(40)); /* 41 */ + HH(e, a, b, c, d, W(41)); /* 42 */ + HH(d, e, a, b, c, W(42)); /* 43 */ + HH(c, d, e, a, b, W(43)); /* 44 */ + HH(b, c, d, e, a, W(44)); /* 45 */ + HH(a, b, c, d, e, W(45)); /* 46 */ + HH(e, a, b, c, d, W(46)); /* 47 */ + HH(d, e, a, b, c, W(47)); /* 48 */ + HH(c, d, e, a, b, W(48)); /* 49 */ + HH(b, c, d, e, a, W(49)); /* 50 */ + HH(a, b, c, d, e, W(50)); /* 51 */ + HH(e, a, b, c, d, W(51)); /* 52 */ + HH(d, e, a, b, c, W(52)); /* 53 */ + HH(c, d, e, a, b, W(53)); /* 54 */ + HH(b, c, d, e, a, W(54)); /* 55 */ + HH(a, b, c, d, e, W(55)); /* 56 */ + HH(e, a, b, c, d, W(56)); /* 57 */ + HH(d, e, a, b, c, W(57)); /* 58 */ + HH(c, d, e, a, b, W(58)); /* 59 */ + HH(b, c, d, e, a, W(59)); /* 60 */ + + /* Round 4 */ + II(a, b, c, d, e, W(60)); /* 61 */ + II(e, a, b, c, d, W(61)); /* 62 */ + II(d, e, a, b, c, W(62)); /* 63 */ + II(c, d, e, a, b, W(63)); /* 64 */ + II(b, c, d, e, a, W(64)); /* 65 */ + II(a, b, c, d, e, W(65)); /* 66 */ + II(e, a, b, c, d, W(66)); /* 67 */ + II(d, e, a, b, c, W(67)); /* 68 */ + II(c, d, e, a, b, W(68)); /* 69 */ + II(b, c, d, e, a, W(69)); /* 70 */ + II(a, b, c, d, e, W(70)); /* 71 */ + II(e, a, b, c, d, W(71)); /* 72 */ + II(d, e, a, b, c, W(72)); /* 73 */ + II(c, d, e, a, b, W(73)); /* 74 */ + II(b, c, d, e, a, W(74)); /* 75 */ + II(a, b, c, d, e, W(75)); /* 76 */ + II(e, a, b, c, d, W(76)); /* 77 */ + II(d, e, a, b, c, W(77)); /* 78 */ + II(c, d, e, a, b, W(78)); /* 79 */ + II(b, c, d, e, a, W(79)); /* 80 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Zeroize sensitive information. */ + memset((unsigned char*) x, 0, sizeof(x)); +} +/* }}} */ + +/* {{{ ma_SHA1Encode + Encodes input (uint32) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void ma_SHA1Encode(unsigned char *output, uint32 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char) ((input[i] >> 24) & 0xff); + output[j + 1] = (unsigned char) ((input[i] >> 16) & 0xff); + output[j + 2] = (unsigned char) ((input[i] >> 8) & 0xff); + output[j + 3] = (unsigned char) (input[i] & 0xff); + } +} +/* }}} */ + +/* {{{ ma_SHA1Decode + Decodes input (unsigned char) into output (uint32). Assumes len is + a multiple of 4. + */ +static void ma_SHA1Decode(uint32 *output, const unsigned char * input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint32) input[j + 3]) | (((uint32) input[j + 2]) << 8) | + (((uint32) input[j + 1]) << 16) | (((uint32) input[j]) << 24); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/libmariadb/libmariadb/ma_stmt_codec.c b/libmariadb/libmariadb/ma_stmt_codec.c new file mode 100644 index 00000000..afb46b25 --- /dev/null +++ b/libmariadb/libmariadb/ma_stmt_codec.c @@ -0,0 +1,1362 @@ +/**************************************************************************** + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*****************************************************************************/ + +/* The implementation for prepared statements was ported from PHP's mysqlnd + extension, written by Andrey Hristov, Georg Richter and Ulf Wendel + + Original file header: + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +#include "ma_global.h" +#include <ma_sys.h> +#include <ma_string.h> +#include <mariadb_ctype.h> +#include "mysql.h" +#include <math.h> /* ceil() */ +#include <limits.h> + +#ifdef WIN32 +#include <malloc.h> +#endif + +#define MYSQL_SILENT + +/* ranges for C-binding */ +#define UINT_MAX32 0xFFFFFFFFL +#define UINT_MAX24 0x00FFFFFF +#define UINT_MAX16 0xFFFF +#ifndef INT_MIN8 +#define INT_MIN8 (~0x7F) +#define INT_MAX8 0x7F +#endif +#define UINT_MAX8 0xFF + + #define MAX_DOUBLE_STRING_REP_LENGTH 300 +#if defined(HAVE_LONG_LONG) && !defined(LONGLONG_MIN) +#define LONGLONG_MIN ((long long) 0x8000000000000000LL) +#define LONGLONG_MAX ((long long) 0x7FFFFFFFFFFFFFFFLL) +#endif + +#define MAX_DBL_STR (3 + DBL_MANT_DIG - DBL_MIN_EXP) + +#if defined(HAVE_LONG_LONG) && !defined(ULONGLONG_MAX) +/* First check for ANSI C99 definition: */ +#ifdef ULLONG_MAX +#define ULONGLONG_MAX ULLONG_MAX +#else +#define ULONGLONG_MAX ((unsigned long long)(~0ULL)) +#endif +#endif /* defined (HAVE_LONG_LONG) && !defined(ULONGLONG_MAX)*/ + +#define YY_PART_YEAR 70 + +MYSQL_PS_CONVERSION mysql_ps_fetch_functions[MYSQL_TYPE_GEOMETRY + 1]; +my_bool mysql_ps_subsystem_initialized= 0; + + +#define NUMERIC_TRUNCATION(val,min_range, max_range)\ + ((((val) > (max_range)) || ((val) < (min_range)) ? 1 : 0)) + + +void ma_bmove_upp(register char *dst, register const char *src, register size_t len) +{ + while (len-- != 0) *--dst = *--src; +} + +/* {{{ ps_fetch_from_1_to_8_bytes */ +void ps_fetch_from_1_to_8_bytes(MYSQL_BIND *r_param, const MYSQL_FIELD * const field, + unsigned char **row, unsigned int byte_count) +{ + my_bool is_unsigned= test(field->flags & UNSIGNED_FLAG); + r_param->buffer_length= byte_count; + switch (byte_count) { + case 1: + *(uchar *)r_param->buffer= **row; + *r_param->error= is_unsigned != r_param->is_unsigned && *(uchar *)r_param->buffer > INT_MAX8; + break; + case 2: + shortstore(r_param->buffer, ((ushort) sint2korr(*row))); + *r_param->error= is_unsigned != r_param->is_unsigned && *(ushort *)r_param->buffer > INT_MAX16; + break; + case 4: + { + longstore(r_param->buffer, ((uint32)sint4korr(*row))); + *r_param->error= is_unsigned != r_param->is_unsigned && *(uint32 *)r_param->buffer > INT_MAX32; + } + break; + case 8: + { + ulonglong val= (ulonglong)sint8korr(*row); + longlongstore(r_param->buffer, val); + *r_param->error= is_unsigned != r_param->is_unsigned && val > LONGLONG_MAX ; + } + break; + default: + r_param->buffer_length= 0; + break; + } + (*row)+= byte_count; +} +/* }}} */ + +static unsigned long long my_strtoull(const char *str, size_t len, const char **end, int *err) +{ + unsigned long long val = 0; + const char *p = str; + const char *end_str = p + len; + + for (; p < end_str; p++) + { + if (*p < '0' || *p > '9') + break; + + if (val > ULONGLONG_MAX /10 || val*10 > ULONGLONG_MAX - (*p - '0')) + { + *err = ERANGE; + break; + } + val = val * 10 + *p -'0'; + } + + if (p == str) + /* Did not parse anything.*/ + *err = ERANGE; + + *end = p; + return val; +} + +static long long my_strtoll(const char *str, size_t len, const char **end, int *err) +{ + unsigned long long uval = 0; + const char *p = str; + const char *end_str = p + len; + int neg; + + while (p < end_str && isspace(*p)) + p++; + + if (p == end_str) + { + *end = p; + *err = ERANGE; + return 0; + } + + neg = *p == '-'; + if (neg) + p++; + + uval = my_strtoull(p, (end_str - p), &p, err); + *end = p; + if (*err) + return uval; + + if (!neg) + { + /* Overflow of the long long range. */ + if (uval > LONGLONG_MAX) + { + *end = p - 1; + uval = LONGLONG_MAX; + *err = ERANGE; + } + return uval; + } + + if (uval == (unsigned long long) LONGLONG_MIN) + return LONGLONG_MIN; + + if (uval > LONGLONG_MAX) + { + *end = p - 1; + uval = LONGLONG_MIN; + *err = ERANGE; + } + + return -1LL * uval; +} + + +static long long my_atoll(const char *str, const char *end_str, int *error) +{ + const char *p=str; + const char *end; + long long ret; + while (p < end_str && isspace(*p)) + p++; + + ret = my_strtoll(p, end_str - p, &end, error); + + while(end < end_str && isspace(*end)) + end++; + + if(end != end_str) + *error= 1; + + return ret; +} + + +static unsigned long long my_atoull(const char *str, const char *end_str, int *error) +{ + const char *p = str; + const char *end; + unsigned long long ret; + + while (p < end_str && isspace(*p)) + p++; + + ret = my_strtoull(p, end_str - p, &end, error); + + while(end < end_str && isspace(*end)) + end++; + + if(end != end_str) + *error= 1; + + return ret; +} + +double my_atod(const char *number, const char *end, int *error) +{ + double val= 0.0; + char buffer[MAX_DBL_STR + 1]; + int len= (int)(end - number); + + *error= errno= 0; + + if (len > MAX_DBL_STR) + { + *error= 1; + len= MAX_DBL_STR; + } + + memcpy(buffer, number, len); + buffer[len]= '\0'; + + val= strtod(buffer, NULL); + + if (errno) + *error= errno; + + return val; +} + + +/* + strtoui() version, that works for non-null terminated strings +*/ +static unsigned int my_strtoui(const char *str, size_t len, const char **end, int *err) +{ + unsigned long long ull = my_strtoull(str, len, end, err); + if (ull > UINT_MAX) + *err = ERANGE; + return (unsigned int)ull; +} + +/* + Parse time, in MySQL format. + + the input string needs is in form "hour:minute:second[.fraction]" + hour, minute and second can have leading zeroes or not, + they are not necessarily 2 chars. + + Hour must be < 838, minute < 60, second < 60 + Only 6 places of fraction are considered, the value is truncated after 6 places. +*/ +static const unsigned int frac_mul[] = { 1000000,100000,10000,1000,100,10 }; + +static int parse_time(const char *str, size_t length, const char **end_ptr, MYSQL_TIME *tm) +{ + int err= 0; + const char *p = str; + const char *end = str + length; + size_t frac_len; + int ret=1; + + tm->hour = my_strtoui(p, end-p, &p, &err); + if (err || tm->hour > 838 || p == end || *p != ':' ) + goto end; + + p++; + tm->minute = my_strtoui(p, end-p, &p, &err); + if (err || tm->minute > 59 || p == end || *p != ':') + goto end; + + p++; + tm->second = my_strtoui(p, end-p, &p, &err); + if (err || tm->second > 59) + goto end; + + ret = 0; + tm->second_part = 0; + + if (p == end) + goto end; + + /* Check for fractional part*/ + if (*p != '.') + goto end; + + p++; + frac_len = MIN(6,end-p); + + tm->second_part = my_strtoui(p, frac_len, &p, &err); + if (err) + goto end; + + if (frac_len < 6) + tm->second_part *= frac_mul[frac_len]; + + ret = 0; + + /* Consume whole fractional part, even after 6 digits.*/ + p += frac_len; + while(p < *end_ptr) + { + if (*p < '0' || *p > '9') + break; + p++; + } +end: + *end_ptr = p; + return ret; +} + + +/* + Parse date, in MySQL format. + + The input string needs is in form "year-month-day" + year, month and day can have leading zeroes or not, + they do not have fixed length. + + Year must be < 10000, month < 12, day < 32 + + Years with 2 digits, are converted to values 1970-2069 according to + usual rules: + + 00-69 is converted to 2000-2069. + 70-99 is converted to 1970-1999. +*/ +static int parse_date(const char *str, size_t length, const char **end_ptr, MYSQL_TIME *tm) +{ + int err = 0; + const char *p = str; + const char *end = str + length; + int ret = 1; + + tm->year = my_strtoui(p, end - p, &p, &err); + if (err || tm->year > 9999 || p == end || *p != '-') + goto end; + + if (p - str == 2) // 2-digit year + tm->year += (tm->year >= 70) ? 1900 : 2000; + + p++; + tm->month = my_strtoui(p,end -p, &p, &err); + if (err || tm->month > 12 || p == end || *p != '-') + goto end; + + p++; + tm->day = my_strtoui(p, end -p , &p, &err); + if (err || tm->day > 31) + goto end; + + ret = 0; + +end: + *end_ptr = p; + return ret; +} + +/* + Parse (not null terminated) string representing + TIME, DATE, or DATETIME into MYSQL_TIME structure + + The supported formats by this functions are + - TIME : [-]hours:minutes:seconds[.fraction] + - DATE : year-month-day + - DATETIME : year-month-day<space>hours:minutes:seconds[.fraction] + + cf https://dev.mysql.com/doc/refman/8.0/en/datetime.html + + Whitespaces are trimmed from the start and end of the string. + The function ignores junk at the end of the string. + + Parts of date of time do not have fixed length, so that parsing is compatible with server. + However server supports additional formats, e.g YYYYMMDD, HHMMSS, which this function does + not support. + +*/ +int str_to_TIME(const char *str, size_t length, MYSQL_TIME *tm) +{ + const char *p = str; + const char *end = str + length; + int is_time = 0; + + if (!p) + goto error; + + while (p < end && isspace(*p)) + p++; + while (p < end && isspace(end[-1])) + end--; + + if (end -p < 5) + goto error; + + if (*p == '-') + { + tm->neg = 1; + /* Only TIME can't be negative.*/ + is_time = 1; + p++; + } + else + { + int i; + tm->neg = 0; + /* + Date parsing (in server) accepts leading zeroes, thus position of the delimiters + is not fixed. Scan the string to find out what we need to parse. + */ + for (i = 1; p + i < end; i++) + { + if(p[i] == '-' || p [i] == ':') + { + is_time = p[i] == ':'; + break; + } + } + } + + if (is_time) + { + if (parse_time(p, end - p, &p, tm)) + goto error; + + tm->year = tm->month = tm->day = 0; + tm->time_type = MYSQL_TIMESTAMP_TIME; + return 0; + } + + if (parse_date(p, end - p, &p, tm)) + goto error; + + if (p == end || p[0] != ' ') + { + tm->hour = tm->minute = tm->second = tm->second_part = 0; + tm->time_type = MYSQL_TIMESTAMP_DATE; + return 0; + } + + /* Skip space. */ + p++; + if (parse_time(p, end - p, &p, tm)) + goto error; + + /* In DATETIME, hours must be < 24.*/ + if (tm->hour > 23) + goto error; + + tm->time_type = MYSQL_TIMESTAMP_DATETIME; + return 0; + +error: + memset(tm, 0, sizeof(*tm)); + tm->time_type = MYSQL_TIMESTAMP_ERROR; + return 1; +} + + +static void convert_froma_string(MYSQL_BIND *r_param, char *buffer, size_t len) +{ + int error= 0; + switch (r_param->buffer_type) + { + case MYSQL_TYPE_TINY: + { + longlong val= my_atoll(buffer, buffer + len, &error); + *r_param->error= error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX8) : NUMERIC_TRUNCATION(val, INT_MIN8, INT_MAX8) || error > 0; + int1store(r_param->buffer, (uchar) val); + r_param->buffer_length= sizeof(uchar); + } + break; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + { + longlong val= my_atoll(buffer, buffer + len, &error); + *r_param->error= error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX16) : NUMERIC_TRUNCATION(val, INT_MIN16, INT_MAX16) || error > 0; + shortstore(r_param->buffer, (short)val); + r_param->buffer_length= sizeof(short); + } + break; + case MYSQL_TYPE_LONG: + { + longlong val= my_atoll(buffer, buffer + len, &error); + *r_param->error=error ? 1 : r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX32) : NUMERIC_TRUNCATION(val, INT_MIN32, INT_MAX32) || error > 0; + longstore(r_param->buffer, (int32)val); + r_param->buffer_length= sizeof(uint32); + } + break; + case MYSQL_TYPE_LONGLONG: + { + longlong val= r_param->is_unsigned ? (longlong)my_atoull(buffer, buffer + len, &error) : my_atoll(buffer, buffer + len, &error); + *r_param->error= error > 0; /* no need to check for truncation */ + longlongstore(r_param->buffer, val); + r_param->buffer_length= sizeof(longlong); + } + break; + case MYSQL_TYPE_DOUBLE: + { + double val= my_atod(buffer, buffer + len, &error); + *r_param->error= error > 0; /* no need to check for truncation */ + doublestore((uchar *)r_param->buffer, val); + r_param->buffer_length= sizeof(double); + } + break; + case MYSQL_TYPE_FLOAT: + { + float val= (float)my_atod(buffer, buffer + len, &error); + *r_param->error= error > 0; /* no need to check for truncation */ + floatstore((uchar *)r_param->buffer, val); + r_param->buffer_length= sizeof(float); + } + break; + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + { + MYSQL_TIME *tm= (MYSQL_TIME *)r_param->buffer; + str_to_TIME(buffer, len, tm); + break; + } + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + default: + { + if (len >= r_param->offset) + { + char *start= buffer + r_param->offset; /* stmt_fetch_column sets offset */ + char *end= buffer + len; + size_t copylen= 0; + + if (start < end) + { + copylen= end - start; + if (r_param->buffer_length) + memcpy(r_param->buffer, start, MIN(copylen, r_param->buffer_length)); + } + if (copylen < r_param->buffer_length) + ((char *)r_param->buffer)[copylen]= 0; + *r_param->error= (copylen > r_param->buffer_length); + + } + *r_param->length= (ulong)len; + } + break; + } +} + +static void convert_from_long(MYSQL_BIND *r_param, const MYSQL_FIELD *field, longlong val, my_bool is_unsigned) +{ + switch (r_param->buffer_type) { + case MYSQL_TYPE_TINY: + *(uchar *)r_param->buffer= (uchar)(val & 0xff); + *r_param->error= r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX8) : NUMERIC_TRUNCATION(val, INT_MIN8, INT_MAX8); + r_param->buffer_length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + shortstore(r_param->buffer, (short)(val & 0xffff)); + *r_param->error= r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX16) : NUMERIC_TRUNCATION(val, INT_MIN16, INT_MAX16); + r_param->buffer_length= 2; + break; + case MYSQL_TYPE_LONG: + longstore(r_param->buffer, (int32)(val & 0xffffffff)); + *r_param->error= r_param->is_unsigned ? NUMERIC_TRUNCATION(val, 0, UINT_MAX32) : NUMERIC_TRUNCATION(val, INT_MIN32, INT_MAX32); + r_param->buffer_length= 4; + break; + case MYSQL_TYPE_LONGLONG: + *r_param->error= (val < 0 && r_param->is_unsigned != is_unsigned); + longlongstore(r_param->buffer, val); + r_param->buffer_length= 8; + break; + case MYSQL_TYPE_DOUBLE: + { + volatile double dbl; + + dbl= (is_unsigned) ? ulonglong2double((ulonglong)val) : (double)val; + doublestore(r_param->buffer, dbl); + + *r_param->error = (dbl != ceil(dbl)) || + (is_unsigned ? (ulonglong )dbl != (ulonglong)val : + (longlong)dbl != (longlong)val); + + r_param->buffer_length= 8; + break; + } + case MYSQL_TYPE_FLOAT: + { + volatile float fval; + fval= is_unsigned ? (float)(ulonglong)(val) : (float)val; + floatstore((uchar *)r_param->buffer, fval); + *r_param->error= (fval != ceilf(fval)) || + (is_unsigned ? (ulonglong)fval != (ulonglong)val : + (longlong)fval != val); + r_param->buffer_length= 4; + } + break; + default: + { + char *buffer; + char *endptr; + uint len; + my_bool zf_truncated= 0; + + buffer= alloca(MAX(field->length, 22)); + endptr= ma_ll2str(val, buffer, is_unsigned ? 10 : -10); + len= (uint)(endptr - buffer); + + /* check if field flag is zerofill */ + if (field->flags & ZEROFILL_FLAG) + { + uint display_width= MAX(field->length, len); + if (display_width < r_param->buffer_length) + { + ma_bmove_upp(buffer + display_width, buffer + len, len); + /* coverity[bad_memset] */ + memset((void*) buffer, (int) '0', display_width - len); + len= display_width; + } + else + zf_truncated= 1; + } + convert_froma_string(r_param, buffer, len); + *r_param->error+= zf_truncated; + } + break; + } +} + + +/* {{{ ps_fetch_null */ +static +void ps_fetch_null(MYSQL_BIND *r_param __attribute__((unused)), + const MYSQL_FIELD * field __attribute__((unused)), + unsigned char **row __attribute__((unused))) +{ + /* do nothing */ +} +/* }}} */ + +#define GET_LVALUE_FROM_ROW(is_unsigned, data, ucast, scast)\ + (is_unsigned) ? (longlong)(ucast) *(longlong *)(data) : (longlong)(scast) *(longlong *)(data) +/* {{{ ps_fetch_int8 */ +static +void ps_fetch_int8(MYSQL_BIND *r_param, const MYSQL_FIELD * const field, + unsigned char **row) +{ + switch(r_param->buffer_type) { + case MYSQL_TYPE_TINY: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 1); + break; + default: + { + uchar val= **row; + longlong lval= field->flags & UNSIGNED_FLAG ? (longlong) val : (longlong)(signed char)val; + convert_from_long(r_param, field, lval, field->flags & UNSIGNED_FLAG); + (*row) += 1; + } + break; + } +} +/* }}} */ + + +/* {{{ ps_fetch_int16 */ +static +void ps_fetch_int16(MYSQL_BIND *r_param, const MYSQL_FIELD * const field, + unsigned char **row) +{ + switch (r_param->buffer_type) { + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 2); + break; + default: + { + short sval= sint2korr(*row); + longlong lval= field->flags & UNSIGNED_FLAG ? (longlong)(ushort) sval : (longlong)sval; + convert_from_long(r_param, field, lval, field->flags & UNSIGNED_FLAG); + (*row) += 2; + } + break; + } +} +/* }}} */ + + +/* {{{ ps_fetch_int32 */ +static +void ps_fetch_int32(MYSQL_BIND *r_param, const MYSQL_FIELD * const field, + unsigned char **row) +{ + switch (r_param->buffer_type) { +/* case MYSQL_TYPE_TINY: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 1); + break; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 2); + break; */ + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 4); + break; + default: + { + int32 sval= sint4korr(*row); + longlong lval= field->flags & UNSIGNED_FLAG ? (longlong)(uint32) sval : (longlong)sval; + convert_from_long(r_param, field, lval, field->flags & UNSIGNED_FLAG); + (*row) += 4; + } + break; + } +} +/* }}} */ + + +/* {{{ ps_fetch_int64 */ +static +void ps_fetch_int64(MYSQL_BIND *r_param, const MYSQL_FIELD * const field, + unsigned char **row) +{ + switch(r_param->buffer_type) + { +/* case MYSQL_TYPE_TINY: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 1); + break; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 2); + break; + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 4); + break; */ + case MYSQL_TYPE_LONGLONG: + ps_fetch_from_1_to_8_bytes(r_param, field, row, 8); + break; + default: + { + longlong sval= (longlong)sint8korr(*row); + longlong lval= field->flags & UNSIGNED_FLAG ? (longlong)(ulonglong) sval : (longlong)sval; + convert_from_long(r_param, field, lval, field->flags & UNSIGNED_FLAG); + (*row) += 8; + } + break; + } +} +/* }}} */ + +static void convert_from_float(MYSQL_BIND *r_param, const MYSQL_FIELD *field, float val, int size __attribute__((unused))) +{ + double check_trunc_val= (val > 0) ? floor(val) : -floor(-val); + char *buf= (char *)r_param->buffer; + switch (r_param->buffer_type) + { + case MYSQL_TYPE_TINY: + *buf= (r_param->is_unsigned) ? (uint8)val : (int8)val; + *r_param->error= check_trunc_val != (r_param->is_unsigned ? (double)((uint8)*buf) : + (double)((int8)*buf)); + r_param->buffer_length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + { + if (r_param->is_unsigned) + { + ushort sval= (ushort)val; + shortstore(buf, sval); + *r_param->error= check_trunc_val != (double)sval; + } else { + short sval= (short)val; + shortstore(buf, sval); + *r_param->error= check_trunc_val != (double)sval; + } + r_param->buffer_length= 2; + } + break; + case MYSQL_TYPE_LONG: + { + if (r_param->is_unsigned) + { + uint32 lval= (uint32)val; + longstore(buf, lval); + *r_param->error= (check_trunc_val != (double)lval); + } else { + int32 lval= (int32)val; + longstore(buf, lval); + *r_param->error= (check_trunc_val != (double)lval); + } + r_param->buffer_length= 4; + } + break; + case MYSQL_TYPE_LONGLONG: + { + if (r_param->is_unsigned) + { + ulonglong llval= (ulonglong)val; + longlongstore(buf, llval); + *r_param->error= (check_trunc_val != (double)llval); + } else { + longlong llval= (longlong)val; + longlongstore(buf, llval); + *r_param->error= (check_trunc_val != (double)llval); + } + r_param->buffer_length= 8; + } + break; + case MYSQL_TYPE_DOUBLE: + { + double dval= (double)val; + memcpy(buf, &dval, sizeof(double)); + r_param->buffer_length= 8; + } + break; + default: + { + char buff[MAX_DOUBLE_STRING_REP_LENGTH]; + size_t length; + + length= MIN(MAX_DOUBLE_STRING_REP_LENGTH - 1, r_param->buffer_length); + + if (field->decimals >= NOT_FIXED_DEC) + { + length= ma_gcvt(val, MY_GCVT_ARG_FLOAT, (int)length, buff, NULL); + } + else + { + length= ma_fcvt(val, field->decimals, buff, NULL); + } + + /* check if ZEROFILL flag is active */ + if (field->flags & ZEROFILL_FLAG) + { + /* enough space available ? */ + if (field->length < length || field->length > MAX_DOUBLE_STRING_REP_LENGTH - 1) + break; + ma_bmove_upp(buff + field->length, buff + length, length); + /* coverity[bad_memset] */ + memset((void*) buff, (int) '0', field->length - length); + length= field->length; + } + + convert_froma_string(r_param, buff, length); + } + break; + } +} + +static void convert_from_double(MYSQL_BIND *r_param, const MYSQL_FIELD *field, double val, int size __attribute__((unused))) +{ + double check_trunc_val= (val > 0) ? floor(val) : -floor(-val); + char *buf= (char *)r_param->buffer; + switch (r_param->buffer_type) + { + case MYSQL_TYPE_TINY: + *buf= (r_param->is_unsigned) ? (uint8)val : (int8)val; + *r_param->error= check_trunc_val != (r_param->is_unsigned ? (double)((uint8)*buf) : + (double)((int8)*buf)); + r_param->buffer_length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + { + if (r_param->is_unsigned) + { + ushort sval= (ushort)val; + shortstore(buf, sval); + *r_param->error= check_trunc_val != (double)sval; + } else { + short sval= (short)val; + shortstore(buf, sval); + *r_param->error= check_trunc_val != (double)sval; + } + r_param->buffer_length= 2; + } + break; + case MYSQL_TYPE_LONG: + { + if (r_param->is_unsigned) + { + uint32 lval= (uint32)val; + longstore(buf, lval); + *r_param->error= (check_trunc_val != (double)lval); + } else { + int32 lval= (int32)val; + longstore(buf, lval); + *r_param->error= (check_trunc_val != (double)lval); + } + r_param->buffer_length= 4; + } + break; + case MYSQL_TYPE_LONGLONG: + { + if (r_param->is_unsigned) + { + ulonglong llval= (ulonglong)val; + longlongstore(buf, llval); + *r_param->error= (check_trunc_val != (double)llval); + } else { + longlong llval= (longlong)val; + longlongstore(buf, llval); + *r_param->error= (check_trunc_val != (double)llval); + } + r_param->buffer_length= 8; + } + break; + case MYSQL_TYPE_FLOAT: + { + float fval= (float)val; + memcpy(buf, &fval, sizeof(float)); + *r_param->error= (*(float*)buf != fval); + r_param->buffer_length= 4; + } + break; + default: + { + char buff[MAX_DOUBLE_STRING_REP_LENGTH]; + size_t length; + + length= MIN(MAX_DOUBLE_STRING_REP_LENGTH - 1, r_param->buffer_length); + + if (field->decimals >= NOT_FIXED_DEC) + { + length= ma_gcvt(val, MY_GCVT_ARG_DOUBLE, (int)length, buff, NULL); + } + else + { + length= ma_fcvt(val, field->decimals, buff, NULL); + } + + /* check if ZEROFILL flag is active */ + if (field->flags & ZEROFILL_FLAG) + { + /* enough space available ? */ + if (field->length < length || field->length > MAX_DOUBLE_STRING_REP_LENGTH - 1) + break; + ma_bmove_upp(buff + field->length, buff + length, length); + /* coverity [bad_memset] */ + memset((void*) buff, (int) '0', field->length - length); + length= field->length; + } + convert_froma_string(r_param, buff, length); + } + break; + } +} + + +/* {{{ ps_fetch_double */ +static +void ps_fetch_double(MYSQL_BIND *r_param, const MYSQL_FIELD * field , unsigned char **row) +{ + switch (r_param->buffer_type) + { + case MYSQL_TYPE_DOUBLE: + { + double *value= (double *)r_param->buffer; + float8get(*value, *row); + r_param->buffer_length= 8; + } + break; + default: + { + double value; + float8get(value, *row); + convert_from_double(r_param, field, value, sizeof(double)); + } + break; + } + (*row)+= 8; +} +/* }}} */ + +/* {{{ ps_fetch_float */ +static +void ps_fetch_float(MYSQL_BIND *r_param, const MYSQL_FIELD * field, unsigned char **row) +{ + switch(r_param->buffer_type) + { + case MYSQL_TYPE_FLOAT: + { + float *value= (float *)r_param->buffer; + float4get(*value, *row); + r_param->buffer_length= 4; + *r_param->error= 0; + } + break; + default: + { + float value; + memcpy(&value, *row, sizeof(float)); + float4get(value, (char *)*row); + convert_from_float(r_param, field, value, sizeof(float)); + } + break; + } + (*row)+= 4; +} +/* }}} */ + +static void convert_to_datetime(MYSQL_TIME *t, unsigned char **row, uint len, enum enum_field_types type) +{ + memset(t, 0, sizeof(MYSQL_TIME)); + + /* binary protocol for datetime: + 4-bytes: DATE + 7-bytes: DATE + TIME + >7 bytes: DATE + TIME with second_part + */ + if (len) + { + unsigned char *to= *row; + int has_date= 0; + uint offset= 7; + + if (type == MYSQL_TYPE_TIME) + { + t->neg= to[0]; + t->day= (ulong) sint4korr(to + 1); + t->time_type= MYSQL_TIMESTAMP_TIME; + offset= 8; + to++; + } else + { + t->year= (uint) sint2korr(to); + t->month= (uint) to[2]; + t->day= (uint) to[3]; + t->time_type= MYSQL_TIMESTAMP_DATE; + if (type == MYSQL_TYPE_DATE) + return; + has_date= 1; + } + + if (len > 4) + { + t->hour= (uint) to[4]; + if (type == MYSQL_TYPE_TIME) + t->hour+= t->day * 24; + t->minute= (uint) to[5]; + t->second= (uint) to[6]; + if (has_date) + t->time_type= MYSQL_TIMESTAMP_DATETIME; + } + if (len > offset) + { + t->second_part= (ulong)sint4korr(to+7); + } + } +} + + +/* {{{ ps_fetch_datetime */ +static +void ps_fetch_datetime(MYSQL_BIND *r_param, const MYSQL_FIELD * field, + unsigned char **row) +{ + MYSQL_TIME *t= (MYSQL_TIME *)r_param->buffer; + unsigned int len= net_field_length(row); + + switch (r_param->buffer_type) { + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + convert_to_datetime(t, row, len, field->type); + break; + case MYSQL_TYPE_DATE: + convert_to_datetime(t, row, len, field->type); + break; + case MYSQL_TYPE_TIME: + convert_to_datetime(t, row, len, field->type); + t->year= t->day= t->month= 0; + break; + case MYSQL_TYPE_YEAR: + { + MYSQL_TIME tm; + convert_to_datetime(&tm, row, len, field->type); + shortstore(r_param->buffer, tm.year); + break; + } + default: + { + char dtbuffer[60]; + MYSQL_TIME tm; + size_t length; + convert_to_datetime(&tm, row, len, field->type); + + switch(field->type) { + case MYSQL_TYPE_DATE: + length= sprintf(dtbuffer, "%04u-%02u-%02u", tm.year, tm.month, tm.day); + break; + case MYSQL_TYPE_TIME: + length= sprintf(dtbuffer, "%s%02u:%02u:%02u", (tm.neg ? "-" : ""), tm.hour, tm.minute, tm.second); + if (field->decimals && field->decimals <= 6) + { + char ms[8]; + sprintf(ms, ".%06lu", tm.second_part); + if (field->decimals < 6) + ms[field->decimals + 1]= 0; + length+= strlen(ms); + strcat(dtbuffer, ms); + } + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + length= sprintf(dtbuffer, "%04u-%02u-%02u %02u:%02u:%02u", tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second); + if (field->decimals && field->decimals <= 6) + { + char ms[8]; + sprintf(ms, ".%06lu", tm.second_part); + if (field->decimals < 6) + ms[field->decimals + 1]= 0; + length+= strlen(ms); + strcat(dtbuffer, ms); + } + break; + default: + dtbuffer[0]= 0; + length= 0; + break; + } + convert_froma_string(r_param, dtbuffer, length); + break; + } + } + (*row) += len; +} +/* }}} */ + +/* {{{ ps_fetch_string */ +static +void ps_fetch_string(MYSQL_BIND *r_param, + const MYSQL_FIELD *field __attribute__((unused)), + unsigned char **row) +{ + /* C-API differs from PHP. While PHP just converts string to string, + C-API needs to convert the string to the defined type with in + the result bind buffer. + */ + ulong field_length= net_field_length(row); + + convert_froma_string(r_param, (char *)*row, field_length); + (*row) += field_length; +} +/* }}} */ + +/* {{{ ps_fetch_bin */ +static +void ps_fetch_bin(MYSQL_BIND *r_param, + const MYSQL_FIELD *field, + unsigned char **row) +{ + if (field->charsetnr == 63) + { + ulong field_length= *r_param->length= net_field_length(row); + uchar *current_pos= (*row) + r_param->offset, + *end= (*row) + field_length; + size_t copylen= 0; + + if (current_pos < end) + { + copylen= end - current_pos; + if (r_param->buffer_length) + memcpy(r_param->buffer, current_pos, MIN(copylen, r_param->buffer_length)); + } + if (copylen < r_param->buffer_length && + (r_param->buffer_type == MYSQL_TYPE_STRING || + r_param->buffer_type == MYSQL_TYPE_JSON)) + ((char *)r_param->buffer)[copylen]= 0; + *r_param->error= copylen > r_param->buffer_length; + (*row)+= field_length; + } + else + ps_fetch_string(r_param, field, row); +} +/* }}} */ + +/* {{{ _mysqlnd_init_ps_subsystem */ +void mysql_init_ps_subsystem(void) +{ + memset(mysql_ps_fetch_functions, 0, sizeof(mysql_ps_fetch_functions)); + mysql_ps_fetch_functions[MYSQL_TYPE_NULL].func= ps_fetch_null; + mysql_ps_fetch_functions[MYSQL_TYPE_NULL].pack_len = 0; + mysql_ps_fetch_functions[MYSQL_TYPE_NULL].max_len = 0; + + mysql_ps_fetch_functions[MYSQL_TYPE_TINY].func = ps_fetch_int8; + mysql_ps_fetch_functions[MYSQL_TYPE_TINY].pack_len = 1; + mysql_ps_fetch_functions[MYSQL_TYPE_TINY].max_len = 4; + + mysql_ps_fetch_functions[MYSQL_TYPE_SHORT].func = ps_fetch_int16; + mysql_ps_fetch_functions[MYSQL_TYPE_SHORT].pack_len = 2; + mysql_ps_fetch_functions[MYSQL_TYPE_SHORT].max_len = 6; + + mysql_ps_fetch_functions[MYSQL_TYPE_YEAR].func = ps_fetch_int16; + mysql_ps_fetch_functions[MYSQL_TYPE_YEAR].pack_len = 2; + mysql_ps_fetch_functions[MYSQL_TYPE_YEAR].max_len = 4; + + mysql_ps_fetch_functions[MYSQL_TYPE_INT24].func = ps_fetch_int32; + mysql_ps_fetch_functions[MYSQL_TYPE_INT24].pack_len = 4; + mysql_ps_fetch_functions[MYSQL_TYPE_INT24].max_len = 8; + + mysql_ps_fetch_functions[MYSQL_TYPE_LONG].func = ps_fetch_int32; + mysql_ps_fetch_functions[MYSQL_TYPE_LONG].pack_len = 4; + mysql_ps_fetch_functions[MYSQL_TYPE_LONG].max_len = 11; + + mysql_ps_fetch_functions[MYSQL_TYPE_LONGLONG].func = ps_fetch_int64; + mysql_ps_fetch_functions[MYSQL_TYPE_LONGLONG].pack_len= 8; + mysql_ps_fetch_functions[MYSQL_TYPE_LONGLONG].max_len = 20; + + mysql_ps_fetch_functions[MYSQL_TYPE_FLOAT].func = ps_fetch_float; + mysql_ps_fetch_functions[MYSQL_TYPE_FLOAT].pack_len = 4; + mysql_ps_fetch_functions[MYSQL_TYPE_FLOAT].max_len = MAX_DOUBLE_STRING_REP_LENGTH; + + mysql_ps_fetch_functions[MYSQL_TYPE_DOUBLE].func = ps_fetch_double; + mysql_ps_fetch_functions[MYSQL_TYPE_DOUBLE].pack_len = 8; + mysql_ps_fetch_functions[MYSQL_TYPE_DOUBLE].max_len = MAX_DOUBLE_STRING_REP_LENGTH; + + mysql_ps_fetch_functions[MYSQL_TYPE_TIME].func = ps_fetch_datetime; + mysql_ps_fetch_functions[MYSQL_TYPE_TIME].pack_len = MYSQL_PS_SKIP_RESULT_W_LEN; + mysql_ps_fetch_functions[MYSQL_TYPE_TIME].max_len = 17; + + mysql_ps_fetch_functions[MYSQL_TYPE_DATE].func = ps_fetch_datetime; + mysql_ps_fetch_functions[MYSQL_TYPE_DATE].pack_len = MYSQL_PS_SKIP_RESULT_W_LEN; + mysql_ps_fetch_functions[MYSQL_TYPE_DATE].max_len = 10; + + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDATE].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDATE].pack_len = MYSQL_PS_SKIP_RESULT_W_LEN; + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDATE].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_DATETIME].func = ps_fetch_datetime; + mysql_ps_fetch_functions[MYSQL_TYPE_DATETIME].pack_len= MYSQL_PS_SKIP_RESULT_W_LEN; + mysql_ps_fetch_functions[MYSQL_TYPE_DATETIME].max_len = 30; + + mysql_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].func = ps_fetch_datetime; + mysql_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].pack_len= MYSQL_PS_SKIP_RESULT_W_LEN; + mysql_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].max_len = 30; + + mysql_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].func = ps_fetch_bin; + mysql_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].pack_len= MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_BLOB].func = ps_fetch_bin; + mysql_ps_fetch_functions[MYSQL_TYPE_BLOB].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_BLOB].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].func = ps_fetch_bin; + mysql_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].pack_len= MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].func = ps_fetch_bin; + mysql_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_BIT].func = ps_fetch_bin; + mysql_ps_fetch_functions[MYSQL_TYPE_BIT].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_BIT].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_VARCHAR].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_VARCHAR].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_VARCHAR].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_STRING].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_STRING].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_STRING].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_JSON].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_JSON].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_JSON].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_DECIMAL].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_DECIMAL].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_DECIMAL].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_ENUM].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_ENUM].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_ENUM].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_SET].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_SET].pack_len = MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_SET].max_len = -1; + + mysql_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].func = ps_fetch_string; + mysql_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].pack_len= MYSQL_PS_SKIP_RESULT_STR; + mysql_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].max_len = -1; + + mysql_ps_subsystem_initialized= 1; +} +/* }}} */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/libmariadb/libmariadb/ma_string.c b/libmariadb/libmariadb/ma_string.c new file mode 100644 index 00000000..9a62e06e --- /dev/null +++ b/libmariadb/libmariadb/ma_string.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +/* + Code for handling strings which can grow dynamically. + Copyright Monty Program KB. + By monty. +*/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_string.h> + +my_bool ma_init_dynamic_string(DYNAMIC_STRING *str, const char *init_str, + size_t init_alloc, size_t alloc_increment) +{ + uint length; + + if (!alloc_increment) + alloc_increment=128; + length=1; + if (init_str && (length= (uint) strlen(init_str)+1) < init_alloc) + init_alloc=((length+alloc_increment-1)/alloc_increment)*alloc_increment; + if (!init_alloc) + init_alloc=alloc_increment; + + if (!(str->str=(char*) malloc(init_alloc))) + return(TRUE); + str->length=length-1; + if (init_str) + memcpy(str->str,init_str,length); + str->max_length=init_alloc; + str->alloc_increment=alloc_increment; + return(FALSE); +} + +my_bool ma_dynstr_set(DYNAMIC_STRING *str, const char *init_str) +{ + uint length; + + if (init_str && (length= (uint) strlen(init_str)+1) > str->max_length) + { + str->max_length=((length+str->alloc_increment-1)/str->alloc_increment)* + str->alloc_increment; + if (!str->max_length) + str->max_length=str->alloc_increment; + if (!(str->str=(char*) realloc(str->str,str->max_length))) + return(TRUE); + } + if (init_str) + { + str->length=length-1; + memcpy(str->str,init_str,length); + } + else + str->length=0; + return(FALSE); +} + + +my_bool ma_dynstr_realloc(DYNAMIC_STRING *str, size_t additional_size) +{ + if (!additional_size) return(FALSE); + if (str->length + additional_size > str->max_length) + { + str->max_length=((str->length + additional_size+str->alloc_increment-1)/ + str->alloc_increment)*str->alloc_increment; + if (!(str->str=(char*) realloc(str->str,str->max_length))) + return(TRUE); + } + return(FALSE); +} + + +my_bool ma_dynstr_append(DYNAMIC_STRING *str, const char *append) +{ + return ma_dynstr_append_mem(str,append,strlen(append)); +} + +my_bool ma_dynstr_append_quoted(DYNAMIC_STRING *str, + const char *append, size_t len, + char quote) +{ + size_t additional= str->alloc_increment; + size_t lim= additional; + uint i; + + if (ma_dynstr_realloc(str, len + additional + 2)) + return TRUE; + str->str[str->length++]= quote; + for (i= 0; i < len; i++) + { + register char c= append[i]; + if (c == quote || c == '\\') + { + if (!lim) + { + if (ma_dynstr_realloc(str, additional)) + return TRUE; + lim= additional; + } + lim--; + str->str[str->length++]= '\\'; + } + str->str[str->length++]= c; + } + str->str[str->length++]= quote; + return FALSE; +} + +my_bool ma_dynstr_append_mem(DYNAMIC_STRING *str, const char *append, + size_t length) +{ + char *new_ptr; + if (str->length+length >= str->max_length) + { + size_t new_length=(str->length+length+str->alloc_increment)/ + str->alloc_increment; + new_length*=str->alloc_increment; + if (!(new_ptr=(char*) realloc(str->str,new_length))) + return TRUE; + str->str=new_ptr; + str->max_length=new_length; + } + memcpy(str->str + str->length,append,length); + str->length+=length; + str->str[str->length]=0; /* Safety for C programs */ + return FALSE; +} + + +void ma_dynstr_free(DYNAMIC_STRING *str) +{ + if (str->str) + { + free(str->str); + str->str=0; + } +} + +char *ma_strmake(register char *dst, register const char *src, size_t length) +{ + while (length--) + if (! (*dst++ = *src++)) + return dst-1; + *dst=0; + return dst; +} diff --git a/libmariadb/libmariadb/ma_time.c b/libmariadb/libmariadb/ma_time.c new file mode 100644 index 00000000..460c32d4 --- /dev/null +++ b/libmariadb/libmariadb/ma_time.c @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (C) 2013 Monty Program AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*****************************************************************************/ +#include <ma_global.h> +#include <mysql.h> +#include <stdio.h> + + +size_t mariadb_time_to_string(const MYSQL_TIME *tm, char *time_str, size_t len, + unsigned int digits) +{ + size_t length; + + if (!time_str || !len) + return 0; + + if (digits == AUTO_SEC_PART_DIGITS) + digits= (tm->second_part) ? SEC_PART_DIGITS : 0; + + switch(tm->time_type) { + case MYSQL_TIMESTAMP_DATE: + length= snprintf(time_str, len, "%04u-%02u-%02u", tm->year, tm->month, tm->day); + digits= 0; + break; + case MYSQL_TIMESTAMP_DATETIME: + length= snprintf(time_str, len, "%04u-%02u-%02u %02u:%02u:%02u", + tm->year, tm->month, tm->day, tm->hour, tm->minute, tm->second); + break; + case MYSQL_TIMESTAMP_TIME: + length= snprintf(time_str, len, "%s%02u:%02u:%02u", + (tm->neg ? "-" : ""), tm->hour, tm->minute, tm->second); + break; + default: + time_str[0]= '\0'; + return 0; + break; + } + if (digits && (len < length)) + { + char helper[16]; + snprintf(helper, 16, ".%%0%du", digits); + length+= snprintf(time_str + length, len - length, helper, digits); + } + return length; +} + diff --git a/libmariadb/libmariadb/ma_tls.c b/libmariadb/libmariadb/ma_tls.c new file mode 100644 index 00000000..3f48ad8b --- /dev/null +++ b/libmariadb/libmariadb/ma_tls.c @@ -0,0 +1,237 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ + +/* + * this is the abstraction layer for communication via SSL. + * The following SSL libraries/variants are currently supported: + * - openssl + * - gnutls + * - schannel (windows only) + * + * Different SSL variants are implemented as plugins + * On Windows schannel is implemented as (standard) + * built-in plugin. + */ + +#ifdef HAVE_TLS + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <string.h> +#include <errmsg.h> +#include <ma_pvio.h> +#include <ma_tls.h> +#include <mysql/client_plugin.h> +#include <mariadb/ma_io.h> + +#ifdef HAVE_NONBLOCK +#include <mariadb_async.h> +#include <ma_context.h> +#endif + +/* Errors should be handled via pvio callback function */ +my_bool ma_tls_initialized= FALSE; +unsigned int mariadb_deinitialize_ssl= 1; + +const char *tls_protocol_version[]= + {"SSLv3", "TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3", "Unknown"}; + +MARIADB_TLS *ma_pvio_tls_init(MYSQL *mysql) +{ + MARIADB_TLS *ctls= NULL; + + if (!ma_tls_initialized) + ma_tls_start(mysql->net.last_error, MYSQL_ERRMSG_SIZE); + + if (!(ctls= (MARIADB_TLS *)calloc(1, sizeof(MARIADB_TLS)))) + { + return NULL; + } + + /* register error routine and methods */ + ctls->pvio= mysql->net.pvio; + if (!(ctls->ssl= ma_tls_init(mysql))) + { + free(ctls); + ctls= NULL; + } + return ctls; +} + +my_bool ma_pvio_tls_connect(MARIADB_TLS *ctls) +{ + my_bool rc; + + if ((rc= ma_tls_connect(ctls))) + ma_tls_close(ctls); + return rc; +} + +ssize_t ma_pvio_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + return ma_tls_read(ctls, buffer, length); +} + +ssize_t ma_pvio_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + return ma_tls_write(ctls, buffer, length); +} + +my_bool ma_pvio_tls_close(MARIADB_TLS *ctls) +{ + return ma_tls_close(ctls); +} + +int ma_pvio_tls_verify_server_cert(MARIADB_TLS *ctls) +{ + return ma_tls_verify_server_cert(ctls); +} + +const char *ma_pvio_tls_cipher(MARIADB_TLS *ctls) +{ + return ma_tls_get_cipher(ctls); +} + +void ma_pvio_tls_end() +{ + ma_tls_end(); +} + +int ma_pvio_tls_get_protocol_version_id(MARIADB_TLS *ctls) +{ + return ma_tls_get_protocol_version(ctls); +} + +const char *ma_pvio_tls_get_protocol_version(MARIADB_TLS *ctls) +{ + int version; + + version= ma_tls_get_protocol_version(ctls); + if (version < 0 || version > PROTOCOL_MAX) + return tls_protocol_version[PROTOCOL_UNKNOWN]; + return tls_protocol_version[version]; +} + +static signed char ma_hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + return -1; +} + +static my_bool ma_pvio_tls_compare_fp(const char *cert_fp, + unsigned int cert_fp_len, + const char *fp, unsigned int fp_len) +{ + char *p= (char *)fp, + *c; + + /* check length */ + if (cert_fp_len != 20) + return 1; + + /* We support two formats: + 2 digits hex numbers, separated by colons (length=59) + 20 * 2 digits hex numbers without separators (length = 40) + */ + if (fp_len != (strchr(fp, ':') ? 59 : 40)) + return 1; + + for(c= (char *)cert_fp; c < cert_fp + cert_fp_len; c++) + { + signed char d1, d2; + if (*p == ':') + p++; + if (p - fp > (int)fp_len -1) + return 1; + if ((d1 = ma_hex2int(*p)) == - 1 || + (d2 = ma_hex2int(*(p+1))) == -1 || + (char)(d1 * 16 + d2) != *c) + return 1; + p+= 2; + } + return 0; +} + +my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_list) +{ + unsigned int cert_fp_len= 64; + char *cert_fp= NULL; + my_bool rc=1; + MYSQL *mysql= ctls->pvio->mysql; + + cert_fp= (char *)malloc(cert_fp_len); + + if ((cert_fp_len= ma_tls_get_finger_print(ctls, cert_fp, cert_fp_len)) < 1) + goto end; + if (fp) + rc= ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, fp, (unsigned int)strlen(fp)); + else if (fp_list) + { + MA_FILE *fp; + char buff[255]; + + if (!(fp = ma_open(fp_list, "r", mysql))) + goto end; + + while (ma_gets(buff, sizeof(buff)-1, fp)) + { + /* remove trailing new line character */ + char *pos= strchr(buff, '\r'); + if (!pos) + pos= strchr(buff, '\n'); + if (pos) + *pos= '\0'; + + if (!ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, buff, (unsigned int)strlen(buff))) + { + /* finger print is valid: close file and exit */ + ma_close(fp); + rc= 0; + goto end; + } + } + + /* No finger print matched - close file and return error */ + ma_close(fp); + } + +end: + if (cert_fp) + free(cert_fp); + if (rc) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Fingerprint verification of server certificate failed"); + } + return rc; +} + +void ma_pvio_tls_set_connection(MYSQL *mysql) +{ + ma_tls_set_connection(mysql); +} +#endif /* HAVE_TLS */ diff --git a/libmariadb/libmariadb/mariadb_async.c b/libmariadb/libmariadb/mariadb_async.c new file mode 100644 index 00000000..feb73329 --- /dev/null +++ b/libmariadb/libmariadb/mariadb_async.c @@ -0,0 +1,1946 @@ +/* Copyright (C) 2012 MariaDB Services and Kristian Nielsen + 2015 MariaDB Corporation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +*/ + +/* + MySQL non-blocking client library functions. +*/ + +#include "ma_global.h" +#include "ma_sys.h" +#include "mysql.h" +#include "errmsg.h" +#ifndef LIBMARIADB +#include "sql_common.h" +#else +#include "ma_common.h" +#endif +#include "ma_context.h" +#include "ma_pvio.h" +#include "mariadb_async.h" +#include <string.h> + + +#ifdef _WIN32 +/* + Windows does not support MSG_DONTWAIT for send()/recv(). So we need to ensure + that the socket is non-blocking at the start of every operation. +*/ +#define WIN_SET_NONBLOCKING(mysql) do { \ + my_bool old_mode; \ + if ((mysql)->net.pvio) ma_pvio_blocking((mysql)->net.pvio, FALSE, &old_mode); \ + } while(0); +#else +#define WIN_SET_NONBLOCKING(mysql) +#endif + +extern void mysql_close_slow_part(MYSQL *mysql); + + +void +my_context_install_suspend_resume_hook(struct mysql_async_context *b, + void (*hook)(my_bool, void *), + void *user_data) +{ + b->suspend_resume_hook= hook; + b->suspend_resume_hook_user_data= user_data; +} + + +/* Asynchronous connect(); socket must already be set non-blocking. */ +int +my_connect_async(MARIADB_PVIO *pvio, + const struct sockaddr *name, uint namelen, int vio_timeout) +{ + int res; + size_socket s_err_size; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + my_socket sock; + + ma_pvio_get_handle(pvio, &sock); + + /* Make the socket non-blocking. */ + ma_pvio_blocking(pvio, 0, 0); + + b->events_to_wait_for= 0; + /* + Start to connect asynchronously. + If this will block, we suspend the call and return control to the + application context. The application will then resume us when the socket + polls ready for write, indicating that the connection attempt completed. + */ + res= connect(sock, name, namelen); + if (res != 0) + { +#ifdef _WIN32 + int wsa_err= WSAGetLastError(); + if (wsa_err != WSAEWOULDBLOCK) + return res; + b->events_to_wait_for|= MYSQL_WAIT_EXCEPT; +#else + int err= errno; + if (err != EINPROGRESS && err != EALREADY && err != EAGAIN) + return res; +#endif + b->events_to_wait_for|= MYSQL_WAIT_WRITE; + if (vio_timeout >= 0) + { + b->timeout_value= vio_timeout; + b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT; + } + else + b->timeout_value= 0; + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + if (b->events_occurred & MYSQL_WAIT_TIMEOUT) + return -1; + + s_err_size= sizeof(res); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*) &res, &s_err_size) != 0) + return -1; + if (res) + { + errno= res; + return -1; + } + } + return res; +} + +#define IS_BLOCKING_ERROR() \ + IF_WIN(WSAGetLastError() != WSAEWOULDBLOCK, \ + (errno != EAGAIN && errno != EINTR)) + +#ifdef _AIX +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif +#endif + +#ifdef HAVE_TLS_FIXME +static my_bool +my_ssl_async_check_result(int res, struct mysql_async_context *b, MARIADB_SSL *cssl) +{ + int ssl_err; + b->events_to_wait_for= 0; + if (res >= 0) + return 1; + ssl_err= SSL_get_error(ssl, res); + if (ssl_err == SSL_ERROR_WANT_READ) + b->events_to_wait_for|= MYSQL_WAIT_READ; + else if (ssl_err == SSL_ERROR_WANT_WRITE) + b->events_to_wait_for|= MYSQL_WAIT_WRITE; + else + return 1; + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + return 0; +} + +int +my_ssl_read_async(struct mysql_async_context *b, SSL *ssl, + void *buf, int size) +{ + int res; + + for (;;) + { + res= SSL_read(ssl, buf, size); + if (my_ssl_async_check_result(res, b, ssl)) + return res; + } +} + +int +my_ssl_write_async(struct mysql_async_context *b, SSL *ssl, + const void *buf, int size) +{ + int res; + + for (;;) + { + res= SSL_write(ssl, buf, size); + if (my_ssl_async_check_result(res, b, ssl)) + return res; + } +} +#endif /* HAVE_OPENSSL */ + + + + +/* + Now create non-blocking definitions for all the calls that may block. + + Each call FOO gives rise to FOO_start() that prepares the MYSQL object for + doing non-blocking calls that can suspend operation mid-way, and then starts + the call itself. And a FOO_start_internal trampoline to assist with running + the real call in a co-routine that can be suspended. And a FOO_cont() that + can continue a suspended operation. +*/ + +#define MK_ASYNC_INTERNAL_BODY(call, invoke_args, mysql_val, ret_type, ok_val)\ + struct call ## _params *parms= (struct call ## _params *)d; \ + ret_type ret; \ + struct mysql_async_context *b= \ + (mysql_val)->options.extension->async_context; \ + \ + ret= call invoke_args; \ + b->ret_result. ok_val = ret; \ + b->events_to_wait_for= 0; + +#define MK_ASYNC_START_BODY(call, mysql_val, parms_assign, err_val, ok_val, extra1) \ + int res; \ + struct mysql_async_context *b; \ + struct call ## _params parms; \ + \ + extra1 \ + b= mysql_val->options.extension->async_context; \ + parms_assign \ + \ + b->active= 1; \ + res= my_context_spawn(&b->async_context, call ## _start_internal, &parms); \ + b->active= b->suspended= 0; \ + if (res > 0) \ + { \ + /* Suspended. */ \ + b->suspended= 1; \ + return b->events_to_wait_for; \ + } \ + if (res < 0) \ + { \ + set_mariadb_error((mysql_val), CR_OUT_OF_MEMORY, unknown_sqlstate); \ + *ret= err_val; \ + } \ + else \ + *ret= b->ret_result. ok_val; \ + return 0; + +#define MK_ASYNC_CONT_BODY(mysql_val, err_val, ok_val) \ + int res; \ + struct mysql_async_context *b= \ + (mysql_val)->options.extension->async_context; \ + if (!b->suspended) \ + { \ + set_mariadb_error((mysql_val), CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); \ + *ret= err_val; \ + return 0; \ + } \ + \ + b->active= 1; \ + b->events_occurred= ready_status; \ + res= my_context_continue(&b->async_context); \ + b->active= 0; \ + if (res > 0) \ + return b->events_to_wait_for; /* (Still) suspended */ \ + b->suspended= 0; \ + if (res < 0) \ + { \ + set_mariadb_error((mysql_val), CR_OUT_OF_MEMORY, unknown_sqlstate); \ + *ret= err_val; \ + } \ + else \ + *ret= b->ret_result. ok_val; /* Finished. */ \ + return 0; + +#define MK_ASYNC_INTERNAL_BODY_VOID_RETURN(call, invoke_args, mysql_val) \ + struct call ## _params *parms= (struct call ## _params *)d; \ + struct mysql_async_context *b= \ + (mysql_val)->options.extension->async_context; \ + \ + call invoke_args; \ + b->events_to_wait_for= 0; + +#define MK_ASYNC_START_BODY_VOID_RETURN(call, mysql_val, parms_assign, extra1)\ + int res; \ + struct mysql_async_context *b; \ + struct call ## _params parms; \ + \ + extra1 \ + b= mysql_val->options.extension->async_context; \ + parms_assign \ + \ + b->active= 1; \ + res= my_context_spawn(&b->async_context, call ## _start_internal, &parms); \ + b->active= b->suspended= 0; \ + if (res > 0) \ + { \ + /* Suspended. */ \ + b->suspended= 1; \ + return b->events_to_wait_for; \ + } \ + if (res < 0) \ + set_mariadb_error((mysql_val), CR_OUT_OF_MEMORY, unknown_sqlstate); \ + return 0; + +#define MK_ASYNC_CONT_BODY_VOID_RETURN(mysql_val) \ + int res; \ + struct mysql_async_context *b= \ + (mysql_val)->options.extension->async_context; \ + if (!b->suspended) \ + { \ + set_mariadb_error((mysql_val), CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); \ + return 0; \ + } \ + \ + b->active= 1; \ + b->events_occurred= ready_status; \ + res= my_context_continue(&b->async_context); \ + b->active= 0; \ + if (res > 0) \ + return b->events_to_wait_for; /* (Still) suspended */ \ + b->suspended= 0; \ + if (res < 0) \ + set_mariadb_error((mysql_val), CR_OUT_OF_MEMORY, unknown_sqlstate); \ + return 0; + + +/* Structure used to pass parameters from mysql_real_connect_start(). */ +struct mysql_real_connect_params { + MYSQL *mysql; + const char *host; + const char *user; + const char *passwd; + const char *db; + unsigned int port; + const char *unix_socket; + unsigned long client_flags; +}; +static void +mysql_real_connect_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_real_connect, + (parms->mysql, parms->host, parms->user, parms->passwd, parms->db, + parms->port, parms->unix_socket, parms->client_flags), + parms->mysql, + MYSQL *, + r_ptr) +} +int STDCALL +mysql_real_connect_start(MYSQL **ret, MYSQL *mysql, const char *host, + const char *user, const char *passwd, const char *db, + unsigned int port, const char *unix_socket, + unsigned long client_flags) +{ +MK_ASYNC_START_BODY( + mysql_real_connect, + mysql, + { + parms.mysql= mysql; + parms.host= host; + parms.user= user; + parms.passwd= passwd; + parms.db= db; + parms.port= port; + parms.unix_socket= unix_socket; + parms.client_flags= client_flags | CLIENT_REMEMBER_OPTIONS; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_real_connect_cont(MYSQL **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_real_query_start(). */ +struct mysql_real_query_params { + MYSQL *mysql; + const char *stmt_str; + unsigned long length; +}; +static void +mysql_real_query_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_real_query, + (parms->mysql, parms->stmt_str, parms->length), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_real_query_start(int *ret, MYSQL *mysql, const char *stmt_str, unsigned long length) +{ + int res; + struct mysql_async_context *b; + struct mysql_real_query_params parms; + + b= mysql->options.extension->async_context; + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.stmt_str= stmt_str; + parms.length= length; + } + + b->active= 1; + res= my_context_spawn(&b->async_context, mysql_real_query_start_internal, &parms); + b->active= b->suspended= 0; + if (res > 0) + { + /* Suspended. */ + b->suspended= 1; + return b->events_to_wait_for; + } + if (res < 0) + { + set_mariadb_error((mysql), CR_OUT_OF_MEMORY, unknown_sqlstate); + *ret= 1; + } + else + *ret= b->ret_result.r_int; + return 0; + +} +int STDCALL +mysql_real_query_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_fetch_row_start(). */ +struct mysql_fetch_row_params { + MYSQL_RES *result; +}; +static void +mysql_fetch_row_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_fetch_row, + (parms->result), + parms->result->handle, + MYSQL_ROW, + r_ptr) +} +int STDCALL +mysql_fetch_row_start(MYSQL_ROW *ret, MYSQL_RES *result) +{ +MK_ASYNC_START_BODY( + mysql_fetch_row, + result->handle, + { + WIN_SET_NONBLOCKING(result->handle) + parms.result= result; + }, + NULL, + r_ptr, + /* + If we already fetched all rows from server (eg. mysql_store_result()), + then result->handle will be NULL and we cannot suspend. But that is fine, + since in this case mysql_fetch_row cannot block anyway. Just return + directly. + */ + if (!result->handle) + { + *ret= mysql_fetch_row(result); + return 0; + }) +} +int STDCALL +mysql_fetch_row_cont(MYSQL_ROW *ret, MYSQL_RES *result, int ready_status) +{ +MK_ASYNC_CONT_BODY( + result->handle, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_set_character_set_start(). */ +struct mysql_set_character_set_params { + MYSQL *mysql; + const char *csname; +}; +static void +mysql_set_character_set_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_set_character_set, + (parms->mysql, parms->csname), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_set_character_set_start(int *ret, MYSQL *mysql, const char *csname) +{ +MK_ASYNC_START_BODY( + mysql_set_character_set, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.csname= csname; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_set_character_set_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_sekect_db_start(). */ +struct mysql_select_db_params { + MYSQL *mysql; + const char *db; +}; +static void +mysql_select_db_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_select_db, + (parms->mysql, parms->db), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_select_db_start(int *ret, MYSQL *mysql, const char *db) +{ +MK_ASYNC_START_BODY( + mysql_select_db, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.db= db; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_select_db_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_send_query_start(). */ +struct mysql_send_query_params { + MYSQL *mysql; + const char *q; + unsigned long length; +}; +static void +mysql_send_query_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_send_query, + (parms->mysql, parms->q, parms->length), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_send_query_start(int *ret, MYSQL *mysql, const char *q, unsigned long length) +{ +MK_ASYNC_START_BODY( + mysql_send_query, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.q= q; + parms.length= length; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_send_query_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_store_result_start(). */ +struct mysql_store_result_params { + MYSQL *mysql; +}; +static void +mysql_store_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_store_result, + (parms->mysql), + parms->mysql, + MYSQL_RES *, + r_ptr) +} +int STDCALL +mysql_store_result_start(MYSQL_RES **ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_store_result, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_store_result_cont(MYSQL_RES **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_free_result_start(). */ +struct mysql_free_result_params { + MYSQL_RES *result; +}; +static void +mysql_free_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY_VOID_RETURN( + mysql_free_result, + (parms->result), + parms->result->handle) +} +int STDCALL +mysql_free_result_start(MYSQL_RES *result) +{ +MK_ASYNC_START_BODY_VOID_RETURN( + mysql_free_result, + result->handle, + { + WIN_SET_NONBLOCKING(result->handle) + parms.result= result; + }, + /* + mysql_free_result() can have NULL in result->handle (this happens when all + rows have been fetched and mysql_fetch_row() returned NULL.) + So we cannot suspend, but it does not matter, as in this case + mysql_free_result() cannot block. + It is also legitimate to have NULL result, which will do nothing. + */ + if (!result || !result->handle) + { + mysql_free_result(result); + return 0; + }) +} +int STDCALL +mysql_free_result_cont(MYSQL_RES *result, int ready_status) +{ +MK_ASYNC_CONT_BODY_VOID_RETURN(result->handle) +} + +/* Structure used to pass parameters from mysql_close_slow_part_start(). */ +struct mysql_close_slow_part_params { + MYSQL *sock; +}; +/* + We need special handling for mysql_close(), as the first part may block, + while the last part needs to free our extra library context stack. + + So we do the first part (mysql_close_slow_part()) non-blocking, but the last + part blocking. +*/ +static void +mysql_close_slow_part_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY_VOID_RETURN( + mysql_close_slow_part, + (parms->sock), + parms->sock) +} + +int STDCALL +mysql_close_slow_part_start(MYSQL *sock) +{ +MK_ASYNC_START_BODY_VOID_RETURN( + mysql_close_slow_part, + sock, + { + WIN_SET_NONBLOCKING(sock) + parms.sock= sock; + }, + /* Nothing */) +} +int STDCALL +mysql_close_slow_part_cont(MYSQL *sock, int ready_status) +{ +MK_ASYNC_CONT_BODY_VOID_RETURN(sock) +} +int STDCALL +mysql_close_start(MYSQL *sock) +{ + int res; + + /* It is legitimate to have NULL sock argument, which will do nothing. */ + if (sock && sock->net.pvio) + { + res= mysql_close_slow_part_start(sock); + /* If we need to block, return now and do the rest in mysql_close_cont(). */ + if (res) + return res; + } + mysql_close(sock); + return 0; +} +int STDCALL +mysql_close_cont(MYSQL *sock, int ready_status) +{ + int res; + + res= mysql_close_slow_part_cont(sock, ready_status); + if (res) + return res; + mysql_close(sock); + return 0; +} + +/* + These following are not available inside the server (neither blocking or + non-blocking). +*/ +#ifndef MYSQL_SERVER +/* Structure used to pass parameters from mysql_change_user_start(). */ +struct mysql_change_user_params { + MYSQL *mysql; + const char *user; + const char *passwd; + const char *db; +}; +static void +mysql_change_user_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_change_user, + (parms->mysql, parms->user, parms->passwd, parms->db), + parms->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_change_user_start(my_bool *ret, MYSQL *mysql, const char *user, const char *passwd, const char *db) +{ +MK_ASYNC_START_BODY( + mysql_change_user, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.user= user; + parms.passwd= passwd; + parms.db= db; + }, + TRUE, + r_my_bool, + /* Nothing */) +} +int STDCALL +mysql_change_user_cont(my_bool *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_query_start(). */ +struct mysql_query_params { + MYSQL *mysql; + const char *q; +}; +static void +mysql_query_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_query, + (parms->mysql, parms->q), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_query_start(int *ret, MYSQL *mysql, const char *q) +{ +MK_ASYNC_START_BODY( + mysql_query, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.q= q; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_query_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_shutdown_start(). */ +struct mysql_shutdown_params { + MYSQL *mysql; + enum mysql_enum_shutdown_level shutdown_level; +}; +static void +mysql_shutdown_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_shutdown, + (parms->mysql, parms->shutdown_level), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_shutdown_start(int *ret, MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level) +{ +MK_ASYNC_START_BODY( + mysql_shutdown, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.shutdown_level= shutdown_level; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_shutdown_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_dump_debug_info_start(). */ +struct mysql_dump_debug_info_params { + MYSQL *mysql; +}; +static void +mysql_dump_debug_info_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_dump_debug_info, + (parms->mysql), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_dump_debug_info_start(int *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_dump_debug_info, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_dump_debug_info_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_refresh_start(). */ +struct mysql_refresh_params { + MYSQL *mysql; + unsigned int refresh_options; +}; +static void +mysql_refresh_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_refresh, + (parms->mysql, parms->refresh_options), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_refresh_start(int *ret, MYSQL *mysql, unsigned int refresh_options) +{ +MK_ASYNC_START_BODY( + mysql_refresh, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.refresh_options= refresh_options; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_refresh_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_kill_start(). */ +struct mysql_kill_params { + MYSQL *mysql; + unsigned long pid; +}; +static void +mysql_kill_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_kill, + (parms->mysql, parms->pid), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_kill_start(int *ret, MYSQL *mysql, unsigned long pid) +{ +MK_ASYNC_START_BODY( + mysql_kill, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.pid= pid; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_kill_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_set_server_option_start(). */ +struct mysql_set_server_option_params { + MYSQL *mysql; + enum enum_mysql_set_option option; +}; +static void +mysql_set_server_option_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_set_server_option, + (parms->mysql, parms->option), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_set_server_option_start(int *ret, MYSQL *mysql, + enum enum_mysql_set_option option) +{ +MK_ASYNC_START_BODY( + mysql_set_server_option, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.option= option; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_set_server_option_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_ping_start(). */ +struct mysql_ping_params { + MYSQL *mysql; +}; +static void +mysql_ping_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_ping, + (parms->mysql), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_ping_start(int *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_ping, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_ping_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_reset_connection_start(). */ +struct mysql_reset_connection_params { + MYSQL *mysql; +}; +static void +mysql_reset_connection_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_reset_connection, + (parms->mysql), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_reset_connection_start(int *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_reset_connection, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_reset_connection_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stat_start(). */ +struct mysql_stat_params { + MYSQL *mysql; +}; +static void +mysql_stat_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stat, + (parms->mysql), + parms->mysql, + const char *, + r_const_ptr) +} +int STDCALL +mysql_stat_start(const char **ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_stat, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + NULL, + r_const_ptr, + /* Nothing */) +} +int STDCALL +mysql_stat_cont(const char **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_const_ptr) +} + +/* Structure used to pass parameters from mysql_list_dbs_start(). */ +struct mysql_list_dbs_params { + MYSQL *mysql; + const char *wild; +}; +static void +mysql_list_dbs_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_list_dbs, + (parms->mysql, parms->wild), + parms->mysql, + MYSQL_RES *, + r_ptr) +} +int STDCALL +mysql_list_dbs_start(MYSQL_RES **ret, MYSQL *mysql, const char *wild) +{ +MK_ASYNC_START_BODY( + mysql_list_dbs, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.wild= wild; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_list_dbs_cont(MYSQL_RES **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_list_tables_start(). */ +struct mysql_list_tables_params { + MYSQL *mysql; + const char *wild; +}; +static void +mysql_list_tables_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_list_tables, + (parms->mysql, parms->wild), + parms->mysql, + MYSQL_RES *, + r_ptr) +} +int STDCALL +mysql_list_tables_start(MYSQL_RES **ret, MYSQL *mysql, const char *wild) +{ +MK_ASYNC_START_BODY( + mysql_list_tables, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.wild= wild; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_list_tables_cont(MYSQL_RES **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_list_processes_start(). */ +struct mysql_list_processes_params { + MYSQL *mysql; +}; +static void +mysql_list_processes_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_list_processes, + (parms->mysql), + parms->mysql, + MYSQL_RES *, + r_ptr) +} +int STDCALL +mysql_list_processes_start(MYSQL_RES **ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_list_processes, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_list_processes_cont(MYSQL_RES **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_list_fields_start(). */ +struct mysql_list_fields_params { + MYSQL *mysql; + const char *table; + const char *wild; +}; +static void +mysql_list_fields_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_list_fields, + (parms->mysql, parms->table, parms->wild), + parms->mysql, + MYSQL_RES *, + r_ptr) +} +int STDCALL +mysql_list_fields_start(MYSQL_RES **ret, MYSQL *mysql, const char *table, + const char *wild) +{ +MK_ASYNC_START_BODY( + mysql_list_fields, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.table= table; + parms.wild= wild; + }, + NULL, + r_ptr, + /* Nothing */) +} +int STDCALL +mysql_list_fields_cont(MYSQL_RES **ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + NULL, + r_ptr) +} + +/* Structure used to pass parameters from mysql_read_query_result_start(). */ +struct mysql_read_query_result_params { + MYSQL *mysql; +}; +static void +mysql_read_query_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_read_query_result, + (parms->mysql), + parms->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_read_query_result_start(my_bool *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_read_query_result, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + TRUE, + r_my_bool, + /* Nothing */) +} +int STDCALL +mysql_read_query_result_cont(my_bool *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_stmt_prepare_start(). */ +struct mysql_stmt_prepare_params { + MYSQL_STMT *stmt; + const char *query; + unsigned long length; +}; +static void +mysql_stmt_prepare_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_prepare, + (parms->stmt, parms->query, parms->length), + parms->stmt->mysql, + int, + r_int) +} +int STDCALL +mysql_stmt_prepare_start(int *ret, MYSQL_STMT *stmt, const char *query, + unsigned long length) +{ +MK_ASYNC_START_BODY( + mysql_stmt_prepare, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + parms.query= query; + parms.length= length; + }, + 1, + r_int, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_prepare(stmt, query, length); + return 0; + }) +} +int STDCALL +mysql_stmt_prepare_cont(int *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stmt_execute_start(). */ +struct mysql_stmt_execute_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_execute_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_execute, + (parms->stmt), + parms->stmt->mysql, + int, + r_int) +} +int STDCALL +mysql_stmt_execute_start(int *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_execute, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + 1, + r_int, + /* + If eg. mysql_change_user(), stmt->mysql will be NULL. + In this case, we cannot block. + */ + if (!stmt->mysql) + { + *ret= mysql_stmt_execute(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_execute_cont(int *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stmt_fetch_start(). */ +struct mysql_stmt_fetch_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_fetch_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_fetch, + (parms->stmt), + parms->stmt->mysql, + int, + r_int) +} +int STDCALL +mysql_stmt_fetch_start(int *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_fetch, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + 1, + r_int, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_fetch(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_fetch_cont(int *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stmt_store_result_start(). */ +struct mysql_stmt_store_result_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_store_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_store_result, + (parms->stmt), + parms->stmt->mysql, + int, + r_int) +} +int STDCALL +mysql_stmt_store_result_start(int *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_store_result, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + 1, + r_int, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_store_result(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_store_result_cont(int *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stmt_close_start(). */ +struct mysql_stmt_close_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_close_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_close, + (parms->stmt), + parms->stmt->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_stmt_close_start(my_bool *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_close, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + TRUE, + r_my_bool, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_close(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_close_cont(my_bool *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_stmt_reset_start(). */ +struct mysql_stmt_reset_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_reset_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_reset, + (parms->stmt), + parms->stmt->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_stmt_reset_start(my_bool *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_reset, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + TRUE, + r_my_bool, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_reset(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_reset_cont(my_bool *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_stmt_free_result_start(). */ +struct mysql_stmt_free_result_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_free_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_free_result, + (parms->stmt), + parms->stmt->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_stmt_free_result_start(my_bool *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_free_result, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + TRUE, + r_my_bool, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_free_result(stmt); + return 0; + }) +} +int STDCALL +mysql_stmt_free_result_cont(my_bool *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_stmt_send_long_data_start(). */ +struct mysql_stmt_send_long_data_params { + MYSQL_STMT *stmt; + unsigned int param_number; + const char *data; + unsigned long length; +}; +static void +mysql_stmt_send_long_data_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_send_long_data, + (parms->stmt, parms->param_number, parms->data, parms->length), + parms->stmt->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_stmt_send_long_data_start(my_bool *ret, MYSQL_STMT *stmt, + unsigned int param_number, + const char *data, unsigned long length) +{ +MK_ASYNC_START_BODY( + mysql_stmt_send_long_data, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + parms.param_number= param_number; + parms.data= data; + parms.length= length; + }, + TRUE, + r_my_bool, + /* If stmt->mysql==NULL then we will not block so can call directly. */ + if (!stmt->mysql) + { + *ret= mysql_stmt_send_long_data(stmt, param_number, data, length); + return 0; + }) +} +int STDCALL +mysql_stmt_send_long_data_cont(my_bool *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_commit_start(). */ +struct mysql_commit_params { + MYSQL *mysql; +}; +static void +mysql_commit_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_commit, + (parms->mysql), + parms->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_commit_start(my_bool *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_commit, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + TRUE, + r_my_bool, + /* Nothing */) +} +int STDCALL +mysql_commit_cont(my_bool *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_rollback_start(). */ +struct mysql_rollback_params { + MYSQL *mysql; +}; +static void +mysql_rollback_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_rollback, + (parms->mysql), + parms->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_rollback_start(my_bool *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_rollback, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + TRUE, + r_my_bool, + /* Nothing */) +} +int STDCALL +mysql_rollback_cont(my_bool *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_autocommit_start(). */ +struct mysql_autocommit_params { + MYSQL *mysql; + my_bool auto_mode; +}; +static void +mysql_autocommit_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_autocommit, + (parms->mysql, parms->auto_mode), + parms->mysql, + my_bool, + r_my_bool) +} +int STDCALL +mysql_autocommit_start(my_bool *ret, MYSQL *mysql, my_bool auto_mode) +{ +MK_ASYNC_START_BODY( + mysql_autocommit, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + parms.auto_mode= auto_mode; + }, + TRUE, + r_my_bool, + /* Nothing */) +} +int STDCALL +mysql_autocommit_cont(my_bool *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + TRUE, + r_my_bool) +} + +/* Structure used to pass parameters from mysql_next_result_start(). */ +struct mysql_next_result_params { + MYSQL *mysql; +}; +static void +mysql_next_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_next_result, + (parms->mysql), + parms->mysql, + int, + r_int) +} +int STDCALL +mysql_next_result_start(int *ret, MYSQL *mysql) +{ +MK_ASYNC_START_BODY( + mysql_next_result, + mysql, + { + WIN_SET_NONBLOCKING(mysql) + parms.mysql= mysql; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_next_result_cont(int *ret, MYSQL *mysql, int ready_status) +{ +MK_ASYNC_CONT_BODY( + mysql, + 1, + r_int) +} + +/* Structure used to pass parameters from mysql_stmt_next_result_start(). */ +struct mysql_stmt_next_result_params { + MYSQL_STMT *stmt; +}; +static void +mysql_stmt_next_result_start_internal(void *d) +{ +MK_ASYNC_INTERNAL_BODY( + mysql_stmt_next_result, + (parms->stmt), + parms->stmt->mysql, + int, + r_int) +} +int STDCALL +mysql_stmt_next_result_start(int *ret, MYSQL_STMT *stmt) +{ +MK_ASYNC_START_BODY( + mysql_stmt_next_result, + stmt->mysql, + { + WIN_SET_NONBLOCKING(stmt->mysql) + parms.stmt= stmt; + }, + 1, + r_int, + /* Nothing */) +} +int STDCALL +mysql_stmt_next_result_cont(int *ret, MYSQL_STMT *stmt, int ready_status) +{ +MK_ASYNC_CONT_BODY( + stmt->mysql, + 1, + r_int) +} +#endif + + +/* + The following functions are deprecated, and so have no non-blocking version: + + mysql_connect + mysql_create_db + mysql_drop_db +*/ + +/* + The following functions can newer block, and so do not have special + non-blocking versions: + + mysql_num_rows() + mysql_num_fields() + mysql_eof() + mysql_fetch_field_direct() + mysql_fetch_fields() + mysql_row_tell() + mysql_field_tell() + mysql_field_count() + mysql_affected_rows() + mysql_insert_id() + mysql_errno() + mysql_error() + mysql_sqlstate() + mysql_warning_count() + mysql_info() + mysql_thread_id() + mysql_character_set_name() + mysql_init() + mysql_ssl_set() + mysql_get_ssl_cipher() + mysql_use_result() + mysql_get_character_set_info() + mysql_set_local_infile_handler() + mysql_set_local_infile_default() + mysql_get_server_info() + mysql_get_server_name() + mysql_get_client_info() + mysql_get_client_version() + mysql_get_host_info() + mysql_get_server_version() + mysql_get_proto_info() + mysql_options() + mysql_data_seek() + mysql_row_seek() + mysql_field_seek() + mysql_fetch_lengths() + mysql_fetch_field() + mysql_escape_string() + mysql_hex_string() + mysql_real_escape_string() + mysql_debug() + myodbc_remove_escape() + mysql_thread_safe() + mysql_embedded() + mariadb_connection() + mysql_stmt_init() + mysql_stmt_fetch_column() + mysql_stmt_param_count() + mysql_stmt_attr_set() + mysql_stmt_attr_get() + mysql_stmt_bind_param() + mysql_stmt_bind_result() + mysql_stmt_result_metadata() + mysql_stmt_param_metadata() + mysql_stmt_errno() + mysql_stmt_error() + mysql_stmt_sqlstate() + mysql_stmt_row_seek() + mysql_stmt_row_tell() + mysql_stmt_data_seek() + mysql_stmt_num_rows() + mysql_stmt_affected_rows() + mysql_stmt_insert_id() + mysql_stmt_field_count() + mysql_more_results() + mysql_get_socket() + mysql_get_timeout_value() +*/ diff --git a/libmariadb/libmariadb/mariadb_charset.c b/libmariadb/libmariadb/mariadb_charset.c new file mode 100644 index 00000000..a406af2f --- /dev/null +++ b/libmariadb/libmariadb/mariadb_charset.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + 2016 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02111-1301, USA */ + +#include <ma_global.h> +#include <ma_sys.h> +// #include "mysys_err.h" +#include <mariadb_ctype.h> +#include <ma_string.h> + +MARIADB_CHARSET_INFO *ma_default_charset_info; /* will be set in mysql_server_init */ +MARIADB_CHARSET_INFO *ma_charset_bin= (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[32]; +MARIADB_CHARSET_INFO *ma_charset_latin1= (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[5]; +MARIADB_CHARSET_INFO *ma_charset_utf8_general_ci= (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[21]; +MARIADB_CHARSET_INFO *ma_charset_utf16le_general_ci= (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[68]; + +MARIADB_CHARSET_INFO * STDCALL mysql_get_charset_by_nr(uint cs_number) +{ + int i= 0; + + while (mariadb_compiled_charsets[i].nr && cs_number != mariadb_compiled_charsets[i].nr) + i++; + + return (mariadb_compiled_charsets[i].nr) ? (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[i] : NULL; +} + +my_bool set_default_charset(uint cs, myf flags __attribute__((unused))) +{ + MARIADB_CHARSET_INFO *new_charset; + new_charset = mysql_get_charset_by_nr(cs); + if (!new_charset) + { + return(TRUE); /* error */ + } + ma_default_charset_info = new_charset; + return(FALSE); +} + +MARIADB_CHARSET_INFO * STDCALL mysql_get_charset_by_name(const char *cs_name) +{ + int i= 0; + + while (mariadb_compiled_charsets[i].nr && strcmp(cs_name, mariadb_compiled_charsets[i].csname) != 0) + i++; + + return (mariadb_compiled_charsets[i].nr) ? (MARIADB_CHARSET_INFO *)&mariadb_compiled_charsets[i] : NULL; +} + +my_bool set_default_charset_by_name(const char *cs_name, myf flags __attribute__((unused))) +{ + MARIADB_CHARSET_INFO *new_charset; + new_charset = mysql_get_charset_by_name(cs_name); + if (!new_charset) + { + return(TRUE); /* error */ + } + + ma_default_charset_info = new_charset; + return(FALSE); +} diff --git a/libmariadb/libmariadb/mariadb_dyncol.c b/libmariadb/libmariadb/mariadb_dyncol.c new file mode 100644 index 00000000..42de69ee --- /dev/null +++ b/libmariadb/libmariadb/mariadb_dyncol.c @@ -0,0 +1,4369 @@ +/* Copyright (c) 2011,2013 Monty Program Ab; + Copyright (c) 2011,2012 Oleksandr Byelkin + 2013, 2022 MariaDB Corporation AB + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + Numeric format: + =============== + * Fixed header part + 1 byte flags: + 0,1 bits - <offset size> - 1 + 2-7 bits - 0 + 2 bytes column counter + * Columns directory sorted by column number, each entry contains of: + 2 bytes column number + <offset size> bytes (1-4) combined offset from beginning of + the data segment + 3 bit type + * Data of above columns size of data and length depend on type + + Columns with names: + =================== + * Fixed header part + 1 byte flags: + 0,1 bits - <offset size> - 2 + 2 bit - 1 (means format with names) + 3,4 bits - 00 (means <names offset size> - 2, + now 2 is the only supported size) + 5-7 bits - 0 + 2 bytes column counter + * Variable header part (now it is actually fixed part) + <names offset size> (2) bytes size of stored names pool + * Column directory sorted by names, each consists of + <names offset size> (2) bytes offset of name + <offset size> bytes (2-5)bytes combined offset from beginning of + the data segment + 4 bit type + * Names stored one after another + * Data of above columns size of data and length depend on type +*/ + +#include <stdio.h> +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_string.h> +//#include <ma_hashtbl.h> +#include <mariadb_dyncol.h> +#include <mysql.h> + + + +#ifndef LIBMARIADB +uint32 copy_and_convert(char *to, uint32 to_length, MARIADB_CHARSET_INFO *to_cs, + const char *from, uint32 from_length, + MARIADB_CHARSET_INFO *from_cs, uint *errors); +#else + +size_t mariadb_time_to_string(const MYSQL_TIME *tm, char *time_str, size_t len, + unsigned int digits); +size_t STDCALL mariadb_convert_string(const char *from, size_t *from_len, MARIADB_CHARSET_INFO *from_cs, + char *to, size_t *to_len, MARIADB_CHARSET_INFO *to_cs, int *errorcode); +#endif +/* + Flag byte bits + + 2 bits which determinate size of offset in the header -1 +*/ +/* mask to get above bits */ +#define DYNCOL_FLG_OFFSET (1|2) +#define DYNCOL_FLG_NAMES 4 +#define DYNCOL_FLG_NMOFFSET (8|16) +/** + All known flags mask that could be set. + + @note DYNCOL_FLG_NMOFFSET should be 0 for now. +*/ +#define DYNCOL_FLG_KNOWN (1|2|4) + +/* formats */ +enum enum_dyncol_format +{ + dyncol_fmt_num= 0, + dyncol_fmt_str= 1 +}; + +/* dynamic column size reserve */ +#define DYNCOL_SYZERESERVE 80 + +#define DYNCOL_OFFSET_ERROR 0xffffffff + +/* length of fixed string header 1 byte - flags, 2 bytes - columns counter */ +#define FIXED_HEADER_SIZE 3 +/* + length of fixed string header with names + 1 byte - flags, 2 bytes - columns counter, 2 bytes - name pool size +*/ +#define FIXED_HEADER_SIZE_NM 5 + +#define COLUMN_NUMBER_SIZE 2 +/* 2 bytes offset from the name pool */ +#define COLUMN_NAMEPTR_SIZE 2 + +#define MAX_OFFSET_LENGTH 4 +#define MAX_OFFSET_LENGTH_NM 5 + +#define DYNCOL_NUM_CHAR 6 + +my_bool mariadb_dyncol_has_names(DYNAMIC_COLUMN *str) +{ + if (str->length < 1) + return FALSE; + return test(str->str[0] & DYNCOL_FLG_NAMES); +} + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value, enum enum_dyncol_format format); +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static enum enum_dyncol_func_result +dynamic_column_get_internal(DYNAMIC_COLUMN *str, + DYNAMIC_COLUMN_VALUE *store_it_here, + uint num_key, LEX_STRING *str_key); +static enum enum_dyncol_func_result +dynamic_column_exists_internal(DYNAMIC_COLUMN *str, uint num_key, + LEX_STRING *str_key); +static enum enum_dyncol_func_result +dynamic_column_update_many_fmt(DYNAMIC_COLUMN *str, + uint add_column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool string_keys); +static int plan_sort_num(const void *a, const void *b); +static int plan_sort_named(const void *a, const void *b); + +/* + Structure to hold information about dynamic columns record and + iterate through it. +*/ + +struct st_dyn_header +{ + uchar *header, *nmpool, *dtpool, *data_end; + size_t offset_size; + size_t entry_size; + size_t header_size; + size_t nmpool_size; + size_t data_size; + /* dyncol_fmt_num - numeric columns, dyncol_fmt_str - column names */ + enum enum_dyncol_format format; + uint column_count; + + uchar *entry, *data, *name; + size_t offset; + size_t length; + enum enum_dynamic_column_type type; +}; + +typedef struct st_dyn_header DYN_HEADER; + +static inline my_bool read_fixed_header(DYN_HEADER *hdr, + DYNAMIC_COLUMN *str); +static void set_fixed_header(DYNAMIC_COLUMN *str, + uint offset_size, + uint column_count); + +/* + Calculate entry size (E) and header size (H) by offset size (O) and column + count (C) and fixed part of entry size (F). +*/ + +#define calc_param(E,H,F,O,C) do { \ + (*(E))= (O) + F; \ + (*(H))= (*(E)) * (C); \ +}while(0); + + +/** + Name pool size functions, for numeric format it is 0 +*/ + +static size_t name_size_num(void *keys __attribute__((unused)), + uint i __attribute__((unused))) +{ + return 0; +} + + +/** + Name pool size functions. +*/ +static size_t name_size_named(void *keys, uint i) +{ + return ((LEX_STRING *) keys)[i].length; +} + + +/** + Comparator function for references on column numbers for qsort + (numeric format) +*/ + +static int column_sort_num(const void *a, const void *b) +{ + return **((uint **)a) - **((uint **)b); +} + +/** + Comparator function for references on column numbers for qsort + (names format) +*/ + +int mariadb_dyncol_column_cmp_named(const LEX_STRING *s1, const LEX_STRING *s2) +{ + /* + We compare instead of subtraction to avoid data loss in case of huge + length difference (more then fit in int). + */ + int rc= (s1->length > s2->length ? 1 : + (s1->length < s2->length ? -1 : 0)); + if (rc == 0) + rc= memcmp((void *)s1->str, (void *)s2->str, + (size_t) s1->length); + return rc; +} + + +/** + Comparator function for references on column numbers for qsort + (names format) +*/ + +static int column_sort_named(const void *a, const void *b) +{ + return mariadb_dyncol_column_cmp_named(*((LEX_STRING **)a), + *((LEX_STRING **)b)); +} + + +/** + Check limit function (numeric format) +*/ + +static my_bool check_limit_num(const void *val) +{ + return **((uint **)val) > UINT_MAX16; +} + + +/** + Check limit function (names format) +*/ + +static my_bool check_limit_named(const void *val) +{ + return (*((LEX_STRING **)val))->length > MAX_NAME_LENGTH; +} + + +/** + Write numeric format static header part. +*/ + +static void set_fixed_header_num(DYNAMIC_COLUMN *str, DYN_HEADER *hdr) +{ + set_fixed_header(str, (uint)hdr->offset_size, hdr->column_count); + hdr->header= (uchar *)str->str + FIXED_HEADER_SIZE; + hdr->nmpool= hdr->dtpool= hdr->header + hdr->header_size; +} + + +/** + Write names format static header part. +*/ + +static void set_fixed_header_named(DYNAMIC_COLUMN *str, DYN_HEADER *hdr) +{ + DBUG_ASSERT(hdr->column_count <= 0xffff); + DBUG_ASSERT(hdr->offset_size <= MAX_OFFSET_LENGTH_NM); + /* size of data offset, named format flag, size of names offset (0 means 2) */ + str->str[0]= + (char) ((str->str[0] & ~(DYNCOL_FLG_OFFSET | DYNCOL_FLG_NMOFFSET)) | + (hdr->offset_size - 2) | DYNCOL_FLG_NAMES); + int2store(str->str + 1, hdr->column_count); /* columns number */ + int2store(str->str + 3, hdr->nmpool_size); + hdr->header= (uchar *)str->str + FIXED_HEADER_SIZE_NM; + hdr->nmpool= hdr->header + hdr->header_size; + hdr->dtpool= hdr->nmpool + hdr->nmpool_size; +} + + +/** + Store offset and type information in the given place + + @param place Beginning of the index entry + @param offset_size Size of offset field in bytes + @param type Type to be written + @param offset Offset to be written +*/ + +static my_bool type_and_offset_store_num(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulong val = (((ulong) offset) << 3) | (type - 1); + DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~7)) == 0); /* fit in 3 bits */ + DBUG_ASSERT(offset_size >= 1 && offset_size <= 4); + + /* Index entry starts with column number; jump over it */ + place+= COLUMN_NUMBER_SIZE; + + switch (offset_size) { + case 1: + if (offset >= 0x1f) /* all 1 value is reserved */ + return TRUE; + place[0]= (uchar)val; + break; + case 2: + if (offset >= 0x1fff) /* all 1 value is reserved */ + return TRUE; + int2store(place, val); + break; + case 3: + if (offset >= 0x1fffff) /* all 1 value is reserved */ + return TRUE; + int3store(place, val); + break; + case 4: + if (offset >= 0x1fffffff) /* all 1 value is reserved */ + return TRUE; + int4store(place, val); + break; + default: + return TRUE; + } + return FALSE; +} + + +static my_bool type_and_offset_store_named(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulonglong val = (((ulong) offset) << 4) | (type - 1); + DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~0xf)) == 0); /* fit in 4 bits */ + DBUG_ASSERT(offset_size >= 2 && offset_size <= 5); + + /* Index entry starts with name offset; jump over it */ + place+= COLUMN_NAMEPTR_SIZE; + switch (offset_size) { + case 2: + if (offset >= 0xfff) /* all 1 value is reserved */ + return TRUE; + int2store(place, val); + break; + case 3: + if (offset >= 0xfffff) /* all 1 value is reserved */ + return TRUE; + int3store(place, val); + break; + case 4: + if (offset >= 0xfffffff) /* all 1 value is reserved */ + return TRUE; + int4store(place, val); + break; + case 5: +#if SIZEOF_SIZE_T > 4 + if (offset >= 0xfffffffffull) /* all 1 value is reserved */ + return TRUE; +#endif + int5store(place, val); + break; + case 1: + default: + return TRUE; + } + return FALSE; +} + +/** + Write numeric format header entry + 2 bytes - column number + 1-4 bytes - data offset combined with type + + @param hdr descriptor of dynamic column record + @param column_key pointer to uint (column number) + @param value value which will be written (only type used) + @param offset offset of the data +*/ + +static my_bool put_header_entry_num(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset) +{ + uint *column_number= (uint *)column_key; + int2store(hdr->entry, *column_number); + DBUG_ASSERT(hdr->nmpool_size == 0); + if (type_and_offset_store_num(hdr->entry, hdr->offset_size, + value->type, + offset)) + return TRUE; + hdr->entry= hdr->entry + hdr->entry_size; + return FALSE; +} + + +/** + Write names format header entry + 1 byte - name length + 2 bytes - name offset in the name pool + 1-4 bytes - data offset combined with type + + @param hdr descriptor of dynamic column record + @param column_key pointer to LEX_STRING (column name) + @param value value which will be written (only type used) + @param offset offset of the data +*/ + +static my_bool put_header_entry_named(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset) +{ + LEX_STRING *column_name= (LEX_STRING *)column_key; + DBUG_ASSERT(column_name->length <= MAX_NAME_LENGTH); + DBUG_ASSERT(hdr->name - hdr->nmpool < (long) 0x10000L); + int2store(hdr->entry, hdr->name - hdr->nmpool); + memcpy(hdr->name, column_name->str, column_name->length); + DBUG_ASSERT(hdr->nmpool_size != 0 || column_name->length == 0); + if (type_and_offset_store_named(hdr->entry, hdr->offset_size, + value->type, + offset)) + return TRUE; + hdr->entry+= hdr->entry_size; + hdr->name+= column_name->length; + return FALSE; +} + + +/** + Calculate length of offset field for given data length + + @param data_length Length of the data segment + + @return number of bytes +*/ + +static size_t dynamic_column_offset_bytes_num(size_t data_length) +{ + if (data_length < 0x1f) /* all 1 value is reserved */ + return 1; + if (data_length < 0x1fff) /* all 1 value is reserved */ + return 2; + if (data_length < 0x1fffff) /* all 1 value is reserved */ + return 3; + if (data_length < 0x1fffffff) /* all 1 value is reserved */ + return 4; + return MAX_OFFSET_LENGTH + 1; /* For an error generation*/ +} + +static size_t dynamic_column_offset_bytes_named(size_t data_length) +{ + if (data_length < 0xfff) /* all 1 value is reserved */ + return 2; + if (data_length < 0xfffff) /* all 1 value is reserved */ + return 3; + if (data_length < 0xfffffff) /* all 1 value is reserved */ + return 4; +#if SIZEOF_SIZE_T > 4 + if (data_length < 0xfffffffffull) /* all 1 value is reserved */ +#endif + return 5; + return MAX_OFFSET_LENGTH_NM + 1; /* For an error generation */ +} + +/** + Read offset and type information from index entry + + @param type Where to put type info + @param offset Where to put offset info + @param place beginning of the type and offset + @param offset_size Size of offset field in bytes +*/ + +static my_bool type_and_offset_read_num(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulong UNINIT_VAR(val); + ulong UNINIT_VAR(lim); + + DBUG_ASSERT(offset_size >= 1 && offset_size <= 4); + + switch (offset_size) { + case 1: + val= (ulong)place[0]; + lim= 0x1f; + break; + case 2: + val= uint2korr(place); + lim= 0x1fff; + break; + case 3: + val= uint3korr(place); + lim= 0x1fffff; + break; + case 4: + val= uint4korr(place); + lim= 0x1fffffff; + break; + default: + DBUG_ASSERT(0); /* impossible */ + return 1; + } + *type= (val & 0x7) + 1; + *offset= val >> 3; + return (*offset >= lim); +} + +static my_bool type_and_offset_read_named(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulonglong UNINIT_VAR(val); + ulonglong UNINIT_VAR(lim); + DBUG_ASSERT(offset_size >= 2 && offset_size <= 5); + + switch (offset_size) { + case 2: + val= uint2korr(place); + lim= 0xfff; + break; + case 3: + val= uint3korr(place); + lim= 0xfffff; + break; + case 4: + val= uint4korr(place); + lim= 0xfffffff; + break; + case 5: + val= uint5korr(place); + lim= 0xfffffffffull; + break; + case 1: + default: + DBUG_ASSERT(0); /* impossible */ + return 1; + } + *type= (val & 0xf) + 1; + *offset= (size_t)(val >> 4); + return (*offset >= lim); +} + +/** + Format descriptor, contain constants and function references for + format processing +*/ + +struct st_service_funcs +{ + /* size of fixed header */ + uint fixed_hdr; + /* size of fixed part of header entry */ + uint fixed_hdr_entry; + + /*size of array element which stores keys */ + uint key_size_in_array; + + /* Maximum data offset size in bytes */ + size_t max_offset_size; + + size_t (*name_size) + (void *, uint); + int (*column_sort) + (const void *a, const void *b); + my_bool (*check_limit) + (const void *val); + void (*set_fixed_hdr) + (DYNAMIC_COLUMN *str, DYN_HEADER *hdr); + my_bool (*put_header_entry)(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset); + int (*plan_sort)(const void *a, const void *b); + size_t (*dynamic_column_offset_bytes)(size_t data_length); + my_bool (*type_and_offset_read)(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size); + +}; + + +/** + Actual our 2 format descriptors +*/ + +static struct st_service_funcs fmt_data[2]= +{ + { + FIXED_HEADER_SIZE, + COLUMN_NUMBER_SIZE, + sizeof(uint), + MAX_OFFSET_LENGTH, + &name_size_num, + &column_sort_num, + &check_limit_num, + &set_fixed_header_num, + &put_header_entry_num, + &plan_sort_num, + &dynamic_column_offset_bytes_num, + &type_and_offset_read_num + }, + { + FIXED_HEADER_SIZE_NM, + COLUMN_NAMEPTR_SIZE, + sizeof(LEX_STRING), + MAX_OFFSET_LENGTH_NM, + &name_size_named, + &column_sort_named, + &check_limit_named, + &set_fixed_header_named, + &put_header_entry_named, + &plan_sort_named, + &dynamic_column_offset_bytes_named, + &type_and_offset_read_named + } +}; + + +/** + Read dynamic column record header and fill the descriptor + + @param hdr dynamic columns record descriptor to fill + @param str dynamic columns record + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +init_read_hdr(DYN_HEADER *hdr, DYNAMIC_COLUMN *str) +{ + if (read_fixed_header(hdr, str)) + return ER_DYNCOL_FORMAT; + hdr->header= (uchar*)str->str + fmt_data[hdr->format].fixed_hdr; + calc_param(&hdr->entry_size, &hdr->header_size, + fmt_data[hdr->format].fixed_hdr_entry, hdr->offset_size, + hdr->column_count); + hdr->nmpool= hdr->header + hdr->header_size; + hdr->dtpool= hdr->nmpool + hdr->nmpool_size; + hdr->data_size= str->length - fmt_data[hdr->format].fixed_hdr - + hdr->header_size - hdr->nmpool_size; + hdr->data_end= (uchar*)str->str + str->length; + return ER_DYNCOL_OK; +} + + +/** + Initialize dynamic column string with (make it empty but correct format) + + @param str The string to initialize + @param size Amount of preallocated memory for the string. + + @retval FALSE OK + @retval TRUE error +*/ + +static my_bool dynamic_column_init_named(DYNAMIC_COLUMN *str, size_t size) +{ + DBUG_ASSERT(size != 0); + + /* + Make string with no fields (empty header) + - First \0 is flags + - other 2 \0 is number of fields + */ + if (ma_init_dynamic_string(str, NULL, size, DYNCOL_SYZERESERVE)) + return TRUE; + return FALSE; +} + + +/** + Calculate how many bytes needed to store val as variable length integer + where first bit indicate continuation of the sequence. + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_var_uint_bytes(ulonglong val) +{ + size_t len= 0; + do + { + len++; + val>>= 7; + } while (val); + return len; +} + + +/** + Stores variable length unsigned integer value to a string + + @param str The string where to append the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code + + @notes + This is used to store a number together with other data in the same + object. (Like decimals, length of string etc) + (As we don't know the length of this object, we can't store 0 in 0 bytes) +*/ + +static enum enum_dyncol_func_result +dynamic_column_var_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (ma_dynstr_realloc(str, 10)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + do + { + ulonglong rest= val >> 7; + str->str[str->length++]= ((val & 0x7f) | (rest ? 0x80 : 0x00)); + val= rest; + } while (val); + return ER_DYNCOL_OK; +} + + +/** + Reads variable length unsigned integer value from a string + + @param data The string from which the int should be read + @param data_length Max length of data + @param len Where to put length of the string read in bytes + + @return value of the unsigned integer read from the string + + In case of error, *len is set to 0 +*/ + +static ulonglong +dynamic_column_var_uint_get(uchar *data, size_t data_length, + size_t *len) +{ + ulonglong val= 0; + uint length; + uchar *end= data + data_length; + + for (length=0; data < end ; data++) + { + val+= (((ulonglong)((*data) & 0x7f)) << (length * 7)); + length++; + if (!((*data) & 0x80)) + { + /* End of data */ + *len= length; + return val; + } + } + /* Something was wrong with data */ + *len= 0; /* Mark error */ + return 0; +} + + +/** + Calculate how many bytes needed to store val as unsigned. + + @param val The value for which we are calculating length + + @return number of bytes (0-8) +*/ + +static size_t dynamic_column_uint_bytes(ulonglong val) +{ + size_t len; + + for (len= 0; val ; val>>= 8, len++) + ; + return len; +} + + +/** + Append the string with given unsigned int value. + + @param str The string where to put the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (ma_dynstr_realloc(str, 8)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + for (; val; val>>= 8) + str->str[str->length++]= (char) (val & 0xff); + return ER_DYNCOL_OK; +} + + +/** + Read unsigned int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_uint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + ulonglong value= 0; + size_t i; + + for (i= 0; i < length; i++) + value+= ((ulonglong)data[i]) << (i*8); + + store_it_here->x.ulong_value= value; + return ER_DYNCOL_OK; +} + +/** + Calculate how many bytes needed to store val as signed in following encoding: + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + ... + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_sint_bytes(longlong val) +{ + return dynamic_column_uint_bytes((val << 1) ^ + (val < 0 ? 0xffffffffffffffffull : 0)); +} + + +/** + Append the string with given signed int value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_sint_store(DYNAMIC_COLUMN *str, longlong val) +{ + return dynamic_column_uint_store(str, + (val << 1) ^ + (val < 0 ? 0xffffffffffffffffULL : 0)); +} + + +/** + Read signed int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_sint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + ulonglong val; + dynamic_column_uint_read(store_it_here, data, length); + val= store_it_here->x.ulong_value; + if (val & 1) + val= (val >> 1) ^ 0xffffffffffffffffULL; + else + val>>= 1; + store_it_here->x.long_value= (longlong) val; + return ER_DYNCOL_OK; +} + + +/** + Calculate how many bytes needed to store the value. + + @param value The value for which we are calculating length + + @return + Error: (size_t) ~0 + ok number of bytes +*/ + +static size_t +dynamic_column_value_len(DYNAMIC_COLUMN_VALUE *value, + enum enum_dyncol_format format) +{ + switch (value->type) { + case DYN_COL_NULL: + return 0; + case DYN_COL_INT: + return dynamic_column_sint_bytes(value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_bytes(value->x.ulong_value); + case DYN_COL_DOUBLE: + return 8; + case DYN_COL_STRING: +#ifdef LIBMARIADB + return (dynamic_column_var_uint_bytes(value->x.string.charset->nr) + + value->x.string.value.length); +#else + return (dynamic_column_var_uint_bytes(value->x.string.charset->number) + + value->x.string.value.length); +#endif +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + { + int precision= value->x.decimal.value.intg + value->x.decimal.value.frac; + int scale= value->x.decimal.value.frac; + + if (precision == 0 || decimal_is_zero(&value->x.decimal.value)) + { + /* This is here to simplify dynamic_column_decimal_store() */ + value->x.decimal.value.intg= value->x.decimal.value.frac= 0; + return 0; + } + /* + Check if legal decimal; This is needed to not get an assert in + decimal_bin_size(). However this should be impossible as all + decimals entered here should be valid and we have the special check + above to handle the unlikely but possible case that decimal.value.intg + and decimal.frac is 0. + */ + if (scale < 0 || precision <= 0) + { + DBUG_ASSERT(0); /* Impossible */ + return (size_t) ~0; + } + return (dynamic_column_var_uint_bytes(value->x.decimal.value.intg) + + dynamic_column_var_uint_bytes(value->x.decimal.value.frac) + + decimal_bin_size(precision, scale)); + } +#endif + case DYN_COL_DATETIME: + if (format == dyncol_fmt_num || value->x.time_value.second_part) + /* date+time in bits: 14 + 4 + 5 + 10 + 6 + 6 + 20 + 1 66bits ~= 9 bytes*/ + return 9; + else + return 6; + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return 3; + case DYN_COL_TIME: + if (format == dyncol_fmt_num || value->x.time_value.second_part) + /* time in bits: 10 + 6 + 6 + 20 + 1 = 43bits ~= 6bytes*/ + return 6; + else + return 3; + case DYN_COL_DYNCOL: + return value->x.string.value.length; + default: + break; + } + DBUG_ASSERT(0); + return 0; +} + + +/** + Append double value to a string + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_store(DYNAMIC_COLUMN *str, double val) +{ + if (ma_dynstr_realloc(str, 8)) + return ER_DYNCOL_RESOURCE; + float8store(str->str + str->length, val); + str->length+= 8; + return ER_DYNCOL_OK; +} + + +/** + Read double value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 8) + return ER_DYNCOL_FORMAT; + float8get(store_it_here->x.double_value, data); + return ER_DYNCOL_OK; +} + + +/** + Append the string with given string value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_store(DYNAMIC_COLUMN *str, LEX_STRING *string, + MARIADB_CHARSET_INFO *charset) +{ + enum enum_dyncol_func_result rc; +#ifdef LIBMARIADB + if ((rc= dynamic_column_var_uint_store(str, charset->nr))) +#else + if ((rc= dynamic_column_var_uint_store(str, charset->number))) +#endif + return rc; + if (ma_dynstr_append_mem(str, string->str, string->length)) + return ER_DYNCOL_RESOURCE; + return ER_DYNCOL_OK; +} + +/** + Append the string with given string value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_dyncol_store(DYNAMIC_COLUMN *str, LEX_STRING *string) +{ + if (ma_dynstr_append_mem(str, string->str, string->length)) + return ER_DYNCOL_RESOURCE; + return ER_DYNCOL_OK; +} + +/** + Read string value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t len; + uint charset_nr= (uint)dynamic_column_var_uint_get(data, length, &len); + if (len == 0) /* Wrong packed number */ + return ER_DYNCOL_FORMAT; +#ifndef LIBMARIADB + store_it_here->x.string.charset= get_charset_by_nr(charset_nr); +#else + store_it_here->x.string.charset= mariadb_get_charset_by_nr(charset_nr); +#endif + if (store_it_here->x.string.charset == NULL) + return ER_DYNCOL_UNKNOWN_CHARSET; + data+= len; + store_it_here->x.string.value.length= (length-= len); + store_it_here->x.string.value.str= (char*) data; + return ER_DYNCOL_OK; +} + +/** + Read Dynamic columns packet string value of given length + from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_dyncol_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.string.charset= ma_charset_bin; + store_it_here->x.string.value.length= length; + store_it_here->x.string.value.str= (char*) data; + return ER_DYNCOL_OK; +} + +/** + Append the string with given decimal value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ +#ifndef LIBMARIADB +static enum enum_dyncol_func_result +dynamic_column_decimal_store(DYNAMIC_COLUMN *str, + decimal_t *value) +{ + uint bin_size; + int precision= value->intg + value->frac; + + /* Store decimal zero as empty string */ + if (precision == 0) + return ER_DYNCOL_OK; + + bin_size= decimal_bin_size(precision, value->frac); + if (ma_dynstr_realloc(str, bin_size + 20)) + return ER_DYNCOL_RESOURCE; + + /* The following can't fail as memory is already allocated */ + (void) dynamic_column_var_uint_store(str, value->intg); + (void) dynamic_column_var_uint_store(str, value->frac); + + decimal2bin(value, (uchar *) str->str + str->length, + precision, value->frac); + str->length+= bin_size; + return ER_DYNCOL_OK; +} + + +/** + Prepare the value to be used as decimal. + + @param value The value structure which should be setup. +*/ + +void mariadb_dyncol_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + value->x.decimal.value.buf= value->x.decimal.buffer; + value->x.decimal.value.len= DECIMAL_BUFF_LENGTH; + /* just to be safe */ + value->type= DYN_COL_DECIMAL; + decimal_make_zero(&value->x.decimal.value); +} + +void dynamic_column_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + mariadb_dyncol_prepare_decimal(value); +} + + + +/** + Read decimal value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_decimal_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t intg_len, frac_len; + int intg, frac, precision, scale; + + dynamic_column_prepare_decimal(store_it_here); + /* Decimals 0.0 is stored as a zero length string */ + if (length == 0) + return ER_DYNCOL_OK; /* value contains zero */ + + intg= (int)dynamic_column_var_uint_get(data, length, &intg_len); + data+= intg_len; + frac= (int)dynamic_column_var_uint_get(data, length - intg_len, &frac_len); + data+= frac_len; + + /* Check the size of data is correct */ + precision= intg + frac; + scale= frac; + if (scale < 0 || precision <= 0 || scale > precision || + (length - intg_len - frac_len) > + (size_t) (DECIMAL_BUFF_LENGTH*sizeof(decimal_digit_t)) || + decimal_bin_size(intg + frac, frac) != + (int) (length - intg_len - frac_len)) + return ER_DYNCOL_FORMAT; + + if (bin2decimal(data, &store_it_here->x.decimal.value, precision, scale) != + E_DEC_OK) + return ER_DYNCOL_FORMAT; + return ER_DYNCOL_OK; +} +#endif + +/** + Append the string with given datetime value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value, + enum enum_dyncol_format format) +{ + enum enum_dyncol_func_result rc; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if ((rc= dynamic_column_date_store(str, value)) || + (rc= dynamic_column_time_store(str, value, format))) + return rc; + return ER_DYNCOL_OK; +} + + +/** + Read datetime value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if (length != 9 && length != 6) + goto err; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + if ((rc= dynamic_column_date_read_internal(store_it_here, data, 3)) || + (rc= dynamic_column_time_read_internal(store_it_here, data + 3, + length - 3))) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return rc; +} + + +/** + Append the string with given time value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value, + enum enum_dyncol_format format) +{ + uchar *buf; + if (ma_dynstr_realloc(str, 6)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_DATE) + { + value->neg= 0; + value->second_part= 0; + value->hour= 0; + value->minute= 0; + value->second= 0; + } + DBUG_ASSERT(value->hour <= 838); + DBUG_ASSERT(value->minute <= 59); + DBUG_ASSERT(value->second <= 59); + DBUG_ASSERT(value->second_part <= 999999); + if (format == dyncol_fmt_num || value->second_part) + { + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + buf[0]= (value->second_part & 0xff); + buf[1]= ((value->second_part & 0xff00) >> 8); + buf[2]= (uchar)(((value->second & 0xf) << 4) | + ((value->second_part & 0xf0000) >> 16)); + buf[3]= ((value->minute << 2) | ((value->second & 0x30) >> 4)); + buf[4]= (value->hour & 0xff); + buf[5]= ((value->neg ? 0x4 : 0) | (value->hour >> 8)); + str->length+= 6; + } + else + { + /* + !<-hours--><min-><sec-> + 11234567890123456123456 + <123456><123456><123456> + */ + buf[0]= (value->second) | ((value->minute & 0x3) << 6); + buf[1]= (value->minute >> 2) | ((value->hour & 0xf) << 4); + buf[2]= (value->hour >> 4) | (value->neg ? 0x80 : 0); + str->length+= 3; + } + + return ER_DYNCOL_OK; +} + + +/** + Read time value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.year= store_it_here->x.time_value.month= + store_it_here->x.time_value.day= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_TIME; + return dynamic_column_time_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading time part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 6 && length != 3) + goto err; + if (length == 6) + { + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + store_it_here->x.time_value.second_part= (data[0] | + (data[1] << 8) | + ((data[2] & 0xf) << 16)); + store_it_here->x.time_value.second= ((data[2] >> 4) | + ((data[3] & 0x3) << 4)); + store_it_here->x.time_value.minute= (data[3] >> 2); + store_it_here->x.time_value.hour= (((((uint)data[5]) & 0x3 ) << 8) | data[4]); + store_it_here->x.time_value.neg= ((data[5] & 0x4) ? 1 : 0); + } + else + { + /* + !<-hours--><min-><sec-> + 11234567890123456123456 + <123456><123456><123456> + */ + store_it_here->x.time_value.second_part= 0; + store_it_here->x.time_value.second= (data[0] & 0x3f); + store_it_here->x.time_value.minute= (data[0] >> 6) | ((data[1] & 0xf) << 2); + store_it_here->x.time_value.hour= (data[1] >> 4) | ((data[2] & 0x3f) << 4); + store_it_here->x.time_value.neg= ((data[2] & 0x80) ? 1 : 0); + } + if (store_it_here->x.time_value.second > 59 || + store_it_here->x.time_value.minute > 59 || + store_it_here->x.time_value.hour > 838 || + store_it_here->x.time_value.second_part > 999999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given date value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf; + if (ma_dynstr_realloc(str, 3)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_TIME) + value->year= value->month= value->day = 0; + DBUG_ASSERT(value->year <= 9999); + DBUG_ASSERT(value->month <= 12); + DBUG_ASSERT(value->day <= 31); + /* + 0<----year----><mn><day> + 012345678901234123412345 + <123456><123456><123456> + */ + buf[0]= (value->day | + ((value->month & 0x7) << 5)); + buf[1]= ((value->month >> 3) | ((value->year & 0x7F) << 1)); + buf[2]= (value->year >> 7); + str->length+= 3; + return ER_DYNCOL_OK; +} + + + +/** + Read date value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.neg= 0; + store_it_here->x.time_value.second_part= 0; + store_it_here->x.time_value.hour= 0; + store_it_here->x.time_value.minute= 0; + store_it_here->x.time_value.second= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATE; + return dynamic_column_date_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading date part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + if (length != 3) + goto err; + /* + 0<----year----><mn><day> + 12345678901234123412345 + <123456><123456><123456> + */ + store_it_here->x.time_value.day= (data[0] & 0x1f); + store_it_here->x.time_value.month= (((data[1] & 0x1) << 3) | + (data[0] >> 5)); + store_it_here->x.time_value.year= ((((uint)data[2]) << 7) | + (data[1] >> 1)); + if (store_it_here->x.time_value.day > 31 || + store_it_here->x.time_value.month > 12 || + store_it_here->x.time_value.year > 9999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +data_store(DYNAMIC_COLUMN *str, DYNAMIC_COLUMN_VALUE *value, + enum enum_dyncol_format format) +{ + switch (value->type) { + case DYN_COL_INT: + return dynamic_column_sint_store(str, value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_store(str, value->x.ulong_value); + case DYN_COL_DOUBLE: + return dynamic_column_double_store(str, value->x.double_value); + case DYN_COL_STRING: + return dynamic_column_string_store(str, &value->x.string.value, + value->x.string.charset); +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + return dynamic_column_decimal_store(str, &value->x.decimal.value); +#endif + case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 5 + 6 + 6 40bits = 5 bytes */ + return dynamic_column_date_time_store(str, &value->x.time_value, format); + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return dynamic_column_date_store(str, &value->x.time_value); + case DYN_COL_TIME: + /* time in bits: 5 + 6 + 6 = 17bits ~= 3bytes*/ + return dynamic_column_time_store(str, &value->x.time_value, format); + case DYN_COL_DYNCOL: + return dynamic_column_dyncol_store(str, &value->x.string.value); + case DYN_COL_NULL: + break; /* Impossible */ + default: + break; + } + DBUG_ASSERT(0); + return ER_DYNCOL_OK; /* Impossible */ +} + + +/** + Write information to the fixed header + + @param str String where to write the header + @param offset_size Size of offset field in bytes + @param column_count Number of columns +*/ + +static void set_fixed_header(DYNAMIC_COLUMN *str, + uint offset_size, + uint column_count) +{ + DBUG_ASSERT(column_count <= 0xffff); + DBUG_ASSERT(offset_size <= MAX_OFFSET_LENGTH); + str->str[0]= ((str->str[0] & ~DYNCOL_FLG_OFFSET) | + (offset_size - 1)); /* size of offset */ + int2store(str->str + 1, column_count); /* columns number */ + DBUG_ASSERT((str->str[0] & (~DYNCOL_FLG_KNOWN)) == 0); +} + +/** + Adds columns into the empty string + + @param str String where to write the data (the record) + @param hdr Dynamic columns record descriptor + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (uint or LEX_STRING) + @param values Array of columns values + @param new_str True if we need to allocate new string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_new_column_store(DYNAMIC_COLUMN *str, + DYN_HEADER *hdr, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + void **columns_order; + uchar *element; + uint i; + enum enum_dyncol_func_result rc= ER_DYNCOL_RESOURCE; + size_t all_headers_size; + + if (!(columns_order= malloc(sizeof(void*)*column_count))) + return ER_DYNCOL_RESOURCE; + if (new_str || str->str == 0) + { + if (column_count) + { + if (dynamic_column_init_named(str, + fmt->fixed_hdr + + hdr->header_size + + hdr->nmpool_size + + hdr->data_size + + DYNCOL_SYZERESERVE)) + goto err; + } + else + { + dynamic_column_initialize(str); + } + } + else + { + str->length= 0; + if (ma_dynstr_realloc(str, + fmt->fixed_hdr + + hdr->header_size + + hdr->nmpool_size + + hdr->data_size + + DYNCOL_SYZERESERVE)) + goto err; + } + if (!column_count) + { + free(columns_order); + return ER_DYNCOL_OK; + } + + memset(str->str, 0, fmt->fixed_hdr); + str->length= fmt->fixed_hdr; + + /* sort columns for the header */ + for (i= 0, element= (uchar *) column_keys; + i < column_count; + i++, element+= fmt->key_size_in_array) + columns_order[i]= (void *)element; + qsort(columns_order, (size_t)column_count, sizeof(void*), fmt->column_sort); + + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + for (i= 0; i < column_count - 1; i++) + { + if ((*fmt->check_limit)(&columns_order[i]) || + (*fmt->column_sort)(&columns_order[i], &columns_order[i + 1]) == 0) + { + rc= ER_DYNCOL_DATA; + goto err; + } + } + if ((*fmt->check_limit)(&columns_order[i])) + { + rc= ER_DYNCOL_DATA; + goto err; + } + + (*fmt->set_fixed_hdr)(str, hdr); + /* reserve place for header and name pool */ + str->length+= hdr->header_size + hdr->nmpool_size; + + hdr->entry= hdr->header; + hdr->name= hdr->nmpool; + all_headers_size= fmt->fixed_hdr + hdr->header_size + hdr->nmpool_size; + for (i= 0; i < column_count; i++) + { + uint ord= (uint)(((uchar*)columns_order[i] - (uchar*)column_keys) / + fmt->key_size_in_array); + if (values[ord].type != DYN_COL_NULL) + { + /* Store header first in the str */ + if ((*fmt->put_header_entry)(hdr, columns_order[i], values + ord, + str->length - all_headers_size)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + + /* Store value in 'str + str->length' and increase str->length */ + if ((rc= data_store(str, values + ord, hdr->format))) + goto err; + } + } + rc= ER_DYNCOL_OK; +err: + free(columns_order); + return rc; +} + +/** + Calculate size of header, name pool and data pool + + @param hdr descriptor of dynamic column record + @param column_count number of elements in arrays + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (uint or LEX_STRING) + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +calc_var_sizes(DYN_HEADER *hdr, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + uint i; + hdr->nmpool_size= hdr->data_size= 0; + hdr->column_count= 0; + for (i= 0; i < column_count; i++) + { + if (values[i].type != DYN_COL_NULL) + { + size_t tmp; + hdr->column_count++; + hdr->data_size+= (tmp= dynamic_column_value_len(values + i, + hdr->format)); + if (tmp == (size_t) ~0) + return ER_DYNCOL_DATA; + hdr->nmpool_size+= (*fmt->name_size)(column_keys, i); + } + } + /* + We can handle data up to 0x1fffffff (old format) and + 0xfffffffff (new format) bytes now. + */ + if ((hdr->offset_size= fmt->dynamic_column_offset_bytes(hdr->data_size)) >= + fmt->max_offset_size) + return ER_DYNCOL_LIMIT; + + /* header entry is column number or string pointer + offset & type */ + hdr->entry_size= fmt->fixed_hdr_entry + hdr->offset_size; + hdr->header_size= hdr->column_count * hdr->entry_size; + return ER_DYNCOL_OK; +} + +/** + Create packed string which contains given columns (internal multi format) + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (format dependent) + @param values Array of columns values + @param new_str True if we need allocate new string + @param string_keys keys are strings + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_create_many_internal_fmt(DYNAMIC_COLUMN *str, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str, + my_bool string_keys) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + memset(&header, 0, sizeof(header)); + header.format= (string_keys ? 1 : 0); + + if (new_str) + { + /* to make dynstr_free() working in case of errors */ + memset(str, 0, sizeof(DYNAMIC_COLUMN)); + } + + if ((rc= calc_var_sizes(&header, column_count, column_keys, values)) < 0) + return rc; + + return dynamic_new_column_store(str, &header, + column_count, + column_keys, values, + new_str); +} + + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create_many(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + return(dynamic_column_create_many_internal_fmt(str, column_count, + column_numbers, values, + TRUE, FALSE)); +} + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + @param new_string True if we need allocate new string + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_create_many_num(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_string) +{ + return(dynamic_column_create_many_internal_fmt(str, column_count, + column_numbers, values, + new_string, FALSE)); +} + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys + @param values Array of columns value + @param new_string True if we need allocate new string + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_create_many_named(DYNAMIC_COLUMN *str, + uint column_count, + LEX_STRING *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_string) +{ + return(dynamic_column_create_many_internal_fmt(str, column_count, + column_keys, values, + new_string, TRUE)); +} + +/** + Create packed string which contains given column + + @param str String where to write the data + @param column_number Column number + @param value The columns value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + return(dynamic_column_create_many(str, 1, &column_nr, value)); +} + + +/** + Calculate length of data between given two header entries + + @param entry Pointer to the first entry + @param entry_next Pointer to the last entry + @param header_end Pointer to the header end + @param offset_size Size of offset field in bytes + @param last_offset Size of the data segment + + @return number of bytes +*/ + +static size_t get_length_interval(uchar *entry, uchar *entry_next, + uchar *header_end, size_t offset_size, + size_t last_offset) +{ + size_t offset, offset_next; + DYNAMIC_COLUMN_TYPE type, type_next; + DBUG_ASSERT(entry < entry_next); + + if (type_and_offset_read_num(&type, &offset, entry + COLUMN_NUMBER_SIZE, + offset_size)) + return DYNCOL_OFFSET_ERROR; + if (entry_next >= header_end) + return (last_offset - offset); + if (type_and_offset_read_num(&type_next, &offset_next, + entry_next + COLUMN_NUMBER_SIZE, offset_size)) + return DYNCOL_OFFSET_ERROR; + return (offset_next - offset); +} + + +/** + Calculate length of data between given hdr->entry and next_entry + + @param hdr descriptor of dynamic column record + @param next_entry next header entry (can point just after last header + entry) + + @return number of bytes +*/ + +static size_t hdr_interval_length(DYN_HEADER *hdr, uchar *next_entry) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + size_t next_entry_offset; + DYNAMIC_COLUMN_TYPE next_entry_type; + DBUG_ASSERT(hdr->entry < next_entry); + DBUG_ASSERT(hdr->entry >= hdr->header); + DBUG_ASSERT(next_entry <= hdr->header + hdr->header_size); + + if ((*fmt->type_and_offset_read)(&hdr->type, &hdr->offset, + hdr->entry + fmt->fixed_hdr_entry, + hdr->offset_size)) + return DYNCOL_OFFSET_ERROR; + if (next_entry == hdr->header + hdr->header_size) + return hdr->data_size - hdr->offset; + if ((*fmt->type_and_offset_read)(&next_entry_type, &next_entry_offset, + next_entry + fmt->fixed_hdr_entry, + hdr->offset_size)) + return DYNCOL_OFFSET_ERROR; + return (next_entry_offset - hdr->offset); +} + + +/** + Comparator function for references to header entries for qsort +*/ + +static int header_compar_num(const void *a, const void *b) +{ + uint va= uint2korr((uchar*)a), vb= uint2korr((uchar*)b); + return (va > vb ? 1 : (va < vb ? -1 : 0)); +} + + +/** + Find entry in the numeric format header by the column number + + @param hdr descriptor of dynamic column record + @param key number to find + + @return pointer to the entry or NULL +*/ + +static uchar *find_entry_num(DYN_HEADER *hdr, uint key) +{ + uchar header_entry[2+4]; + DBUG_ASSERT(hdr->format == dyncol_fmt_num); + int2store(header_entry, key); + return hdr->entry= bsearch(header_entry, hdr->header, + (size_t)hdr->column_count, + hdr->entry_size, &header_compar_num); +} + + +/** + Read name from header entry + + @param hdr descriptor of dynamic column record + @param entry pointer to the header entry + @param name where to put name + + @return 0 ok + @return 1 error in data +*/ + +static my_bool read_name(DYN_HEADER *hdr, uchar *entry, LEX_STRING *name) +{ + size_t nmoffset= uint2korr(entry); + uchar *next_entry= entry + hdr->entry_size; + + if (nmoffset > hdr->nmpool_size) + return 1; + + name->str= (char *)hdr->nmpool + nmoffset; + if (next_entry == hdr->header + hdr->header_size) + name->length= hdr->nmpool_size - nmoffset; + else + { + size_t next_nmoffset= uint2korr(next_entry); + if (next_nmoffset > hdr->nmpool_size) + return 1; + name->length= next_nmoffset - nmoffset; + } + return 0; +} + + +/** + Find entry in the names format header by the column number + + @param hdr descriptor of dynamic column record + @param key name to find + + @return pointer to the entry or NULL +*/ +static uchar *find_entry_named(DYN_HEADER *hdr, LEX_STRING *key) +{ + uchar *min= hdr->header; + uchar *max= hdr->header + (hdr->column_count - 1) * hdr->entry_size; + uchar *mid; + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + DBUG_ASSERT(hdr->nmpool != NULL); + while (max >= min) + { + LEX_STRING name; + int cmp; + mid= hdr->header + ((min - hdr->header) + + (max - hdr->header)) / + 2 / + hdr->entry_size * hdr->entry_size; + if (read_name(hdr, mid, &name)) + return NULL; + cmp= mariadb_dyncol_column_cmp_named(&name, key); + if (cmp < 0) + min= mid + hdr->entry_size; + else if (cmp > 0) + max= mid - hdr->entry_size; + else + return mid; + } + return NULL; +} + + +/** + Write number in the buffer (backward direction - starts from the buffer end) + + @return pointer on the number beginning +*/ + +static char *backwritenum(char *chr, uint numkey) +{ + if (numkey == 0) + *(--chr)= '0'; + else + while (numkey > 0) + { + *(--chr)= '0' + numkey % 10; + numkey/= 10; + } + return chr; +} + + +/** + Find column and fill information about it + + @param hdr descriptor of dynamic column record + @param numkey Number of the column to fetch (if strkey is NULL) + @param strkey Name of the column to fetch (or NULL) + + @return 0 ok + @return 1 error in data +*/ + +static my_bool +find_column(DYN_HEADER *hdr, uint numkey, LEX_STRING *strkey) +{ + LEX_STRING nmkey; + char nmkeybuff[DYNCOL_NUM_CHAR]; /* to fit max 2 bytes number */ + DBUG_ASSERT(hdr->header != NULL); + + if (hdr->header + hdr->header_size > hdr->data_end) + return TRUE; + + /* fix key */ + if (hdr->format == dyncol_fmt_num && strkey != NULL) + { + char *end; + numkey= (uint) strtoul(strkey->str, &end, 10); + if (end != strkey->str + strkey->length) + { + /* we can't find non-numeric key among numeric ones */ + hdr->type= DYN_COL_NULL; + return 0; + } + } + else if (hdr->format == dyncol_fmt_str && strkey == NULL) + { + nmkey.str= backwritenum(nmkeybuff + sizeof(nmkeybuff), numkey); + nmkey.length= (nmkeybuff + sizeof(nmkeybuff)) - nmkey.str; + strkey= &nmkey; + } + if (hdr->format == dyncol_fmt_num) + hdr->entry= find_entry_num(hdr, numkey); + else + hdr->entry= find_entry_named(hdr, strkey); + + if (!hdr->entry) + { + /* Column not found */ + hdr->type= DYN_COL_NULL; + return 0; + } + hdr->length= hdr_interval_length(hdr, hdr->entry + hdr->entry_size); + hdr->data= hdr->dtpool + hdr->offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (hdr->length == DYNCOL_OFFSET_ERROR || + hdr->length > INT_MAX || hdr->offset > hdr->data_size) + return 1; + + return 0; +} + + +/** + Read and check the header of the dynamic string + + @param hdr descriptor of dynamic column record + @param str Dynamic string + + @retval FALSE OK + @retval TRUE error + + Note + We don't check for str->length == 0 as all code that calls this + already have handled this case. +*/ + +static inline my_bool read_fixed_header(DYN_HEADER *hdr, + DYNAMIC_COLUMN *str) +{ + DBUG_ASSERT(str != NULL && str->length != 0); + if ((str->length < 1) || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return 1; + hdr->format= ((str->str[0] & DYNCOL_FLG_NAMES) ? + dyncol_fmt_str: + dyncol_fmt_num); + if ((str->length < fmt_data[hdr->format].fixed_hdr)) + return 1; /* Wrong header */ + hdr->offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1 + + (hdr->format == dyncol_fmt_str ? 1 : 0); + hdr->column_count= uint2korr(str->str + 1); + if (hdr->format == dyncol_fmt_str) + hdr->nmpool_size= uint2korr(str->str + 3); // only 2 bytes supported for now + else + hdr->nmpool_size= 0; + return 0; +} + + +/** + Get dynamic column value by column number + + @param str The packed string to extract the column + @param column_nr Number of column to fetch + @param store_it_here Where to store the extracted value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_get(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + return dynamic_column_get_internal(str, store_it_here, column_nr, NULL); +} + +enum enum_dyncol_func_result +mariadb_dyncol_get_num(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + return dynamic_column_get_internal(str, store_it_here, column_nr, NULL); +} + + +/** + Get dynamic column value by name + + @param str The packed string to extract the column + @param name Name of column to fetch + @param store_it_here Where to store the extracted value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_get_named(DYNAMIC_COLUMN *str, LEX_STRING *name, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + DBUG_ASSERT(name != NULL); + return dynamic_column_get_internal(str, store_it_here, 0, name); +} + + +static enum enum_dyncol_func_result +dynamic_column_get_value(DYN_HEADER *hdr, DYNAMIC_COLUMN_VALUE *store_it_here) +{ + static enum enum_dyncol_func_result rc; + switch ((store_it_here->type= hdr->type)) { + case DYN_COL_INT: + rc= dynamic_column_sint_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_UINT: + rc= dynamic_column_uint_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_DOUBLE: + rc= dynamic_column_double_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_STRING: + rc= dynamic_column_string_read(store_it_here, hdr->data, hdr->length); + break; +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + rc= dynamic_column_decimal_read(store_it_here, hdr->data, hdr->length); + break; +#endif + case DYN_COL_DATETIME: + rc= dynamic_column_date_time_read(store_it_here, hdr->data, + hdr->length); + break; + case DYN_COL_DATE: + rc= dynamic_column_date_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_TIME: + rc= dynamic_column_time_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_NULL: + rc= ER_DYNCOL_OK; + break; + case DYN_COL_DYNCOL: + rc= dynamic_column_dyncol_read(store_it_here, hdr->data, hdr->length); + break; + default: + rc= ER_DYNCOL_FORMAT; + store_it_here->type= DYN_COL_NULL; + break; + } + return rc; +} + +/** + Get dynamic column value by number or name + + @param str The packed string to extract the column + @param store_it_here Where to store the extracted value + @param numkey Number of the column to fetch (if strkey is NULL) + @param strkey Name of the column to fetch (or NULL) + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_get_internal(DYNAMIC_COLUMN *str, + DYNAMIC_COLUMN_VALUE *store_it_here, + uint num_key, LEX_STRING *str_key) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + memset(&header, 0, sizeof(header)); + + if (str->length == 0) + goto null; + + if ((rc= init_read_hdr(&header, str)) < 0) + goto err; + + if (header.column_count == 0) + goto null; + + if (find_column(&header, num_key, str_key)) + goto err; + + rc= dynamic_column_get_value(&header, store_it_here); + return rc; + +null: + rc= ER_DYNCOL_OK; +err: + store_it_here->type= DYN_COL_NULL; + return rc; +} + + +/** + Check existence of the column in the packed string (by number) + + @param str The packed string to check the column + @param column_nr Number of column to check + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_exists_num(DYNAMIC_COLUMN *str, uint column_nr) +{ + return dynamic_column_exists_internal(str, column_nr, NULL); +} + +/** + Check existence of the column in the packed string (by name) + + @param str The packed string to check the column + @param name Name of column to check + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_exists_named(DYNAMIC_COLUMN *str, LEX_STRING *name) +{ + DBUG_ASSERT(name != NULL); + return dynamic_column_exists_internal(str, 0, name); +} + + +/** + Check existence of the column in the packed string (by name of number) + + @param str The packed string to check the column + @param num_key Number of the column to fetch (if strkey is NULL) + @param str_key Name of the column to fetch (or NULL) + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_exists_internal(DYNAMIC_COLUMN *str, uint num_key, + LEX_STRING *str_key) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + memset(&header, 0, sizeof(header)); + + if (str->length == 0) + return ER_DYNCOL_NO; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.column_count == 0) + return ER_DYNCOL_NO; /* no columns */ + + if (find_column(&header, num_key, str_key)) + return ER_DYNCOL_FORMAT; + + return (header.type != DYN_COL_NULL ? ER_DYNCOL_YES : ER_DYNCOL_NO); +} + + +/** + List not-null columns in the packed string (only numeric format) + + @param str The packed string + @param array_of_uint Where to put reference on created array + + @return ER_DYNCOL_* return code +*/ +enum enum_dyncol_func_result +dynamic_column_list(DYNAMIC_COLUMN *str, DYNAMIC_ARRAY *array_of_uint) +{ + DYN_HEADER header; + uchar *read; + uint i; + enum enum_dyncol_func_result rc; + + memset(array_of_uint, 0, sizeof(*array_of_uint)); /* In case of errors */ + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.format != dyncol_fmt_num) + return ER_DYNCOL_FORMAT; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + if (ma_init_dynamic_array(array_of_uint, sizeof(uint), header.column_count, 0)) + return ER_DYNCOL_RESOURCE; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + uint nm= uint2korr(read); + /* Insert can't never fail as it's pre-allocated above */ + (void) ma_insert_dynamic(array_of_uint, (uchar *)&nm); + } + return ER_DYNCOL_OK; +} + +/** + List not-null columns in the packed string (only numeric format) + + @param str The packed string + @param array_of_uint Where to put reference on created array + + @return ER_DYNCOL_* return code +*/ +enum enum_dyncol_func_result +mariadb_dyncol_list_num(DYNAMIC_COLUMN *str, uint *count, uint **nums) +{ + DYN_HEADER header; + uchar *read; + uint i; + enum enum_dyncol_func_result rc; + + (*nums)= 0; (*count)= 0; /* In case of errors */ + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.format != dyncol_fmt_num) + return ER_DYNCOL_FORMAT; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + if (!((*nums)= (uint *)malloc(sizeof(uint) * header.column_count))) + return ER_DYNCOL_RESOURCE; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + (*nums)[i]= uint2korr(read); + } + (*count)= header.column_count; + return ER_DYNCOL_OK; +} + +/** + List not-null columns in the packed string (any format) + + @param str The packed string + @param count Number of names in the list + @param names Where to put names list (should be freed) + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_list_named(DYNAMIC_COLUMN *str, uint *count, LEX_STRING **names) +{ + DYN_HEADER header; + uchar *read; + char *pool; + struct st_service_funcs *fmt; + uint i; + enum enum_dyncol_func_result rc; + + (*names)= 0; (*count)= 0; + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + fmt= fmt_data + header.format; + + if (header.entry_size * header.column_count + fmt->fixed_hdr > + str->length) + return ER_DYNCOL_FORMAT; + + if (header.format == dyncol_fmt_num) + *names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count + + DYNCOL_NUM_CHAR * header.column_count); + else + *names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count + + header.nmpool_size + header.column_count); + if (!(*names)) + return ER_DYNCOL_RESOURCE; + pool= ((char *)(*names)) + sizeof(LEX_STRING) * header.column_count; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + if (header.format == dyncol_fmt_num) + { + uint nm= uint2korr(read); + (*names)[i].str= pool; + pool+= DYNCOL_NUM_CHAR; + (*names)[i].length= + ma_ll2str(nm, (*names)[i].str, 10) - (*names)[i].str; + } + else + { + LEX_STRING tmp; + if (read_name(&header, read, &tmp)) + return ER_DYNCOL_FORMAT; + (*names)[i].length= tmp.length; + (*names)[i].str= pool; + pool+= tmp.length + 1; + memcpy((*names)[i].str, (const void *)tmp.str, tmp.length); + (*names)[i].str[tmp.length]= '\0'; // just for safety + } + } + (*count)= header.column_count; + return ER_DYNCOL_OK; +} + +/** + Find the place of the column in the header or place where it should be put + + @param hdr descriptor of dynamic column record + @param key Name or number of column to fetch + (depends on string_key) + @param string_key True if we gave pointer to LEX_STRING. + + @retval TRUE found + @retval FALSE pointer set to the next row +*/ + +static my_bool +find_place(DYN_HEADER *hdr, void *key, my_bool string_keys) +{ + uint mid, start, end, val; + int UNINIT_VAR(flag); + LEX_STRING str; + char buff[DYNCOL_NUM_CHAR]; + my_bool need_conversion= ((string_keys ? dyncol_fmt_str : dyncol_fmt_num) != + hdr->format); + /* new format can't be numeric if the old one is names */ + DBUG_ASSERT(string_keys || + hdr->format == dyncol_fmt_num); + + start= 0; + end= hdr->column_count -1; + mid= 1; + while (start != end) + { + uint val; + mid= (start + end) / 2; + hdr->entry= hdr->header + mid * hdr->entry_size; + if (!string_keys) + { + val= uint2korr(hdr->entry); + flag= CMP_NUM(*((uint *)key), val); + } + else + { + if (need_conversion) + { + str.str= backwritenum(buff + sizeof(buff), uint2korr(hdr->entry)); + str.length= (buff + sizeof(buff)) - str.str; + } + else + { + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + if (read_name(hdr, hdr->entry, &str)) + return 0; + } + flag= mariadb_dyncol_column_cmp_named((LEX_STRING *)key, &str); + } + if (flag <= 0) + end= mid; + else + start= mid + 1; + } + hdr->entry= hdr->header + start * hdr->entry_size; + if (start != mid) + { + if (!string_keys) + { + val= uint2korr(hdr->entry); + flag= CMP_NUM(*((uint *)key), val); + } + else + { + if (need_conversion) + { + str.str= backwritenum(buff + sizeof(buff), uint2korr(hdr->entry)); + str.length= (buff + sizeof(buff)) - str.str; + } + else + { + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + if (read_name(hdr, hdr->entry, &str)) + return 0; + } + flag= mariadb_dyncol_column_cmp_named((LEX_STRING *)key, &str); + } + } + if (flag > 0) + hdr->entry+= hdr->entry_size; /* Point at next bigger key */ + return flag == 0; +} + + +/* + It is internal structure which describes a plan of changing the record + of dynamic columns +*/ + +typedef enum {PLAN_REPLACE, PLAN_ADD, PLAN_DELETE, PLAN_NOP} PLAN_ACT; + +struct st_plan { + DYNAMIC_COLUMN_VALUE *val; + void *key; + uchar *place; + size_t length; + long long hdelta, ddelta, ndelta; + long long mv_offset, mv_length; + uint mv_end; + PLAN_ACT act; +}; +typedef struct st_plan PLAN; + + +/** + Sort function for plan by column number +*/ + +static int plan_sort_num(const void *a, const void *b) +{ + return *((uint *)((PLAN *)a)->key) - *((uint *)((PLAN *)b)->key); +} + + +/** + Sort function for plan by column name +*/ + +static int plan_sort_named(const void *a, const void *b) +{ + return mariadb_dyncol_column_cmp_named((LEX_STRING *)((PLAN *)a)->key, + (LEX_STRING *)((PLAN *)b)->key); +} + +#define DELTA_CHECK(S, D, C) \ + if ((S) == 0) \ + (S)= (D); \ + else if (((S) > 0 && (D) < 0) || \ + ((S) < 0 && (D) > 0)) \ + { \ + (C)= TRUE; \ + } + +/** + Update dynamic column by copying in a new record (string). + + @param str Dynamic column record to change + @param plan Plan of changing the record + @param add_column_count number of records in the plan array. + @param hdr descriptor of old dynamic column record + @param new_hdr descriptor of new dynamic column record + @param convert need conversion from numeric to names format + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_update_copy(DYNAMIC_COLUMN *str, PLAN *plan, + uint add_column_count, + DYN_HEADER *hdr, DYN_HEADER *new_hdr, + my_bool convert) +{ + DYNAMIC_COLUMN tmp; + struct st_service_funcs *fmt= fmt_data + hdr->format, + *new_fmt= fmt_data + new_hdr->format; + uint i, j, k; + size_t all_headers_size; + + if (dynamic_column_init_named(&tmp, + (new_fmt->fixed_hdr + new_hdr->header_size + + new_hdr->nmpool_size + + new_hdr->data_size + DYNCOL_SYZERESERVE))) + { + return ER_DYNCOL_RESOURCE; + } + memset(tmp.str, 0, new_fmt->fixed_hdr); + (*new_fmt->set_fixed_hdr)(&tmp, new_hdr); + /* Adjust tmp to contain whole the future header */ + tmp.length= new_fmt->fixed_hdr + new_hdr->header_size + new_hdr->nmpool_size; + + + /* + Copy data to the new string + i= index in array of changes + j= index in packed string header index + */ + new_hdr->entry= new_hdr->header; + new_hdr->name= new_hdr->nmpool; + all_headers_size= new_fmt->fixed_hdr + + new_hdr->header_size + new_hdr->nmpool_size; + for (i= 0, j= 0; i < add_column_count || j < hdr->column_count; i++) + { + size_t first_offset= 0; + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= hdr->column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (hdr->header + j * hdr->entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= hdr->header + k * hdr->entry_size; + void *key; + LEX_STRING name; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + char buff[DYNCOL_NUM_CHAR]; + + if (hdr->format == dyncol_fmt_num) + { + if (convert) + { + name.str= backwritenum(buff + sizeof(buff), uint2korr(read)); + name.length= (buff + sizeof(buff)) - name.str; + key= &name; + } + else + { + nm= uint2korr(read); /* Column number */ + key= &nm; + } + } + else + { + if (read_name(hdr, read, &name)) + goto err; + key= &name; + } + if (fmt->type_and_offset_read(&tp, &offs, + read + fmt->fixed_hdr_entry, + hdr->offset_size)) + goto err; + if (k == start) + first_offset= offs; + else if (offs < first_offset) + goto err; + + offs+= (size_t)plan[i].ddelta; + { + DYNAMIC_COLUMN_VALUE val; + val.type= tp; // only the type used in the header + if ((*new_fmt->put_header_entry)(new_hdr, key, &val, offs)) + goto err; + } + } + + /* copy first the data that was not replaced in original packed data */ + if (start < end) + { + size_t data_size; + /* Add old data last in 'tmp' */ + hdr->entry= hdr->header + start * hdr->entry_size; + data_size= + hdr_interval_length(hdr, hdr->header + end * hdr->entry_size); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > hdr->data_size - first_offset) + goto err; + + memcpy(tmp.str + tmp.length, (char *)hdr->dtpool + first_offset, + data_size); + tmp.length+= data_size; + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + if ((*new_fmt->put_header_entry)(new_hdr, plan[i].key, + plan[i].val, + tmp.length - all_headers_size)) + goto err; + data_store(&tmp, plan[i].val, new_hdr->format); /* Append new data */ + } + } + } + dynamic_column_column_free(str); + *str= tmp; + return ER_DYNCOL_OK; +err: + dynamic_column_column_free(&tmp); + return ER_DYNCOL_FORMAT; +} + +static enum enum_dyncol_func_result +dynamic_column_update_move_left(DYNAMIC_COLUMN *str, PLAN *plan, + size_t offset_size, + size_t entry_size, + size_t header_size, + size_t new_offset_size, + size_t new_entry_size, + size_t new_header_size, + uint column_count, + uint new_column_count, + uint add_column_count, + uchar *header_end, + size_t max_offset) +{ + uchar *write; + uchar *header_base= (uchar *)str->str + FIXED_HEADER_SIZE; + uint i, j, k; + size_t curr_offset; + + write= (uchar *)str->str + FIXED_HEADER_SIZE; + set_fixed_header(str, (uint)new_offset_size, new_column_count); + + /* + Move headers first. + i= index in array of changes + j= index in packed string header index + */ + for (curr_offset= 0, i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + size_t UNINIT_VAR(first_offset); + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (header_base + j * entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + plan[i].mv_end= end; + + { + DYNAMIC_COLUMN_TYPE tp; + if (type_and_offset_read_num(&tp, &first_offset, + header_base + start * entry_size + + COLUMN_NUMBER_SIZE, offset_size)) + return ER_DYNCOL_FORMAT; + } + /* find data to be moved */ + if (start < end) + { + size_t data_size= + get_length_interval(header_base + start * entry_size, + header_base + end * entry_size, + header_end, offset_size, max_offset); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > max_offset - first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + DBUG_ASSERT(curr_offset == first_offset + plan[i].ddelta); + plan[i].mv_offset= first_offset; + plan[i].mv_length= data_size; + curr_offset+= data_size; + } + else + { + plan[i].mv_length= 0; + plan[i].mv_offset= curr_offset; + } + + if (plan[i].ddelta == 0 && offset_size == new_offset_size && + plan[i].act != PLAN_DELETE) + write+= entry_size * (end - start); + else + { + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= header_base + k * entry_size; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); /* Column number */ + if (type_and_offset_read_num(&tp, &offs, read + COLUMN_NUMBER_SIZE, + offset_size)) + return ER_DYNCOL_FORMAT; + + if (k > start && offs < first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + + offs+= (size_t)plan[i].ddelta; + int2store(write, nm); + /* write rest of data at write + COLUMN_NUMBER_SIZE */ + type_and_offset_store_num(write, new_offset_size, tp, offs); + write+= new_entry_size; + } + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + int2store(write, *((uint *)plan[i].key)); + type_and_offset_store_num(write, new_offset_size, + plan[i].val[0].type, + curr_offset); + write+= new_entry_size; + curr_offset+= plan[i].length; + } + } + } + + /* + Move data. + i= index in array of changes + j= index in packed string header index + */ + str->length= (FIXED_HEADER_SIZE + new_header_size); + for (i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + j= end= plan[i].mv_end; + if (i != add_column_count && + (plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; + + /* copy first the data that was not replaced in original packed data */ + if (start < end && plan[i].mv_length) + { + memmove((header_base + new_header_size + + (size_t)plan[i].mv_offset + (size_t)plan[i].ddelta), + header_base + header_size + (size_t)plan[i].mv_offset, + (size_t)plan[i].mv_length); + } + str->length+= (size_t)plan[i].mv_length; + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + data_store(str, plan[i].val, dyncol_fmt_num);/* Append new data */ + } + } + } + return ER_DYNCOL_OK; +} + +#ifdef UNUSED +static enum enum_dyncol_func_result +dynamic_column_update_move_right(DYNAMIC_COLUMN *str, PLAN *plan, + size_t offset_size, + size_t entry_size, + size_t header_size, + size_t new_offset_size, + size_t new_entry_size, + size_t new_header_size, + uint column_count, + uint new_column_count, + uint add_column_count, + uchar *header_end, + size_t max_offset) +{ + uchar *write; + uchar *header_base= (uchar *)str->str + FIXED_HEADER_SIZE; + uint i, j, k; + size_t curr_offset; + + write= (uchar *)str->str + FIXED_HEADER_SIZE; + set_fixed_header(str, new_offset_size, new_column_count); + + /* + Move data first. + i= index in array of changes + j= index in packed string header index + */ + for (curr_offset= 0, i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + size_t UNINIT_VAR(first_offset); + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (header_base + j * entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + plan[i].mv_end= end; + + { + DYNAMIC_COLUMN_TYPE tp; + type_and_offset_read_num(&tp, &first_offset, + header_base + + start * entry_size + COLUMN_NUMBER_SIZE, + offset_size); + } + /* find data to be moved */ + if (start < end) + { + size_t data_size= + get_length_interval(header_base + start * entry_size, + header_base + end * entry_size, + header_end, offset_size, max_offset); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > max_offset - first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + DBUG_ASSERT(curr_offset == first_offset + plan[i].ddelta); + plan[i].mv_offset= first_offset; + plan[i].mv_length= data_size; + curr_offset+= data_size; + } + else + { + plan[i].mv_length= 0; + plan[i].mv_offset= curr_offset; + } + + if (plan[i].ddelta == 0 && offset_size == new_offset_size && + plan[i].act != PLAN_DELETE) + write+= entry_size * (end - start); + else + { + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= header_base + k * entry_size; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); /* Column number */ + type_and_offset_read_num(&tp, &offs, read + COLUMN_NUMBER_SIZE, + offset_size); + if (k > start && offs < first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + + offs+= plan[i].ddelta; + int2store(write, nm); + /* write rest of data at write + COLUMN_NUMBER_SIZE */ + if (type_and_offset_store_num(write, new_offset_size, tp, offs)) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + write+= new_entry_size; + } + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + int2store(write, *((uint *)plan[i].key)); + if (type_and_offset_store_num(write, new_offset_size, + plan[i].val[0].type, + curr_offset)) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + write+= new_entry_size; + curr_offset+= plan[i].length; + } + } + } + + /* + Move headers. + i= index in array of changes + j= index in packed string header index + */ + str->length= (FIXED_HEADER_SIZE + new_header_size); + for (i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + j= end= plan[i].mv_end; + if (i != add_column_count && + (plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; + + /* copy first the data that was not replaced in original packed data */ + if (start < end && plan[i].mv_length) + { + memmove((header_base + new_header_size + + plan[i].mv_offset + plan[i].ddelta), + header_base + header_size + plan[i].mv_offset, + plan[i].mv_length); + } + str->length+= plan[i].mv_length; + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + data_store(str, plan[i].val, dyncol_fmt_num); /* Append new data */ + } + } + } + return ER_DYNCOL_OK; +} +#endif + +/** + Update the packed string with the given columns + + @param str String where to write the data + @param add_column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ +/* plan allocated on the stack */ +#define IN_PLACE_PLAN 4 + +enum enum_dyncol_func_result +dynamic_column_update_many(DYNAMIC_COLUMN *str, + uint add_column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_numbers, + values, FALSE); +} + +enum enum_dyncol_func_result +mariadb_dyncol_update_many_num(DYNAMIC_COLUMN *str, + uint add_column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_numbers, + values, FALSE); +} + +enum enum_dyncol_func_result +mariadb_dyncol_update_many_named(DYNAMIC_COLUMN *str, + uint add_column_count, + LEX_STRING *column_names, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_names, + values, TRUE); +} + +static uint numlen(uint val) +{ + uint res; + if (val == 0) + return 1; + res= 0; + while(val) + { + res++; + val/=10; + } + return res; +} + +static enum enum_dyncol_func_result +dynamic_column_update_many_fmt(DYNAMIC_COLUMN *str, + uint add_column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool string_keys) +{ + PLAN *plan, *alloc_plan= NULL, in_place_plan[IN_PLACE_PLAN]; + uchar *element; + DYN_HEADER header, new_header; + struct st_service_funcs *fmt, *new_fmt; + long long data_delta= 0, name_delta= 0; + uint i; + uint not_null; + long long header_delta= 0; + long long header_delta_sign, data_delta_sign; + int copy= FALSE; + enum enum_dyncol_func_result rc; + my_bool convert; + + if (add_column_count == 0) + return ER_DYNCOL_OK; + + memset(&header, 0, sizeof(header)); + memset(&new_header, 0, sizeof(new_header)); + new_header.format= (string_keys ? dyncol_fmt_str : dyncol_fmt_num); + new_fmt= fmt_data + new_header.format; + + /* + Get columns in column order. As the data in 'str' is already + in column order this allows to replace all columns in one loop. + */ + if (IN_PLACE_PLAN > add_column_count) + plan= in_place_plan; + else if (!(alloc_plan= plan= + (PLAN *)malloc(sizeof(PLAN) * (add_column_count + 1)))) + return ER_DYNCOL_RESOURCE; + + not_null= add_column_count; + for (i= 0, element= (uchar *) column_keys; + i < add_column_count; + i++, element+= new_fmt->key_size_in_array) + { + if ((*new_fmt->check_limit)(&element)) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + plan[i].val= values + i; + plan[i].key= element; + if (values[i].type == DYN_COL_NULL) + not_null--; + + } + + if (str->length == 0) + { + /* + Just add new columns. If there was no columns to add we return + an empty string. + */ + goto create_new_string; + } + + /* Check that header is ok */ + if ((rc= init_read_hdr(&header, str)) < 0) + goto end; + fmt= fmt_data + header.format; + /* new format can't be numeric if the old one is names */ + DBUG_ASSERT(new_header.format == dyncol_fmt_str || + header.format == dyncol_fmt_num); + if (header.column_count == 0) + goto create_new_string; + + qsort(plan, (size_t)add_column_count, sizeof(PLAN), new_fmt->plan_sort); + + new_header.column_count= header.column_count; + new_header.nmpool_size= header.nmpool_size; + if ((convert= (new_header.format == dyncol_fmt_str && + header.format == dyncol_fmt_num))) + { + DBUG_ASSERT(new_header.nmpool_size == 0); + for(i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + new_header.nmpool_size+= numlen(uint2korr(header.entry)); + } + } + + if (fmt->fixed_hdr + header.header_size + header.nmpool_size > str->length) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + /* + Calculate how many columns and data is added/deleted and make a 'plan' + for each of them. + */ + for (i= 0; i < add_column_count; i++) + { + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + if (i < add_column_count - 1 && + new_fmt->column_sort(&plan[i].key, &plan[i + 1].key) == 0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + /* Set common variables for all plans */ + plan[i].ddelta= data_delta; + plan[i].ndelta= name_delta; + /* get header delta in entries */ + plan[i].hdelta= header_delta; + plan[i].length= 0; /* Length if NULL */ + + if (find_place(&header, plan[i].key, string_keys)) + { + size_t entry_data_size, entry_name_size= 0; + + /* Data existed; We have to replace or delete it */ + + entry_data_size= hdr_interval_length(&header, header.entry + + header.entry_size); + if (entry_data_size == DYNCOL_OFFSET_ERROR || + (long) entry_data_size < 0) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + if (new_header.format == dyncol_fmt_str) + { + if (header.format == dyncol_fmt_str) + { + LEX_STRING name; + if (read_name(&header, header.entry, &name)) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + entry_name_size= name.length; + } + else + entry_name_size= numlen(uint2korr(header.entry)); + } + + if (plan[i].val->type == DYN_COL_NULL) + { + /* Inserting a NULL means delete the old data */ + + plan[i].act= PLAN_DELETE; /* Remove old value */ + header_delta--; /* One row less in header */ + data_delta-= entry_data_size; /* Less data to store */ + name_delta-= entry_name_size; + } + else + { + /* Replace the value */ + + plan[i].act= PLAN_REPLACE; + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val, + new_header.format)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length - entry_data_size; + if (new_header.format == dyncol_fmt_str) + { + name_delta+= ((LEX_STRING *)(plan[i].key))->length - entry_name_size; + } + } + } + else + { + /* Data did not exists. Add if it it's not NULL */ + + if (plan[i].val->type == DYN_COL_NULL) + { + plan[i].act= PLAN_NOP; /* Mark entry to be skipped */ + } + else + { + /* Add new value */ + + plan[i].act= PLAN_ADD; + header_delta++; /* One more row in header */ + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val, + new_header.format)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length; + if (new_header.format == dyncol_fmt_str) + name_delta+= ((LEX_STRING *)plan[i].key)->length; + } + } + plan[i].place= header.entry; + } + plan[add_column_count].hdelta= header_delta; + plan[add_column_count].ddelta= data_delta; + plan[add_column_count].act= PLAN_NOP; + plan[add_column_count].place= header.dtpool; + + new_header.column_count= (uint)(header.column_count + header_delta); + + /* + Check if it is only "increasing" or only "decreasing" plan for (header + and data separately). + */ + new_header.data_size= header.data_size + (size_t)data_delta; + new_header.nmpool_size= new_header.nmpool_size + (size_t)name_delta; + DBUG_ASSERT(new_header.format != dyncol_fmt_num || + new_header.nmpool_size == 0); + if ((new_header.offset_size= + new_fmt->dynamic_column_offset_bytes(new_header.data_size)) >= + new_fmt->max_offset_size) + { + rc= ER_DYNCOL_LIMIT; + goto end; + } + + copy= ((header.format != new_header.format) || + (new_header.format == dyncol_fmt_str)); + /* if (new_header.offset_size!=offset_size) then we have to rewrite header */ + header_delta_sign= + ((int)new_header.offset_size + new_fmt->fixed_hdr_entry) - + ((int)header.offset_size + fmt->fixed_hdr_entry); + data_delta_sign= 0; + // plan[add_column_count] contains last deltas. + for (i= 0; i <= add_column_count && !copy; i++) + { + /* This is the check for increasing/decreasing */ + DELTA_CHECK(header_delta_sign, plan[i].hdelta, copy); + DELTA_CHECK(data_delta_sign, plan[i].ddelta, copy); + } + calc_param(&new_header.entry_size, &new_header.header_size, + new_fmt->fixed_hdr_entry, + new_header.offset_size, new_header.column_count); + + /* + Need copy because: + 1, Header/data parts moved in different directions. + 2. There is no enough allocated space in the string. + 3. Header and data moved in different directions. + */ + if (copy || /*1.*/ + str->max_length < str->length + header_delta + data_delta || /*2.*/ + ((header_delta_sign < 0 && data_delta_sign > 0) || + (header_delta_sign > 0 && data_delta_sign < 0))) /*3.*/ + rc= dynamic_column_update_copy(str, plan, add_column_count, + &header, &new_header, + convert); + else + if (header_delta_sign < 0) + rc= dynamic_column_update_move_left(str, plan, header.offset_size, + header.entry_size, + header.header_size, + new_header.offset_size, + new_header.entry_size, + new_header.header_size, + header.column_count, + new_header.column_count, + add_column_count, header.dtpool, + header.data_size); + else + /* + rc= dynamic_column_update_move_right(str, plan, offset_size, + entry_size, header_size, + new_header.offset_size, + new_header.entry_size, + new_heder.header_size, column_count, + new_header.column_count, + add_column_count, header_end, + header.data_size); + */ + rc= dynamic_column_update_copy(str, plan, add_column_count, + &header, &new_header, + convert); +end: + free(alloc_plan); + return rc; + +create_new_string: + /* There is no columns from before, so let's just add the new ones */ + rc= ER_DYNCOL_OK; + if (not_null != 0) + rc= dynamic_column_create_many_internal_fmt(str, add_column_count, + (uint*)column_keys, values, + str->str == NULL, + string_keys); + goto end; +} + + +/** + Update the packed string with the given column + + @param str String where to write the data + @param column_number Array of columns number + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + + +int dynamic_column_update(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + return dynamic_column_update_many(str, 1, &column_nr, value); +} + + +enum enum_dyncol_func_result +mariadb_dyncol_check(DYNAMIC_COLUMN *str) +{ + struct st_service_funcs *fmt; + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + DYN_HEADER header; + uint i; + size_t data_offset= 0, name_offset= 0; + size_t prev_data_offset= 0, prev_name_offset= 0; + LEX_STRING name= {0,0}, prev_name= {0,0}; + uint num= 0, prev_num= 0; + void *key, *prev_key; + enum enum_dynamic_column_type type= DYN_COL_NULL, prev_type= DYN_COL_NULL; + + if (str->length == 0) + { + return(ER_DYNCOL_OK); + } + + memset(&header, 0, sizeof(header)); + + /* Check that header is OK */ + if (read_fixed_header(&header, str)) + { + goto end; + } + fmt= fmt_data + header.format; + calc_param(&header.entry_size, &header.header_size, + fmt->fixed_hdr_entry, header.offset_size, + header.column_count); + /* headers are out of string length (no space for data and part of headers) */ + if (fmt->fixed_hdr + header.header_size + header.nmpool_size > str->length) + { + goto end; + } + header.header= (uchar*)str->str + fmt->fixed_hdr; + header.nmpool= header.header + header.header_size; + header.dtpool= header.nmpool + header.nmpool_size; + header.data_size= str->length - fmt->fixed_hdr - + header.header_size - header.nmpool_size; + + /* read and check headers */ + if (header.format == dyncol_fmt_num) + { + key= # + prev_key= &prev_num; + } + else + { + key= &name; + prev_key= &prev_name; + } + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + + if (header.format == dyncol_fmt_num) + { + num= uint2korr(header.entry); + } + else + { + DBUG_ASSERT(header.format == dyncol_fmt_str); + if (read_name(&header, header.entry, &name)) + { + goto end; + } + name_offset= name.str - (char *)header.nmpool; + } + if ((*fmt->type_and_offset_read)(&type, &data_offset, + header.entry + fmt->fixed_hdr_entry, + header.offset_size)) + goto end; + + DBUG_ASSERT(type != DYN_COL_NULL); + if (data_offset > header.data_size) + { + goto end; + } + if (prev_type != DYN_COL_NULL) + { + /* It is not first entry */ + if (prev_data_offset >= data_offset) + { + goto end; + } + if (prev_name_offset > name_offset) + { + goto end; + } + if ((*fmt->column_sort)(&prev_key, &key) >= 0) + { + goto end; + } + } + prev_num= num; + prev_name= name; + prev_data_offset= data_offset; + prev_name_offset= name_offset; + prev_type= type; + } + + /* check data, which we can */ + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + DYNAMIC_COLUMN_VALUE store; + /* already checked by previous pass */ + (*fmt->type_and_offset_read)(&header.type, &header.offset, + header.entry + fmt->fixed_hdr_entry, + header.offset_size); + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + switch ((header.type)) { + case DYN_COL_INT: + rc= dynamic_column_sint_read(&store, header.data, header.length); + break; + case DYN_COL_UINT: + rc= dynamic_column_uint_read(&store, header.data, header.length); + break; + case DYN_COL_DOUBLE: + rc= dynamic_column_double_read(&store, header.data, header.length); + break; + case DYN_COL_STRING: + rc= dynamic_column_string_read(&store, header.data, header.length); + break; +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + rc= dynamic_column_decimal_read(&store, header.data, header.length); + break; +#endif + case DYN_COL_DATETIME: + rc= dynamic_column_date_time_read(&store, header.data, + header.length); + break; + case DYN_COL_DATE: + rc= dynamic_column_date_read(&store, header.data, header.length); + break; + case DYN_COL_TIME: + rc= dynamic_column_time_read(&store, header.data, header.length); + break; + case DYN_COL_DYNCOL: + rc= dynamic_column_dyncol_read(&store, header.data, header.length); + break; + case DYN_COL_NULL: + default: + rc= ER_DYNCOL_FORMAT; + goto end; + } + if (rc != ER_DYNCOL_OK) + { + DBUG_ASSERT(rc < 0); + goto end; + } + } + + rc= ER_DYNCOL_OK; +end: + return(rc); +} + +enum enum_dyncol_func_result +mariadb_dyncol_val_str(DYNAMIC_STRING *str, DYNAMIC_COLUMN_VALUE *val, + MARIADB_CHARSET_INFO *cs, char quote) +{ + char buff[40]; + size_t len; + switch (val->type) { + case DYN_COL_INT: + len= snprintf(buff, sizeof(buff), "%lld", val->x.long_value); + if (ma_dynstr_append_mem(str, buff, len)) + return ER_DYNCOL_RESOURCE; + break; + case DYN_COL_UINT: + len= snprintf(buff, sizeof(buff), "%llu", val->x.ulong_value); + if (ma_dynstr_append_mem(str, buff, len)) + return ER_DYNCOL_RESOURCE; + break; + case DYN_COL_DOUBLE: + len= snprintf(buff, sizeof(buff), "%g", val->x.double_value); + if (ma_dynstr_realloc(str, len + (quote ? 2 : 0))) + return ER_DYNCOL_RESOURCE; + if (quote) + str->str[str->length++]= quote; + ma_dynstr_append_mem(str, buff, len); + if (quote) + str->str[str->length++]= quote; + break; + case DYN_COL_DYNCOL: + case DYN_COL_STRING: + { + char *alloc= NULL; + char *from= val->x.string.value.str; + ulong bufflen; + my_bool conv= ((val->x.string.charset == cs) || + !strcmp(val->x.string.charset->name, cs->name)); + my_bool rc; + len= val->x.string.value.length; + bufflen= (ulong)(len * (conv ? cs->char_maxlen : 1)); + if (ma_dynstr_realloc(str, bufflen)) + return ER_DYNCOL_RESOURCE; + + // guaranty UTF-8 string for value + if (!conv) + { +#ifndef LIBMARIADB + uint dumma_errors; +#else + int dumma_errors; +#endif + if (!quote) + { + /* convert to the destination */ + str->length+= +#ifndef LIBMARIADB + copy_and_convert_extended(str->str, bufflen, + cs, + from, (uint32)len, + val->x.string.charset, + &dumma_errors); +#else + mariadb_convert_string(from, &len, val->x.string.charset, + str->str, (size_t *)&bufflen, cs, &dumma_errors); +#endif + return ER_DYNCOL_OK; + } + if ((alloc= (char *)malloc(bufflen))) + { + len= +#ifndef LIBMARIADB + copy_and_convert_extended(alloc, bufflen, cs, + from, (uint32)len, + val->x.string.charset, + &dumma_errors); +#else + mariadb_convert_string(from, &len, val->x.string.charset, + alloc, (size_t *)&bufflen, cs, &dumma_errors); +#endif + from= alloc; + } + else + return ER_DYNCOL_RESOURCE; + } + if (quote) + rc= ma_dynstr_append_quoted(str, from, len, quote); + else + rc= ma_dynstr_append_mem(str, from, len); + if (alloc) + free(alloc); + if (rc) + return ER_DYNCOL_RESOURCE; + break; + } +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + { + int len= sizeof(buff); + decimal2string(&val->x.decimal.value, buff, &len, + 0, val->x.decimal.value.frac, + '0'); + if (ma_dynstr_append_mem(str, buff, len)) + return ER_DYNCOL_RESOURCE; + break; + } +#endif + case DYN_COL_DATETIME: + case DYN_COL_DATE: + case DYN_COL_TIME: +#ifndef LIBMARIADB + len= my_TIME_to_str(&val->x.time_value, buff, AUTO_SEC_PART_DIGITS); +#else + len= mariadb_time_to_string(&val->x.time_value, buff, 39, AUTO_SEC_PART_DIGITS); +#endif + if (ma_dynstr_realloc(str, len + (quote ? 2 : 0))) + return ER_DYNCOL_RESOURCE; + if (quote) + str->str[str->length++]= '"'; + ma_dynstr_append_mem(str, buff, len); + if (quote) + str->str[str->length++]= '"'; + break; + case DYN_COL_NULL: + if (ma_dynstr_append_mem(str, "null", 4)) + return ER_DYNCOL_RESOURCE; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(ER_DYNCOL_OK); +} + +enum enum_dyncol_func_result +mariadb_dyncol_val_long(longlong *ll, DYNAMIC_COLUMN_VALUE *val) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_OK; + *ll= 0; + switch (val->type) { + case DYN_COL_INT: + *ll= val->x.long_value; + break; + case DYN_COL_UINT: + *ll= (longlong)val->x.ulong_value; + if (*ll > (longlong)ULONGLONG_MAX) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DOUBLE: + *ll= (longlong)val->x.double_value; + if (((double) *ll) != val->x.double_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_STRING: + { + char *src= val->x.string.value.str; + size_t len= val->x.string.value.length; + longlong i= 0, sign= 1; + + while (len && isspace(*src)) src++,len--; + + if (len) + { + if (*src == '-') + { + sign= -1; + src++; + } + while(len && isdigit(*src)) + { + i= i * 10 + (*src - '0'); + src++; + } + } + else + rc= ER_DYNCOL_TRUNCATED; + if (len) + rc= ER_DYNCOL_TRUNCATED; + *ll= i * sign; + break; + } +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + if (decimal2longlong(&val->x.decimal.value, ll) != E_DEC_OK) + rc= ER_DYNCOL_TRUNCATED; + break; +#endif + case DYN_COL_DATETIME: + *ll= (val->x.time_value.year * 10000000000ull + + val->x.time_value.month * 100000000L + + val->x.time_value.day * 1000000 + + val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DATE: + *ll= (val->x.time_value.year * 10000 + + val->x.time_value.month * 100 + + val->x.time_value.day) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_TIME: + *ll= (val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DYNCOL: + case DYN_COL_NULL: + rc= ER_DYNCOL_TRUNCATED; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(rc); +} + + +enum enum_dyncol_func_result +mariadb_dyncol_val_double(double *dbl, DYNAMIC_COLUMN_VALUE *val) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_OK; + *dbl= 0; + switch (val->type) { + case DYN_COL_INT: + *dbl= (double)val->x.long_value; + if (((longlong) *dbl) != val->x.long_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_UINT: + *dbl= (double)val->x.ulong_value; + if (((ulonglong) *dbl) != val->x.ulong_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DOUBLE: + *dbl= val->x.double_value; + break; + case DYN_COL_STRING: + { + char *str, *end; + if (!(str= malloc(val->x.string.value.length + 1))) + return ER_DYNCOL_RESOURCE; + memcpy(str, val->x.string.value.str, val->x.string.value.length); + str[val->x.string.value.length]= '\0'; + *dbl= strtod(str, &end); + if (*end != '\0') + rc= ER_DYNCOL_TRUNCATED; + free(str); + break; + } +#ifndef LIBMARIADB + case DYN_COL_DECIMAL: + if (decimal2double(&val->x.decimal.value, dbl) != E_DEC_OK) + rc= ER_DYNCOL_TRUNCATED; + break; +#endif + case DYN_COL_DATETIME: + *dbl= (double)(val->x.time_value.year * 10000000000ull + + val->x.time_value.month * 100000000L + + val->x.time_value.day * 1000000 + + val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DATE: + *dbl= (double)(val->x.time_value.year * 10000 + + val->x.time_value.month * 100 + + val->x.time_value.day) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_TIME: + *dbl= (double)(val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DYNCOL: + case DYN_COL_NULL: + rc= ER_DYNCOL_TRUNCATED; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(rc); +} + + +/** + Convert to JSON + + @param str The packed string + @param json Where to put json result + + @return ER_DYNCOL_* return code +*/ + +#define JSON_STACK_PROTECTION 10 + +static enum enum_dyncol_func_result +mariadb_dyncol_json_internal(DYNAMIC_COLUMN *str, DYNAMIC_STRING *json, + uint lvl) +{ + DYN_HEADER header; + uint i; + enum enum_dyncol_func_result rc; + + if (lvl >= JSON_STACK_PROTECTION) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + goto err; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + + rc= ER_DYNCOL_RESOURCE; + + if (ma_dynstr_append_mem(json, "{", 1)) + goto err; + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + DYNAMIC_COLUMN_VALUE val; + if (i != 0 && ma_dynstr_append_mem(json, ",", 1)) + goto err; + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (header.length == DYNCOL_OFFSET_ERROR || + header.length > INT_MAX || header.offset > header.data_size) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if ((rc= dynamic_column_get_value(&header, &val)) < 0) + goto err; + if (header.format == dyncol_fmt_num) + { + uint nm= uint2korr(header.entry); + if (ma_dynstr_realloc(json, DYNCOL_NUM_CHAR + 3)) + goto err; + json->str[json->length++]= '"'; + json->length+= snprintf(json->str + json->length, + DYNCOL_NUM_CHAR, "%u", nm); + } + else + { + LEX_STRING name; + if (read_name(&header, header.entry, &name)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if (ma_dynstr_realloc(json, name.length + 3)) + goto err; + json->str[json->length++]= '"'; + memcpy(json->str + json->length, name.str, name.length); + json->length+= name.length; + } + json->str[json->length++]= '"'; + json->str[json->length++]= ':'; + if (val.type == DYN_COL_DYNCOL) + { + /* here we use it only for read so can cheat a bit */ + DYNAMIC_COLUMN dc; + memset(&dc, 0, sizeof(dc)); + dc.str= val.x.string.value.str; + dc.length= val.x.string.value.length; + if (mariadb_dyncol_json_internal(&dc, json, lvl + 1) < 0) + { + dc.str= NULL; dc.length= 0; + goto err; + } + dc.str= NULL; dc.length= 0; + } + else + { + if ((rc= mariadb_dyncol_val_str(json, &val, + ma_charset_utf8_general_ci, '"')) < 0) + goto err; + } + } + if (ma_dynstr_append_mem(json, "}", 1)) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + return ER_DYNCOL_OK; + +err: + json->length= 0; + return rc; +} + +enum enum_dyncol_func_result +mariadb_dyncol_json(DYNAMIC_COLUMN *str, DYNAMIC_STRING *json) +{ + + if (ma_init_dynamic_string(json, NULL, str->length * 2, 100)) + return ER_DYNCOL_RESOURCE; + + return mariadb_dyncol_json_internal(str, json, 1); +} + +/** + Convert to DYNAMIC_COLUMN_VALUE values and names (LEX_STING) dynamic array + + @param str The packed string + @param count number of elements in the arrays + @param names Where to put names (should be free by user) + @param vals Where to put values (should be free by user) + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_unpack(DYNAMIC_COLUMN *str, + uint *count, + LEX_STRING **names, DYNAMIC_COLUMN_VALUE **vals) +{ + DYN_HEADER header; + char *nm; + uint i; + enum enum_dyncol_func_result rc; + + *count= 0; *names= 0; *vals= 0; + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + *vals= (DYNAMIC_COLUMN_VALUE *)malloc(sizeof(DYNAMIC_COLUMN_VALUE)* header.column_count); + if (header.format == dyncol_fmt_num) + { + *names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count + + DYNCOL_NUM_CHAR * header.column_count); + nm= (char *)((*names) + header.column_count); + } + else + { + *names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count); + nm= 0; + } + if (!(*vals) || !(*names)) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (header.length == DYNCOL_OFFSET_ERROR || + header.length > INT_MAX || header.offset > header.data_size) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if ((rc= dynamic_column_get_value(&header, (*vals) + i)) < 0) + goto err; + + if (header.format == dyncol_fmt_num) + { + uint num= uint2korr(header.entry); + (*names)[i].str= nm; + (*names)[i].length= snprintf(nm, DYNCOL_NUM_CHAR, "%u", num); + nm+= (*names)[i].length + 1; + } + else + { + if (read_name(&header, header.entry, (*names) + i)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + } + } + + *count= header.column_count; + return ER_DYNCOL_OK; + +err: + if (*vals) + { + free(*vals); + *vals= 0; + } + if (*names) + { + free(*names); + *names= 0; + } + return rc; +} + + +/** + Get not NULL column count + + @param str The packed string + @param column_count Where to put column count + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_column_count(DYNAMIC_COLUMN *str, uint *column_count) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + + (*column_count)= 0; + if (str->length == 0) + return ER_DYNCOL_OK; + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + *column_count= header.column_count; + return rc; +} + +/** + Release dynamic column memory + + @param str dynamic column + @return void +*/ +void mariadb_dyncol_free(DYNAMIC_COLUMN *str) +{ + ma_dynstr_free(str); +} diff --git a/libmariadb/libmariadb/mariadb_lib.c b/libmariadb/libmariadb/mariadb_lib.c new file mode 100644 index 00000000..e8db51a0 --- /dev/null +++ b/libmariadb/libmariadb/mariadb_lib.c @@ -0,0 +1,4979 @@ +/************************************************************************************ + Copyright (C) 2000, 2012 MySQL AB & MySQL Finland AB & TCX DataKonsult AB, + Monty Program AB + 2013, 2022 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ + +#include <ma_global.h> + +#include <ma_sys.h> +#include <ma_string.h> +#include <mariadb_ctype.h> +#include <ma_common.h> +#include "ma_priv.h" +#include "ma_context.h" +#include "mysql.h" +#include "mariadb_version.h" +#include "ma_server_error.h" +#include <mariadb/ma_io.h> +#include "errmsg.h" +#include <sys/stat.h> +#include <signal.h> +#include <time.h> +#include <mariadb_dyncol.h> +#include <mariadb_rpl.h> + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#if !defined(_WIN32) +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#ifdef HAVE_SELECT_H +# include <select.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif +#ifdef HAVE_SYS_UN_H +# include <sys/un.h> +#endif +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif +#include <ma_sha1.h> +#ifndef _WIN32 +#include <poll.h> +#endif +#include <ma_pvio.h> +#ifdef HAVE_TLS +#include <ma_tls.h> +#endif +#include <mysql/client_plugin.h> +#ifdef _WIN32 +#include "shlwapi.h" +#include "ws2tcpip.h" +#define strncasecmp _strnicmp +#endif + +#define ASYNC_CONTEXT_DEFAULT_STACK_SIZE (4096*15) +#define MA_RPL_VERSION_HACK "5.5.5-" + +#define CHARSET_NAME_LEN 64 + +#undef max_allowed_packet +#undef net_buffer_length +extern ulong max_allowed_packet; /* net.c */ +extern ulong net_buffer_length; /* net.c */ + +static MYSQL_PARAMETERS mariadb_internal_parameters= {&max_allowed_packet, &net_buffer_length, 0}; +static my_bool mysql_client_init=0; +static void mysql_close_options(MYSQL *mysql); +static void ma_clear_session_state(MYSQL *mysql); +static int parse_connection_string(MYSQL *mysql, const char *unused, const char *conn_str, ssize_t len); +extern void release_configuration_dirs(); +extern char **get_default_configuration_dirs(); +extern my_bool ma_init_done; +extern my_bool mysql_ps_subsystem_initialized; +extern my_bool mysql_handle_local_infile(MYSQL *mysql, const char *filename, my_bool can_local_infile); +extern const MARIADB_CHARSET_INFO * mysql_find_charset_nr(uint charsetnr); +extern const MARIADB_CHARSET_INFO * mysql_find_charset_name(const char * const name); +extern my_bool set_default_charset_by_name(const char *cs_name, myf flags __attribute__((unused))); +extern int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, + const char *data_plugin, const char *db); +extern int net_add_multi_command(NET *net, uchar command, const uchar *packet, + size_t length); +extern unsigned char* ma_stmt_execute_generate_request(MYSQL_STMT *stmt, size_t *request_len, my_bool internal); + +extern LIST *pvio_callback; + +/* prepare statement methods from my_stmt.c */ +extern my_bool mthd_supported_buffer_type(enum enum_field_types type); +extern my_bool mthd_stmt_read_prepare_response(MYSQL_STMT *stmt); +extern my_bool mthd_stmt_get_param_metadata(MYSQL_STMT *stmt); +extern my_bool mthd_stmt_get_result_metadata(MYSQL_STMT *stmt); +extern int mthd_stmt_read_execute_response(MYSQL_STMT *stmt); +extern int mthd_stmt_fetch_row(MYSQL_STMT *stmt, unsigned char **row); +extern int mthd_stmt_fetch_to_bind(MYSQL_STMT *stmt, unsigned char *row); +extern int mthd_stmt_read_all_rows(MYSQL_STMT *stmt); +extern void mthd_stmt_flush_unbuffered(MYSQL_STMT *stmt); +extern my_bool _mariadb_read_options(MYSQL *mysql, const char *dir, const char *config_file, const char *group, unsigned int recursion); +extern unsigned char *mysql_net_store_length(unsigned char *packet, ulonglong length); + +extern void +my_context_install_suspend_resume_hook(struct mysql_async_context *b, + void (*hook)(my_bool, void *), + void *user_data); + +uint mysql_port=0; +my_string mysql_unix_port=0; + +#define CONNECT_TIMEOUT 0 + +struct st_mariadb_methods MARIADB_DEFAULT_METHODS; + +#if defined(_WIN32) +// socket_errno is defined in ma_global.h for all platforms +#define perror(A) +#else +#include <errno.h> +#define SOCKET_ERROR -1 +#endif /* _WIN32 */ + +#include <mysql/client_plugin.h> + +#define IS_CONNHDLR_ACTIVE(mysql)\ + ((mysql)->extension && (mysql)->extension->conn_hdlr) + +static void end_server(MYSQL *mysql); +static void mysql_close_memory(MYSQL *mysql); +void read_user_name(char *name); +my_bool STDCALL mariadb_reconnect(MYSQL *mysql); +static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length); + +extern int mysql_client_plugin_init(); +extern void mysql_client_plugin_deinit(); + +/* net_get_error */ +void net_get_error(char *buf, size_t buf_len, + char *error, size_t error_len, + unsigned int *error_no, + char *sqlstate) +{ + char *p= buf; + size_t error_msg_len= 0; + + if (buf_len > 2) + { + *error_no= uint2korr(p); + p+= 2; + + /* since 4.1 sqlstate is following */ + if (*p == '#') + { + memcpy(sqlstate, ++p, SQLSTATE_LENGTH); + p+= SQLSTATE_LENGTH; + } + error_msg_len= buf_len - (p - buf); + error_msg_len= MIN(error_msg_len, error_len - 1); + memcpy(error, p, error_msg_len); + } + else + { + *error_no= CR_UNKNOWN_ERROR; + memcpy(sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH); + } +} + +/***************************************************************************** +** read a packet from server. Give error message if socket was down +** or packet is an error message +*****************************************************************************/ + +ulong +ma_net_safe_read(MYSQL *mysql) +{ + NET *net= &mysql->net; + ulong len=0; + +restart: + if (net->pvio != 0) + len=ma_net_read(net); + + if (len == packet_error || len == 0) + { + end_server(mysql); +#ifdef HAVE_TLS + /* don't overwrite possible tls protocol errors */ + if (net->last_errno != CR_SSL_CONNECTION_ERROR) +#endif + { + my_set_error(mysql, net->last_errno == ER_NET_PACKET_TOO_LARGE ? + CR_NET_PACKET_TOO_LARGE: + CR_SERVER_LOST, + SQLSTATE_UNKNOWN, 0, errno); + } + return(packet_error); + } + if (net->read_pos[0] == 255) + { + if (len > 3) + { + char *pos=(char*) net->read_pos+1; + uint last_errno=uint2korr(pos); + pos+=2; + len-=2; + + if (last_errno== 65535 && + ((mariadb_connection(mysql) && (mysql->server_capabilities & CLIENT_PROGRESS)) || + (!(mysql->extension->mariadb_server_capabilities & MARIADB_CLIENT_PROGRESS << 32)))) + { + if (cli_report_progress(mysql, (uchar *)pos, (uint) (len-1))) + { + /* Wrong packet */ + my_set_error(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0); + return (packet_error); + } + goto restart; + } + net->last_errno= last_errno; + if (pos[0]== '#') + { + ma_strmake(net->sqlstate, pos+1, SQLSTATE_LENGTH); + pos+= SQLSTATE_LENGTH + 1; + } + else + { + strncpy(net->sqlstate, SQLSTATE_UNKNOWN, SQLSTATE_LENGTH); + } + ma_strmake(net->last_error,(char*) pos, + min(len,sizeof(net->last_error)-1)); + } + else + { + my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0); + } + + mysql->server_status&= ~SERVER_MORE_RESULTS_EXIST; + + return(packet_error); + } + return len; +} + +/* + Report progress to the client + + RETURN VALUES + 0 ok + 1 error +*/ +static int cli_report_progress(MYSQL *mysql, uchar *packet, uint length) +{ + uint stage, max_stage, proc_length; + double progress; + uchar *start= packet; + + if (length < 5) + return 1; /* Wrong packet */ + + if (!(mysql->options.extension && mysql->options.extension->report_progress)) + return 0; /* No callback, ignore packet */ + + packet++; /* Ignore number of strings */ + stage= (uint) *packet++; + max_stage= (uint) *packet++; + progress= uint3korr(packet)/1000.0; + packet+= 3; + proc_length= net_field_length(&packet); + if (packet + proc_length > start + length) + return 1; /* Wrong packet */ + (*mysql->options.extension->report_progress)(mysql, stage, max_stage, + progress, (char*) packet, + proc_length); + return 0; +} + +/* Get the length of next field. Change parameter to point at fieldstart */ +ulong +net_field_length(uchar **packet) +{ + reg1 uchar *pos= *packet; + if (*pos < 251) + { + (*packet)++; + return (ulong) *pos; + } + if (*pos == 251) + { + (*packet)++; + return NULL_LENGTH; + } + if (*pos == 252) + { + (*packet)+=3; + return (ulong) uint2korr(pos+1); + } + if (*pos == 253) + { + (*packet)+=4; + return (ulong) uint3korr(pos+1); + } + (*packet)+=9; /* Must be 254 when here */ + return (ulong) uint4korr(pos+1); +} + +/* Same as above, but returns ulonglong values */ + +static unsigned long long +net_field_length_ll(uchar **packet) +{ + reg1 uchar *pos= *packet; + if (*pos < 251) + { + (*packet)++; + return (unsigned long long) *pos; + } + if (*pos == 251) + { + (*packet)++; + return (unsigned long long) NULL_LENGTH; + } + if (*pos == 252) + { + (*packet)+=3; + return (unsigned long long) uint2korr(pos+1); + } + if (*pos == 253) + { + (*packet)+=4; + return (unsigned long long) uint3korr(pos+1); + } + (*packet)+=9; /* Must be 254 when here */ +#ifdef NO_CLIENT_LONGLONG + return (unsigned long long) uint4korr(pos+1); +#else + return (unsigned long long) uint8korr(pos+1); +#endif +} + + +void free_rows(MYSQL_DATA *cur) +{ + if (cur) + { + ma_free_root(&cur->alloc,MYF(0)); + free(cur); + } +} + +int +mthd_my_send_cmd(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skip_check, void *opt_arg) +{ + NET *net= &mysql->net; + int result= -1; + if (mysql->net.pvio == 0) + { + /* Do reconnect if possible */ + if (mariadb_reconnect(mysql)) + return(1); + } + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXIST) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + goto end; + } + + if (IS_CONNHDLR_ACTIVE(mysql)) + { + result= mysql->extension->conn_hdlr->plugin->set_connection(mysql, command, arg, length, skip_check, opt_arg); + if (result== -1) + return(result); + } + + CLEAR_CLIENT_ERROR(mysql); + + if (command == COM_QUERY || + command == COM_STMT_PREPARE) + { + if ((mysql->options.client_flag & CLIENT_LOCAL_FILES) && + mysql->options.extension && mysql->extension->auto_local_infile == WAIT_FOR_QUERY && + arg && (*arg == 'l' || *arg == 'L')) + { + if (strncasecmp(arg, "load", 4) == 0) + mysql->extension->auto_local_infile= ACCEPT_FILE_REQUEST; + } + } + + mysql->info=0; + mysql->affected_rows= ~(unsigned long long) 0; + ma_net_clear(net); /* Clear receive buffer */ + if (!arg) + arg=""; + + if (net->extension->multi_status== COM_MULTI_ENABLED) + { + return net_add_multi_command(net, command, (const uchar *)arg, length); + } + + if (ma_net_write_command(net,(uchar) command,arg, + length ? length : (ulong) strlen(arg), 0)) + { + if (net->last_errno == ER_NET_PACKET_TOO_LARGE) + { + my_set_error(mysql, CR_NET_PACKET_TOO_LARGE, SQLSTATE_UNKNOWN, 0); + goto end; + } + end_server(mysql); + if (mariadb_reconnect(mysql)) + goto end; + if (ma_net_write_command(net,(uchar) command,arg, + length ? length : (ulong) strlen(arg), 0)) + { + my_set_error(mysql, CR_SERVER_GONE_ERROR, SQLSTATE_UNKNOWN, 0); + goto end; + } + } + result=0; + + if (net->extension->multi_status > COM_MULTI_OFF) + skip_check= 1; + + if (!skip_check) + { + result= ((mysql->packet_length=ma_net_safe_read(mysql)) == packet_error ? + 1 : 0); + } + end: + return(result); +} + +int +ma_simple_command(MYSQL *mysql,enum enum_server_command command, const char *arg, + size_t length, my_bool skip_check, void *opt_arg) +{ + return mysql->methods->db_command(mysql, command, arg, length, skip_check, opt_arg); +} + +int ma_multi_command(MYSQL *mysql, enum enum_multi_status status) +{ + NET *net= &mysql->net; + + switch (status) { + case COM_MULTI_OFF: + ma_net_clear(net); + net->extension->multi_status= status; + return 0; + case COM_MULTI_ENABLED: + if (net->extension->multi_status > COM_MULTI_DISABLED) + return 1; + ma_net_clear(net); + net->extension->multi_status= status; + return 0; + case COM_MULTI_DISABLED: + /* Opposite to COM_MULTI_OFF we don't clear net buffer, + next command or com_nulti_end will flush entire buffer */ + net->extension->multi_status= status; + return 0; + case COM_MULTI_END: + { + size_t len= net->write_pos - net->buff - NET_HEADER_SIZE; + + if (len < NET_HEADER_SIZE) /* don't send empty request */ + { + ma_net_clear(net); + return 1; + } + net->extension->multi_status= COM_MULTI_OFF; + return ma_net_flush(net); + } + case COM_MULTI_CANCEL: + ma_net_clear(net); + net->extension->multi_status= COM_MULTI_OFF; + return 0; + default: + return 1; + } +} + +static void free_old_query(MYSQL *mysql) +{ + if (mysql->fields) + ma_free_root(&mysql->field_alloc,MYF(0)); + ma_init_alloc_root(&mysql->field_alloc,8192,0); /* Assume rowlength < 8192 */ + mysql->fields=0; + mysql->field_count=0; /* For API */ + mysql->info= 0; + return; +} + +#if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL) +struct passwd *getpwuid(uid_t); +char* getlogin(void); +#endif + +#if !defined(_WIN32) +void read_user_name(char *name) +{ + if (geteuid() == 0) + strcpy(name,"root"); /* allow use of surun */ + else + { +#ifdef HAVE_GETPWUID + struct passwd *skr; + const char *str; + if ((skr=getpwuid(geteuid())) != NULL) + { + str=skr->pw_name; + } else if ((str=getlogin()) == NULL) + { + if (!(str=getenv("USER")) && !(str=getenv("LOGNAME")) && + !(str=getenv("LOGIN"))) + str="UNKNOWN_USER"; + } + ma_strmake(name,str,USERNAME_LENGTH); +#elif defined(HAVE_CUSERID) + (void) cuserid(name); +#else + ma_strmake(name,"UNKNOWN_USER", USERNAME_LENGTH); +#endif + } + return; +} + +#else /* WIN32 */ + +void read_user_name(char *name) +{ + char *str=getenv("USERNAME"); /* ODBC will send user variable */ + ma_strmake(name,str ? str : "ODBC", USERNAME_LENGTH); +} + +#endif + + +/************************************************************************** +** Shut down connection +**************************************************************************/ + +static void +end_server(MYSQL *mysql) +{ + /* if net->error 2 and reconnect is activated, we need to inform + connection handler */ + if (mysql->net.pvio != 0) + { + ma_pvio_close(mysql->net.pvio); + mysql->net.pvio= 0; /* Marker */ + } + ma_net_end(&mysql->net); + free_old_query(mysql); + return; +} + +void mthd_my_skip_result(MYSQL *mysql) +{ + ulong pkt_len; + + do { + pkt_len= ma_net_safe_read(mysql); + if (pkt_len == packet_error) + break; + } while (pkt_len > 8 || mysql->net.read_pos[0] != 254); + return; +} + +void STDCALL +mysql_free_result(MYSQL_RES *result) +{ + if (result) + { + if (result->handle && result->handle->status == MYSQL_STATUS_USE_RESULT) + { + result->handle->methods->db_skip_result(result->handle); + result->handle->status=MYSQL_STATUS_READY; + } + free_rows(result->data); + if (result->fields) + ma_free_root(&result->field_alloc,MYF(0)); + if (result->row) + free(result->row); + free(result); + } + return; +} + + +/**************************************************************************** +** Get options from my.cnf +****************************************************************************/ +enum enum_option_type { + MARIADB_OPTION_NONE, + MARIADB_OPTION_BOOL, + MARIADB_OPTION_INT, + MARIADB_OPTION_SIZET, + MARIADB_OPTION_STR, + MARIADB_OPTION_FUNC, +}; + +struct st_default_options { + union { + enum mysql_option option; + int (*option_func)(MYSQL *mysql, const char *key, const char *value, ssize_t len); + } u; + enum enum_option_type type; + const char *conf_key; +}; + +struct st_default_options mariadb_defaults[] = +{ + {{MARIADB_OPT_PORT}, MARIADB_OPTION_INT,"port"}, + {{MARIADB_OPT_UNIXSOCKET}, MARIADB_OPTION_STR, "socket"}, + {{MYSQL_OPT_COMPRESS}, MARIADB_OPTION_BOOL, "compress"}, + {{MARIADB_OPT_PASSWORD}, MARIADB_OPTION_STR, "password"}, + {{MYSQL_OPT_NAMED_PIPE}, MARIADB_OPTION_BOOL, "pipe"}, + {{MYSQL_OPT_CONNECT_TIMEOUT}, MARIADB_OPTION_INT, "timeout"}, + {{MARIADB_OPT_USER}, MARIADB_OPTION_STR, "user"}, + {{MYSQL_INIT_COMMAND}, MARIADB_OPTION_STR, "init-command"}, + {{MARIADB_OPT_HOST}, MARIADB_OPTION_STR, "host"}, + {{MARIADB_OPT_SCHEMA}, MARIADB_OPTION_STR, "database"}, + {{MARIADB_OPT_DEBUG}, MARIADB_OPTION_STR, "debug"}, + {{MARIADB_OPT_FOUND_ROWS}, MARIADB_OPTION_NONE, "return-found-rows"}, + {{MYSQL_OPT_SSL_KEY}, MARIADB_OPTION_STR, "ssl-key"}, + {{MYSQL_OPT_SSL_CERT}, MARIADB_OPTION_STR,"ssl-cert"}, + {{MYSQL_OPT_SSL_CA}, MARIADB_OPTION_STR,"ssl-ca"}, + {{MYSQL_OPT_SSL_CAPATH}, MARIADB_OPTION_STR,"ssl-capath"}, + {{MYSQL_OPT_SSL_CRL}, MARIADB_OPTION_STR,"ssl-crl"}, + {{MYSQL_OPT_SSL_CRLPATH}, MARIADB_OPTION_STR,"ssl-crlpath"}, + {{MYSQL_OPT_SSL_VERIFY_SERVER_CERT}, MARIADB_OPTION_BOOL,"ssl-verify-server-cert"}, + {{MYSQL_SET_CHARSET_DIR}, MARIADB_OPTION_STR, "character-sets-dir"}, + {{MYSQL_SET_CHARSET_NAME}, MARIADB_OPTION_STR, "default-character-set"}, + {{MARIADB_OPT_INTERACTIVE}, MARIADB_OPTION_NONE, "interactive-timeout"}, + {{MYSQL_OPT_CONNECT_TIMEOUT}, MARIADB_OPTION_INT, "connect-timeout"}, + {{MYSQL_OPT_LOCAL_INFILE}, MARIADB_OPTION_BOOL, "local-infile"}, + {{0}, 0 ,"disable-local-infile",}, + {{MYSQL_OPT_SSL_CIPHER}, MARIADB_OPTION_STR, "ssl-cipher"}, + {{MYSQL_OPT_MAX_ALLOWED_PACKET}, MARIADB_OPTION_SIZET, "max-allowed-packet"}, + {{MYSQL_OPT_NET_BUFFER_LENGTH}, MARIADB_OPTION_SIZET, "net-buffer-length"}, + {{MYSQL_OPT_PROTOCOL}, MARIADB_OPTION_INT, "protocol"}, + {{MYSQL_SHARED_MEMORY_BASE_NAME}, MARIADB_OPTION_STR,"shared-memory-base-name"}, + {{MARIADB_OPT_MULTI_RESULTS}, MARIADB_OPTION_BOOL, "multi-results"}, + {{MARIADB_OPT_MULTI_STATEMENTS}, MARIADB_OPTION_BOOL, "multi-statements"}, + {{MARIADB_OPT_MULTI_STATEMENTS}, MARIADB_OPTION_BOOL, "multi-queries"}, + {{MYSQL_SECURE_AUTH}, MARIADB_OPTION_BOOL, "secure-auth"}, + {{MYSQL_REPORT_DATA_TRUNCATION}, MARIADB_OPTION_BOOL, "report-data-truncation"}, + {{MYSQL_OPT_RECONNECT}, MARIADB_OPTION_BOOL, "reconnect"}, + {{MYSQL_PLUGIN_DIR}, MARIADB_OPTION_STR, "plugin-dir"}, + {{MYSQL_DEFAULT_AUTH}, MARIADB_OPTION_STR, "default-auth"}, + {{MARIADB_OPT_SSL_FP}, MARIADB_OPTION_STR, "ssl-fp"}, + {{MARIADB_OPT_SSL_FP_LIST}, MARIADB_OPTION_STR, "ssl-fp-list"}, + {{MARIADB_OPT_SSL_FP_LIST}, MARIADB_OPTION_STR, "ssl-fplist"}, + {{MARIADB_OPT_TLS_PASSPHRASE}, MARIADB_OPTION_STR, "ssl-passphrase"}, + {{MARIADB_OPT_TLS_VERSION}, MARIADB_OPTION_STR, "tls-version"}, + {{MYSQL_SERVER_PUBLIC_KEY}, MARIADB_OPTION_STR, "server-public-key"}, + {{MYSQL_OPT_BIND}, MARIADB_OPTION_STR, "bind-address"}, + {{MYSQL_OPT_SSL_ENFORCE}, MARIADB_OPTION_BOOL, "ssl-enforce"}, + {{MARIADB_OPT_RESTRICTED_AUTH}, MARIADB_OPTION_STR, "restricted-auth"}, + {{.option_func=parse_connection_string}, MARIADB_OPTION_FUNC, "connection"}, + /* Aliases */ + {{MARIADB_OPT_SCHEMA}, MARIADB_OPTION_STR, "db"}, + {{MARIADB_OPT_UNIXSOCKET}, MARIADB_OPTION_STR, "unix_socket"}, + {{MARIADB_OPT_HOST}, MARIADB_OPTION_STR, "servername"}, + {{MARIADB_OPT_PASSWORD}, MARIADB_OPTION_STR, "passwd"}, + {{MARIADB_OPT_SSL_FP}, MARIADB_OPTION_STR, "tls-fp"}, + {{MARIADB_OPT_SSL_FP_LIST}, MARIADB_OPTION_STR, "tls-fplist"}, + {{MYSQL_OPT_SSL_KEY}, MARIADB_OPTION_STR, "tls-key"}, + {{MYSQL_OPT_SSL_CERT}, MARIADB_OPTION_STR,"tls-cert"}, + {{MYSQL_OPT_SSL_CA}, MARIADB_OPTION_STR,"tls-ca"}, + {{MYSQL_OPT_SSL_CAPATH}, MARIADB_OPTION_STR,"tls-capath"}, + {{MYSQL_OPT_SSL_CRL}, MARIADB_OPTION_STR,"tls-crl"}, + {{MYSQL_OPT_SSL_CRLPATH}, MARIADB_OPTION_STR,"tls-crlpath"}, + {{MYSQL_OPT_SSL_CIPHER}, MARIADB_OPTION_STR, "tls-cipher"}, + {{MARIADB_OPT_TLS_PASSPHRASE}, MARIADB_OPTION_STR, "tls-passphrase"}, + {{MYSQL_OPT_SSL_ENFORCE}, MARIADB_OPTION_BOOL, "tls-enforce"}, + {{MYSQL_OPT_SSL_VERIFY_SERVER_CERT}, MARIADB_OPTION_BOOL,"tls-verify-peer"}, + {{0}, 0, NULL} +}; + +#define CHECK_OPT_EXTENSION_SET(OPTS)\ + if (!(OPTS)->extension) \ + (OPTS)->extension= (struct st_mysql_options_extension *) \ + calloc(1, sizeof(struct st_mysql_options_extension)); + +#define OPT_SET_EXTENDED_VALUE_BIN(OPTS, KEY, KEY_LEN, VAL, LEN)\ + CHECK_OPT_EXTENSION_SET(OPTS) \ + free((gptr)(OPTS)->extension->KEY); \ + if((VAL) && (LEN)) { \ + if (((OPTS)->extension->KEY= malloc((LEN)))) { \ + memcpy((OPTS)->extension->KEY, (VAL), (LEN)); \ + (OPTS)->extension->KEY_LEN= (LEN); \ + } \ + } \ + else \ + (OPTS)->extension->KEY= NULL + +#define OPT_SET_EXTENDED_VALUE_STR(OPTS, KEY, VAL) \ + CHECK_OPT_EXTENSION_SET(OPTS) \ + free((gptr)(OPTS)->extension->KEY); \ + if((VAL)) \ + (OPTS)->extension->KEY= strdup((char *)(VAL)); \ + else \ + (OPTS)->extension->KEY= NULL + +#define OPT_SET_EXTENDED_VALUE(OPTS, KEY, VAL) \ + CHECK_OPT_EXTENSION_SET(OPTS) \ + (OPTS)->extension->KEY= (VAL) + +#define OPT_SET_EXTENDED_VALUE_INT(A,B,C) OPT_SET_EXTENDED_VALUE(A,B,C) + +#define OPT_SET_VALUE_STR(OPTS, KEY, VAL) \ + free((OPTS)->KEY); \ + if((VAL)) \ + (OPTS)->KEY= strdup((char *)(VAL)); \ + else \ + (OPTS)->KEY= NULL + +#define OPT_SET_VALUE_INT(OPTS, KEY, VAL) \ + (OPTS)->KEY= (VAL) + +static void options_add_initcommand(struct st_mysql_options *options, + const char *init_cmd) +{ + char *insert= strdup(init_cmd); + if (!options->init_command) + { + options->init_command= (DYNAMIC_ARRAY*)malloc(sizeof(DYNAMIC_ARRAY)); + ma_init_dynamic_array(options->init_command, sizeof(char*), 5, 5); + } + + if (ma_insert_dynamic(options->init_command, (gptr)&insert)) + free(insert); +} +my_bool _mariadb_set_conf_option(MYSQL *mysql, const char *config_option, const char *config_value) +{ + if (config_option) + { + int i; + char *c; + + /* CONC-395: replace underscore "_" by dash "-" */ + while ((c= strchr(config_option, '_'))) + *c= '-'; + + for (i=0; mariadb_defaults[i].conf_key; i++) + { + if (!strcmp(mariadb_defaults[i].conf_key, config_option)) + { + my_bool val_bool; + int val_int; + size_t val_sizet; + int rc; + void *option_val= NULL; + switch (mariadb_defaults[i].type) { + case MARIADB_OPTION_FUNC: + return mariadb_defaults[i].u.option_func(mysql, config_option, config_value, -1); + break; + case MARIADB_OPTION_BOOL: + val_bool= 0; + if (config_value) + val_bool= atoi(config_value); + option_val= &val_bool; + break; + case MARIADB_OPTION_INT: + val_int= 0; + if (config_value) + val_int= atoi(config_value); + option_val= &val_int; + break; + case MARIADB_OPTION_SIZET: + val_sizet= 0; + if (config_value) + val_sizet= strtol(config_value, NULL, 10); + option_val= &val_sizet; + break; + case MARIADB_OPTION_STR: + option_val= (void*)config_value; + break; + case MARIADB_OPTION_NONE: + break; + } + rc= mysql_optionsv(mysql, mariadb_defaults[i].u.option, option_val); + return(test(rc)); + } + } + } + /* unknown key */ + return 1; +} + +/** + * @brief: simple connection string parser + * + * key/value pairs (or key only) are separated by semicolons. + * If a semicolon is part of a value, it must be enclosed in + * curly braces. + * + * Unknown keys will be ignored. + */ +static int parse_connection_string(MYSQL *mysql, const char *unused __attribute__((unused)), + const char *conn_str, ssize_t len) +{ + char *conn_save, + *end, *pos, + *key= NULL, *val= NULL; + my_bool in_curly_brace= 0; + + if (len == -1) + len= strlen(conn_str); + + /* don't modify original dsn */ + conn_save= (char *)malloc(len + 1); + memcpy(conn_save, conn_str, len); + conn_save[len]= 0; + + /* start and end */ + pos= conn_save; + end= conn_save + len; + + while (pos <= end) + { + /* ignore white space, unless it is between curly braces */ + if (isspace(*pos) && !in_curly_brace) + { + pos++; + continue; + } + + switch (*pos) { + case '{': + if (!key) + goto error; + if (!in_curly_brace) + { + in_curly_brace= 1; + if (pos < end) + { + pos++; + val= pos; + continue; + } + } + break; + case '}': + if (in_curly_brace) + { + if (!key) + goto error; + if (pos < end && *(pos + 1) == '}') + { + memmove(pos, pos + 1, end - pos - 1); + end--; + pos += 2; + continue; + } + if (in_curly_brace) + in_curly_brace= 0; + else + goto error; + *pos++= 0; + continue; + } + break; + case '=': + if (in_curly_brace) + { + pos++; + continue; + } + if (!key) + goto error; + *pos++= 0; + if (pos < end) + val= pos; + continue; + break; + case ';': + if (in_curly_brace) + { + pos++; + continue; + } + if (!key) + goto error; + *pos++= 0; + if (key && strcasecmp(key, "connection") != 0) + _mariadb_set_conf_option(mysql, key, val); + key= val= NULL; + continue; + break; + } + if (!key && *pos) + key= pos; + pos++; + } + if (key && strcasecmp(key, "connection") != 0) + _mariadb_set_conf_option(mysql, key, val); + free(conn_save); + return 0; + +error: + my_set_error(mysql, CR_CONNSTR_PARSE_ERROR, SQLSTATE_UNKNOWN, 0, pos - conn_save); + free(conn_save); + return 1; +} + + +static MARIADB_CONST_STRING null_const_string= {0,0}; + +/*************************************************************************** +** Allocate a string copy on memroot +***************************************************************************/ +static MARIADB_CONST_STRING ma_const_string_copy_root(MA_MEM_ROOT *memroot, + const char *str, + size_t length) +{ + MARIADB_CONST_STRING res; + if (!str || !(res.str= ma_memdup_root(memroot, str, length))) + return null_const_string; + res.length= length; + return res; +} + + +/*************************************************************************** +** Allocate and initialize MA_FIELD_EXTENSION +***************************************************************************/ +MA_FIELD_EXTENSION *new_ma_field_extension(MA_MEM_ROOT *memroot) +{ + MA_FIELD_EXTENSION *ext= ma_alloc_root(memroot, sizeof(MA_FIELD_EXTENSION)); + if (ext) + memset((void *) ext, 0, sizeof(*ext)); + return ext; +} + + +/*************************************************************************** +** Populate field extension from a type info packet +***************************************************************************/ + +static void ma_field_extension_init_type_info(MA_MEM_ROOT *memroot, + MA_FIELD_EXTENSION *ext, + const char *ptr, size_t length) +{ + const char *end= ptr + length; + for ( ; ptr < end; ) + { + uint itype= (uchar) *ptr++; + uint len= (uchar) *ptr++; + if (ptr + len > end || len > 127) + break; /*Badly encoded data*/ + if (itype <= 127 && itype <= MARIADB_FIELD_ATTR_LAST) + ext->metadata[itype]= ma_const_string_copy_root(memroot, ptr, len); + ptr+= len; + } +} + + +/*************************************************************************** +** Allocate a field extension deep copy +***************************************************************************/ + +MA_FIELD_EXTENSION *ma_field_extension_deep_dup(MA_MEM_ROOT *memroot, + const MA_FIELD_EXTENSION *from) +{ + MA_FIELD_EXTENSION *ext= new_ma_field_extension(memroot); + uint i; + if (!ext) + return NULL; + for (i= 0; i < MARIADB_FIELD_ATTR_LAST; i++) + { + if (from->metadata[i].str) + ext->metadata[i]= ma_const_string_copy_root(memroot, + from->metadata[i].str, + from->metadata[i].length); + } + return ext; +} + +/*************************************************************************** +** Change field rows to field structs +***************************************************************************/ + +static size_t rset_field_offsets[]= { + OFFSET(MYSQL_FIELD, catalog), + OFFSET(MYSQL_FIELD, catalog_length), + OFFSET(MYSQL_FIELD, db), + OFFSET(MYSQL_FIELD, db_length), + OFFSET(MYSQL_FIELD, table), + OFFSET(MYSQL_FIELD, table_length), + OFFSET(MYSQL_FIELD, org_table), + OFFSET(MYSQL_FIELD, org_table_length), + OFFSET(MYSQL_FIELD, name), + OFFSET(MYSQL_FIELD, name_length), + OFFSET(MYSQL_FIELD, org_name), + OFFSET(MYSQL_FIELD, org_name_length) +}; + +MYSQL_FIELD * +unpack_fields(const MYSQL *mysql, + MYSQL_DATA *data, MA_MEM_ROOT *alloc, uint fields, + my_bool default_value) +{ + MYSQL_ROWS *row; + MYSQL_FIELD *field,*result; + char *p; + unsigned int i, field_count= sizeof(rset_field_offsets)/sizeof(size_t)/2; + + field=result=(MYSQL_FIELD*) ma_alloc_root(alloc,sizeof(MYSQL_FIELD)*fields); + if (!result) + return(0); + + for (row=data->data; row ; row = row->next,field++) + { + if (field >= result + fields) + goto error; + + for (i=0; i < field_count; i++) + { + uint length= (uint)(row->data[i+1] - row->data[i] - 1); + if (!row->data[i] || row->data[i][length]) + goto error; + + *(char **)(((char *)field) + rset_field_offsets[i*2])= + ma_strdup_root(alloc, (char *)row->data[i]); + *(unsigned int *)(((char *)field) + rset_field_offsets[i*2+1])= length; + } + + field->extension= NULL; + if (ma_has_extended_type_info(mysql)) + { + if (row->data[i+1] - row->data[i] > 1) + { + size_t len= row->data[i+1] - row->data[i] - 1; + MA_FIELD_EXTENSION *ext= new_ma_field_extension(alloc); + if ((field->extension= ext)) + ma_field_extension_init_type_info(alloc, ext, row->data[i], len); + } + i++; + } + + p= (char *)row->data[i]; + /* filler */ + field->charsetnr= uint2korr(p); + p+= 2; + field->length= (uint) uint4korr(p); + p+= 4; + field->type= (enum enum_field_types)uint1korr(p); + p++; + field->flags= uint2korr(p); + p+= 2; + field->decimals= (uint) p[0]; + p++; + + /* filler */ + p+= 2; + + if (INTERNAL_NUM_FIELD(field)) + field->flags|= NUM_FLAG; + + i++; + /* This is used by deprecated function mysql_list_fields only, + however the reported length is not correct, so we always zero it */ + if (default_value && row->data[i]) + field->def=ma_strdup_root(alloc,(char*) row->data[i]); + else + field->def=0; + field->def_length= 0; + + field->max_length= 0; + } + if (field < result + fields) + goto error; + free_rows(data); /* Free old data */ + return(result); +error: + free_rows(data); + ma_free_root(alloc, MYF(0)); + return(0); +} + + +/* Read all rows (fields or data) from server */ + +MYSQL_DATA *mthd_my_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + uint fields) +{ + uint field; + ulong pkt_len; + ulong len; + uchar *cp; + char *to, *end_to; + MYSQL_DATA *result; + MYSQL_ROWS **prev_ptr,*cur; + NET *net = &mysql->net; + + if ((pkt_len= ma_net_safe_read(mysql)) == packet_error) + return(0); + if (!(result=(MYSQL_DATA*) calloc(1, sizeof(MYSQL_DATA)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(0); + } + ma_init_alloc_root(&result->alloc,8192,0); /* Assume rowlength < 8192 */ + result->alloc.min_malloc=sizeof(MYSQL_ROWS); + prev_ptr= &result->data; + result->rows=0; + result->fields=fields; + + while (*(cp=net->read_pos) != 254 || pkt_len >= 8) + { + result->rows++; + if (!(cur= (MYSQL_ROWS*) ma_alloc_root(&result->alloc, + sizeof(MYSQL_ROWS))) || + !(cur->data= ((MYSQL_ROW) + ma_alloc_root(&result->alloc, + (fields+1)*sizeof(char *)+fields+pkt_len)))) + { + free_rows(result); + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(0); + } + *prev_ptr=cur; + prev_ptr= &cur->next; + to= (char*) (cur->data+fields+1); + end_to=to+fields+pkt_len-1; + for (field=0 ; field < fields ; field++) + { + if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH) + { /* null field */ + cur->data[field] = 0; + } + else + { + cur->data[field] = to; + if (len > (ulong)(end_to - to) || to > end_to) + { + free_rows(result); + SET_CLIENT_ERROR(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0); + return(0); + } + memcpy(to,(char*) cp,len); to[len]=0; + to+=len+1; + cp+=len; + if (mysql_fields) + { + if (mysql_fields[field].max_length < len) + mysql_fields[field].max_length=len; + } + } + } + cur->data[field]=to; /* End of last field */ + if ((pkt_len=ma_net_safe_read(mysql)) == packet_error) + { + free_rows(result); + return(0); + } + } + *prev_ptr=0; /* last pointer is null */ + /* save status */ + if (pkt_len > 1) + { + unsigned int last_status= mysql->server_status; + cp++; + mysql->warning_count= uint2korr(cp); + cp+= 2; + mysql->server_status= uint2korr(cp); + ma_status_callback(mysql, last_status) + } + return(result); +} + + +/* +** Read one row. Uses packet buffer as storage for fields. +** When next packet is read, the previous field values are destroyed +*/ + + +int mthd_my_read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths) +{ + uint field; + ulong pkt_len,len; + uchar *pos,*prev_pos, *end_pos; + + if ((pkt_len=(uint) ma_net_safe_read(mysql)) == packet_error) + return -1; + + if (pkt_len <= 8 && mysql->net.read_pos[0] == 254) + { + unsigned int last_status= mysql->server_status; + mysql->warning_count= uint2korr(mysql->net.read_pos + 1); + mysql->server_status= uint2korr(mysql->net.read_pos + 3); + ma_status_callback(mysql, last_status); + return 1; /* End of data */ + } + prev_pos= 0; /* allowed to write at packet[-1] */ + pos=mysql->net.read_pos; + end_pos=pos+pkt_len; + for (field=0 ; field < fields ; field++) + { + if ((len=(ulong) net_field_length(&pos)) == NULL_LENGTH) + { /* null field */ + row[field] = 0; + *lengths++=0; + } + else + { + if (len > (ulong) (end_pos - pos) || pos > end_pos) + { + mysql->net.last_errno=CR_UNKNOWN_ERROR; + strncpy(mysql->net.last_error,ER(mysql->net.last_errno), + MYSQL_ERRMSG_SIZE - 1); + return -1; + } + row[field] = (char*) pos; + pos+=len; + *lengths++=len; + } + if (prev_pos) + *prev_pos=0; /* Terminate prev field */ + prev_pos=pos; + } + row[field]=(char*) prev_pos+1; /* End of last field */ + *prev_pos=0; /* Terminate last field */ + return 0; +} + +/**************************************************************************** +** Init MySQL structure or allocate one +****************************************************************************/ + +MYSQL * STDCALL +mysql_init(MYSQL *mysql) +{ + if (mysql_server_init(0, NULL, NULL)) + return NULL; + if (!mysql) + { + if (!(mysql=(MYSQL*) calloc(1, sizeof(MYSQL)))) + return 0; + mysql->free_me=1; + mysql->net.pvio= 0; + mysql->net.extension= 0; + } + else + { + memset((char*) (mysql), 0, sizeof(*(mysql))); + mysql->net.pvio= 0; + mysql->free_me= 0; + mysql->net.extension= 0; + } + + if (!(mysql->net.extension= (struct st_mariadb_net_extension *) + calloc(1, sizeof(struct st_mariadb_net_extension))) || + !(mysql->extension= (struct st_mariadb_extension *) + calloc(1, sizeof(struct st_mariadb_extension)))) + goto error; + mysql->options.report_data_truncation= 1; + mysql->options.connect_timeout=CONNECT_TIMEOUT; + mysql->charset= mysql_find_charset_name(MARIADB_DEFAULT_CHARSET); + mysql->methods= &MARIADB_DEFAULT_METHODS; + strcpy(mysql->net.sqlstate, "00000"); + mysql->net.last_error[0]= mysql->net.last_errno= mysql->net.extension->extended_errno= 0; + + if (ENABLED_LOCAL_INFILE != LOCAL_INFILE_MODE_OFF) + mysql->options.client_flag|= CLIENT_LOCAL_FILES; + mysql->extension->auto_local_infile= ENABLED_LOCAL_INFILE == LOCAL_INFILE_MODE_AUTO + ? WAIT_FOR_QUERY : ALWAYS_ACCEPT; + mysql->options.reconnect= 0; + return mysql; +error: + if (mysql->free_me) + free(mysql); + return 0; +} + +int STDCALL +mysql_ssl_set(MYSQL *mysql __attribute__((unused)), + const char *key __attribute__((unused)), + const char *cert __attribute__((unused)), + const char *ca __attribute__((unused)), + const char *capath __attribute__((unused)), + const char *cipher __attribute__((unused))) +{ +#ifdef HAVE_TLS + char enable= 1; + return (mysql_optionsv(mysql, MYSQL_OPT_SSL_ENFORCE, &enable) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_KEY, key) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CERT, cert) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CA, ca) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CAPATH, capath) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CIPHER, cipher)) ? 1 : 0; +#else + return 0; +#endif +} + +/************************************************************************** +**************************************************************************/ + +const char * STDCALL +mysql_get_ssl_cipher(MYSQL *mysql __attribute__((unused))) +{ +#ifdef HAVE_TLS + if (mysql->net.pvio && mysql->net.pvio->ctls) + { + return ma_pvio_tls_cipher(mysql->net.pvio->ctls); + } +#endif + return(NULL); +} + +/************************************************************************** +** Free strings in the SSL structure and clear 'use_ssl' flag. +** NB! Errors are not reported until you do mysql_real_connect. +**************************************************************************/ + +char *ma_send_connect_attr(MYSQL *mysql, unsigned char *buffer) +{ + if (mysql->server_capabilities & CLIENT_CONNECT_ATTRS) + { + buffer= (unsigned char *)mysql_net_store_length((unsigned char *)buffer, (mysql->options.extension) ? + mysql->options.extension->connect_attrs_len : 0); + if (mysql->options.extension && + ma_hashtbl_inited(&mysql->options.extension->connect_attrs)) + { + uint i; + for (i=0; i < mysql->options.extension->connect_attrs.records; i++) + { + size_t len; + uchar *p= ma_hashtbl_element(&mysql->options.extension->connect_attrs, i); + + len= strlen((char *)p); + buffer= mysql_net_store_length(buffer, len); + memcpy(buffer, p, len); + buffer+= (len); + p+= (len + 1); + len= strlen((char *)p); + buffer= mysql_net_store_length(buffer, len); + memcpy(buffer, p, len); + buffer+= len; + } + } + } + return (char *)buffer; +} + +/** set some default attributes */ +static my_bool +ma_set_connect_attrs(MYSQL *mysql, const char *host) +{ + char buffer[255]; + int rc= 0; + + rc= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_name") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_version") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_os") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_server_host") + +#ifdef _WIN32 + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_thread") + +#endif + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_pid") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_platform"); + + rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_name", "libmariadb") + + mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_version", MARIADB_PACKAGE_VERSION) + + mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_os", MARIADB_SYSTEM_TYPE); + + if (host && *host) + rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_server_host", host); + +#ifdef _WIN32 + snprintf(buffer, 255, "%lu", (ulong) GetCurrentThreadId()); + rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_thread", buffer); + snprintf(buffer, 255, "%lu", (ulong) GetCurrentProcessId()); +#else + snprintf(buffer, 255, "%lu", (ulong) getpid()); +#endif + rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_pid", buffer); + + rc+= mysql_optionsv(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_platform", MARIADB_MACHINE_TYPE); + return(test(rc>0)); +} + +/* +** Note that the mysql argument must be initialized with mysql_init() +** before calling mysql_real_connect ! +*/ + +MYSQL * STDCALL +mysql_real_connect(MYSQL *mysql, const char *host, const char *user, + const char *passwd, const char *db, + uint port, const char *unix_socket,unsigned long client_flag) +{ + char *end= NULL; + char *connection_handler= (mysql->options.extension) ? + mysql->options.extension->connection_handler : 0; + + if (!mysql->methods) + mysql->methods= &MARIADB_DEFAULT_METHODS; + + /* set default */ + if (!mysql->options.extension || !mysql->options.extension->status_callback) + mysql_optionsv(mysql, MARIADB_OPT_STATUS_CALLBACK, NULL, NULL); + + /* if host contains a semicolon, we need to parse connection string */ + if (host && strchr(host, ';')) + { + if (parse_connection_string(mysql, NULL, host, strlen(host))) + return NULL; + /* if host was passed in connection string, it is now + stored in mysql->options.host */ + host= NULL; + } else if (connection_handler || + (host && (end= strstr(host, "://")))) + { + MARIADB_CONNECTION_PLUGIN *plugin; + char plugin_name[64]; + + if (!connection_handler || !connection_handler[0]) + { + memset(plugin_name, 0, 64); + ma_strmake(plugin_name, host, MIN(end - host, 63)); + end+= 3; + } + else + ma_strmake(plugin_name, connection_handler, MIN(63, strlen(connection_handler))); + + if (!(plugin= (MARIADB_CONNECTION_PLUGIN *)mysql_client_find_plugin(mysql, plugin_name, MARIADB_CLIENT_CONNECTION_PLUGIN))) + return NULL; + + if (!(mysql->extension->conn_hdlr= (MA_CONNECTION_HANDLER *)calloc(1, sizeof(MA_CONNECTION_HANDLER)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return NULL; + } + + /* save URL for reconnect */ + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, url, host); + + mysql->extension->conn_hdlr->plugin= plugin; + + if (plugin && plugin->connect) + { + MYSQL *my= plugin->connect(mysql, end, user, passwd, db, port, unix_socket, client_flag); + if (!my) + { + free(mysql->extension->conn_hdlr); + mysql->extension->conn_hdlr= NULL; + } + return my; + } + } +#ifndef HAVE_SCHANNEL + return mysql->methods->db_connect(mysql, host, user, passwd, + db, port, unix_socket, client_flag); +#else +/* + With older windows versions (prior Win 10) TLS connections periodically + fail with SEC_E_INVALID_TOKEN, SEC_E_BUFFER_TOO_SMALL or SEC_E_MESSAGE_ALTERED + error (see MDEV-13492). If the connect attempt returns on of these error codes + in mysql->net.extended_errno we will try to connect again (max. 3 times) +*/ +#define MAX_SCHANNEL_CONNECT_ATTEMPTS 3 + { + int ssl_retry= (mysql->options.use_ssl) ? MAX_SCHANNEL_CONNECT_ATTEMPTS : 1; + MYSQL *my= NULL; + while (ssl_retry) + { + if ((my= mysql->methods->db_connect(mysql, host, user, passwd, + db, port, unix_socket, client_flag | CLIENT_REMEMBER_OPTIONS))) + return my; + + switch (mysql->net.extension->extended_errno) { + case SEC_E_INVALID_TOKEN: + case SEC_E_BUFFER_TOO_SMALL: + case SEC_E_MESSAGE_ALTERED: + ssl_retry--; + break; + default: + ssl_retry= 0; + break; + } + } + if (!my && !(client_flag & CLIENT_REMEMBER_OPTIONS)) + mysql_close_options(mysql); + return my; + } +#endif +} + +struct st_host { + char *host; + int port; +}; + +static void ma_get_host_list(char *host_list, struct st_host *host_info, int default_port) +{ + char *token, *start, *save; + int host_nr= 0; + + start= host_list; + while ((token= strtok_r(start, ",", &save))) + { + char *p; + + /* ipv6 hostname */ + if ((p= strchr(token, ']'))) + { + host_info[host_nr].host= token + 1; + *p++= 0; + token= p; + } + else + host_info[host_nr].host= token; + /* check if port was specified */ + if ((p= strchr(token, ':'))) + { + *p++= 0; + host_info[host_nr].port= atoi(p); + } + else + host_info[host_nr].port= default_port; + host_nr++; + start= NULL; + } + return; +} + +MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, + const char *passwd, const char *db, + uint port, const char *unix_socket, unsigned long client_flag) +{ + char buff[NAME_LEN+USERNAME_LENGTH+100]; + char *end, *end_pkt, *host_info; + MA_PVIO_CINFO cinfo= {NULL, NULL, 0, -1, NULL}; + MARIADB_PVIO *pvio= NULL; + char *scramble_data; + const char *scramble_plugin; + uint pkt_length, scramble_len, pkt_scramble_len= 0; + NET *net= &mysql->net; + my_bool is_multi= 0; + char *host_copy= NULL; + struct st_host *host_list= NULL; + int connect_attempts= 0; + + if (!mysql->methods) + mysql->methods= &MARIADB_DEFAULT_METHODS; + + if (net->pvio) /* check if we are already connected */ + { + SET_CLIENT_ERROR(mysql, CR_ALREADY_CONNECTED, SQLSTATE_UNKNOWN, 0); + return(NULL); + } + + /* use default options */ + if (mysql->options.my_cnf_file || mysql->options.my_cnf_group) + { + _mariadb_read_options(mysql, NULL, + (mysql->options.my_cnf_file ? + mysql->options.my_cnf_file : NULL), + mysql->options.my_cnf_group, 0); + free(mysql->options.my_cnf_file); + free(mysql->options.my_cnf_group); + mysql->options.my_cnf_file=mysql->options.my_cnf_group=0; + } + + if (!port) + port=mysql->options.port; + if (!host || !host[0]) + host= mysql->options.host; + + /* check if we have multi hosts */ + if (host && host[0] && strchr(host, ',')) + { + char *p; + size_t host_count= 1; + + is_multi= 1; + + /* don't change original entry, so let's make a copy */ + if (!(host_copy= strdup(host))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto error; + } + + p= host_copy; + + while ((p = strchr(p, ','))) + { + host_count++; + p++; + } + + if (!(host_list= (struct st_host *)calloc(host_count + 1, sizeof(struct st_host)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto error; + } + + ma_get_host_list(host_copy, host_list, port); + } + +restart: + /* check if we reached end of list */ + if (is_multi) + { + if (!host_list[connect_attempts].host) + goto error; + + host= host_list[connect_attempts].host; + port= host_list[connect_attempts].port; + } + + ma_set_connect_attrs(mysql, host); + +#ifndef WIN32 + if (mysql->options.protocol > MYSQL_PROTOCOL_SOCKET) + { + SET_CLIENT_ERROR(mysql, CR_CONN_UNKNOWN_PROTOCOL, SQLSTATE_UNKNOWN, 0); + return(NULL); + } +#endif + + /* Some empty-string-tests are done because of ODBC */ + if (!user || !user[0]) + user=mysql->options.user; + if (!passwd) + { + passwd=mysql->options.password; +#ifndef DONT_USE_MYSQL_PWD + if (!passwd) + passwd=getenv("MYSQL_PWD"); /* get it from environment (haneke) */ + if (!passwd) + passwd= ""; +#endif + } + if (!db || !db[0]) + db=mysql->options.db; + if (!unix_socket) + unix_socket=mysql->options.unix_socket; + + mysql->server_status=SERVER_STATUS_AUTOCOMMIT; + + /* try to connect via pvio_init */ + cinfo.host= host; + cinfo.unix_socket= unix_socket; + cinfo.port= port; + cinfo.mysql= mysql; + + /* + ** Grab a socket and connect it to the server + */ +#ifndef _WIN32 +#if defined(HAVE_SYS_UN_H) + if ((!host || strcmp(host,LOCAL_HOST) == 0) && + mysql->options.protocol != MYSQL_PROTOCOL_TCP && + (unix_socket || mysql_unix_port)) + { + cinfo.host= LOCAL_HOST; + cinfo.unix_socket= (unix_socket) ? unix_socket : mysql_unix_port; + cinfo.type= PVIO_TYPE_UNIXSOCKET; + sprintf(host_info=buff,ER(CR_LOCALHOST_CONNECTION),cinfo.host); + } + else +#endif +#else + if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) + { + cinfo.host= mysql->options.shared_memory_base_name; + cinfo.type= PVIO_TYPE_SHAREDMEM; + sprintf(host_info=buff,ER(CR_SHARED_MEMORY_CONNECTION), cinfo.host ? cinfo.host : SHM_DEFAULT_NAME); + } + /* named pipe */ + else if (mysql->options.protocol == MYSQL_PROTOCOL_PIPE || + (host && strcmp(host,LOCAL_HOST_NAMEDPIPE) == 0)) + { + cinfo.type= PVIO_TYPE_NAMEDPIPE; + sprintf(host_info=buff,ER(CR_NAMEDPIPE_CONNECTION),cinfo.host); + } + else +#endif + { + cinfo.unix_socket=0; /* This is not used */ + if (!port) + port=mysql_port; + if (!host) + host=LOCAL_HOST; + cinfo.host= host; + cinfo.port= port; + cinfo.type= PVIO_TYPE_SOCKET; + sprintf(host_info=buff,ER(CR_TCP_CONNECTION), cinfo.host); + } + /* Initialize and load pvio plugin */ + if (!(pvio= ma_pvio_init(&cinfo))) + goto error; + + /* try to connect */ + if (ma_pvio_connect(pvio, &cinfo) != 0) + { + ma_pvio_close(pvio); + if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->pvio) + { + /* pvio delegated to mysql->net.pvio by ma_net_init(). + * invalidate the pvio pointer in the async context */ + mysql->options.extension->async_context->pvio = NULL; + } + + if (is_multi) + { + connect_attempts++; + goto restart; + } + goto error; + } + + if (mysql->options.extension && mysql->options.extension->proxy_header) + { + char *hdr = mysql->options.extension->proxy_header; + size_t len = mysql->options.extension->proxy_header_len; + if (ma_pvio_write(pvio, (unsigned char *)hdr, len) <= 0) + { + ma_pvio_close(pvio); + goto error; + } + } + + if (ma_net_init(net, pvio)) + { + ma_pvio_close(pvio); + goto error; + } + + if (mysql->options.extension && mysql->options.extension->async_context && mysql->options.extension->async_context->pvio) + { + /* pvio delegated to mysql->net.pvio by ma_net_init(). + * invalidate the pvio pointer in the async context */ + mysql->options.extension->async_context->pvio = NULL; + } + + + if (mysql->options.max_allowed_packet) + net->max_packet_size= mysql->options.max_allowed_packet; + + ma_pvio_keepalive(net->pvio); + strcpy(mysql->net.sqlstate, "00000"); + + /* Get version info */ + mysql->protocol_version= PROTOCOL_VERSION; /* Assume this */ +/* + if (ma_pvio_wait_io_or_timeout(net->pvio, FALSE, 0) < 1) + { + my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, + ER(CR_SERVER_LOST_EXTENDED), + "handshake: waiting for initial communication packet", + errno); + goto error; + } + */ + if ((pkt_length=ma_net_safe_read(mysql)) == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, + ER(CR_SERVER_LOST_EXTENDED), + "handshake: reading initial communication packet", + errno); + + goto error; + } + end= (char *)net->read_pos; + end_pkt= (char *)net->read_pos + pkt_length; + + /* Check if version of protocol matches current one */ + + mysql->protocol_version= end[0]; + end++; + + /* Check if server sends an error */ + if (mysql->protocol_version == 0XFF) + { + net_get_error(end, pkt_length - 1, net->last_error, sizeof(net->last_error), + &net->last_errno, net->sqlstate); + /* fix for bug #26426 */ + if (net->last_errno == 1040) + memcpy(net->sqlstate, "08004", SQLSTATE_LENGTH); + goto error; + } + + if (mysql->protocol_version < PROTOCOL_VERSION) + { + net->last_errno= CR_VERSION_ERROR; + sprintf(net->last_error, ER(CR_VERSION_ERROR), mysql->protocol_version, + PROTOCOL_VERSION); + goto error; + } + /* Save connection information */ + if (!user) user=""; + + if (!(mysql->host_info= strdup(host_info)) || + !(mysql->host= strdup(cinfo.host ? cinfo.host : "")) || + !(mysql->user=strdup(user)) || + !(mysql->passwd=strdup(passwd))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto error; + } + if (cinfo.unix_socket) + mysql->unix_socket= strdup(cinfo.unix_socket); + else + mysql->unix_socket=0; + mysql->port=port; + client_flag|=mysql->options.client_flag; + + if (strncmp(end, MA_RPL_VERSION_HACK, sizeof(MA_RPL_VERSION_HACK) - 1) == 0) + { + mysql->server_version= strdup(end + sizeof(MA_RPL_VERSION_HACK) - 1); + } + else + { + if (!(mysql->server_version= strdup(end))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto error; + } + } + end+= strlen(end) + 1; + + mysql->thread_id=uint4korr(end); + end+=4; + + /* This is the first part of scramble packet. In 4.1 and later + a second package will follow later */ + scramble_data= end; + scramble_len= SCRAMBLE_LENGTH_323 + 1; + scramble_plugin= old_password_plugin_name; + end+= SCRAMBLE_LENGTH_323; + + /* 1st pad */ + end++; + + if (end + 1<= end_pkt) + { + mysql->server_capabilities=uint2korr(end); + } + + /* mysql 5.5 protocol */ + if (end + 18 <= end_pkt) + { + mysql->server_language= uint1korr(end + 2); + mysql->server_status= uint2korr(end + 3); + mysql->server_capabilities|= (unsigned int)(uint2korr(end + 5)) << 16; + pkt_scramble_len= uint1korr(end + 7); + + /* check if MariaD2B specific capabilities are available */ + if (mariadb_connection(mysql) && !(mysql->server_capabilities & CLIENT_MYSQL)) + { + mysql->extension->mariadb_server_capabilities= (ulonglong) uint4korr(end + 14); + } + } + + /* pad 2 */ + end+= 18; + + /* second scramble package */ + if (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 <= end_pkt) + { + memcpy(end - SCRAMBLE_LENGTH_323, scramble_data, SCRAMBLE_LENGTH_323); + scramble_data= end - SCRAMBLE_LENGTH_323; + if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) + { + scramble_len= pkt_scramble_len; + scramble_plugin= scramble_data + scramble_len; + if (scramble_data + scramble_len > end_pkt) + { + SET_CLIENT_ERROR(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0); + goto error; + } + } else + { + scramble_len= (uint)(end_pkt - scramble_data); + scramble_plugin= native_password_plugin_name; + } + } else + { + mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION; + if (mysql->options.secure_auth) + { + SET_CLIENT_ERROR(mysql, CR_SECURE_AUTH, SQLSTATE_UNKNOWN, 0); + goto error; + } + } + + /* Set character set */ + if (mysql->options.charset_name) + mysql->charset= mysql_find_charset_name(mysql->options.charset_name); + else + mysql->charset=mysql_find_charset_name(MARIADB_DEFAULT_CHARSET); + + if (!mysql->charset) + { + net->last_errno=CR_CANT_READ_CHARSET; + sprintf(net->last_error,ER(net->last_errno), + mysql->options.charset_name ? mysql->options.charset_name : + MARIADB_DEFAULT_CHARSET, + "compiled_in"); + goto error; + } + + mysql->client_flag= client_flag; + + if (run_plugin_auth(mysql, scramble_data, scramble_len, + scramble_plugin, db)) + goto error; + + if (mysql->client_flag & CLIENT_COMPRESS || + mysql->client_flag & CLIENT_ZSTD_COMPRESSION) + { + if (!compression_plugin(net) || + (!(compression_ctx(net) = compression_plugin(net)->init_ctx(COMPRESSION_LEVEL_DEFAULT)))) + { + int alg= (mysql->client_flag & CLIENT_ZSTD_COMPRESSION) ? + COMPRESSION_ZSTD : COMPRESSION_ZLIB; + compression_plugin(net)= NULL; + my_set_error(mysql, CR_ERR_LOAD_PLUGIN, SQLSTATE_UNKNOWN, NULL, + _mariadb_compression_algorithm_str(alg)); + goto error; + } + net->compress= 1; + } + + /* last part: select default db */ + if (!(mysql->server_capabilities & CLIENT_CONNECT_WITH_DB) && + (db && !mysql->db)) + { + if (mysql_select_db(mysql, db)) + { + my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, + ER(CR_SERVER_LOST_EXTENDED), + "Setting intital database", + errno); + goto error; + } + } + + if (mysql->options.init_command) + { + char **begin= (char **)mysql->options.init_command->buffer; + char **end= begin + mysql->options.init_command->elements; + + /* Avoid reconnect in mysql_real_connect */ + my_bool save_reconnect= mysql->options.reconnect; + mysql->options.reconnect= 0; + + for (;begin < end; begin++) + { + if (mysql_real_query(mysql, *begin, (unsigned long)strlen(*begin))) + goto error; + + /* check if query produced a result set */ + do { + MYSQL_RES *res; + if ((res= mysql_use_result(mysql))) + mysql_free_result(res); + } while (!mysql_next_result(mysql)); + } + mysql->options.reconnect= save_reconnect; + } + + strcpy(mysql->net.sqlstate, "00000"); + + /* connection established, apply timeouts */ + ma_pvio_set_timeout(mysql->net.pvio, PVIO_READ_TIMEOUT, mysql->options.read_timeout); + ma_pvio_set_timeout(mysql->net.pvio, PVIO_WRITE_TIMEOUT, mysql->options.write_timeout); + + free(host_list); + free(host_copy); + + return(mysql); + +error: + /* Free allocated memory */ + free(host_list); + free(host_copy); + end_server(mysql); + /* only free the allocated memory, user needs to call mysql_close */ + mysql_close_memory(mysql); + if (!(client_flag & CLIENT_REMEMBER_OPTIONS) && + !(IS_MYSQL_ASYNC(mysql))) + mysql_close_options(mysql); + return(0); +} + +struct my_hook_data { + MYSQL *orig_mysql; + MYSQL *new_mysql; + /* This is always NULL currently, but restoring does not hurt just in case. */ + MARIADB_PVIO *orig_pvio; +}; +/* + Callback hook to make the new VIO accessible via the old MYSQL to calling + application when suspending a non-blocking call during automatic reconnect. +*/ +static void +my_suspend_hook(my_bool suspend, void *data) +{ + struct my_hook_data *hook_data= (struct my_hook_data *)data; + if (suspend) + { + hook_data->orig_pvio= hook_data->orig_mysql->net.pvio; + hook_data->orig_mysql->net.pvio= hook_data->new_mysql->net.pvio; + } + else + hook_data->orig_mysql->net.pvio= hook_data->orig_pvio; +} + +my_bool STDCALL mariadb_reconnect(MYSQL *mysql) +{ + MYSQL tmp_mysql; + struct my_hook_data hook_data; + struct mysql_async_context *ctxt= NULL; + LIST *li_stmt= mysql->stmts; + + /* check if connection handler is active */ + if (IS_CONNHDLR_ACTIVE(mysql)) + { + if (mysql->extension->conn_hdlr->plugin && mysql->extension->conn_hdlr->plugin->reconnect) + return(mysql->extension->conn_hdlr->plugin->reconnect(mysql)); + } + + if (!mysql->options.reconnect || + (mysql->server_status & SERVER_STATUS_IN_TRANS) || !mysql->host_info) + { + /* Allow reconnect next time */ + mysql->server_status&= ~SERVER_STATUS_IN_TRANS; + my_set_error(mysql, CR_SERVER_GONE_ERROR, SQLSTATE_UNKNOWN, 0); + return(1); + } + + mysql_init(&tmp_mysql); + tmp_mysql.free_me= 0; + tmp_mysql.options=mysql->options; + if (mysql->extension->conn_hdlr) + { + tmp_mysql.extension->conn_hdlr= mysql->extension->conn_hdlr; + mysql->extension->conn_hdlr= 0; + } + + /* don't reread options from configuration files */ + tmp_mysql.options.my_cnf_group= tmp_mysql.options.my_cnf_file= NULL; + if (IS_MYSQL_ASYNC_ACTIVE(mysql)) + { + ctxt= mysql->options.extension->async_context; + hook_data.orig_mysql= mysql; + hook_data.new_mysql= &tmp_mysql; + hook_data.orig_pvio= mysql->net.pvio; + my_context_install_suspend_resume_hook(ctxt, my_suspend_hook, &hook_data); + } + + if (!mysql_real_connect(&tmp_mysql,mysql->host,mysql->user,mysql->passwd, + mysql->db, mysql->port, mysql->unix_socket, + mysql->client_flag | CLIENT_REMEMBER_OPTIONS) || + mysql_set_character_set(&tmp_mysql, mysql->charset->csname)) + { + if (ctxt) + my_context_install_suspend_resume_hook(ctxt, NULL, NULL); + /* don't free options (CONC-118) */ + memset(&tmp_mysql.options, 0, sizeof(struct st_mysql_options)); + my_set_error(mysql, tmp_mysql.net.last_errno, + tmp_mysql.net.sqlstate, + tmp_mysql.net.last_error); + mysql_close(&tmp_mysql); + return(1); + } + + for (;li_stmt;li_stmt= li_stmt->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data; + + if (stmt->state != MYSQL_STMT_INITTED) + { + stmt->state= MYSQL_STMT_INITTED; + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + } + } + + tmp_mysql.free_me= mysql->free_me; + tmp_mysql.stmts= mysql->stmts; + mysql->stmts= NULL; + + if (ctxt) + my_context_install_suspend_resume_hook(ctxt, NULL, NULL); + /* Don't free options, we moved them to tmp_mysql */ + memset(&mysql->options, 0, sizeof(mysql->options)); + mysql->free_me=0; + mysql_close(mysql); + *mysql=tmp_mysql; + mysql->net.pvio->mysql= mysql; +#ifdef HAVE_TLS + /* CONC-604: Set new connection handle */ + if (mysql_get_ssl_cipher(mysql)) + ma_pvio_tls_set_connection(mysql); +#endif + ma_net_clear(&mysql->net); + mysql->affected_rows= ~(unsigned long long) 0; + mysql->info= 0; + return(0); +} + +void ma_invalidate_stmts(MYSQL *mysql, const char *function_name) +{ + if (mysql->stmts) + { + LIST *li_stmt= mysql->stmts; + + for (; li_stmt; li_stmt= li_stmt->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *)li_stmt->data; + stmt->mysql= NULL; + stmt_set_error(stmt, CR_STMT_CLOSED, SQLSTATE_UNKNOWN, 0, function_name); + } + mysql->stmts= NULL; + } +} + +/* + Legacy support of the MariaDB 5.5 version, where timeouts where only in + seconds resolution. Applications that use this will be asked to set a timeout + at the nearest higher whole-seconds value. +*/ +unsigned int STDCALL +mysql_get_timeout_value(const MYSQL *mysql) +{ + unsigned int timeout= 0; + + if (mysql->options.extension && mysql->options.extension->async_context) + timeout= mysql->options.extension->async_context->timeout_value; + /* Avoid overflow. */ + if (timeout > UINT_MAX - 999) + return (timeout - 1)/1000 + 1; + else + return (timeout+999)/1000; +} + + +unsigned int STDCALL +mysql_get_timeout_value_ms(const MYSQL *mysql) +{ + if (mysql->options.extension && mysql->options.extension->async_context) + return mysql->options.extension->async_context->timeout_value; + return 0; +} + +/************************************************************************** +** Change user and database +**************************************************************************/ + +my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, + const char *passwd, const char *db) +{ + const MARIADB_CHARSET_INFO *s_cs= mysql->charset; + char *s_user= mysql->user, + *s_passwd= mysql->passwd, + *s_db= mysql->db; + int rc; + + if (mysql->options.charset_name) + mysql->charset= mysql_find_charset_name(mysql->options.charset_name); + else + mysql->charset=mysql_find_charset_name(MARIADB_DEFAULT_CHARSET); + + mysql->user= strdup(user ? user : ""); + mysql->passwd= strdup(passwd ? passwd : ""); + + /* db will be set in run_plugin_auth */ + mysql->db= 0; + rc= run_plugin_auth(mysql, 0, 0, 0, db); + + /* COM_CHANGE_USER always releases prepared statements, so we need to invalidate them */ + ma_invalidate_stmts(mysql, "mysql_change_user()"); + + if (rc==0) + { + free(s_user); + free(s_passwd); + free(s_db); + + if (!mysql->db && db && !(mysql->db= strdup(db))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + rc= 1; + } + } else + { + free(mysql->user); + free(mysql->passwd); + free(mysql->db); + + mysql->user= s_user; + mysql->passwd= s_passwd; + mysql->db= s_db; + mysql->charset= s_cs; + } + return(rc); +} + + +/************************************************************************** +** Set current database +**************************************************************************/ + +int STDCALL +mysql_select_db(MYSQL *mysql, const char *db) +{ + int error; + + if (!db) + return 1; + + if ((error=ma_simple_command(mysql, COM_INIT_DB, db, + (uint) strlen(db),0,0))) + return(error); + free(mysql->db); + mysql->db=strdup(db); + return(0); +} + + +/************************************************************************* +** Send a QUIT to the server and close the connection +** If handle is allocated by mysql connect free it. +*************************************************************************/ + +static void mysql_close_options(MYSQL *mysql) +{ + if (mysql->options.init_command) + { + char **begin= (char **)mysql->options.init_command->buffer; + char **end= begin + mysql->options.init_command->elements; + + for (;begin < end; begin++) + free(*begin); + ma_delete_dynamic(mysql->options.init_command); + free(mysql->options.init_command); + } + free(mysql->options.user); + free(mysql->options.host); + free(mysql->options.password); + free(mysql->options.unix_socket); + free(mysql->options.db); + free(mysql->options.my_cnf_file); + free(mysql->options.my_cnf_group); + free(mysql->options.charset_dir); + free(mysql->options.charset_name); + free(mysql->options.bind_address); + free(mysql->options.ssl_key); + free(mysql->options.ssl_cert); + free(mysql->options.ssl_ca); + free(mysql->options.ssl_capath); + free(mysql->options.ssl_cipher); + + if (mysql->options.extension) + { + struct mysql_async_context *ctxt; + if ((ctxt = mysql->options.extension->async_context)) + { + my_context_destroy(&ctxt->async_context); + free(ctxt); + mysql->options.extension->async_context= 0; + } + free(mysql->options.extension->plugin_dir); + free(mysql->options.extension->default_auth); + free(mysql->options.extension->db_driver); + free(mysql->options.extension->ssl_crl); + free(mysql->options.extension->ssl_crlpath); + free(mysql->options.extension->tls_fp); + free(mysql->options.extension->tls_fp_list); + free(mysql->options.extension->tls_pw); + free(mysql->options.extension->tls_version); + free(mysql->options.extension->url); + free(mysql->options.extension->connection_handler); + free(mysql->options.extension->proxy_header); + if(ma_hashtbl_inited(&mysql->options.extension->connect_attrs)) + ma_hashtbl_free(&mysql->options.extension->connect_attrs); + if (ma_hashtbl_inited(&mysql->options.extension->userdata)) + ma_hashtbl_free(&mysql->options.extension->userdata); + free(mysql->options.extension->restricted_auth); + free(mysql->options.extension->rpl_host); + + } + free(mysql->options.extension); + /* clear all pointer */ + memset(&mysql->options, 0, sizeof(mysql->options)); +} + +static void mysql_close_memory(MYSQL *mysql) +{ + ma_clear_session_state(mysql); + free(mysql->host_info); + free(mysql->host); + free(mysql->user); + free(mysql->passwd); + free(mysql->db); + free(mysql->unix_socket); + free(mysql->server_version); + mysql->host_info= mysql->host= mysql->unix_socket= + mysql->server_version=mysql->user=mysql->passwd=mysql->db=0; +} + +void my_set_error(MYSQL *mysql, + unsigned int error_nr, + const char *sqlstate, + const char *format, + ...) +{ + va_list ap; + + const char *errmsg= format; + + mysql->net.last_errno= error_nr; + ma_strmake(mysql->net.sqlstate, sqlstate, SQLSTATE_LENGTH); + + if (!format) + { + if (IS_MYSQL_ERROR(error_nr) || IS_MARIADB_ERROR(error_nr)) + errmsg= ER(error_nr); + else { + snprintf(mysql->net.last_error, MYSQL_ERRMSG_SIZE - 1, + ER_UNKNOWN_ERROR_CODE, error_nr); + return; + } + } + + va_start(ap, format); + vsnprintf(mysql->net.last_error, MYSQL_ERRMSG_SIZE - 1, errmsg, ap); + va_end(ap); + return; +} + +void mysql_close_slow_part(MYSQL *mysql) +{ + if (mysql->net.pvio) + { + free_old_query(mysql); + mysql->status=MYSQL_STATUS_READY; /* Force command */ + mysql->options.reconnect=0; + if (mysql->net.pvio && mysql->net.buff) + ma_simple_command(mysql, COM_QUIT,NullS,0,1,0); + end_server(mysql); + } + /* there is an ongoing async operation */ + else if (mysql->options.extension && mysql->options.extension->async_context) + { + if (mysql->options.extension->async_context->pending_gai_res) + { + freeaddrinfo(mysql->options.extension->async_context->pending_gai_res); + mysql->options.extension->async_context->pending_gai_res = 0; + } + if (mysql->options.extension->async_context->pvio) + { + ma_pvio_close(mysql->options.extension->async_context->pvio); + mysql->options.extension->async_context->pvio = 0; + } + } +} + +static void ma_clear_session_state(MYSQL *mysql) +{ + uint i; + + if (!mysql || !mysql->extension) + return; + + for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++) + { + list_free(mysql->extension->session_state[i].list, 0); + } + memset(mysql->extension->session_state, 0, sizeof(struct st_mariadb_session_state) * SESSION_TRACK_TYPES); +} + +void STDCALL +mysql_close(MYSQL *mysql) +{ + if (mysql) /* Some simple safety */ + { + if (mysql->extension && mysql->extension->conn_hdlr) + { + MA_CONNECTION_HANDLER *p= mysql->extension->conn_hdlr; + if (p->plugin->close) + p->plugin->close(mysql); + free(p); + /* Fix for CONC-294: Since we already called plugin->close function + we need to prevent that mysql_close_slow_part (which sends COM_QUIT + to the server) will be handled by plugin again. */ + mysql->extension->conn_hdlr= NULL; + } + + if (mysql->methods) + mysql->methods->db_close(mysql); + + /* reset the connection in all active statements */ + ma_invalidate_stmts(mysql, "mysql_close()"); + + mysql_close_memory(mysql); + mysql_close_options(mysql); + ma_clear_session_state(mysql); + + if (mysql->net.extension) + { + if (compression_plugin(&mysql->net)) + compression_plugin(&mysql->net)->free_ctx(compression_ctx(&mysql->net)); + free(mysql->net.extension); + } + + mysql->host_info=mysql->user=mysql->passwd=mysql->db=0; + + /* Clear pointers for better safety */ + memset((char*) &mysql->options, 0, sizeof(mysql->options)); + + if (mysql->extension) + free(mysql->extension); + + /* Clear pointers for better safety */ + mysql->net.extension = NULL; + mysql->extension = NULL; + + mysql->net.pvio= 0; + if (mysql->free_me) + free(mysql); + } + return; +} + + +/************************************************************************** +** Do a query. If query returned rows, free old rows. +** Read data by mysql_store_result or by repeating calls to mysql_fetch_row +**************************************************************************/ + +int STDCALL +mysql_query(MYSQL *mysql, const char *query) +{ + return mysql_real_query(mysql,query, (unsigned long) strlen(query)); +} + +/* + Send the query and return so we can do something else. + Needs to be followed by mysql_read_query_result() when we want to + finish processing it. +*/ + + + +int STDCALL +mysql_send_query(MYSQL* mysql, const char* query, unsigned long length) +{ + return ma_simple_command(mysql, COM_QUERY, query, length, 1,0); +} + +void ma_save_session_track_info(void *ptr, enum enum_mariadb_status_info type, ...) +{ + MYSQL *mysql= (MYSQL *)ptr; + enum enum_session_state_type track_type; + va_list ap; + + DBUG_ASSERT(mysql != NULL); + + /* We only handle SESSION_TRACK_TYPE here */ + if (type != SESSION_TRACK_TYPE) + return; + + va_start(ap, type); + + track_type= va_arg(ap, enum enum_session_state_type); + + switch (track_type) { + case SESSION_TRACK_SCHEMA: + case SESSION_TRACK_STATE_CHANGE: + case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: + case SESSION_TRACK_TRANSACTION_STATE: + case SESSION_TRACK_GTIDS: + case SESSION_TRACK_SYSTEM_VARIABLES: + { + LIST *session_item; + MYSQL_LEX_STRING *str; + char *tmp; + MARIADB_CONST_STRING *data1= va_arg(ap, MARIADB_CONST_STRING *); + + if (!(session_item= ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &tmp, data1->length, + NULL))) + goto mem_error; + + str->str= tmp; + memcpy(str->str, data1->str, data1->length); + str->length= data1->length; + session_item->data= str; + mysql->extension->session_state[track_type].list= list_add(mysql->extension->session_state[track_type].list, + session_item); + if (track_type == SESSION_TRACK_SYSTEM_VARIABLES) + { + MARIADB_CONST_STRING *data2= va_arg(ap, MARIADB_CONST_STRING *); + if (!(session_item= ma_multi_malloc(0, + &session_item, sizeof(LIST), + &str, sizeof(MYSQL_LEX_STRING), + &tmp, data2->length, + NULL))) + goto mem_error; + + str->str= tmp; + memcpy(str->str, data2->str, data2->length); + str->length= data2->length; + session_item->data= str; + mysql->extension->session_state[track_type].list= list_add(mysql->extension->session_state[track_type].list, + session_item); + } + } + break; + } + return; + +mem_error: + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return; +} + +int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length) +{ + uchar *end= mysql->net.read_pos+length; + size_t item_len; + unsigned int last_status= mysql->server_status; + mysql->affected_rows= net_field_length_ll(&pos); + mysql->insert_id= net_field_length_ll(&pos); + mysql->server_status=uint2korr(pos); + + /* clear error */ + mysql->net.last_error[0]= mysql->net.last_errno= mysql->net.extension->extended_errno= 0; + + /* callback */ + ma_status_callback(mysql, last_status); + pos+=2; + mysql->warning_count=uint2korr(pos); + pos+=2; + if (pos > end) + goto corrupted; + if (pos < end) + { + if ((item_len= net_field_length(&pos))) + mysql->info=(char*) pos; + if (pos + item_len > end) + goto corrupted; + + /* check if server supports session tracking */ + if (mysql->server_capabilities & CLIENT_SESSION_TRACKING) + { + ma_clear_session_state(mysql); + pos+= item_len; + + if (mysql->server_status & SERVER_SESSION_STATE_CHANGED) + { + int i; + if (pos < end) + { + enum enum_session_state_type si_type; + uchar *old_pos= pos; + + item_len= net_field_length(&pos); /* length for all items */ + if (pos + item_len > end) + goto corrupted; + end= pos + item_len; + + /* length was already set, so make sure that info will be zero terminated */ + if (mysql->info) + *old_pos= 0; + + while (pos < end) + { + size_t plen; + MARIADB_CONST_STRING data1, data2; + si_type= (enum enum_session_state_type)net_field_length(&pos); + + switch(si_type) { + case SESSION_TRACK_SCHEMA: + case SESSION_TRACK_STATE_CHANGE: + case SESSION_TRACK_TRANSACTION_CHARACTERISTICS: + case SESSION_TRACK_SYSTEM_VARIABLES: + case SESSION_TRACK_TRANSACTION_STATE: + case SESSION_TRACK_GTIDS: + if (si_type != SESSION_TRACK_STATE_CHANGE) + { + net_field_length(&pos); /* ignore total length, item length will follow next */ + } + if (si_type == SESSION_TRACK_GTIDS) + { + /* skip encoding */ + net_field_length(&pos); + } + plen= net_field_length(&pos); + if (pos + plen > end) + goto corrupted; + + data1.str= (char *)pos; + data1.length= plen; + if (si_type != SESSION_TRACK_SYSTEM_VARIABLES) + { + mysql->options.extension->status_callback(mysql->options.extension->status_data, + SESSION_TRACK_TYPE, si_type, + &data1); + if (mysql->net.last_errno) + goto oom; + } + pos+= plen; + /* in case schema has changed, we have to update mysql->db */ + if (si_type == SESSION_TRACK_SCHEMA) + { + free(mysql->db); + mysql->db= malloc(plen + 1); + memcpy(mysql->db, data1.str, data1.length); + mysql->db[data1.length]= 0; + } + else if (si_type == SESSION_TRACK_SYSTEM_VARIABLES) + { + my_bool set_charset= 0; + /* make sure that we update charset in case it has changed */ + if (!strncmp(data1.str, "character_set_client", plen)) + set_charset= 1; + plen= net_field_length(&pos); + if (pos + plen > end) + goto corrupted; + data2.str= (char *)pos; + data2.length= plen; + + mysql->options.extension->status_callback(mysql->options.extension->status_data, + SESSION_TRACK_TYPE, si_type, + &data1, &data2); + if (mysql->net.last_errno) + goto oom; + + pos+= plen; + if (set_charset && plen < CHARSET_NAME_LEN && + strncmp(mysql->charset->csname, data2.str, data2.length) != 0) + { + char cs_name[CHARSET_NAME_LEN]; + const MARIADB_CHARSET_INFO *cs_info; + memcpy(cs_name, data2.str, data2.length); + cs_name[plen]= 0; + if ((cs_info = mysql_find_charset_name(cs_name))) + mysql->charset= cs_info; + } + } + break; + default: + /* not supported yet */ + plen= net_field_length(&pos); + if (pos + plen > end) + goto corrupted; + pos+= plen; + break; + } + } + } + for (i= SESSION_TRACK_BEGIN; i <= SESSION_TRACK_END; i++) + { + mysql->extension->session_state[i].list= list_reverse(mysql->extension->session_state[i].list); + mysql->extension->session_state[i].current= mysql->extension->session_state[i].list; + } + } + } + } + /* CONC-351: clear session state information */ + else if (mysql->server_capabilities & CLIENT_SESSION_TRACKING) + ma_clear_session_state(mysql); + return(0); + +oom: + ma_clear_session_state(mysql); + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return -1; + +corrupted: + ma_clear_session_state(mysql); + SET_CLIENT_ERROR(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0); + return -1; +} + + +static int ma_deep_copy_field(const MYSQL_FIELD *src, MYSQL_FIELD *dst, + MA_MEM_ROOT *r) +{ +#define MA_STRDUP(f) \ + do \ + { \ + if (src->f) \ + { \ + if ((dst->f= ma_strdup_root(r, src->f)) == NULL) \ + return -1; \ + } \ + else \ + { \ + dst->f= NULL; \ + } \ + } \ + while (0) + + + MA_STRDUP(catalog); + MA_STRDUP(db); + MA_STRDUP(def); + MA_STRDUP(name); + MA_STRDUP(org_name); + MA_STRDUP(org_table); + MA_STRDUP(table); +#undef MA_STRDUP + + dst->catalog_length= src->catalog_length; + dst->charsetnr= src->charsetnr; + dst->db_length= src->db_length; + dst->decimals= src->decimals; + dst->def_length= src->def_length; + dst->extension= + src->extension + ? ma_field_extension_deep_dup(r, + src->extension) + : NULL; + dst->flags= src->flags; + dst->length= src->length; + dst->max_length = src->max_length; + dst->name_length= src->name_length; + dst->org_name_length= src->org_name_length; + dst->org_table_length= src->org_table_length; + dst->table_length= src->table_length; + dst->type= src->type; + return 0; +} + + +MYSQL_FIELD *ma_duplicate_resultset_metadata(MYSQL_FIELD *fields, size_t count, + MA_MEM_ROOT *memroot) +{ + size_t i; + MYSQL_FIELD *result= + (MYSQL_FIELD *) ma_alloc_root(memroot, sizeof(MYSQL_FIELD) * count); + if (!result) + return NULL; + + for (i= 0; i < count; i++) + { + if (ma_deep_copy_field(&fields[i], &result[i], memroot)) + return NULL; + } + return result; +} + + +int mthd_my_read_query_result(MYSQL *mysql) +{ + uchar *pos; + ulong field_count; + MYSQL_DATA *fields; + ulong length; + const uchar *end; + uchar has_metadata; + + my_bool can_local_infile= (mysql->options.extension) && (mysql->extension->auto_local_infile != WAIT_FOR_QUERY); + + if (mysql->options.extension && mysql->extension->auto_local_infile == ACCEPT_FILE_REQUEST) + mysql->extension->auto_local_infile= WAIT_FOR_QUERY; + + if ((length = ma_net_safe_read(mysql)) == packet_error) + { + return(1); + } + free_old_query(mysql); /* Free old result */ +get_info: + pos=(uchar*) mysql->net.read_pos; + end= pos + length; + if ((field_count= net_field_length(&pos)) == 0) + return ma_read_ok_packet(mysql, pos, length); + if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ + { + int error=mysql_handle_local_infile(mysql, (char *)pos, can_local_infile); + + if ((length=ma_net_safe_read(mysql)) == packet_error || error) + return(-1); + goto get_info; /* Get info packet */ + } + + has_metadata= 1; + if (ma_supports_cache_metadata(mysql)) + { + assert(mysql->fields == NULL); + if (pos < end) + { + has_metadata= *pos; + pos++; + } + } + + if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) + mysql->server_status|= SERVER_STATUS_IN_TRANS; + + if (has_metadata) + { + if (!(fields= mysql->methods->db_read_rows(mysql, (MYSQL_FIELD *) 0, + ma_result_set_rows(mysql)))) + return (-1); + if (!(mysql->fields= unpack_fields(mysql, fields, &mysql->field_alloc, + (uint) field_count, 1))) + return (-1); + } + else + { + unsigned int last_status= mysql->server_status; + /* Read EOF, to get the status and warning count. */ + if ((length= ma_net_safe_read(mysql)) == packet_error) + { + return -1; + } + pos= (uchar *) mysql->net.read_pos; + if (length != 5 || pos[0] != 0xfe) + { + return -1; + } + mysql->warning_count= uint2korr(pos + 1); + mysql->server_status= uint2korr(pos + 3); + ma_status_callback(mysql, last_status); + } + mysql->status=MYSQL_STATUS_GET_RESULT; + mysql->field_count=field_count; + return(0); +} + +int STDCALL mysql_session_track_get_next(MYSQL *mysql, enum enum_session_state_type type, + const char **data, size_t *length) +{ + MYSQL_LEX_STRING *str; + if (!mysql->extension->session_state[type].current) + return 1; + + str= (MYSQL_LEX_STRING *)mysql->extension->session_state[type].current->data; + mysql->extension->session_state[type].current= mysql->extension->session_state[type].current->next; + + *data= str->str ? str->str : NULL; + *length= str->str ? str->length : 0; + return 0; +} + +int STDCALL mysql_session_track_get_first(MYSQL *mysql, enum enum_session_state_type type, + const char **data, size_t *length) +{ + mysql->extension->session_state[type].current= mysql->extension->session_state[type].list; + return mysql_session_track_get_next(mysql, type, data, length); +} + +my_bool STDCALL +mysql_read_query_result(MYSQL *mysql) +{ + return test(mysql->methods->db_read_query_result(mysql)) ? 1 : 0; +} + +int STDCALL +mysql_real_query(MYSQL *mysql, const char *query, unsigned long length) +{ + my_bool skip_result= OPT_EXT_VAL(mysql, multi_command); + + if (length == (unsigned long)-1) + length= (unsigned long)strlen(query); + + free_old_query(mysql); + + if (ma_simple_command(mysql, COM_QUERY,query,length,1,0)) + return(-1); + if (!skip_result && !mysql->options.extension->skip_read_response) + return(mysql->methods->db_read_query_result(mysql)); + return(0); +} + +/************************************************************************** +** Alloc result struct for buffered results. All rows are read to buffer. +** mysql_data_seek may be used. +**************************************************************************/ + +MYSQL_RES * STDCALL +mysql_store_result(MYSQL *mysql) +{ + MYSQL_RES *result; + + if (!mysql->fields) + return(0); + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(0); + } + mysql->status=MYSQL_STATUS_READY; /* server is ready */ + if (!(result=(MYSQL_RES*) calloc(1, sizeof(MYSQL_RES)+ + sizeof(ulong)*mysql->field_count))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(0); + } + result->eof=1; /* Marker for buffered */ + result->lengths=(ulong*) (result+1); + if (!(result->data=mysql->methods->db_read_rows(mysql,mysql->fields,mysql->field_count))) + { + free(result); + return(0); + } + mysql->affected_rows= result->row_count= result->data->rows; + result->data_cursor= result->data->data; + result->fields= mysql->fields; + result->field_alloc= mysql->field_alloc; + result->field_count= mysql->field_count; + result->current_field=0; + result->current_row=0; /* Must do a fetch first */ + mysql->fields=0; /* fields is now in result */ + return(result); /* Data fetched */ +} + + +/************************************************************************** +** Alloc struct for use with unbuffered reads. Data is fetched by domand +** when calling to mysql_fetch_row. +** mysql_data_seek is a noop. +** +** No other queries may be specified with the same MYSQL handle. +** There shouldn't be much processing per row because mysql server shouldn't +** have to wait for the client (and will not wait more than 30 sec/packet). +**************************************************************************/ + +MYSQL_RES * STDCALL +mysql_use_result(MYSQL *mysql) +{ + MYSQL_RES *result; + + if (!mysql->fields) + return(0); + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(0); + } + if (!(result=(MYSQL_RES*) calloc(1, sizeof(*result)+ + sizeof(ulong)*mysql->field_count))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(0); + } + result->lengths=(ulong*) (result+1); + if (!(result->row=(MYSQL_ROW) + malloc(sizeof(result->row[0])*(mysql->field_count+1)))) + { /* Ptrs: to one row */ + free(result); + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(0); + } + result->fields= mysql->fields; + result->field_alloc= mysql->field_alloc; + result->field_count= mysql->field_count; + result->current_field=0; + result->handle= mysql; + result->current_row= 0; + mysql->fields=0; /* fields is now in result */ + mysql->status=MYSQL_STATUS_USE_RESULT; + return(result); /* Data is read to be fetched */ +} + +/************************************************************************** +** Return next field of the query results +**************************************************************************/ +MYSQL_FIELD * STDCALL +mysql_fetch_field(MYSQL_RES *result) +{ + if (result->current_field >= result->field_count) + return(NULL); + return &result->fields[result->current_field++]; +} + + +/************************************************************************** +** Return mysql field metadata +**************************************************************************/ +int STDCALL +mariadb_field_attr(MARIADB_CONST_STRING *attr, + const MYSQL_FIELD *field, + enum mariadb_field_attr_t type) +{ + MA_FIELD_EXTENSION *ext= (MA_FIELD_EXTENSION*) field->extension; + if (!ext || type > MARIADB_FIELD_ATTR_LAST) + { + *attr= null_const_string; + return 1; + } + *attr= ext->metadata[type]; + return 0; +} + + +/************************************************************************** +** Return next row of the query results +**************************************************************************/ +MYSQL_ROW STDCALL +mysql_fetch_row(MYSQL_RES *res) +{ + if (!res) + return 0; + if (res->handle) + { + if (res->handle->status != MYSQL_STATUS_USE_RESULT && + res->handle->status != MYSQL_STATUS_GET_RESULT) + return 0; + } + if (!res->data) + { /* Unbufferred fetch */ + if (!res->eof && res->handle) + { + if (!(res->handle->methods->db_read_one_row(res->handle,res->field_count,res->row, res->lengths))) + { + res->row_count++; + return(res->current_row=res->row); + } + res->eof=1; + res->handle->status=MYSQL_STATUS_READY; + /* Don't clear handle in mysql_free_results */ + res->handle=0; + } + return((MYSQL_ROW) NULL); + } + { + MYSQL_ROW tmp; + if (!res->data_cursor) + { + return(res->current_row=(MYSQL_ROW) NULL); + } + tmp = res->data_cursor->data; + res->data_cursor = res->data_cursor->next; + return(res->current_row=tmp); + } +} + +/************************************************************************** +** Get column lengths of the current row +** If one uses mysql_use_result, res->lengths contains the length information, +** else the lengths are calculated from the offset between pointers. +**************************************************************************/ + +ulong * STDCALL +mysql_fetch_lengths(MYSQL_RES *res) +{ + ulong *lengths,*prev_length; + char *start; + MYSQL_ROW column,end; + + if (!(column=res->current_row)) + return 0; /* Something is wrong */ + if (res->data) + { + start=0; + prev_length=0; /* Keep gcc happy */ + lengths=res->lengths; + for (end=column+res->field_count+1 ; column != end ; column++,lengths++) + { + if (!*column) + { + *lengths=0; /* Null */ + continue; + } + if (start) /* Found end of prev string */ + *prev_length= (uint) (*column-start-1); + start= *column; + prev_length=lengths; + } + } + return res->lengths; +} + +/************************************************************************** +** Move to a specific row and column +**************************************************************************/ + +void STDCALL +mysql_data_seek(MYSQL_RES *result, unsigned long long row) +{ + MYSQL_ROWS *tmp=0; + if (result->data) + for (tmp=result->data->data; row-- && tmp ; tmp = tmp->next) ; + result->current_row=0; + result->data_cursor = tmp; +} + +/************************************************************************* +** put the row or field cursor one a position one got from mysql_row_tell() +** This doesn't restore any data. The next mysql_fetch_row or +** mysql_fetch_field will return the next row or field after the last used +*************************************************************************/ + +MYSQL_ROW_OFFSET STDCALL +mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET row) +{ + MYSQL_ROW_OFFSET return_value=result->data_cursor; + result->current_row= 0; + result->data_cursor= row; + return return_value; +} + + +MYSQL_FIELD_OFFSET STDCALL +mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET field_offset) +{ + MYSQL_FIELD_OFFSET return_value=result->current_field; + result->current_field=field_offset; + return return_value; +} + +/******************************************************** + Warning: mysql_list_dbs is deprecated and will be + removed. Use SQL statement "SHOW DATABASES" + instead + ********************************************************/ + +/***************************************************************************** +** List all databases +*****************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_dbs(MYSQL *mysql, const char *wild) +{ + char buff[255]; + snprintf(buff, 255, "SHOW DATABASES LIKE '%s'", wild ? wild : "%"); + if (mysql_query(mysql,buff)) + return(0); + return (mysql_store_result(mysql)); +} + + +/******************************************************** + Warning: mysql_list_tables is deprecated and will be + removed. Use SQL statement "SHOW TABLES" + instead + ********************************************************/ +/***************************************************************************** +** List all tables in a database +** If wild is given then only the tables matching wild are returned +*****************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_tables(MYSQL *mysql, const char *wild) +{ + char buff[255]; + + snprintf(buff, 255, "SHOW TABLES LIKE '%s'", wild ? wild : "%"); + if (mysql_query(mysql,buff)) + return(0); + return (mysql_store_result(mysql)); +} + + +/************************************************************************** +** List all fields in a table +** If wild is given then only the fields matching wild are returned +** Instead of this use query: +** show fields in 'table' like "wild" +**************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_fields(MYSQL *mysql, const char *table, const char *wild) +{ + MYSQL_RES *result; + MYSQL_DATA *query; + char buff[255]; + int length= 0; + + LINT_INIT(query); + + length= snprintf(buff, 128, "%s%c%s", table, '\0', wild ? wild : ""); + + if (ma_simple_command(mysql, COM_FIELD_LIST,buff,length,1,0) || + !(query = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0, + ma_result_set_rows(mysql)))) + return(NULL); + + free_old_query(mysql); + if (!(result = (MYSQL_RES *) calloc(1, sizeof(MYSQL_RES)))) + { + free_rows(query); + return(NULL); + } + result->field_alloc=mysql->field_alloc; + mysql->fields=0; + result->eof=1; + result->field_count = (uint) query->rows; + result->fields= unpack_fields(mysql, query, &result->field_alloc, + result->field_count, 1); + if (result->fields) + return(result); + + free(result); + return(NULL); +} + +/******************************************************** + Warning: mysql_list_processes is deprecated and will be + removed. Use SQL statement "SHOW PROCESSLIST" + instead + ********************************************************/ + +/* List all running processes (threads) in server */ + +MYSQL_RES * STDCALL +mysql_list_processes(MYSQL *mysql) +{ + MYSQL_DATA *fields; + uint field_count; + uchar *pos; + + LINT_INIT(fields); + if (ma_simple_command(mysql, COM_PROCESS_INFO,0,0,0,0)) + return(NULL); + free_old_query(mysql); + pos=(uchar*) mysql->net.read_pos; + field_count=(uint) net_field_length(&pos); + if (!(fields = mysql->methods->db_read_rows(mysql,(MYSQL_FIELD*) 0,7))) + return(NULL); + if (!(mysql->fields= unpack_fields(mysql, fields, &mysql->field_alloc, + field_count, 0))) + return(NULL); + mysql->status=MYSQL_STATUS_GET_RESULT; + mysql->field_count=field_count; + return(mysql_store_result(mysql)); +} + +/* In 5.0 this version became an additional parameter shutdown_level */ +int STDCALL +mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level) +{ + uchar s_level[2]; + s_level[0]= (uchar)shutdown_level; + return(ma_simple_command(mysql, COM_SHUTDOWN, (char *)s_level, 1, 0, 0)); +} + +int STDCALL +mysql_refresh(MYSQL *mysql,uint options) +{ + uchar bits[1]; + bits[0]= (uchar) options; + return(ma_simple_command(mysql, COM_REFRESH,(char*) bits,1,0,0)); +} + +int STDCALL +mysql_kill(MYSQL *mysql,ulong pid) +{ + char buff[12]; + int4store(buff,pid); + /* if we kill our own thread, reading the response packet will fail */ + return(ma_simple_command(mysql, COM_PROCESS_KILL,buff,4,0,0)); +} + + +int STDCALL +mysql_dump_debug_info(MYSQL *mysql) +{ + return(ma_simple_command(mysql, COM_DEBUG,0,0,0,0)); +} + +char * STDCALL +mysql_stat(MYSQL *mysql) +{ + if (ma_simple_command(mysql, COM_STATISTICS,0,0,0,0)) + return mysql->net.last_error; + mysql->net.read_pos[mysql->packet_length]=0; /* End of stat string */ + if (!mysql->net.read_pos[0]) + { + SET_CLIENT_ERROR(mysql, CR_WRONG_HOST_INFO , SQLSTATE_UNKNOWN, 0); + return mysql->net.last_error; + } + return((char*) mysql->net.read_pos); +} + +int STDCALL +mysql_ping(MYSQL *mysql) +{ + int rc; + rc= ma_simple_command(mysql, COM_PING, 0, 0, 0, 0); + if (rc && mysql->options.reconnect) + rc= ma_simple_command(mysql, COM_PING, 0, 0, 0, 0); + return rc; +} + +char * STDCALL +mysql_get_server_info(MYSQL *mysql) +{ + return((char*) mysql->server_version); +} + +static size_t mariadb_server_version_id(MYSQL *mysql) +{ + size_t major, minor, patch; + char *p; + + if (!(p = mysql->server_version)) { + return 0; + } + + major = strtol(p, &p, 10); + p += 1; /* consume the dot */ + minor = strtol(p, &p, 10); + p += 1; /* consume the dot */ + patch = strtol(p, &p, 10); + + return (major * 10000L + (unsigned long)(minor * 100L + patch)); +} + +unsigned long STDCALL mysql_get_server_version(MYSQL *mysql) +{ + return (unsigned long)mariadb_server_version_id(mysql); +} + +char * STDCALL +mysql_get_host_info(MYSQL *mysql) +{ + return(mysql->host_info); +} + +uint STDCALL +mysql_get_proto_info(MYSQL *mysql) +{ + return (mysql->protocol_version); +} + +const char * STDCALL +mysql_get_client_info(void) +{ + return (char*) MARIADB_PACKAGE_VERSION; +} + +static size_t get_store_length(size_t length) +{ + #define MAX_STORE_SIZE 9 + unsigned char buffer[MAX_STORE_SIZE], *p; + + /* We just store the length and subtract offset of our buffer + to determine the length */ + p= mysql_net_store_length(buffer, length); + return p - buffer; +} + +uchar *ma_get_hash_keyval(const uchar *hash_entry, + unsigned int *length, + my_bool not_used __attribute__((unused))) +{ + /* Hash entry has the following format: + Offset: 0 key (\0 terminated) + key_length + 1 value (\0 terminated) + */ + uchar *p= (uchar *)hash_entry; + size_t len= strlen((char *)p); + *length= (unsigned int)len; + return p; +} + +void ma_int_hash_free(void *p) +{ + free(p); +} + +int +mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) +{ + va_list ap; + void *arg1; + size_t stacksize; + struct mysql_async_context *ctxt; + + va_start(ap, option); + + arg1= va_arg(ap, void *); + + switch (option) { + case MYSQL_OPT_CONNECT_TIMEOUT: + mysql->options.connect_timeout= *(uint*) arg1; + break; + case MYSQL_OPT_COMPRESS: + mysql->options.compress= 1; /* Remember for connect */ + mysql->options.client_flag|= CLIENT_COMPRESS; + break; + case MYSQL_OPT_NAMED_PIPE: + mysql->options.named_pipe=1; /* Force named pipe */ + break; + case MYSQL_OPT_LOCAL_INFILE: /* Allow LOAD DATA LOCAL ?*/ + if (!arg1 || test(*(unsigned int*) arg1)) + mysql->options.client_flag|= CLIENT_LOCAL_FILES; + else + mysql->options.client_flag&= ~CLIENT_LOCAL_FILES; + if (arg1) { + CHECK_OPT_EXTENSION_SET(&mysql->options); + mysql->extension->auto_local_infile= *(uint*)arg1 == LOCAL_INFILE_MODE_AUTO + ? WAIT_FOR_QUERY : ALWAYS_ACCEPT; + } + break; + case MYSQL_INIT_COMMAND: + options_add_initcommand(&mysql->options, (char *)arg1); + break; + case MYSQL_READ_DEFAULT_FILE: + OPT_SET_VALUE_STR(&mysql->options, my_cnf_file, (char *)arg1); + break; + case MYSQL_READ_DEFAULT_GROUP: + OPT_SET_VALUE_STR(&mysql->options, my_cnf_group, arg1 ? (char *)arg1 : ""); + break; + case MYSQL_SET_CHARSET_DIR: + OPT_SET_VALUE_STR(&mysql->options, charset_dir, arg1); + break; + case MYSQL_SET_CHARSET_NAME: + OPT_SET_VALUE_STR(&mysql->options, charset_name, arg1); + break; + case MYSQL_OPT_RECONNECT: + mysql->options.reconnect= *(my_bool *)arg1; + break; + case MYSQL_OPT_PROTOCOL: + mysql->options.protocol= *((uint *)arg1); + break; +#ifdef _WIN32 + case MYSQL_SHARED_MEMORY_BASE_NAME: + OPT_SET_VALUE_STR(&mysql->options, shared_memory_base_name, arg1); + break; +#endif + case MYSQL_OPT_READ_TIMEOUT: + mysql->options.read_timeout= *(uint *)arg1; + break; + case MYSQL_OPT_WRITE_TIMEOUT: + mysql->options.write_timeout= *(uint *)arg1; + break; + case MYSQL_REPORT_DATA_TRUNCATION: + mysql->options.report_data_truncation= *(my_bool *)arg1; + break; + case MYSQL_PROGRESS_CALLBACK: + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (mysql->options.extension) + mysql->options.extension->report_progress= + (void (*)(const MYSQL *, uint, uint, double, const char *, uint)) arg1; + break; + case MYSQL_SERVER_PUBLIC_KEY: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, server_public_key, (char *)arg1); + break; + case MYSQL_PLUGIN_DIR: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, plugin_dir, (char *)arg1); + break; + case MYSQL_DEFAULT_AUTH: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, default_auth, (char *)arg1); + break; + case MYSQL_OPT_NONBLOCK: + if (mysql->options.extension && + (ctxt = mysql->options.extension->async_context) != 0) + { + /* + We must not allow changing the stack size while a non-blocking call is + suspended (as the stack is then in use). + */ + if (ctxt->suspended) + goto end; + my_context_destroy(&ctxt->async_context); + free(ctxt); + } + if (!(ctxt= (struct mysql_async_context *) + calloc(1, sizeof(*ctxt)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + stacksize= 0; + if (arg1) + stacksize= *(const size_t *)arg1; + if (!stacksize) + stacksize= ASYNC_CONTEXT_DEFAULT_STACK_SIZE; + if (my_context_init(&ctxt->async_context, stacksize)) + { + free(ctxt); + goto end; + } + if (!mysql->options.extension) + if(!(mysql->options.extension= (struct st_mysql_options_extension *) + calloc(1, sizeof(struct st_mysql_options_extension)))) + { + free(ctxt); + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + mysql->options.extension->async_context= ctxt; + break; + case MYSQL_OPT_MAX_ALLOWED_PACKET: + if (mysql) + mysql->options.max_allowed_packet= (unsigned long)(*(size_t *)arg1); + else + max_allowed_packet= (unsigned long)(*(size_t *)arg1); + break; + case MYSQL_OPT_NET_BUFFER_LENGTH: + net_buffer_length= (unsigned long)(*(size_t *)arg1); + break; + case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS: + if (*(my_bool *)arg1) + mysql->options.client_flag |= CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS; + else + mysql->options.client_flag &= ~CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS; + break; + case MYSQL_OPT_SSL_ENFORCE: + mysql->options.use_ssl= (*(my_bool *)arg1); + break; + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + OPT_SET_EXTENDED_VALUE(&mysql->options, tls_verify_server_cert, *(my_bool *)arg1); + break; + case MYSQL_OPT_SSL_KEY: + OPT_SET_VALUE_STR(&mysql->options, ssl_key, (char *)arg1); + break; + case MYSQL_OPT_SSL_CERT: + OPT_SET_VALUE_STR(&mysql->options, ssl_cert, (char *)arg1); + break; + case MYSQL_OPT_SSL_CA: + OPT_SET_VALUE_STR(&mysql->options, ssl_ca, (char *)arg1); + break; + case MYSQL_OPT_SSL_CAPATH: + OPT_SET_VALUE_STR(&mysql->options, ssl_capath, (char *)arg1); + break; + case MYSQL_OPT_SSL_CIPHER: + OPT_SET_VALUE_STR(&mysql->options, ssl_cipher, (char *)arg1); + break; + case MYSQL_OPT_SSL_CRL: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crl, (char *)arg1); + break; + case MYSQL_OPT_SSL_CRLPATH: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crlpath, (char *)arg1); + break; + case MYSQL_OPT_CONNECT_ATTR_DELETE: + { + uchar *h; + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (ma_hashtbl_inited(&mysql->options.extension->connect_attrs) && + (h= (uchar *)ma_hashtbl_search(&mysql->options.extension->connect_attrs, (uchar *)arg1, + arg1 ? (uint)strlen((char *)arg1) : 0))) + { + uchar *p= h; + size_t key_len= strlen((char *)p); + mysql->options.extension->connect_attrs_len-= key_len + get_store_length(key_len); + p+= key_len + 1; + key_len= strlen((char *)p); + mysql->options.extension->connect_attrs_len-= key_len + get_store_length(key_len); + ma_hashtbl_delete(&mysql->options.extension->connect_attrs, h); + } + + } + break; + case MYSQL_OPT_CONNECT_ATTR_RESET: + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (ma_hashtbl_inited(&mysql->options.extension->connect_attrs)) + { + ma_hashtbl_free(&mysql->options.extension->connect_attrs); + mysql->options.extension->connect_attrs_len= 0; + } + break; + case MARIADB_OPT_CONNECTION_HANDLER: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, connection_handler, (char *)arg1); + break; + case MARIADB_OPT_PORT: + OPT_SET_VALUE_INT(&mysql->options, port, *((uint *)arg1)); + break; + case MARIADB_OPT_UNIXSOCKET: + OPT_SET_VALUE_STR(&mysql->options, unix_socket, arg1); + break; + case MARIADB_OPT_USER: + OPT_SET_VALUE_STR(&mysql->options, user, arg1); + break; + case MARIADB_OPT_HOST: + OPT_SET_VALUE_STR(&mysql->options, host, arg1); + break; + case MARIADB_OPT_SCHEMA: + OPT_SET_VALUE_STR(&mysql->options, db, arg1); + break; + case MARIADB_OPT_DEBUG: + break; + case MARIADB_OPT_FOUND_ROWS: + mysql->options.client_flag|= CLIENT_FOUND_ROWS; + break; + case MARIADB_OPT_INTERACTIVE: + mysql->options.client_flag|= CLIENT_INTERACTIVE; + break; + case MARIADB_OPT_MULTI_RESULTS: + mysql->options.client_flag|= CLIENT_MULTI_RESULTS; + break; + case MARIADB_OPT_MULTI_STATEMENTS: + mysql->options.client_flag|= CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS; + break; + case MARIADB_OPT_PASSWORD: + OPT_SET_VALUE_STR(&mysql->options, password, arg1); + break; + case MARIADB_OPT_USERDATA: + { + void *data= va_arg(ap, void *); + uchar *buffer, *p; + char *key= (char *)arg1; + + if (!key || !data) + { + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto end; + } + + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (!ma_hashtbl_inited(&mysql->options.extension->userdata)) + { + if (_ma_hashtbl_init(&mysql->options.extension->userdata, + 0, 0, 0, ma_get_hash_keyval, ma_int_hash_free, 0)) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + } + /* check if key is already in buffer */ + p= (uchar *)ma_hashtbl_search(&mysql->options.extension->userdata, + (uchar *)key, + (uint)strlen(key)); + if (p) + { + p+= strlen(key) + 1; + memcpy(p, &data, sizeof(void *)); + break; + } + + if (!(buffer= (uchar *)malloc(strlen(key) + 1 + sizeof(void *)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + + p= buffer; + strcpy((char *)p, key); + p+= strlen(key) + 1; + memcpy(p, &data, sizeof(void *)); + + if (ma_hashtbl_insert(&mysql->options.extension->userdata, buffer)) + { + free(buffer); + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto end; + } + } + break; + case MYSQL_OPT_CONNECT_ATTR_ADD: + { + uchar *buffer; + void *arg2= va_arg(ap, void *); + size_t storage_len, key_len= arg1 ? strlen((char *)arg1) : 0, + value_len= arg2 ? strlen((char *)arg2) : 0; + if (!key_len || !value_len) + { + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto end; + } + storage_len= key_len + value_len + + get_store_length(key_len) + + get_store_length(value_len); + + /* since we store terminating zero character in hash, we need + * to increase lengths */ + key_len++; + value_len++; + + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (!key_len || + storage_len + mysql->options.extension->connect_attrs_len > 0xFFFF) + { + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto end; + } + + if (!ma_hashtbl_inited(&mysql->options.extension->connect_attrs)) + { + if (_ma_hashtbl_init(&mysql->options.extension->connect_attrs, + 0, 0, 0, ma_get_hash_keyval, ma_int_hash_free, 0)) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + } + if ((buffer= (uchar *)malloc(key_len + value_len))) + { + uchar *p= buffer; + strcpy((char *)p, arg1); + p+= (strlen(arg1) + 1); + if (arg2) + strcpy((char *)p, arg2); + + if (ma_hashtbl_insert(&mysql->options.extension->connect_attrs, buffer)) + { + free(buffer); + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + goto end; + } + mysql->options.extension->connect_attrs_len+= storage_len; + } + else + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto end; + } + } + break; + case MYSQL_ENABLE_CLEARTEXT_PLUGIN: + break; + case MYSQL_SECURE_AUTH: + mysql->options.secure_auth= *(my_bool *)arg1; + break; + case MYSQL_OPT_BIND: + OPT_SET_VALUE_STR(&mysql->options, bind_address, arg1); + break; + case MARIADB_OPT_TLS_CIPHER_STRENGTH: + OPT_SET_EXTENDED_VALUE_INT(&mysql->options, tls_cipher_strength, *((unsigned int *)arg1)); + break; + case MARIADB_OPT_SSL_FP: + case MARIADB_OPT_TLS_PEER_FP: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_fp, (char *)arg1); + mysql->options.use_ssl= 1; + break; + case MARIADB_OPT_SSL_FP_LIST: + case MARIADB_OPT_TLS_PEER_FP_LIST: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_fp_list, (char *)arg1); + mysql->options.use_ssl= 1; + break; + case MARIADB_OPT_TLS_PASSPHRASE: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_pw, (char *)arg1); + break; + case MARIADB_OPT_CONNECTION_READ_ONLY: + OPT_SET_EXTENDED_VALUE_INT(&mysql->options, read_only, *(my_bool *)arg1); + break; + case MARIADB_OPT_PROXY_HEADER: + { + size_t arg2 = va_arg(ap, size_t); + OPT_SET_EXTENDED_VALUE_BIN(&mysql->options, proxy_header, proxy_header_len, (char *)arg1, arg2); + } + break; + case MARIADB_OPT_TLS_VERSION: + case MYSQL_OPT_TLS_VERSION: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, tls_version, (char *)arg1); + break; + case MARIADB_OPT_IO_WAIT: + CHECK_OPT_EXTENSION_SET(&mysql->options); + mysql->options.extension->io_wait = (int(*)(my_socket, my_bool, int))arg1; + break; + case MARIADB_OPT_SKIP_READ_RESPONSE: + OPT_SET_EXTENDED_VALUE_INT(&mysql->options, skip_read_response, *(my_bool *)arg1); + break; + case MARIADB_OPT_RESTRICTED_AUTH: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, restricted_auth, (char *)arg1); + break; + case MARIADB_OPT_RPL_REGISTER_REPLICA: + { + unsigned int arg2 = va_arg(ap, unsigned int); + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, rpl_host,(char *)arg1); + OPT_SET_EXTENDED_VALUE(&mysql->options, rpl_port, (ushort)arg2); + } + break; + case MARIADB_OPT_STATUS_CALLBACK: + { + void *arg2= va_arg(ap, void *); + if (arg1 || arg2) + { + if (arg1) { + OPT_SET_EXTENDED_VALUE(&mysql->options, status_callback, arg1); + } + if (arg2) { + OPT_SET_EXTENDED_VALUE(&mysql->options, status_data, arg2); + } + } else { + OPT_SET_EXTENDED_VALUE(&mysql->options, status_callback, ma_save_session_track_info); + OPT_SET_EXTENDED_VALUE(&mysql->options, status_data, mysql); + } + } + break; + default: + va_end(ap); + SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); + return(1); + } + va_end(ap); + return(0); +end: + va_end(ap); + return(1); +} + +int +mysql_get_optionv(MYSQL *mysql, enum mysql_option option, void *arg, ...) +{ + va_list ap; + + va_start(ap, arg); + + switch(option) { + case MYSQL_OPT_CONNECT_TIMEOUT: + *((uint *)arg)= mysql->options.connect_timeout; + break; + case MYSQL_OPT_COMPRESS: + *((my_bool *)arg)= mysql->options.compress; + break; + case MYSQL_OPT_NAMED_PIPE: + *((my_bool *)arg)= mysql->options.named_pipe; + break; + case MYSQL_OPT_LOCAL_INFILE: /* Allow LOAD DATA LOCAL ?*/ + *((uint *)arg)= test(mysql->options.client_flag & CLIENT_LOCAL_FILES); + break; + case MYSQL_INIT_COMMAND: + /* mysql_get_optionsv(mysql, MYSQL_INIT_COMMAND, commands, elements) */ + { + unsigned int *elements; + if (arg) + *((char **)arg)= mysql->options.init_command ? mysql->options.init_command->buffer : NULL; + if ((elements= va_arg(ap, unsigned int *))) + *elements= mysql->options.init_command ? mysql->options.init_command->elements : 0; + } + break; + case MYSQL_READ_DEFAULT_FILE: + *((char **)arg)= mysql->options.my_cnf_file; + break; + case MYSQL_READ_DEFAULT_GROUP: + *((char **)arg)= mysql->options.my_cnf_group; + break; + case MYSQL_SET_CHARSET_DIR: + /* not supported in this version. Since all character sets + are internally available, we don't throw an error */ + *((char **)arg)= NULL; + break; + case MYSQL_SET_CHARSET_NAME: + if (mysql->charset) + *((const char **)arg)= mysql->charset->csname; + else + *((char **)arg)= mysql->options.charset_name; + break; + case MYSQL_OPT_RECONNECT: + *((my_bool *)arg)= mysql->options.reconnect; + break; + case MYSQL_OPT_PROTOCOL: + *((uint *)arg)= mysql->options.protocol; + break; + case MYSQL_OPT_READ_TIMEOUT: + *((uint *)arg)= mysql->options.read_timeout; + break; + case MYSQL_OPT_WRITE_TIMEOUT: + *((uint *)arg)= mysql->options.write_timeout; + break; + case MYSQL_REPORT_DATA_TRUNCATION: + *((my_bool *)arg)= mysql->options.report_data_truncation; + break; + case MYSQL_PROGRESS_CALLBACK: + *((void (**)(const MYSQL *, uint, uint, double, const char *, uint))arg)= + mysql->options.extension ? mysql->options.extension->report_progress : NULL; + break; + case MYSQL_SERVER_PUBLIC_KEY: + *((char **)arg)= mysql->options.extension ? + mysql->options.extension->server_public_key : NULL; + break; + case MYSQL_PLUGIN_DIR: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->plugin_dir : NULL; + break; + case MYSQL_DEFAULT_AUTH: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->default_auth : NULL; + break; + case MYSQL_OPT_NONBLOCK: + *((my_bool *)arg)= test(mysql->options.extension && mysql->options.extension->async_context); + break; + case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS: + *((my_bool *)arg)= test(mysql->options.client_flag & CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS); + break; + case MYSQL_OPT_SSL_ENFORCE: + *((my_bool *)arg)= mysql->options.use_ssl; + break; + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + *((my_bool*)arg) = mysql->options.extension ? mysql->options.extension->tls_verify_server_cert : 0; + break; + case MYSQL_OPT_SSL_KEY: + *((char **)arg)= mysql->options.ssl_key; + break; + case MYSQL_OPT_SSL_CERT: + *((char **)arg)= mysql->options.ssl_cert; + break; + case MYSQL_OPT_SSL_CA: + *((char **)arg)= mysql->options.ssl_ca; + break; + case MYSQL_OPT_SSL_CAPATH: + *((char **)arg)= mysql->options.ssl_capath; + break; + case MYSQL_OPT_SSL_CIPHER: + *((char **)arg)= mysql->options.ssl_cipher; + break; + case MYSQL_OPT_SSL_CRL: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->ssl_crl : NULL; + break; + case MYSQL_OPT_SSL_CRLPATH: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->ssl_crlpath : NULL; + break; + case MARIADB_OPT_TLS_VERSION: + case MYSQL_OPT_TLS_VERSION: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_version : NULL; + break; + case MYSQL_OPT_CONNECT_ATTRS: + /* mysql_get_optionsv(mysql, MYSQL_OPT_CONNECT_ATTRS, keys, vals, elements) */ + { + unsigned int i, *elements; + char **key= NULL; + void *arg1; + char **val= NULL; + + if (arg) + key= *(char ***)arg; + + arg1= va_arg(ap, char **); + if (arg1) + val= *(char ***)arg1; + + if (!(elements= va_arg(ap, unsigned int *))) + goto error; + + *elements= 0; + + if (!mysql->options.extension || + !ma_hashtbl_inited(&mysql->options.extension->connect_attrs)) + break; + + *elements= mysql->options.extension->connect_attrs.records; + + if (val || key) + { + for (i=0; i < *elements; i++) + { + uchar *p= ma_hashtbl_element(&mysql->options.extension->connect_attrs, i); + if (key) + key[i]= (char *)p; + p+= strlen((char *)p) + 1; + if (val) + val[i]= (char *)p; + } + } + } + break; + case MYSQL_OPT_MAX_ALLOWED_PACKET: + *((unsigned long *)arg)= (mysql) ? mysql->options.max_allowed_packet : + max_allowed_packet; + break; + case MYSQL_OPT_NET_BUFFER_LENGTH: + *((unsigned long *)arg)= net_buffer_length; + break; + case MYSQL_SECURE_AUTH: + *((my_bool *)arg)= mysql->options.secure_auth; + break; + case MYSQL_OPT_BIND: + *((char **)arg)= mysql->options.bind_address; + break; + case MARIADB_OPT_TLS_CIPHER_STRENGTH: + *((unsigned int *)arg) = mysql->options.extension ? mysql->options.extension->tls_cipher_strength : 0; + break; + case MARIADB_OPT_SSL_FP: + case MARIADB_OPT_TLS_PEER_FP: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_fp : NULL; + break; + case MARIADB_OPT_SSL_FP_LIST: + case MARIADB_OPT_TLS_PEER_FP_LIST: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_fp_list : NULL; + break; + case MARIADB_OPT_TLS_PASSPHRASE: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->tls_pw : NULL; + break; + case MARIADB_OPT_CONNECTION_READ_ONLY: + *((my_bool *)arg)= mysql->options.extension ? mysql->options.extension->read_only : 0; + break; + case MARIADB_OPT_USERDATA: + /* nysql_get_optionv(mysql, MARIADB_OPT_USERDATA, key, value) */ + { + uchar *p; + void *data= va_arg(ap, void *); + char *key= (char *)arg; + if (key && data && mysql->options.extension && ma_hashtbl_inited(&mysql->options.extension->userdata) && + (p= (uchar *)ma_hashtbl_search(&mysql->options.extension->userdata, (uchar *)key, + (uint)strlen((char *)key)))) + { + p+= strlen(key) + 1; + *((void **)data)= *((void **)p); + break; + } + if (data) + *((void **)data)= NULL; + } + break; + case MARIADB_OPT_CONNECTION_HANDLER: + *((char **)arg)= mysql->options.extension ? mysql->options.extension->connection_handler : NULL; + break; + case MARIADB_OPT_IO_WAIT: + *((int(**)(my_socket, my_bool, int))arg) = mysql->options.extension ? mysql->options.extension->io_wait : NULL; + break; + case MARIADB_OPT_SKIP_READ_RESPONSE: + *((my_bool*)arg)= mysql->options.extension ? mysql->options.extension->skip_read_response : 0; + break; + default: + va_end(ap); + SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); + return(1); + } + va_end(ap); + return(0); +error: + va_end(ap); + return(1); +} + +int STDCALL mysql_get_option(MYSQL *mysql, enum mysql_option option, void *arg) +{ + return mysql_get_optionv(mysql, option, arg); +} + +int STDCALL +mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) +{ + return mysql_optionsv(mysql, option, arg); +} + +int STDCALL +mysql_options4(MYSQL *mysql,enum mysql_option option, const void *arg1, const void *arg2) +{ + return mysql_optionsv(mysql, option, arg1, arg2); +} +/**************************************************************************** +** Functions to get information from the MySQL structure +** These are functions to make shared libraries more usable. +****************************************************************************/ + +/* MYSQL_RES */ +my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res) +{ + return res->row_count; +} + +unsigned int STDCALL mysql_num_fields(MYSQL_RES *res) +{ + return res->field_count; +} + +/* deprecated */ +my_bool STDCALL mysql_eof(MYSQL_RES *res) +{ + return res->eof; +} + +MYSQL_FIELD * STDCALL mysql_fetch_field_direct(MYSQL_RES *res,uint fieldnr) +{ + return &(res)->fields[fieldnr]; +} + +MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res) +{ + return (res)->fields; +} + +MYSQL_ROWS * STDCALL mysql_row_tell(MYSQL_RES *res) +{ + return res->data_cursor; +} + +uint STDCALL mysql_field_tell(MYSQL_RES *res) +{ + return (res)->current_field; +} + +/* MYSQL */ + +unsigned int STDCALL mysql_field_count(MYSQL *mysql) +{ + return mysql->field_count; +} + +my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql) +{ + return (mysql)->affected_rows; +} + +my_bool STDCALL mysql_autocommit(MYSQL *mysql, my_bool mode) +{ + return((my_bool) mysql_real_query(mysql, (mode) ? "SET autocommit=1" : + "SET autocommit=0", 16)); +} + +my_bool STDCALL mysql_commit(MYSQL *mysql) +{ + return((my_bool)mysql_real_query(mysql, "COMMIT", (unsigned long)strlen("COMMIT"))); +} + +my_bool STDCALL mysql_rollback(MYSQL *mysql) +{ + return((my_bool)mysql_real_query(mysql, "ROLLBACK", (unsigned long)strlen("ROLLBACK"))); +} + +my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql) +{ + return (mysql)->insert_id; +} + +uint STDCALL mysql_errno(MYSQL *mysql) +{ + return mysql ? mysql->net.last_errno : 0; +} + +const char * STDCALL mysql_error(MYSQL *mysql) +{ + return mysql ? (mysql)->net.last_error : (char *)""; +} + +const char *STDCALL mysql_info(MYSQL *mysql) +{ + return (mysql)->info; +} + +my_bool STDCALL mysql_more_results(MYSQL *mysql) +{ + return(test(mysql->server_status & SERVER_MORE_RESULTS_EXIST)); +} + +int STDCALL mysql_next_result(MYSQL *mysql) +{ + + /* make sure communication is not blocking */ + if (mysql->status != MYSQL_STATUS_READY) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + /* clear error, and mysql status variables */ + CLEAR_CLIENT_ERROR(mysql); + mysql->affected_rows = (ulonglong) ~0; + + if (mysql->server_status & SERVER_MORE_RESULTS_EXIST) + { + return(mysql->methods->db_read_query_result(mysql)); + } + + return(-1); +} + +ulong STDCALL mysql_thread_id(MYSQL *mysql) +{ + return (mysql)->thread_id; +} + +const char * STDCALL mysql_character_set_name(MYSQL *mysql) +{ + return mysql->charset->csname; +} + + +uint STDCALL mysql_thread_safe(void) +{ +#ifdef THREAD + return 1; +#else + return 0; +#endif +} + +/**************************************************************************** +** Some support functions +****************************************************************************/ + +/* +** Add escape characters to a string (blob?) to make it suitable for a insert +** to should at least have place for length*2+1 chars +** Returns the length of the to string +*/ + +ulong STDCALL +mysql_escape_string(char *to,const char *from, ulong length) +{ + return (ulong)mysql_cset_escape_slashes(ma_default_charset_info, to, from, length); +} + +ulong STDCALL +mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, + ulong length) +{ + if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) + return (ulong)mysql_cset_escape_quotes(mysql->charset, to, from, length); + else + return (ulong)mysql_cset_escape_slashes(mysql->charset, to, from, length); +} + +static void mariadb_get_charset_info(MYSQL *mysql, MY_CHARSET_INFO *cs) +{ + if (!cs) + return; + + cs->number= mysql->charset->nr; + cs->csname= mysql->charset->csname; + cs->name= mysql->charset->name; + cs->state= 0; + cs->comment= NULL; + cs->dir= NULL; + cs->mbminlen= mysql->charset->char_minlen; + cs->mbmaxlen= mysql->charset->char_maxlen; + + return; +} + +void STDCALL mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *cs) +{ + mariadb_get_charset_info(mysql, cs); +} + +int STDCALL mysql_set_character_set(MYSQL *mysql, const char *csname) +{ + const MARIADB_CHARSET_INFO *cs; + + if (!csname) + goto error; + + if ((cs= mysql_find_charset_name(csname))) + { + char buff[64]; + + snprintf(buff, 63, "SET NAMES %s", cs->csname); + if (!mysql_real_query(mysql, buff, (unsigned long)strlen(buff))) + { + mysql->charset= cs; + return(0); + } + return(mysql->net.last_errno); + } + +error: + my_set_error(mysql, CR_CANT_READ_CHARSET, SQLSTATE_UNKNOWN, + 0, csname, "compiled_in"); + return(mysql->net.last_errno); +} + +unsigned int STDCALL mysql_warning_count(MYSQL *mysql) +{ + return mysql->warning_count; +} + +const char * STDCALL mysql_sqlstate(MYSQL *mysql) +{ + return mysql->net.sqlstate; +} + +#ifndef _WIN32 +#include <signal.h> +static void ignore_sigpipe() +{ + signal(SIGPIPE, SIG_IGN); +} +#else +#define ignore_sigpipe() +#endif + +#ifdef _WIN32 +static int mysql_once_init() +#else +static void mysql_once_init() +#endif +{ + ma_init(); /* Will init threads */ + init_client_errs(); + get_default_configuration_dirs(); + set_default_charset_by_name(MARIADB_DEFAULT_CHARSET, 0); + if (mysql_client_plugin_init()) + { +#ifdef _WIN32 + return 1; +#else + return; +#endif + } + if (!mysql_port) + { +#if !__has_feature(memory_sanitizer) /* work around MSAN deficiency */ + struct servent *serv_ptr; +#endif + char *env; + + mysql_port = MARIADB_PORT; +#if !__has_feature(memory_sanitizer) /* work around MSAN deficiency */ + if ((serv_ptr = getservbyname("mysql", "tcp"))) + mysql_port = (uint)ntohs((ushort)serv_ptr->s_port); +#endif + if ((env = getenv("MYSQL_TCP_PORT"))) + mysql_port =(uint)atoi(env); + } + if (!mysql_unix_port) + { + char *env; +#ifdef _WIN32 + mysql_unix_port = (char*)MARIADB_NAMEDPIPE; +#else + mysql_unix_port = (char*)MARIADB_UNIX_ADDR; +#endif + if ((env = getenv("MYSQL_UNIX_PORT")) || + (env = getenv("MARIADB_UNIX_PORT"))) + mysql_unix_port = env; + } + if (!mysql_ps_subsystem_initialized) + mysql_init_ps_subsystem(); +#ifdef HAVE_TLS + ma_tls_start(0, 0); +#endif + ignore_sigpipe(); + mysql_client_init = 1; +#ifdef _WIN32 + return 0; +#endif +} + +#ifdef _WIN32 +static INIT_ONCE init_once= INIT_ONCE_STATIC_INIT; +BOOL CALLBACK win_init_once( + PINIT_ONCE InitOnce, + PVOID Parameter, + PVOID *lpContext) +{ + return !mysql_once_init(); + return TRUE; +} +#else +static pthread_once_t init_once = PTHREAD_ONCE_INIT; +#endif + +int STDCALL mysql_server_init(int argc __attribute__((unused)), + char **argv __attribute__((unused)), + char **groups __attribute__((unused))) +{ +#ifdef _WIN32 + BOOL ret = InitOnceExecuteOnce(&init_once, win_init_once, NULL, NULL); + return ret? 0: 1; +#else + return pthread_once(&init_once, mysql_once_init); +#endif +} + +void STDCALL mysql_server_end(void) +{ + if (!mysql_client_init) + return; + + release_configuration_dirs(); + mysql_client_plugin_deinit(); + + list_free(pvio_callback, 0); + if (ma_init_done) + ma_end(0); +#ifdef HAVE_TLS + ma_pvio_tls_end(); +#endif + mysql_client_init= 0; + ma_init_done= 0; +#ifdef WIN32 + init_once = (INIT_ONCE)INIT_ONCE_STATIC_INIT; +#else + init_once = (pthread_once_t)PTHREAD_ONCE_INIT; +#endif +} + +my_bool STDCALL mysql_thread_init(void) +{ + return 0; +} + +void STDCALL mysql_thread_end(void) +{ +} + +int STDCALL mysql_set_server_option(MYSQL *mysql, + enum enum_mysql_set_option option) +{ + char buffer[2]; + int2store(buffer, (uint)option); + return(ma_simple_command(mysql, COM_SET_OPTION, buffer, sizeof(buffer), 0, 0)); +} + +ulong STDCALL mysql_get_client_version(void) +{ + return MARIADB_PACKAGE_VERSION_ID; +} + +ulong STDCALL mysql_hex_string(char *to, const char *from, unsigned long len) +{ + char *start= to; + char hexdigits[]= "0123456789ABCDEF"; + + while (len--) + { + *to++= hexdigits[((unsigned char)*from) >> 4]; + *to++= hexdigits[((unsigned char)*from) & 0x0F]; + from++; + } + *to= 0; + return (ulong)(to - start); +} + +my_bool STDCALL mariadb_connection(MYSQL *mysql) +{ + return (strstr(mysql->server_version, "MariaDB") || + strstr(mysql->server_version, "-maria-")); +} + +const char * STDCALL +mysql_get_server_name(MYSQL *mysql) +{ + if (mysql->options.extension && + mysql->options.extension->db_driver != NULL) + return mysql->options.extension->db_driver->name; + return mariadb_connection(mysql) ? "MariaDB" : "MySQL"; +} + +static my_socket mariadb_get_socket(MYSQL *mysql) +{ + my_socket sock= INVALID_SOCKET; + if (mysql->net.pvio) + { + ma_pvio_get_handle(mysql->net.pvio, &sock); + + } + /* if an asynchronous connect is in progress, we need to obtain + pvio handle from async_context until the connection was + successfully established. + */ + else if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->pvio) + { + ma_pvio_get_handle(mysql->options.extension->async_context->pvio, &sock); + } + return sock; +} + +my_socket STDCALL +mysql_get_socket(MYSQL *mysql) +{ + return mariadb_get_socket(mysql); +} + +MARIADB_CHARSET_INFO * STDCALL mariadb_get_charset_by_name(const char *csname) +{ + return (MARIADB_CHARSET_INFO *)mysql_find_charset_name(csname); +} + +MARIADB_CHARSET_INFO * STDCALL mariadb_get_charset_by_nr(unsigned int csnr) +{ + return (MARIADB_CHARSET_INFO *)mysql_find_charset_nr(csnr); +} + +my_bool mariadb_get_infov(MYSQL *mysql, enum mariadb_value value, void *arg, ...) +{ + va_list ap; + + va_start(ap, arg); + + switch(value) { + case MARIADB_MAX_ALLOWED_PACKET: + *((size_t *)arg)= (size_t)max_allowed_packet; + break; + case MARIADB_NET_BUFFER_LENGTH: + *((size_t *)arg)= (size_t)net_buffer_length; + break; + case MARIADB_CONNECTION_ERROR_ID: + if (!mysql) + goto error; + *((unsigned int *)arg)= mysql->net.last_errno; + break; + case MARIADB_CONNECTION_ERROR: + if (!mysql) + goto error; + *((char **)arg)= mysql->net.last_error; + break; + case MARIADB_CONNECTION_SQLSTATE: + if (!mysql) + goto error; + *((char **)arg)= mysql->net.sqlstate; + break; + case MARIADB_CONNECTION_TLS_VERSION: + #ifdef HAVE_TLS + if (mysql && mysql->net.pvio && mysql->net.pvio->ctls) + *((char **)arg)= (char *)ma_pvio_tls_get_protocol_version(mysql->net.pvio->ctls); + else + #endif + goto error; + break; + case MARIADB_CONNECTION_TLS_VERSION_ID: + #ifdef HAVE_TLS + if (mysql && mysql->net.pvio && mysql->net.pvio->ctls) + *((unsigned int *)arg)= ma_pvio_tls_get_protocol_version_id(mysql->net.pvio->ctls); + else + #endif + goto error; + break; + case MARIADB_TLS_LIBRARY: +#ifdef HAVE_TLS + *((const char **)arg)= tls_library_version; +#else + *((const char **)arg)= "Off"; +#endif + break; + case MARIADB_CLIENT_VERSION: + *((const char **)arg)= MARIADB_CLIENT_VERSION_STR; + break; + case MARIADB_CLIENT_VERSION_ID: + *((size_t *)arg)= MARIADB_VERSION_ID; + break; + case MARIADB_CONNECTION_SERVER_VERSION: + if (mysql) + *((char **)arg)= mysql->server_version; + else + goto error; + break; + case MARIADB_CONNECTION_SERVER_TYPE: + if (mysql) + *((const char **)arg)= mariadb_connection(mysql) ? "MariaDB" : "MySQL"; + else + goto error; + break; + case MARIADB_CONNECTION_SERVER_VERSION_ID: + if (mysql) + *((size_t *)arg)= mariadb_server_version_id(mysql); + else + goto error; + break; + case MARIADB_CONNECTION_PROTOCOL_VERSION_ID: + if (mysql) + *((unsigned int *)arg)= mysql->protocol_version; + else + goto error; + break; + case MARIADB_CONNECTION_MARIADB_CHARSET_INFO: + if (mysql) + mariadb_get_charset_info(mysql, (MY_CHARSET_INFO *)arg); + else + goto error; + break; + case MARIADB_CONNECTION_SOCKET: + if (mysql) + *((my_socket *)arg)= mariadb_get_socket(mysql); + else + goto error; + break; + case MARIADB_CONNECTION_TYPE: + if (mysql && mysql->net.pvio) + *((int *)arg)= (int)mysql->net.pvio->type; + else + goto error; + break; + case MARIADB_CONNECTION_ASYNC_TIMEOUT_MS: + if (mysql && mysql->options.extension && mysql->options.extension->async_context) + *((unsigned int *)arg)= mysql->options.extension->async_context->timeout_value; + break; + case MARIADB_CONNECTION_ASYNC_TIMEOUT: + if (mysql && mysql->options.extension && mysql->options.extension->async_context) + { + unsigned int timeout= mysql->options.extension->async_context->timeout_value; + if (timeout > UINT_MAX - 999) + *((unsigned int *)arg)= (timeout - 1)/1000 + 1; + else + *((unsigned int *)arg)= (timeout+999)/1000; + } + break; + case MARIADB_CHARSET_NAME: + { + char *name; + name= va_arg(ap, char *); + if (name) + *((MARIADB_CHARSET_INFO **)arg)= (MARIADB_CHARSET_INFO *)mysql_find_charset_name(name); + else + goto error; + } + break; + case MARIADB_CHARSET_ID: + { + unsigned int nr; + nr= va_arg(ap, unsigned int); + *((MARIADB_CHARSET_INFO **)arg)= (MARIADB_CHARSET_INFO *)mysql_find_charset_nr(nr); + } + break; + case MARIADB_CONNECTION_SSL_CIPHER: + #ifdef HAVE_TLS + if (mysql && mysql->net.pvio && mysql->net.pvio->ctls) + *((char **)arg)= (char *)ma_pvio_tls_cipher(mysql->net.pvio->ctls); + else + #endif + *((char **)arg)= NULL; + break; + case MARIADB_CLIENT_ERRORS: + *((char ***)arg)= (char **)client_errors; + break; + case MARIADB_CONNECTION_INFO: + if (mysql) + *((char **)arg)= (char *)mysql->info; + else + goto error; + break; + case MARIADB_CONNECTION_PVIO_TYPE: + if (mysql && mysql->net.pvio) + *((unsigned int *)arg)= (unsigned int)mysql->net.pvio->type; + else + goto error; + break; + case MARIADB_CONNECTION_SCHEMA: + if (mysql) + *((char **)arg)= mysql->db; + else + goto error; + break; + case MARIADB_CONNECTION_USER: + if (mysql) + *((char **)arg)= mysql->user; + else + goto error; + break; + case MARIADB_CONNECTION_PORT: + if (mysql) + *((unsigned int *)arg)= mysql->port; + else + goto error; + break; + case MARIADB_CONNECTION_UNIX_SOCKET: + if (mysql) + *((char **)arg)= mysql->unix_socket; + else + goto error; + break; + case MARIADB_CONNECTION_HOST: + if (mysql) + *((char **)arg)= mysql->host; + else + goto error; + break; + case MARIADB_CONNECTION_SERVER_STATUS: + if (mysql) + *((unsigned int *)arg)= mysql->server_status; + else + goto error; + break; + case MARIADB_CONNECTION_SERVER_CAPABILITIES: + if (mysql) + *((unsigned long *)arg)= mysql->server_capabilities; + else + goto error; + break; + case MARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES: + if (mysql) + *((unsigned long *)arg)= mysql->extension->mariadb_server_capabilities; + else + goto error; + break; + case MARIADB_CONNECTION_CLIENT_CAPABILITIES: + if (mysql) + *((unsigned long *)arg)= mysql->client_flag; + else + goto error; + break; + case MARIADB_CONNECTION_BYTES_READ: + *((size_t *)arg)= mysql->net.pvio->bytes_read; + break; + case MARIADB_CONNECTION_BYTES_SENT: + *((size_t *)arg)= mysql->net.pvio->bytes_sent; + break; + default: + va_end(ap); + return(-1); + } + va_end(ap); + return(0); +error: + va_end(ap); + return(-1); +} + +my_bool STDCALL mariadb_get_info(MYSQL *mysql, enum mariadb_value value, void *arg) +{ + return mariadb_get_infov(mysql, value, arg); +} + +/* + Immediately aborts connection, making all subsequent read/write operations fail. + Does not invalidate memory used for mysql structure, nor closes any communication + channels - mysql_close is still needed. + Useful to break long query, in situation sending KILL is not possible. +*/ +int STDCALL mariadb_cancel(MYSQL *mysql) +{ + if (!mysql || !mysql->net.pvio || !mysql->net.pvio->methods || !mysql->net.pvio->methods->shutdown) + { + return 1; + } + else + { + MARIADB_PVIO *pvio = mysql->net.pvio; + return pvio->methods->shutdown(pvio); + } +} + +/* compatibility functions for MariaDB */ +void STDCALL +mysql_debug(const char *debug __attribute__((unused))) +{ + return; +} + +/******************************************************************** + mysql_net_ functions - low-level API to MySQL protocol +*********************************************************************/ +ulong STDCALL mysql_net_read_packet(MYSQL *mysql) +{ + return ma_net_safe_read(mysql); +} + +ulong STDCALL mysql_net_field_length(uchar **packet) +{ + return net_field_length(packet); +} + +my_bool STDCALL mysql_embedded(void) +{ +#ifdef EMBEDDED_LIBRARY + return 1; +#else + return 0; +#endif +} + +MYSQL_PARAMETERS *STDCALL +mysql_get_parameters(void) +{ + return &mariadb_internal_parameters; +} + +int STDCALL mysql_reset_connection(MYSQL *mysql) +{ + int rc; + + /* check if connection handler is active */ + if (IS_CONNHDLR_ACTIVE(mysql)) + { + if (mysql->extension->conn_hdlr->plugin && mysql->extension->conn_hdlr->plugin->reset) + return(mysql->extension->conn_hdlr->plugin->reset(mysql)); + } + + /* skip result sets */ + if (mysql->status == MYSQL_STATUS_USE_RESULT || + mysql->status == MYSQL_STATUS_GET_RESULT) + { + mthd_my_skip_result(mysql); + } + + if (mysql->server_status & SERVER_MORE_RESULTS_EXIST) + { + while (mysql_next_result(mysql)) + { + MYSQL_RES *res= mysql_use_result(mysql); + mysql_free_result(res); + } + } + + mysql->status= MYSQL_STATUS_READY; + + rc= ma_simple_command(mysql, COM_RESET_CONNECTION, 0, 0, 0, 0); + if (rc && mysql->options.reconnect) + { + /* There is no big sense in resetting but we need reconnect */ + rc= ma_simple_command(mysql, COM_RESET_CONNECTION,0,0,0,0); + } + if (rc) + return 1; + + /* reset the connection in all active statements */ + ma_invalidate_stmts(mysql, "mysql_reset_connection()"); + free_old_query(mysql); + mysql->status= MYSQL_STATUS_READY; + mysql->affected_rows= ~(my_ulonglong)0; + mysql->insert_id= 0; + return 0; +} + +#undef STDCALL +/* API functions for usage in dynamic plugins */ +struct st_mariadb_api MARIADB_API= +{ + mysql_num_rows, + mysql_num_fields, + mysql_eof, + mysql_fetch_field_direct, + mysql_fetch_fields, + mysql_row_tell, + mysql_field_tell, + mysql_field_count, + mysql_more_results, + mysql_next_result, + mysql_affected_rows, + mysql_autocommit, + mysql_commit, + mysql_rollback, + mysql_insert_id, + mysql_errno, + mysql_error, + mysql_info, + mysql_thread_id, + mysql_character_set_name, + mysql_get_character_set_info, + mysql_set_character_set, + mariadb_get_infov, + mariadb_get_info, + mysql_init, + mysql_ssl_set, + mysql_get_ssl_cipher, + mysql_change_user, + mysql_real_connect, + mysql_close, + mysql_select_db, + mysql_query, + mysql_send_query, + mysql_read_query_result, + mysql_real_query, + mysql_shutdown, + mysql_dump_debug_info, + mysql_refresh, + mysql_kill, + mysql_ping, + mysql_stat, + mysql_get_server_info, + mysql_get_server_version, + mysql_get_host_info, + mysql_get_proto_info, + mysql_list_dbs, + mysql_list_tables, + mysql_list_fields, + mysql_list_processes, + mysql_store_result, + mysql_use_result, + mysql_options, + mysql_free_result, + mysql_data_seek, + mysql_row_seek, + mysql_field_seek, + mysql_fetch_row, + mysql_fetch_lengths, + mysql_fetch_field, + mysql_escape_string, + mysql_real_escape_string, + mysql_thread_safe, + mysql_warning_count, + mysql_sqlstate, + mysql_server_init, + mysql_server_end, + mysql_thread_end, + mysql_thread_init, + mysql_set_server_option, + mysql_get_client_info, + mysql_get_client_version, + mariadb_connection, + mysql_get_server_name, + mariadb_get_charset_by_name, + mariadb_get_charset_by_nr, + mariadb_convert_string, + mysql_optionsv, + mysql_get_optionv, + mysql_get_option, + mysql_hex_string, + mysql_get_socket, + mysql_get_timeout_value, + mysql_get_timeout_value_ms, + mariadb_reconnect, + mysql_stmt_init, + mysql_stmt_prepare, + mysql_stmt_execute, + mysql_stmt_fetch, + mysql_stmt_fetch_column, + mysql_stmt_store_result, + mysql_stmt_param_count, + mysql_stmt_attr_set, + mysql_stmt_attr_get, + mysql_stmt_bind_param, + mysql_stmt_bind_result, + mysql_stmt_close, + mysql_stmt_reset, + mysql_stmt_free_result, + mysql_stmt_send_long_data, + mysql_stmt_result_metadata, + mysql_stmt_param_metadata, + mysql_stmt_errno, + mysql_stmt_error, + mysql_stmt_sqlstate, + mysql_stmt_row_seek, + mysql_stmt_row_tell, + mysql_stmt_data_seek, + mysql_stmt_num_rows, + mysql_stmt_affected_rows, + mysql_stmt_insert_id, + mysql_stmt_field_count, + mysql_stmt_next_result, + mysql_stmt_more_results, + mariadb_stmt_execute_direct, + mysql_reset_connection +}; + +/* + * Default methods for a connection. These methods are + * stored in mysql->methods and can be overwritten by + * a plugin, e.g. for using another database + */ +struct st_mariadb_methods MARIADB_DEFAULT_METHODS = { + /* open a connection */ + mthd_my_real_connect, + /* close connection */ + mysql_close_slow_part, + /* send command to server */ + mthd_my_send_cmd, + /* skip result set */ + mthd_my_skip_result, + /* read response packet */ + mthd_my_read_query_result, + /* read all rows from a result set */ + mthd_my_read_rows, + /* read one/next row */ + mthd_my_read_one_row, + /* check if datatype is supported */ + mthd_supported_buffer_type, + /* read response packet from prepare */ + mthd_stmt_read_prepare_response, + /* read response from stmt execute */ + mthd_my_read_query_result, + /* get result set metadata for a prepared statement */ + mthd_stmt_get_result_metadata, + /* get param metadata for a prepared statement */ + mthd_stmt_get_param_metadata, + /* read all rows (buffered) */ + mthd_stmt_read_all_rows, + /* fetch one row (unbuffered) */ + mthd_stmt_fetch_row, + /* store values in bind buffer */ + mthd_stmt_fetch_to_bind, + /* skip unbuffered stmt result */ + mthd_stmt_flush_unbuffered, + /* set error */ + my_set_error, + /* invalidate statements */ + ma_invalidate_stmts, + /* API functions */ + &MARIADB_API, + /* read execute response */ + mthd_stmt_read_execute_response, + /* generate execute request */ + ma_stmt_execute_generate_request, +}; diff --git a/libmariadb/libmariadb/mariadb_rpl.c b/libmariadb/libmariadb/mariadb_rpl.c new file mode 100644 index 00000000..0019f246 --- /dev/null +++ b/libmariadb/libmariadb/mariadb_rpl.c @@ -0,0 +1,2099 @@ +/************************************************************************************ + Copyright (C) 2018,2022 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + +*************************************************************************************/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <mysql.h> +#include <errmsg.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <zlib.h> +#include <ma_decimal.h> +#include <mariadb_rpl.h> + + +#ifdef WIN32 +#include <malloc.h> +#undef alloca +#define alloca _malloca +#endif + +#define RPL_EVENT_HEADER_SIZE 19 +#define RPL_ERR_POS(r) (r)->filename_length, (r)->filename, (r)->start_position +#define RPL_CHECK_NULL_POS(position, end)\ +{\ + uchar *tmp= (position);\ + while (*tmp && tmp < (end))\ + tmp++;\ + if (tmp > (end))\ + goto malformed_packet;\ +} + +#define RPL_CHECK_POS(position, end, bytes)\ +if ((end)-(position) < (ssize_t)(bytes))\ + goto malformed_packet; + +#define RPL_CHECK_FIELD_LENGTH(position, end)\ +{\ + RPL_CHECK_POS((position), (end), 1);\ + RPL_CHECK_POS((position), (end), net_field_size((position)));\ +} + +#define RPL_CHECK_POST_HEADER_LEN(position, end, type)\ +if (rpl->post_header_len[(type) - 1])\ +{\ + RPL_CHECK_POS((position), (end), rpl->post_header_len[(type)-1])\ +} + +static inline uint64_t uintNkorr(uint8_t len, u_char *p) +{ + switch (len) { + case 1: + return *p; + case 2: + return uint2korr(p); + case 3: + return uint3korr(p); + case 4: + return uint4korr(p); + case 8: + return uint8korr(p); + default: + return 0; + } +} + +static inline int net_field_size(uchar *p) +{ + if (*p <= 251) + return 1; + if (*p == 252) + return 3; + if (*p == 253) + return 4; + return 9; +} + +static inline int rpl_bit_size(uint32_t x) +{ + int bits= 1; + + while (x >>= 1) + bits++; + + return bits; +} + +static inline int rpl_byte_size(uint32_t x) +{ + int bits= rpl_bit_size(x); + + return (bits + 7) / 8; +} + +void rpl_set_error(MARIADB_RPL *rpl, + unsigned int error_nr, + const char *format, + ...) +{ + va_list ap; + + const char *errmsg; + + return; + if (!format) + { + if (error_nr >= CR_MIN_ERROR && error_nr <= CR_MYSQL_LAST_ERROR) + errmsg= ER(error_nr); + else if (error_nr >= CER_MIN_ERROR && error_nr <= CR_MARIADB_LAST_ERROR) + errmsg= CER(error_nr); + else + errmsg= ER(CR_UNKNOWN_ERROR); + } + + rpl->error_no= error_nr; + va_start(ap, format); + vsnprintf(rpl->error_msg, MYSQL_ERRMSG_SIZE - 1, + format ? format : errmsg, ap); + va_end(ap); + + /* For backward compatibility we also need to set a connection + error, if we read from primary instead of file */ + if (rpl->mysql) + { + my_set_error(rpl->mysql, error_nr, SQLSTATE_UNKNOWN, rpl->error_msg); + } +} + +const char * STDCALL mariadb_rpl_error(MARIADB_RPL *rpl) +{ + return rpl->error_msg; +} + +uint32_t STDCALL mariadb_rpl_errno(MARIADB_RPL *rpl) +{ + return rpl->error_no; +} + +uint8_t rpl_parse_opt_metadata(MARIADB_RPL_EVENT *event, const uchar *buffer, size_t length) +{ + const uchar *pos= buffer, *end= buffer + length; + struct st_mariadb_rpl_table_map_event *tm_event= (struct st_mariadb_rpl_table_map_event *)&event->event; + + if (event->event_type != TABLE_MAP_EVENT) + return 1; + + while (pos < end) + { + uint8_t meta_type= *pos++; + uint32_t len; + + RPL_CHECK_FIELD_LENGTH((uchar *)pos, end); + len= net_field_length((uchar **)&pos); + RPL_CHECK_POS(pos, end,len); + + switch(meta_type) + { + case SIGNEDNESS: + tm_event->signed_indicator= (uchar *)pos; + pos+= len; + break; + case DEFAULT_CHARSET: + tm_event->default_charset= *pos; + pos+= len; + break; + case COLUMN_CHARSET: + tm_event->column_charsets.data= pos; + tm_event->column_charsets.length= len; + pos+= len; + break; + case COLUMN_NAME: + tm_event->column_names.data= pos; + tm_event->column_names.length= len; + pos+= len; + break; + case SIMPLE_PRIMARY_KEY: + tm_event->simple_primary_keys.data= pos; + tm_event->simple_primary_keys.length= len; + pos+= len; + break; + case PRIMARY_KEY_WITH_PREFIX: + tm_event->prefixed_primary_keys.data= pos; + tm_event->prefixed_primary_keys.length= len; + pos+= len; + break; + case GEOMETRY_TYPE: + { + tm_event->geometry_types.data= pos; + tm_event->geometry_types.length= len; + pos+= len; + break; + } + /* Default character set used by all columns */ + case ENUM_AND_SET_DEFAULT_CHARSET: + tm_event->enum_set_default_charset= *pos; + pos+= len; + break; + case ENUM_AND_SET_COLUMN_CHARSET: + tm_event->enum_set_column_charsets.data= pos; + tm_event->enum_set_column_charsets.length= len; + pos+= len; + break; + case SET_STR_VALUE: + tm_event->set_values.data= pos; + tm_event->set_values.length= len; + pos+= len; + break; + case ENUM_STR_VALUE: + tm_event->enum_values.data= pos; + tm_event->enum_values.length= len; + pos+= len; + break; + default: + rpl_set_error(event->rpl, CR_BINLOG_ERROR, 0, RPL_ERR_POS(event->rpl), "Unknown/unsupported event type"); + pos+= len; + break; + } + } + return 0; +malformed_packet: + return 1; +} + +static void *ma_calloc_root(void *memroot, size_t len) +{ + void *p; + + if ((p= ma_alloc_root(memroot, len))) + memset(p, 0, len); + return p; +} + +static void rpl_set_string_and_len(MARIADB_STRING *s, + unsigned char *buffer, + size_t len) +{ + if (!buffer || !len) + { + s->length= 0; + return; + } + s->str= (char *)buffer; + s->length= len; +} + +static uint8_t rpl_alloc_set_string_and_len(MARIADB_RPL_EVENT *event, + MARIADB_STRING *s, + void *buffer, + size_t len) +{ + if (!buffer || !len) + { + s->length= 0; + return 0; + } + + if (!(s->str = (char *)ma_alloc_root(&event->memroot, len))) + return 1; + + memcpy(s->str, buffer, len); + s->length= len; + return 0; +} + +static uint8_t rpl_metadata_size(enum enum_field_types field_type) +{ + switch (field_type) { + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_DATETIME2: + case MYSQL_TYPE_TIMESTAMP2: + case MYSQL_TYPE_TIME2: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + return 1; + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_BIT: + return 2; + default: + return 0; + } +} + +static uint8_t ma_rpl_get_second_part(MYSQL_TIME *tm, uchar *ptr, uchar *metadata) +{ + switch(metadata[0]) + { + case 0: + tm->second_part= 0; + return 0; + case 1: + case 2: + tm->second_part= (uint32_t)ptr[0] * 10000; + return 1; + case 3: + case 4: + tm->second_part= myisam_sint2korr(ptr) * 100; + return 2; + case 5: + case 6: + tm->second_part= myisam_sint3korr(ptr); + return 3; + default: + return 0; + } +} + +MARIADB_RPL_ROW * STDCALL +mariadb_rpl_extract_rows(MARIADB_RPL *rpl, + MARIADB_RPL_EVENT *tm_event, + MARIADB_RPL_EVENT *row_event) +{ + uchar *start, *pos, *end; + MARIADB_RPL_ROW *f_row= NULL, *p_row= NULL, *c_row= NULL; + uint32_t column_count; + + if (!rpl || !tm_event || !row_event) + return NULL; + + if (tm_event->event_type != TABLE_MAP_EVENT || !(IS_ROW_EVENT(row_event))) + { + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, RPL_ERR_POS(rpl), "Event with wrong event type passed."); + return NULL; + } + + if (row_event->event.rows.table_id != tm_event->event.table_map.table_id) + { + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, RPL_ERR_POS(rpl), "table_id in table_map event differs."); + return NULL; + } + + if (!row_event->event.rows.row_data_size || + !row_event->event.rows.row_data) + { + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, "Row event has no data."); + return NULL; + } + + column_count= tm_event->event.table_map.column_count; + + start= pos = row_event->event.rows.row_data; + end= start + row_event->event.rows.row_data_size; + + while (pos < end) + { + uchar *n_bitmap; + uint32_t i; + + uchar *metadata= (uchar *)tm_event->event.table_map.metadata.str; + + if (!(c_row = (MARIADB_RPL_ROW *)ma_calloc_root(&row_event->memroot, sizeof(MARIADB_RPL_ROW))) || + !(c_row->columns= (MARIADB_RPL_VALUE *)ma_calloc_root(&row_event->memroot, + sizeof(MARIADB_RPL_VALUE) * column_count))) + { + rpl_set_error(rpl, CR_OUT_OF_MEMORY, 0); + return NULL; + } + + if (!f_row) + f_row= c_row; + if (p_row) + p_row->next= c_row; + + c_row->column_count= column_count; + n_bitmap= pos; + pos+= (column_count + 7) / 8; + + for (i= 0; i < column_count; i++) + { + MARIADB_RPL_VALUE *column= &c_row->columns[i]; + column->field_type= (uchar)tm_event->event.table_map.column_types.str[i]; + /* enum, set and string types are stored as string - first metadata + byte contains real_type, second byte contains the length */ + if (column->field_type == MYSQL_TYPE_STRING) + { + if (metadata[0] == MYSQL_TYPE_ENUM || metadata[0] == MYSQL_TYPE_SET) + column->field_type = metadata[0]; + } + + if ((n_bitmap[i / 8] >> (i % 8)) & 1) + { + column->is_null= 1; + metadata+= rpl_metadata_size(column->field_type); + continue; + } + if (column->field_type == MYSQL_TYPE_BLOB) + { + switch(metadata[0]) + { + case 1: + column->field_type= MYSQL_TYPE_TINY_BLOB; + break; + case 3: + column->field_type= MYSQL_TYPE_MEDIUM_BLOB; + break; + case 4: + column->field_type= MYSQL_TYPE_LONG_BLOB; + break; + default: + break; + } + } + switch (column->field_type) { + case MYSQL_TYPE_TINY: + column->val.ll= sint1korr(pos); + column->val.ull= uint1korr(pos); + pos++; + break; + case MYSQL_TYPE_YEAR: + column->val.ull= uint1korr(pos++) + 1900; + break; + case MYSQL_TYPE_SHORT: + column->val.ll= sint2korr(pos); + column->val.ull= uint2korr(pos); + pos+= 2; + break; + case MYSQL_TYPE_INT24: + column->val.ll= sint3korr(pos); + column->val.ull= uint3korr(pos); + pos+= 3; + break; + case MYSQL_TYPE_LONG: + column->val.ll= sint4korr(pos); + column->val.ull= uint4korr(pos); + pos+= 4; + break; + case MYSQL_TYPE_LONGLONG: + column->val.ll= sint8korr(pos); + column->val.ull= uint8korr(pos); + pos+= 8; + break; + case MYSQL_TYPE_NEWDECIMAL: + { + uint8_t precision= *metadata++; + uint8_t scale= *metadata++; + uint32_t bin_size; + decimal dec; + char str[200]; + char buf[100]; + int s_len= sizeof(str) - 1; + + dec.buf= (void *)buf; + dec.len= sizeof(buf) / sizeof(decimal_digit); + + bin_size= decimal_bin_size(precision, scale); + bin2decimal((char *)pos, &dec, precision, scale); + decimal2string(&dec, str, &s_len); + pos+= bin_size; + + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, str, s_len)) + goto mem_error; + + break; + } + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + { + uint8_t flen= *metadata++; + if (flen == 4) + { + float4get(column->val.f, pos); + } + if (flen == 8) + { + float8get(column->val.d, pos); + } + pos+= flen; + break; + } + case MYSQL_TYPE_BIT: + { + uint8_t num_bits= (metadata[0] & 0xFF) + metadata[1] * 8; + uint8_t b_len= (num_bits + 7) / 8; + metadata+= 2; + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, pos, b_len)) + goto mem_error; + pos+= b_len; + break; + } + case MYSQL_TYPE_TIMESTAMP: + { + column->val.ull= uint4korr(pos); + pos+= 4; + break; + } + case MYSQL_TYPE_TIMESTAMP2: + { + char tmp[20]; + uint32_t p1= uint4korr(pos); + uint8_t f_len= *metadata++; + uint32_t p2; + pos+= 4; + p2= (uint32_t)uintNkorr(f_len, pos); + pos+= f_len; + sprintf(tmp, "%d.%d", p1, p2); + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, tmp, strlen(tmp))) + goto mem_error; + break; + } + case MYSQL_TYPE_DATE: + { + MYSQL_TIME *tm= &column->val.tm; + uint32_t d_val= uint3korr(pos); + pos+= 3; + tm->year= (int)(d_val / (16 * 32)); + tm->month= (int)(d_val / 32 % 16); + tm->day= d_val % 32; + tm->time_type= MYSQL_TIMESTAMP_DATE; + break; + } + case MYSQL_TYPE_TIME2: + { + MYSQL_TIME *tm= &column->val.tm; + int64_t t_val= myisam_uint3korr(pos) - 0x800000LL; + + if ((tm->neg = t_val < 0)) + t_val= -t_val; + + pos+= 3; + tm->hour= (t_val >> 12) % (1 << 10); + tm->minute= (t_val >> 6) % (1 << 6); + tm->second= t_val % (1 << 6); + + pos+= ma_rpl_get_second_part(tm, pos, metadata); + metadata++; + tm->time_type= MYSQL_TIMESTAMP_TIME; + column->field_type= MYSQL_TYPE_TIME; + break; + } + case MYSQL_TYPE_DATETIME2: + { + MYSQL_TIME *tm= &column->val.tm; + uint64_t dt_val= mi_uint5korr(pos) - 0x8000000000LL, + date_part, time_part; + pos+= 5; + + date_part= dt_val >> 17; + time_part= dt_val % (1 << 17); + + tm->day= (unsigned int)date_part % (1 << 5); + tm->month= (unsigned int)(date_part >> 5) % 13; + tm->year= (unsigned int)(date_part >> 5) / 13; + + tm->second= time_part % (1 << 6); + tm->minute= (time_part >> 6) % (1 << 6); + tm->hour= (uint32_t)(time_part >> 12); + + tm->time_type= MYSQL_TIMESTAMP_DATETIME; + column->field_type= MYSQL_TYPE_DATETIME; + + pos+= ma_rpl_get_second_part(tm, pos, metadata); + metadata++; + break; + } + case MYSQL_TYPE_STRING: + { + uint8_t s_len= metadata[2]; + metadata+= 2; + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, pos, s_len)) + goto mem_error; + pos+= s_len; + break; + } + case MYSQL_TYPE_ENUM: + { + uint8_t e_len= metadata[2]; + metadata+= 2; + column->val.ull= uintNkorr(e_len, pos); + pos+= e_len; + break; + } + case MYSQL_TYPE_SET: + { + uint8_t e_len= metadata[2]; + metadata+= 2; + column->val.ull= uintNkorr(e_len, pos); + pos+= e_len; + break; + } + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_GEOMETRY: + { + uint8_t h_len= *metadata++; + uint64_t b_len= uintNkorr(h_len, pos); + pos+= h_len; + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, pos, (size_t)b_len)) + goto mem_error; + pos+= b_len; + break; + } + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + { + uint32_t s_len= uint2korr(metadata); + uint8_t byte_len= rpl_byte_size(s_len); + metadata+= 2; + s_len= (uint32_t)uintNkorr(byte_len, pos); + pos+= byte_len; + if (rpl_alloc_set_string_and_len(row_event, &column->val.str, pos, s_len)) + goto mem_error; + pos+= s_len; + break; + } + case MYSQL_TYPE_TIME: + { + MYSQL_TIME *tm= &column->val.tm; + uint64_t t= uint8korr(pos); + pos+= 8; + tm->hour= (unsigned int)(t/100)/100; + tm->minute= (unsigned int)(t/100) % 100; + tm->second= (unsigned int)t % 100; + tm->time_type= MYSQL_TIMESTAMP_TIME; + break; + } + case MYSQL_TYPE_DATETIME: + { + MYSQL_TIME *tm= &column->val.tm; + uint64_t t= uint8korr(pos); + uint32_t d_val= (uint32_t)t / 1000000, + t_val= (uint32_t)t % 1000000; + pos+= 8; + tm->year= (unsigned int)(d_val / 100) / 100; + tm->month= (unsigned int)(d_val / 100) % 100; + tm->day= (unsigned int)d_val % 100; + tm->hour= (t_val/100)/100; + tm->minute= (t_val/100) % 100; + tm->second= t_val % 100; + tm->time_type= MYSQL_TIMESTAMP_DATETIME; + break; + } + + + default: + break; + } + } + p_row= c_row; + } + return f_row; + +mem_error: + rpl_set_error(rpl, CR_OUT_OF_MEMORY, 0); + return NULL; +} + +MARIADB_RPL * STDCALL mariadb_rpl_init_ex(MYSQL *mysql, unsigned int version) +{ + MARIADB_RPL *rpl; + + if (version < MARIADB_RPL_REQUIRED_VERSION || + version > MARIADB_RPL_VERSION) + { + if (mysql) + my_set_error(mysql, CR_VERSION_MISMATCH, SQLSTATE_UNKNOWN, 0, version, + MARIADB_RPL_VERSION, MARIADB_RPL_REQUIRED_VERSION); + return 0; + } + + if (!(rpl= (MARIADB_RPL *)calloc(1, sizeof(MARIADB_RPL)))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return 0; + } + rpl->version= version; + + if ((rpl->mysql= mysql)) + { + MYSQL_RES *result; + if (!mysql_query(mysql, "select @@binlog_checksum")) + { + if ((result= mysql_store_result(mysql))) + { + MYSQL_ROW row= mysql_fetch_row(result); + if (!strcmp(row[0], "CRC32")) + { + rpl->artificial_checksum= 1; + } + mysql_free_result(result); + } + } + } + + /* recommended way to set replica host and port is via rpl_optionsv(), however if + hostname and port was set via mysql_optionsv, we set it here to rpl */ + if (rpl->mysql && rpl->mysql->options.extension && rpl->mysql->options.extension->rpl_host) + { + mariadb_rpl_optionsv(rpl, MARIADB_RPL_HOST, rpl->mysql->options.extension->rpl_host); + mariadb_rpl_optionsv(rpl, MARIADB_RPL_PORT, rpl->mysql->options.extension->rpl_port); + } + return rpl; +} + +void STDCALL mariadb_free_rpl_event(MARIADB_RPL_EVENT *event) +{ + if (event) + { + ma_free_root(&event->memroot, MYF(0)); + free(event); + } +} + +int STDCALL mariadb_rpl_open(MARIADB_RPL *rpl) +{ + unsigned char *ptr, *buf; + + if (!rpl) + return 1; + + /* COM_BINLOG_DUMP: + Ofs Len Data + 0 1 COM_BINLOG_DUMP + 1 4 position + 5 2 flags + 7 4 server id + 11 * filename + + * = filename length + + */ + rpl_clear_error(rpl); + + /* if replica was specified, we will register replica via + COM_REGISTER_SLAVE */ + if (rpl->host) + { + /* Protocol: + Ofs Len Data + 0 1 COM_REGISTER_SLAVE + 1 4 server id + 5 1 replica host name length + 6 <n> replica host name + 1 user name length + <n> user name + 1 password length + <n> password + 2 replica port + 4 replication rank (unused) + 4 source server id (unused) + */ + unsigned char *p, buffer[1024]; + size_t len= MIN(strlen(rpl->host), 255); + + p= buffer; + int4store(p, rpl->server_id); + p+= 4; + *p++= (unsigned char)len; + memcpy(p, rpl->host, len); + p+= len; + + /* Don't send user, password, rank and server_id */ + *p++= 0; + *p++= 0; + int2store(p, rpl->port); + p+= 2; + + int4store(p, 0); + p+= 4; + int4store(p, 0); + p+= 4; + + if (ma_simple_command(rpl->mysql, COM_REGISTER_SLAVE, (const char *)buffer, p - buffer, 0, 0)) + { + rpl_set_error(rpl, mysql_errno(rpl->mysql), 0, NULL, 0); + return 1; + } + } + + if (rpl->mysql) + { + uint32_t replica_id= rpl->server_id; + ptr= buf= (unsigned char *)alloca(rpl->filename_length + 11); + + if (!ptr) + { + rpl_set_error(rpl, CR_OUT_OF_MEMORY, 0); + } + + if (rpl->is_semi_sync) + { + if (mysql_query(rpl->mysql, "SET @rpl_semi_sync_slave=1")) + { + rpl_set_error(rpl, mysql_errno(rpl->mysql), 0, mysql_error(rpl->mysql)); + return 1; + } + } + else { + MYSQL_RES* result; + MYSQL_ROW row; + if (mysql_query(rpl->mysql, "SELECT @rpl_semi_sync_slave=1")) + { + rpl_set_error(rpl, mysql_errno(rpl->mysql), 0, mysql_error(rpl->mysql)); + return 1; + } + if ((result = mysql_store_result(rpl->mysql))) + { + if ((row = mysql_fetch_row(result))) + rpl->is_semi_sync = (row[0] != NULL && row[0][0] == '1'); + mysql_free_result(result); + } + } + + int4store(ptr, (unsigned int)rpl->start_position); + ptr+= 4; + int2store(ptr, rpl->flags); + ptr+= 2; + if ((rpl->flags & MARIADB_RPL_BINLOG_DUMP_NON_BLOCK) && !replica_id) + replica_id= 1; + int4store(ptr, replica_id); + ptr+= 4; + memcpy(ptr, rpl->filename, rpl->filename_length); + ptr+= rpl->filename_length; + + return (ma_simple_command(rpl->mysql, COM_BINLOG_DUMP, (const char *)buf, ptr - buf, 1, 0)); + } else + { + char *buf[RPL_BINLOG_MAGIC_SIZE]; + MYSQL mysql; + + /* Semi sync doesn't work when processing files */ + rpl->is_semi_sync = 0; + + if (rpl->fp) + ma_close(rpl->fp); + + if (!(rpl->fp= ma_open((const char *)rpl->filename, "r", &mysql))) + { + rpl_set_error(rpl, CR_FILE_NOT_FOUND, 0, rpl->filename, errno); + return errno; + } + + if (ma_read(buf, 1, RPL_BINLOG_MAGIC_SIZE, rpl->fp) != 4) + { + rpl_set_error(rpl, CR_FILE_READ, 0, rpl->filename, errno); + return errno; + } + + /* check if it is a valid binlog file */ + if (memcmp(buf, RPL_BINLOG_MAGIC, RPL_BINLOG_MAGIC_SIZE) != 0) + { + rpl_set_error(rpl, CR_BINLOG_INVALID_FILE, 0, rpl->filename, errno); + return errno; + } + + return 0; + } +} + +static int ma_set_rpl_filename(MARIADB_RPL *rpl, const unsigned char *filename, size_t len) +{ + if (!rpl) + return 1; + free(rpl->filename); + if (!(rpl->filename= (char *)malloc(len))) + return 1; + memcpy(rpl->filename, filename, len); + rpl->filename_length= (uint32_t)len; + return 0; +} + +/* + * Returns compression info: + * Ofs Len + * 0 1 header: + * ofs & 0x07 >> 4: algorithm, always 0=zlib + * ofs & 0x07: header size + * 1 header size uncompressed length in MyISAM format. + */ +static uint32_t get_compression_info(const unsigned char *buf, + uint8_t *algorithm, + uint8_t *header_size) +{ + uint8_t alg, header; + uint32 len= 0; + + if (!algorithm) + algorithm= &alg; + if (!header_size) + header_size= &header; + + *header_size= 0; + *algorithm= 0; + + if (!buf) + return len; + + if ((buf[0] & 0xe0) != 0x80) + return len; + + *header_size= buf[0] & 0x07; + *algorithm = (buf[0] & 0x07) >> 4; + + buf++; + + /* Attention: we can't use uint*korr, here, we need myisam macros since + length is stored in high byte first order + */ + switch(*header_size) { + case 1: + len= *buf; + break; + case 2: + len= myisam_uint2korr(buf); + break; + case 3: + len= myisam_uint3korr(buf); + break; + case 4: + len= myisam_uint4korr(buf); + break; + default: + len= 0; + break; + } + + *header_size += 1; + return len; +} + +static uint8_t mariadb_rpl_send_semisync_ack(MARIADB_RPL* rpl, MARIADB_RPL_EVENT* event) +{ + size_t buf_size = 0; + uchar* buf; + + if (!rpl) + return 1; + + if (!event) + { + rpl_set_error(rpl, CR_BINLOG_SEMI_SYNC_ERROR, 0, "Invalid event"); + return 1; + } + + if (!rpl->is_semi_sync) + { + rpl_set_error(rpl, CR_BINLOG_SEMI_SYNC_ERROR, 0, "semi synchronous replication is not enabled"); + return 1; + } + if (!event->is_semi_sync || (event->semi_sync_flags != SEMI_SYNC_ACK_REQ)) + { + rpl_set_error(rpl, CR_BINLOG_SEMI_SYNC_ERROR, 0, "This event doesn't require to send semi synchronous acknoledgement"); + return 1; + } + + buf_size = rpl->filename_length + 9; + buf = alloca(buf_size); + + buf[0] = SEMI_SYNC_INDICATOR; + int8store(buf + 1, (uint64_t)event->next_event_pos); + memcpy(buf + 9, rpl->filename, rpl->filename_length); + + ma_net_clear(&rpl->mysql->net); + + if (ma_net_write(&rpl->mysql->net, buf, buf_size) || + (ma_net_flush(&rpl->mysql->net))) + { + rpl_set_error(rpl, CR_CONNECTION_ERROR, 0); + return 1; + } + + return 0; +} + +MARIADB_RPL_EVENT * STDCALL mariadb_rpl_fetch(MARIADB_RPL *rpl, MARIADB_RPL_EVENT *event) +{ + unsigned char *ev= 0; + unsigned char *checksum_start= 0; + unsigned char *ev_start= 0; + unsigned char *ev_end= 0; + size_t len= 0; + MARIADB_RPL_EVENT *rpl_event= 0; + + if (!rpl || (!rpl->mysql && !rpl->fp)) + return 0; + + if (event) + { + MA_MEM_ROOT memroot= event->memroot; + rpl_event= event; + ma_free_root(&memroot, MYF(MY_KEEP_PREALLOC)); + memset(rpl_event, 0, sizeof(MARIADB_RPL_EVENT)); + rpl_event->memroot= memroot; + } else { + if (!(rpl_event = (MARIADB_RPL_EVENT *)malloc(sizeof(MARIADB_RPL_EVENT)))) + goto mem_error; + memset(rpl_event, 0, sizeof(MARIADB_RPL_EVENT)); + ma_init_alloc_root(&rpl_event->memroot, 8192, 0); + } + + rpl_event->rpl= rpl; + + while (1) { + unsigned long pkt_len; + + if (rpl->mysql) + { + pkt_len= ma_net_safe_read(rpl->mysql); + + if (pkt_len == packet_error) + { + mariadb_free_rpl_event(rpl_event); + return 0; + } + + /* EOF packet: + see https://mariadb.com/kb/en/library/eof_packet/ + Packet length must be less than 9 bytes, EOF header + is 0xFE. + */ + if (pkt_len < 9 && rpl->mysql->net.read_pos[0] == 0xFE) + { + mariadb_free_rpl_event(rpl_event); + return 0; + } + + /* if ignore heartbeat flag was set, we ignore this + record and continue to fetch next record. + The first byte is always status byte (0x00) + For event header description see + https://mariadb.com/kb/en/library/2-binlog-event-header/ */ + if (rpl->flags & MARIADB_RPL_IGNORE_HEARTBEAT) + { + if (rpl->mysql->net.read_pos[1 + 4] == HEARTBEAT_LOG_EVENT) + continue; + } + + if (!(rpl_event->raw_data= ma_alloc_root(&rpl_event->memroot, pkt_len))) + goto mem_error; + + rpl_event->raw_data_size= pkt_len; + memcpy(rpl_event->raw_data, rpl->mysql->net.read_pos, pkt_len); + ev= rpl_event->raw_data; + } else if (rpl->fp) { + char buf[EVENT_HEADER_OFS]; /* header */ + size_t rc; + uint32_t len= 0; + char *p= buf; + + if (ma_feof(rpl->fp)) + { + return NULL; + } + + memset(buf, 0, EVENT_HEADER_OFS); + if ((rc= ma_read(buf, 1, EVENT_HEADER_OFS - 1, rpl->fp)) != EVENT_HEADER_OFS - 1) + { + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, "Can't read event header"); + mariadb_free_rpl_event(rpl_event); + return NULL; + } + len= uint4korr(p + 9); + + if (!(rpl_event->raw_data= ma_alloc_root(&rpl_event->memroot, len))) + { + rpl_set_error(rpl, CR_OUT_OF_MEMORY, 0); + mariadb_free_rpl_event(rpl_event); + return NULL; + } + + rpl_event->raw_data_size= len; + memcpy(rpl_event->raw_data, buf, EVENT_HEADER_OFS - 1); + len-= (EVENT_HEADER_OFS - 1); + rc= ma_read(rpl_event->raw_data + EVENT_HEADER_OFS - 1, 1, len, rpl->fp); + if (rc != len) + { + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, "Error while reading post header"); + mariadb_free_rpl_event(rpl_event); + return NULL; + } + ev= rpl_event->raw_data; + + /* We don't decrypt yet */ + if (rpl->encrypted) { + return rpl_event; + } + } + + ev_end= rpl_event->raw_data + rpl_event->raw_data_size; + + + if (rpl->mysql) + { + RPL_CHECK_POS(ev, ev_end, 1); + rpl_event->ok= *ev++; + + /* CONC-470: add support for semi snychronous replication */ + if (rpl->is_semi_sync && (rpl_event->is_semi_sync= (*ev == SEMI_SYNC_INDICATOR))) + { + RPL_CHECK_POS(ev, ev_end, 1); + ev++; + rpl_event->semi_sync_flags= *ev++; + } + } + rpl_event->raw_data_ofs= ev - rpl_event->raw_data; + + /* check sum verification: + check sum will be calculated from begin of binlog header + */ + checksum_start= ev; + + /****************************************************************** + Binlog event header: + + All binary log events have the same header: + - uint32_t timestamp: creation time + - uint8_t event_type: type code of the event + - uint32_t server_id: server which created the event + - uint32_t event_len: length of the event. If checksum is + enabled, the length also include 4 bytes + of checksum + ------------- if START_ENCRYPTION_EVENT was sent, --------------- + encrypted part starts here: + - uint32_t next_pos: Position of next binary log event + - uint16_t flags: flags + + The size of binlog event header must match the header size returned + by FORMAT_DESCIPTION_EVENT. In version 4 it is always 19. + ********************************************************************/ + RPL_CHECK_POS(ev, ev_end, RPL_EVENT_HEADER_SIZE); + rpl_event->timestamp= uint4korr(ev); + ev+= 4; + rpl_event->event_type= (unsigned char)*ev++; + rpl_event->server_id= uint4korr(ev); + ev+= 4; + rpl_event->event_length= uint4korr(ev); + ev+= 4; + rpl_event->next_event_pos= uint4korr(ev); + ev+= 4; + rpl_event->flags= uint2korr(ev); + ev+=2; + rpl_event->checksum= 0; + + /* start of post_header */ + ev_start= ev; + + DBUG_ASSERT(rpl_event->event_type < ENUM_END_EVENT); + + switch(rpl_event->event_type) { + case UNKNOWN_EVENT: + case SLAVE_EVENT: + return rpl_event; + break; + case HEARTBEAT_LOG_EVENT: + /* no post header size */ + RPL_CHECK_POS(ev, ev_end, 11); + rpl_event->event.heartbeat.timestamp= uint4korr(ev); + ev+= 4; + rpl_event->event.heartbeat.next_position= uint4korr(ev); + ev+= 4; + rpl_event->event.heartbeat.type= (uint8_t)*ev; + ev+= 1; + rpl_event->event.heartbeat.flags= uint2korr(ev); + ev+= 2; + + break; + + case BEGIN_LOAD_QUERY_EVENT: + /* check post header size */ + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, BEGIN_LOAD_QUERY_EVENT); + rpl_event->event.begin_load_query.file_id= uint4korr(ev); + ev+= 4; + + /* Payload: query_data (zero terminated) */ + RPL_CHECK_NULL_POS(ev, ev_end); + rpl_event->event.begin_load_query.data= ev; + RPL_CHECK_NULL_POS(ev, ev_end); + ev+= strlen((char *)ev); + /* terminating zero */ + RPL_CHECK_POS(ev, ev_end, 1); + ev++; + break; + + case START_ENCRYPTION_EVENT: + /* Post header */ + RPL_CHECK_POS(ev, ev_end, 17); + rpl_event->event.start_encryption.scheme= *ev++; + rpl_event->event.start_encryption.key_version= uint4korr(ev); + ev+= 4; + memcpy(rpl_event->event.start_encryption.nonce, ev, 12); + memcpy(rpl->nonce, ev, 12); + ev+= 12; + rpl->encrypted= 1; + break; + + case EXECUTE_LOAD_QUERY_EVENT: + { + uint16_t status_len; + uint8_t schema_len; + + /* Post header */ + RPL_CHECK_POS(ev, ev_end, rpl->post_header_len[EXECUTE_LOAD_QUERY_EVENT - 1]); + rpl_event->event.execute_load_query.thread_id= uint4korr(ev); + ev+= 4; + rpl_event->event.execute_load_query.execution_time= uint4korr(ev); + ev+= 4; + schema_len= *ev++; + rpl_event->event.execute_load_query.error_code= uint2korr(ev); + ev+= 2; + status_len= uint2korr(ev); + ev+= 2; + rpl_event->event.execute_load_query.file_id= uint4korr(ev); + ev+= 4; + rpl_event->event.execute_load_query.ofs1= uint4korr(ev); + ev+= 4; + rpl_event->event.execute_load_query.ofs2= uint4korr(ev); + ev+= 4; + rpl_event->event.execute_load_query.duplicate_flag= *ev++; + + /* Payload: + - status variables + - query schema + - statement */ + RPL_CHECK_POS(ev, ev_end, status_len); + rpl_set_string_and_len(&rpl_event->event.execute_load_query.status_vars, ev, status_len); + ev+= status_len; + RPL_CHECK_POS(ev, ev_end, schema_len); + rpl_set_string_and_len(&rpl_event->event.execute_load_query.schema, ev, schema_len); + /* terminating zero */ + RPL_CHECK_POS(ev, ev_end, 1); + ev+= (schema_len + 1); + + len= rpl_event->event_length - (ev - ev_start) - (rpl->use_checksum ? 4 : 0) - (EVENT_HEADER_OFS - 1); + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.execute_load_query.statement, ev, len); + ev+= len; + break; + } + case BINLOG_CHECKPOINT_EVENT: + /* Post header */ + RPL_CHECK_POS(ev, ev_end, rpl->post_header_len[BINLOG_CHECKPOINT_EVENT - 1]); + len= uint4korr(ev); + ev+= 4; + + /* payload: filename */ + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.checkpoint.filename, ev, len); + if (ma_set_rpl_filename(rpl, ev, len)) + goto mem_error; + ev+= len; + break; + + case FORMAT_DESCRIPTION_EVENT: + /* + FORMAT_DESCRIPTION_EVENT: + + Header: + uint<2> binary log version + (we support only version 4) + str<50> server version, right padded with \0 + uint<4> timestamp <redundant> + uint<1> header length + byte<n> post header lengths. Length can be calculated by + ev_end - end - 1 - 4 + uint<1> check sum algorithm byte + uint<4> CRC32 checksum + */ + + + /* We don't speak bing log protocol version < 4, in case it's an older + protocol version an error will be returned. */ + RPL_CHECK_POS(ev, ev_end, 57); + if ((rpl_event->event.format_description.format = uint2korr(ev)) < 4) + { + mariadb_free_rpl_event(rpl_event); + rpl_set_error(rpl, CR_ERR_UNSUPPORTED_BINLOG_FORMAT, SQLSTATE_UNKNOWN, 0, + RPL_ERR_POS(rpl), uint2korr(ev)); + return 0; + } + + ev+= 2; + rpl_event->event.format_description.server_version = (char *)(ev); + ev+= 50; + rpl_event->event.format_description.timestamp= uint4korr(ev); + ev+= 4; + rpl->fd_header_len= rpl_event->event.format_description.header_len= *ev; + ev+= 1; + /*Post header lengths: 1 byte for each event, non used events/gaps in enum should + have a zero value */ + len= ev_end - ev - 5; + rpl_set_string_and_len(&rpl_event->event.format_description.post_header_lengths, ev, len); + memset(rpl->post_header_len, 0, ENUM_END_EVENT); + memcpy(rpl->post_header_len, rpl_event->event.format_description.post_header_lengths.str, + MIN(len, ENUM_END_EVENT)); + + ev+= len; + RPL_CHECK_POS(ev, ev_end, 5); + if ((rpl->use_checksum= *ev++)) + { + rpl_event->checksum= uint4korr(ev); + ev+= 4; + } + break; + + case QUERY_COMPRESSED_EVENT: + case QUERY_EVENT: + { + size_t db_len, status_len; + + /*********** + post_header + ***********/ + RPL_CHECK_POS(ev, ev_end, rpl->post_header_len[rpl_event->event_type - 1]); + rpl_event->event.query.thread_id= uint4korr(ev); + ev+= 4; + rpl_event->event.query.seconds= uint4korr(ev); + ev+= 4; + db_len= *ev; + ev++; + rpl_event->event.query.errornr= uint2korr(ev); + ev+= 2; + status_len= uint2korr(ev); + ev+= 2; + + /******* + payload + ******/ + RPL_CHECK_POS(ev, ev_end, status_len + db_len + 1); + rpl_set_string_and_len(&rpl_event->event.query.status, ev, status_len); + ev+= status_len; + + rpl_set_string_and_len(&rpl_event->event.query.database, ev, db_len); + ev+= db_len + 1; /* zero terminated */ + + len= rpl_event->event_length - (ev - ev_start) - (rpl->use_checksum ? 4 : 0) - (EVENT_HEADER_OFS - 1); + RPL_CHECK_POS(ev, ev_end, len); + + if (rpl_event->event_type == QUERY_EVENT || !rpl->uncompress) { + rpl_set_string_and_len(&rpl_event->event.query.statement, ev, len); + } + else if (rpl_event->event_type == QUERY_COMPRESSED_EVENT) + { + uint8_t header_size= 0, + algorithm= 0; + + uint32_t uncompressed_len= get_compression_info(ev, &algorithm, &header_size); + + len-= header_size; + if (!(rpl_event->event.query.statement.str = ma_calloc_root(&rpl_event->memroot, uncompressed_len))) + goto mem_error; + + if ((uncompress((Bytef*)rpl_event->event.query.statement.str, (uLongf *)&uncompressed_len, + (Bytef*)ev + header_size, (uLongf)*&len) != Z_OK)) + { + mariadb_free_rpl_event(rpl_event); + rpl_set_error(rpl, CR_ERR_BINLOG_UNCOMPRESS, SQLSTATE_UNKNOWN, RPL_ERR_POS(rpl)); + return 0; + } + rpl_event->event.query.statement.length= uncompressed_len; + } + break; + } + case TABLE_MAP_EVENT: + { + /* + TABLE_MAP_EVENT: + + Header: + uint<6> table_id + uint<2> unused + + Payload: + uint<1> schema_name length + str<NULL> schema_name (zero terminated) + uint<1> table_name length + str<NULL> table_name (zero terminated) + int<lenc> column_count + byte<n> column_types[column_count], 1 byte for + each column + int<lenc> meta_data_size + byte<n> netadata{metadata_size] + byte<n> bit fields, indicating which column can be null + n= (column_count + 7) / 8; + + if (remaining_bytes) + byte<n> optional metadata + */ + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, TABLE_MAP_EVENT); + + /* Post header */ + rpl_event->event.table_map.table_id= uint6korr(ev); + ev+= 8; /* 2 byte in header ignored */ + + /* Payload */ + RPL_CHECK_POS(ev, ev_end, 1); + len= *ev++; + RPL_CHECK_POS(ev, ev_end, len + 1); + + rpl_set_string_and_len(&rpl_event->event.table_map.database, ev, len); + ev+= len + 1; /* Zero terminated */ + + RPL_CHECK_POS(ev, ev_end, 1); + len= *ev++; + RPL_CHECK_POS(ev, ev_end, len + 1); + rpl_set_string_and_len(&rpl_event->event.table_map.table, ev, len); + ev+= len + 1; /* Zero terminated */ + + RPL_CHECK_FIELD_LENGTH(ev, ev_end); + len= rpl_event->event.table_map.column_count= mysql_net_field_length(&ev); + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.table_map.column_types, ev, len); + ev+= len; + + RPL_CHECK_FIELD_LENGTH(ev, ev_end); + len= mysql_net_field_length(&ev); + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.table_map.metadata, ev, len); + ev+= len; + + len= (rpl_event->event.table_map.column_count + 7) / 8; + RPL_CHECK_POS(ev, ev_end, len); + rpl_event->event.table_map.null_indicator= ev; + ev+= len; + + len= ev_end - ev - (rpl->use_checksum ? 4 : 0); + + if (len > 0) /* optional metadata */ + { + rpl_parse_opt_metadata(rpl_event, ev, len); + ev+= len; + } + + break; + + case RAND_EVENT: + RPL_CHECK_POS(ev, ev_end, 16); + rpl_event->event.rand.first_seed= uint8korr(ev); + ev+= 8; + rpl_event->event.rand.second_seed= uint8korr(ev); + ev+= 8; + + break; + } + + case INTVAR_EVENT: + RPL_CHECK_POS(ev, ev_end, 9); + rpl_event->event.intvar.type= *ev; + ev++; + rpl_event->event.intvar.value= uint8korr(ev); + ev+= 8; + break; + + case USER_VAR_EVENT: + RPL_CHECK_POS(ev, ev_end, 4); + len= uint4korr(ev); + ev+= 4; + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.uservar.name, ev, len); + ev+= len; + RPL_CHECK_POS(ev, ev_end, 1); + if (!(rpl_event->event.uservar.is_null= (uint8)*ev)) + { + ev++; + RPL_CHECK_POS(ev, ev_end, 9); + rpl_event->event.uservar.type= *ev; + ev++; + rpl_event->event.uservar.charset_nr= uint4korr(ev); + ev+= 4; + len= uint4korr(ev); + ev+= 4; + RPL_CHECK_POS(ev, ev_end, len); + + if (rpl_event->event.uservar.type == DECIMAL_RESULT) + { + char str[200]; + int s_len= sizeof(str) - 1; + int precision= (int)ev[0], + scale= (int)ev[1]; + decimal d; + decimal_digit buf[10]; + d.len= 10; + d.buf= buf; + bin2decimal((char *)(ev+2), &d, precision, scale); + decimal2string(&d, str, &s_len); + if (!(rpl_event->event.uservar.value.str = + (char *)ma_calloc_root(&rpl_event->memroot, s_len))) + goto mem_error; + memcpy(rpl_event->event.uservar.value.str, str, s_len); + rpl_event->event.uservar.value.length= s_len; + } else if (rpl_event->event.uservar.type == INT_RESULT) + { + uint64_t val64; + if (!(rpl_event->event.uservar.value.str = + (char *)ma_calloc_root(&rpl_event->memroot, sizeof(longlong)))) + goto mem_error; + val64= uint8korr(ev); + memcpy(rpl_event->event.uservar.value.str, &val64, sizeof(uint64_t)); + rpl_event->event.uservar.value.length= sizeof(uint64_t); + } else if (rpl_event->event.uservar.type == REAL_RESULT) + { + double d; + float8get(d, ev); + ev+= 8; + if (!(rpl_event->event.uservar.value.str = + (char *)ma_calloc_root(&rpl_event->memroot, 24))) + goto mem_error; + memset(rpl_event->event.uservar.value.str, 0, 24); + sprintf(rpl_event->event.uservar.value.str, "%.14g", d); + rpl_event->event.uservar.value.length= strlen(rpl_event->event.uservar.value.str); + } + else + rpl_set_string_and_len(&rpl_event->event.uservar.value, ev, len); + ev+= len; + if ((unsigned long)(ev - rpl_event->raw_data) < rpl_event->raw_data_size) + rpl_event->event.uservar.flags= *ev; + ev++; + } + break; + + case ANNOTATE_ROWS_EVENT: + /* Payload */ + len= ev_end - ev - (rpl->use_checksum ? 4 : 0); + if (len > 0) + rpl_set_string_and_len(&rpl_event->event.annotate_rows.statement, ev, len); + break; + + case ROTATE_EVENT: + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, ROTATE_EVENT); + + rpl_event->event.rotate.position= uint8korr(ev); + ev+= 8; + + /* Payload */ + len= ev_end - ev - 4; + if (!len) + goto malformed_packet; + + if (rpl_event->timestamp == 0 && + rpl_event->flags & LOG_EVENT_ARTIFICIAL_F) + { + if (rpl->artificial_checksum) + { + unsigned long crc= crc32(0L, Z_NULL, 0); + rpl_event->checksum= (uint32_t) crc32(crc, checksum_start, (uint32_t)(ev_end - checksum_start)); + } + } + rpl_set_string_and_len(&rpl_event->event.rotate.filename, ev, len); + if (ma_set_rpl_filename(rpl, ev, len)) + goto mem_error; + + ev+= len; + break; + + case XID_EVENT: + /* + XID_EVENT was generated if a transaction which modified tables was + committed. + + Header: + - uint64_t transaction number + */ + RPL_CHECK_POS(ev, ev_end, 8); + + rpl_event->event.xid.transaction_nr= uint8korr(ev); + break; + + case XA_PREPARE_LOG_EVENT: + /* + MySQL only! + + Header: + uint8_t one phase commit + uint32_t format_id + uint32_t length of gtrid + uint32_t length of bqual + + Payload: + char<n> xid, where n is sum of gtrid and bqual lengths + */ + RPL_CHECK_POS(ev, ev_end, 13); + + rpl_event->event.xa_prepare_log.one_phase= *ev; + ev++; + rpl_event->event.xa_prepare_log.format_id= uint4korr(ev); + ev+= 4; + len= rpl_event->event.xa_prepare_log.gtrid_len= uint4korr(ev); + ev+= 4; + len+= rpl_event->event.xa_prepare_log.bqual_len= uint4korr(ev); + ev+= 4; + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.xa_prepare_log.xid, ev, len); + break; + + case STOP_EVENT: + /* + STOP_EVENT - server shutdown or crash. It's always the last written + event after shutdown or after resuming from crash. + + After starting the server a new binary log file will be created, additionally + a ROTATE_EVENT will be appended to the old log file. + + No data to process. + */ + break; + + case PREVIOUS_GTIDS_LOG_EVENT: + { + /* + PREVIOUS_GTID_LOG_EVENT (MySQL only): + + 8-bytes, always zero ?! + */ + ssize_t len= ev_end - ev - rpl->use_checksum * 4; + + if (len) + { + rpl_event->event.previous_gtid.content.data= ev; + rpl_event->event.previous_gtid.content.length= len; + ev+= len; + } + break; + } + case ANONYMOUS_GTID_LOG_EVENT: + case GTID_LOG_EVENT: + /* + ANONYMOUS_GTID_LOG_EVENT + + uint32_t thread_id + + Header: + uint8_t flag: commit flag + byte<16> source_id: numerical representation of server's UUID + uint64_t sequence_nr: sequence number + */ + RPL_CHECK_POS(ev, ev_end, 25); + rpl_event->event.gtid_log.commit_flag= *ev; + ev++; + memcpy(rpl_event->event.gtid_log.source_id, ev, 16); + ev+= 16; + rpl_event->event.gtid_log.sequence_nr= uint8korr(ev); + ev+= 8; + break; + + case GTID_EVENT: + /* + GTID_EVENT (MariaDB Only): + + A New transaction (BEGIN) was started, or a single transaction + (ddl) statement was executed. In case a single transaction was + executed, the FL_GROUP_COMMIT id flag is not set. + + Header: + uint64_t sequence_nr + uint64_t domain_id + uint8_t flags + + if (flags & FL_GROUP_COMMIT_D) + uint64_t commit_id + else + char[6] unused + */ + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, GTID_EVENT); + rpl_event->event.gtid.sequence_nr= uint8korr(ev); + ev+= 8; + rpl_event->event.gtid.domain_id= uint4korr(ev); + ev+= 4; + rpl_event->event.gtid.flags= *ev; + ev++; + if (rpl_event->event.gtid.flags & FL_GROUP_COMMIT_ID) + { + RPL_CHECK_POS(ev, ev_end, 8); + rpl_event->event.gtid.commit_id= uint8korr(ev); + ev+= 8; + } + else if (rpl_event->event.gtid.flags & (FL_PREPARED_XA | FL_COMPLETED_XA)) + { + uint16_t len; + RPL_CHECK_POS(ev, ev_end, 6); + rpl_event->event.gtid.format_id= uint4korr(ev); + ev+= 4; + rpl_event->event.gtid.gtrid_len= *ev; + ev++; + rpl_event->event.gtid.bqual_len= *ev; + ev++; + len= rpl_event->event.gtid.gtrid_len + rpl_event->event.gtid.bqual_len; + RPL_CHECK_POS(ev, ev_end, len); + rpl_set_string_and_len(&rpl_event->event.gtid.xid, ev, len); + ev+= len; + } + else + ev+= 6; + break; + + case GTID_LIST_EVENT: + /* + GTID_LIST_EVENT (MariaDB only) + + Logged in every binlog to record the current replication state. + Consists of the last GTID seen for each replication domain. + + The Global Transaction ID, GTID for short, consists of three components: + replication domain id, server id and sequence nr + + Header: + uint32_t gtid_cnt - number of global transaction id's + + Payload: + for i=0; i < gtid_cnt; i++ + uint32_t domain_id + uint32_t server_id + uint64_t sequence_nr + */ + + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, GTID_LIST_EVENT); + rpl_event->event.gtid_list.gtid_cnt= uint4korr(ev); + ev+=4; + + RPL_CHECK_POS(ev, ev_end, rpl_event->event.gtid_list.gtid_cnt * 16); + /* Payload */ + if (rpl_event->event.gtid_list.gtid_cnt) + { + uint32_t i; + if (!(rpl_event->event.gtid_list.gtid= + (MARIADB_GTID *)ma_calloc_root(&rpl_event->memroot, + sizeof(MARIADB_GTID) * rpl_event->event.gtid_list.gtid_cnt))) + goto mem_error; + for (i=0; i < rpl_event->event.gtid_list.gtid_cnt; i++) + { + rpl_event->event.gtid_list.gtid[i].domain_id= uint4korr(ev); + ev+= 4; + rpl_event->event.gtid_list.gtid[i].server_id= uint4korr(ev); + ev+= 4; + rpl_event->event.gtid_list.gtid[i].sequence_nr= uint8korr(ev); + ev+= 8; + } + } + break; + + case WRITE_ROWS_COMPRESSED_EVENT_V1: + case UPDATE_ROWS_COMPRESSED_EVENT_V1: + case DELETE_ROWS_COMPRESSED_EVENT_V1: + case WRITE_ROWS_EVENT_V1: + case UPDATE_ROWS_EVENT_V1: + case DELETE_ROWS_EVENT_V1: + case WRITE_ROWS_EVENT: + case UPDATE_ROWS_EVENT: + case DELETE_ROWS_EVENT: + { + /* + WRITE/UPDATE/DELETE_ROWS_EVENT_V1 (MariaDB only) + WRITE/UPDATE/DELETE_ROWS_EVENT_COMPRESSED_V1 (MariaDB only) + WRITE/UPDATE/DELETE_ROWS_EVENT (MySQL only) + + ROWS events are written for row based replicatoin if data is + inserted, deleted or updated. + + Header + uint<6> table_id + uint<2> flags + + if MySQL (version 2) + uint<<2> extra_data_length + char[extra_data_length] extra_data + + uint<lenenc> number of columns + uint8_t<n> Bitmap of columns used. + n= (number of columns + 7) / 8 + + if UPDATE_ROWS_v1 (MariaDB) + uint8_t<n> columns updated + n= (number of columns + 7) / 8 + + uint7_t<n> null bitmap + n= (number of columns + 7) / 8 + + str<len> Column data. If event is not compressed, + length must be calculated. + + if UPDATE_ROWS_v1 (MariaDB) + byte<n> Null bitmap update + n= (number of columns + 7) / 8 + str<len> Update column data + + */ + + uint32_t bitmap_len= 0; + + RPL_CHECK_POST_HEADER_LEN(ev, ev_end, rpl_event->event_type); + + if (rpl_event->event_type >= WRITE_ROWS_COMPRESSED_EVENT) { + return rpl_event; + rpl_event->event.rows.compressed= 1; + rpl_event->event.rows.type= rpl_event->event_type - WRITE_ROWS_COMPRESSED_EVENT; + } else if (rpl_event->event_type >= WRITE_ROWS_COMPRESSED_EVENT_V1) { + rpl_event->event.rows.compressed= 1; + rpl_event->event.rows.type= rpl_event->event_type - WRITE_ROWS_COMPRESSED_EVENT_V1; + } else if (rpl_event->event_type >= WRITE_ROWS_EVENT) + rpl_event->event.rows.type= rpl_event->event_type - WRITE_ROWS_EVENT; + else + rpl_event->event.rows.type= rpl_event->event_type - WRITE_ROWS_EVENT_V1; + + rpl_event->event.rows.table_id= uint6korr(ev); + ev+= 6; + + rpl_event->event.rows.flags= uint2korr(ev); + ev+= 2; + + /* payload */ + + /* ROWS_EVENT V2 has the extra-data field. + See also: https://dev.mysql.com/doc/internals/en/rows-event.html + */ + if (IS_ROW_VERSION2(rpl_event->event_type)) + { + RPL_CHECK_POS(ev, ev_end, 2); + rpl_event->event.rows.extra_data_size= uint2korr(ev); + ev+= 2; + RPL_CHECK_POS(ev, ev_end, rpl_event->event.rows.extra_data_size); + if (rpl_event->event.rows.extra_data_size - 2 > 0) + { + rpl_alloc_set_string_and_len(rpl_event, rpl_event->event.rows.extra_data, ev, rpl_event->event.rows.extra_data_size - 2); + ev+= rpl_event->event.rows.extra_data_size; + } + } + /* END_ROWS_EVENT_V2 */ + + /* number of columns */ + RPL_CHECK_FIELD_LENGTH(ev, ev_end); + rpl_event->event.rows.column_count= mysql_net_field_length(&ev); + bitmap_len= (rpl_event->event.rows.column_count + 7) / 8; + DBUG_ASSERT(rpl_event->event.rows.column_count > 0); + + /* columns updated bitmap */ + RPL_CHECK_POS(ev, ev_end, bitmap_len); + rpl_event->event.rows.column_bitmap= ev; + ev+= bitmap_len; + + if (rpl_event->event_type == UPDATE_ROWS_EVENT_V1 || + rpl_event->event_type == UPDATE_ROWS_COMPRESSED_EVENT_V1) + { + RPL_CHECK_POS(ev, ev_end, bitmap_len); + rpl_event->event.rows.column_update_bitmap= ev; + ev+= bitmap_len; + } + + len= ev_end - ev - (rpl->use_checksum ? 4 : 0); + + if (rpl_event->event.rows.compressed) + { + uint8_t algorithm= 0, header_size= 0; + uint32_t uncompressed_len= get_compression_info(ev, &algorithm, &header_size); + + if (!(rpl_event->event.rows.row_data = ma_calloc_root(&rpl_event->memroot, uncompressed_len))) + goto mem_error; + + if ((uncompress((Bytef*)rpl_event->event.rows.row_data, (uLong *)&uncompressed_len, + (Bytef*)ev + header_size, (uLongf )len) != Z_OK)) + { + rpl_set_error(rpl, CR_ERR_BINLOG_UNCOMPRESS, SQLSTATE_UNKNOWN, 0, RPL_ERR_POS(rpl)); + mariadb_free_rpl_event(rpl_event); + return 0; + } + rpl_event->event.rows.row_data_size= uncompressed_len; + RPL_CHECK_POS(ev, ev_end, header_size + len); + ev+= header_size + len; + } else { + rpl_event->event.rows.row_data_size= ev_end - ev - (rpl->use_checksum ? 4 : 0); + if (!(rpl_event->event.rows.row_data = + (char *)ma_calloc_root(&rpl_event->memroot, rpl_event->event.rows.row_data_size))) + goto mem_error; + memcpy(rpl_event->event.rows.row_data, ev, rpl_event->event.rows.row_data_size); + } + break; + } + default: + /* We need to report an error if this event can't be ignored */ + if (!(rpl_event->flags & LOG_EVENT_IGNORABLE_F)) + { + mariadb_free_rpl_event(rpl_event); + rpl_set_error(rpl, CR_UNKNOWN_BINLOG_EVENT, 0, RPL_ERR_POS(rpl), + rpl_event->event_type); + return 0; + } + return rpl_event; + break; + } + + /* check if we have to send acknowledgement to primary + when semi sync replication is used */ + if (rpl_event->is_semi_sync && + rpl_event->semi_sync_flags == SEMI_SYNC_ACK_REQ) + { + if (mariadb_rpl_send_semisync_ack(rpl, rpl_event)) + { + /* ACK failed and rpl->error was set */ + return rpl_event; + } + } + + if (rpl->use_checksum && !rpl_event->checksum) + { + rpl_event->checksum= uint4korr(ev_end - 4); + + if (rpl_event->checksum && rpl->verify_checksum) + { + unsigned long crc= crc32(0L, Z_NULL, 0); + crc= crc32(crc, checksum_start, (uint32_t)(ev_end - checksum_start - 4)); + if (rpl_event->checksum != (uint32_t)crc) + { + rpl_set_error(rpl, CR_ERR_CHECKSUM_VERIFICATION_ERROR, SQLSTATE_UNKNOWN, 0, + RPL_ERR_POS(rpl), + rpl_event->checksum, (uint32_t)crc); + mariadb_free_rpl_event(rpl_event); + return 0; + } + } + } + return rpl_event; + } +mem_error: + mariadb_free_rpl_event(rpl_event); + rpl_set_error(rpl, CR_OUT_OF_MEMORY, 0); + return 0; +malformed_packet: + rpl_set_error(rpl, CR_BINLOG_ERROR, 0, RPL_ERR_POS(rpl), + "Malformed packet"); + mariadb_free_rpl_event(rpl_event); + return 0; +} + +void STDCALL mariadb_rpl_close(MARIADB_RPL *rpl) +{ + if (!rpl) + return; + free((void *)rpl->filename); + if (rpl->fp) + { + ma_close(rpl->fp); + } + free(rpl->host); + free(rpl); + return; +} + +int STDCALL mariadb_rpl_optionsv(MARIADB_RPL *rpl, + enum mariadb_rpl_option option, + ...) +{ + va_list ap; + int rc= 0; + + if (!rpl) + return 1; + + va_start(ap, option); + + switch (option) { + case MARIADB_RPL_FILENAME: + { + char *arg1= va_arg(ap, char *); + rpl->filename_length= (uint32_t)va_arg(ap, size_t); + free((void *)rpl->filename); + rpl->filename= NULL; + if (rpl->filename_length) + { + rpl->filename= (char *)malloc(rpl->filename_length); + memcpy((void *)rpl->filename, arg1, rpl->filename_length); + } + else if (arg1) + { + rpl->filename= strdup((const char *)arg1); + rpl->filename_length= (uint32_t)strlen(rpl->filename); + } + break; + } + case MARIADB_RPL_SERVER_ID: + { + rpl->server_id= va_arg(ap, unsigned int); + break; + } + case MARIADB_RPL_FLAGS: + { + rpl->flags= va_arg(ap, unsigned int); + break; + } + case MARIADB_RPL_START: + { + rpl->start_position= va_arg(ap, unsigned long); + break; + } + case MARIADB_RPL_VERIFY_CHECKSUM: + { + rpl->verify_checksum= va_arg(ap, uint32_t); + break; + } + case MARIADB_RPL_UNCOMPRESS: + { + rpl->uncompress= (uint8_t)va_arg(ap, uint32_t); + break; + } + case MARIADB_RPL_PORT: + { + rpl->port= va_arg(ap, uint32_t); + break; + } + case MARIADB_RPL_HOST: + { + rpl->host= strdup(va_arg(ap, char *)); + break; + } + case MARIADB_RPL_EXTRACT_VALUES: + { + rpl->extract_values= (uint8_t)va_arg(ap, uint32_t); + break; + } + case MARIADB_RPL_SEMI_SYNC: + { + rpl->is_semi_sync = (uint8_t)va_arg(ap, uint32_t); + break; + } + default: + rc= -1; + goto end; + } +end: + va_end(ap); + return rc; +} + +int STDCALL mariadb_rpl_get_optionsv(MARIADB_RPL *rpl, + enum mariadb_rpl_option option, + ...) +{ + va_list ap; + + if (!rpl) + return 1; + + va_start(ap, option); + + switch (option) { + case MARIADB_RPL_FILENAME: + { + const char **name= (const char **)va_arg(ap, char **); + size_t *len= (size_t*)va_arg(ap, size_t *); + + *name= rpl->filename; + *len= rpl->filename_length; + break; + } + case MARIADB_RPL_SERVER_ID: + { + unsigned int *id= va_arg(ap, unsigned int *); + *id= rpl->server_id; + break; + } + case MARIADB_RPL_FLAGS: + { + unsigned int *flags= va_arg(ap, unsigned int *); + *flags= rpl->flags; + break; + } + case MARIADB_RPL_START: + { + unsigned long *start= va_arg(ap, unsigned long *); + *start= rpl->start_position; + break; + } + case MARIADB_RPL_SEMI_SYNC: + { + unsigned int* semi_sync = va_arg(ap, unsigned int*); + *semi_sync = rpl->is_semi_sync; + break; + } + + default: + va_end(ap); + return 1; + break; + } + va_end(ap); + return 0; +} diff --git a/libmariadb/libmariadb/mariadb_stmt.c b/libmariadb/libmariadb/mariadb_stmt.c new file mode 100644 index 00000000..5bcecfde --- /dev/null +++ b/libmariadb/libmariadb/mariadb_stmt.c @@ -0,0 +1,2636 @@ +/**************************************************************************** + Copyright (C) 2012 Monty Program AB + 2013, 2022 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net + *****************************************************************************/ + +/* The implementation for prepared statements was ported from PHP's mysqlnd + extension, written by Andrey Hristov, Georg Richter and Ulf Wendel + + Original file header: + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + */ + +#include "ma_global.h" +#include <ma_sys.h> +#include <ma_string.h> +#include <mariadb_ctype.h> +#include "mysql.h" +#include "errmsg.h" +#include <ma_pvio.h> +#include <sys/stat.h> +#include <signal.h> +#include <time.h> +#include <mysql/client_plugin.h> +#include <ma_common.h> +#include "ma_priv.h" +#include <assert.h> + + +#define UPDATE_STMT_ERROR(stmt)\ +stmt_set_error((stmt), (stmt)->mysql->net.last_errno, (stmt)->mysql->net.sqlstate, (stmt)->mysql->net.last_error) + +#define STMT_NUM_OFS(type, a, r) (((type *)(a))[r]) +#define MADB_RESET_ERROR 1 +#define MADB_RESET_LONGDATA 2 +#define MADB_RESET_SERVER 4 +#define MADB_RESET_BUFFER 8 +#define MADB_RESET_STORED 16 + +#define MAX_TIME_STR_LEN 13 +#define MAX_DATE_STR_LEN 5 +#define MAX_DATETIME_STR_LEN 12 + +typedef struct +{ + MA_MEM_ROOT fields_ma_alloc_root; +} MADB_STMT_EXTENSION; + +static my_bool net_stmt_close(MYSQL_STMT *stmt, my_bool remove); + +static my_bool is_not_null= 0; +static my_bool is_null= 1; + +void stmt_set_error(MYSQL_STMT *stmt, + unsigned int error_nr, + const char *sqlstate, + const char *format, + ...) +{ + va_list ap; + + const char *errmsg= format; + + stmt->last_errno= error_nr; + ma_strmake(stmt->sqlstate, sqlstate, SQLSTATE_LENGTH); + + if (!format) + { + if (IS_MYSQL_ERROR(error_nr) || IS_MARIADB_ERROR(error_nr)) + errmsg= ER(error_nr); + else { + snprintf(stmt->last_error, MYSQL_ERRMSG_SIZE - 1, + ER_UNKNOWN_ERROR_CODE, error_nr); + return; + } + } + + /* Fix for CONC-627: If this is a server error message, we don't + need to substitute and possible variadic arguments will be + ignored */ + if (!IS_MYSQL_ERROR(error_nr) && !IS_MARIADB_ERROR(error_nr)) + { + strncpy(stmt->last_error, format, MYSQL_ERRMSG_SIZE - 1); + return; + } + + va_start(ap, format); + vsnprintf(stmt->last_error, MYSQL_ERRMSG_SIZE - 1, errmsg, ap); + va_end(ap); + return; +} + +my_bool mthd_supported_buffer_type(enum enum_field_types type) +{ + switch (type) { + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_YEAR: + return 1; + break; + default: + return 0; + break; + } +} + +static my_bool madb_reset_stmt(MYSQL_STMT *stmt, unsigned int flags); +static my_bool mysql_stmt_internal_reset(MYSQL_STMT *stmt, my_bool is_close); +static int stmt_unbuffered_eof(MYSQL_STMT *stmt __attribute__((unused)), + uchar **row __attribute__((unused))) +{ + return MYSQL_NO_DATA; +} + +static int stmt_unbuffered_fetch(MYSQL_STMT *stmt, uchar **row) +{ + ulong pkt_len; + + pkt_len= ma_net_safe_read(stmt->mysql); + + if (pkt_len == packet_error) + { + stmt->fetch_row_func= stmt_unbuffered_eof; + return(1); + } + + if (stmt->mysql->net.read_pos[0] == 254) + { + *row = NULL; + stmt->fetch_row_func= stmt_unbuffered_eof; + return(MYSQL_NO_DATA); + } + else + *row = stmt->mysql->net.read_pos; + stmt->result.rows++; + return(0); +} + +static int stmt_buffered_fetch(MYSQL_STMT *stmt, uchar **row) +{ + if (!stmt->result_cursor) + { + *row= NULL; + stmt->state= MYSQL_STMT_FETCH_DONE; + return MYSQL_NO_DATA; + } + stmt->state= MYSQL_STMT_USER_FETCHING; + *row= (uchar *)stmt->result_cursor->data; + + stmt->result_cursor= stmt->result_cursor->next; + return 0; +} + +int mthd_stmt_read_all_rows(MYSQL_STMT *stmt) +{ + MYSQL_DATA *result= &stmt->result; + MYSQL_ROWS *current, **pprevious; + ulong packet_len; + unsigned char *p; + + pprevious= &result->data; + + while ((packet_len = ma_net_safe_read(stmt->mysql)) != packet_error) + { + p= stmt->mysql->net.read_pos; + if (packet_len > 7 || p[0] != 254) + { + /* allocate space for rows */ + if (!(current= (MYSQL_ROWS *)ma_alloc_root(&result->alloc, sizeof(MYSQL_ROWS) + packet_len))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(1); + } + current->data= (MYSQL_ROW)(current + 1); + *pprevious= current; + pprevious= ¤t->next; + + /* copy binary row, we will encode it during mysql_stmt_fetch */ + memcpy((char *)current->data, (char *)p, packet_len); + + if (stmt->update_max_length) + { + uchar *null_ptr, bit_offset= 4; + uchar *cp= p; + unsigned int i; + + cp++; /* skip first byte */ + null_ptr= cp; + cp+= (stmt->field_count + 9) / 8; + + for (i=0; i < stmt->field_count; i++) + { + if (!(*null_ptr & bit_offset)) + { + if (mysql_ps_fetch_functions[stmt->fields[i].type].pack_len < 0) + { + /* We need to calculate the sizes for date and time types */ + size_t len= net_field_length(&cp); + switch(stmt->fields[i].type) { + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + stmt->fields[i].max_length= mysql_ps_fetch_functions[stmt->fields[i].type].max_len; + break; + default: + if (len > stmt->fields[i].max_length) + stmt->fields[i].max_length= (ulong)len; + break; + } + cp+= len; + } + else + { + if (stmt->fields[i].flags & ZEROFILL_FLAG) + { + /* The -1 is because a ZEROFILL:ed field is always unsigned */ + size_t len= MAX(stmt->fields[i].length, mysql_ps_fetch_functions[stmt->fields[i].type].max_len-1); + if (len > stmt->fields[i].max_length) + stmt->fields[i].max_length= (unsigned long)len; + } + else if (!stmt->fields[i].max_length) + { + stmt->fields[i].max_length= mysql_ps_fetch_functions[stmt->fields[i].type].max_len; + if (stmt->fields[i].flags & UNSIGNED_FLAG && + stmt->fields[i].type != MYSQL_TYPE_INT24 && + stmt->fields[i].type != MYSQL_TYPE_LONGLONG) + { + /* + Unsigned integers has one character less than signed integers + as '-' is counted as part of max_length + */ + stmt->fields[i].max_length--; + } + } + cp+= mysql_ps_fetch_functions[stmt->fields[i].type].pack_len; + } + } + if (!((bit_offset <<=1) & 255)) + { + bit_offset= 1; /* To next byte */ + null_ptr++; + } + } + } + current->length= packet_len; + result->rows++; + } else /* end of stream */ + { + unsigned int last_status= stmt->mysql->server_status; + *pprevious= 0; + /* sace status info */ + p++; + stmt->upsert_status.warning_count= stmt->mysql->warning_count= uint2korr(p); + p+=2; + stmt->upsert_status.server_status= stmt->mysql->server_status= uint2korr(p); + ma_status_callback(stmt->mysql, last_status); + stmt->result_cursor= result->data; + return(0); + } + } + stmt->result_cursor= 0; + stmt_set_error(stmt, stmt->mysql->net.last_errno, stmt->mysql->net.sqlstate, + stmt->mysql->net.last_error); + return(1); +} + +static int stmt_cursor_fetch(MYSQL_STMT *stmt, uchar **row) +{ + uchar buf[STMT_ID_LENGTH + 4]; + MYSQL_DATA *result= &stmt->result; + + if (stmt->state < MYSQL_STMT_USE_OR_STORE_CALLED) + { + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + /* do we have some prefetched rows available ? */ + if (stmt->result_cursor) + return(stmt_buffered_fetch(stmt, row)); + if (stmt->upsert_status.server_status & SERVER_STATUS_LAST_ROW_SENT) + stmt->upsert_status.server_status&= ~SERVER_STATUS_LAST_ROW_SENT; + else + { + int4store(buf, stmt->stmt_id); + int4store(buf + STMT_ID_LENGTH, stmt->prefetch_rows); + + if (stmt->mysql->methods->db_command(stmt->mysql, COM_STMT_FETCH, (char *)buf, sizeof(buf), 1, stmt)) + { + UPDATE_STMT_ERROR(stmt); + return(1); + } + + /* free previously allocated buffer */ + ma_free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= 0; + result->rows= 0; + + if (!stmt->mysql->options.extension->skip_read_response) + { + if (stmt->mysql->methods->db_stmt_read_all_rows(stmt)) + return(1); + + return(stmt_buffered_fetch(stmt, row)); + } + } + /* no more cursor data available */ + *row= NULL; + return(MYSQL_NO_DATA); +} + +/* flush one result set */ +void mthd_stmt_flush_unbuffered(MYSQL_STMT *stmt) +{ + ulong packet_len; + int in_resultset= stmt->state > MYSQL_STMT_EXECUTED && + stmt->state < MYSQL_STMT_FETCH_DONE; + while ((packet_len = ma_net_safe_read(stmt->mysql)) != packet_error) + { + unsigned int last_status= stmt->mysql->server_status; + uchar *pos= stmt->mysql->net.read_pos; + + if (!in_resultset && *pos == 0) /* OK */ + { + pos++; + net_field_length(&pos); + net_field_length(&pos); + stmt->mysql->server_status= uint2korr(pos); + ma_status_callback(stmt->mysql, last_status); + goto end; + } + if (packet_len < 8 && *pos == 254) /* EOF */ + { + if (mariadb_connection(stmt->mysql)) + { + stmt->mysql->server_status= uint2korr(pos + 3); + ma_status_callback(stmt->mysql, last_status); + if (in_resultset) + goto end; + in_resultset= 1; + } + else + goto end; + } + } +end: + stmt->state= MYSQL_STMT_FETCH_DONE; +} + +int mthd_stmt_fetch_to_bind(MYSQL_STMT *stmt, unsigned char *row) +{ + uint i; + size_t truncations= 0; + unsigned char *null_ptr, bit_offset= 4; + row++; /* skip status byte */ + null_ptr= row; + row+= (stmt->field_count + 9) / 8; + + for (i=0; i < stmt->field_count; i++) + { + /* save row position for fetching values in pieces */ + if (*null_ptr & bit_offset) + { + if (stmt->result_callback) + stmt->result_callback(stmt->user_data, i, NULL); + else + { + if (!stmt->bind[i].is_null) + stmt->bind[i].is_null= &stmt->bind[i].is_null_value; + *stmt->bind[i].is_null= 1; + stmt->bind[i].u.row_ptr= NULL; + } + } else + { + stmt->bind[i].u.row_ptr= row; + if (!stmt->bind_result_done || + stmt->bind[i].flags & MADB_BIND_DUMMY) + { + unsigned long length; + + if (stmt->result_callback) + stmt->result_callback(stmt->user_data, i, &row); + else { + if (mysql_ps_fetch_functions[stmt->fields[i].type].pack_len >= 0) + length= mysql_ps_fetch_functions[stmt->fields[i].type].pack_len; + else + length= net_field_length(&row); + row+= length; + if (!stmt->bind[i].length) + stmt->bind[i].length= &stmt->bind[i].length_value; + *stmt->bind[i].length= stmt->bind[i].length_value= length; + } + } + else + { + if (!stmt->bind[i].length) + stmt->bind[i].length= &stmt->bind[i].length_value; + if (!stmt->bind[i].is_null) + stmt->bind[i].is_null= &stmt->bind[i].is_null_value; + *stmt->bind[i].is_null= 0; + mysql_ps_fetch_functions[stmt->fields[i].type].func(&stmt->bind[i], &stmt->fields[i], &row); + if (stmt->mysql->options.report_data_truncation) + truncations+= *stmt->bind[i].error; + } + } + + if (!((bit_offset <<=1) & 255)) { + bit_offset= 1; /* To next byte */ + null_ptr++; + } + } + return((truncations) ? MYSQL_DATA_TRUNCATED : 0); +} + +MYSQL_RES *_mysql_stmt_use_result(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + + if (!stmt->field_count || + (!stmt->cursor_exists && mysql->status != MYSQL_STATUS_STMT_RESULT) || + (stmt->cursor_exists && mysql->status != MYSQL_STATUS_READY) || + (stmt->state != MYSQL_STMT_WAITING_USE_OR_STORE)) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(NULL); + } + + CLEAR_CLIENT_STMT_ERROR(stmt); + + stmt->state = MYSQL_STMT_USE_OR_STORE_CALLED; + if (!stmt->cursor_exists) + stmt->fetch_row_func= stmt_unbuffered_fetch; //mysql_stmt_fetch_unbuffered_row; + else + stmt->fetch_row_func= stmt_cursor_fetch; + + return(NULL); +} + +unsigned char *mysql_net_store_length(unsigned char *packet, ulonglong length) +{ + if (length < (unsigned long long) L64(251)) { + *packet = (unsigned char) length; + return packet + 1; + } + + if (length < (unsigned long long) L64(65536)) { + *packet++ = 252; + int2store(packet,(uint) length); + return packet + 2; + } + + if (length < (unsigned long long) L64(16777216)) { + *packet++ = 253; + int3store(packet,(ulong) length); + return packet + 3; + } + *packet++ = 254; + int8store(packet, length); + return packet + 8; +} + +static long ma_get_length(MYSQL_STMT *stmt, unsigned int param_nr, unsigned long row_nr) +{ + if (!stmt->params[param_nr].length) + return 0; + if (stmt->param_callback) + return (long)*stmt->params[param_nr].length; + if (stmt->row_size) + return *(long *)((char *)stmt->params[param_nr].length + row_nr * stmt->row_size); + else + return stmt->params[param_nr].length[row_nr]; +} + +static signed char ma_get_indicator(MYSQL_STMT *stmt, unsigned int param_nr, unsigned long row_nr) +{ + if (!MARIADB_STMT_BULK_SUPPORTED(stmt) || + !stmt->array_size || + !stmt->params[param_nr].u.indicator) + return 0; + if (stmt->param_callback) + return *stmt->params[param_nr].u.indicator; + if (stmt->row_size) + return *((char *)stmt->params[param_nr].u.indicator + (row_nr * stmt->row_size)); + return stmt->params[param_nr].u.indicator[row_nr]; +} + +static void *ma_get_buffer_offset(MYSQL_STMT *stmt, enum enum_field_types type, + void *buffer, unsigned long row_nr) +{ + if (stmt->param_callback) + return buffer; + + if (stmt->array_size) + { + int len; + if (stmt->row_size) + return (void *)((char *)buffer + stmt->row_size * row_nr); + len= mysql_ps_fetch_functions[type].pack_len; + if (len > 0) + return (void *)((char *)buffer + len * row_nr); + return ((void **)buffer)[row_nr]; + } + return buffer; +} + +int store_param(MYSQL_STMT *stmt, int column, unsigned char **p, unsigned long row_nr) +{ + void *buf= ma_get_buffer_offset(stmt, stmt->params[column].buffer_type, + stmt->params[column].buffer, row_nr); + signed char indicator= ma_get_indicator(stmt, column, row_nr); + + switch (stmt->params[column].buffer_type) { + case MYSQL_TYPE_TINY: + int1store(*p, (*(uchar *)buf)); + (*p) += 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + int2store(*p, (*(short *)buf)); + (*p) += 2; + break; + case MYSQL_TYPE_FLOAT: + float4store(*p, (*(float *)buf)); + (*p) += 4; + break; + case MYSQL_TYPE_DOUBLE: + float8store(*p, (*(double *)buf)); + (*p) += 8; + break; + case MYSQL_TYPE_LONGLONG: + int8store(*p, (*(ulonglong *)buf)); + (*p) += 8; + break; + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: + int4store(*p, (*(int32 *)buf)); + (*p)+= 4; + break; + case MYSQL_TYPE_TIME: + { + /* binary encoding: + Offset Length Field + 0 1 Length + 1 1 negative + 2-5 4 day + 6 1 hour + 7 1 minute + 8 1 second; + 9-13 4 second_part + */ + MYSQL_TIME *t= (MYSQL_TIME *)ma_get_buffer_offset(stmt, stmt->params[column].buffer_type, + stmt->params[column].buffer, row_nr); + char t_buffer[MAX_TIME_STR_LEN]; + uint len= 0; + + t_buffer[1]= t->neg ? 1 : 0; + int4store(t_buffer + 2, t->day); + t_buffer[6]= (uchar) t->hour; + t_buffer[7]= (uchar) t->minute; + t_buffer[8]= (uchar) t->second; + if (t->second_part) + { + int4store(t_buffer + 9, t->second_part); + len= 12; + } + else if (t->day || t->hour || t->minute || t->second) + len= 8; + t_buffer[0]= len++; + memcpy(*p, t_buffer, len); + (*p)+= len; + break; + } + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + { + /* binary format for date, timestamp and datetime + Offset Length Field + 0 1 Length + 1-2 2 Year + 3 1 Month + 4 1 Day + 5 1 Hour + 6 1 minute + 7 1 second + 8-11 4 secondpart + */ + MYSQL_TIME *t= (MYSQL_TIME *)ma_get_buffer_offset(stmt, stmt->params[column].buffer_type, + stmt->params[column].buffer, row_nr); + char t_buffer[MAX_DATETIME_STR_LEN]; + uint len= 0; + + int2store(t_buffer + 1, t->year); + t_buffer[3]= (char) t->month; + t_buffer[4]= (char) t->day; + t_buffer[5]= (char) t->hour; + t_buffer[6]= (char) t->minute; + t_buffer[7]= (char) t->second; + if (t->second_part) + { + int4store(t_buffer + 8, t->second_part); + len= 11; + } + else if (t->hour || t->minute || t->second) + len= 7; + else if (t->year || t->month || t->day) + len= 4; + else + len=0; + t_buffer[0]= len++; + memcpy(*p, t_buffer, len); + (*p)+= len; + break; + } + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + { + ulong len; + /* to is after p. The latter hasn't been moved */ + uchar *to; + + if (indicator == STMT_INDICATOR_NTS) + len= -1; + else + len= ma_get_length(stmt, column, row_nr); + + if (len == (ulong)-1) + len= (ulong)strlen((char *)buf); + + to = mysql_net_store_length(*p, len); + + if (len) + memcpy(to, buf, len); + (*p) = to + len; + break; + } + + default: + /* unsupported parameter type */ + stmt_set_error(stmt, CR_UNSUPPORTED_PARAM_TYPE, SQLSTATE_UNKNOWN, 0); + return 1; + } + return 0; +} + +/* {{{ ma_stmt_execute_generate_simple_request */ +unsigned char* ma_stmt_execute_generate_simple_request(MYSQL_STMT *stmt, size_t *request_len) +{ + /* execute packet has the following format: + Offset Length Description + ----------------------------------------- + 0 4 Statement id + 4 1 Flags (cursor type) + 5 4 Iteration count + ----------------------------------------- + if (stmt->param_count): + 6 (paramcount+7)/8 null bitmap + ------------------------------------------ + if (stmt->send_types_to_server): + param_count*2 parameter types + 1st byte: parameter type + 2nd byte flag: + unsigned flag (32768) + indicator variable exists (16384) + ------------------------------------------ + n data from bind_buffer + + */ + + size_t length= 1024; + size_t free_bytes= 0; + size_t null_byte_offset= 0; + uchar *tmp_start; + uint i; + + uchar *start= NULL, *p; + + /* preallocate length bytes */ + /* check: gr */ + if (!(start= p= (uchar *)malloc(length))) + goto mem_error; + + int4store(p, stmt->stmt_id); + p += STMT_ID_LENGTH; + + /* flags is 4 bytes, we store just 1 */ + int1store(p, (unsigned char) stmt->flags); + p++; + + int4store(p, 1); + p+= 4; + + if (stmt->param_count) + { + size_t null_count= (stmt->param_count + 7) / 8; + + free_bytes= length - (p - start); + if (null_count + 20 > free_bytes) + { + size_t offset= p - start; + length+= offset + null_count + 20; + if (!(tmp_start= (uchar *)realloc(start, length))) + goto mem_error; + start= tmp_start; + p= start + offset; + } + + null_byte_offset= p - start; + memset(p, 0, null_count); + p += null_count; + + int1store(p, stmt->send_types_to_server); + p++; + + free_bytes= length - (p - start); + + /* Store type information: + 2 bytes per type + */ + if (stmt->send_types_to_server) + { + if (free_bytes < stmt->param_count * 2 + 20) + { + size_t offset= p - start; + length= offset + stmt->param_count * 2 + 20; + if (!(tmp_start= (uchar *)realloc(start, length))) + goto mem_error; + start= tmp_start; + p= start + offset; + } + for (i = 0; i < stmt->param_count; i++) + { + /* this differs from mysqlnd, c api supports unsigned !! */ + uint buffer_type= stmt->params[i].buffer_type | (stmt->params[i].is_unsigned ? 32768 : 0); + /* check if parameter requires indicator variable */ + int2store(p, buffer_type); + p+= 2; + } + } + + /* calculate data size */ + for (i=0; i < stmt->param_count; i++) + { + size_t size= 0; + my_bool has_data= TRUE; + + if (stmt->params[i].long_data_used) + { + has_data= FALSE; + stmt->params[i].long_data_used= 0; + } + + if (has_data) + { + switch (stmt->params[i].buffer_type) { + case MYSQL_TYPE_NULL: + has_data= FALSE; + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_SET: + size+= 9; /* max 8 bytes for size */ + size+= (size_t)ma_get_length(stmt, i, 0); + break; + case MYSQL_TYPE_TIME: + size+= MAX_TIME_STR_LEN; + break; + case MYSQL_TYPE_DATE: + size+= MAX_DATE_STR_LEN; + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + size+= MAX_DATETIME_STR_LEN; + break; + default: + size+= mysql_ps_fetch_functions[stmt->params[i].buffer_type].pack_len; + break; + } + } + free_bytes= length - (p - start); + if (free_bytes < size + 20) + { + size_t offset= p - start; + length= MAX(2 * length, offset + size + 20); + if (!(tmp_start= (uchar *)realloc(start, length))) + goto mem_error; + start= tmp_start; + p= start + offset; + } + if (((stmt->params[i].is_null && *stmt->params[i].is_null) || + stmt->params[i].buffer_type == MYSQL_TYPE_NULL || + !stmt->params[i].buffer)) + { + has_data= FALSE; + (start + null_byte_offset)[i/8] |= (unsigned char) (1 << (i & 7)); + } + + if (has_data) + { + store_param(stmt, i, &p, 0); + } + } + } + stmt->send_types_to_server= 0; + *request_len = (size_t)(p - start); + return start; +mem_error: + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + free(start); + *request_len= 0; + return NULL; +} +/* }}} */ + +/* {{{ mysql_stmt_skip_paramset */ +my_bool mysql_stmt_skip_paramset(MYSQL_STMT *stmt, uint row) +{ + uint i; + for (i=0; i < stmt->param_count; i++) + { + if (ma_get_indicator(stmt, i, row) == STMT_INDICATOR_IGNORE_ROW) + return '\1'; + } + + return '\0'; +} +/* }}} */ + +/* {{{ ma_stmt_execute_generate_bulk_request */ +unsigned char* ma_stmt_execute_generate_bulk_request(MYSQL_STMT *stmt, size_t *request_len) +{ + /* execute packet has the following format: + Offset Length Description + ----------------------------------------- + 0 4 Statement id + 4 2 Flags (cursor type): + STMT_BULK_FLAG_CLIENT_SEND_TYPES = 128 + STMT_BULK_FLAG_INSERT_ID_REQUEST = 64 + ----------------------------------------- + if (stmt->send_types_to_server): + for (i=0; i < param_count; i++) + 1st byte: parameter type + 2nd byte flag: + unsigned flag (32768) + ------------------------------------------ + for (i=0; i < param_count; i++) + 1 indicator variable + STMT_INDICATOR_NONE 0 + STMT_INDICATOR_NULL 1 + STMT_INDICATOR_DEFAULT 2 + STMT_INDICATOR_IGNORE 3 + STMT_INDICATOR_SKIP_SET 4 + n data from bind buffer + + */ + + size_t length= 1024; + size_t free_bytes= 0; + ushort flags= 0; + uchar *tmp_start; + uint i, j; + + uchar *start= NULL, *p; + + if (!MARIADB_STMT_BULK_SUPPORTED(stmt)) + { + stmt_set_error(stmt, CR_FUNCTION_NOT_SUPPORTED, "IM001", + CER(CR_FUNCTION_NOT_SUPPORTED), "Bulk operation"); + return NULL; + } + + if (!stmt->param_count) + { + stmt_set_error(stmt, CR_BULK_WITHOUT_PARAMETERS, "IM001", + CER(CR_BULK_WITHOUT_PARAMETERS)); + return NULL; + } + + /* preallocate length bytes */ + if (!(start= p= (uchar *)malloc(length))) + { + goto mem_error; + } + + int4store(p, stmt->stmt_id); + p += STMT_ID_LENGTH; + + /* todo: request to return auto generated ids */ + if (stmt->send_types_to_server) + flags|= STMT_BULK_FLAG_CLIENT_SEND_TYPES; + int2store(p, flags); + p+=2; + + /* When using mariadb_stmt_execute_direct stmt->paran_count is + not known, so we need to assign prebind_params, which was previously + set by mysql_stmt_attr_set + */ + if (!stmt->param_count && stmt->prebind_params) + stmt->param_count= stmt->prebind_params; + + if (stmt->param_count) + { + free_bytes= length - (p - start); + + /* Store type information: + 2 bytes per type + */ + if (stmt->send_types_to_server) + { + if (free_bytes < stmt->param_count * 2 + 20) + { + size_t offset= p - start; + length= offset + stmt->param_count * 2 + 20; + if (!(tmp_start= (uchar *)realloc(start, length))) + { + goto mem_error; + } + start= tmp_start; + p= start + offset; + } + for (i = 0; i < stmt->param_count; i++) + { + /* this differs from mysqlnd, c api supports unsigned !! */ + uint buffer_type= stmt->params[i].buffer_type | (stmt->params[i].is_unsigned ? 32768 : 0); + int2store(p, buffer_type); + p+= 2; + } + } + + /* calculate data size */ + for (j=0; j < stmt->array_size; j++) + { + /* If callback for parameters was specified, we need to + update bind information for new row */ + if (stmt->param_callback) + { + if (stmt->param_callback(stmt->user_data, stmt->params, j)) + { + stmt_set_error(stmt, CR_ERR_STMT_PARAM_CALLBACK, SQLSTATE_UNKNOWN, 0); + goto error; + } + } + + if (mysql_stmt_skip_paramset(stmt, j)) + continue; + + for (i=0; i < stmt->param_count; i++) + { + size_t size= 0; + my_bool has_data= TRUE; + signed char indicator= ma_get_indicator(stmt, i, j); + /* check if we need to send data */ + if (indicator > 0) + has_data= FALSE; + size= 1; + + /* Please note that mysql_stmt_send_long_data is not supported + current when performing bulk execute */ + + if (has_data) + { + switch (stmt->params[i].buffer_type) { + case MYSQL_TYPE_NULL: + has_data= FALSE; + indicator= STMT_INDICATOR_NULL; + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_SET: + size+= 5; /* max 8 bytes for size */ + if (!stmt->param_callback) + { + if (indicator == STMT_INDICATOR_NTS || + (!stmt->row_size && ma_get_length(stmt,i,j) == -1)) + { + size+= strlen(ma_get_buffer_offset(stmt, + stmt->params[i].buffer_type, + stmt->params[i].buffer,j)); + } + else + size+= (size_t)ma_get_length(stmt, i, j); + } + else { + size+= stmt->params[i].buffer_length; + } + break; + default: + size+= mysql_ps_fetch_functions[stmt->params[i].buffer_type].pack_len; + break; + } + } + free_bytes= length - (p - start); + if (free_bytes < size + 20) + { + size_t offset= p - start; + length= MAX(2 * length, offset + size + 20); + if (!(tmp_start= (uchar *)realloc(start, length))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + goto error; + } + start= tmp_start; + p= start + offset; + } + + int1store(p, indicator > 0 ? indicator : 0); + p++; + if (has_data) { + store_param(stmt, i, &p, (stmt->param_callback) ? 0 : j); + } + } + } + + } + stmt->send_types_to_server= 0; + *request_len = (size_t)(p - start); + return start; +mem_error: + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); +error: + free(start); + *request_len= 0; + return NULL; +} +/* }}} */ + + +unsigned char* ma_stmt_execute_generate_request(MYSQL_STMT *stmt, size_t *request_len, my_bool internal) +{ + unsigned char *buf; + + + if (stmt->request_buffer) + { + *request_len= stmt->request_length; + buf= stmt->request_buffer; + /* store actual stmt id */ + int4store(buf, stmt->stmt_id); + /* clear buffer, memory will be freed in execute */ + stmt->request_buffer= NULL; + stmt->request_length= 0; + return buf; + } + if (stmt->array_size > 0) + buf= ma_stmt_execute_generate_bulk_request(stmt, request_len); + else + buf= ma_stmt_execute_generate_simple_request(stmt, request_len); + + if (internal) + { + if (stmt->request_buffer) + free(stmt->request_buffer); + stmt->request_buffer= buf; + stmt->request_length= *request_len; + } + return buf; +} + + +/*! + ******************************************************************************* + + \fn unsigned long long mysql_stmt_affected_rows + \brief returns the number of affected rows from last mysql_stmt_execute + call + + \param[in] stmt The statement handle + ******************************************************************************* + */ +unsigned long long STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt) +{ + return stmt->upsert_status.affected_rows; +} + +my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *value) +{ + switch (attr_type) { + case STMT_ATTR_STATE: + *(enum mysql_stmt_state *)value= stmt->state; + break; + case STMT_ATTR_UPDATE_MAX_LENGTH: + *(my_bool *)value= stmt->update_max_length; + break; + case STMT_ATTR_CURSOR_TYPE: + *(unsigned long *)value= stmt->flags; + break; + case STMT_ATTR_PREFETCH_ROWS: + *(unsigned long *)value= stmt->prefetch_rows; + break; + case STMT_ATTR_PREBIND_PARAMS: + *(unsigned int *)value= stmt->prebind_params; + break; + case STMT_ATTR_ARRAY_SIZE: + *(unsigned int *)value= stmt->array_size; + break; + case STMT_ATTR_ROW_SIZE: + *(size_t *)value= stmt->row_size; + break; + case STMT_ATTR_CB_USER_DATA: + *((void **)value) = stmt->user_data; + break; + default: + return(1); + } + return(0); +} + +my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *value) +{ + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH: + stmt->update_max_length= *(my_bool *)value; + break; + case STMT_ATTR_CURSOR_TYPE: + if (*(ulong *)value > (unsigned long) CURSOR_TYPE_READ_ONLY) + { + stmt_set_error(stmt, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); + return(1); + } + stmt->flags = *(ulong *)value; + break; + case STMT_ATTR_PREFETCH_ROWS: + if (*(ulong *)value == 0) + *(long *)value= MYSQL_DEFAULT_PREFETCH_ROWS; + else + stmt->prefetch_rows= *(long *)value; + break; + case STMT_ATTR_PREBIND_PARAMS: + if (stmt->state > MYSQL_STMT_INITTED) + { + mysql_stmt_internal_reset(stmt, 1); + net_stmt_close(stmt, 0); + stmt->state= MYSQL_STMT_INITTED; + stmt->params= 0; + } + stmt->prebind_params= stmt->param_count= *(unsigned int *)value; + break; + case STMT_ATTR_ARRAY_SIZE: + stmt->array_size= *(unsigned int *)value; + break; + case STMT_ATTR_ROW_SIZE: + stmt->row_size= *(size_t *)value; + break; + case STMT_ATTR_CB_RESULT: + stmt->result_callback= (ps_result_callback)value; + break; + case STMT_ATTR_CB_PARAM: + stmt->param_callback= (ps_param_callback)value; + break; + case STMT_ATTR_CB_USER_DATA: + stmt->user_data= (void *)value; + break; + default: + stmt_set_error(stmt, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); + return(1); + } + return(0); +} + +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind) +{ + MYSQL *mysql= stmt->mysql; + + if (!mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + /* If number of parameters was specified via mysql_stmt_attr_set we need to realloc + them, e.g. for mariadb_stmt_execute_direct() + */ + if ((stmt->state < MYSQL_STMT_PREPARED || stmt->state >= MYSQL_STMT_EXECUTED) && + stmt->prebind_params > 0) + { + if (!stmt->params && stmt->prebind_params) + { + if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->prebind_params * sizeof(MYSQL_BIND)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(1); + } + memset(stmt->params, '\0', stmt->prebind_params * sizeof(MYSQL_BIND)); + } + stmt->param_count= stmt->prebind_params; + } + else if (stmt->state < MYSQL_STMT_PREPARED) { + stmt_set_error(stmt, CR_NO_PREPARE_STMT, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->param_count && bind) + { + uint i; + + memcpy(stmt->params, bind, sizeof(MYSQL_BIND) * stmt->param_count); + stmt->send_types_to_server= 1; + + for (i=0; i < stmt->param_count; i++) + { + if (stmt->mysql->methods->db_supported_buffer_type && + !stmt->mysql->methods->db_supported_buffer_type(stmt->params[i].buffer_type)) + { + stmt_set_error(stmt, CR_UNSUPPORTED_PARAM_TYPE, SQLSTATE_UNKNOWN, 0); + return(1); + } + if (!stmt->params[i].is_null) + stmt->params[i].is_null= &is_not_null; + + if (stmt->params[i].long_data_used) + stmt->params[i].long_data_used= 0; + + if (!stmt->params[i].length) + stmt->params[i].length= &stmt->params[i].buffer_length; + + switch(stmt->params[i].buffer_type) { + case MYSQL_TYPE_NULL: + stmt->params[i].is_null= &is_null; + break; + case MYSQL_TYPE_TINY: + stmt->params[i].buffer_length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + stmt->params[i].buffer_length= 2; + break; + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + stmt->params[i].buffer_length= 4; + break; + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_DOUBLE: + stmt->params[i].buffer_length= 8; + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + stmt->params[i].buffer_length= 12; + break; + case MYSQL_TYPE_TIME: + stmt->params[i].buffer_length= 13; + break; + case MYSQL_TYPE_DATE: + stmt->params[i].buffer_length= 5; + break; + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + break; + default: + stmt_set_error(stmt, CR_UNSUPPORTED_PARAM_TYPE, SQLSTATE_UNKNOWN, 0); + return(1); + break; + } + } + } + stmt->bind_param_done= stmt->send_types_to_server= 1; + + CLEAR_CLIENT_STMT_ERROR(stmt); + return(0); +} + +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) +{ + uint i; + + if (stmt->state < MYSQL_STMT_PREPARED) + { + stmt_set_error(stmt, CR_NO_PREPARE_STMT, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!stmt->field_count) + { + stmt_set_error(stmt, CR_NO_STMT_METADATA, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!bind) + return(1); + + /* In case of a stored procedure we don't allocate memory for bind + in mysql_stmt_prepare + */ + + if (stmt->field_count && !stmt->bind) + { + MA_MEM_ROOT *fields_ma_alloc_root= + &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root, stmt->field_count * sizeof(MYSQL_BIND)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(1); + } + } + + memcpy(stmt->bind, bind, sizeof(MYSQL_BIND) * stmt->field_count); + + for (i=0; i < stmt->field_count; i++) + { + if (stmt->mysql->methods->db_supported_buffer_type && + !stmt->mysql->methods->db_supported_buffer_type(bind[i].buffer_type)) + { + stmt_set_error(stmt, CR_UNSUPPORTED_PARAM_TYPE, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!stmt->bind[i].is_null) + stmt->bind[i].is_null= &stmt->bind[i].is_null_value; + if (!stmt->bind[i].length) + stmt->bind[i].length= &stmt->bind[i].length_value; + if (!stmt->bind[i].error) + stmt->bind[i].error= &stmt->bind[i].error_value; + + /* set length values for numeric types */ + switch(bind[i].buffer_type) { + case MYSQL_TYPE_NULL: + *stmt->bind[i].length= stmt->bind[i].length_value= 0; + break; + case MYSQL_TYPE_TINY: + *stmt->bind[i].length= stmt->bind[i].length_value= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + *stmt->bind[i].length= stmt->bind[i].length_value= 2; + break; + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + *stmt->bind[i].length= stmt->bind[i].length_value= 4; + break; + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_DOUBLE: + *stmt->bind[i].length= stmt->bind[i].length_value= 8; + break; + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + *stmt->bind[i].length= stmt->bind[i].length_value= sizeof(MYSQL_TIME); + break; + default: + break; + } + } + stmt->bind_result_done= 1; + CLEAR_CLIENT_STMT_ERROR(stmt); + + return(0); +} + +static my_bool net_stmt_close(MYSQL_STMT *stmt, my_bool remove) +{ + char stmt_id[STMT_ID_LENGTH]; + MA_MEM_ROOT *fields_ma_alloc_root= &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + + /* clear memory */ + ma_free_root(&stmt->result.alloc, MYF(0)); /* allocated in mysql_stmt_store_result */ + ma_free_root(&stmt->mem_root,MYF(0)); + ma_free_root(fields_ma_alloc_root, MYF(0)); + + if (stmt->mysql) + { + CLEAR_CLIENT_ERROR(stmt->mysql); + + /* remove from stmt list */ + if (remove) + stmt->mysql->stmts= list_delete(stmt->mysql->stmts, &stmt->list); + + /* check if all data are fetched */ + if (stmt->mysql->status != MYSQL_STATUS_READY) + { + do { + stmt->mysql->methods->db_stmt_flush_unbuffered(stmt); + } while(mysql_stmt_more_results(stmt)); + stmt->mysql->status= MYSQL_STATUS_READY; + } + if (stmt->state > MYSQL_STMT_INITTED) + { + int4store(stmt_id, stmt->stmt_id); + if (stmt->mysql->methods->db_command(stmt->mysql,COM_STMT_CLOSE, stmt_id, + sizeof(stmt_id), 1, stmt)) + { + UPDATE_STMT_ERROR(stmt); + return 1; + } + } + } + return 0; +} + +my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) +{ + my_bool rc= 1; + + if (stmt) + { + if (stmt->mysql && stmt->mysql->net.pvio) + mysql_stmt_internal_reset(stmt, 1); + + rc= net_stmt_close(stmt, 1); + + free(stmt->extension); + free(stmt); + } + return(rc); +} + +void STDCALL mysql_stmt_data_seek(MYSQL_STMT *stmt, unsigned long long offset) +{ + unsigned long long i= offset; + MYSQL_ROWS *ptr= stmt->result.data; + + while(i-- && ptr) + ptr= ptr->next; + + stmt->result_cursor= ptr; + stmt->state= MYSQL_STMT_USER_FETCHING; + + return; +} + +unsigned int STDCALL mysql_stmt_errno(MYSQL_STMT *stmt) +{ + return stmt->last_errno; +} + +const char * STDCALL mysql_stmt_error(MYSQL_STMT *stmt) +{ + return (const char *)stmt->last_error; +} + +int mthd_stmt_fetch_row(MYSQL_STMT *stmt, unsigned char **row) +{ + return stmt->fetch_row_func(stmt, row); +} + +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt) +{ + unsigned char *row; + int rc; + + if (stmt->state <= MYSQL_STMT_EXECUTED) + { + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->state < MYSQL_STMT_WAITING_USE_OR_STORE || !stmt->field_count) + { + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } else if (stmt->state== MYSQL_STMT_WAITING_USE_OR_STORE) + { + stmt->default_rset_handler(stmt); + } + + if (stmt->state == MYSQL_STMT_FETCH_DONE) + return(MYSQL_NO_DATA); + + if ((rc= stmt->mysql->methods->db_stmt_fetch(stmt, &row))) + { + stmt->state= MYSQL_STMT_FETCH_DONE; + stmt->mysql->status= MYSQL_STATUS_READY; + /* to fetch data again, stmt must be executed again */ + return(rc); + } + + rc= stmt->mysql->methods->db_stmt_fetch_to_bind(stmt, row); + + stmt->state= MYSQL_STMT_USER_FETCHING; + CLEAR_CLIENT_ERROR(stmt->mysql); + CLEAR_CLIENT_STMT_ERROR(stmt); + return(rc); +} + +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, unsigned int column, unsigned long offset) +{ + if (stmt->state < MYSQL_STMT_USER_FETCHING || column >= stmt->field_count || + stmt->state == MYSQL_STMT_FETCH_DONE) { + stmt_set_error(stmt, CR_NO_DATA, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!stmt->bind[column].u.row_ptr) + { + /* we set row_ptr only for columns which contain data, so this must be a NULL column */ + if (bind[0].is_null) + *bind[0].is_null= 1; + } + else + { + unsigned char *save_ptr; + if (bind[0].length) + *bind[0].length= *stmt->bind[column].length; + else + bind[0].length= &stmt->bind[column].length_value; + if (bind[0].is_null) + *bind[0].is_null= 0; + else + bind[0].is_null= &bind[0].is_null_value; + if (!bind[0].error) + bind[0].error= &bind[0].error_value; + *bind[0].error= 0; + bind[0].offset= offset; + save_ptr= stmt->bind[column].u.row_ptr; + mysql_ps_fetch_functions[stmt->fields[column].type].func(&bind[0], &stmt->fields[column], &stmt->bind[column].u.row_ptr); + stmt->bind[column].u.row_ptr= save_ptr; + } + return(0); +} + +unsigned int STDCALL mysql_stmt_field_count(MYSQL_STMT *stmt) +{ + return stmt->field_count; +} + +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) +{ + return madb_reset_stmt(stmt, MADB_RESET_LONGDATA | MADB_RESET_STORED | + MADB_RESET_BUFFER | MADB_RESET_ERROR); +} + +MYSQL_STMT * STDCALL mysql_stmt_init(MYSQL *mysql) +{ + + MYSQL_STMT *stmt= NULL; + + if (!(stmt= (MYSQL_STMT *)calloc(1, sizeof(MYSQL_STMT))) || + !(stmt->extension= (MADB_STMT_EXTENSION *)calloc(1, sizeof(MADB_STMT_EXTENSION)))) + { + free(stmt); + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(NULL); + } + + + /* fill mysql's stmt list */ + stmt->list.data= stmt; + stmt->mysql= mysql; + stmt->stmt_id= 0; + mysql->stmts= list_add(mysql->stmts, &stmt->list); + + + /* clear flags */ + strcpy(stmt->sqlstate, "00000"); + + stmt->state= MYSQL_STMT_INITTED; + + /* set default */ + stmt->prefetch_rows= 1; + + ma_init_alloc_root(&stmt->mem_root, 2048, 2048); + ma_init_alloc_root(&stmt->result.alloc, 4096, 4096); + ma_init_alloc_root(&((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root, 2048, 2048); + + return(stmt); +} + +my_bool mthd_stmt_read_prepare_response(MYSQL_STMT *stmt) +{ + ulong packet_length; + uchar *p; + + if ((packet_length= ma_net_safe_read(stmt->mysql)) == packet_error) + return(1); + + p= (uchar *)stmt->mysql->net.read_pos; + + if (0xFF == p[0]) /* Error occurred */ + { + return(1); + } + + p++; + stmt->stmt_id= uint4korr(p); + p+= 4; + stmt->field_count= uint2korr(p); + p+= 2; + stmt->param_count= uint2korr(p); + p+= 2; + + /* filler */ + p++; + /* for backward compatibility we also update mysql->warning_count */ + stmt->mysql->warning_count= stmt->upsert_status.warning_count= uint2korr(p); + +/* metadata not supported yet */ + + if (stmt->param_count && + stmt->mysql->methods->db_stmt_get_param_metadata(stmt)) + { + return 1; + } + + /* allocated bind buffer for parameters */ + if (stmt->field_count && + stmt->mysql->methods->db_stmt_get_result_metadata(stmt)) + { + return 1; + } + if (stmt->param_count) + { + if (stmt->prebind_params) + { + if (stmt->prebind_params != stmt->param_count) + { + stmt_set_error(stmt, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + stmt->param_count= stmt->prebind_params; + return 1; + } + } else { + if (!(stmt->params= (MYSQL_BIND *)ma_alloc_root(&stmt->mem_root, stmt->param_count * sizeof(MYSQL_BIND)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return 1; + } + memset(stmt->params, '\0', stmt->param_count * sizeof(MYSQL_BIND)); + } + } + /* allocated bind buffer for result */ + if (stmt->field_count) + { + MA_MEM_ROOT *fields_ma_alloc_root= &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root, stmt->field_count * sizeof(MYSQL_BIND)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return 1; + } + memset(stmt->bind, 0, sizeof(MYSQL_BIND) * stmt->field_count); + } + stmt->state = MYSQL_STMT_PREPARED; + + return(0); +} + +my_bool mthd_stmt_get_param_metadata(MYSQL_STMT *stmt) +{ + MYSQL_DATA *result; + + if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0, + 7 + ma_extended_type_info_rows(stmt->mysql)))) + return(1); + + free_rows(result); + return(0); +} + +my_bool mthd_stmt_get_result_metadata(MYSQL_STMT *stmt) +{ + MYSQL_DATA *result; + MA_MEM_ROOT *fields_ma_alloc_root= &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + + if (!(result= stmt->mysql->methods->db_read_rows(stmt->mysql, (MYSQL_FIELD *)0, + 7 + ma_extended_type_info_rows(stmt->mysql)))) + return(1); + if (!(stmt->fields= unpack_fields(stmt->mysql, result, fields_ma_alloc_root, + stmt->field_count, 0))) + return(1); + return(0); +} + +int STDCALL mysql_stmt_warning_count(MYSQL_STMT *stmt) +{ + return stmt->upsert_status.warning_count; +} + +int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length) +{ + MYSQL *mysql= stmt->mysql; + int rc= 1; + my_bool is_multi= 0; + + if (!stmt->mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (length == (unsigned long) -1) + length= (unsigned long)strlen(query); + + /* clear flags */ + CLEAR_CLIENT_STMT_ERROR(stmt); + CLEAR_CLIENT_ERROR(stmt->mysql); + stmt->upsert_status.affected_rows= mysql->affected_rows= (unsigned long long) ~0; + + /* check if we have to clear results */ + if (stmt->state > MYSQL_STMT_INITTED) + { + char stmt_id[STMT_ID_LENGTH]; + is_multi= (mysql->net.extension->multi_status > COM_MULTI_OFF); + /* We need to semi-close the prepared statement: + reset stmt and free all buffers and close the statement + on server side. Statement handle will get a new stmt_id */ + + if (!is_multi) + ma_multi_command(mysql, COM_MULTI_ENABLED); + + if (mysql_stmt_internal_reset(stmt, 1)) + goto fail; + + ma_free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); + ma_free_root(&((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root, MYF(0)); + + stmt->param_count= 0; + stmt->field_count= 0; + stmt->fields= NULL; + stmt->params= NULL; + + int4store(stmt_id, stmt->stmt_id); + if (mysql->methods->db_command(mysql, COM_STMT_CLOSE, stmt_id, + sizeof(stmt_id), 1, stmt)) + goto fail; + } + if (mysql->methods->db_command(mysql, COM_STMT_PREPARE, query, length, 1, stmt)) + goto fail; + + if (!is_multi && mysql->net.extension->multi_status == COM_MULTI_ENABLED) + if (ma_multi_command(mysql, COM_MULTI_END)) + goto fail; + + if (mysql->net.extension->multi_status > COM_MULTI_OFF || + mysql->options.extension->skip_read_response) + return 0; + + if (mysql->methods->db_read_prepare_response && + mysql->methods->db_read_prepare_response(stmt)) + goto fail; + + return(0); + +fail: + stmt->state= MYSQL_STMT_INITTED; + UPDATE_STMT_ERROR(stmt); + return(rc); +} + +int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt) +{ + unsigned int last_server_status; + + if (!stmt->mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!stmt->field_count) + return(0); + + /* test_pure_coverage requires checking of error_no */ + if (stmt->last_errno) + return(1); + + if (stmt->state < MYSQL_STMT_EXECUTED) + { + SET_CLIENT_ERROR(stmt->mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + last_server_status= stmt->mysql->server_status; + + /* if stmt is a cursor, we need to tell server to send all rows */ + if (stmt->cursor_exists && stmt->mysql->status == MYSQL_STATUS_READY) + { + char buff[STMT_ID_LENGTH + 4]; + int4store(buff, stmt->stmt_id); + int4store(buff + STMT_ID_LENGTH, (int)~0); + + if (stmt->mysql->methods->db_command(stmt->mysql, COM_STMT_FETCH, + buff, sizeof(buff), 1, stmt)) + { + UPDATE_STMT_ERROR(stmt); + return(1); + } + } + else if (stmt->mysql->status != MYSQL_STATUS_STMT_RESULT) + { + SET_CLIENT_ERROR(stmt->mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->mysql->methods->db_stmt_read_all_rows(stmt)) + { + /* error during read - reset stmt->data */ + ma_free_root(&stmt->result.alloc, 0); + stmt->result.data= NULL; + stmt->result.rows= 0; + stmt->mysql->status= MYSQL_STATUS_READY; + return(1); + } + + /* workaround for MDEV 6304: + more results not set if the resultset has + SERVER_PS_OUT_PARAMS set + */ + if (last_server_status & SERVER_PS_OUT_PARAMS && + !(stmt->mysql->server_status & SERVER_MORE_RESULTS_EXIST)) + stmt->mysql->server_status|= SERVER_MORE_RESULTS_EXIST; + + stmt->result_cursor= stmt->result.data; + stmt->fetch_row_func= stmt_buffered_fetch; + stmt->mysql->status= MYSQL_STATUS_READY; + + if (!stmt->result.rows) + stmt->state= MYSQL_STMT_FETCH_DONE; + else + stmt->state= MYSQL_STMT_USE_OR_STORE_CALLED; + + /* set affected rows: see bug 2247 */ + stmt->upsert_status.affected_rows= stmt->result.rows; + stmt->mysql->affected_rows= stmt->result.rows; + + return(0); +} + +static int madb_alloc_stmt_fields(MYSQL_STMT *stmt) +{ + MA_MEM_ROOT *fields_ma_alloc_root= &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + MYSQL *mysql= stmt->mysql; + if (!mysql->field_count) + return 0; + + stmt->field_count= mysql->field_count; + if (mysql->fields) + { + /* Column info was sent by server */ + ma_free_root(fields_ma_alloc_root, MYF(0)); + if (!(stmt->fields= ma_duplicate_resultset_metadata( + mysql->fields, mysql->field_count, + fields_ma_alloc_root))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(1); + } + if (!(stmt->bind= (MYSQL_BIND *) ma_alloc_root( + fields_ma_alloc_root, stmt->field_count * sizeof(MYSQL_BIND)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return (1); + } + } + memset(stmt->bind, 0, stmt->field_count * sizeof(MYSQL_BIND)); + stmt->bind_result_done= 0; + return(0); +} + +int mthd_stmt_read_execute_response(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + int ret; + unsigned int last_status= mysql->server_status; + + if (!mysql) + return(1); + + /* if a reconnect occurred, our connection handle is invalid */ + if (!stmt->mysql) + return (1); + + ret= test((mysql->methods->db_read_stmt_result && + mysql->methods->db_read_stmt_result(mysql))); + + if (!ret && mysql->field_count && !mysql->fields) + { + /* + Column info was not sent by server, copy + from stmt->fields + */ + assert(stmt->fields); + /* + Too bad, C/C resets stmt->field_count to 0 + before reading SP output variables result sets. + */ + if(!stmt->field_count) + stmt->field_count = mysql->field_count; + else + assert(mysql->field_count == stmt->field_count); + mysql->fields= ma_duplicate_resultset_metadata( + stmt->fields, stmt->field_count, &mysql->field_alloc); + if (!mysql->fields) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return (1); + } + } + + /* update affected rows, also if an error occurred */ + stmt->upsert_status.affected_rows= stmt->mysql->affected_rows; + + if (ret) + { + stmt_set_error(stmt, mysql->net.last_errno, mysql->net.sqlstate, + mysql->net.last_error); + /* if mariadb_stmt_execute_direct was used, we need to send the number + of parameters to the specified prebinded value to prevent possible + memory overrun */ + if (stmt->prebind_params) + { + stmt->param_count= stmt->prebind_params; + } + stmt->state= MYSQL_STMT_PREPARED; + return(1); + } + stmt->upsert_status.last_insert_id= mysql->insert_id; + stmt->upsert_status.server_status= mysql->server_status; + ma_status_callback(stmt->mysql, last_status); + stmt->upsert_status.warning_count= mysql->warning_count; + + CLEAR_CLIENT_ERROR(mysql); + CLEAR_CLIENT_STMT_ERROR(stmt); + + stmt->execute_count++; + stmt->send_types_to_server= 0; + + stmt->state= MYSQL_STMT_EXECUTED; + + if (mysql->field_count) + { + if (!stmt->field_count || + mysql->server_status & SERVER_MORE_RESULTS_EXIST) /* fix for ps_bug: test_misc */ + { + MA_MEM_ROOT *fields_ma_alloc_root= + &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root; + uint i; + + ma_free_root(fields_ma_alloc_root, MYF(0)); + if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root, + sizeof(MYSQL_BIND) * mysql->field_count)) || + !(stmt->fields= (MYSQL_FIELD *)ma_alloc_root(fields_ma_alloc_root, + sizeof(MYSQL_FIELD) * mysql->field_count))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(1); + } + memset(stmt->bind, 0, sizeof(MYSQL_BIND) * mysql->field_count); + stmt->field_count= mysql->field_count; + + for (i=0; i < stmt->field_count; i++) + { + memcpy(&stmt->fields[i], &mysql->fields[i], sizeof(MYSQL_FIELD)); + + /* since all pointers will be incorrect if another statement will + be executed, so we need to allocate memory and copy the + information */ + if (mysql->fields[i].db) + stmt->fields[i].db= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].db); + if (mysql->fields[i].table) + stmt->fields[i].table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].table); + if (mysql->fields[i].org_table) + stmt->fields[i].org_table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_table); + if (mysql->fields[i].name) + stmt->fields[i].name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].name); + if (mysql->fields[i].org_name) + stmt->fields[i].org_name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_name); + if (mysql->fields[i].catalog) + stmt->fields[i].catalog= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].catalog); + if (mysql->fields[i].def) + stmt->fields[i].def= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].def); + stmt->fields[i].extension= + mysql->fields[i].extension ? + ma_field_extension_deep_dup(fields_ma_alloc_root, + mysql->fields[i].extension) : + NULL; + } + } + + if ((stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS) && + (stmt->flags & CURSOR_TYPE_READ_ONLY)) + { + stmt->cursor_exists = TRUE; + mysql->status = MYSQL_STATUS_READY; + + /* Only cursor read */ + stmt->default_rset_handler = _mysql_stmt_use_result; + + } else if (stmt->flags & CURSOR_TYPE_READ_ONLY && + !(stmt->upsert_status.server_status & SERVER_MORE_RESULTS_EXIST)) + { + /* + We have asked for CURSOR but got no cursor, because the condition + above is not fulfilled. Then... + This is a single-row result set, a result set with no rows, EXPLAIN, + SHOW VARIABLES, or some other command which either a) bypasses the + cursors framework in the server and writes rows directly to the + network or b) is more efficient if all (few) result set rows are + precached on client and server's resources are freed. + */ + + /* preferred is buffered read */ + if (mysql_stmt_store_result(stmt)) + return 1; + stmt->mysql->status= MYSQL_STATUS_STMT_RESULT; + } else + { + /* preferred is unbuffered read */ + stmt->default_rset_handler = _mysql_stmt_use_result; + stmt->mysql->status= MYSQL_STATUS_STMT_RESULT; + } + stmt->state= MYSQL_STMT_WAITING_USE_OR_STORE; + /* in certain cases parameter types can change: For example see bug + 4026 (SELECT ?), so we need to update field information */ + if (mysql->field_count == stmt->field_count) + { + uint i; + for (i=0; i < stmt->field_count; i++) + { + stmt->fields[i].type= mysql->fields[i].type; + stmt->fields[i].length= mysql->fields[i].length; + stmt->fields[i].flags= mysql->fields[i].flags; + stmt->fields[i].decimals= mysql->fields[i].decimals; + stmt->fields[i].charsetnr= mysql->fields[i].charsetnr; + stmt->fields[i].max_length= mysql->fields[i].max_length; + } + } else + { + /* table was altered, see test_wl4166_2 */ + stmt_set_error(stmt, CR_NEW_STMT_METADATA, SQLSTATE_UNKNOWN, 0); + return(1); + } + } + return(0); +} + +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + char *request; + int ret; + size_t request_len= 0; + + if (!stmt->mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->state < MYSQL_STMT_PREPARED) + { + SET_CLIENT_ERROR(mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->param_count && !stmt->bind_param_done) + { + stmt_set_error(stmt, CR_PARAMS_NOT_BOUND, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->state == MYSQL_STMT_WAITING_USE_OR_STORE) + { + stmt->default_rset_handler = _mysql_stmt_use_result; + stmt->default_rset_handler(stmt); + } + if (stmt->state > MYSQL_STMT_WAITING_USE_OR_STORE && stmt->state < MYSQL_STMT_FETCH_DONE && !stmt->result.data) + { + if (!stmt->cursor_exists) + do { + stmt->mysql->methods->db_stmt_flush_unbuffered(stmt); + } while(mysql_stmt_more_results(stmt)); + stmt->state= MYSQL_STMT_PREPARED; + stmt->mysql->status= MYSQL_STATUS_READY; + } + + /* clear data, in case mysql_stmt_store_result was called */ + if (stmt->result.data) + { + ma_free_root(&stmt->result.alloc, MYF(MY_KEEP_PREALLOC)); + stmt->result_cursor= stmt->result.data= 0; + } + /* CONC-344: set row count to zero */ + stmt->result.rows= 0; + + request= (char *)ma_stmt_execute_generate_request(stmt, &request_len, 0); + if (!request) + return 1; + + ret= stmt->mysql->methods->db_command(mysql, + stmt->array_size > 0 ? COM_STMT_BULK_EXECUTE : COM_STMT_EXECUTE, + request, request_len, 1, stmt); + if (request) + free(request); + + if (ret) + { + UPDATE_STMT_ERROR(stmt); + return(1); + } + + if (mysql->net.extension->multi_status > COM_MULTI_OFF || + mysql->options.extension->skip_read_response) + return(0); + + return(mthd_stmt_read_execute_response(stmt)); +} + +static my_bool madb_reset_stmt(MYSQL_STMT *stmt, unsigned int flags) +{ + MYSQL *mysql= stmt->mysql; + my_bool ret= 0; + + if (!stmt->mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + /* clear error */ + if (flags & MADB_RESET_ERROR) + { + CLEAR_CLIENT_ERROR(stmt->mysql); + CLEAR_CLIENT_STMT_ERROR(stmt); + } + + if (stmt->stmt_id) + { + /* free buffered resultset, previously allocated + * by mysql_stmt_store_result + */ + if (flags & MADB_RESET_STORED && + stmt->result_cursor) + { + ma_free_root(&stmt->result.alloc, MYF(MY_KEEP_PREALLOC)); + stmt->result.data= NULL; + stmt->result.rows= 0; + stmt->result_cursor= NULL; + stmt->mysql->status= MYSQL_STATUS_READY; + stmt->state= MYSQL_STMT_FETCH_DONE; + } + + /* if there is a pending result set, we will flush it */ + if (flags & MADB_RESET_BUFFER) + { + if (stmt->state == MYSQL_STMT_WAITING_USE_OR_STORE) + { + stmt->default_rset_handler(stmt); + stmt->state = MYSQL_STMT_USER_FETCHING; + } + + if (stmt->mysql->status!= MYSQL_STATUS_READY && stmt->field_count) + { + mysql->methods->db_stmt_flush_unbuffered(stmt); + mysql->status= MYSQL_STATUS_READY; + } + } + + if (flags & MADB_RESET_SERVER) + { + /* reset statement on server side */ + if (stmt->mysql && stmt->mysql->status == MYSQL_STATUS_READY && + stmt->mysql->net.pvio) + { + unsigned char cmd_buf[STMT_ID_LENGTH]; + int4store(cmd_buf, stmt->stmt_id); + if ((ret= stmt->mysql->methods->db_command(mysql,COM_STMT_RESET, (char *)cmd_buf, + sizeof(cmd_buf), 0, stmt))) + { + UPDATE_STMT_ERROR(stmt); + return(ret); + } + } + } + + if (flags & MADB_RESET_LONGDATA) + { + if (stmt->params) + { + ulonglong i; + for (i=0; i < stmt->param_count; i++) + if (stmt->params[i].long_data_used) + stmt->params[i].long_data_used= 0; + } + } + + } + return(ret); +} + +static my_bool mysql_stmt_internal_reset(MYSQL_STMT *stmt, my_bool is_close) +{ + MYSQL *mysql= stmt->mysql; + my_bool ret= 1; + unsigned int flags= MADB_RESET_LONGDATA | MADB_RESET_BUFFER | MADB_RESET_ERROR; + unsigned int last_status; + + if (!mysql) + { + /* connection could be invalid, e.g. after mysql_stmt_close or failed reconnect + attempt (see bug CONC-97) */ + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + last_status= mysql->server_status; + + if (stmt->state >= MYSQL_STMT_USER_FETCHING && + stmt->fetch_row_func == stmt_unbuffered_fetch) + flags|= MADB_RESET_BUFFER; + + ret= madb_reset_stmt(stmt, flags); + + if (stmt->stmt_id) + { + if ((stmt->state > MYSQL_STMT_EXECUTED && + stmt->mysql->status != MYSQL_STATUS_READY) || + stmt->mysql->server_status & SERVER_MORE_RESULTS_EXIST) + { + /* flush any pending (multiple) result sets */ + if (stmt->state == MYSQL_STMT_WAITING_USE_OR_STORE) + { + stmt->default_rset_handler(stmt); + stmt->state = MYSQL_STMT_USER_FETCHING; + } + + if (stmt->field_count) + { + while (mysql_stmt_next_result(stmt) == 0); + stmt->mysql->status= MYSQL_STATUS_READY; + } + } + if (!is_close) + ret= madb_reset_stmt(stmt, MADB_RESET_SERVER); + stmt->state= MYSQL_STMT_PREPARED; + } + else + stmt->state= MYSQL_STMT_INITTED; + + stmt->upsert_status.affected_rows= mysql->affected_rows; + stmt->upsert_status.last_insert_id= mysql->insert_id; + stmt->upsert_status.server_status= mysql->server_status; + ma_status_callback(stmt->mysql, last_status); + stmt->upsert_status.warning_count= mysql->warning_count; + mysql->status= MYSQL_STATUS_READY; + + return(ret); +} + +MYSQL_RES * STDCALL mysql_stmt_result_metadata(MYSQL_STMT *stmt) +{ + MYSQL_RES *res; + + if (!stmt->field_count) + return(NULL); + + /* allocate result set structure and copy stmt information */ + if (!(res= (MYSQL_RES *)calloc(1, sizeof(MYSQL_RES)))) + { + stmt_set_error(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return(NULL); + } + + res->eof= 1; + res->fields= stmt->fields; + res->field_count= stmt->field_count; + return(res); +} + +my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) +{ + if (stmt->stmt_id > 0 && + stmt->stmt_id != (unsigned long) -1) + return mysql_stmt_internal_reset(stmt, 0); + return 0; +} + +const char * STDCALL mysql_stmt_sqlstate(MYSQL_STMT *stmt) +{ + return stmt->sqlstate; +} + +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_tell(MYSQL_STMT *stmt) +{ + return(stmt->result_cursor); +} + +unsigned long STDCALL mysql_stmt_param_count(MYSQL_STMT *stmt) +{ + return stmt->param_count; +} + +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET new_row) +{ + MYSQL_ROW_OFFSET old_row; /* for returning old position */ + + old_row= stmt->result_cursor; + stmt->result_cursor= new_row; + + return(old_row); +} + +my_bool STDCALL mysql_stmt_send_long_data(MYSQL_STMT *stmt, uint param_number, + const char *data, unsigned long length) +{ + CLEAR_CLIENT_ERROR(stmt->mysql); + CLEAR_CLIENT_STMT_ERROR(stmt); + + if (stmt->state < MYSQL_STMT_PREPARED || !stmt->params) + { + stmt_set_error(stmt, CR_NO_PREPARE_STMT, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (param_number >= stmt->param_count) + { + stmt_set_error(stmt, CR_INVALID_PARAMETER_NO, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (length || !stmt->params[param_number].long_data_used) + { + int ret; + size_t packet_len= STMT_ID_LENGTH + 2 + length; + uchar *cmd_buff= (uchar *)calloc(1, packet_len); + int4store(cmd_buff, stmt->stmt_id); + int2store(cmd_buff + STMT_ID_LENGTH, param_number); + memcpy(cmd_buff + STMT_ID_LENGTH + 2, data, length); + stmt->params[param_number].long_data_used= 1; + ret= stmt->mysql->methods->db_command(stmt->mysql, COM_STMT_SEND_LONG_DATA, + (char *)cmd_buff, packet_len, 1, stmt); + if (ret) + UPDATE_STMT_ERROR(stmt); + free(cmd_buff); + return(ret); + } + return(0); +} + +unsigned long long STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt) +{ + return stmt->upsert_status.last_insert_id; +} + +unsigned long long STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) +{ + return stmt->result.rows; +} + +MYSQL_RES* STDCALL mysql_stmt_param_metadata(MYSQL_STMT *stmt __attribute__((unused))) +{ + /* server doesn't deliver any information yet, + so we just return NULL + */ + return(NULL); +} + +my_bool STDCALL mysql_stmt_more_results(MYSQL_STMT *stmt) +{ + /* MDEV 4604: Server doesn't set MORE_RESULT flag for + OutParam result set, so we need to check + for SERVER_MORE_RESULTS_EXIST and for + SERVER_PS_OUT_PARAMS) + */ + return (stmt && + stmt->mysql && + ((stmt->mysql->server_status & SERVER_MORE_RESULTS_EXIST) || + (stmt->mysql->server_status & SERVER_PS_OUT_PARAMS))); +} + +int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) +{ + int rc= 0; + + if (!stmt->mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (stmt->state < MYSQL_STMT_EXECUTED) + { + SET_CLIENT_ERROR(stmt->mysql, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return(1); + } + + if (!mysql_stmt_more_results(stmt)) + return(-1); + + if (stmt->state > MYSQL_STMT_EXECUTED && + stmt->state < MYSQL_STMT_FETCH_DONE) + madb_reset_stmt(stmt, MADB_RESET_ERROR | MADB_RESET_BUFFER | MADB_RESET_LONGDATA); + stmt->state= MYSQL_STMT_WAITING_USE_OR_STORE; + + if (mysql_next_result(stmt->mysql)) + { + stmt->state= MYSQL_STMT_FETCH_DONE; + stmt_set_error(stmt, stmt->mysql->net.last_errno, stmt->mysql->net.sqlstate, + stmt->mysql->net.last_error); + return(1); + } + + if (stmt->mysql->status == MYSQL_STATUS_GET_RESULT) + stmt->mysql->status= MYSQL_STATUS_STMT_RESULT; + + if (stmt->mysql->field_count) + rc= madb_alloc_stmt_fields(stmt); + else + { + unsigned int last_status= stmt->mysql->server_status; + stmt->upsert_status.affected_rows= stmt->mysql->affected_rows; + stmt->upsert_status.last_insert_id= stmt->mysql->insert_id; + stmt->upsert_status.server_status= stmt->mysql->server_status; + ma_status_callback(stmt->mysql, last_status); + stmt->upsert_status.warning_count= stmt->mysql->warning_count; + } + + stmt->field_count= stmt->mysql->field_count; + stmt->result.rows= 0; + + return(rc); +} + +int STDCALL mariadb_stmt_execute_direct(MYSQL_STMT *stmt, + const char *stmt_str, + size_t length) +{ + MYSQL *mysql; + my_bool emulate_cmd; + my_bool clear_result= 0; + + if (!stmt) + return 1; + + mysql= stmt->mysql; + if (!mysql) + { + stmt_set_error(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); + return 1; + } + + emulate_cmd= !(!(stmt->mysql->server_capabilities & CLIENT_MYSQL) && + (stmt->mysql->extension->mariadb_server_capabilities & + (MARIADB_CLIENT_STMT_BULK_OPERATIONS >> 32))) || mysql->net.compress; + + /* Server versions < 10.2 don't support execute_direct, so we need to + emulate it */ + if (emulate_cmd) + { + int rc; + + /* avoid sending close + prepare in 2 packets */ + if ((rc= mysql_stmt_prepare(stmt, stmt_str, (unsigned long)length))) + return rc; + return mysql_stmt_execute(stmt); + } + + if (ma_multi_command(mysql, COM_MULTI_ENABLED)) + { + stmt_set_error(stmt, CR_COMMANDS_OUT_OF_SYNC, SQLSTATE_UNKNOWN, 0); + return 1; + } + + if (length == (size_t) -1) + length= strlen(stmt_str); + + /* clear flags */ + CLEAR_CLIENT_STMT_ERROR(stmt); + CLEAR_CLIENT_ERROR(stmt->mysql); + stmt->upsert_status.affected_rows= mysql->affected_rows= (unsigned long long) ~0; + + /* check if we have to clear results */ + if (stmt->state > MYSQL_STMT_INITTED) + { + /* We need to semi-close the prepared statement: + reset stmt and free all buffers and close the statement + on server side. Statement handle will get a new stmt_id */ + char stmt_id[STMT_ID_LENGTH]; + + if (mysql_stmt_internal_reset(stmt, 1)) + goto fail; + + ma_free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); + ma_free_root(&((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root, MYF(0)); + stmt->field_count= 0; + stmt->param_count= 0; + stmt->params= 0; + + int4store(stmt_id, stmt->stmt_id); + if (mysql->methods->db_command(mysql, COM_STMT_CLOSE, stmt_id, + sizeof(stmt_id), 1, stmt)) + goto fail; + } + stmt->stmt_id= -1; + if (mysql->methods->db_command(mysql, COM_STMT_PREPARE, stmt_str, length, 1, stmt)) + goto fail; + + /* in case prepare fails, we need to clear the result package from execute, which + is always an error packet (invalid statement id) */ + clear_result= 1; + + stmt->state= MYSQL_STMT_PREPARED; + /* Since we can't determine stmt_id here, we need to set it to -1, so server will know that the + * execute command belongs to previous prepare */ + stmt->stmt_id= -1; + if (mysql_stmt_execute(stmt)) + goto fail; + + /* flush multi buffer */ + if (ma_multi_command(mysql, COM_MULTI_END)) + goto fail; + + if (!mysql->options.extension->skip_read_response) + { + /* read prepare response */ + if (mysql->methods->db_read_prepare_response && + mysql->methods->db_read_prepare_response(stmt)) + goto fail; + + clear_result= 0; + + /* read execute response packet */ + return mthd_stmt_read_execute_response(stmt); + } +fail: + /* check if we need to set error message */ + if (!mysql_stmt_errno(stmt)) + UPDATE_STMT_ERROR(stmt); + if (clear_result) { + do { + stmt->mysql->methods->db_stmt_flush_unbuffered(stmt); + } while(mysql_stmt_more_results(stmt)); + } + + /* CONC-633: If prepare returned an error, we ignore error from execute */ + if (mysql_stmt_errno(stmt)) + { + my_set_error(mysql, mysql_stmt_errno(stmt), mysql_stmt_sqlstate(stmt), + mysql_stmt_error(stmt)); + stmt->state= MYSQL_STMT_INITTED; + } + return 1; +} + +MYSQL_FIELD * STDCALL mariadb_stmt_fetch_fields(MYSQL_STMT *stmt) +{ + if (stmt) + return stmt->fields; + return NULL; +} diff --git a/libmariadb/libmariadb/secure/gnutls.c b/libmariadb/libmariadb/secure/gnutls.c new file mode 100644 index 00000000..4782be62 --- /dev/null +++ b/libmariadb/libmariadb/secure/gnutls.c @@ -0,0 +1,1438 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#ifdef HAVE_GNUTLS + +#include <ma_global.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gnutls/abstract.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <ma_pvio.h> +#include <errmsg.h> +#include <ma_pthread.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <ma_tls.h> +#include <mariadb_async.h> +#include <ma_context.h> + +pthread_mutex_t LOCK_gnutls_config; + +extern my_bool ma_tls_initialized; +extern unsigned int mariadb_deinitialize_ssl; + +enum ma_pem_type { + MA_TLS_PEM_CERT= 0, + MA_TLS_PEM_KEY, + MA_TLS_PEM_CA, + MA_TLS_PEM_CRL +}; + +static int my_verify_callback(gnutls_session_t ssl); + +char tls_library_version[TLS_VERSION_LENGTH]; + +struct st_cipher_map { + unsigned char sid[2]; + const char *iana_name; + const char *openssl_name; + const char *gnutls_name; +}; + +const struct st_cipher_map tls_ciphers[]= +{ + { {0x00, 0x01}, + "TLS_RSA_WITH_NULL_MD5", + NULL, + "TLS_RSA_NULL_MD5"}, + { {0x00, 0x02}, + "TLS_RSA_WITH_NULL_SHA", + NULL, + "TLS_RSA_NULL_SHA1"}, + { {0x00, 0x3B}, + "TLS_RSA_WITH_NULL_SHA256", + NULL, + "TLS_RSA_NULL_SHA256"}, + { {0x00, 0x05}, + "TLS_RSA_WITH_RC4_128_SHA", + NULL, + "TLS_RSA_ARCFOUR_128_SHA1"}, + { {0x00, 0x04}, + "TLS_RSA_WITH_RC4_128_MD5", + NULL, + "TLS_RSA_ARCFOUR_128_MD5"}, + { {0x00, 0x0A}, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "DES-CBC3-SHA", + "TLS_RSA_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x2F}, + "TLS_RSA_WITH_AES_128_CBC_SHA", + "AES128-SHA", + "TLS_RSA_AES_128_CBC_SHA1"}, + { {0x00, 0x35}, + "TLS_RSA_WITH_AES_256_CBC_SHA", + "AES256-SHA", + "TLS_RSA_AES_256_CBC_SHA1"}, + { {0x00, 0xBA}, + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "CAMELLIA128-SHA256", + "TLS_RSA_CAMELLIA_128_CBC_SHA256"}, + { {0x00, 0xC0}, + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + NULL, + "TLS_RSA_CAMELLIA_256_CBC_SHA256"}, + { {0x00, 0x41}, + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "CAMELLIA128-SHA", + "TLS_RSA_CAMELLIA_128_CBC_SHA1"}, + { {0x00, 0x84}, + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "CAMELLIA256-SHA", + "TLS_RSA_CAMELLIA_256_CBC_SHA1"}, + { {0x00, 0x3C}, + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "AES128-SHA256", + "TLS_RSA_AES_128_CBC_SHA256"}, + { {0x00, 0x3D}, + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "AES256-SHA256", + "TLS_RSA_AES_256_CBC_SHA256"}, + { {0x00, 0x9C}, + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "AES128-GCM-SHA256", + "TLS_RSA_AES_128_GCM_SHA256"}, + { {0x00, 0x9D}, + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "AES256-GCM-SHA384", + "TLS_RSA_AES_256_GCM_SHA384"}, + { {0xC0, 0x7A}, + "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_RSA_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x7B}, + "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_RSA_CAMELLIA_256_GCM_SHA384"}, + { {0xC0, 0x9C}, + "TLS_RSA_WITH_AES_128_CCM", + NULL, + "TLS_RSA_AES_128_CCM"}, + { {0xC0, 0x9D}, + "TLS_RSA_WITH_AES_256_CCM", + NULL, + "TLS_RSA_AES_256_CCM"}, + { {0xC0, 0xA0}, + "TLS_RSA_WITH_AES_128_CCM_8", + NULL, + "TLS_RSA_AES_128_CCM_8"}, + { {0xC0, 0xA1}, + "TLS_RSA_WITH_AES_256_CCM_8", + NULL, + "TLS_RSA_AES_256_CCM_8"}, + { {0x00, 0x66}, + "TLS_DHE_DSS_WITH_RC4_128_SHA", + NULL, + "TLS_DHE_DSS_ARCFOUR_128_SHA1"}, + { {0x00, 0x13}, + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + NULL, + "TLS_DHE_DSS_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x32}, + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + NULL, + "TLS_DHE_DSS_AES_128_CBC_SHA1"}, + { {0x00, 0x38}, + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + NULL, + "TLS_DHE_DSS_AES_256_CBC_SHA1"}, + { {0x00, 0xBD}, + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA256"}, + { {0x00, 0xC3}, + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + NULL, + "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA256"}, + { {0x00, 0x44}, + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + NULL, + "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA1"}, + { {0x00, 0x87}, + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + NULL, + "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA1"}, + { {0x00, 0x40}, + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + NULL, + "TLS_DHE_DSS_AES_128_CBC_SHA256"}, + { {0x00, 0x6A}, + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + NULL, + "TLS_DHE_DSS_AES_256_CBC_SHA256"}, + { {0x00, 0xA2}, + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + NULL, + "TLS_DHE_DSS_AES_128_GCM_SHA256"}, + { {0x00, 0xA3}, + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + NULL, + "TLS_DHE_DSS_AES_256_GCM_SHA384"}, + { {0xC0, 0x80}, + "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_DHE_DSS_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x81}, + "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_DHE_DSS_CAMELLIA_256_GCM_SHA384"}, + { {0x00, 0x16}, + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "EDH-RSA-DES-CBC3-SHA", + "TLS_DHE_RSA_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x33}, + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "DHE-RSA-AES128-SHA", + "TLS_DHE_RSA_AES_128_CBC_SHA1"}, + { {0x00, 0x39}, + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "DHE-RSA-AES256-SHA", + "TLS_DHE_RSA_AES_256_CBC_SHA1"}, + { {0x00, 0xBE}, + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA256"}, + { {0x00, 0xC4}, + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + NULL, + "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA256"}, + { {0x00, 0x45}, + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "DHE-RSA-CAMELLIA128-SHA", + "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA1"}, + { {0x00, 0x88}, + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "DHE-RSA-CAMELLIA256-SHA", + "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA1"}, + { {0x00, 0x67}, + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "DHE-RSA-AES128-SHA256", + "TLS_DHE_RSA_AES_128_CBC_SHA256"}, + { {0x00, 0x6B}, + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "DHE-RSA-AES256-SHA256", + "TLS_DHE_RSA_AES_256_CBC_SHA256"}, + { {0x00, 0x9E}, + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "DHE-RSA-AES128-GCM-SHA256", + "TLS_DHE_RSA_AES_128_GCM_SHA256"}, + { {0x00, 0x9F}, + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "DHE-RSA-AES256-GCM-SHA384", + "TLS_DHE_RSA_AES_256_GCM_SHA384"}, + { {0xC0, 0x7C}, + "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_DHE_RSA_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x7D}, + "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_DHE_RSA_CAMELLIA_256_GCM_SHA384"}, + { {0xCC, 0xAA}, + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "DHE-RSA-CHACHA20-POLY1305", + "TLS_DHE_RSA_CHACHA20_POLY1305"}, + { {0xC0, 0x9E}, + "TLS_DHE_RSA_WITH_AES_128_CCM", + NULL, + "TLS_DHE_RSA_AES_128_CCM"}, + { {0xC0, 0x9F}, + "TLS_DHE_RSA_WITH_AES_256_CCM", + NULL, + "TLS_DHE_RSA_AES_256_CCM"}, + { {0xC0, 0xA2}, + "TLS_DHE_RSA_WITH_AES_128_CCM_8", + NULL, + "TLS_DHE_RSA_AES_128_CCM_8"}, + { {0xC0, 0xA3}, + "TLS_DHE_RSA_WITH_AES_256_CCM_8", + NULL, + "TLS_DHE_RSA_AES_256_CCM_8"}, + { {0xC0, 0x10}, + "TLS_ECDHE_RSA_WITH_", + NULL, + "TLS_ECDHE_RSA_NULL_SHA1"}, + { {0xC0, 0x12}, + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "ECDHE-RSA-DES-CBC3-SHA", + "TLS_ECDHE_RSA_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x13}, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "ECDHE-RSA-AES128-SHA", + "TLS_ECDHE_RSA_AES_128_CBC_SHA1"}, + { {0xC0, 0x14}, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "ECDHE-RSA-AES256-SHA", + "TLS_ECDHE_RSA_AES_256_CBC_SHA1"}, + { {0xC0, 0x28}, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "ECDHE-RSA-AES256-SHA384", + "TLS_ECDHE_RSA_AES_256_CBC_SHA384"}, + { {0xC0, 0x11}, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + NULL, + "TLS_ECDHE_RSA_ARCFOUR_128_SHA1"}, + { {0xC0, 0x76}, + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_ECDHE_RSA_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x77}, + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_ECDHE_RSA_CAMELLIA_256_CBC_SHA384"}, + { {0xC0, 0x06}, + "TLS_ECDHE_ECDSA_WITH_", + NULL, + "TLS_ECDHE_ECDSA_NULL_SHA1"}, + { {0xC0, 0x08}, + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "ECDHE-ECDSA-DES-CBC3-SHA", + "TLS_ECDHE_ECDSA_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x09}, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "ECDHE-ECDSA-AES128-SHA", + "TLS_ECDHE_ECDSA_AES_128_CBC_SHA1"}, + { {0xC0, 0x0A}, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "ECDHE-ECDSA-AES256-SHA", + "TLS_ECDHE_ECDSA_AES_256_CBC_SHA1"}, + { {0xC0, 0x07}, + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + NULL, + "TLS_ECDHE_ECDSA_ARCFOUR_128_SHA1"}, + { {0xC0, 0x72}, + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_ECDHE_ECDSA_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x73}, + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_ECDHE_ECDSA_CAMELLIA_256_CBC_SHA384"}, + { {0xC0, 0x23}, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "ECDHE-ECDSA-AES128-SHA256", + "TLS_ECDHE_ECDSA_AES_128_CBC_SHA256"}, + { {0xC0, 0x27}, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "ECDHE-RSA-AES128-SHA256", + "TLS_ECDHE_RSA_AES_128_CBC_SHA256"}, + { {0xC0, 0x86}, + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_ECDHE_ECDSA_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x87}, + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_ECDHE_ECDSA_CAMELLIA_256_GCM_SHA384"}, + { {0xC0, 0x2B}, + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "TLS_ECDHE_ECDSA_AES_128_GCM_SHA256"}, + { {0xC0, 0x2C}, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "TLS_ECDHE_ECDSA_AES_256_GCM_SHA384"}, + { {0xC0, 0x2F}, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "TLS_ECDHE_RSA_AES_128_GCM_SHA256"}, + { {0xC0, 0x30}, + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + "TLS_ECDHE_RSA_AES_256_GCM_SHA384"}, + { {0xC0, 0x24}, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "ECDHE-ECDSA-AES256-SHA384", + "TLS_ECDHE_ECDSA_AES_256_CBC_SHA384"}, + { {0xC0, 0x8A}, + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_ECDHE_RSA_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x8B}, + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_ECDHE_RSA_CAMELLIA_256_GCM_SHA384"}, + { {0xCC, 0xA8}, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "ECDHE-RSA-CHACHA20-POLY1305", + "TLS_ECDHE_RSA_CHACHA20_POLY1305"}, + { {0xCC, 0xA9}, + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "ECDHE-ECDSA-CHACHA20-POLY1305", + "TLS_ECDHE_ECDSA_CHACHA20_POLY1305"}, + { {0xC0, 0xAC}, + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + NULL, + "TLS_ECDHE_ECDSA_AES_128_CCM"}, + { {0xC0, 0xAD}, + "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + NULL, + "TLS_ECDHE_ECDSA_AES_256_CCM"}, + { {0xC0, 0xAE}, + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + NULL, + "TLS_ECDHE_ECDSA_AES_128_CCM_8"}, + { {0xC0, 0xAF}, + "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + NULL, + "TLS_ECDHE_ECDSA_AES_256_CCM_8"}, + { {0xC0, 0x34}, + "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", + "ECDHE-PSK-3DES-EDE-CBC-SHA", + "TLS_ECDHE_PSK_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x35}, + "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", + "ECDHE-PSK-AES128-CBC-SHA", + "TLS_ECDHE_PSK_AES_128_CBC_SHA1"}, + { {0xC0, 0x36}, + "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", + "ECDHE-PSK-AES256-CBC-SHA", + "TLS_ECDHE_PSK_AES_256_CBC_SHA1"}, + { {0xC0, 0x37}, + "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", + "ECDHE-PSK-AES128-CBC-SHA256", + "TLS_ECDHE_PSK_AES_128_CBC_SHA256"}, + { {0xC0, 0x38}, + "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", + "ECDHE-PSK-AES256-CBC-SHA384", + "TLS_ECDHE_PSK_AES_256_CBC_SHA384"}, + { {0xC0, 0x33}, + "TLS_ECDHE_PSK_WITH_RC4_128_SHA", + NULL, + "TLS_ECDHE_PSK_ARCFOUR_128_SHA1"}, + { {0xC0, 0x39}, + "TLS_ECDHE_PSK_WITH_NULL_SHA", + NULL, + "TLS_ECDHE_PSK_NULL_SHA1"}, + { {0xC0, 0x3A}, + "TLS_ECDHE_PSK_WITH_NULL_SHA256", + NULL, + "TLS_ECDHE_PSK_NULL_SHA256"}, + { {0xC0, 0x3B}, + "TLS_ECDHE_PSK_WITH_NULL_SHA384", + NULL, + "TLS_ECDHE_PSK_NULL_SHA384"}, + { {0xC0, 0x9A}, + "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_ECDHE_PSK_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x9B}, + "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_ECDHE_PSK_CAMELLIA_256_CBC_SHA384"}, + { {0x00, 0x8A}, + "TLS_PSK_WITH_RC4_128_SHA", + NULL, + "TLS_PSK_ARCFOUR_128_SHA1"}, + { {0x00, 0x8B}, + "TLS_PSK_WITH_3DES_EDE_CBC_SHA", + "PSK-3DES-EDE-CBC-SHA", + "TLS_PSK_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x8C}, + "TLS_PSK_WITH_AES_128_CBC_SHA", + "PSK-AES128-CBC-SHA", + "TLS_PSK_AES_128_CBC_SHA1"}, + { {0x00, 0x8D}, + "TLS_PSK_WITH_AES_256_CBC_SHA", + "PSK-AES256-CBC-SHA", + "TLS_PSK_AES_256_CBC_SHA1"}, + { {0x00, 0xAE}, + "TLS_PSK_WITH_AES_128_CBC_SHA256", + "PSK-AES128-CBC-SHA256", + "TLS_PSK_AES_128_CBC_SHA256"}, + { {0x00, 0xA9}, + "TLS_PSK_WITH_AES_256_GCM_SHA384", + "PSK-AES256-GCM-SHA384", + "TLS_PSK_AES_256_GCM_SHA384"}, + { {0xC0, 0x8E}, + "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_PSK_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x8F}, + "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_PSK_CAMELLIA_256_GCM_SHA384"}, + { {0x00, 0xA8}, + "TLS_PSK_WITH_AES_128_GCM_SHA256", + "PSK-AES128-GCM-SHA256", + "TLS_PSK_AES_128_GCM_SHA256"}, + { {0x00, 0x2C}, + "TLS_PSK_WITH_", + NULL, + "TLS_PSK_NULL_SHA1"}, + { {0x00, 0xB0}, + "TLS_PSK_WITH_", + NULL, + "TLS_PSK_NULL_SHA256"}, + { {0xC0, 0x94}, + "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_PSK_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x95}, + "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_PSK_CAMELLIA_256_CBC_SHA384"}, + { {0x00, 0xAF}, + "TLS_PSK_WITH_AES_256_CBC_SHA384", + "PSK-AES256-CBC-SHA384", + "TLS_PSK_AES_256_CBC_SHA384"}, + { {0x00, 0xB1}, + "TLS_PSK_WITH_", + NULL, + "TLS_PSK_NULL_SHA384"}, + { {0x00, 0x92}, + "TLS_RSA_PSK_WITH_RC4_128_SHA", + NULL, + "TLS_RSA_PSK_ARCFOUR_128_SHA1"}, + { {0x00, 0x93}, + "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", + "RSA-PSK-3DES-EDE-CBC-SHA", + "TLS_RSA_PSK_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x94}, + "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", + "RSA-PSK-AES128-CBC-SHA", + "TLS_RSA_PSK_AES_128_CBC_SHA1"}, + { {0x00, 0x95}, + "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", + "RSA-PSK-AES256-CBC-SHA", + "TLS_RSA_PSK_AES_256_CBC_SHA1"}, + { {0xC0, 0x92}, + "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_RSA_PSK_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x93}, + "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_RSA_PSK_CAMELLIA_256_GCM_SHA384"}, + { {0x00, 0xAC}, + "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", + "RSA-PSK-AES128-GCM-SHA256", + "TLS_RSA_PSK_AES_128_GCM_SHA256"}, + { {0x00, 0xB6}, + "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", + "RSA-PSK-AES128-CBC-SHA256", + "TLS_RSA_PSK_AES_128_CBC_SHA256"}, + { {0x00, 0x2E}, + "TLS_RSA_PSK_WITH_NULL_SHA", + NULL, + "TLS_RSA_PSK_NULL_SHA1"}, + { {0x00, 0xB8}, + "TLS_RSA_PSK_WITH_", + NULL, + "TLS_RSA_PSK_NULL_SHA256"}, + { {0x00, 0xAD}, + "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", + "RSA-PSK-AES256-GCM-SHA384", + "TLS_RSA_PSK_AES_256_GCM_SHA384"}, + { {0x00, 0xB7}, + "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", + "RSA-PSK-AES256-CBC-SHA384", + "TLS_RSA_PSK_AES_256_CBC_SHA384"}, + { {0x00, 0xB9}, + "TLS_RSA_PSK_WITH_", + NULL, + "TLS_RSA_PSK_NULL_SHA384"}, + { {0xC0, 0x98}, + "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_RSA_PSK_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x99}, + "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_RSA_PSK_CAMELLIA_256_CBC_SHA384"}, + { {0x00, 0x8E}, + "TLS_DHE_PSK_WITH_RC4_128_SHA", + NULL, + "TLS_DHE_PSK_ARCFOUR_128_SHA1"}, + { {0x00, 0x8F}, + "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", + "DHE-PSK-3DES-EDE-CBC-SHA", + "TLS_DHE_PSK_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x90}, + "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", + "DHE-PSK-AES128-CBC-SHA", + "TLS_DHE_PSK_AES_128_CBC_SHA1"}, + { {0x00, 0x91}, + "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", + "DHE-PSK-AES256-CBC-SHA", + "TLS_DHE_PSK_AES_256_CBC_SHA1"}, + { {0x00, 0xB2}, + "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", + "DHE-PSK-AES128-CBC-SHA256", + "TLS_DHE_PSK_AES_128_CBC_SHA256"}, + { {0x00, 0xAA}, + "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", + "DHE-PSK-AES128-GCM-SHA256", + "TLS_DHE_PSK_AES_128_GCM_SHA256"}, + { {0x00, 0x2D}, + "TLS_DHE_PSK_WITH_", + NULL, + "TLS_DHE_PSK_NULL_SHA1"}, + { {0x00, 0xB4}, + "TLS_DHE_PSK_WITH_", + NULL, + "TLS_DHE_PSK_NULL_SHA256"}, + { {0x00, 0xB5}, + "TLS_DHE_PSK_WITH_", + NULL, + "TLS_DHE_PSK_NULL_SHA384"}, + { {0x00, 0xB3}, + "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", + "DHE-PSK-AES256-CBC-SHA384", + "TLS_DHE_PSK_AES_256_CBC_SHA384"}, + { {0x00, 0xAB}, + "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", + "DHE-PSK-AES256-GCM-SHA384", + "TLS_DHE_PSK_AES_256_GCM_SHA384"}, + { {0xC0, 0x96}, + "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_DHE_PSK_CAMELLIA_128_CBC_SHA256"}, + { {0xC0, 0x97}, + "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + NULL, + "TLS_DHE_PSK_CAMELLIA_256_CBC_SHA384"}, + { {0xC0, 0x90}, + "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_DHE_PSK_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x91}, + "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_DHE_PSK_CAMELLIA_256_GCM_SHA384"}, + { {0xC0, 0xA4}, + "TLS_PSK_WITH_AES_128_CCM", + NULL, + "TLS_PSK_AES_128_CCM"}, + { {0xC0, 0xA5}, + "TLS_PSK_WITH_AES_256_CCM", + NULL, + "TLS_PSK_AES_256_CCM"}, + { {0xC0, 0xA6}, + "TLS_DHE_PSK_WITH_AES_128_CCM", + NULL, + "TLS_DHE_PSK_AES_128_CCM"}, + { {0xC0, 0xA7}, + "TLS_DHE_PSK_WITH_AES_256_CCM", + NULL, + "TLS_DHE_PSK_AES_256_CCM"}, + { {0xC0, 0xA8}, + "TLS_PSK_WITH_AES_128_CCM_8", + NULL, + "TLS_PSK_AES_128_CCM_8"}, + { {0xC0, 0xA9}, + "TLS_PSK_WITH_AES_256_CCM_8", + NULL, + "TLS_PSK_AES_256_CCM_8"}, + { {0xC0, 0xAA}, + "TLS_PSK_DHE_WITH_AES_128_CCM_8", + NULL, + "TLS_DHE_PSK_AES_128_CCM_8"}, + { {0xC0, 0xAB}, + "TLS_PSK_DHE_WITH_AES_256_CCM_8", + NULL, + "TLS_DHE_PSK_AES_256_CCM_8"}, + { {0xCC, 0xAD}, + "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + "DHE-PSK-CHACHA20-POLY1305", + "TLS_DHE_PSK_CHACHA20_POLY1305"}, + { {0xCC, 0xAC}, + "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + "ECDHE-PSK-CHACHA20-POLY1305", + "TLS_ECDHE_PSK_CHACHA20_POLY1305"}, + { {0xCC, 0xAE}, + "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", + "RSA-PSK-CHACHA20-POLY1305", + "TLS_RSA_PSK_CHACHA20_POLY1305"}, + { {0xCC, 0xAB}, + "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", + "PSK-CHACHA20-POLY1305", + "TLS_PSK_CHACHA20_POLY1305"}, + { {0x00, 0x18}, + "TLS_DH_anon_WITH_RC4_128_MD5", + NULL, + "TLS_DH_ANON_ARCFOUR_128_MD5"}, + { {0x00, 0x1B}, + "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + NULL, + "TLS_DH_ANON_3DES_EDE_CBC_SHA1"}, + { {0x00, 0x34}, + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + NULL, + "TLS_DH_ANON_AES_128_CBC_SHA1"}, + { {0x00, 0x3A}, + "TLS_DH_anon_WITH_AES_256_CBC_SHA", + NULL, + "TLS_DH_ANON_AES_256_CBC_SHA1"}, + { {0x00, 0xBF}, + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + NULL, + "TLS_DH_ANON_CAMELLIA_128_CBC_SHA256"}, + { {0x00, 0xC5}, + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + NULL, + "TLS_DH_ANON_CAMELLIA_256_CBC_SHA256"}, + { {0x00, 0x46}, + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + NULL, + "TLS_DH_ANON_CAMELLIA_128_CBC_SHA1"}, + { {0x00, 0x89}, + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + NULL, + "TLS_DH_ANON_CAMELLIA_256_CBC_SHA1"}, + { {0x00, 0x6C}, + "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + NULL, + "TLS_DH_ANON_AES_128_CBC_SHA256"}, + { {0x00, 0x6D}, + "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + NULL, + "TLS_DH_ANON_AES_256_CBC_SHA256"}, + { {0x00, 0xA6}, + "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + NULL, + "TLS_DH_ANON_AES_128_GCM_SHA256"}, + { {0x00, 0xA7}, + "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + NULL, + "TLS_DH_ANON_AES_256_GCM_SHA384"}, + { {0xC0, 0x84}, + "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", + NULL, + "TLS_DH_ANON_CAMELLIA_128_GCM_SHA256"}, + { {0xC0, 0x85}, + "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", + NULL, + "TLS_DH_ANON_CAMELLIA_256_GCM_SHA384"}, + { {0xC0, 0x15}, + "TLS_ECDH_anon_WITH_", + NULL, + "TLS_ECDH_ANON_NULL_SHA1"}, + { {0xC0, 0x17}, + "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + NULL, + "TLS_ECDH_ANON_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x18}, + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + NULL, + "TLS_ECDH_ANON_AES_128_CBC_SHA1"}, + { {0xC0, 0x19}, + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + NULL, + "TLS_ECDH_ANON_AES_256_CBC_SHA1"}, + { {0xC0, 0x16}, + "TLS_ECDH_anon_WITH_RC4_128_SHA", + NULL, + "TLS_ECDH_ANON_ARCFOUR_128_SHA1"}, + { {0xC0, 0x1A}, + "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", + "SRP-3DES-EDE-CBC-SHA", + "TLS_SRP_SHA_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x1D}, + "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", + "SRP-AES-128-CBC-SHA", + "TLS_SRP_SHA_AES_128_CBC_SHA1"}, + { {0xC0, 0x20}, + "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", + "SRP-AES-256-CBC-SHA", + "TLS_SRP_SHA_AES_256_CBC_SHA1"}, + { {0xC0, 0x1C}, + "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", + NULL, + "TLS_SRP_SHA_DSS_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x1B}, + "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", + "SRP-RSA-3DES-EDE-CBC-SHA", + "TLS_SRP_SHA_RSA_3DES_EDE_CBC_SHA1"}, + { {0xC0, 0x1F}, + "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", + NULL, + "TLS_SRP_SHA_DSS_AES_128_CBC_SHA1"}, + { {0xC0, 0x1E}, + "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", + "SRP-RSA-AES-128-CBC-SHA", + "TLS_SRP_SHA_RSA_AES_128_CBC_SHA1"}, + { {0xC0, 0x22}, + "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", + NULL, + "TLS_SRP_SHA_DSS_AES_256_CBC_SHA1"}, + { {0xC0, 0x21}, + "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", + "SRP-RSA-AES-256-CBC-SHA", + "TLS_SRP_SHA_RSA_AES_256_CBC_SHA1"}, + { {0x00, 0x00}, + NULL, + NULL, + NULL} +}; + +/* map the gnutls cipher suite (defined by key exchange algorithm, cipher + and mac algorithm) to the corresponding OpenSSL cipher name */ +static const char *openssl_cipher_name(gnutls_kx_algorithm_t kx, + gnutls_cipher_algorithm_t cipher, + gnutls_mac_algorithm_t mac) +{ + unsigned int i=0; + const char *name= 0; + unsigned char sid[2]; + gnutls_kx_algorithm_t lkx; + gnutls_cipher_algorithm_t lcipher; + gnutls_mac_algorithm_t lmac; + + while ((name= gnutls_cipher_suite_info(i++, (unsigned char *)&sid, &lkx, &lcipher, &lmac, NULL))) + { + if (lkx == kx && + lcipher == cipher && + lmac == mac) + { + i=0; + while (tls_ciphers[i].iana_name) + { + if (!memcmp(tls_ciphers[i].sid, &sid, 2)) + { + if (tls_ciphers[i].openssl_name) + return tls_ciphers[i].openssl_name; + if (tls_ciphers[i].gnutls_name) + return tls_ciphers[i].gnutls_name; + return tls_ciphers[i].iana_name; + } + i++; + } + } + } + return NULL; +} + +/* get priority string for a given openssl cipher name */ +static char *get_priority(const char *cipher_name, char *priority, size_t len) +{ + unsigned int i= 0; + while (tls_ciphers[i].iana_name) + { + if (strcmp(tls_ciphers[i].iana_name, cipher_name) == 0 || + (tls_ciphers[i].openssl_name && + strcmp(tls_ciphers[i].openssl_name, cipher_name) == 0) || + (tls_ciphers[i].gnutls_name && + strcmp(tls_ciphers[i].gnutls_name, cipher_name) == 0)) + { + const char *name; + gnutls_kx_algorithm_t kx; + gnutls_cipher_algorithm_t cipher; + gnutls_mac_algorithm_t mac; + gnutls_protocol_t min_version; + unsigned j= 0; + + if (!tls_ciphers[i].gnutls_name) + return NULL; + + while ((name= gnutls_cipher_suite_info(j++, NULL, &kx, &cipher, + &mac, &min_version))) + { + if (!strcmp(name, tls_ciphers[i].gnutls_name)) + { + snprintf(priority, len - 1, ":+%s:+%s:+%s", + gnutls_cipher_get_name(cipher), + gnutls_mac_get_name(mac), + gnutls_kx_get_name(kx)); + return priority; + } + } + return NULL; + } + i++; + } + return NULL; +} + +#define MAX_SSL_ERR_LEN 100 + +static void ma_tls_set_error(MYSQL *mysql, void *ssl, int ssl_errno) +{ + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + MARIADB_PVIO *pvio= mysql->net.pvio; + int save_errno= errno; + + /* give a more descriptive error message for alerts */ + if (ssl_errno == GNUTLS_E_FATAL_ALERT_RECEIVED) + { + gnutls_alert_description_t alert_desc; + const char *alert_name; + alert_desc= gnutls_alert_get((gnutls_session_t)ssl); + alert_name= gnutls_alert_get_name(alert_desc); + snprintf(ssl_error, MAX_SSL_ERR_LEN, "fatal alert received: %s", + alert_name); + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, ssl_error); + return; + } + + if (ssl_errno && (ssl_error_reason= gnutls_strerror(ssl_errno))) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + ssl_error_reason); + return; + } + + strerror_r(save_errno, ssl_error, MAX_SSL_ERR_LEN); + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "TLS/SSL error: %s (%d)", + ssl_error, save_errno); +} + + +static void ma_tls_get_error(char *errmsg, size_t length, int ssl_errno) +{ + const char *ssl_error_reason; + + if (!ssl_errno) + { + strncpy(errmsg, "Unknown SSL error", length); + return; + } + if ((ssl_error_reason= gnutls_strerror(ssl_errno))) + { + strncpy(errmsg, ssl_error_reason, length); + return; + } + snprintf(errmsg, length, "SSL errno=%d", ssl_errno); +} + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + my_gnutls_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int ma_tls_start(char *errmsg, size_t errmsg_len) +{ + int rc= 0; + + if (ma_tls_initialized) + return 0; + + pthread_mutex_init(&LOCK_gnutls_config,NULL); + pthread_mutex_lock(&LOCK_gnutls_config); + + if ((rc= gnutls_global_init()) != GNUTLS_E_SUCCESS) + { + ma_tls_get_error(errmsg, errmsg_len, rc); + goto end; + } + snprintf(tls_library_version, TLS_VERSION_LENGTH - 1, "GnuTLS %s", + gnutls_check_version(NULL)); + + ma_tls_initialized= TRUE; +end: + pthread_mutex_unlock(&LOCK_gnutls_config); + return rc; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + my_gnutls_end() + void + + RETURN VALUES + void +*/ +void ma_tls_end() +{ + if (ma_tls_initialized) + { + pthread_mutex_lock(&LOCK_gnutls_config); + if (mariadb_deinitialize_ssl) + gnutls_global_deinit(); + ma_tls_initialized= FALSE; + pthread_mutex_unlock(&LOCK_gnutls_config); + pthread_mutex_destroy(&LOCK_gnutls_config); + } + return; +} + +static size_t ma_gnutls_get_protocol_version(const char *tls_version_option, + char *priority_string, + size_t prio_len) +{ + char tls_versions[128]; + + tls_versions[0]= 0; + if (!tls_version_option || !tls_version_option[0]) + goto end; + + + if (strstr(tls_version_option, "TLSv1.0")) + strcat(tls_versions, ":+VERS-TLS1.0"); + if (strstr(tls_version_option, "TLSv1.1")) + strcat(tls_versions, ":+VERS-TLS1.1"); + if (strstr(tls_version_option, "TLSv1.2")) + strcat(tls_versions, ":+VERS-TLS1.2"); +#if GNUTLS_VERSION_NUMBER > 0x030605 + if (strstr(tls_version_option, "TLSv1.3")) + strcat(tls_versions, ":+VERS-TLS1.3"); +#endif +end: + if (tls_versions[0]) + snprintf(priority_string, prio_len - 1, "-VERS-TLS-ALL%s:NORMAL", tls_versions); + else + strncpy(priority_string, "NORMAL:+VERS-ALL", prio_len - 1); + return strlen(priority_string); +} + +static int ma_gnutls_set_ciphers(gnutls_session_t ssl, + const char *cipher_str, + const char *tls_version) +{ + const char *err; + char *token; +#define PRIO_SIZE 1024 + char prio[PRIO_SIZE]; + + ma_gnutls_get_protocol_version(tls_version, prio, PRIO_SIZE); + + if (!cipher_str) + return gnutls_priority_set_direct(ssl, prio, &err); + + token= strtok((char *)cipher_str, ":"); + + strcpy(prio, "NONE:+VERS-TLS-ALL:+SIGN-ALL:+COMP-NULL:+CURVE-ALL"); + + while (token) + { + char priority[1024]; + char *p= get_priority(token, priority, 1024); + if (p) + strncat(prio, p, PRIO_SIZE - strlen(prio)); + token = strtok(NULL, ":"); + } + return gnutls_priority_set_direct(ssl, prio , &err); +} + +static int ma_tls_set_certs(MYSQL *mysql, + gnutls_certificate_credentials_t ctx) +{ + int ssl_error= 0; + + if (mysql->options.ssl_ca) + { + + ssl_error= gnutls_certificate_set_x509_trust_file(ctx, + mysql->options.ssl_ca, + GNUTLS_X509_FMT_PEM); + if (ssl_error < 0) + goto error; + } + + if (mysql->options.ssl_capath) + { + ssl_error= gnutls_certificate_set_x509_trust_dir(ctx, + mysql->options.ssl_capath, + GNUTLS_X509_FMT_PEM); + if (ssl_error < 0) + goto error; + } + + if (mysql->options.extension && mysql->options.extension->ssl_crl) + { + ssl_error= gnutls_certificate_set_x509_crl_file(ctx, + mysql->options.extension->ssl_crl, GNUTLS_X509_FMT_PEM); + if (ssl_error < 0) + goto error; + } + + if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) + { + ssl_error= gnutls_certificate_set_x509_system_trust(ctx); + if (ssl_error < 0) + goto error; + } + + gnutls_certificate_set_verify_function(ctx, + my_verify_callback); + + if (mysql->options.ssl_key || mysql->options.ssl_cert) + { + char *keyfile= mysql->options.ssl_key; + char *certfile= mysql->options.ssl_cert; + + if (!certfile) + certfile= keyfile; + else if (!keyfile) + keyfile= certfile; + + /* load cert/key into context */ + if ((ssl_error= gnutls_certificate_set_x509_key_file2(ctx, + certfile, keyfile, GNUTLS_X509_FMT_PEM, + mysql->options.extension ? mysql->options.extension->tls_pw : NULL, 0)) < 0) + goto error; + } + +error: + return ssl_error; +} + +void *ma_tls_init(MYSQL *mysql) +{ + gnutls_session_t ssl= NULL; + gnutls_certificate_credentials_t ctx; + int ssl_error= 0; + + pthread_mutex_lock(&LOCK_gnutls_config); + + if (gnutls_certificate_allocate_credentials(&ctx) != GNUTLS_E_SUCCESS) + goto error; + + if ((ssl_error= ma_tls_set_certs(mysql, ctx)) < 0) + goto error; + + if ((ssl_error = gnutls_init(&ssl, GNUTLS_CLIENT | GNUTLS_NONBLOCK | GNUTLS_NO_SIGNAL)) < 0) + goto error; + + gnutls_session_set_ptr(ssl, (void *)mysql); + /* + gnutls_certificate_set_retrieve_function2(GNUTLS_xcred, client_cert_callback); + */ + ssl_error= ma_gnutls_set_ciphers(ssl, mysql->options.ssl_cipher, mysql->options.extension ? mysql->options.extension->tls_version : NULL); + if (ssl_error < 0) + goto error; + + /* we don't load private key and cert by default - if the server requests + a client certificate we will send it via callback function */ + if ((ssl_error= gnutls_credentials_set(ssl, GNUTLS_CRD_CERTIFICATE, ctx)) < 0) + goto error; + + pthread_mutex_unlock(&LOCK_gnutls_config); + return (void *)ssl; +error: + ma_tls_set_error(mysql, ssl, ssl_error); + gnutls_certificate_free_credentials(ctx); + if (ssl) + gnutls_deinit(ssl); + pthread_mutex_unlock(&LOCK_gnutls_config); + return NULL; +} + +#ifdef GNUTLS_EXTERNAL_TRANSPORT +ssize_t ma_tls_push(gnutls_transport_ptr_t ptr, const void* data, size_t len) +{ + MARIADB_PVIO *pvio= (MARIADB_PVIO *)ptr; + ssize_t rc= pvio->methods->write(pvio, data, len); + return rc; +} + +ssize_t ma_tls_pull(gnutls_transport_ptr_t ptr, void* data, size_t len) +{ + MARIADB_PVIO *pvio= (MARIADB_PVIO *)ptr; + ssize_t rc= pvio->methods->read(pvio, data, len); + return rc; +} + +static int ma_tls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) +{ + MARIADB_PVIO *pvio= (MARIADB_PVIO *)ptr; + return pvio->methods->wait_io_or_timeout(pvio, 0, ms); +} +#endif + +my_bool ma_tls_connect(MARIADB_TLS *ctls) +{ + gnutls_session_t ssl = (gnutls_session_t)ctls->ssl; + my_bool blocking; + MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ssl); + MARIADB_PVIO *pvio; + int ret; + + if (!mysql) + return 1; + + pvio= mysql->net.pvio; + + /* Set socket to blocking if not already set */ + if (!(blocking= pvio->methods->is_blocking(pvio))) + pvio->methods->blocking(pvio, TRUE, 0); + + +#ifdef GNUTLS_EXTERNAL_TRANSPORT + /* we don't use GnuTLS read/write functions */ + gnutls_transport_set_ptr(ssl, pvio); + gnutls_transport_set_push_function(ssl, ma_tls_push); + gnutls_transport_set_pull_function(ssl, ma_tls_pull); + gnutls_transport_set_pull_timeout_function(ssl, ma_tls_pull_timeout); + gnutls_handshake_set_timeout(ssl, pvio->timeout[PVIO_CONNECT_TIMEOUT]); +#else + gnutls_transport_set_int(ssl, mysql_get_socket(mysql)); +#endif + + do { + ret = gnutls_handshake(ssl); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + { + /* If error message was not set while calling certification callback function, + use default error message (which is not very descriptive */ + if (!mysql_errno(mysql)) + ma_tls_set_error(mysql, ssl, ret); + + ma_tls_close(ctls); + + /* restore blocking mode */ + if (!blocking) + pvio->methods->blocking(pvio, FALSE, 0); + return 1; + } + ctls->ssl= (void *)ssl; + return 0; +} + +ssize_t ma_tls_write_async(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t res; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + MARIADB_TLS *ctls= pvio->ctls; + + for (;;) + { + b->events_to_wait_for= 0; + res= gnutls_record_send((gnutls_session_t)ctls->ssl, (void *)buffer, length); + if (res > 0) + return res; + if (res == GNUTLS_E_AGAIN) + b->events_to_wait_for|= MYSQL_WAIT_WRITE; + else + return res; + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + } +} + + +ssize_t ma_tls_read_async(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t res; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + MARIADB_TLS *ctls= pvio->ctls; + + for (;;) + { + b->events_to_wait_for= 0; + res= gnutls_record_recv((gnutls_session_t)ctls->ssl, (void *)buffer, length); + if (res > 0) + return res; + if (res == GNUTLS_E_AGAIN) + b->events_to_wait_for|= MYSQL_WAIT_READ; + else + return res; + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + } +} + +ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + ssize_t rc; + MARIADB_PVIO *pvio= ctls->pvio; + + while ((rc= gnutls_record_recv((gnutls_session_t)ctls->ssl, (void *)buffer, length)) <= 0) + { + if (rc != GNUTLS_E_AGAIN && rc != GNUTLS_E_INTERRUPTED) + break; + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.read_timeout) < 1) + break; + } + if (rc <= 0) { + MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ctls->ssl); + ma_tls_set_error(mysql, ctls->ssl, rc); + } + return rc; +} + +ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + ssize_t rc; + MARIADB_PVIO *pvio= ctls->pvio; + + while ((rc= gnutls_record_send((gnutls_session_t)ctls->ssl, (void *)buffer, length)) <= 0) + { + if (rc != GNUTLS_E_AGAIN && rc != GNUTLS_E_INTERRUPTED) + break; + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.write_timeout) < 1) + break; + } + if (rc <= 0) { + MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ctls->ssl); + ma_tls_set_error(mysql, ctls->ssl, rc); + } + return rc; +} + +my_bool ma_tls_close(MARIADB_TLS *ctls) +{ + if (ctls->ssl) + { + gnutls_certificate_credentials_t ctx; + /* this would be the correct way, however can't detect afterwards + if the socket is closed or not, so we don't send encrypted + finish alert. + rc= gnutls_bye((gnutls_session_t )ctls->ssl, GNUTLS_SHUT_WR); + */ + gnutls_credentials_get(ctls->ssl, GNUTLS_CRD_CERTIFICATE, (void **)&ctx); + gnutls_certificate_free_keys(ctx); + gnutls_certificate_free_cas(ctx); + gnutls_certificate_free_crls(ctx); + gnutls_certificate_free_ca_names(ctx); + gnutls_certificate_free_credentials(ctx); + gnutls_deinit((gnutls_session_t )ctls->ssl); + ctls->ssl= NULL; + } + return 0; +} + +int ma_tls_verify_server_cert(MARIADB_TLS *ctls __attribute__((unused))) +{ + /* server verification is already handled before during handshake */ + return 0; +} + +const char *ma_tls_get_cipher(MARIADB_TLS *ctls) +{ + gnutls_kx_algorithm_t kx; + gnutls_cipher_algorithm_t cipher; + gnutls_mac_algorithm_t mac; + + if (!ctls || !ctls->ssl) + return NULL; + + mac= gnutls_mac_get((gnutls_session_t)ctls->ssl); + cipher= gnutls_cipher_get((gnutls_session_t)ctls->ssl); + kx= gnutls_kx_get((gnutls_session_t)ctls->ssl); + return openssl_cipher_name(kx, cipher, mac); +} + +static int my_verify_callback(gnutls_session_t ssl) +{ + unsigned int status= 0; + MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ssl); + + CLEAR_CLIENT_ERROR(mysql); + + if ((mysql->options.extension->tls_verify_server_cert)) + { + const char *hostname= mysql->host; + + if (gnutls_certificate_verify_peers3 (ssl, hostname, &status) < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + } else { + if (gnutls_certificate_verify_peers2 (ssl, &status) < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + } + if (status & GNUTLS_CERT_INVALID) + { + gnutls_datum_t out; + int type; + /* accept self signed certificates if we don't have to verify server cert */ + if (!(mysql->options.extension->tls_verify_server_cert) && + (status & GNUTLS_CERT_SIGNER_NOT_FOUND)) + return 0; + + /* gnutls default error message "certificate validation failed" isn't very + descriptive, so we provide more information about the error here */ + type= gnutls_certificate_type_get(ssl); + gnutls_certificate_verification_status_print(status, type, &out, 0); + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), out.data); + gnutls_free(out.data); + + return GNUTLS_E_CERTIFICATE_ERROR; + } + + + return 0; +} + +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +{ + MYSQL *mysql; + size_t fp_len= len; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + + if (!ctls || !ctls->ssl) + return 0; + + mysql= (MYSQL *)gnutls_session_get_ptr(ctls->ssl); + + cert_list = gnutls_certificate_get_peers (ctls->ssl, &cert_list_size); + if (cert_list == NULL) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + return 0; + } + + if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &cert_list[0], fp, &fp_len) == 0) + return fp_len; + else + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Finger print buffer too small"); + return 0; + } +} + +int ma_tls_get_protocol_version(MARIADB_TLS *ctls) +{ + if (!ctls || !ctls->ssl) + return 1; + + return gnutls_protocol_get_version(ctls->ssl) - 1; +} + +void ma_tls_set_connection(MYSQL *mysql) +{ + (void)gnutls_session_set_ptr(mysql->net.pvio->ctls->ssl, (void *)mysql); +} +#endif /* HAVE_GNUTLS */ diff --git a/libmariadb/libmariadb/secure/gnutls_crypt.c b/libmariadb/libmariadb/secure/gnutls_crypt.c new file mode 100644 index 00000000..a669e88e --- /dev/null +++ b/libmariadb/libmariadb/secure/gnutls_crypt.c @@ -0,0 +1,77 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include <ma_crypt.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static gnutls_digest_algorithm_t ma_hash_get_algorithm(unsigned int alg) +{ + switch(alg) + { + case MA_HASH_MD5: + return GNUTLS_DIG_MD5; + case MA_HASH_SHA1: + return GNUTLS_DIG_SHA1; + case MA_HASH_SHA256: + return GNUTLS_DIG_SHA256; + case MA_HASH_SHA384: + return GNUTLS_DIG_SHA384; + case MA_HASH_SHA512: + return GNUTLS_DIG_SHA512; + case MA_HASH_RIPEMD160: + return GNUTLS_DIG_RMD160; + default: + return GNUTLS_DIG_UNKNOWN; + } +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm) +{ + gnutls_hash_hd_t ctx= NULL; + gnutls_digest_algorithm_t hash_alg= ma_hash_get_algorithm(algorithm); + + /* unknown or unsupported hash algorithm */ + if (hash_alg == GNUTLS_DIG_UNKNOWN) + return NULL; + + if (gnutls_hash_init(&ctx, hash_alg) < 0) + return NULL; + + return (MA_HASH_CTX *)ctx; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + if (ctx) + gnutls_hash_deinit((gnutls_hash_hd_t)ctx, NULL); +} + +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len) +{ + gnutls_hash((gnutls_hash_hd_t)ctx, (const void *)buffer, len); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + gnutls_hash_output((gnutls_hash_hd_t)ctx, digest); +} + + diff --git a/libmariadb/libmariadb/secure/ma_schannel.c b/libmariadb/libmariadb/secure/ma_schannel.c new file mode 100644 index 00000000..be4148c7 --- /dev/null +++ b/libmariadb/libmariadb/secure/ma_schannel.c @@ -0,0 +1,629 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Author: Georg Richter + + *************************************************************************************/ +#include "ma_schannel.h" +#include "schannel_certs.h" +#include <assert.h> + +#define SC_IO_BUFFER_SIZE 0x4000 +#define MAX_SSL_ERR_LEN 100 + +#define SCHANNEL_PAYLOAD(A) ((A).cbMaximumMessage + (A).cbHeader + (A).cbTrailer) +void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo); + + + + +/* {{{ void ma_schannel_set_sec_error */ +void ma_schannel_set_sec_error(MARIADB_PVIO* pvio, DWORD ErrorNo) +{ + MYSQL* mysql = pvio->mysql; + if (ErrorNo != SEC_E_OK) + mysql->net.extension->extended_errno = ErrorNo; + if (ErrorNo == SEC_E_INTERNAL_ERROR && GetLastError()) + { + ma_schannel_set_win_error(pvio, GetLastError()); + return; + } + ma_schannel_set_win_error(pvio, ErrorNo); +} +/* }}} */ + +#include "win32_errmsg.h" +/* {{{ void ma_schnnel_set_win_error */ +void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo) +{ + char buffer[256]; + ma_format_win32_error(buffer, sizeof(buffer), ErrorNo, "TLS/SSL error: "); + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, buffer); + return; +} +/* }}} */ + + +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) */ +/* + perform handshake loop + + SYNOPSIS + ma_schannel_handshake_loop() + pvio Pointer to an Communication/IO structure + InitialRead TRUE if it's the very first read + ExtraData Pointer to an SecBuffer which contains extra data (sent by application) + + +*/ + +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) +{ + SecBufferDesc OutBuffer, InBuffer; + SecBuffer InBuffers[2], OutBuffers; + DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer; + TimeStamp tsExpiry; + SECURITY_STATUS rc; + PUCHAR IoBuffer; + BOOL fDoRead; + MARIADB_TLS *ctls= pvio->ctls; + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + + /* Allocate data buffer */ + if (!(IoBuffer = malloc(SC_IO_BUFFER_SIZE))) + return SEC_E_INSUFFICIENT_MEMORY; + + cbIoBuffer = 0; + fDoRead = InitialRead; + + /* handshake loop: We will leave if handshake is finished + or an error occurs */ + + rc = SEC_I_CONTINUE_NEEDED; + + while (rc == SEC_I_CONTINUE_NEEDED || + rc == SEC_E_INCOMPLETE_MESSAGE || + rc == SEC_I_INCOMPLETE_CREDENTIALS ) + { + /* Read data */ + if (rc == SEC_E_INCOMPLETE_MESSAGE || + !cbIoBuffer) + { + if(fDoRead) + { + ssize_t nbytes = pvio->methods->read(pvio, IoBuffer + cbIoBuffer, (size_t)(SC_IO_BUFFER_SIZE - cbIoBuffer)); + if (nbytes <= 0) + { + rc = SEC_E_INTERNAL_ERROR; + break; + } + cbData = (DWORD)nbytes; + cbIoBuffer += cbData; + } + else + fDoRead = TRUE; + } + + /* input buffers + First buffer stores data received from server. leftover data + will be stored in second buffer with BufferType SECBUFFER_EXTRA */ + + InBuffers[0].pvBuffer = IoBuffer; + InBuffers[0].cbBuffer = cbIoBuffer; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + InBuffers[1].pvBuffer = NULL; + InBuffers[1].cbBuffer = 0; + InBuffers[1].BufferType = SECBUFFER_EMPTY; + + InBuffer.cBuffers = 2; + InBuffer.pBuffers = InBuffers; + InBuffer.ulVersion = SECBUFFER_VERSION; + + + /* output buffer */ + OutBuffers.pvBuffer = NULL; + OutBuffers.BufferType= SECBUFFER_TOKEN; + OutBuffers.cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = &OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + + rc = InitializeSecurityContextA(&sctx->CredHdl, + &sctx->hCtxt, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + &InBuffer, + 0, + NULL, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry ); + + + if (rc == SEC_E_OK || + rc == SEC_I_CONTINUE_NEEDED || + (FAILED(rc) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))) + { + if(OutBuffers.cbBuffer && OutBuffers.pvBuffer) + { + ssize_t nbytes = pvio->methods->write(pvio, (uchar *)OutBuffers.pvBuffer, (size_t)OutBuffers.cbBuffer); + if(nbytes <= 0) + { + FreeContextBuffer(OutBuffers.pvBuffer); + DeleteSecurityContext(&sctx->hCtxt); + return SEC_E_INTERNAL_ERROR; + } + cbData= (DWORD)nbytes; + /* Free output context buffer */ + FreeContextBuffer(OutBuffers.pvBuffer); + OutBuffers.pvBuffer = NULL; + } + } + /* check if we need to read more data */ + switch (rc) { + case SEC_E_INCOMPLETE_MESSAGE: + /* we didn't receive all data, so just continue loop */ + continue; + break; + case SEC_E_OK: + /* handshake completed, but we need to check if extra + data was sent (which contains encrypted application data) */ + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + if (!(pExtraData->pvBuffer= LocalAlloc(0, InBuffers[1].cbBuffer))) + return SEC_E_INSUFFICIENT_MEMORY; + + MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + pExtraData->BufferType = SECBUFFER_TOKEN; + pExtraData->cbBuffer = InBuffers[1].cbBuffer; + } + else + { + pExtraData->BufferType= SECBUFFER_EMPTY; + pExtraData->pvBuffer= NULL; + pExtraData->cbBuffer= 0; + } + break; + + case SEC_I_INCOMPLETE_CREDENTIALS: + /* Provided credentials didn't contain a valid client certificate. + We will try to connect anonymously, using current credentials */ + fDoRead= FALSE; + rc= SEC_I_CONTINUE_NEEDED; + continue; + break; + default: + if (FAILED(rc)) + { + goto loopend; + } + break; + } + + if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) + { + MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + cbIoBuffer = InBuffers[1].cbBuffer; + } + else + cbIoBuffer = 0; + } +loopend: + if (FAILED(rc)) + { + ma_schannel_set_sec_error(pvio, rc); + DeleteSecurityContext(&sctx->hCtxt); + } + free(IoBuffer); + + return rc; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) */ +/* + performs client side handshake + + SYNOPSIS + ma_schannel_client_handshake() + ctls Pointer to a MARIADB_TLS structure + + DESCRIPTION + initiates a client/server handshake. This function can be used + by clients only + + RETURN + SEC_E_OK on success +*/ + +SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) +{ + MARIADB_PVIO *pvio; + SECURITY_STATUS sRet; + DWORD OutFlags; + SC_CTX *sctx; + SecBuffer ExtraData; + DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | + ISC_REQ_USE_SUPPLIED_CREDS | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; + + SecBufferDesc BufferOut; + SecBuffer BuffersOut; + + if (!ctls || !ctls->pvio) + return 1; + + pvio= ctls->pvio; + sctx= (SC_CTX *)ctls->ssl; + + /* Initialie securifty context */ + BuffersOut.BufferType= SECBUFFER_TOKEN; + BuffersOut.cbBuffer= 0; + BuffersOut.pvBuffer= NULL; + + + BufferOut.cBuffers= 1; + BufferOut.pBuffers= &BuffersOut; + BufferOut.ulVersion= SECBUFFER_VERSION; + + sRet = InitializeSecurityContext(&sctx->CredHdl, + NULL, + pvio->mysql->host, + SFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &sctx->hCtxt, + &BufferOut, + &OutFlags, + NULL); + + if(sRet != SEC_I_CONTINUE_NEEDED) + { + ma_schannel_set_sec_error(pvio, sRet); + return sRet; + } + + /* send client hello */ + if(BuffersOut.cbBuffer != 0 && BuffersOut.pvBuffer != NULL) + { + ssize_t nbytes = (DWORD)pvio->methods->write(pvio, (uchar *)BuffersOut.pvBuffer, (size_t)BuffersOut.cbBuffer); + + if (nbytes <= 0) + { + sRet= SEC_E_INTERNAL_ERROR; + goto end; + } + } + sRet= ma_schannel_handshake_loop(pvio, TRUE, &ExtraData); + + /* allocate IO-Buffer for write operations: After handshake + was successful, we are able now to calculate payload */ + if ((sRet = QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ))) + goto end; + + sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); + if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, sctx->IoBufferSize))) + { + sRet= SEC_E_INSUFFICIENT_MEMORY; + goto end; + } + + return sRet; +end: + if (BuffersOut.pvBuffer) + FreeContextBuffer(BuffersOut.pvBuffer); + return sRet; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext, + DWORD DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) */ +/* + Reads encrypted data from a SSL stream and decrypts it. + + SYNOPSIS + ma_schannel_read + pvio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + + DESCRIPTION + Reads decrypted data from a SSL stream and encrypts it. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occurred +*/ + +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize) +{ + ssize_t nbytes = 0; + DWORD dwOffset = 0; + SC_CTX *sctx; + SECURITY_STATUS sRet = 0; + SecBufferDesc Msg; + SecBuffer Buffers[4]; + int i; + + if (!pvio || !pvio->methods || !pvio->methods->read || !pvio->ctls || !DecryptLength) + return SEC_E_INTERNAL_ERROR; + + sctx = (SC_CTX *)pvio->ctls->ssl; + *DecryptLength = 0; + + if (sctx->dataBuf.cbBuffer) + { + /* Have unread decrypted data from the last time, copy. */ + nbytes = MIN(ReadBufferSize, sctx->dataBuf.cbBuffer); + memcpy(ReadBuffer, sctx->dataBuf.pvBuffer, nbytes); + sctx->dataBuf.pvBuffer = (char *)(sctx->dataBuf.pvBuffer) + nbytes; + sctx->dataBuf.cbBuffer -= (DWORD)nbytes; + *DecryptLength = (DWORD)nbytes; + return SEC_E_OK; + } + + + while (1) + { + /* Check for any encrypted data returned by last DecryptMessage() in SECBUFFER_EXTRA buffer. */ + if (sctx->extraBuf.cbBuffer) + { + memmove(sctx->IoBuffer, sctx->extraBuf.pvBuffer, sctx->extraBuf.cbBuffer); + dwOffset = sctx->extraBuf.cbBuffer; + sctx->extraBuf.cbBuffer = 0; + } + + do { + assert(sctx->IoBufferSize > dwOffset); + if (dwOffset == 0 || sRet == SEC_E_INCOMPLETE_MESSAGE) + { + nbytes = pvio->methods->read(pvio, sctx->IoBuffer + dwOffset, (size_t)(sctx->IoBufferSize - dwOffset)); + if (nbytes <= 0) + { + /* server closed connection, or an error */ + // todo: error + return SEC_E_INVALID_HANDLE; + } + dwOffset += (DWORD)nbytes; + } + ZeroMemory(Buffers, sizeof(SecBuffer) * 4); + Buffers[0].pvBuffer = sctx->IoBuffer; + Buffers[0].cbBuffer = dwOffset; + + Buffers[0].BufferType = SECBUFFER_DATA; + Buffers[1].BufferType = SECBUFFER_EMPTY; + Buffers[2].BufferType = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Msg.ulVersion = SECBUFFER_VERSION; // Version number + Msg.cBuffers = 4; + Msg.pBuffers = Buffers; + + sRet = DecryptMessage(phContext, &Msg, 0, NULL); + + } while (sRet == SEC_E_INCOMPLETE_MESSAGE); /* Continue reading until full message arrives */ + + + if (sRet != SEC_E_OK) + { + ma_schannel_set_sec_error(pvio, sRet); + return sRet; + } + + sctx->extraBuf.cbBuffer = 0; + sctx->dataBuf.cbBuffer = 0; + for (i = 0; i < 4; i++) + { + if (Buffers[i].BufferType == SECBUFFER_DATA) + sctx->dataBuf = Buffers[i]; + if (Buffers[i].BufferType == SECBUFFER_EXTRA) + sctx->extraBuf = Buffers[i]; + } + + + if (sctx->dataBuf.cbBuffer) + { + assert(sctx->dataBuf.pvBuffer); + /* + Copy at most ReadBufferSize bytes to output. + Store the rest (if any) to be processed next time. + */ + nbytes = MIN(sctx->dataBuf.cbBuffer, ReadBufferSize); + memcpy((char *)ReadBuffer, sctx->dataBuf.pvBuffer, nbytes); + sctx->dataBuf.cbBuffer -= (unsigned long)nbytes; + sctx->dataBuf.pvBuffer = (char *)sctx->dataBuf.pvBuffer + nbytes; + + *DecryptLength = (DWORD)nbytes; + return SEC_E_OK; + } + // No data buffer, loop + } +} +/* }}} */ +#include "win32_errmsg.h" +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name) +{ + SECURITY_STATUS status; + + MARIADB_PVIO *pvio= ctls->pvio; + MYSQL *mysql= pvio->mysql; + SC_CTX *sctx = (SC_CTX *)ctls->ssl; + const char *ca_file= mysql->options.ssl_ca; + const char* ca_path = mysql->options.ssl_capath; + const char *crl_file= mysql->options.extension ? mysql->options.extension->ssl_crl : NULL; + const char* crl_path = mysql->options.extension ? mysql->options.extension->ssl_crlpath : NULL; + PCCERT_CONTEXT pServerCert= NULL; + char errmsg[256]; + HCERTSTORE store= NULL; + int ret= 0; + + status = schannel_create_store(ca_file, ca_path, crl_file, crl_path, &store, errmsg, sizeof(errmsg)); + if(status) + goto end; + + status = QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert); + if (status) + { + ma_format_win32_error(errmsg, sizeof(errmsg), GetLastError(), + "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed."); + goto end; + } + + status = schannel_verify_server_certificate( + pServerCert, + store, + crl_file != 0 || crl_path != 0, + mysql->host, + verify_server_name, + errmsg, sizeof(errmsg)); + + if (status) + goto end; + + ret= 1; + +end: + if (!ret) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg); + } + if (pServerCert) + CertFreeCertificateContext(pServerCert); + if(store) + schannel_free_store(store); + return ret; +} + + +/* {{{ size_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* + Decrypts data and write to SSL stream + SYNOPSIS + ma_schannel_write_decrypt + pvio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + DESCRIPTION + Write encrypted data to SSL stream. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occurred +*/ +ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, + uchar *WriteBuffer, + size_t WriteBufferSize) +{ + SECURITY_STATUS scRet; + SecBufferDesc Message; + SecBuffer Buffers[4]; + SC_CTX *sctx= (SC_CTX *)pvio->ctls->ssl; + size_t payload; + ssize_t nbytes; + DWORD write_size; + + payload= MIN(WriteBufferSize, sctx->Sizes.cbMaximumMessage); + + memcpy(&sctx->IoBuffer[sctx->Sizes.cbHeader], WriteBuffer, payload); + + Buffers[0].pvBuffer = sctx->IoBuffer; + Buffers[0].cbBuffer = sctx->Sizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer + + Buffers[1].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader]; + Buffers[1].cbBuffer = (DWORD)payload; + Buffers[1].BufferType = SECBUFFER_DATA; + + Buffers[2].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader] + payload; + Buffers[2].cbBuffer = sctx->Sizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + Buffers[3].pvBuffer = SECBUFFER_EMPTY; + Buffers[3].cbBuffer = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + if ((scRet = EncryptMessage(&sctx->hCtxt, 0, &Message, 0))!= SEC_E_OK) + return -1; + write_size = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer; + nbytes = pvio->methods->write(pvio, sctx->IoBuffer, write_size); + return nbytes == write_size ? payload : -1; +} +/* }}} */ + +extern char *ssl_protocol_version[5]; + +/* {{{ ma_tls_get_protocol_version(MARIADB_TLS *ctls) */ +int ma_tls_get_protocol_version(MARIADB_TLS *ctls) +{ + SC_CTX *sctx; + SecPkgContext_ConnectionInfo ConnectionInfo; + if (!ctls->ssl) + return 1; + + sctx= (SC_CTX *)ctls->ssl; + + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_CONNECTION_INFO, &ConnectionInfo) != SEC_E_OK) + return -1; + + switch(ConnectionInfo.dwProtocol) + { + case SP_PROT_SSL3_CLIENT: + return PROTOCOL_SSLV3; + case SP_PROT_TLS1_CLIENT: + return PROTOCOL_TLS_1_0; + case SP_PROT_TLS1_1_CLIENT: + return PROTOCOL_TLS_1_1; + case SP_PROT_TLS1_2_CLIENT: + return PROTOCOL_TLS_1_2; + default: + return -1; + } +} +/* }}} */ diff --git a/libmariadb/libmariadb/secure/ma_schannel.h b/libmariadb/libmariadb/secure/ma_schannel.h new file mode 100644 index 00000000..af7fc602 --- /dev/null +++ b/libmariadb/libmariadb/secure/ma_schannel.h @@ -0,0 +1,87 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Author: Georg Richter + + *************************************************************************************/ +#ifndef _ma_schannel_h_ +#define _ma_schannel_h_ + +#define SECURITY_WIN32 +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <ma_pvio.h> +#include <errmsg.h> + + +#include <wincrypt.h> +#include <wintrust.h> + + +#include <security.h> + +#include <schnlsp.h> +#undef SECURITY_WIN32 +#include <windows.h> +#include <sspi.h> + +#define SC_IO_BUFFER_SIZE 0x4000 + + +#include <ma_pthread.h> + +struct st_DER { + char* der_buffer; + DWORD der_length; +}; + +struct st_schannel { + CredHandle CredHdl; + PUCHAR IoBuffer; + DWORD IoBufferSize; + SecPkgContext_StreamSizes Sizes; + CtxtHandle hCtxt; + + /* Cached data from the last read/decrypt call.*/ + SecBuffer extraBuf; /* encrypted data read from server. */ + SecBuffer dataBuf; /* decrypted but still unread data from server.*/ + +}; + +typedef struct st_schannel SC_CTX; + +extern HCERTSTORE ca_CertStore, crl_CertStore; +extern my_bool ca_Check, crl_Check; + +; +SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls); +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData); + +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name); +ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, + uchar *WriteBuffer, + size_t WriteBufferSize); +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, + CtxtHandle* phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize); + + +#endif /* _ma_schannel_h_ */ diff --git a/libmariadb/libmariadb/secure/openssl.c b/libmariadb/libmariadb/secure/openssl.c new file mode 100644 index 00000000..2a272504 --- /dev/null +++ b/libmariadb/libmariadb/secure/openssl.c @@ -0,0 +1,786 @@ +/************************************************************************************ + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#include <ma_global.h> +#include <ma_sys.h> +#include <ma_common.h> +#include <ma_pvio.h> +#include <errmsg.h> +#include <string.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <openssl/ssl.h> /* SSL and SSL_CTX */ +#include <openssl/err.h> /* error reporting */ +#include <openssl/conf.h> +#include <openssl/md4.h> + +#if defined(_WIN32) && !defined(_OPENSSL_Applink) && defined(HAVE_OPENSSL_APPLINK_C) +#include <openssl/applink.c> +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) +#include <openssl/x509v3.h> +#define HAVE_OPENSSL_CHECK_HOST 1 +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#define HAVE_OPENSSL_1_1_API +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10000000L +#define SSL_OP_NO_TLSv1_1 0L +#define SSL_OP_NO_TLSv1_2 0L +#define CRYPTO_THREADID_set_callback CRYPTO_set_id_callback +#define CRYPTO_THREADID_get_callback CRYPTO_get_id_callback +#endif + +#if defined(OPENSSL_USE_BIOMETHOD) +#undef OPENSSL_USE_BIOMETHOD +#endif +#ifndef HAVE_OPENSSL_DEFAULT +#include <memory.h> +#define ma_malloc(A,B) malloc((A)) +#undef ma_free +#define ma_free(A) free((A)) +#define ma_snprintf snprintf +#define ma_vsnprintf vsnprintf +#undef SAFE_MUTEX +#endif +#include <ma_pthread.h> + +#include <mariadb_async.h> +#include <ma_context.h> + +extern my_bool ma_tls_initialized; +extern unsigned int mariadb_deinitialize_ssl; + +#define MAX_SSL_ERR_LEN 100 +char tls_library_version[TLS_VERSION_LENGTH]; + +static pthread_mutex_t LOCK_openssl_config; +#ifndef HAVE_OPENSSL_1_1_API +static pthread_mutex_t *LOCK_crypto= NULL; +#endif +#if defined(OPENSSL_USE_BIOMETHOD) +static int ma_bio_read(BIO *h, char *buf, int size); +static int ma_bio_write(BIO *h, const char *buf, int size); +static BIO_METHOD ma_BIO_method; +#endif + + +static long ma_tls_version_options(const char *version) +{ + long protocol_options, + disable_all_protocols; + + protocol_options= disable_all_protocols= + SSL_OP_NO_SSLv2 | + SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | + SSL_OP_NO_TLSv1_1 | + SSL_OP_NO_TLSv1_2 +#ifdef TLS1_3_VERSION + | SSL_OP_NO_TLSv1_3 +#endif + ; + + if (!version) + return 0; + + if (strstr(version, "TLSv1.0")) + protocol_options&= ~SSL_OP_NO_TLSv1; + if (strstr(version, "TLSv1.1")) + protocol_options&= ~SSL_OP_NO_TLSv1_1; + if (strstr(version, "TLSv1.2")) + protocol_options&= ~SSL_OP_NO_TLSv1_2; +#ifdef TLS1_3_VERSION + if (strstr(version, "TLSv1.3")) + protocol_options&= ~SSL_OP_NO_TLSv1_3; +#endif + + if (protocol_options != disable_all_protocols) + return protocol_options; + return 0; +} + +static void ma_tls_set_error(MYSQL *mysql) +{ + ulong ssl_errno= ERR_get_error(); + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + MARIADB_PVIO *pvio= mysql->net.pvio; + int save_errno= errno; + + if (ssl_errno && (ssl_error_reason= ERR_reason_error_string(ssl_errno))) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + 0, ssl_error_reason); + return; + } else if (!save_errno) { + pvio->set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, + ER(CR_SERVER_LOST)); + return; + } + + strerror_r(save_errno, ssl_error, MAX_SSL_ERR_LEN); + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "TLS/SSL error: %s (%d)", + ssl_error, save_errno); + return; +} + +#ifndef HAVE_OPENSSL_1_1_API +static void my_cb_locking(int mode, int n, + const char *file __attribute__((unused)), + int line __attribute__((unused))) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&LOCK_crypto[n]); + else + pthread_mutex_unlock(&LOCK_crypto[n]); +} + +static int ssl_thread_init() +{ + if (LOCK_crypto == NULL) + { + int i, max= CRYPTO_num_locks(); + + if (!(LOCK_crypto= + (pthread_mutex_t *)ma_malloc(sizeof(pthread_mutex_t) * max, MYF(0)))) + return 1; + + for (i=0; i < max; i++) + pthread_mutex_init(&LOCK_crypto[i], NULL); + + CRYPTO_set_locking_callback(my_cb_locking); + } + return 0; +} +#endif + +#if defined(_WIN32) || !defined(DISABLE_SIGPIPE) +#define disable_sigpipe() +#else +#include <signal.h> +static void ma_sigpipe_handler() +{ +} + +static void disable_sigpipe() +{ + struct sigaction old_handler, new_handler={NULL}; + if (!sigaction (SIGPIPE, NULL, &old_handler) && + !old_handler.sa_handler) + { + new_handler.sa_handler= ma_sigpipe_handler; + new_handler.sa_flags= 0; + if (!sigemptyset(&new_handler.sa_mask)) + sigaction(SIGPIPE, &new_handler, NULL); + } +} +#endif + +/* + Initializes SSL + + SYNOPSIS + my_ssl_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int ma_tls_start(char *errmsg __attribute__((unused)), size_t errmsg_len __attribute__((unused))) +{ + int rc= 1; + char *p; + if (ma_tls_initialized) + return 0; + + /* lock mutex to prevent multiple initialization */ + pthread_mutex_init(&LOCK_openssl_config, NULL); + pthread_mutex_lock(&LOCK_openssl_config); +#ifdef HAVE_OPENSSL_1_1_API + if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL)) + goto end; +#else + if (ssl_thread_init()) + { + strncpy(errmsg, "Not enough memory", errmsg_len); + goto end; + } + SSL_library_init(); +#if SSLEAY_VERSION_NUMBER >= 0x00907000L + OPENSSL_config(NULL); +#endif +#endif +#ifndef HAVE_OPENSSL_1_1_API + /* load errors */ + SSL_load_error_strings(); + /* digests and ciphers */ + OpenSSL_add_all_algorithms(); +#endif + disable_sigpipe(); +#ifdef OPENSSL_USE_BIOMETHOD + memcpy(&ma_BIO_method, BIO_s_socket(), sizeof(BIO_METHOD)); + ma_BIO_method.bread= ma_bio_read; + ma_BIO_method.bwrite= ma_bio_write; +#endif + snprintf(tls_library_version, TLS_VERSION_LENGTH - 1, "%s", +#if defined(LIBRESSL_VERSION_NUMBER) || !defined(HAVE_OPENSSL_1_1_API) + SSLeay_version(SSLEAY_VERSION)); +#else + OpenSSL_version(OPENSSL_VERSION)); +#endif + /* remove date from version */ + if ((p= strstr(tls_library_version, " "))) + *p= 0; + rc= 0; + ma_tls_initialized= TRUE; +end: + pthread_mutex_unlock(&LOCK_openssl_config); + return rc; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + my_ssl_end() + void + + RETURN VALUES + void +*/ +void ma_tls_end() +{ + if (ma_tls_initialized) + { + pthread_mutex_lock(&LOCK_openssl_config); +#ifndef HAVE_OPENSSL_1_1_API + if (LOCK_crypto) + { + int i; + CRYPTO_set_locking_callback(NULL); + CRYPTO_THREADID_set_callback(NULL); + + for (i=0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&LOCK_crypto[i]); + ma_free((gptr)LOCK_crypto); + LOCK_crypto= NULL; + } +#endif + if (mariadb_deinitialize_ssl) + { +#ifndef HAVE_OPENSSL_1_1_API + ERR_remove_thread_state(NULL); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + CONF_modules_free(); + CONF_modules_unload(1); +#endif + } + ma_tls_initialized= FALSE; + pthread_mutex_unlock(&LOCK_openssl_config); + pthread_mutex_destroy(&LOCK_openssl_config); + } + return; +} + +int ma_tls_get_password(char *buf, int size, + int rwflag __attribute__((unused)), + void *userdata) +{ + memset(buf, 0, size); + if (userdata) + strncpy(buf, (char *)userdata, size); + return (int)strlen(buf); +} + + +static int ma_tls_set_certs(MYSQL *mysql, SSL_CTX *ctx) +{ + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key; + char *pw= (mysql->options.extension) ? + mysql->options.extension->tls_pw : NULL; + + /* add cipher */ + if ((mysql->options.ssl_cipher && + mysql->options.ssl_cipher[0] != 0)) + { + if( +#ifdef TLS1_3_VERSION + SSL_CTX_set_ciphersuites(ctx, mysql->options.ssl_cipher) == 0 && +#endif + SSL_CTX_set_cipher_list(ctx, mysql->options.ssl_cipher) == 0) + goto error; + } + + /* ca_file and ca_path */ + if (!SSL_CTX_load_verify_locations(ctx, + mysql->options.ssl_ca, + mysql->options.ssl_capath)) + { + if (mysql->options.ssl_ca || mysql->options.ssl_capath) + goto error; + if (SSL_CTX_set_default_verify_paths(ctx) == 0) + goto error; + } + + if (mysql->options.extension && + (mysql->options.extension->ssl_crl || mysql->options.extension->ssl_crlpath)) + { + X509_STORE *certstore; + + if ((certstore= SSL_CTX_get_cert_store(ctx))) + { + if (X509_STORE_load_locations(certstore, mysql->options.extension->ssl_crl, + mysql->options.extension->ssl_crlpath) == 0) + goto error; + + if (X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0) + goto error; + } + } + + if (keyfile && !certfile) + certfile= keyfile; + if (certfile && !keyfile) + keyfile= certfile; + + /* set cert */ + if (certfile && certfile[0] != 0) + { + if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) + { + goto error; + } + } + + if (keyfile && keyfile[0]) + { + FILE *fp; + if ((fp= fopen(keyfile, "rb"))) + { + EVP_PKEY *key= EVP_PKEY_new(); + PEM_read_PrivateKey(fp, &key, NULL, pw); + fclose(fp); + if (SSL_CTX_use_PrivateKey(ctx, key) != 1) + { + unsigned long err= ERR_peek_error(); + EVP_PKEY_free(key); + if (!(ERR_GET_LIB(err) == ERR_LIB_X509 && + ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) + goto error; + } + EVP_PKEY_free(key); + } else { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + CER(CR_FILE_NOT_FOUND), keyfile); + return 1; + } + } + /* verify key */ + if (certfile && SSL_CTX_check_private_key(ctx) != 1) + goto error; + + SSL_CTX_set_verify(ctx, (mysql->options.ssl_ca || mysql->options.ssl_capath) ? + SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + return 0; + +error: + ma_tls_set_error(mysql); + return 1; +} + +void *ma_tls_init(MYSQL *mysql) +{ + SSL *ssl= NULL; + SSL_CTX *ctx= NULL; + long default_options= SSL_OP_ALL | + SSL_OP_NO_SSLv2 | + SSL_OP_NO_SSLv3; + long options= 0; + pthread_mutex_lock(&LOCK_openssl_config); + + #if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (!(ctx= SSL_CTX_new(TLS_client_method()))) +#else + if (!(ctx= SSL_CTX_new(SSLv23_client_method()))) +#endif + goto error; + if (mysql->options.extension) + options= ma_tls_version_options(mysql->options.extension->tls_version); + SSL_CTX_set_options(ctx, options ? options : default_options); + + if (ma_tls_set_certs(mysql, ctx)) + { + goto error; + } + + if (!(ssl= SSL_new(ctx))) + goto error; + + if (!SSL_set_app_data(ssl, mysql)) + goto error; + + pthread_mutex_unlock(&LOCK_openssl_config); + return (void *)ssl; +error: + pthread_mutex_unlock(&LOCK_openssl_config); + if (ctx) + SSL_CTX_free(ctx); + if (ssl) + SSL_free(ssl); + return NULL; +} + +my_bool ma_tls_connect(MARIADB_TLS *ctls) +{ + SSL *ssl = (SSL *)ctls->ssl; + my_bool blocking, try_connect= 1; + MYSQL *mysql; + MARIADB_PVIO *pvio; + int rc; +#ifdef OPENSSL_USE_BIOMETHOD + BIO_METHOD *bio_method= NULL; + BIO *bio; +#endif + + mysql= (MYSQL *)SSL_get_app_data(ssl); + pvio= mysql->net.pvio; + + /* Set socket to non blocking if not already set */ + if (!(blocking= pvio->methods->is_blocking(pvio))) + pvio->methods->blocking(pvio, FALSE, 0); + + SSL_clear(ssl); + +#ifdef OPENSSL_USE_BIOMETHOD + bio= BIO_new(&ma_BIO_method); + bio->ptr= pvio; + SSL_set_bio(ssl, bio, bio); + BIO_set_fd(bio, mysql_get_socket(mysql), BIO_NOCLOSE); +#else + SSL_set_fd(ssl, (int)mysql_get_socket(mysql)); +#endif + + while (try_connect && (rc= SSL_connect(ssl)) == -1) + { + switch((SSL_get_error(ssl, rc))) { + case SSL_ERROR_WANT_READ: + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1) + try_connect= 0; + break; + case SSL_ERROR_WANT_WRITE: + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1) + try_connect= 0; + break; + default: + try_connect= 0; + } + } + + /* In case handshake failed or if a root certificate (ca) was specified, + we need to check the result code of X509 verification. A detailed check + of the peer certificate (hostname checking will follow later) */ + if (rc != 1 || mysql->options.extension->tls_verify_server_cert || + mysql->options.ssl_ca || mysql->options.ssl_capath) + { + long x509_err= SSL_get_verify_result(ssl); + if (x509_err != X509_V_OK) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(x509_err)); + /* restore blocking mode */ + if (!blocking) + pvio->methods->blocking(pvio, FALSE, 0); + + return 1; + } else if (rc != 1) { + ma_tls_set_error(mysql); + return 1; + } + } + pvio->ctls->ssl= ctls->ssl= (void *)ssl; + + return 0; +} + +static my_bool +ma_tls_async_check_result(int res, struct mysql_async_context *b, SSL *ssl) +{ + int ssl_err; + b->events_to_wait_for= 0; + if (res >= 0) + return 1; + ssl_err= SSL_get_error(ssl, res); + if (ssl_err == SSL_ERROR_WANT_READ) + b->events_to_wait_for|= MYSQL_WAIT_READ; + else if (ssl_err == SSL_ERROR_WANT_WRITE) + b->events_to_wait_for|= MYSQL_WAIT_WRITE; + else + return 1; + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); + my_context_yield(&b->async_context); + if (b->suspend_resume_hook) + (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); + return 0; +} + +ssize_t ma_tls_read_async(MARIADB_PVIO *pvio, + const unsigned char *buffer, + size_t length) +{ + int res; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + MARIADB_TLS *ctls= pvio->ctls; + + for (;;) + { + res= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length); + if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl)) + return res; + } +} + +ssize_t ma_tls_write_async(MARIADB_PVIO *pvio, + const unsigned char *buffer, + size_t length) +{ + int res; + struct mysql_async_context *b= pvio->mysql->options.extension->async_context; + MARIADB_TLS *ctls= pvio->ctls; + + for (;;) + { + res= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length); + if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl)) + return res; + } +} + + +ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + int rc; + MARIADB_PVIO *pvio= ctls->pvio; + + while ((rc= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length)) <= 0) + { + int error= SSL_get_error((SSL *)ctls->ssl, rc); + if (error != SSL_ERROR_WANT_READ) + break; + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.read_timeout) < 1) + break; + } + if (rc <= 0) + { + MYSQL *mysql= SSL_get_app_data(ctls->ssl); + ma_tls_set_error(mysql); + } + return rc; +} + +ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + int rc; + MARIADB_PVIO *pvio= ctls->pvio; + + while ((rc= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length)) <= 0) + { + int error= SSL_get_error((SSL *)ctls->ssl, rc); + if (error != SSL_ERROR_WANT_WRITE) + break; + if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.write_timeout) < 1) + break; + } + if (rc <= 0) + { + MYSQL *mysql= SSL_get_app_data(ctls->ssl); + ma_tls_set_error(mysql); + } + return rc; +} + +my_bool ma_tls_close(MARIADB_TLS *ctls) +{ + int i, rc; + SSL *ssl; + SSL_CTX *ctx= NULL; + + if (!ctls || !ctls->ssl) + return 1; + ssl= (SSL *)ctls->ssl; + ctx= SSL_get_SSL_CTX(ssl); + if (ctx) + SSL_CTX_free(ctx); + + SSL_set_quiet_shutdown(ssl, 1); + /* 2 x pending + 2 * data = 4 */ + for (i=0; i < 4; i++) + if ((rc= SSL_shutdown(ssl))) + break; + + /* Since we transferred ownership of BIO to ssl, BIO will + automatically freed - no need for an explicit BIO_free_all */ + + SSL_free(ssl); + ctls->ssl= NULL; + + return rc; +} + +int ma_tls_verify_server_cert(MARIADB_TLS *ctls) +{ + X509 *cert; + MYSQL *mysql; + SSL *ssl; + MARIADB_PVIO *pvio; +#if !defined(HAVE_OPENSSL_CHECK_HOST) + X509_NAME *x509sn; + int cn_pos; + X509_NAME_ENTRY *cn_entry; + ASN1_STRING *cn_asn1; + const char *cn_str; +#endif + if (!ctls || !ctls->ssl) + return 1; + ssl= (SSL *)ctls->ssl; + + mysql= (MYSQL *)SSL_get_app_data(ssl); + pvio= mysql->net.pvio; + + if (!mysql->host) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), "Invalid (empty) hostname"); + return 1; + } + + if (!(cert= SSL_get_peer_certificate(ssl))) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); + return 1; + } +#ifdef HAVE_OPENSSL_CHECK_HOST + if (X509_check_host(cert, mysql->host, strlen(mysql->host), 0, 0) != 1 + && X509_check_ip_asc(cert, mysql->host, 0) != 1) + goto error; +#else + x509sn= X509_get_subject_name(cert); + + if ((cn_pos= X509_NAME_get_index_by_NID(x509sn, NID_commonName, -1)) < 0) + goto error; + + if (!(cn_entry= X509_NAME_get_entry(x509sn, cn_pos))) + goto error; + + if (!(cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry))) + goto error; + + cn_str = (char *)ASN1_STRING_data(cn_asn1); + + /* Make sure there is no embedded \0 in the CN */ + if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(cn_str)) + goto error; + + if (strcmp(cn_str, mysql->host)) + goto error; +#endif + X509_free(cert); + + return 0; +error: + X509_free(cert); + + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), "Validation of SSL server certificate failed"); + return 1; +} + +const char *ma_tls_get_cipher(MARIADB_TLS *ctls) +{ + if (!ctls || !ctls->ssl) + return NULL; + return SSL_get_cipher_name(ctls->ssl); +} + +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +{ + X509 *cert= NULL; + MYSQL *mysql; + unsigned int fp_len; + + if (!ctls || !ctls->ssl) + return 0; + + mysql= SSL_get_app_data(ctls->ssl); + + if (!(cert= SSL_get_peer_certificate(ctls->ssl))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + goto end; + } + + if (len < EVP_MAX_MD_SIZE) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Finger print buffer too small"); + goto end; + } + if (!X509_digest(cert, EVP_sha1(), (unsigned char *)fp, &fp_len)) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "invalid finger print of server certificate"); + goto end; + } + + X509_free(cert); + return (fp_len); +end: + X509_free(cert); + return 0; +} + + +int ma_tls_get_protocol_version(MARIADB_TLS *ctls) +{ + if (!ctls || !ctls->ssl) + return -1; + + return SSL_version(ctls->ssl) & 0xFF; +} + +void ma_tls_set_connection(MYSQL *mysql) +{ + (void)SSL_set_app_data(mysql->net.pvio->ctls->ssl, mysql); +} + diff --git a/libmariadb/libmariadb/secure/openssl_crypt.c b/libmariadb/libmariadb/secure/openssl_crypt.c new file mode 100644 index 00000000..faf755c9 --- /dev/null +++ b/libmariadb/libmariadb/secure/openssl_crypt.c @@ -0,0 +1,88 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include <ma_global.h> +#include <ma_crypt.h> +#include <openssl/evp.h> + +static const EVP_MD *ma_hash_get_algorithm(unsigned int alg) +{ + switch(alg) + { + case MA_HASH_MD5: + return EVP_md5(); + case MA_HASH_SHA1: + return EVP_sha1(); + case MA_HASH_SHA224: + return EVP_sha224(); + case MA_HASH_SHA256: + return EVP_sha256(); + case MA_HASH_SHA384: + return EVP_sha384(); + case MA_HASH_SHA512: + return EVP_sha512(); + case MA_HASH_RIPEMD160: + return EVP_ripemd160(); + default: + return NULL; + } +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm) +{ + EVP_MD_CTX *ctx= NULL; + const EVP_MD *evp_md= ma_hash_get_algorithm(algorithm); + + /* unknown or unsupported hash algorithm */ + if (!evp_md) + return NULL; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (!(ctx= EVP_MD_CTX_new())) +#else + if (!(ctx= EVP_MD_CTX_create())) +#endif + return NULL; + if (!EVP_DigestInit(ctx, evp_md)) + { + ma_hash_free(ctx); + return NULL; + } + return ctx; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + if (ctx) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_MD_CTX_free(ctx); +#else + EVP_MD_CTX_destroy(ctx); +#endif +} + +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len) +{ + EVP_DigestUpdate(ctx, buffer, len); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + EVP_DigestFinal_ex(ctx, digest, NULL); +} diff --git a/libmariadb/libmariadb/secure/schannel.c b/libmariadb/libmariadb/secure/schannel.c new file mode 100644 index 00000000..8069af53 --- /dev/null +++ b/libmariadb/libmariadb/secure/schannel.c @@ -0,0 +1,567 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#include "ma_schannel.h" +#include "schannel_certs.h" +#include <string.h> + +extern my_bool ma_tls_initialized; +char tls_library_version[] = "Schannel"; + +#define PROT_SSL3 1 +#define PROT_TLS1_0 2 +#define PROT_TLS1_2 4 +#define PROT_TLS1_3 8 + +static struct +{ + DWORD cipher_id; + DWORD protocol; + const char *iana_name; + const char *openssl_name; + ALG_ID algs[4]; /* exchange, encryption, hash, signature */ +} +cipher_map[] = +{ + { + 0x0002, + PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3, + "TLS_RSA_WITH_NULL_SHA", "NULL-SHA", + { CALG_RSA_KEYX, 0, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x0004, + PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3, + "TLS_RSA_WITH_RC4_128_MD5", "RC4-MD5", + { CALG_RSA_KEYX, CALG_RC4, CALG_MD5, CALG_RSA_SIGN } + }, + { + 0x0005, + PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3, + "TLS_RSA_WITH_RC4_128_SHA", "RC4-SHA", + { CALG_RSA_KEYX, CALG_RC4, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x000A, + PROT_SSL3, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "DES-CBC3-SHA", + {CALG_RSA_KEYX, CALG_3DES, CALG_SHA1, CALG_DSS_SIGN} + }, + { + 0x0013, + PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3, + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "EDH-DSS-DES-CBC3-SHA", + { CALG_DH_EPHEM, CALG_3DES, CALG_SHA1, CALG_DSS_SIGN } + }, + { + 0x002F, + PROT_SSL3 | PROT_TLS1_0 | PROT_TLS1_2, + "TLS_RSA_WITH_AES_128_CBC_SHA", "AES128-SHA", + { CALG_RSA_KEYX, CALG_AES_128, CALG_SHA, CALG_RSA_SIGN} + }, + { + 0x0032, + PROT_TLS1_0 | PROT_TLS1_2, + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "DHE-DSS-AES128-SHA", + { CALG_DH_EPHEM, CALG_AES_128, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x0033, + PROT_TLS1_0 | PROT_TLS1_2, + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "DHE-RSA-AES128-SHA", + { CALG_DH_EPHEM, CALG_AES_128, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x0035, + PROT_TLS1_0 | PROT_TLS1_2, + "TLS_RSA_WITH_AES_256_CBC_SHA", "AES256-SHA", + { CALG_RSA_KEYX, CALG_AES_256, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x0038, + PROT_TLS1_0 | PROT_TLS1_2, + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "DHE-DSS-AES256-SHA", + { CALG_DH_EPHEM, CALG_AES_256, CALG_SHA1, CALG_DSS_SIGN } + }, + { + 0x0039, + PROT_TLS1_0 | PROT_TLS1_2, + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "DHE-RSA-AES256-SHA", + { CALG_DH_EPHEM, CALG_AES_256, CALG_SHA1, CALG_RSA_SIGN } + }, + { + 0x003B, + PROT_TLS1_2, + "TLS_RSA_WITH_NULL_SHA256", "NULL-SHA256", + { CALG_RSA_KEYX, 0, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0x003C, + PROT_TLS1_2, + "TLS_RSA_WITH_AES_128_CBC_SHA256", "AES128-SHA256", + { CALG_RSA_KEYX, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0x003D, + PROT_TLS1_2, + "TLS_RSA_WITH_AES_256_CBC_SHA256", "AES256-SHA256", + { CALG_RSA_KEYX, CALG_AES_256, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0x0040, + PROT_TLS1_2, + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DHE-DSS-AES128-SHA256", + { CALG_DH_EPHEM, CALG_AES_128, CALG_SHA_256, CALG_DSS_SIGN } + }, + { + 0x009C, + PROT_TLS1_2, + "TLS_RSA_WITH_AES_128_GCM_SHA256", "AES128-GCM-SHA256", + { CALG_RSA_KEYX, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0x009D, + PROT_TLS1_2, + "TLS_RSA_WITH_AES_256_GCM_SHA384", "AES256-GCM-SHA384", + { CALG_RSA_KEYX, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN } + }, + { + 0x009E, + PROT_TLS1_2, + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DHE-RSA-AES128-GCM-SHA256", + { CALG_DH_EPHEM, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0x009F, + PROT_TLS1_2, + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DHE-RSA-AES256-GCM-SHA384", + { CALG_DH_EPHEM, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN } + }, + { + 0xC027, + PROT_TLS1_2, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDHE-RSA-AES128-SHA256", + { CALG_ECDH, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN } + }, + { + 0xC028, + PROT_TLS1_2, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDHE-RSA-AES256-SHA384", + { CALG_ECDH, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN } + } +}; + +#define MAX_ALG_ID 50 + +extern void ma_schannel_set_sec_error(MARIADB_PVIO *pvio, DWORD ErrorNo); + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + ma_tls_start + + RETURN VALUES + 0 success + 1 error +*/ +int ma_tls_start(char *errmsg, size_t errmsg_len) +{ + ma_tls_initialized = TRUE; + return 0; +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + ma_tls_end() + void + + RETURN VALUES + void +*/ +void ma_tls_end() +{ + return; +} + +/* {{{ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) */ +static int ma_tls_set_client_certs(MARIADB_TLS *ctls,const CERT_CONTEXT **cert_ctx) +{ + MYSQL *mysql= ctls->pvio->mysql; + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key; + MARIADB_PVIO *pvio= ctls->pvio; + char errmsg[256]; + + if (!certfile && keyfile) + certfile= keyfile; + if (!keyfile && certfile) + keyfile= certfile; + + if (!certfile) + return 0; + + *cert_ctx = schannel_create_cert_context(certfile, keyfile, errmsg, sizeof(errmsg)); + if (!*cert_ctx) + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg); + return 1; + } + + return 0; +} +/* }}} */ + +/* {{{ void *ma_tls_init(MARIADB_TLS *ctls, MYSQL *mysql) */ +void *ma_tls_init(MYSQL *mysql) +{ + SC_CTX *sctx = (SC_CTX *)LocalAlloc(LMEM_ZEROINIT, sizeof(SC_CTX)); + if (sctx) + { + SecInvalidateHandle(&sctx->CredHdl); + SecInvalidateHandle(&sctx->hCtxt); + } + return sctx; +} +/* }}} */ + + +/* + Maps between openssl suite names and schannel alg_ids. + Every suite has 4 algorithms (for exchange, encryption, hash and signing). + + The input string is a set of suite names (openssl), separated + by ':' + + The output is written into the array 'arr' of size 'arr_size' + The function returns number of elements written to the 'arr'. +*/ + +static struct _tls_version { + const char *tls_version; + DWORD protocol; +} tls_version[]= { + {"TLSv1.0", PROT_TLS1_0}, + {"TLSv1.2", PROT_TLS1_2}, + {"TLSv1.3", PROT_TLS1_3}, + {"SSLv3", PROT_SSL3} +}; + +/* The following list was produced with OpenSSL 1.1.1j + by executing `openssl ciphers -V`. */ +static struct { + DWORD dwCipherSuite; + const char *openssl_name; +} openssl_ciphers[] = { + {0x002F, "AES128-SHA"}, + {0x0033, "DHE-RSA-AES128-SHA"}, + {0x0035, "AES256-SHA"}, + {0x0039, "DHE-RSA-AES256-SHA"}, + {0x003C, "AES128-SHA256"}, + {0x003D, "AES256-SHA256"}, + {0x0067, "DHE-RSA-AES128-SHA256"}, + {0x006B, "DHE-RSA-AES256-SHA256"}, + {0x008C, "PSK-AES128-CBC-SHA"}, + {0x008D, "PSK-AES256-CBC-SHA"}, + {0x0090, "DHE-PSK-AES128-CBC-SHA"}, + {0x0091, "DHE-PSK-AES256-CBC-SHA"}, + {0x0094, "RSA-PSK-AES128-CBC-SHA"}, + {0x0095, "RSA-PSK-AES256-CBC-SHA"}, + {0x009C, "AES128-GCM-SHA256"}, + {0x009D, "AES256-GCM-SHA384"}, + {0x009E, "DHE-RSA-AES128-GCM-SHA256"}, + {0x009F, "DHE-RSA-AES256-GCM-SHA384"}, + {0x00A8, "PSK-AES128-GCM-SHA256"}, + {0x00A9, "PSK-AES256-GCM-SHA384"}, + {0x00AA, "DHE-PSK-AES128-GCM-SHA256"}, + {0x00AB, "DHE-PSK-AES256-GCM-SHA384"}, + {0x00AC, "RSA-PSK-AES128-GCM-SHA256"}, + {0x00AD, "RSA-PSK-AES256-GCM-SHA384"}, + {0x00AE, "PSK-AES128-CBC-SHA256"}, + {0x00AF, "PSK-AES256-CBC-SHA384"}, + {0x00B2, "DHE-PSK-AES128-CBC-SHA256"}, + {0x00B3, "DHE-PSK-AES256-CBC-SHA384"}, + {0x00B6, "RSA-PSK-AES128-CBC-SHA256"}, + {0x00B7, "RSA-PSK-AES256-CBC-SHA384"}, + {0x1301, "TLS_AES_128_GCM_SHA256"}, + {0x1302, "TLS_AES_256_GCM_SHA384"}, + {0x1303, "TLS_CHACHA20_POLY1305_SHA256"}, + {0xC009, "ECDHE-ECDSA-AES128-SHA"}, + {0xC00A, "ECDHE-ECDSA-AES256-SHA"}, + {0xC013, "ECDHE-RSA-AES128-SHA"}, + {0xC014, "ECDHE-RSA-AES256-SHA"}, + {0xC01D, "SRP-AES-128-CBC-SHA"}, + {0xC01E, "SRP-RSA-AES-128-CBC-SHA"}, + {0xC020, "SRP-AES-256-CBC-SHA"}, + {0xC021, "SRP-RSA-AES-256-CBC-SHA"}, + {0xC023, "ECDHE-ECDSA-AES128-SHA256"}, + {0xC024, "ECDHE-ECDSA-AES256-SHA384"}, + {0xC027, "ECDHE-RSA-AES128-SHA256"}, + {0xC028, "ECDHE-RSA-AES256-SHA384"}, + {0xC02B, "ECDHE-ECDSA-AES128-GCM-SHA256"}, + {0xC02C, "ECDHE-ECDSA-AES256-GCM-SHA384"}, + {0xC02F, "ECDHE-RSA-AES128-GCM-SHA256"}, + {0xC030, "ECDHE-RSA-AES256-GCM-SHA384"}, + {0xC035, "ECDHE-PSK-AES128-CBC-SHA"}, + {0xC036, "ECDHE-PSK-AES256-CBC-SHA"}, + {0xC037, "ECDHE-PSK-AES128-CBC-SHA256"}, + {0xC038, "ECDHE-PSK-AES256-CBC-SHA384"}, + {0xCCA8, "ECDHE-RSA-CHACHA20-POLY1305"}, + {0xCCA9, "ECDHE-ECDSA-CHACHA20-POLY1305"}, + {0xCCAA, "DHE-RSA-CHACHA20-POLY1305"}, + {0xCCAB, "PSK-CHACHA20-POLY1305"}, + {0xCCAC, "ECDHE-PSK-CHACHA20-POLY1305"}, + {0xCCAD, "DHE-PSK-CHACHA20-POLY1305"}, + {0xCCAE, "RSA-PSK-CHACHA20-POLY1305"} +}; + +static size_t set_cipher(char * cipher_str, DWORD protocol, ALG_ID *arr , size_t arr_size) +{ + char *token = strtok(cipher_str, ":"); + size_t pos = 0; + + while (token) + { + size_t i; + + for(i = 0; i < sizeof(cipher_map)/sizeof(cipher_map[0]) ; i++) + { + if((pos + 4 < arr_size && strcmp(cipher_map[i].openssl_name, token) == 0) || + (cipher_map[i].protocol <= protocol)) + { + memcpy(arr + pos, cipher_map[i].algs, sizeof(ALG_ID)* 4); + pos += 4; + break; + } + } + token = strtok(NULL, ":"); + } + return pos; +} + +my_bool ma_tls_connect(MARIADB_TLS *ctls) +{ + MYSQL *mysql; + SCHANNEL_CRED Cred = {0}; + MARIADB_PVIO *pvio; + my_bool rc= 1; + SC_CTX *sctx; + SECURITY_STATUS sRet; + ALG_ID AlgId[MAX_ALG_ID]; + size_t i; + DWORD protocol = 0; + int verify_certs; + const CERT_CONTEXT* cert_context = NULL; + + if (!ctls) + return 1; + + pvio= ctls->pvio; + sctx= (SC_CTX *)ctls->ssl; + if (!pvio || !sctx) + return 1; + + mysql= pvio->mysql; + if (!mysql) + return 1; + + /* Set cipher */ + if (mysql->options.ssl_cipher) + { + + /* check if a protocol was specified as a cipher: + * In this case don't allow cipher suites which belong to newer protocols + * Please note: There are no cipher suites for TLS1.1 + */ + for (i = 0; i < sizeof(tls_version) / sizeof(tls_version[0]); i++) + { + if (!_stricmp(mysql->options.ssl_cipher, tls_version[i].tls_version)) + protocol |= tls_version[i].protocol; + } + memset(AlgId, 0, sizeof(AlgId)); + Cred.cSupportedAlgs = (DWORD)set_cipher(mysql->options.ssl_cipher, protocol, AlgId, MAX_ALG_ID); + if (Cred.cSupportedAlgs) + { + Cred.palgSupportedAlgs = AlgId; + } + else if (!protocol) + { + ma_schannel_set_sec_error(pvio, SEC_E_ALGORITHM_MISMATCH); + goto end; + } + } + + Cred.dwVersion= SCHANNEL_CRED_VERSION; + + Cred.dwFlags = SCH_CRED_NO_SERVERNAME_CHECK | SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION; + + if (mysql->options.extension && mysql->options.extension->tls_version) + { + if (strstr(mysql->options.extension->tls_version, "TLSv1.0")) + Cred.grbitEnabledProtocols|= SP_PROT_TLS1_0_CLIENT; + if (strstr(mysql->options.extension->tls_version, "TLSv1.1")) + Cred.grbitEnabledProtocols|= SP_PROT_TLS1_1_CLIENT; + if (strstr(mysql->options.extension->tls_version, "TLSv1.2")) + Cred.grbitEnabledProtocols|= SP_PROT_TLS1_2_CLIENT; + } + if (!Cred.grbitEnabledProtocols) + Cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; + + + if (ma_tls_set_client_certs(ctls, &cert_context)) + goto end; + + if (cert_context) + { + Cred.cCreds = 1; + Cred.paCred = &cert_context; + } + sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL); + if (sRet) + { + ma_schannel_set_sec_error(pvio, sRet); + goto end; + } + if (ma_schannel_client_handshake(ctls) != SEC_E_OK) + goto end; + + verify_certs = mysql->options.ssl_ca || mysql->options.ssl_capath || + (mysql->options.extension->tls_verify_server_cert); + + if (verify_certs) + { + if (!ma_schannel_verify_certs(ctls, mysql->options.extension->tls_verify_server_cert)) + goto end; + } + + rc = 0; + +end: + if (cert_context) + schannel_free_cert_context(cert_context); + return rc; +} + +ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + MARIADB_PVIO *pvio= ctls->pvio; + DWORD dlength= 0; + SECURITY_STATUS status = ma_schannel_read_decrypt(pvio, &sctx->hCtxt, &dlength, (uchar *)buffer, (DWORD)length); + if (status == SEC_I_CONTEXT_EXPIRED) + return 0; /* other side shut down the connection. */ + if (status == SEC_I_RENEGOTIATE) + return -1; /* Do not handle renegotiate yet */ + + return (status == SEC_E_OK)? (ssize_t)dlength : -1; +} + +ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length) +{ + MARIADB_PVIO *pvio= ctls->pvio; + ssize_t rc, wlength= 0; + ssize_t remain= length; + + while (remain > 0) + { + if ((rc= ma_schannel_write_encrypt(pvio, (uchar *)buffer + wlength, remain)) <= 0) + return rc; + wlength+= rc; + remain-= rc; + } + return length; +} + +/* {{{ my_bool ma_tls_close(MARIADB_PVIO *pvio) */ +my_bool ma_tls_close(MARIADB_TLS *ctls) +{ + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + + if (sctx) + { + LocalFree(sctx->IoBuffer); + + if (SecIsValidHandle(&sctx->CredHdl)) + FreeCredentialHandle(&sctx->CredHdl); + + if (SecIsValidHandle(&sctx->hCtxt)) + DeleteSecurityContext(&sctx->hCtxt); + } + LocalFree(sctx); + return 0; +} +/* }}} */ + +int ma_tls_verify_server_cert(MARIADB_TLS *ctls) +{ + /* Done elsewhere */ + return 0; +} + +static const char *cipher_name(const SecPkgContext_CipherInfo *CipherInfo) +{ + size_t i; + + for(i = 0; i < sizeof(openssl_ciphers)/sizeof(openssl_ciphers[0]) ; i++) + { + if (CipherInfo->dwCipherSuite == openssl_ciphers[i].dwCipherSuite) + return openssl_ciphers[i].openssl_name; + } + return ""; +}; + +const char *ma_tls_get_cipher(MARIADB_TLS *ctls) +{ + SecPkgContext_CipherInfo CipherInfo = { SECPKGCONTEXT_CIPHERINFO_V1 }; + SECURITY_STATUS sRet; + SC_CTX *sctx; + + if (!ctls || !ctls->ssl) + return NULL; + + sctx= (SC_CTX *)ctls->ssl; + sRet= QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo); + + if (sRet != SEC_E_OK) + return NULL; + + return cipher_name(&CipherInfo); +} + +unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) +{ + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + PCCERT_CONTEXT pRemoteCertContext = NULL; + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) + return 0; + CertGetCertificateContextProperty(pRemoteCertContext, CERT_HASH_PROP_ID, fp, (DWORD *)&len); + CertFreeCertificateContext(pRemoteCertContext); + return len; +} + +void ma_tls_set_connection(MYSQL *mysql __attribute__((unused))) +{ + return; +} diff --git a/libmariadb/libmariadb/secure/schannel_certs.c b/libmariadb/libmariadb/secure/schannel_certs.c new file mode 100644 index 00000000..c8ff3753 --- /dev/null +++ b/libmariadb/libmariadb/secure/schannel_certs.c @@ -0,0 +1,852 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ + + /* + This module contain X509 certificate handling on Windows. + PEM parsing, loading client certificate and key, server certificate validation + */ + + /* + CERT_CHAIN_ENGINE_CONFIG has additional members in Windows 8.1 + To allow client to be work on pre-8.1 Windows, compile + with corresponding _WIN32_WINNT + */ +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif +#include "schannel_certs.h" +#include <malloc.h> +#include <stdio.h> +#include <string.h> +#include <winhttp.h> +#include <limits.h> +#include <assert.h> +#include "win32_errmsg.h" + + /* + Return GetLastError(), or, if this unexpectedly gives success, + return ERROR_INTERNAL_ERROR. + + Background - in several cases in this module we return GetLastError() + after an Windows function fails. However, we do not want the function to + return success, even if GetLastError() was suddenly 0. + */ +static DWORD get_last_error() +{ + DWORD ret = GetLastError(); + if (ret) + return ret; + + // We generally expect last error to be set API fails. + // thus the debug assertion- + assert(0); + return ERROR_INTERNAL_ERROR; +} + +#define FAIL(...) \ + do{\ + status = get_last_error();\ + ma_format_win32_error(errmsg, errmsg_len, status, __VA_ARGS__);\ + goto cleanup;\ + } while (0) + +/* + Load file into memory. Add null terminator at the end, so it will be a valid C string. +*/ +static char* pem_file_to_string(const char* file, char* errmsg, size_t errmsg_len) +{ + LARGE_INTEGER file_size; + size_t file_bufsize = 0; + size_t total_bytes_read = 0; + char* file_buffer = NULL; + SECURITY_STATUS status = SEC_E_OK; + + HANDLE file_handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + { + FAIL("failed to open file '%s'", file); + } + + if (!GetFileSizeEx(file_handle, &file_size)) + { + FAIL("GetFileSizeEx failed on '%s'", file); + } + + if (file_size.QuadPart > ULONG_MAX - 1) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("file '%s' too large", file); + } + + file_bufsize = (size_t)file_size.QuadPart; + file_buffer = (char*)LocalAlloc(0,file_bufsize + 1); + if (!file_buffer) + { + FAIL("LocalAlloc(0,%zu) failed", file_bufsize + 1); + } + + while (total_bytes_read < file_bufsize) + { + DWORD bytes_to_read = (DWORD)(file_bufsize - total_bytes_read); + DWORD bytes_read = 0; + + if (!ReadFile(file_handle, file_buffer + total_bytes_read, + bytes_to_read, &bytes_read, NULL)) + { + FAIL("ReadFile() failed to read file '%s'", file); + } + if (bytes_read == 0) + { + /* Premature EOF -- adjust the bufsize to the new value */ + file_bufsize = total_bytes_read; + } + else + { + total_bytes_read += bytes_read; + } + } + + /* Null terminate the buffer */ + file_buffer[file_bufsize] = '\0'; + +cleanup: + if (file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file_handle); + } + if (status) + { + /* Some error happened. */ + LocalFree(file_buffer); + file_buffer = NULL; + } + return file_buffer; +} + + +// Structure for parsing BEGIN/END sections inside pem. +typedef struct _pem_type_desc +{ + const char* begin_tag; + size_t begin_tag_len; + const char* end_tag; + size_t end_tag_len; +} pem_type_desc; + +#define BEGIN_TAG(x) "-----BEGIN " x "-----" +#define END_TAG(x) "\n-----END " x "-----" +#define PEM_SECTION(tag) {BEGIN_TAG(tag), sizeof(BEGIN_TAG(tag))-1, END_TAG(tag), sizeof(END_TAG(tag))-1} + +typedef enum { + PEM_TYPE_CERTIFICATE = 0, + PEM_TYPE_X509_CRL, + PEM_TYPE_RSA_PRIVATE_KEY, + PEM_TYPE_PRIVATE_KEY +} PEM_TYPE; + +static const pem_type_desc pem_sections[] = { + PEM_SECTION("CERTIFICATE"), + PEM_SECTION("X509 CRL"), + PEM_SECTION("RSA PRIVATE KEY"), + PEM_SECTION("PRIVATE KEY") +}; + +/* + Locate a substring in pem for given type, + e.g section between BEGIN CERTIFICATE and END CERTIFICATE + in PEMs base64 format, with header and footer. + + output parameters 'begin' and 'end' are set upon return. + it is possible that functions returns 'begin' != NULL but + 'end' = NULL. This is generally a format error, meaning that + the end tag was not found +*/ +void pem_locate(char* pem_str, + PEM_TYPE type, + char** begin, + char** end) +{ + *begin = NULL; + *end = NULL; + char c; + + const pem_type_desc* desc = &pem_sections[type]; + *begin = strstr(pem_str, desc->begin_tag); + if (!(*begin)) + return; + + // We expect newline after the + // begin tag, LF or CRLF + c = (*begin)[desc->begin_tag_len]; + + if (c != '\r' && c != '\n') + { + *begin = NULL; + return; + } + + *end = strstr(*begin + desc->begin_tag_len + 1, desc->end_tag); + if (!*end) + return; // error, end marker not found + + (*end) += desc->end_tag_len; + return; +} + + +/* + Add certificates, or CRLs from a PEM file to Wincrypt store +*/ +static SECURITY_STATUS add_certs_to_store( + HCERTSTORE trust_store, + const char* file, + PEM_TYPE type, + char* errmsg, + size_t errmsg_len) +{ + char* file_buffer = NULL; + char* cur = NULL; + SECURITY_STATUS status = SEC_E_OK; + CRL_CONTEXT* crl_context = NULL; + CERT_CONTEXT* cert_context = NULL; + char* begin; + char* end; + + file_buffer = pem_file_to_string(file, errmsg, errmsg_len); + if (!file_buffer) + goto cleanup; + + for (cur = file_buffer; ; cur = end) + { + pem_locate(cur, type, &begin, &end); + + if (!begin) + break; + + if (!end) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("Invalid PEM file '%s', missing end marker corresponding to begin marker '%s' at offset %zu", + file, pem_sections[type].begin_tag, (size_t)(begin - file_buffer)); + } + CERT_BLOB cert_blob; + void* context = NULL; + DWORD actual_content_type = 0; + + cert_blob.pbData = (BYTE*)begin; + cert_blob.cbData = (DWORD)(end - begin); + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_CRL, + CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type, + NULL, NULL, NULL, (const void**)&context)) + { + FAIL("failed to extract certificate from PEM file '%s'",file); + } + + if (!context) + { + SetLastError(SEC_E_INTERNAL_ERROR); + FAIL("unexpected result from CryptQueryObject(),cert_context is NULL" + " after successful completion, file '%s'", + file); + } + + if (actual_content_type == CERT_QUERY_CONTENT_CERT) + { + CERT_CONTEXT* cert_context = (CERT_CONTEXT*)context; + if (!CertAddCertificateContextToStore( + trust_store, cert_context, + CERT_STORE_ADD_ALWAYS, NULL)) + { + FAIL("CertAddCertificateContextToStore failed"); + } + } + else if (actual_content_type == CERT_QUERY_CONTENT_CRL) + { + CRL_CONTEXT* crl_context = (CRL_CONTEXT*)context; + if (!CertAddCRLContextToStore( + trust_store, crl_context, + CERT_STORE_ADD_ALWAYS, NULL)) + { + FAIL("CertAddCRLContextToStore() failed"); + } + } + } +cleanup: + LocalFree(file_buffer); + if (cert_context) + CertFreeCertificateContext(cert_context); + if (crl_context) + CertFreeCRLContext(crl_context); + return status; +} + +/* +Add a directory to store, i.e try to load all files. +(extract certificates and add them to store) + +@return 0 on success, error only if directory is invalid. +*/ +SECURITY_STATUS add_dir_to_store(HCERTSTORE trust_store, const char* dir, + PEM_TYPE type, char* errmsg, size_t errmsg_len) +{ + WIN32_FIND_DATAA ffd; + char path[MAX_PATH]; + char pattern[MAX_PATH]; + DWORD dwAttr; + HANDLE hFind = INVALID_HANDLE_VALUE; + SECURITY_STATUS status = SEC_E_OK; + + if ((dwAttr = GetFileAttributes(dir)) == INVALID_FILE_ATTRIBUTES) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("directory '%s' does not exist", dir); + } + if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("'%s' is not a directory", dir); + } + sprintf_s(pattern, sizeof(pattern), "%s\\*", dir); + hFind = FindFirstFile(pattern, &ffd); + if (hFind == INVALID_HANDLE_VALUE) + { + FAIL("FindFirstFile(%s) failed",pattern); + } + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + sprintf_s(path, sizeof(path), "%s\\%s", dir, ffd.cFileName); + + // ignore error from add_certs_to_store(), not all file + // maybe PEM. + add_certs_to_store(trust_store, path, type, errmsg, + errmsg_len); + } while (FindNextFile(hFind, &ffd) != 0); + +cleanup: + if (hFind != INVALID_HANDLE_VALUE) + FindClose(hFind); + + return status; +} + +/* Count certificates in store. */ +static int count_certificates(HCERTSTORE store) +{ + int num_certs = 0; + PCCERT_CONTEXT c = NULL; + + while ((c = CertEnumCertificatesInStore(store, c))) + num_certs++; + + return num_certs; +} + +/** + Creates certificate store with user defined CA chain and/or CRL. + Loads PEM certificate from files or directories. + + If only CRLFile/CRLPath is defined, the "system" store is duplicated, + and new CRLs are added to it. + + If CAFile/CAPAth is defined, then new empty store is created, and CAs + (and CRLs, if defined), are added to it. + + The function throws an error, if none of the files in CAFile/CAPath have a valid certificate. + It is also an error if CRLFile does not exist. +*/ +SECURITY_STATUS schannel_create_store( + const char* CAFile, + const char* CAPath, + const char* CRLFile, + const char* CRLPath, + HCERTSTORE* out_store, + char* errmsg, + size_t errmsg_len) +{ + + HCERTSTORE store = NULL; + HCERTSTORE system_store = NULL; + int status = SEC_E_OK; + + *out_store = NULL; + if (!CAFile && !CAPath && !CRLFile && !CRLPath) + { + /* Nothing to do, caller will use default store*/ + *out_store = NULL; + return SEC_E_OK; + } + if (CAFile || CAPath) + { + /* Open the certificate store */ + store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, NULL); + if (!store) + { + FAIL("CertOpenStore failed for memory store"); + } + } + else if (CRLFile || CRLPath) + { + /* Only CRL was provided, copy system store, add revocation list to + * it. */ + system_store = + CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY)NULL, + CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); + if (!system_store) + { + FAIL("CertOpenStore failed for system store"); + } + + store = CertDuplicateStore(system_store); + if (!store) + { + FAIL("CertDuplicateStore failed"); + } + } + + if (CAFile) + { + status = add_certs_to_store(store, CAFile, + PEM_TYPE_CERTIFICATE, errmsg, errmsg_len); + if (status) + goto cleanup; + } + if (CAPath) + { + status = add_dir_to_store(store, CAPath, + PEM_TYPE_CERTIFICATE, errmsg, errmsg_len); + if (status) + goto cleanup; + } + + if ((CAFile || CAPath) && store && !count_certificates(store)) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("no valid certificates were found, CAFile='%s', CAPath='%s'", + CAFile ? CAFile : "<not set>", CAPath ? CAPath : "<not set>"); + } + + if (CRLFile) + { + status = add_certs_to_store(store, CRLFile, PEM_TYPE_X509_CRL, + errmsg, errmsg_len); + } + if (CRLPath) + { + status = add_dir_to_store(store, CRLPath, PEM_TYPE_X509_CRL, + errmsg, errmsg_len); + } + +cleanup: + if (system_store) + CertCloseStore(system_store, 0); + if (status && store) + { + CertCloseStore(store, 0); + store = NULL; + } + *out_store = store; + return status; +} + +/* + The main verification logic. + Taken almost completely from Windows 2003 Platform SDK 2003 + (Samples\Security\SSPI\SSL\WebClient.c) + + The only difference here is is usage of custom store + and chain engine. +*/ +static SECURITY_STATUS VerifyServerCertificate( + PCCERT_CONTEXT pServerCert, + HCERTSTORE hStore, + LPWSTR pwszServerName, + DWORD dwRevocationCheckFlags, + DWORD dwVerifyFlags, + LPSTR errmsg, + size_t errmsg_len) +{ + SSL_EXTRA_CERT_CHAIN_POLICY_PARA polExtra; + CERT_CHAIN_POLICY_PARA PolicyPara; + CERT_CHAIN_POLICY_STATUS PolicyStatus; + CERT_CHAIN_PARA ChainPara; + HCERTCHAINENGINE hChainEngine = NULL; + PCCERT_CHAIN_CONTEXT pChainContext = NULL; + LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE }; + DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); + SECURITY_STATUS status = SEC_E_OK; + + if (pServerCert == NULL) + { + SetLastError(SEC_E_WRONG_PRINCIPAL); + FAIL("Invalid parameter pServerCert passed to VerifyServerCertificate"); + } + + ZeroMemory(&ChainPara, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; + + if (hStore) + { + CERT_CHAIN_ENGINE_CONFIG EngineConfig = { 0 }; + EngineConfig.cbSize = sizeof(EngineConfig); + EngineConfig.hExclusiveRoot = hStore; + if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine)) + { + FAIL("CertCreateCertificateChainEngine failed"); + } + } + + if (!CertGetCertificateChain( + hChainEngine, + pServerCert, + NULL, + pServerCert->hCertStore, + &ChainPara, + dwRevocationCheckFlags, + NULL, + &pChainContext)) + { + FAIL("CertGetCertificateChain failed"); + goto cleanup; + } + + // Validate certificate chain. + ZeroMemory(&polExtra, sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA)); + polExtra.cbStruct = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA); + polExtra.dwAuthType = AUTHTYPE_SERVER; + polExtra.fdwChecks = dwVerifyFlags; + polExtra.pwszServerName = pwszServerName; + + memset(&PolicyPara, 0, sizeof(PolicyPara)); + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.pvExtraPolicyPara = &polExtra; + + memset(&PolicyStatus, 0, sizeof(PolicyStatus)); + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + pChainContext, + &PolicyPara, + &PolicyStatus)) + { + FAIL("CertVerifyCertificateChainPolicy failed"); + } + + if (PolicyStatus.dwError) + { + SetLastError(PolicyStatus.dwError); + FAIL("Server certificate validation failed"); + } + +cleanup: + if (hChainEngine) + { + CertFreeCertificateChainEngine(hChainEngine); + } + if (pChainContext) + { + CertFreeCertificateChain(pChainContext); + } + return status; +} + + +void schannel_free_store(HCERTSTORE store) +{ + if (store) + CertCloseStore(store, 0); +} + + +/* +Verify server certificate against a wincrypt store +@return 0 - success, otherwise error occurred. +*/ +SECURITY_STATUS schannel_verify_server_certificate( + const CERT_CONTEXT* cert, + HCERTSTORE store, + BOOL check_revocation, + const char* server_name, + BOOL check_server_name, + char* errmsg, + size_t errmsg_len) +{ + SECURITY_STATUS status = SEC_E_OK; + wchar_t* wserver_name = NULL; + DWORD dwVerifyFlags; + DWORD dwRevocationFlags; + + if (check_server_name) + { + int cchServerName = (int)strlen(server_name) + 1; + wserver_name = (wchar_t*)LocalAlloc(0,sizeof(wchar_t) * cchServerName); + if (!wserver_name) + { + FAIL("LocalAlloc() failed"); + } + if (MultiByteToWideChar(CP_UTF8, 0, server_name, cchServerName, wserver_name, cchServerName) < 0) + { + FAIL("MultiByteToWideChar() failed"); + } + } + + dwVerifyFlags = 0; + dwRevocationFlags = 0; + if (check_revocation) + dwRevocationFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; + if (!check_server_name) + dwVerifyFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID; + + status = VerifyServerCertificate(cert, store, wserver_name ? wserver_name : L"SERVER_NAME", + dwRevocationFlags, dwVerifyFlags, errmsg, errmsg_len); + +cleanup: + LocalFree(wserver_name); + return status; +} + + +/* Attach private key (in PEM format) to client certificate */ +static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_str, size_t len, char* errmsg, size_t errmsg_len) +{ + DWORD derlen = (DWORD)len; + BYTE* derbuf = NULL; + DWORD keyblob_len = 0; + BYTE* keyblob = NULL; + HCRYPTPROV hProv = 0; + HCRYPTKEY hKey = 0; + CERT_KEY_CONTEXT cert_key_context = { 0 }; + PCRYPT_PRIVATE_KEY_INFO pki = NULL; + DWORD pki_len = 0; + SECURITY_STATUS status = SEC_E_OK; + + derbuf = LocalAlloc(0, derlen); + if (!derbuf) + { + FAIL("LocalAlloc failed"); + } + + if (!CryptStringToBinaryA(private_key_str, (DWORD)len, CRYPT_STRING_BASE64HEADER, derbuf, &derlen, NULL, NULL)) + { + FAIL("Failed to convert BASE64 private key"); + } + + /* + To accommodate for both "BEGIN PRIVATE KEY" vs "BEGIN RSA PRIVATE KEY" + sections in PEM, we try to decode with PKCS_PRIVATE_KEY_INFO first, + and, if it fails, with PKCS_RSA_PRIVATE_KEY flag. + */ + if (CryptDecodeObjectEx( + X509_ASN_ENCODING, + PKCS_PRIVATE_KEY_INFO, + derbuf, derlen, + CRYPT_DECODE_ALLOC_FLAG, + NULL, &pki, &pki_len)) + { + // convert private key info to RSA private key blob + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + pki->PrivateKey.pbData, + pki->PrivateKey.cbData, + CRYPT_DECODE_ALLOC_FLAG, + NULL, &keyblob, &keyblob_len)) + { + FAIL("Failed to parse private key"); + } + } + else if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + derbuf, derlen, + CRYPT_DECODE_ALLOC_FLAG, NULL, + &keyblob, &keyblob_len)) + { + FAIL("Failed to parse private key"); + } + + if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + FAIL("CryptAcquireContext failed"); + } + + if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey)) + { + FAIL("CryptImportKey failed"); + } + cert_key_context.hCryptProv = hProv; + cert_key_context.dwKeySpec = AT_KEYEXCHANGE; + cert_key_context.cbSize = sizeof(cert_key_context); + + /* assign private key to certificate context */ + if (!CertSetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, + CERT_STORE_NO_CRYPT_RELEASE_FLAG, + &cert_key_context)) + { + FAIL("CertSetCertificateContextProperty failed"); + } + +cleanup: + LocalFree(derbuf); + LocalFree(keyblob); + LocalFree(pki); + if (hKey) + CryptDestroyKey(hKey); + if (status) + { + if (hProv) + CryptReleaseContext(hProv, 0); + } + return status; +} + +/* + Given PEM strings for certificate and private key, + create a client certificate* +*/ +static CERT_CONTEXT* create_client_certificate_mem( + char* cert_file_content, + char* key_file_content, + char* errmsg, + size_t errmsg_len) +{ + CERT_CONTEXT* ctx = NULL; + char* begin; + char* end; + CERT_BLOB cert_blob; + DWORD actual_content_type = 0; + SECURITY_STATUS status = SEC_E_OK; + + /* Parse certificate */ + pem_locate(cert_file_content, PEM_TYPE_CERTIFICATE, + &begin, &end); + + if (!begin || !end) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("Client certificate not found in PEM file"); + } + + cert_blob.pbData = (BYTE*)begin; + cert_blob.cbData = (DWORD)(end - begin); + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type, + NULL, NULL, NULL, (const void**)&ctx)) + { + FAIL("Can't parse client certficate"); + } + + /* Parse key */ + PEM_TYPE types[] = { PEM_TYPE_RSA_PRIVATE_KEY, PEM_TYPE_PRIVATE_KEY }; + for (int i = 0; i < sizeof(types) / sizeof(types[0]); i++) + { + pem_locate(key_file_content, types[i], &begin, &end); + if (begin && end) + { + /* Assign key to certificate.*/ + status = load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len); + goto cleanup; + } + } + + if (!begin || !end) + { + SetLastError(SEC_E_INVALID_PARAMETER); + FAIL("Client private key not found in PEM"); + } + +cleanup: + if (status && ctx) + { + CertFreeCertificateContext(ctx); + ctx = NULL; + } + return ctx; +} + + +/* Given cert and key, as PEM file names, create a client certificate */ +CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char* errmsg, size_t errmsg_len) +{ + CERT_CONTEXT* ctx = NULL; + char* key_file_content = NULL; + char* cert_file_content = NULL; + + cert_file_content = pem_file_to_string(cert_file, errmsg, errmsg_len); + + if (!cert_file_content) + goto cleanup; + + if (cert_file == key_file) + { + key_file_content = cert_file_content; + } + else + { + key_file_content = pem_file_to_string(key_file, errmsg, errmsg_len); + if (!key_file_content) + goto cleanup; + } + + ctx = create_client_certificate_mem(cert_file_content, key_file_content, errmsg, errmsg_len); + +cleanup: + LocalFree(cert_file_content); + if (cert_file != key_file) + LocalFree(key_file_content); + + return ctx; +} + +/* + Free certificate, and all resources, created by schannel_create_cert_context() +*/ +void schannel_free_cert_context(const CERT_CONTEXT* cert) +{ + /* release provider handle which was acquires in load_private_key() */ + CERT_KEY_CONTEXT cert_key_context = { 0 }; + cert_key_context.cbSize = sizeof(cert_key_context); + DWORD cbData = sizeof(CERT_KEY_CONTEXT); + HCRYPTPROV hProv = 0; + + if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData)) + { + hProv = cert_key_context.hCryptProv; + } + CertFreeCertificateContext(cert); + if (hProv) + { + CryptReleaseContext(cert_key_context.hCryptProv, 0); + } +} diff --git a/libmariadb/libmariadb/secure/schannel_certs.h b/libmariadb/libmariadb/secure/schannel_certs.h new file mode 100644 index 00000000..67a8d11e --- /dev/null +++ b/libmariadb/libmariadb/secure/schannel_certs.h @@ -0,0 +1,53 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ + +#pragma once +#include <windows.h> +#include <wincrypt.h> + +extern SECURITY_STATUS schannel_create_store( + const char* CAFile, + const char* CAPath, + const char* CRLFile, + const char* CRLPath, + HCERTSTORE* store, + char* errmsg, + size_t errmsg_len +); + +extern SECURITY_STATUS schannel_verify_server_certificate( + const CERT_CONTEXT* cert, + HCERTSTORE store, + BOOL check_revocation, + const char* server_name, + BOOL check_server_name, + char* errmsg, + size_t errmsg_len); + +extern void schannel_free_store(HCERTSTORE store); + +extern CERT_CONTEXT* schannel_create_cert_context( + char* cert_file, + char* key_file, + char* errmsg, + size_t errmsg_len); + +extern void schannel_free_cert_context(const CERT_CONTEXT* cert); + diff --git a/libmariadb/libmariadb/secure/win_crypt.c b/libmariadb/libmariadb/secure/win_crypt.c new file mode 100644 index 00000000..9d9fe495 --- /dev/null +++ b/libmariadb/libmariadb/secure/win_crypt.c @@ -0,0 +1,162 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include <windows.h> +#include <bcrypt.h> +#include <ma_crypt.h> +#include <malloc.h> +#include <stdlib.h> +#include <stdio.h> + +/* + Error handling for bcrypt. + If we can't meaningfully return an error, dump error on stderr + and abort. Those errors are mostly likely programming errors + (invalid parameters and such) +*/ +static inline void check_nt_status(int ret, const char *function, + const char *file, int line) +{ + if (ret) + { + fprintf(stderr,"invalid return %d from bcrypt, " + "function %s with file %s, line %d\n", + ret, function, file, line); + abort(); + } +} +#define CHECK_NT_STATUS(ret) check_nt_status(ret, __func__, __FILE__, __LINE__) + +/* + Return Bcrypt algorithm ID (wchar string) for MariaDB numeric ID +*/ +static LPCWSTR ma_hash_get_algorithm(unsigned int alg) +{ + switch (alg) + { + case MA_HASH_SHA1: + return BCRYPT_SHA1_ALGORITHM; + case MA_HASH_SHA256: + return BCRYPT_SHA256_ALGORITHM; + case MA_HASH_SHA384: + return BCRYPT_SHA384_ALGORITHM; + case MA_HASH_SHA512: + return BCRYPT_SHA512_ALGORITHM; + default: + return NULL; + } +} + +/* Cached algorithm provides handles. */ +static BCRYPT_ALG_HANDLE cached_alg_handles[MA_HASH_MAX]; + +/* + Cleanup cached algorithm handles. It runs either on process exit, + or when DLL is unloaded (see _onexit() documentation) +*/ +static int win_crypt_onexit(void) +{ + int i; + for (i= 0; i < MA_HASH_MAX; i++) + { + if (cached_alg_handles[i]) + BCryptCloseAlgorithmProvider(cached_alg_handles[i], 0); + } + return 0; +} + +static void register_cleanup_onexit_once() +{ + static LONG onexit_called; + if (!InterlockedCompareExchange(&onexit_called, 1, 0)) + _onexit(win_crypt_onexit); +} + +/* + Given algorithm ID, return BCRYPT provider handle. + Uses or populates algorithm provider handle cache. +*/ +static BCRYPT_ALG_HANDLE ma_hash_get_algorithm_handle(unsigned int alg) +{ + static SRWLOCK lock= SRWLOCK_INIT; + BCRYPT_ALG_HANDLE handle= NULL; + const wchar_t *name; + + if ((handle= cached_alg_handles[alg]) != NULL) + return handle; + + name= ma_hash_get_algorithm(alg); + if (!name) + return NULL; + + AcquireSRWLockExclusive(&lock); + if ((handle= cached_alg_handles[alg]) == NULL) + { + if (BCryptOpenAlgorithmProvider(&handle, name, NULL, 0) == 0) + cached_alg_handles[alg]= handle; + else + handle= NULL; + } + ReleaseSRWLockExclusive(&lock); + + if (handle) + register_cleanup_onexit_once(); + return handle; +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm) +{ + BCRYPT_HASH_HANDLE hash_handle; + BCRYPT_ALG_HANDLE alg_handle= ma_hash_get_algorithm_handle(algorithm); + + if (!alg_handle) + return NULL; + + if (BCryptCreateHash(alg_handle, &hash_handle, NULL, 0, NULL, 0, 0)) + return NULL; + + return hash_handle; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + NTSTATUS status; + if (!ctx) + return; + status= BCryptDestroyHash(ctx); + CHECK_NT_STATUS(status); +} + +void ma_hash_input(MA_HASH_CTX *ctx, const unsigned char *buffer, size_t len) +{ + NTSTATUS status= BCryptHashData(ctx, (PUCHAR) buffer, (ULONG) len, 0); + CHECK_NT_STATUS(status); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + DWORD hash_length; + DWORD data_length; + NTSTATUS status= + BCryptGetProperty(ctx, BCRYPT_HASH_LENGTH, (PBYTE) &hash_length, + sizeof(DWORD), &data_length, 0); + CHECK_NT_STATUS(status); + + status= BCryptFinishHash(ctx, digest, (ULONG) hash_length, 0); + CHECK_NT_STATUS(status); +} diff --git a/libmariadb/libmariadb/win32_errmsg.c b/libmariadb/libmariadb/win32_errmsg.c new file mode 100644 index 00000000..f890c4f6 --- /dev/null +++ b/libmariadb/libmariadb/win32_errmsg.c @@ -0,0 +1,138 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ + +#include <windows.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + Format Windows error, with optional text. + + For "known" errors we also output their symbolic error constant, e.g + CERT_E_CN_NO_MATCH in addition to numeric value. + + We also try to output English text for all error messages, as not to mess up + with encodings. +*/ +void ma_format_win32_error(char* buf, size_t buflen, DWORD code, _Printf_format_string_ const char* fmt, ...) +{ + char* cur = buf; + char* end = cur + buflen; + *cur = 0; + if (fmt) + { + va_list vargs; + va_start(vargs, fmt); + cur += vsnprintf_s(cur, end - cur, _TRUNCATE, fmt, vargs); + va_end(vargs); + } + + if (code == 0) + return; + + static struct map_entry + { + DWORD code; + const char* sym; + const char* msg; + } + map[] = + { +#define ENTRY(x, y) {x,#x, y} + ENTRY(SEC_E_WRONG_PRINCIPAL, "The target principal name is incorrect"), + ENTRY(CERT_E_CN_NO_MATCH,"The certificate's CN name does not match the passed value"), + ENTRY(SEC_E_UNTRUSTED_ROOT,"The certificate chain was issued by an authority that is not trusted"), + ENTRY(TRUST_E_CERT_SIGNATURE,"The signature of the certificate cannot be verified"), + ENTRY(SEC_E_CERT_EXPIRED,"The received certificate has expired"), + ENTRY(CERT_E_EXPIRED,"A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file"), + ENTRY(CRYPT_E_NO_REVOCATION_CHECK, "The revocation function was unable to check revocation for the certificate"), + ENTRY(CRYPT_E_REVOCATION_OFFLINE,"The revocation function was unable to check revocation because the revocation server was offline"), + ENTRY(CRYPT_E_REVOKED,"The certificate is revoked"), + ENTRY(SEC_E_CERT_UNKNOWN,"An unknown error occurred while processing the certificate"), + ENTRY(CERT_E_ROLE," A certificate that can only be used as an end-entity is being used as a CA or vice versa"), + ENTRY(CERT_E_WRONG_USAGE,"The certificate is not valid for the requested usage"), + ENTRY(SEC_E_ILLEGAL_MESSAGE, "The message received was unexpected or badly formatted"), + ENTRY(CERT_E_VALIDITYPERIODNESTING,"The validity periods of the certification chain do not nest correctly"), + ENTRY(CERT_E_PATHLENCONST,"A path length constraint in the certification chain has been violated"), + ENTRY(CERT_E_CRITICAL,"A certificate contains an unknown extension that is marked 'critical'"), + ENTRY(CERT_E_PURPOSE,"A certificate being used for a purpose other than the ones specified by its CA"), + ENTRY(CERT_E_ISSUERCHAINING,"A parent of a given certificate in fact did not issue that child certificate"), + ENTRY(CERT_E_MALFORMED, "A certificate is missing or has an empty value for an important field, such as a subject or issuer name"), + ENTRY(CERT_E_CHAINING,"A certificate chain could not be built to a trusted root authority"), + ENTRY(TRUST_E_FAIL," Generic trust failure"), + ENTRY(CERT_E_UNTRUSTEDTESTROOT,"The certification path terminates with the test root which is not trusted with the current policy settings"), + ENTRY(CERT_E_UNTRUSTEDROOT,"A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider"), + ENTRY(CERT_E_REVOCATION_FAILURE,"The revocation process could not continue - the certificate(s) could not be checked"), + ENTRY(SEC_E_ILLEGAL_MESSAGE, "The message received was unexpected or badly formatted"), + ENTRY(SEC_E_UNTRUSTED_ROOT, "Untrusted root certificate"), + ENTRY(SEC_E_BUFFER_TOO_SMALL, "Buffer too small"), + ENTRY(SEC_E_CRYPTO_SYSTEM_INVALID, "Cipher is not supported"), + ENTRY(SEC_E_INSUFFICIENT_MEMORY, "Out of memory"), + ENTRY(SEC_E_OUT_OF_SEQUENCE, "Invalid message sequence"), + ENTRY(SEC_E_DECRYPT_FAILURE, "The specified data could not be decrypted"), + ENTRY(SEC_I_INCOMPLETE_CREDENTIALS, "Incomplete credentials"), + ENTRY(SEC_E_ENCRYPT_FAILURE, "The specified data could not be encrypted"), + ENTRY(SEC_I_CONTEXT_EXPIRED, "The context has expired and can no longer be used"), + ENTRY(SEC_E_ALGORITHM_MISMATCH, "no cipher match"), + ENTRY(SEC_E_NO_CREDENTIALS, "no credentials"), + ENTRY(SEC_E_INVALID_TOKEN, "The token supplied to function is invalid"), + ENTRY(SEC_E_UNSUPPORTED_FUNCTION,"The function requested is not supported") + }; + + struct map_entry* entry = NULL; + + if (cur > buf && cur[-1] != ' ' && cur[-1] != '.') + { + strncpy_s(cur,end-cur, ". ", _TRUNCATE); + cur += 2; + } + + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) + { + if (code == map[i].code) + { + entry = &map[i]; + break; + } + } + if (cur > end - 20) + return; + if (entry) + { + snprintf(cur, end - cur, "%s. Error 0x%08lX(%s)", entry->msg, code, entry->sym); + } + else + { + cur += FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + cur, (DWORD)(end - cur), NULL); + while (cur > buf && (*cur == '\0' || *cur == '\n' || *cur == '\r' || *cur == '.')) + cur--; + if (*cur) + { + cur++; + *cur = 0; + } + snprintf(cur, end - cur, ". Error %lu/0x%08lX", code, code); + } + end[-1] = 0; +} + diff --git a/libmariadb/libmariadb/win32_errmsg.h b/libmariadb/libmariadb/win32_errmsg.h new file mode 100644 index 00000000..865d9928 --- /dev/null +++ b/libmariadb/libmariadb/win32_errmsg.h @@ -0,0 +1,2 @@ +#include <windows.h> +void ma_format_win32_error(char* buf, size_t buflen, DWORD code, _Printf_format_string_ const char* fmt, ...); |