diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 31234a6d51bd..5f6736cf4b25 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -34,6 +34,7 @@ #include "client/mysqltest/utils.h" #include +#include #include // std::isinf #include #include @@ -54,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +64,8 @@ #include #include #ifndef _WIN32 +#include +#include #include #endif #ifdef _WIN32 @@ -219,6 +223,15 @@ static const char *load_default_groups[] = {"mysqltest", "client", 0}; static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos = line_buffer; static bool can_handle_expired_passwords = true; +/* + These variables control the behavior of the asynchronous operations for + mysqltest client. If --async-client is specified, use_async_client is true. + Each command checks enable_async_client (which can be forced off or + disabled for a single command) to decide the mode it uses to run. +*/ +static bool use_async_client = false; +static bool enable_async_client = false; + // Secondary engine options static const char *opt_load_pool = 0; static const char *opt_offload_count_file; @@ -253,7 +266,8 @@ static struct Property prop_list[] = { {&ps_protocol_enabled, 0, 0, 0, "$ENABLE_PS_PROTOCOL"}, {&disable_query_log, 0, 0, 1, "$ENABLE_QUERY_LOG"}, {&disable_result_log, 0, 0, 1, "$ENABLE_RESULT_LOG"}, - {&disable_warnings, 0, 0, 1, "$ENABLE_WARNINGS"}}; + {&disable_warnings, 0, 0, 1, "$ENABLE_WARNINGS"}, + {&enable_async_client, 0, 0, 0, "$ENABLE_ASYNC_CLIENT"}}; static bool once_property = false; @@ -267,6 +281,7 @@ enum enum_prop { P_QUERY, P_RESULT, P_WARN, + P_ASYNC, P_MAX }; @@ -542,6 +557,8 @@ enum enum_commands { Q_DISABLE_SESSION_TRACK_INFO, Q_ENABLE_METADATA, Q_DISABLE_METADATA, + Q_ENABLE_ASYNC_CLIENT, + Q_DISABLE_ASYNC_CLIENT, Q_EXEC, Q_EXECW, Q_EXEC_BACKGROUND, @@ -615,15 +632,15 @@ const char *command_names[] = { "disable_connect_log", "wait_for_slave_to_stop", "enable_warnings", "disable_warnings", "enable_info", "disable_info", "enable_session_track_info", "disable_session_track_info", - "enable_metadata", "disable_metadata", "exec", "execw", - "exec_in_background", "delimiter", "disable_abort_on_error", - "enable_abort_on_error", "vertical_results", "horizontal_results", - "query_vertical", "query_horizontal", "sorted_result", "lowercase_result", - "start_timer", "end_timer", "character_set", "disable_ps_protocol", - "enable_ps_protocol", "disable_reconnect", "enable_reconnect", "if", - "disable_testcase", "enable_testcase", "replace_regex", - "replace_numeric_round", "remove_file", "file_exists", "write_file", - "copy_file", "perl", "die", + "enable_metadata", "disable_metadata", "enable_async_client", + "disable_async_client", "exec", "execw", "exec_in_background", "delimiter", + "disable_abort_on_error", "enable_abort_on_error", "vertical_results", + "horizontal_results", "query_vertical", "query_horizontal", "sorted_result", + "lowercase_result", "start_timer", "end_timer", "character_set", + "disable_ps_protocol", "enable_ps_protocol", "disable_reconnect", + "enable_reconnect", "if", "disable_testcase", "enable_testcase", + "replace_regex", "replace_numeric_round", "remove_file", "file_exists", + "write_file", "copy_file", "perl", "die", /* Don't execute any more commands, compare result */ "exit", "skip", "chmod", "append_file", "cat_file", "diff_files", @@ -760,6 +777,334 @@ static void free_all_replace() { free_replace_numeric_round(); } +/* + To run tests via the async API, invoke mysqltest with --async-client. +*/ +class AsyncTimer { + public: + explicit AsyncTimer(std::string label) + : label_(label), time_(std::chrono::system_clock::now()), start_(time_) {} + + ~AsyncTimer() { + auto now = std::chrono::system_clock::now(); + auto delta = now - start_; + ulonglong MY_ATTRIBUTE((unused)) micros = + std::chrono::duration_cast(delta).count(); + DBUG_PRINT("async_timing", + ("%s total micros: %llu", label_.c_str(), micros)); + } + + void check() { + auto now = std::chrono::system_clock::now(); + auto delta = now - time_; + time_ = now; + ulonglong MY_ATTRIBUTE((unused)) micros = + std::chrono::duration_cast(delta).count(); + DBUG_PRINT("async_timing", ("%s op micros: %llu", label_.c_str(), micros)); + } + + private: + std::string label_; + std::chrono::system_clock::time_point time_; + std::chrono::system_clock::time_point start_; +}; + +#ifdef _WIN32 +/* + Check if any data is available in the socket to be read or written. +*/ +static int socket_event_listen(net_async_block_state async_blocking_state, + my_socket fd) { + int result; + fd_set readfds, writefds, exceptfds; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + FD_SET(fd, &exceptfds); + + switch (async_blocking_state) { + case NET_NONBLOCKING_READ: + FD_SET(fd, &readfds); + break; + case NET_NONBLOCKING_WRITE: + case NET_NONBLOCKING_CONNECT: + FD_SET(fd, &writefds); + break; + default: + DBUG_ASSERT(false); + } + result = select((int)(fd + 1), &readfds, &writefds, &exceptfds, NULL); + if (result < 0) { + perror("select"); + } + return result; +} +#else +static int socket_event_listen(net_async_block_state async_blocking_state, + my_socket fd) { + int result; + pollfd pfd; + pfd.fd = fd; + switch (async_blocking_state) { + case NET_NONBLOCKING_READ: + pfd.events = POLLIN; + break; + case NET_NONBLOCKING_WRITE: + pfd.events = POLLOUT; + break; + case NET_NONBLOCKING_CONNECT: + pfd.events = POLLIN | POLLOUT; + break; + default: + DBUG_ASSERT(false); + } + result = poll(&pfd, 1, -1); + if (result < 0) { + perror("poll"); + } + return result; +} +#endif + +/* + Below async_mysql_*_wrapper functions are used to measure how much time + each nonblocking call spends before completing the operations. +i*/ +static MYSQL_ROW async_mysql_fetch_row_wrapper(MYSQL_RES *res) { + MYSQL_ROW row; + MYSQL *mysql = res->handle; + AsyncTimer t(__func__); + while (mysql_fetch_row_nonblocking(res, &row) == NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA((&(mysql->net))); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return NULL; + } + return row; +} + +static MYSQL_RES *async_mysql_store_result_wrapper(MYSQL *mysql) { + MYSQL_RES *result; + AsyncTimer t(__func__); + while (mysql_store_result_nonblocking(mysql, &result) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return NULL; + } + return result; +} + +static int async_mysql_real_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_real_query_nonblocking(mysql, query, length)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static int async_mysql_send_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + net_async_status status; + ASYNC_DATA(mysql)->async_query_length = length; + ASYNC_DATA(mysql)->async_query_state = QUERY_SENDING; + + AsyncTimer t(__func__); + while ((status = mysql_send_query_nonblocking(mysql, query, length)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static bool async_mysql_read_query_result_wrapper(MYSQL *mysql) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = (*mysql->methods->read_query_result_nonblocking)(mysql)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return true; + } + if (status == NET_ASYNC_ERROR) { + return true; + } + return false; +} + +static int async_mysql_next_result_wrapper(MYSQL *mysql) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_next_result_nonblocking(mysql)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) + return 1; + else if (status == NET_ASYNC_COMPLETE_WITH_MORE_RESULTS) + return -1; + else + return 0; +} + +static MYSQL *async_mysql_real_connect_wrapper( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, uint port, const char *unix_socket, ulong client_flag) { + net_async_status status; + AsyncTimer t(__func__); + + while ((status = mysql_real_connect_nonblocking( + mysql, host, user, passwd, db, port, unix_socket, client_flag)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + } + if (status == NET_ASYNC_ERROR) + return nullptr; + else + return mysql; +} + +static int async_mysql_query_wrapper(MYSQL *mysql, const char *query) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_real_query_nonblocking(mysql, query, strlen(query))) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static void async_mysql_free_result_wrapper(MYSQL_RES *result) { + AsyncTimer t(__func__); + while (mysql_free_result_nonblocking(result) == NET_ASYNC_NOT_READY) { + t.check(); + MYSQL *mysql = result->handle; + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return; + } + return; +} + +/* + Below are the wrapper functions which are defined on top of standard C APIs + to make a decision on whether to call blocking or non blocking API based on + --async-client option is set or not. +*/ +static MYSQL_ROW mysql_fetch_row_wrapper(MYSQL_RES *res) { + if (enable_async_client) + return async_mysql_fetch_row_wrapper(res); + else + return mysql_fetch_row(res); +} + +static MYSQL_RES *mysql_store_result_wrapper(MYSQL *mysql) { + if (enable_async_client) + return async_mysql_store_result_wrapper(mysql); + else + return mysql_store_result(mysql); +} + +static int mysql_real_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + if (enable_async_client) + return async_mysql_real_query_wrapper(mysql, query, length); + else + return mysql_real_query(mysql, query, length); +} + +static int mysql_send_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + if (enable_async_client) + return async_mysql_send_query_wrapper(mysql, query, length); + else + return mysql_send_query(mysql, query, length); +} + +static bool mysql_read_query_result_wrapper(MYSQL *mysql) { + bool ret; + if (enable_async_client) + ret = async_mysql_read_query_result_wrapper(mysql); + else + ret = mysql_read_query_result(mysql); + return ret; +} + +static int mysql_query_wrapper(MYSQL *mysql, const char *query) { + if (enable_async_client) + return async_mysql_query_wrapper(mysql, query); + else + return mysql_query(mysql, query); +} + +static int mysql_next_result_wrapper(MYSQL *mysql) { + if (enable_async_client) + return async_mysql_next_result_wrapper(mysql); + else + return mysql_next_result(mysql); +} + +static MYSQL *mysql_real_connect_wrapper(MYSQL *mysql, const char *host, + const char *user, const char *passwd, + const char *db, uint port, + const char *unix_socket, + ulong client_flag) { + if (enable_async_client) + return async_mysql_real_connect_wrapper(mysql, host, user, passwd, db, port, + unix_socket, client_flag); + else + return mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket, + client_flag); +} + +static void mysql_free_result_wrapper(MYSQL_RES *result) { + if (enable_async_client) + return async_mysql_free_result_wrapper(result); + else + return mysql_free_result(result); +} + +/* async client test code (end) */ + class LogFile { FILE *m_file; char m_file_name[FN_REFLEN]; @@ -984,13 +1329,13 @@ static void show_query(MYSQL *mysql, const char *query) { if (!mysql) DBUG_VOID_RETURN; - if (mysql_query(mysql, query)) { + if (mysql_query_wrapper(mysql, query)) { log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); DBUG_VOID_RETURN; } - if ((res = mysql_store_result(mysql)) == NULL) { + if ((res = mysql_store_result_wrapper(mysql)) == NULL) { /* No result set returned */ DBUG_VOID_RETURN; } @@ -1003,7 +1348,7 @@ static void show_query(MYSQL *mysql, const char *query) { MYSQL_FIELD *fields = mysql_fetch_fields(res); fprintf(stderr, "=== %s ===\n", query); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { unsigned long *lengths = mysql_fetch_lengths(res); row_num++; @@ -1018,7 +1363,7 @@ static void show_query(MYSQL *mysql, const char *query) { for (i = 0; i < std::strlen(query) + 8; i++) fprintf(stderr, "="); fprintf(stderr, "\n\n"); } - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -1043,13 +1388,13 @@ static void show_warnings_before_error(MYSQL *mysql) { if (!mysql) DBUG_VOID_RETURN; - if (mysql_query(mysql, query)) { + if (mysql_query_wrapper(mysql, query)) { log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); DBUG_VOID_RETURN; } - if ((res = mysql_store_result(mysql)) == NULL) { + if ((res = mysql_store_result_wrapper(mysql)) == NULL) { /* No result set returned */ DBUG_VOID_RETURN; } @@ -1062,7 +1407,7 @@ static void show_warnings_before_error(MYSQL *mysql) { unsigned int num_fields = mysql_num_fields(res); fprintf(stderr, "\nWarnings from just before the error:\n"); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { unsigned int i; unsigned long *lengths = mysql_fetch_lengths(res); @@ -1079,7 +1424,7 @@ static void show_warnings_before_error(MYSQL *mysql) { fprintf(stderr, "\n"); } } - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -2405,8 +2750,8 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); do_eval(&ds_query, query, end, false); - if (mysql_real_query(mysql, ds_query.str, - static_cast(ds_query.length))) { + if (mysql_real_query_wrapper(mysql, ds_query.str, + static_cast(ds_query.length))) { handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), &ds_res); /* If error was acceptable, return empty string */ @@ -2415,11 +2760,11 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { DBUG_VOID_RETURN; } - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("Query '%s' didn't return a result set", ds_query.str); dynstr_free(&ds_query); - if ((row = mysql_fetch_row(res)) && row[0]) { + if ((row = mysql_fetch_row_wrapper(res)) && row[0]) { /* Concatenate all fields in the first row with tab in between and assign that string to the $variable @@ -2486,7 +2831,7 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { } else eval_expr(var, "", 0); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -2638,8 +2983,8 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { die("Mismatched \"'s around query '%s'", ds_query.str); /* Run the query */ - if (mysql_real_query(mysql, ds_query.str, - static_cast(ds_query.length))) { + if (mysql_real_query_wrapper(mysql, ds_query.str, + static_cast(ds_query.length))) { handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), &ds_res); /* If error was acceptable, return empty string */ @@ -2649,7 +2994,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { DBUG_VOID_RETURN; } - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("Query '%s' didn't return a result set", ds_query.str); { @@ -2666,7 +3011,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { } } if (col_no == -1) { - mysql_free_result(res); + mysql_free_result_wrapper(res); die("Could not find column '%s' in the result of '%s'", ds_col.str, ds_query.str); } @@ -2680,7 +3025,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { long rows = 0; const char *value = "No such row"; - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { if (++rows == row_no) { DBUG_PRINT("info", ("At row %ld, column %d is '%s'", row_no, col_no, row[col_no])); @@ -2696,7 +3041,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { eval_expr(var, value, 0, false, false); } dynstr_free(&ds_query); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -4643,23 +4988,23 @@ static void do_wait_for_slave_to_stop( MYSQL_ROW row; int done; - if (mysql_query( + if (mysql_query_wrapper( mysql, "SELECT 'Slave_running' as Variable_name," " IF(count(*)>0,'ON','OFF') as Value FROM" " performance_schema.replication_applier_status ras," "performance_schema.replication_connection_status rcs WHERE " "ras.SERVICE_STATE='ON' AND rcs.SERVICE_STATE='ON'") || - !(res = mysql_store_result(mysql))) + !(res = mysql_store_result_wrapper(mysql))) die("Query failed while probing slave for stop: %s", mysql_error(mysql)); - if (!(row = mysql_fetch_row(res)) || !row[1]) { - mysql_free_result(res); + if (!(row = mysql_fetch_row_wrapper(res)) || !row[1]) { + mysql_free_result_wrapper(res); die("Strange result from query while probing slave for stop"); } done = !std::strcmp(row[1], "OFF"); - mysql_free_result(res); + mysql_free_result_wrapper(res); if (done) break; my_sleep(SLAVE_POLL_INTERVAL); } @@ -4679,14 +5024,14 @@ static void do_sync_with_master2(struct st_command *command, long offset) { sprintf(query_buf, "select master_pos_wait('%s', %ld, %d)", master_pos.file, master_pos.pos + offset, timeout); - if (mysql_query(mysql, query_buf)) + if (mysql_query_wrapper(mysql, query_buf)) die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query_buf); - if (!(row = mysql_fetch_row(res))) { - mysql_free_result(res); + if (!(row = mysql_fetch_row_wrapper(res))) { + mysql_free_result_wrapper(res); die("empty result in %s", query_buf); } @@ -4694,7 +5039,7 @@ static void do_sync_with_master2(struct st_command *command, long offset) { const char *result_str = row[0]; if (result_str) result = atoi(result_str); - mysql_free_result(res); + mysql_free_result_wrapper(res); if (!result_str || result < 0) { /* master_pos_wait returned NULL or < 0 */ @@ -4755,18 +5100,18 @@ static void ndb_wait_for_binlog_injector(void) { MYSQL *mysql = &cur_con->mysql; const char *query; ulong have_ndbcluster; - if (mysql_query(mysql, query = - "select count(*) from information_schema.engines" - " where engine = 'ndbcluster' and" - " support in ('YES', 'DEFAULT')")) + if (mysql_query_wrapper( + mysql, query = "select count(*) from information_schema.engines" + " where engine = 'ndbcluster' and" + " support in ('YES', 'DEFAULT')")) die("'%s' failed: %d %s", query, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query); - if (!(row = mysql_fetch_row(res))) + if (!(row = mysql_fetch_row_wrapper(res))) die("Query '%s' returned empty result", query); have_ndbcluster = std::strcmp(row[0], "1") == 0; - mysql_free_result(res); + mysql_free_result_wrapper(res); if (!have_ndbcluster) { return; @@ -4785,14 +5130,14 @@ static void ndb_wait_for_binlog_injector(void) { "latest_handled_binlog_epoch="; if (count) my_sleep(100 * 1000); /* 100ms */ - if (mysql_query(mysql, query = "show engine ndb status")) + if (mysql_query_wrapper(mysql, query = "show engine ndb status")) die("failed in '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { if (std::strcmp(row[1], binlog) == 0) { const char *status = row[2]; @@ -4846,7 +5191,7 @@ static void ndb_wait_for_binlog_injector(void) { start_handled_binlog_epoch & 0xffffffff); } - mysql_free_result(res); + mysql_free_result_wrapper(res); } } @@ -4862,16 +5207,17 @@ static int do_save_master_pos() { */ ndb_wait_for_binlog_injector(); - if (mysql_query(mysql, query = "show master status")) + if (mysql_query_wrapper(mysql, query = "show master status")) die("failed in 'show master status': %d %s", mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() retuned NULL for '%s'", query); - if (!(row = mysql_fetch_row(res))) die("empty result in show master status"); + if (!(row = mysql_fetch_row_wrapper(res))) + die("empty result in show master status"); my_stpnmov(master_pos.file, row[0], sizeof(master_pos.file) - 1); master_pos.pos = strtoul(row[1], (char **)0, 10); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_RETURN(0); } @@ -5503,7 +5849,7 @@ static void do_shutdown_server(struct st_command *command) { } // Tell server to shutdown if timeout > 0. - if (timeout > 0 && mysql_query(mysql, "shutdown")) { + if (timeout > 0 && mysql_query_wrapper(mysql, "shutdown")) { // Failed to issue shutdown command. error = 1; goto end; @@ -6137,9 +6483,9 @@ static void safe_connect(MYSQL *mysql, const char *name, const char *host, "mysqltest"); mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired_passwords); - while ( - !mysql_real_connect(mysql, host, user, pass, db, port, sock, - CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { + while (!mysql_real_connect_wrapper( + mysql, host, user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { /* Connect failed @@ -6230,8 +6576,9 @@ static int connect_n_handle_errors(struct st_command *command, MYSQL *con, mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest"); mysql_options(con, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired_passwords); - while (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock : 0, - CLIENT_MULTI_STATEMENTS)) { + while (!mysql_real_connect_wrapper(con, host, user, pass, db, port, + sock ? sock : 0, + CLIENT_MULTI_STATEMENTS)) { /* If we have used up all our connections check whether this is expected (by --error). If so, handle the error right away. @@ -6458,6 +6805,10 @@ static void do_connect(struct st_command *command) { opt_protocol = MYSQL_PROTOCOL_PIPE; } + if (opt_compress || con_compress) { + enable_async_client = false; + } + if (opt_protocol) { mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol); /* @@ -7416,6 +7767,9 @@ static struct my_option my_long_options[] = { GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select.", &view_protocol, &view_protocol, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"async-client", '*', "Use async client.", &use_async_client, + &use_async_client, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}}; static void usage() { @@ -7803,7 +8157,7 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) { MYSQL_FIELD *fields = mysql_fetch_fields(res); ulong *lengths; - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { uint i; lengths = mysql_fetch_lengths(res); for (i = 0; i < num_fields; i++) { @@ -8094,16 +8448,16 @@ static int append_warnings(DYNAMIC_STRING *ds, MYSQL *mysql) { DBUG_ASSERT(!mysql_more_results(mysql)); MYSQL_RES *warn_res; - if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) + if (mysql_real_query_wrapper(mysql, "SHOW WARNINGS", 13)) die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); - if (!(warn_res = mysql_store_result(mysql))) + if (!(warn_res = mysql_store_result_wrapper(mysql))) die("Warning count is %u but didn't get any warnings", count); DYNAMIC_STRING ds_warnings; init_dynamic_string(&ds_warnings, "", 1024, 1024); append_result(&ds_warnings, warn_res); - mysql_free_result(warn_res); + mysql_free_result_wrapper(warn_res); if (disable_warnings && (disabled_warnings->count() || enabled_warnings->count())) @@ -8146,8 +8500,9 @@ static void run_query_normal(struct st_connection *cn, } if (flags & QUERY_SEND_FLAG) { - // Send the query - if (mysql_send_query(&cn->mysql, query, static_cast(query_len))) { + /* Send the query */ + if (mysql_send_query_wrapper(&cn->mysql, query, + static_cast(query_len))) { handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; @@ -8160,18 +8515,23 @@ static void run_query_normal(struct st_connection *cn, } do { - // When on first result set, call mysql_read_query_result to - // retrieve answer to the query sent earlier. - if ((counter == 0) && mysql_read_query_result(&cn->mysql)) { - // We've failed to collect the result set. + /* + When on first result set, call mysql_read_query_result_wrapper to + retrieve answer to the query sent earlier + */ + if ((counter == 0) && mysql_read_query_result_wrapper(&cn->mysql)) { + /* we've failed to collect the result set */ cn->pending = true; handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } - // Store the result of the query if it will return any fields. - if (mysql_field_count(mysql) && ((res = mysql_store_result(mysql)) == 0)) { + /* + Store the result of the query if it will return any fields + */ + if (mysql_field_count(mysql) && + ((res = mysql_store_result_wrapper(mysql)) == 0)) { handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; @@ -8211,12 +8571,11 @@ static void run_query_normal(struct st_connection *cn, } if (res) { - mysql_free_result(res); + mysql_free_result_wrapper(res); res = 0; } counter++; - } while (!(error = mysql_next_result(mysql))); - + } while (!(error = mysql_next_result_wrapper(mysql))); if (error > 0) { // We got an error from mysql_next_result, maybe expected. handle_error(command, mysql_errno(mysql), mysql_error(mysql), @@ -8366,7 +8725,7 @@ static void run_query_stmt(MYSQL *mysql, struct st_command *command, append_stmt_result(ds, stmt, fields, num_fields); // Free normal result set with meta data - mysql_free_result(res); + mysql_free_result_wrapper(res); // Clear prepare warnings if there are execute warnings, // since they are probably duplicated. @@ -8479,7 +8838,7 @@ static int util_query(MYSQL *org_mysql, const char *query) { cur_con->util_mysql = mysql; } - DBUG_RETURN(mysql_query(mysql, query)); + DBUG_RETURN(mysql_query_wrapper(mysql, query)); } /* @@ -9141,6 +9500,7 @@ int main(int argc, char **argv) { var_set_int("$ENABLE_WARNINGS", 1); var_set_int("$ENABLE_INFO", 0); var_set_int("$ENABLE_METADATA", 0); + var_set_int("$ENABLE_ASYNC_CLIENT", 0); DBUG_PRINT("info", ("result_file: '%s'", result_file_name ? result_file_name : "")); @@ -9158,6 +9518,7 @@ int main(int argc, char **argv) { log_file.open(opt_logdir, result_file_name, ".log"); verbose_msg("Logging to '%s'.", log_file.file_name()); + enable_async_client = use_async_client; /* Creating a temporary log file using current file name if @@ -9220,6 +9581,9 @@ int main(int argc, char **argv) { opt_ssl_mode = SSL_MODE_VERIFY_CA; } #endif + if (opt_compress) { + enable_async_client = false; + } if (SSL_SET_OPTIONS(&con->mysql)) die("%s", SSL_SET_OPTIONS_ERROR); #if defined(_WIN32) if (shared_memory_base_name) @@ -9639,8 +10003,8 @@ int main(int argc, char **argv) { cleanup_and_exit(1); } - handle_command_error(command, - mysql_query(&cur_con->mysql, "shutdown")); + handle_command_error( + command, mysql_query_wrapper(&cur_con->mysql, "shutdown")); break; case Q_SHUTDOWN_SERVER: do_shutdown_server(command); @@ -9678,9 +10042,16 @@ int main(int argc, char **argv) { break; case Q_ENABLE_RECONNECT: set_reconnect(&cur_con->mysql, 1); + enable_async_client = false; /* Close any open statements - no reconnect, need new prepare */ close_statements(); break; + case Q_ENABLE_ASYNC_CLIENT: + set_property(command, P_ASYNC, 1); + break; + case Q_DISABLE_ASYNC_CLIENT: + set_property(command, P_ASYNC, 0); + break; case Q_DISABLE_TESTCASE: if (testcase_disabled == 0) do_disable_testcase(command); diff --git a/include/errmsg.h b/include/errmsg.h index 550233d02b13..419f03bd7075 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -1,7 +1,7 @@ #ifndef ERRMSG_INCLUDED #define ERRMSG_INCLUDED -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -123,7 +123,8 @@ extern const char *client_errors[]; /* Error messages */ #define CR_INSECURE_API_ERR 2062 #define CR_FILE_NAME_TOO_LONG 2063 #define CR_SSL_FIPS_MODE_ERR 2064 -#define CR_ERROR_LAST /*Copy last error nr:*/ 2064 +#define CR_COMPRESSION_NOT_SUPPORTED 2065 +#define CR_ERROR_LAST /*Copy last error nr:*/ 2065 /* Add error numbers before CR_ERROR_LAST and change it accordingly. */ /* Visual Studio requires '__inline' for C code */ diff --git a/include/mysql.h b/include/mysql.h index b4a7b15645c5..561960cd9250 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -465,6 +465,20 @@ int STDCALL mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql); MYSQL_RES *STDCALL mysql_use_result(MYSQL *mysql); +enum net_async_status STDCALL mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, + unsigned long clientflag); +enum net_async_status STDCALL mysql_send_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status STDCALL mysql_real_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status STDCALL +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result); +enum net_async_status STDCALL mysql_next_result_nonblocking(MYSQL *mysql); +enum net_async_status STDCALL mysql_select_db_nonblocking(MYSQL *mysql, + const char *db, + bool *error); void STDCALL mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); @@ -510,12 +524,16 @@ int STDCALL mysql_options4(MYSQL *mysql, enum mysql_option option, int STDCALL mysql_get_option(MYSQL *mysql, enum mysql_option option, const void *arg); void STDCALL mysql_free_result(MYSQL_RES *result); +enum net_async_status STDCALL mysql_free_result_nonblocking(MYSQL_RES *result); void STDCALL mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result); +enum net_async_status STDCALL mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row); + unsigned long *STDCALL mysql_fetch_lengths(MYSQL_RES *result); MYSQL_FIELD *STDCALL mysql_fetch_field(MYSQL_RES *result); MYSQL_RES *STDCALL mysql_list_fields(MYSQL *mysql, const char *table, diff --git a/include/mysql.h.pp b/include/mysql.h.pp index 1b8bc72a0cd5..e34b34ef478c 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -264,16 +264,31 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct auth_plugin_t { int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; const char *license; void *mysql_api; int (*init)(char *, size_t, int, va_list); int (*deinit)(void); int (*options)(const char *option, const void *); int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; typedef struct auth_plugin_t st_mysql_client_plugin_AUTHENTICATION; struct st_mysql_client_plugin *mysql_load_plugin(struct MYSQL *mysql, @@ -309,7 +324,7 @@ void finish_client_errs(void); extern const char *client_errors[]; static inline const char *ER_CLIENT(int client_errno) { - if (client_errno >= 2000 && client_errno <= 2064) + if (client_errno >= 2000 && client_errno <= 2065) return client_errors[client_errno - 2000]; return client_errors[2000]; } @@ -561,6 +576,20 @@ int mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); MYSQL_RES * mysql_store_result(MYSQL *mysql); MYSQL_RES * mysql_use_result(MYSQL *mysql); +enum net_async_status mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, + unsigned long clientflag); +enum net_async_status mysql_send_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status mysql_real_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result); +enum net_async_status mysql_next_result_nonblocking(MYSQL *mysql); +enum net_async_status mysql_select_db_nonblocking(MYSQL *mysql, + const char *db, + bool *error); void mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); int mysql_session_track_get_first(MYSQL *mysql, @@ -600,12 +629,15 @@ int mysql_get_option(MYSQL *mysql, enum mysql_option option, const void *arg); void mysql_free_result(MYSQL_RES *result); +enum net_async_status mysql_free_result_nonblocking(MYSQL_RES *result); void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); +enum net_async_status mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row); unsigned long * mysql_fetch_lengths(MYSQL_RES *result); MYSQL_FIELD * mysql_fetch_field(MYSQL_RES *result); MYSQL_RES * mysql_list_fields(MYSQL *mysql, const char *table, diff --git a/include/mysql/client_authentication.h b/include/mysql/client_authentication.h index cc366f54f92a..c841b9753314 100644 --- a/include/mysql/client_authentication.h +++ b/include/mysql/client_authentication.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -35,5 +35,9 @@ int sha256_password_deinit(void); int caching_sha2_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); int caching_sha2_password_init(char *, size_t, int, va_list); int caching_sha2_password_deinit(void); - +net_async_status caching_sha2_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *res); +net_async_status sha256_password_auth_client_nonblocking(MYSQL_PLUGIN_VIO *vio, + MYSQL *mysql, + int *res); #endif diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index 3470a56269a7..96e481b33ddd 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -1,5 +1,5 @@ #ifndef MYSQL_CLIENT_PLUGIN_INCLUDED -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -75,7 +75,7 @@ extern "C" { #define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 #define MYSQL_CLIENT_TRACE_PLUGIN 3 -#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0101 #define MYSQL_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION 0x0100 #define MYSQL_CLIENT_MAX_PLUGINS 4 @@ -113,6 +113,9 @@ struct MYSQL; struct auth_plugin_t { MYSQL_CLIENT_PLUGIN_HEADER int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; // Needed for the mysql_declare_client_plugin() macro. Do not use elsewhere. diff --git a/include/mysql/client_plugin.h.pp b/include/mysql/client_plugin.h.pp index 07238a9398c9..fff16b75b669 100644 --- a/include/mysql/client_plugin.h.pp +++ b/include/mysql/client_plugin.h.pp @@ -13,16 +13,31 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct auth_plugin_t { int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; const char *license; void *mysql_api; int (*init)(char *, size_t, int, va_list); int (*deinit)(void); int (*options)(const char *option, const void *); int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; typedef struct auth_plugin_t st_mysql_client_plugin_AUTHENTICATION; struct st_mysql_client_plugin *mysql_load_plugin(struct MYSQL *mysql, diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp index 740ac7c2a123..ed21058d520c 100644 --- a/include/mysql/plugin_auth.h.pp +++ b/include/mysql/plugin_auth.h.pp @@ -130,12 +130,24 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct MYSQL_SERVER_AUTH_INFO { char *user_name; diff --git a/include/mysql/plugin_auth_common.h b/include/mysql/plugin_auth_common.h index 128e406f1710..47bf4bc32533 100644 --- a/include/mysql/plugin_auth_common.h +++ b/include/mysql/plugin_auth_common.h @@ -1,5 +1,5 @@ #ifndef MYSQL_PLUGIN_AUTH_COMMON_INCLUDED -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -126,6 +126,14 @@ struct MYSQL_PLUGIN_VIO_INFO { #endif }; +/* state of an asynchronous operation */ +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; + /** Provides plugin access to communication channel */ @@ -151,6 +159,24 @@ typedef struct MYSQL_PLUGIN_VIO { void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + /** + Non blocking version of read_packet. This function points buf to starting + position of incoming packet. When this function returns NET_ASYNC_NOT_READY + plugin should call this function again until all incoming packets are read. + If return code is NET_ASYNC_COMPLETE, plugin can do further processing of + read packets. + */ + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + /** + Non blocking version of write_packet. Sends data available in pkt of length + pkt_len to server in asynchrnous way. + */ + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); + } MYSQL_PLUGIN_VIO; #endif diff --git a/include/mysql_async.h b/include/mysql_async.h new file mode 100644 index 000000000000..0f8368e722ed --- /dev/null +++ b/include/mysql_async.h @@ -0,0 +1,199 @@ +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + 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, version 2.0, 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 */ + +#ifndef MYSQL_ASYNC_INCLUDED +#define MYSQL_ASYNC_INCLUDED + +#define MYSQL_ASYNC_INCLUDED + +#include + +/* + NOTE:this file should not be included as part of packaging. +*/ +/* Async MySQL extension fields here. */ + +/* + This enum is to represent different asynchronous operations like reading the + network, writing to network, idle state, or complete state. +*/ +enum net_async_operation { + NET_ASYNC_OP_IDLE = 0, /* default state */ + NET_ASYNC_OP_READING, /* used by my_net_read calls */ + NET_ASYNC_OP_WRITING, /* used by my_net_write calls */ + NET_ASYNC_OP_COMPLETE /* network read or write is complete */ +}; + +/* Reading a packet is a multi-step process, so we have a state machine. */ +enum net_async_read_packet_state { + NET_ASYNC_PACKET_READ_IDLE = 0, /* default packet read state */ + NET_ASYNC_PACKET_READ_HEADER, /* read packet header */ + NET_ASYNC_PACKET_READ_BODY, /* read packet contents */ + NET_ASYNC_PACKET_READ_COMPLETE /* state to define if packet is + completely read */ +}; + +/* Different states when reading a query result. */ +enum net_read_query_result_status { + NET_ASYNC_READ_QUERY_RESULT_IDLE = 0, /* default state */ + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT, /* read Ok or read field + count sent as part of + COM_QUERY */ + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO /* read result of above + COM_* command */ +}; + +/* Sending a command involves the write as well as reading the status. */ +enum net_send_command_status { + NET_ASYNC_SEND_COMMAND_IDLE = 0, /* default send command state */ + NET_ASYNC_SEND_COMMAND_WRITE_COMMAND, /* send COM_* command */ + NET_ASYNC_SEND_COMMAND_READ_STATUS /* read result of above COM_* + command */ +}; + +/* + Async operations are broadly classified into 3 phases: + Connection phase, phase of sending data to server (which is writing phase) + and reading data from server (which is reading phase). Below enum describes + the same +*/ +enum net_async_block_state { + NET_NONBLOCKING_CONNECT = 0, + NET_NONBLOCKING_READ, + NET_NONBLOCKING_WRITE +}; + +struct io_vec { + void *iov_base; /* Starting address */ + size_t iov_len; /* Number of bytes to transfer */ +}; + +typedef struct NET_ASYNC { + /* The position in buff we continue reads from when data is next available */ + unsigned char *cur_pos; + /** Blocking state */ + enum net_async_block_state async_blocking_state; + /** Our current operation */ + enum net_async_operation async_operation; + /** How many bytes we want to read */ + size_t async_bytes_wanted; + /* + Simple state to know if we're reading the first row, and + command/query statuses. + */ + bool read_rows_is_first_read; + enum net_send_command_status async_send_command_status; + enum net_read_query_result_status async_read_query_result_status; + + /* State when waiting on an async read */ + enum net_async_read_packet_state async_packet_read_state; + /* Size of the packet we're currently reading */ + size_t async_packet_length; + + /* + Headers and vector for our async writes; see net_serv.c for + detailed description. + */ + unsigned char *async_write_headers; + struct io_vec *async_write_vector; + size_t async_write_vector_size; + size_t async_write_vector_current; + unsigned char + inline_async_write_header[NET_HEADER_SIZE + COMP_HEADER_SIZE + 1 + 1]; + struct io_vec inline_async_write_vector[3]; + + /* State for reading responses that are larger than MAX_PACKET_LENGTH */ + unsigned long async_multipacket_read_saved_whereb; + unsigned long async_multipacket_read_total_len; + bool async_multipacket_read_started; +} NET_ASYNC; + +struct NET_EXTENSION { + NET_ASYNC *net_async_context; +}; + +NET_EXTENSION *net_extension_init(); +void net_extension_free(NET *); + +#define NET_EXTENSION_PTR(N) \ + ((NET_EXTENSION *)((N)->extension ? (N)->extension : NULL)) + +#define NET_ASYNC_DATA(M) \ + ((NET_EXTENSION_PTR(M)) ? (NET_EXTENSION_PTR(M)->net_async_context) : NULL) + +/* + Asynchronous operations are broadly classified into 2 categories. + 1. Connection + 2. Query execution + This classification is defined in below enum +*/ +enum mysql_async_operation_status { + ASYNC_OP_UNSET = 0, + ASYNC_OP_CONNECT, + ASYNC_OP_QUERY +}; + +/* + Query execution in an asynchronous fashion is broadly divided into 3 states + which is described in below enum +*/ +enum mysql_async_query_state_enum { + QUERY_IDLE = 0, + QUERY_SENDING, + QUERY_READING_RESULT +}; + +typedef struct MYSQL_ASYNC { + /* Buffer storing the rows result for cli_read_rows_nonblocking */ + MYSQL_DATA *rows_result_buffer; + /* a pointer to keep track of the previous row of the current result row */ + MYSQL_ROWS **prev_row_ptr; + /* Context needed to track the state of a connection being established */ + struct mysql_async_connect *connect_context; + /* Status of the current async op */ + enum mysql_async_operation_status async_op_status; + /* Size of the current running async query */ + size_t async_query_length; + /* If a query is running, this is its state */ + enum mysql_async_query_state_enum async_query_state; + /* context needed to support metadata read operation */ + unsigned long *async_read_metadata_field_len; + MYSQL_FIELD *async_read_metadata_fields; + MYSQL_ROWS async_read_metadata_data; + unsigned int async_read_metadata_cur_field; + /* a pointer to keep track of the result sets */ + struct MYSQL_RES *async_store_result_result; +} MYSQL_ASYNC; + +enum net_async_status my_net_write_nonblocking(NET *net, + const unsigned char *packet, + size_t len, bool *res); +enum net_async_status net_write_command_nonblocking( + NET *net, unsigned char command, const unsigned char *prefix, + size_t prefix_len, const unsigned char *packet, size_t packet_len, + bool *res); +enum net_async_status my_net_read_nonblocking(NET *net, unsigned long *len_ptr, + unsigned long *complen_ptr); + +int mysql_get_socket_descriptor(MYSQL *mysql); + +#endif /* MYSQL_ASYNC_INCLUDED */ diff --git a/include/mysql_com.h b/include/mysql_com.h index ced715c2328a..ecdb774ad771 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -1001,7 +1001,6 @@ bool net_write_command(struct NET *net, unsigned char command, bool net_write_packet(struct NET *net, const unsigned char *packet, size_t length); unsigned long my_net_read(struct NET *net); - void my_net_set_write_timeout(struct NET *net, unsigned int timeout); void my_net_set_read_timeout(struct NET *net, unsigned int timeout); void my_net_set_retry_count(struct NET *net, unsigned int retry_count); diff --git a/include/sql_common.h b/include/sql_common.h index 2e9f4c832e7d..48c0b21689dc 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -94,6 +94,8 @@ struct st_mysql_trace_info; struct MYSQL_EXTENSION { struct st_mysql_trace_info *trace_data; STATE_INFO state_change; + /* Struct to track the state of asynchronous operations */ + struct MYSQL_ASYNC *mysql_async_context; }; /* "Constructor/destructor" for MYSQL extension structure. */ @@ -109,6 +111,9 @@ void mysql_extension_free(MYSQL_EXTENSION *); ? (H)->extension \ : ((H)->extension = mysql_extension_init(H)))) +#define ASYNC_DATA(M) \ + (NULL != (M) ? (MYSQL_EXTENSION_PTR(M)->mysql_async_context) : NULL) + struct st_mysql_options_extention { char *plugin_dir; char *default_auth; @@ -151,6 +156,21 @@ struct MYSQL_METHODS { int (*read_rows_from_cursor)(MYSQL_STMT *stmt); void (*free_rows)(MYSQL_DATA *cur); #endif + enum net_async_status (*read_query_result_nonblocking)(MYSQL *mysql); + enum net_async_status (*advanced_command_nonblocking)( + MYSQL *mysql, enum enum_server_command command, + const unsigned char *header, unsigned long header_length, + const unsigned char *arg, unsigned long arg_length, bool skip_check, + MYSQL_STMT *stmt, bool *error); + enum net_async_status (*read_rows_nonblocking)(MYSQL *mysql, + MYSQL_FIELD *mysql_fields, + unsigned int fields, + MYSQL_DATA **result); + enum net_async_status (*flush_use_result_nonblocking)(MYSQL *mysql, + bool flush_all_results); + enum net_async_status (*next_result_nonblocking)(MYSQL *mysql); + enum net_async_status (*read_change_user_result_nonblocking)(MYSQL *mysql, + ulong *res); }; #define simple_command(mysql, command, arg, length, skip_check) \ @@ -159,6 +179,11 @@ struct MYSQL_METHODS { length, skip_check, NULL) \ : (set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate), \ 1)) +#define simple_command_nonblocking(mysql, command, arg, length, skip_check, \ + error) \ + (*(mysql)->methods->advanced_command_nonblocking)( \ + mysql, command, 0, 0, arg, length, skip_check, NULL, error) + #define stmt_command(mysql, command, arg, length, stmt) \ ((mysql)->methods \ ? (*(mysql)->methods->advanced_command)(mysql, command, 0, 0, arg, \ @@ -186,6 +211,9 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, const unsigned char *arg, size_t arg_length, bool skip_check, MYSQL_STMT *stmt); unsigned long cli_safe_read(MYSQL *mysql, bool *is_data_packet); +enum net_async_status cli_safe_read_nonblocking(MYSQL *mysql, + bool *is_data_packet, + ulong *res); unsigned long cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet); void net_clear_error(NET *net); diff --git a/include/violite.h b/include/violite.h index c311747d6c7b..c11c77b42842 100644 --- a/include/violite.h +++ b/include/violite.h @@ -129,6 +129,10 @@ enum enum_vio_io_event { VIO_IO_EVENT_CONNECT }; +#define VIO_SOCKET_ERROR ((size_t)-1) +#define VIO_SOCKET_WANT_READ ((size_t)-2) +#define VIO_SOCKET_WANT_WRITE ((size_t)-3) + #define VIO_LOCALHOST 1 /* a localhost connection */ #define VIO_BUFFERED_READ 2 /* use buffered read */ #define VIO_READ_BUFFER_SIZE 16384 /* size of read buffer */ @@ -154,6 +158,9 @@ void vio_delete(MYSQL_VIO vio); int vio_shutdown(MYSQL_VIO vio); bool vio_reset(MYSQL_VIO vio, enum enum_vio_type type, my_socket sd, void *ssl, uint flags); +bool vio_is_blocking(Vio *vio); +int vio_set_blocking(Vio *vio, bool set_blocking_mode); +int vio_set_blocking_flag(Vio *vio, bool set_blocking_flag); size_t vio_read(MYSQL_VIO vio, uchar *buf, size_t size); size_t vio_read_buff(MYSQL_VIO vio, uchar *buf, size_t size); size_t vio_write(MYSQL_VIO vio, const uchar *buf, size_t size); @@ -186,7 +193,7 @@ ssize_t vio_pending(MYSQL_VIO vio); int vio_timeout(MYSQL_VIO vio, uint which, int timeout_sec); /* Connect to a peer. */ bool vio_socket_connect(MYSQL_VIO vio, struct sockaddr *addr, socklen_t len, - int timeout); + bool nonblocking, int timeout); bool vio_get_normalized_ip_string(const struct sockaddr *addr, size_t addr_length, char *ip_string, @@ -258,7 +265,7 @@ struct st_VioSSLFd { int sslaccept(struct st_VioSSLFd *, MYSQL_VIO, long timeout, unsigned long *errptr); int sslconnect(struct st_VioSSLFd *, MYSQL_VIO, long timeout, - unsigned long *errptr); + unsigned long *errptr, SSL **ssl); struct st_VioSSLFd *new_VioSSLConnectorFd( const char *key_file, const char *cert_file, const char *ca_file, @@ -301,6 +308,9 @@ void vio_end(void); (vio)->peer_addr(vio, buf, prt, buflen) #define vio_io_wait(vio, event, timeout) (vio)->io_wait(vio, event, timeout) #define vio_is_connected(vio) (vio)->is_connected(vio) +#define vio_is_blocking(vio) (vio)->is_blocking(vio) +#define vio_set_blocking(vio, val) (vio)->set_blocking(vio, val) +#define vio_set_blocking_flag(vio, val) (vio)->set_blocking_flag(vio, val) #endif /* !defined(DONT_MAP_VIO) */ /* This enumerator is used in parser - should be always visible */ @@ -410,7 +420,13 @@ struct Vio { HANDLE event_conn_closed = {nullptr}; size_t shared_memory_remain = {0}; char *shared_memory_pos = {nullptr}; + #endif /* _WIN32 */ + bool (*is_blocking)(Vio *vio) = {nullptr}; + int (*set_blocking)(Vio *vio, bool val) = {nullptr}; + int (*set_blocking_flag)(Vio *vio, bool val) = {nullptr}; + /* Indicates whether socket or SSL based communication is blocking or not. */ + bool is_blocking_flag = {true}; private: friend Vio *internal_vio_create(uint flags); diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt index cfba88db3804..ff78e387e610 100644 --- a/libmysql/CMakeLists.txt +++ b/libmysql/CMakeLists.txt @@ -140,6 +140,13 @@ mysql_session_track_get_first mysql_session_track_get_next mysql_reset_server_public_key mysql_result_metadata +mysql_real_connect_nonblocking +mysql_send_query_nonblocking +mysql_real_query_nonblocking +mysql_store_result_nonblocking +mysql_next_result_nonblocking +mysql_fetch_row_nonblocking +mysql_free_result_nonblocking CACHE INTERNAL "Functions exported by client API" diff --git a/libmysql/authentication_ldap/auth_ldap_sasl_client.cc b/libmysql/authentication_ldap/auth_ldap_sasl_client.cc index 6788d744020f..0dcd9c4a0d8a 100644 --- a/libmysql/authentication_ldap/auth_ldap_sasl_client.cc +++ b/libmysql/authentication_ldap/auth_ldap_sasl_client.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -360,4 +360,4 @@ static int sasl_authenticate(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "authentication_ldap_sasl_client", "Yashwant Sahu", "LDAP SASL Client Authentication Plugin", {0, 1, 0}, "PROPRIETARY", NULL, NULL, NULL, NULL, - sasl_authenticate mysql_end_client_plugin; + sasl_authenticate, NULL mysql_end_client_plugin; diff --git a/libmysql/errmsg.cc b/libmysql/errmsg.cc index 638dc4e66fe8..a7d8d4ef971d 100644 --- a/libmysql/errmsg.cc +++ b/libmysql/errmsg.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -105,6 +105,7 @@ const char *client_errors[] = { "Insecure API function call: '%s' Use instead: '%s'", "File name is too long", "Set FIPS mode ON/STRICT failed", + "Compression protocol not supported with asynchronous protocol", ""}; static const char *get_client_errmsg(int nr) { diff --git a/libmysql/libmysql.cc b/libmysql/libmysql.cc index 50258149ab96..784b3686cfe7 100644 --- a/libmysql/libmysql.cc +++ b/libmysql/libmysql.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -4542,6 +4542,40 @@ int STDCALL mysql_next_result(MYSQL *mysql) { DBUG_RETURN(-1); /* No more results */ } +/* + This API reads the next statement result and returns a status to indicate + whether more results exist + + @param[in] mysql connection handle + + @retval NET_ASYNC_ERROR Error + @retval NET_ASYNC_NOT_READY reading next result not + yet completed, call + this API again + @retval NET_ASYNC_COMPLETE finished reading result + @retval NET_ASYNC_COMPLETE_WITH_MORE_RESULTS status to indicate if + more results exist +*/ +net_async_status STDCALL mysql_next_result_nonblocking(MYSQL *mysql) { + DBUG_ENTER(__func__); + net_async_status status; + if (mysql->status != MYSQL_STATUS_READY) { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_ERROR); + } + net_clear_error(&mysql->net); + mysql->affected_rows = ~(my_ulonglong)0; + + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { + status = (*mysql->methods->next_result_nonblocking)(mysql); + DBUG_RETURN(status); + } else { + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + } + + DBUG_RETURN(NET_ASYNC_COMPLETE_WITH_MORE_RESULTS); /* No more results */ +} + int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) { MYSQL *mysql = stmt->mysql; int rc; diff --git a/mysql-test/mysql-test-run.dox b/mysql-test/mysql-test-run.dox index e49ada0fdb3d..d83f5bf3d105 100644 --- a/mysql-test/mysql-test-run.dox +++ b/mysql-test/mysql-test-run.dox @@ -3117,6 +3117,12 @@ Display a help message and exit. +
  • + `--async-client` + + Enable asynchronous communication support in mysql protocol. +
  • +
  • `--basedir`=dir_name, -b dir_name diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 5555abfde75b..6067464f013a 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -88,6 +88,7 @@ my $opt_callgrind; my $opt_charset_for_testdb; my $opt_compress; +my $opt_async_client; my $opt_cursor_protocol; my $opt_debug_common; my $opt_do_suite; @@ -1306,6 +1307,7 @@ sub print_global_resfile { resfile_global("callgrind", $opt_callgrind ? 1 : 0); resfile_global("check-testcases", $opt_check_testcases ? 1 : 0); resfile_global("compress", $opt_compress ? 1 : 0); + resfile_global("async-client", $opt_async_client ? 1 : 0); resfile_global("cursor-protocol", $opt_cursor_protocol ? 1 : 0); resfile_global("debug", $opt_debug ? 1 : 0); resfile_global("fast", $opt_fast ? 1 : 0); @@ -1368,6 +1370,7 @@ sub command_line_setup { my %options = ( # Control what engine/variation to run 'compress' => \$opt_compress, + 'async-client' => \$opt_async_client, 'cursor-protocol' => \$opt_cursor_protocol, 'explain-protocol' => \$opt_explain_protocol, 'json-explain-protocol' => \$opt_json_explain_protocol, @@ -6596,6 +6599,10 @@ ($) mtr_add_arg($args, "--compress"); } + if ($opt_async_client ) { + mtr_add_arg($args, "--async-client"); + } + if ($opt_sleep) { mtr_add_arg($args, "--sleep=%d", $opt_sleep); } @@ -7148,6 +7155,7 @@ ($) combination= Use at least twice to run tests with specified options to mysqld. compress Use the compressed protocol between client and server. + async-client Use async-client with select() to run the test case cursor-protocol Use the cursor protocol between client and server (implies --ps-protocol). defaults-extra-file= diff --git a/mysql-test/r/async_client.result b/mysql-test/r/async_client.result new file mode 100644 index 000000000000..2736fb54d0ac --- /dev/null +++ b/mysql-test/r/async_client.result @@ -0,0 +1,95 @@ +# +# WL#11381: Add asynchronous support into the mysql protocol +# +# case1: default connection with default authentication plugin +CREATE DATABASE wl11381; +CREATE USER caching_sha2@localhost IDENTIFIED BY 'caching'; +GRANT ALL ON *.* TO caching_sha2@localhost; +# connect as caching_sha2 +SELECT USER(), DATABASE(); +USER() DATABASE() +caching_sha2@localhost wl11381 +USE wl11381; +CREATE TABLE t1(i INT, j VARCHAR(2048)); +INSERT INTO t1 VALUES(1,repeat('a',1000)),(2,repeat('def',600)); +SELECT * FROM t1; +i j +1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +2 defdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdef +# case2: request a large packet +SET GLOBAL max_allowed_packet=4*1024; +Warnings: +Warning 1708 The value of 'max_allowed_packet' should be no less than the value of 'net_buffer_length' +SELECT SPACE(@@global.max_allowed_packet); +SPACE(@@global.max_allowed_packet) + +SET GLOBAL max_allowed_packet=default; +# connect with wrong password +connect(localhost,caching_sha2,caching1,wl11381,MASTER_PORT,MASTER_SOCKET); +ERROR 28000: Access denied for user 'caching_sha2'@'localhost' (using password: YES) +# check for timeout +SELECT USER(); +USER() +caching_sha2@localhost +SET @@SESSION.wait_timeout = 2; +SELECT SLEEP(10); +SLEEP(10) +0 +# Check that ssl_con has not disconnected +SELECT 1; +1 +1 +# lock the account +ALTER USER caching_sha2@localhost ACCOUNT LOCK; +# account is locked so connect should fail +connect(localhost,caching_sha2,caching,wl11381,MASTER_PORT,MASTER_SOCKET); +ERROR HY000: Access denied for user 'caching_sha2'@'localhost'. Account is locked. +# lock the account +ALTER USER caching_sha2@localhost ACCOUNT UNLOCK; +# account is unlocked so connect should pass +SELECT "connect succeeded after account is unlocked"; +connect succeeded after account is unlocked +connect succeeded after account is unlocked +# restart: --ssl=off --loose-caching_sha2_password_private_key_path=MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-caching_sha2_password_public_key_path=MYSQL_TEST_DIR/std_data/rsa_public_key.pem +# connect as caching_sha2 with SSL disabled +SELECT USER(); +USER() +caching_sha2@localhost +# change to empty password +ALTER USER caching_sha2@localhost IDENTIFIED BY ''; +# connect as caching_sha2 with SSL disabled and empty password +SELECT USER(); +USER() +caching_sha2@localhost +# case3: authenticate user with sha256_password +CREATE USER sha256@localhost IDENTIFIED WITH 'sha256_password' BY 'auth_string'; +# restart: --default-authentication-plugin=sha256_password +# connect as sha256 +SELECT USER(); +USER() +sha256@localhost +# change to empty password +ALTER USER sha256@localhost IDENTIFIED BY ''; +# connect with empty password +SELECT USER(); +USER() +sha256@localhost +# restart: --default-authentication-plugin=sha256_password --ssl=off --loose-sha256_password_private_key_path=MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-sha256_password_public_key_path=MYSQL_TEST_DIR/std_data/rsa_public_key.pem +# connect with wrong password and SSL disabled +connect(localhost,sha256,auth_string,test,MASTER_PORT,MASTER_SOCKET); +ERROR 28000: Access denied for user 'sha256'@'localhost' (using password: YES) +# case4: authenticate user with mysql_native_password +CREATE USER native_user@localhost IDENTIFIED WITH 'mysql_native_password' BY 'native'; +# restart: --default-authentication-plugin=mysql_native_password +# connect as native_user +SELECT USER(); +USER() +native_user@localhost +# Change to empty password +ALTER USER native_user@localhost IDENTIFIED BY ''; +# connect as native_user with empty password +SELECT USER(); +USER() +native_user@localhost +DROP USER sha256@localhost, native_user@localhost, caching_sha2@localhost; +DROP DATABASE wl11381; diff --git a/mysql-test/r/big_packets.result b/mysql-test/r/big_packets.result new file mode 100644 index 000000000000..dac269c8e023 --- /dev/null +++ b/mysql-test/r/big_packets.result @@ -0,0 +1,6 @@ +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +91e7da4395bd55166023dcddc56c69b9 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +33554448 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/r/big_packets_boundary.result b/mysql-test/r/big_packets_boundary.result new file mode 100644 index 000000000000..4e9309c74093 --- /dev/null +++ b/mysql-test/r/big_packets_boundary.result @@ -0,0 +1,96 @@ +MD5("zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +c871179747a53194105131ef0ba4328d +LENGTH("zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777209 +Q zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +8de4762b7dee325e24af556907f303e4 +LENGTH("zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777210 +Q zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +830292a7f4c58ab20717bc6c8d0600da +LENGTH("zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777211 +Q zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +7256c4833815dd42ad9b046b5ca053b0 +LENGTH("zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777212 +Q zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +c57ddebd2c507217cf52dec95e123d11 +LENGTH("zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777213 +Q zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16658c20e22780cefdfd281c6b650c44 +LENGTH("zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777214 +Q zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +6db235989af46461a519808ea756a0b9 +LENGTH("zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777215 +Q zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +707fcc0cc24f79bc3d2e11a65f61fcee +LENGTH("zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777216 +Q zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +01dcbdde50804add0f06a21eaa3b4bb5 +LENGTH("zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777217 +Q zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +a4d792fcfed7cab49499a04aaf78d3ca +LENGTH("zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777218 +Q zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +5fb1131f61d61f5a5fff367293ec3adb +LENGTH("zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777219 +Q zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +b5f8256cc4dac2694cd2b9296370e4bb +LENGTH("zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777220 +Q zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +db5649bd5c180405e68301b24bc4182d +LENGTH("zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777221 +Q zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +50d94b74220b7aceed6810e8e7558a3e +LENGTH("zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777222 +Q zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +badcb2ccca2595fd66d07e2ba8457c6b +LENGTH("zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777223 +Q zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +bf6cd230abc95d80ecae961868db39f5 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777224 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/r/ssl-big-packet.result b/mysql-test/r/ssl-big-packet.result new file mode 100644 index 000000000000..dac269c8e023 --- /dev/null +++ b/mysql-test/r/ssl-big-packet.result @@ -0,0 +1,6 @@ +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +91e7da4395bd55166023dcddc56c69b9 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +33554448 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/t/async_client.test b/mysql-test/t/async_client.test new file mode 100644 index 000000000000..7a6afbb81f0a --- /dev/null +++ b/mysql-test/t/async_client.test @@ -0,0 +1,168 @@ +--source include/have_ssl.inc +--source include/have_openssl.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +--echo # +--echo # WL#11381: Add asynchronous support into the mysql protocol +--echo # + +--echo # case1: default connection with default authentication plugin +CREATE DATABASE wl11381; +CREATE USER caching_sha2@localhost IDENTIFIED BY 'caching'; +GRANT ALL ON *.* TO caching_sha2@localhost; + +--enable_async_client + +--echo # connect as caching_sha2 +connect(caching_con1,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT USER(), DATABASE(); + +USE wl11381; +CREATE TABLE t1(i INT, j VARCHAR(2048)); +INSERT INTO t1 VALUES(1,repeat('a',1000)),(2,repeat('def',600)); +SELECT * FROM t1; + +--echo # case2: request a large packet +SET GLOBAL max_allowed_packet=4*1024; +SELECT SPACE(@@global.max_allowed_packet); +SET GLOBAL max_allowed_packet=default; + +connection default; +disconnect caching_con1; + +--echo # connect with wrong password +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error ER_ACCESS_DENIED_ERROR +connect(caching_con2,localhost,caching_sha2,caching1,wl11381,,,SSL); + +--echo # check for timeout +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT USER(); +SET @@SESSION.wait_timeout = 2; +SELECT SLEEP(10); + +--echo # Check that ssl_con has not disconnected +SELECT 1; + +connection default; +disconnect caching_con3; + +--disable_async_client +--echo # lock the account +ALTER USER caching_sha2@localhost ACCOUNT LOCK; +--enable_async_client + +--echo # account is locked so connect should fail +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error 3118 +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); + +connection default; +--disable_async_client +--echo # lock the account +ALTER USER caching_sha2@localhost ACCOUNT UNLOCK; +--enable_async_client + +--echo # account is unlocked so connect should pass +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT "connect succeeded after account is unlocked"; + +connection default; +disconnect caching_con3; + +--disable_async_client +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--let $restart_parameters=restart: --ssl=off --loose-caching_sha2_password_private_key_path=$MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-caching_sha2_password_public_key_path=$MYSQL_TEST_DIR/std_data/rsa_public_key.pem +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as caching_sha2 with SSL disabled +connect(caching_con4,localhost,caching_sha2,caching,wl11381,,,); +SELECT USER(); + +connection default; +disconnect caching_con4; + +# default connection on windows is a NAMED_PIPE, on this type of connection +# async operations are not allowed, so disable it. +--disable_async_client +--echo # change to empty password +ALTER USER caching_sha2@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect as caching_sha2 with SSL disabled and empty password +connect(caching_con5,localhost,caching_sha2,,wl11381,,,); +SELECT USER(); + +connection default; +disconnect caching_con5; + +--disable_async_client +--echo # case3: authenticate user with sha256_password +CREATE USER sha256@localhost IDENTIFIED WITH 'sha256_password' BY 'auth_string'; +--let $restart_parameters=restart: --default-authentication-plugin=sha256_password +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as sha256 +connect(sha256_con1,localhost,sha256,auth_string,,,,SSL,sha256_password); +SELECT USER(); + +connection default; +disconnect sha256_con1; + +--disable_async_client +--echo # change to empty password +ALTER USER sha256@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect with empty password +connect(sha256_con2,localhost,sha256,,,,,SSL,sha256_password); +SELECT USER(); + +connection default; +disconnect sha256_con2; + +--disable_async_client +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--let $restart_parameters=restart: --default-authentication-plugin=sha256_password --ssl=off --loose-sha256_password_private_key_path=$MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-sha256_password_public_key_path=$MYSQL_TEST_DIR/std_data/rsa_public_key.pem +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect with wrong password and SSL disabled +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error ER_ACCESS_DENIED_ERROR +connect(sha256_con3,localhost,sha256,auth_string,,,,,sha256_password); + +--disable_async_client +--echo # case4: authenticate user with mysql_native_password +CREATE USER native_user@localhost IDENTIFIED WITH 'mysql_native_password' BY 'native'; +--let $restart_parameters=restart: --default-authentication-plugin=mysql_native_password +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as native_user +connect(native_con1,localhost,native_user,native,,,,,mysql_native_password); +SELECT USER(); + +connection default; +disconnect native_con1; + +--disable_async_client +--echo # Change to empty password +ALTER USER native_user@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect as native_user with empty password +connect(native_con2,localhost,native_user,,,,,,mysql_native_password); +SELECT USER(); + +connection default; +disconnect native_con2; + +#cleanup +--disable_async_client +DROP USER sha256@localhost, native_user@localhost, caching_sha2@localhost; +DROP DATABASE wl11381; diff --git a/mysql-test/t/big_packets-master.opt b/mysql-test/t/big_packets-master.opt new file mode 100644 index 000000000000..134a09708fdc --- /dev/null +++ b/mysql-test/t/big_packets-master.opt @@ -0,0 +1 @@ +--max_allowed_packet=64M diff --git a/mysql-test/t/big_packets.test b/mysql-test/t/big_packets.test new file mode 100644 index 000000000000..487ab81cd30d --- /dev/null +++ b/mysql-test/t/big_packets.test @@ -0,0 +1,13 @@ +# Test sending and receiving queries that must span packets (ie, +# larger than 16mb). This test is around a 32mb send/receive, testing +# payloads that cross multiple packets. + +let $str = `SELECT REPEAT('X', 33554432)`; +let $str = zzzzzzzzzzzzzzzz$str; + +--disable_query_log +--eval SELECT MD5("$str"); +--eval SELECT LENGTH("$str"); +--replace_column 1 verylongstring +--eval SELECT "Q $str END"; +--enable_query_log diff --git a/mysql-test/t/big_packets_boundary-master.opt b/mysql-test/t/big_packets_boundary-master.opt new file mode 100644 index 000000000000..9a966f9160e2 --- /dev/null +++ b/mysql-test/t/big_packets_boundary-master.opt @@ -0,0 +1 @@ +--max_allowed_packet=128M diff --git a/mysql-test/t/big_packets_boundary.test b/mysql-test/t/big_packets_boundary.test new file mode 100644 index 000000000000..b8191b0a0690 --- /dev/null +++ b/mysql-test/t/big_packets_boundary.test @@ -0,0 +1,18 @@ + +# Test sending and receiving queries that saddle a MAX_PACKET_LENGTH +# boundary (aka 16mb boundary) + +let $str = `SELECT REPEAT('X', 16777208)`; +let $i = 0; + +--disable_query_log +while ($i < 16) +{ + let $str = z$str; + --eval SELECT MD5("$str"); + --eval SELECT LENGTH("$str"); + --replace_column 1 verylongstring + --eval SELECT "Q $str END"; + inc $i; +} +--enable_query_log diff --git a/mysql-test/t/ssl-big-packet-master.opt b/mysql-test/t/ssl-big-packet-master.opt new file mode 100644 index 000000000000..6d3f113b25c1 --- /dev/null +++ b/mysql-test/t/ssl-big-packet-master.opt @@ -0,0 +1,5 @@ +--ssl=1 +--ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem +--ssl-cert=$MYSQL_TEST_DIR/std_data/server-cert.pem +--ssl-key=$MYSQL_TEST_DIR/std_data/server-key.pem +--max_allowed_packet=64M diff --git a/mysql-test/t/ssl-big-packet.test b/mysql-test/t/ssl-big-packet.test new file mode 100644 index 000000000000..cdb1e783d4aa --- /dev/null +++ b/mysql-test/t/ssl-big-packet.test @@ -0,0 +1,24 @@ +# Turn on ssl between the client and server and run some big queries +--source include/have_ssl.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +connect (ssl_con,localhost,root,,,,,SSL); + +let $str = `SELECT REPEAT('X', 33554432)`; +let $str = zzzzzzzzzzzzzzzz$str; + +--disable_query_log +--eval SELECT MD5("$str") +--eval SELECT LENGTH("$str") +--replace_column 1 verylongstring +--eval SELECT "Q $str END" + +connection default; +disconnect ssl_con; + +--enable_query_log + +# Wait till all disconnects are completed +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/valgrind.supp b/mysql-test/valgrind.supp index d373aa6152b4..f556e6e90d86 100644 --- a/mysql-test/valgrind.supp +++ b/mysql-test/valgrind.supp @@ -1,4 +1,4 @@ -# Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, @@ -1959,3 +1959,88 @@ fun:* fun:* } + +# Suppress all issues related to libcrypto library +{ + Conditional jump or move in BN_bin2bn + Memcheck:Cond + fun:BN_bin2bn + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in BN_ucmp + Memcheck:Cond + fun:BN_ucmp + ... + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in libcrypto + Memcheck:Cond + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in BN_mod_exp_mont + Memcheck:Cond + fun:BN_mod_exp_mont + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in libcrypto + Memcheck:Value8 + obj:/usr/lib64/libcrypto.so.* + ... +} +{ + Conditional jump or move in BN_mod_mul_montgomery + Memcheck:Cond + fun:BN_mod_mul_montgomery + fun:BN_mod_exp_mont + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in libcrypto + Memcheck:Value8 + obj:/usr/lib64/libcrypto.so.* + fun:BN_from_montgomery + fun:BN_mod_exp_mont + obj:* + ... +} +{ + Conditional jump or move in BN_num_bits_word + Memcheck:Cond + fun:BN_num_bits_word + fun:BN_num_bits + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in BN_num_bits_word + Memcheck:Value8 + fun:BN_num_bits_word + fun:BN_num_bits + fun:BN_bn2bin + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Syscall param socketcall.sendto(msg) points to uninitialised byte(s) + Memcheck:Param + socketcall.sendto(msg) + fun:send + fun:* + ... +} diff --git a/plugin/auth/dialog.cc b/plugin/auth/dialog.cc index f38eab06997c..728cd43d267d 100644 --- a/plugin/auth/dialog.cc +++ b/plugin/auth/dialog.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -350,4 +350,4 @@ static int init_dialog(char *unused1 MY_ATTRIBUTE((unused)), mysql_declare_client_plugin(AUTHENTICATION) "dialog", "Sergei Golubchik", "Dialog Client Authentication Plugin", {0, 1, 0}, "GPL", NULL, - init_dialog, NULL, NULL, perform_dialog mysql_end_client_plugin; + init_dialog, NULL, NULL, perform_dialog, NULL mysql_end_client_plugin; diff --git a/plugin/auth/qa_auth_client.cc b/plugin/auth/qa_auth_client.cc index bc2223f60cb1..72217c1fd8b7 100644 --- a/plugin/auth/qa_auth_client.cc +++ b/plugin/auth/qa_auth_client.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -111,4 +111,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "qa_auth_client", "Horst Hunger", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/auth/qa_auth_interface.cc b/plugin/auth/qa_auth_interface.cc index a1507e619e13..60eab2e63f22 100644 --- a/plugin/auth/qa_auth_interface.cc +++ b/plugin/auth/qa_auth_interface.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -271,4 +271,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "qa_auth_interface", "Horst Hunger", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/auth/test_plugin.cc b/plugin/auth/test_plugin.cc index b08a7e087d6a..d292071cbd93 100644 --- a/plugin/auth/test_plugin.cc +++ b/plugin/auth/test_plugin.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -294,4 +294,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "auth_test_plugin", "Georgi Kodinov", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/x/client/xconnection_impl.cc b/plugin/x/client/xconnection_impl.cc index 450c2ada11aa..0631b7cbba12 100644 --- a/plugin/x/client/xconnection_impl.cc +++ b/plugin/x/client/xconnection_impl.cc @@ -349,7 +349,7 @@ XError Connection_impl::connect(sockaddr *addr, const std::size_t addr_size) { auto vio = vio_new(s, type, 0); auto error = - vio_socket_connect(vio, addr, static_cast(addr_size), + vio_socket_connect(vio, addr, static_cast(addr_size), false, details::make_vio_timeout( m_context->m_connection_config.m_timeout_connect)); @@ -539,7 +539,7 @@ XError Connection_impl::activate_tls() { // When mode it set to Ssl_config::Mode_ssl_verify_ca // then lower layers are going to verify it unsigned long error; // NOLINT - if (0 != sslconnect(m_vioSslFd, m_vio, 60, &error)) { + if (0 != sslconnect(m_vioSslFd, m_vio, 60, &error, nullptr)) { return get_ssl_error(error); } diff --git a/sql-common/client.cc b/sql-common/client.cc index 42f213fbd7a6..14a749c06cb5 100644 --- a/sql-common/client.cc +++ b/sql-common/client.cc @@ -61,6 +61,7 @@ #include #include +#include "client_async_authentication.h" #include "errmsg.h" #include "lex_string.h" #include "map_helpers.h" @@ -214,6 +215,10 @@ char mysql_server_last_error[MYSQL_ERRMSG_SIZE]; /* forward declaration */ static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, ulong *lengths); +static net_async_status read_one_row_nonblocking(MYSQL *mysql, uint fields, + MYSQL_ROW row, ulong *lengths, + int *res); + /** Convert the connect timeout option to a timeout value for VIO functions (vio_socket_connect() and vio_io_wait()). @@ -981,6 +986,75 @@ void read_ok_ex(MYSQL *mysql, ulong length) { return; } +/* Helper for cli_safe_read and cli_safe_read_nonblocking */ +static ulong cli_safe_read_with_ok_complete(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, ulong len); + +/** + Read a packet from server in asynchronous way. This function can return + without completly reading the packet, in such a case call this function + again until complete packet is read. + + @param[in] mysql connection handle + @param[in] parse_ok if set to true then parse OK packet if it + was sent by server + @param[out] is_data_packet if set to true then the packet received + was a "data packet". + @param[out] res The length of the packet that was read or + packet_error in case of error. + + @retval NET_ASYNC_NOT_READY packet was not completely read + @retval NET_ASYNC_COMPLETE finished reading packet +*/ +net_async_status cli_safe_read_with_ok_nonblocking(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, + ulong *res) { + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + ulong len = 0, complen = 0; + DBUG_ENTER(__func__); + + if (net_async->async_multipacket_read_started == false) { + net_async->async_multipacket_read_started = true; + net_async->async_multipacket_read_saved_whereb = net->where_b; + net_async->async_multipacket_read_total_len = 0; + } + + if (net->vio != 0) { + net_async_status status = my_net_read_nonblocking(net, &len, &complen); + if (len != packet_error) { + net_async->async_multipacket_read_total_len += len; + net->where_b += len; + } + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + } + + net->where_b = net_async->async_multipacket_read_saved_whereb; + net->read_pos = net->buff + net->where_b; + + DBUG_PRINT("info", ("total nb read: %lu", + net_async->async_multipacket_read_total_len)); + *res = cli_safe_read_with_ok_complete( + mysql, parse_ok, is_data_packet, + net_async->async_multipacket_read_total_len); + + net_async->async_multipacket_read_started = false; + net_async->async_multipacket_read_saved_whereb = 0; + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/** + Its a non blocking version of cli_safe_read +*/ +net_async_status cli_safe_read_nonblocking(MYSQL *mysql, bool *is_data_packet, + ulong *res) { + return cli_safe_read_with_ok_nonblocking(mysql, 0, is_data_packet, res); +} + /** Read a packet from server. Give error message if socket was down or packet is an error message @@ -1007,6 +1081,13 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if (is_data_packet) *is_data_packet = false; if (net->vio != 0) len = my_net_read(net); + return cli_safe_read_with_ok_complete(mysql, parse_ok, is_data_packet, len); +} + +ulong cli_safe_read_with_ok_complete(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, ulong len) { + NET *net = &mysql->net; + DBUG_ENTER(__func__); if (len == packet_error || len == 0) { char desc[VIO_DESCRIPTION_SIZE]; @@ -1015,7 +1096,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { ("Wrong connection or packet. fd: %s len: %lu", desc, len)); #ifdef MYSQL_SERVER if (net->vio && (net->last_errno == ER_NET_READ_INTERRUPTED)) - return (packet_error); + DBUG_RETURN(packet_error); #endif /*MYSQL_SERVER*/ end_server(mysql); set_mysql_error(mysql, @@ -1023,7 +1104,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { ? CR_NET_PACKET_TOO_LARGE : CR_SERVER_LOST, unknown_sqlstate); - return (packet_error); + DBUG_RETURN(packet_error); } MYSQL_TRACE(PACKET_RECEIVED, mysql, (len, net->read_pos)); @@ -1070,13 +1151,13 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { DBUG_PRINT("error", ("Got error: %d/%s (%s)", net->last_errno, net->sqlstate, net->last_error)); - return (packet_error); + DBUG_RETURN(packet_error); } else { /* if it is OK packet irrespective of new/old server */ if (net->read_pos[0] == 0) { if (parse_ok) { read_ok_ex(mysql, len); - return len; + DBUG_RETURN(len); } } /* @@ -1092,12 +1173,12 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if ((mysql->server_capabilities & CLIENT_DEPRECATE_EOF) && (net->read_pos[0] == 254)) { /* detect huge data packet */ - if (len > MAX_PACKET_LENGTH) return len; + if (len > MAX_PACKET_LENGTH) DBUG_RETURN(len); /* otherwise we have OK packet starting with 0xFE */ if (is_data_packet) *is_data_packet = false; /* parse it if requested */ if (parse_ok) read_ok_ex(mysql, len); - return len; + DBUG_RETURN(len); } /* for old client detect EOF packet */ if (!(mysql->server_capabilities & CLIENT_DEPRECATE_EOF) && @@ -1105,7 +1186,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if (is_data_packet) *is_data_packet = false; } } - return len; + DBUG_RETURN(len); } /** @@ -1145,6 +1226,10 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, if (mysql->net.vio == 0) { /* Do reconnect if possible */ if (mysql_reconnect(mysql) || stmt_skip) DBUG_RETURN(1); } + /* turn off non blocking operations */ + if (!vio_is_blocking(mysql->net.vio)) + vio_set_blocking_flag(mysql->net.vio, true); + if (mysql->status != MYSQL_STATUS_READY || mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { DBUG_PRINT("error", ("state: %d", mysql->status)); @@ -1279,6 +1364,110 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, DBUG_RETURN(result); } +net_async_status cli_advanced_command_nonblocking( + MYSQL *mysql, enum enum_server_command command, const uchar *header, + ulong header_length, const uchar *arg, ulong arg_length, bool skip_check, + MYSQL_STMT *stmt, bool *ret) { + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + bool result = 1; + *ret = result; + bool stmt_skip = stmt ? stmt->state != MYSQL_STMT_INIT_DONE : false; + DBUG_ENTER(__func__); + DBUG_DUMP("sending", header, header_length); + if (arg && arg_length) { + DBUG_DUMP("sending arg", arg, arg_length); + } + + if (mysql->net.vio == 0) { + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + goto end; + } + if (net_async->async_send_command_status == NET_ASYNC_SEND_COMMAND_IDLE) { + if (vio_is_blocking(mysql->net.vio)) { + vio_set_blocking_flag(net->vio, false); + } + + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { + DBUG_PRINT("error", ("state: %d", mysql->status)); + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net_clear_error(net); + mysql->info = 0; + mysql->affected_rows = ~(my_ulonglong)0; + /* + Do not check the socket/protocol buffer on COM_QUIT as the + result of a previous command might not have been read. This + can happen if a client sends a query but does not reap + the result before attempting to close the connection. + */ + DBUG_ASSERT(command <= COM_END); + net_clear(&mysql->net, (command != COM_QUIT)); + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_WRITE_COMMAND; + } + + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + if (net_async->async_send_command_status == + NET_ASYNC_SEND_COMMAND_WRITE_COMMAND) { + bool err; + MYSQL_TRACE(SEND_COMMAND, mysql, + (command, header_length, arg_length, header, arg)); + net_async_status status = net_write_command_nonblocking( + net, (uchar)command, header, header_length, arg, arg_length, &err); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (err) { + DBUG_PRINT("error", + ("Can't send command to server. Error: %d", socket_errno)); + if (net->last_errno == ER_NET_PACKET_TOO_LARGE) { + set_mysql_error(mysql, CR_NET_PACKET_TOO_LARGE, unknown_sqlstate); + goto end; + } + end_server(mysql); + if (stmt_skip) goto end; + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + goto end; + } + MYSQL_TRACE(PACKET_SENT, mysql, (header_length + arg_length)); + if (skip_check) { + result = 0; + goto end; + } else { + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_READ_STATUS; + } + } + + if (net_async->async_send_command_status == + NET_ASYNC_SEND_COMMAND_READ_STATUS) { + ulong pkt_len; + net_async_status status = + cli_safe_read_with_ok_nonblocking(mysql, true, nullptr, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = pkt_len; + result = (pkt_len == packet_error ? 1 : 0); +#if defined(CLIENT_PROTOCOL_TRACING) + /* + Return to READY_FOR_COMMAND protocol stage in case server reports + error or sends OK packet. + */ + if (!result || mysql->net.read_pos[0] == 0x00) + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + } +end: + if (net_async) + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_IDLE; + DBUG_PRINT("exit", ("result: %d", result)); + *ret = result; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void free_old_query(MYSQL *mysql) { DBUG_ENTER("free_old_query"); if (mysql->field_alloc) { @@ -1293,6 +1482,53 @@ void free_old_query(MYSQL *mysql) { DBUG_VOID_RETURN; } +/** + Finish reading of a partial result set from the server in asynchronous + way. This function can return without completly flushing the result set, + in such a case call this function again until result set in flushed. + Read OK packet incase result set is not a data packet. + + @param[in] mysql connection handle + @param[out] res true in case of protocol error, false otherwise + + @retval NET_ASYNC_NOT_READY result set not flushed yet + @retval NET_ASYNC_COMPLETE finished flushing result set +*/ +static net_async_status flush_one_result_nonblocking(MYSQL *mysql, bool *res) { + DBUG_ENTER(__func__); + + *res = false; + while (1) { + ulong packet_length; + bool is_data_packet; + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &packet_length) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = packet_length; + if (packet_length == packet_error) { + *res = true; + break; + } + if (mysql->net.read_pos[0] != 0 && !is_data_packet) { + if (protocol_41(mysql)) { + uchar *pos = mysql->net.read_pos + 1; + if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && + !is_data_packet) { + read_ok_ex(mysql, packet_length); + } else { + mysql->warning_count = uint2korr(pos); + pos += 2; + mysql->server_status = uint2korr(pos); + } + pos += 2; + } + break; + } + } + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Finish reading of a partial result set from the server. Get the EOF packet, and update mysql->status @@ -1378,6 +1614,18 @@ static bool opt_flush_ok_packet(MYSQL *mysql, bool *is_ok_packet) { return false; } +static net_async_status cli_flush_use_result_nonblocking( + MYSQL *mysql, bool flush_all_results MY_ATTRIBUTE((unused))) { + DBUG_ENTER(__func__); + /* + flush_all_results is only used for mysql_stmt_close, and async is not + supported for that. + */ + DBUG_ASSERT(!flush_all_results); + bool res; + DBUG_RETURN(flush_one_result_nonblocking(mysql, &res)); +} + /* Flush result set sent from server */ @@ -1498,12 +1746,51 @@ void end_server(MYSQL *mysql) { mysql_prune_stmt_list(mysql); } net_end(&mysql->net); + net_extension_free(&mysql->net); free_old_query(mysql); errno = save_errno; MYSQL_TRACE(DISCONNECTED, mysql, ()); DBUG_VOID_RETURN; } +/** + Frees the memory allocated for a result, set by APIs which would have + returned rows. + + @param[in] result buffer which needs to be freed + + @retval NET_ASYNC_NOT_READY operation not complete, retry again + @retval NET_ASYNC_COMPLETE operation complete +*/ +net_async_status STDCALL mysql_free_result_nonblocking(MYSQL_RES *result) { + DBUG_ENTER(__func__); + DBUG_PRINT("enter", ("mysql_res: %p", result)); + if (!result) DBUG_RETURN(NET_ASYNC_COMPLETE); + + MYSQL *mysql = result->handle; + if (mysql) { + if (mysql->unbuffered_fetch_owner == &result->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner = 0; + if (mysql->status == MYSQL_STATUS_USE_RESULT) { + if (mysql->methods->flush_use_result_nonblocking(mysql, false) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->status = MYSQL_STATUS_READY; + if (mysql->unbuffered_fetch_owner) *mysql->unbuffered_fetch_owner = true; + } + } + free_rows(result->data); + if (result->field_alloc) { + free_root(result->field_alloc, MYF(0)); + my_free(result->field_alloc); + } + my_free(result->row); + my_free(result); + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void STDCALL mysql_free_result(MYSQL_RES *result) { DBUG_ENTER("mysql_free_result"); DBUG_PRINT("enter", ("mysql_res: %p", result)); @@ -2107,6 +2394,107 @@ MYSQL_FIELD *unpack_fields(MYSQL *mysql, MYSQL_ROWS *data, MEM_ROOT *alloc, DBUG_RETURN(result); } +/** + Read metadata resultset from server in asynchronous way. + + @param[in] mysql connection handle + @param[in] alloc memory allocator root + @param[in] field_count total number of fields + @param[in] field number of columns in single field descriptor + @param[out] ret an array of field rows + + @retval NET_ASYNC_NOT_READY metadata resultset not read completely + @retval NET_ASYNC_COMPLETE finished reading metadata resultset +*/ +net_async_status cli_read_metadata_ex_nonblocking(MYSQL *mysql, MEM_ROOT *alloc, + ulong field_count, + unsigned int field, + MYSQL_FIELD **ret) { + DBUG_ENTER(__func__); + uchar *pos; + ulong pkt_len; + NET *net = &mysql->net; + MYSQL_ASYNC *async_data = ASYNC_DATA(mysql); + *ret = nullptr; + + if (!async_data->async_read_metadata_field_len) { + async_data->async_read_metadata_field_len = + (ulong *)alloc_root(alloc, sizeof(ulong) * field); + } + if (!async_data->async_read_metadata_fields) { + async_data->async_read_metadata_fields = (MYSQL_FIELD *)alloc_root( + alloc, (uint)sizeof(MYSQL_FIELD) * field_count); + if (async_data->async_read_metadata_fields) + memset(async_data->async_read_metadata_fields, 0, + sizeof(MYSQL_FIELD) * field_count); + } + + if (!async_data->async_read_metadata_fields) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto end; + } + + if (!async_data->async_read_metadata_data.data) { + async_data->async_read_metadata_data.data = + (MYSQL_ROW)alloc_root(alloc, sizeof(char *) * (field + 1)); + memset(async_data->async_read_metadata_data.data, 0, + sizeof(char *) * (field + 1)); + } + + /* + In this below loop we read each column info as 1 single row + and save it in mysql->fields array + */ + while (async_data->async_read_metadata_cur_field < field_count) { + int res; + if (read_one_row_nonblocking(mysql, field, + async_data->async_read_metadata_data.data, + async_data->async_read_metadata_field_len, + &res) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (res == -1) { + goto end; + } + + if (unpack_field(mysql, alloc, 0, mysql->server_capabilities, + &async_data->async_read_metadata_data, + async_data->async_read_metadata_fields + + async_data->async_read_metadata_cur_field)) { + goto end; + } + async_data->async_read_metadata_cur_field++; + } + + /* Read EOF packet in case of old client */ + if (!(mysql->server_capabilities & CLIENT_DEPRECATE_EOF)) { + if (cli_safe_read_nonblocking(mysql, nullptr, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (pkt_len == packet_error) { + goto end; + } + + pos = net->read_pos; + if (*pos == 254) { + mysql->warning_count = uint2korr(pos + 1); + mysql->server_status = uint2korr(pos + 3); + } + } + *ret = async_data->async_read_metadata_fields; + +end: + async_data->async_read_metadata_field_len = nullptr; + async_data->async_read_metadata_fields = nullptr; + memset(&async_data->async_read_metadata_data, 0, + sizeof(async_data->async_read_metadata_data)); + async_data->async_read_metadata_cur_field = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Read metadata resultset from server Memory allocated in a given allocator root. @@ -2165,6 +2553,20 @@ MYSQL_FIELD *cli_read_metadata_ex(MYSQL *mysql, MEM_ROOT *alloc, DBUG_RETURN(result); } +static int alloc_field_alloc(MYSQL *mysql) { + if (mysql->field_alloc == nullptr) { + mysql->field_alloc = (MEM_ROOT *)my_malloc( + key_memory_MYSQL, sizeof(MEM_ROOT), MYF(MY_WME | MY_ZEROFILL)); + if (mysql->field_alloc == nullptr) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + return 1; + } + init_alloc_root(PSI_NOT_INSTRUMENTED, mysql->field_alloc, 8192, + 0); /* Assume rowlength < 8192 */ + } + return 0; +} + /** Read metadata resultset from server @@ -2177,19 +2579,25 @@ MYSQL_FIELD *cli_read_metadata_ex(MYSQL *mysql, MEM_ROOT *alloc, */ MYSQL_FIELD *cli_read_metadata(MYSQL *mysql, ulong field_count, unsigned int field) { - if (mysql->field_alloc == nullptr) { - mysql->field_alloc = (MEM_ROOT *)my_malloc( - key_memory_MYSQL, sizeof(MEM_ROOT), MYF(MY_WME | MY_ZEROFILL)); - if (mysql->field_alloc == nullptr) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - return nullptr; - } - init_alloc_root(PSI_NOT_INSTRUMENTED, mysql->field_alloc, 8192, - 0); /* Assume rowlength < 8192 */ - } + alloc_field_alloc(mysql); return cli_read_metadata_ex(mysql, mysql->field_alloc, field_count, field); } +/** + Helper method to read metadata in asynchronous way. +*/ +static net_async_status cli_read_metadata_nonblocking(MYSQL *mysql, + ulong field_count, + unsigned int field, + MYSQL_FIELD **ret) { + alloc_field_alloc(mysql); + if (cli_read_metadata_ex_nonblocking(mysql, mysql->field_alloc, field_count, + field, ret) == NET_ASYNC_NOT_READY) { + return NET_ASYNC_NOT_READY; + } + return NET_ASYNC_COMPLETE; +} + /** Read resultset metadata returned by COM_QUERY command. @@ -2236,57 +2644,142 @@ static int read_com_query_metadata(MYSQL *mysql, uchar *pos, return 0; } -/* Read all rows (data) from server */ +/** + Read resultset metadata returned by COM_QUERY command in asynchronous way. -MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, - unsigned int fields) { + @param[in] mysql Client connection handle. + @param[in] pos Position in the packet where the metadata + starts. + @param[in] field_count Number of columns in the field descriptor. + @param[out] res set to false incase of success and true for + error. + + @retval NET_ASYNC_NOT_READY metadata resultset not read completely + @retval NET_ASYNC_COMPLETE finished reading metadata resultset +*/ +static net_async_status read_com_query_metadata_nonblocking(MYSQL *mysql, + uchar *pos, + ulong field_count, + int *res) { + DBUG_ENTER(__func__); + /* pos is only set on the first reentrant call. */ + if (pos) { + /* Store resultset metadata flag. */ + if (mysql->client_flag & CLIENT_OPTIONAL_RESULTSET_METADATA) { + mysql->resultset_metadata = + static_cast(*pos); + } else { + mysql->resultset_metadata = RESULTSET_METADATA_FULL; + } + } + + switch (mysql->resultset_metadata) { + case RESULTSET_METADATA_FULL: + /* Read metadata. */ + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_FIELD_DEF); + + if (cli_read_metadata_nonblocking( + mysql, field_count, protocol_41(mysql) ? 7 : 5, &mysql->fields) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!mysql->fields) { + free_root(mysql->field_alloc, MYF(0)); + *res = 1; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + break; + + case RESULTSET_METADATA_NONE: + /* Skip metadata. */ + mysql->fields = NULL; + break; + + default: + /* Unknown metadata flag. */ + mysql->fields = NULL; + *res = 1; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + *res = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +net_async_status cli_read_rows_nonblocking(MYSQL *mysql, + MYSQL_FIELD *mysql_fields, + unsigned int fields, + MYSQL_DATA **result_out) { uint field; ulong pkt_len; ulong len; uchar *cp; char *to, *end_to; - MYSQL_DATA *result; - MYSQL_ROWS **prev_ptr, *cur; + MYSQL_ROWS *cur; NET *net = &mysql->net; bool is_data_packet; - DBUG_ENTER("cli_read_rows"); + DBUG_ENTER(__func__); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + *result_out = nullptr; - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) - DBUG_RETURN(0); + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } - if (pkt_len == 0) DBUG_RETURN(0); - if (!(result = - (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), - MYF(MY_WME | MY_ZEROFILL))) || - !(result->alloc = - (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), - MYF(MY_WME | MY_ZEROFILL)))) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - free_rows(result); - DBUG_RETURN(0); + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + if (net_async->read_rows_is_first_read) { + free_rows(async_context->rows_result_buffer); + async_context->rows_result_buffer = nullptr; + } + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (net_async->read_rows_is_first_read) { + MYSQL_DATA *result; + if (!(result = + (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), + MYF(MY_WME | MY_ZEROFILL))) || + !(result->alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + net_async->read_rows_is_first_read = true; + free_rows(result); + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + async_context->rows_result_buffer = result; + init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, + 0); /* Assume rowlength < 8192 */ + async_context->prev_row_ptr = &result->data; + result->rows = 0; + result->fields = fields; + + net_async->read_rows_is_first_read = false; } - init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, - 0); /* Assume rowlength < 8192 */ - prev_ptr = &result->data; - result->rows = 0; - result->fields = fields; /* The last EOF packet is either a single 254 character or (in MySQL 4.1) 254 followed by 1-7 status bytes or an OK packet starting with 0xFE */ - while (*(cp = net->read_pos) == 0 || is_data_packet) { + MYSQL_DATA *result = async_context->rows_result_buffer; result->rows++; if (!(cur = (MYSQL_ROWS *)alloc_root(result->alloc, sizeof(MYSQL_ROWS))) || !(cur->data = ((MYSQL_ROW)alloc_root( result->alloc, (fields + 1) * sizeof(char *) + pkt_len)))) { free_rows(result); + async_context->rows_result_buffer = nullptr; set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - DBUG_RETURN(0); + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } - *prev_ptr = cur; - prev_ptr = &cur->next; + *async_context->prev_row_ptr = cur; + async_context->prev_row_ptr = &cur->next; to = (char *)(cur->data + fields + 1); end_to = to + pkt_len - 1; for (field = 0; field < fields; field++) { @@ -2297,8 +2790,10 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, cur->data[field] = to; if (len > (ulong)(end_to - to)) { free_rows(result); + async_context->rows_result_buffer = nullptr; set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); - DBUG_RETURN(0); + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } memcpy(to, (char *)cp, len); to[len] = 0; @@ -2311,12 +2806,20 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, } } cur->data[field] = to; /* End of last field */ - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) { - free_rows(result); - DBUG_RETURN(0); + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + free_rows(async_context->rows_result_buffer); + async_context->rows_result_buffer = nullptr; + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } } - *prev_ptr = 0; /* last pointer is null */ + + *async_context->prev_row_ptr = 0; /* last pointer is null */ /* read EOF packet or OK packet if it is new client */ if (pkt_len > 1) { if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) @@ -2336,25 +2839,126 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, else MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); #endif - DBUG_PRINT("exit", ("Got %lu rows", (ulong)result->rows)); - DBUG_RETURN(result); + DBUG_PRINT("exit", + ("Got %lu rows", (ulong)async_context->rows_result_buffer->rows)); + *result_out = async_context->rows_result_buffer; + async_context->rows_result_buffer = nullptr; + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } -/* - Read one row. Uses packet buffer as storage for fields. - When next packet is read, the previous field values are destroyed -*/ +/* Read all rows (data) from server */ -static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, - ulong *lengths) { +MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, + unsigned int fields) { uint field; - ulong pkt_len, len; + ulong pkt_len; + ulong len; + uchar *cp; + char *to, *end_to; + MYSQL_DATA *result; + MYSQL_ROWS **prev_ptr, *cur; + NET *net = &mysql->net; bool is_data_packet; + DBUG_ENTER("cli_read_rows"); + + if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) + DBUG_RETURN(0); + + if (pkt_len == 0) DBUG_RETURN(0); + if (!(result = + (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), + MYF(MY_WME | MY_ZEROFILL))) || + !(result->alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + free_rows(result); + DBUG_RETURN(0); + } + init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, + 0); /* Assume rowlength < 8192 */ + prev_ptr = &result->data; + result->rows = 0; + result->fields = fields; + + /* + The last EOF packet is either a single 254 character or (in MySQL 4.1) + 254 followed by 1-7 status bytes or an OK packet starting with 0xFE + */ + + while (*(cp = net->read_pos) == 0 || is_data_packet) { + result->rows++; + if (!(cur = (MYSQL_ROWS *)alloc_root(result->alloc, sizeof(MYSQL_ROWS))) || + !(cur->data = ((MYSQL_ROW)alloc_root( + result->alloc, (fields + 1) * sizeof(char *) + pkt_len)))) { + free_rows(result); + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + *prev_ptr = cur; + prev_ptr = &cur->next; + to = (char *)(cur->data + fields + 1); + end_to = to + 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)) { + free_rows(result); + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + DBUG_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 = cli_safe_read(mysql, &is_data_packet)) == packet_error) { + free_rows(result); + DBUG_RETURN(0); + } + } + *prev_ptr = 0; /* last pointer is null */ + /* read EOF packet or OK packet if it is new client */ + if (pkt_len > 1) { + if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) + read_ok_ex(mysql, pkt_len); + else { + mysql->warning_count = uint2korr(cp + 1); + mysql->server_status = uint2korr(cp + 3); + } + + DBUG_PRINT("info", ("status: %u warning_count: %u", mysql->server_status, + mysql->warning_count)); + } + +#if defined(CLIENT_PROTOCOL_TRACING) + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + else + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + DBUG_PRINT("exit", ("Got %lu rows", (ulong)result->rows)); + DBUG_RETURN(result); +} + +static int read_one_row_complete(MYSQL *mysql, ulong pkt_len, + bool is_data_packet, uint fields, + MYSQL_ROW row, ulong *lengths) { + uint field; + ulong len; uchar *pos, *prev_pos, *end_pos; NET *net = &mysql->net; - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) - return -1; if (net->read_pos[0] != 0x00 && !is_data_packet) { if (pkt_len > 1) /* MySQL 4.1 protocol */ { @@ -2399,6 +3003,46 @@ static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, return 0; } +/* + Read one row. Uses packet buffer as storage for fields. + When next packet is read, the previous field values are destroyed +*/ + +static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, + ulong *lengths) { + ulong pkt_len; + bool is_data_packet; + + if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) + return -1; + + return read_one_row_complete(mysql, pkt_len, is_data_packet, fields, row, + lengths); +} + +static net_async_status read_one_row_nonblocking(MYSQL *mysql, uint fields, + MYSQL_ROW row, ulong *lengths, + int *res) { + ulong pkt_len; + bool is_data_packet; + net_async_status status; + + status = cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + return status; + } + + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + *res = -1; + return NET_ASYNC_COMPLETE; + } + + *res = read_one_row_complete(mysql, pkt_len, is_data_packet, fields, row, + lengths); + return NET_ASYNC_COMPLETE; +} + /**************************************************************************** Init MySQL structure or allocate one ****************************************************************************/ @@ -2467,6 +3111,7 @@ MYSQL *STDCALL mysql_init(MYSQL *mysql) { #endif mysql->resultset_metadata = RESULTSET_METADATA_FULL; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_UNSET; return mysql; } @@ -2482,13 +3127,19 @@ MYSQL_EXTENSION *mysql_extension_init(MYSQL *mysql MY_ATTRIBUTE((unused))) { ext = static_cast(my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MYSQL_EXTENSION), MYF(MY_WME | MY_ZEROFILL))); + ext->mysql_async_context = static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(struct MYSQL_ASYNC), + MYF(MY_WME | MY_ZEROFILL))); + /* set default value */ + ext->mysql_async_context->async_op_status = ASYNC_OP_UNSET; + return ext; } void mysql_extension_free(MYSQL_EXTENSION *ext) { if (!ext) return; if (ext->trace_data) my_free(ext->trace_data); - + if (ext->mysql_async_context) my_free(ext->mysql_async_context); // free state change related resources. free_state_change_info(ext); @@ -2717,12 +3368,18 @@ static int ssl_verify_server_cert(Vio *vio, const char *server_hostname, */ static bool cli_read_query_result(MYSQL *mysql); +static net_async_status cli_read_query_result_nonblocking(MYSQL *mysql); static MYSQL_RES *cli_use_result(MYSQL *mysql); int cli_read_change_user_result(MYSQL *mysql) { return cli_safe_read(mysql, NULL); } +net_async_status cli_read_change_user_result_nonblocking(MYSQL *mysql, + ulong *ret) { + return cli_safe_read_nonblocking(mysql, nullptr, ret); +} + static MYSQL_METHODS client_methods = { cli_read_query_result, /* read_query_result */ cli_advanced_command, /* advanced_command */ @@ -2743,6 +3400,14 @@ static MYSQL_METHODS client_methods = { cli_read_binary_rows, /* read_rows_from_cursor */ free_rows #endif + , + cli_read_query_result_nonblocking, /* read_query_result_nonblocking */ + cli_advanced_command_nonblocking, /* advanced_command_nonblocking */ + cli_read_rows_nonblocking, /* read_rows_nonblocking */ + cli_flush_use_result_nonblocking, /* flush_use_result_nonblocking */ + cli_read_query_result_nonblocking, /* next_result_nonblocking */ + cli_read_change_user_result_nonblocking /* read_change_user_result_nonblocking + */ }; typedef enum my_cs_match_type_enum { @@ -3021,7 +3686,11 @@ int mysql_init_character_set(MYSQL *mysql) { /*********** client side authentication support **************************/ static int client_mpvio_write_packet(MYSQL_PLUGIN_VIO *, const uchar *, int); +static net_async_status client_mpvio_write_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *, const uchar *, int, int *); static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); +static net_async_status native_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result); static int clear_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); static auth_plugin_t native_password_client_plugin = { @@ -3036,7 +3705,8 @@ static auth_plugin_t native_password_client_plugin = { NULL, NULL, NULL, - native_password_auth_client}; + native_password_auth_client, + native_password_auth_client_nonblocking}; static auth_plugin_t clear_password_client_plugin = { MYSQL_CLIENT_AUTHENTICATION_PLUGIN, @@ -3050,7 +3720,8 @@ static auth_plugin_t clear_password_client_plugin = { NULL, NULL, NULL, - clear_password_auth_client}; + clear_password_auth_client, + NULL}; #if defined(HAVE_OPENSSL) static auth_plugin_t sha256_password_client_plugin = { @@ -3065,7 +3736,8 @@ static auth_plugin_t sha256_password_client_plugin = { sha256_password_init, sha256_password_deinit, NULL, - sha256_password_auth_client}; + sha256_password_auth_client, + sha256_password_auth_client_nonblocking}; static auth_plugin_t caching_sha2_password_client_plugin = { MYSQL_CLIENT_AUTHENTICATION_PLUGIN, @@ -3079,7 +3751,8 @@ static auth_plugin_t caching_sha2_password_client_plugin = { caching_sha2_password_init, caching_sha2_password_deinit, NULL, - caching_sha2_password_auth_client}; + caching_sha2_password_auth_client, + caching_sha2_password_auth_client_nonblocking}; #endif #ifdef AUTHENTICATION_WIN extern "C" auth_plugin_t win_auth_client_plugin; @@ -3166,24 +3839,6 @@ static size_t get_length_store_length(size_t length) { return ptr - &length_buffer[0]; } -/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ -struct MCPVIO_EXT { - int (*read_packet)(MYSQL_PLUGIN_VIO *vio, uchar **buf); - int (*write_packet)(MYSQL_PLUGIN_VIO *vio, const uchar *pkt, int pkt_len); - void (*info)(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info); - /* -= end of MYSQL_PLUGIN_VIO =- */ - MYSQL *mysql; - auth_plugin_t *plugin; /**< what plugin we're under */ - const char *db; - struct { - uchar *pkt; /**< pointer into NET::buff */ - uint pkt_len; - } cached_server_reply; - int packets_read, packets_written; /**< counters for send/received packets */ - int mysql_change_user; /**< if it's mysql_change_user() */ - int last_read_packet_len; /**< the length of the last *read* packet */ -}; - /* Write 1-8 bytes of string length header infromation to dest depending on value of src_len, then copy src_len bytes from src to dest. @@ -3488,7 +4143,7 @@ static int cli_establish_ssl(MYSQL *mysql) { DBUG_PRINT("info", ("IO layer change in progress...")); MYSQL_TRACE(SSL_CONNECT, mysql, ()); if (sslconnect(ssl_fd, net->vio, (long)(mysql->options.connect_timeout), - &ssl_error)) { + &ssl_error, nullptr)) { char buf[512]; ERR_error_string_n(ssl_error, buf, 512); buf[511] = 0; @@ -3521,8 +4176,266 @@ static int cli_establish_ssl(MYSQL *mysql) { #endif /* HAVE_OPENSSL */ } +/** + This function will establish asynchronous ssl connection by completing 4 + different ssl connection states. Initial state is set to SSL_NONE during + which this functions does priliminary checks like if server supports ssl + or not, if CA certificate is required etc. Once preliminary checks are + done state is changed to SSL_REQUEST. In this state ssl request packet + is sent by client. If this network IO is complete, state is changed to + SSL_CONNECT. During SSL_CONNECT sslconnect() is called which can return + immediately or complete SSL handshake. If it returns immediately client + will save all SSL context in struct mysql_async_auth, so that next call + to this function will ensure that SSL_new() is not called twice. Once + ssl connection is established state is changed to SSL_COMPLETE. + + @param[in] mysql Client connection handle. + @param[out] res set to false incase of success and true for + error. + + @retval NET_ASYNC_NOT_READY ssl connection not yet established + @retval NET_ASYNC_COMPLETE ssl connection established +*/ +static net_async_status cli_establish_ssl_nonblocking(MYSQL *mysql, int *res) { + DBUG_ENTER(__func__); +#ifdef HAVE_OPENSSL + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + mysql_async_connect *ctx = ASYNC_DATA(mysql)->connect_context; + + if (ctx->ssl_state == SSL_NONE) { + /* Don't fallback on unencrypted connection if SSL required. */ + if (mysql->options.extension && + mysql->options.extension->ssl_mode >= SSL_MODE_REQUIRED && + !(mysql->server_capabilities & CLIENT_SSL)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + "SSL is required but the server doesn't " + "support it"); + goto error; + } + + /* + If the ssl_mode is VERIFY_CA or VERIFY_IDENTITY, make sure + that the connection doesn't succeed without providing the + CA certificate. + */ + if (mysql->options.extension && + mysql->options.extension->ssl_mode > SSL_MODE_REQUIRED && + !(mysql->options.ssl_ca || mysql->options.ssl_capath)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + "CA certificate is required if ssl-mode " + "is VERIFY_CA or VERIFY_IDENTITY"); + goto error; + } + + /* + Attempt SSL connection if ssl_mode != SSL_MODE_DISABLED and + the server supports SSL. Fallback on unencrypted + connection otherwise. + */ + if (!mysql->options.extension || + mysql->options.extension->ssl_mode == SSL_MODE_DISABLED || + !(mysql->server_capabilities & CLIENT_SSL)) { + goto done; + } + ctx->ssl_state = SSL_REQUEST; + } + + if (ctx->ssl_state == SSL_REQUEST) { + char buff[33], *end; + + end = mysql_fill_packet_header(mysql, buff, sizeof(buff)); + + /* + Send mysql->client_flag, max_packet_size - unencrypted + otherwise the server does not know we want to do SSL + */ + MYSQL_TRACE(SEND_SSL_REQUEST, mysql, + ((size_t)(end - buff), (const unsigned char *)buff)); + bool ret; + if (my_net_write_nonblocking(net, (uchar *)buff, (size_t)(end - buff), + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (ret) { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "sending connection information to server", + errno); + goto error; + } + + ctx->ssl_state = SSL_CONNECT; + } + + if (ctx->ssl_state == SSL_CONNECT) { + /* Do the SSL layering. */ + struct st_mysql_options *options = &mysql->options; + struct st_VioSSLFd *ssl_fd; + enum enum_ssl_init_error ssl_init_error; + const char *cert_error; + unsigned long ssl_error; + size_t ret; + + MYSQL_TRACE_STAGE(mysql, SSL_NEGOTIATION); + + if (!mysql->connector_fd) { + /* Create the VioSSLConnectorFd - init SSL and load certs */ + if (!(ssl_fd = new_VioSSLConnectorFd( + options->ssl_key, options->ssl_cert, options->ssl_ca, + options->ssl_capath, options->ssl_cipher, + options->extension ? options->extension->tls_ciphersuites + : NULL, + &ssl_init_error, + options->extension ? options->extension->ssl_crl : NULL, + options->extension ? options->extension->ssl_crlpath : NULL, + options->extension ? options->extension->ssl_ctx_flags : 0))) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, + unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + sslGetErrString(ssl_init_error)); + goto error; + } + mysql->connector_fd = (unsigned char *)ssl_fd; + } else { + ssl_fd = (struct st_VioSSLFd *)mysql->connector_fd; + } + + /* Connect to the server */ + DBUG_PRINT("info", ("IO layer change in progress...")); + MYSQL_TRACE(SSL_CONNECT, mysql, ()); + if ((ret = sslconnect(ssl_fd, net->vio, + (long)(mysql->options.connect_timeout), &ssl_error, + &ctx->ssl))) { + switch (ret) { + case VIO_SOCKET_WANT_READ: + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(NET_ASYNC_NOT_READY); + case VIO_SOCKET_WANT_WRITE: + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(NET_ASYNC_NOT_READY); + default: + break; + /* continue for error handling */ + } + + char buf[512]; + ERR_error_string_n(ssl_error, buf, 512); + buf[511] = 0; + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), buf); + goto error; + } + DBUG_PRINT("info", ("IO layer change done!")); + + /* sslconnect creates a new vio, so update it. */ + vio_set_blocking_flag(net->vio, !ctx->non_blocking); + + /* Verify server cert */ + if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + ssl_verify_server_cert(net->vio, mysql->host, &cert_error)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), cert_error); + goto error; + } + + MYSQL_TRACE(SSL_CONNECTED, mysql, ()); + MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); + } + +done: + *res = 0; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +error: + *res = 1; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +#else + (void)mysql; /* avoid warning */ + *res = 0; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +#endif /* HAVE_OPENSSL */ +} + +/** + Asynchronous authentication phase is divided into several smaller chunks + of subtasks like: + 1. Determine the default/initial plugin to use + 2. Call authentication plugin API + 3. Handle response from authentication plugin API + 4. Check if server asked to use a different authentication plugin + 5. In case server asked to use a different authentication plugin + use that plugin to start the authentication process again. + 6. Complete authentication. + + All above tasks are implemented in below authsm_* functions where + authsm stads for authentication state machine. +*/ +static mysql_state_machine_status authsm_begin_plugin_auth( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_run_first_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_first_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_read_change_user_result( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_change_user_result( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_run_second_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_second_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_finish_auth(mysql_async_auth *ctx); + +/** + Asynchronous connection phase is divided into several smaller modules + where wach module does following: + 1. Begin the connection to the server, including any DNS resolution + necessary, socket configuration, etc + 2. Complete the connection itself + 3. Connection established, read the first packet + 4. Parse the handshake from the server + 5. Establish SSL connection if needed + 6. Invoke the plugin to send the authentication data to the server + 7. Authenticated, set intial database if specified + 8. Send COM_INIT_DB. + 9. Prepare to send a sequence of init commands. + 10.Send an init command. + + Below are the modules which does all above tasks. +*/ +static mysql_state_machine_status csm_begin_connect(mysql_async_connect *ctx); +static mysql_state_machine_status csm_complete_connect( + mysql_async_connect *ctx); +static mysql_state_machine_status csm_read_greeting(mysql_async_connect *ctx); +static mysql_state_machine_status csm_parse_handshake(mysql_async_connect *ctx); +static mysql_state_machine_status csm_establish_ssl(mysql_async_connect *ctx); +static mysql_state_machine_status csm_authenticate(mysql_async_connect *ctx); +static mysql_state_machine_status csm_prep_select_database( + mysql_async_connect *ctx); +#ifndef MYSQL_SERVER +static mysql_state_machine_status csm_prep_init_commands( + mysql_async_connect *ctx); +static mysql_state_machine_status csm_send_one_init_command( + mysql_async_connect *ctx); +#endif + #define MAX_CONNECTION_ATTR_STORAGE_LENGTH 65536 +int mysql_get_socket_descriptor(MYSQL *mysql) { + if (mysql && mysql->net.vio) { + return vio_fd(mysql->net.vio); + } + return -1; +} + /* clang-format off */ /** @page page_protocol_connection_phase_packets_protocol_handshake_response Protocol::HandshakeResponse: @@ -3701,16 +4614,19 @@ static int cli_establish_ssl(MYSQL *mysql) { @param mpvio The connection to use @param data The scramble to send @param data_len Length of data + @param buff_out Buffer holding client handshake packet + @param buff_len Length of buffer holding client handshake packet @retval 0 ok @retval 1 error @sa mysql_fill_packet_header() page_protocol_conn_packets_protocol_handshake_response */ -static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, - int data_len) { +static bool prep_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, + int data_len, char **buff_out, + int *buff_len) { + DBUG_ENTER(__func__); MYSQL *mysql = mpvio->mysql; - NET *net = &mysql->net; char *buff, *end; size_t buff_size; size_t connect_attrs_len = @@ -3721,13 +4637,17 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, DBUG_ASSERT(connect_attrs_len < MAX_CONNECTION_ATTR_STORAGE_LENGTH); + *buff_out = nullptr; + *buff_len = 0; + /* Fixed size of the packet is 32 bytes. See mysql_fill_packet_header. +9 because data is a length encoded binary where meta data size is max 9. */ buff_size = 33 + USERNAME_LENGTH + data_len + 9 + NAME_LEN + NAME_LEN + connect_attrs_len + 9; - buff = static_cast(my_alloca(buff_size)); + buff = static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, buff_size, MYF(MY_WME | MY_ZEROFILL))); /* The client_flags is already calculated. Just fill in the packet header */ end = mysql_fill_packet_header(mysql, buff, buff_size); @@ -3779,7 +4699,32 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, end = strmake(end, mpvio->plugin->name, NAME_LEN) + 1; end = (char *)send_client_connect_attrs(mysql, (uchar *)end); + *buff_out = buff; + *buff_len = end - buff; + + DBUG_RETURN(false); + +error: + my_free(buff); + DBUG_RETURN(true); +} + +static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, + int data_len) { + DBUG_ENTER(__func__); + MYSQL *mysql = mpvio->mysql; + NET *net = &mysql->net; + char *buff = nullptr, *end = nullptr; + int buff_len; + int ret = 0; + bool prep_err; + prep_err = prep_client_reply_packet(mpvio, data, data_len, &buff, &buff_len); + if (prep_err) { + DBUG_RETURN(1); + } + + end = buff + buff_len; /* Write authentication package */ MYSQL_TRACE(SEND_AUTH_RESPONSE, mysql, ((size_t)(end - buff), (const unsigned char *)buff)); @@ -3788,15 +4733,46 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "sending authentication information", errno); - goto error; + ret = 1; } MYSQL_TRACE(PACKET_SENT, mysql, ((size_t)(end - buff))); - return 0; - -error: - return 1; + my_free(buff); + DBUG_RETURN(ret); } +static net_async_status send_client_reply_packet_nonblocking(MCPVIO_EXT *mpvio, + const uchar *pkt, + int pkt_len, + bool *result) { + DBUG_ENTER(__func__); + MYSQL *mysql = mpvio->mysql; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + net_async_status status; + + bool error = false; + if (!ctx->change_user_buff) { + error = + prep_client_reply_packet(mpvio, pkt, pkt_len, &ctx->change_user_buff, + &ctx->change_user_buff_len); + if (error) { + goto end; + } + } + + status = my_net_write_nonblocking(&mysql->net, (uchar *)ctx->change_user_buff, + ctx->change_user_buff_len, &error); + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + +end: + *result = error; + my_free(ctx->change_user_buff); + ctx->change_user_buff = nullptr; + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /** vio->read_packet() callback method for client authentication plugins @@ -3854,6 +4830,81 @@ static int client_mpvio_read_packet(MYSQL_PLUGIN_VIO *mpv, uchar **buf) { return pkt_len; } +/** + vio->read_packet() nonblocking callback method for client authentication + plugins +*/ +static net_async_status client_mpvio_read_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *mpv, uchar **buf, int *result) { + DBUG_ENTER(__func__); + MCPVIO_EXT *mpvio = (MCPVIO_EXT *)mpv; + MYSQL *mysql = mpvio->mysql; + ulong pkt_len; + int error; + + /* there are cached data left, feed it to a plugin */ + if (mpvio->cached_server_reply.pkt) { + *buf = mpvio->cached_server_reply.pkt; + mpvio->cached_server_reply.pkt = 0; + mpvio->packets_read++; + *result = mpvio->cached_server_reply.pkt_len; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (mpvio->packets_read == 0) { + /* + the server handshake packet came from the wrong plugin, + or it's mysql_change_user(). Either way, there is no data + for a plugin to read. send a dummy packet to the server + to initiate a dialog. + */ + net_async_status status = + client_mpvio_write_packet_nonblocking(mpv, 0, 0, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (error) { + *result = (int)packet_error; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + + /* + packets_read needs to be set here to avoid entering above condition + again. + */ + mpvio->packets_read++; + /* otherwise read the data */ + net_async_status status = + mysql->methods->read_change_user_result_nonblocking(mysql, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + mpvio->last_read_packet_len = pkt_len; + *buf = mysql->net.read_pos; + + /* was it a request to change plugins ? */ + if (**buf == 254) { + *result = (int)packet_error; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + /* + the server sends \1\255 or \1\254 instead of just \255 or \254 - + for us to not confuse it with an error or "change plugin" packets. + We remove this escaping \1 here. + See also server_mpvio_write_packet() where the escaping is + done. + */ + if (pkt_len && **buf == 1) { + (*buf)++; + pkt_len--; + } + *result = pkt_len; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** vio->write_packet() callback method for client authentication plugins @@ -3894,6 +4945,53 @@ static int client_mpvio_write_packet(MYSQL_PLUGIN_VIO *mpv, const uchar *pkt, return res; } +/** + vio->write_packet() nonblocking callback method for client authentication + plugins +*/ +static net_async_status client_mpvio_write_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *mpv, const uchar *pkt, int pkt_len, int *result) { + DBUG_ENTER(__func__); + MCPVIO_EXT *mpvio = (MCPVIO_EXT *)mpv; + bool error = false; + + if (mpvio->packets_written == 0) { + /* mysql_change_user_nonblocking not implemented yet. */ + DBUG_ASSERT(!mpvio->mysql_change_user); + net_async_status status = + send_client_reply_packet_nonblocking(mpvio, pkt, pkt_len, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + } else { + NET *net = &mpvio->mysql->net; + + MYSQL_TRACE(SEND_AUTH_DATA, mpvio->mysql, ((size_t)pkt_len, pkt)); + + if (mpvio->mysql->thd) + *result = 1; /* no chit-chat in embedded */ + else { + net_async_status status = + my_net_write_nonblocking(net, pkt, pkt_len, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + *result = error; + + if (error) { + set_mysql_extended_error(mpvio->mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "sending authentication information", errno); + } else { + MYSQL_TRACE(PACKET_SENT, mpvio->mysql, ((size_t)pkt_len)); + } + } + } + mpvio->packets_written++; + *result = error ? -1 : 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** fills MYSQL_PLUGIN_VIO_INFO structure with the information about the connection @@ -3944,8 +5042,8 @@ static void client_mpvio_info(MYSQL_PLUGIN_VIO *vio, bool libmysql_cleartext_plugin_enabled = 0; -static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { - if (plugin == &clear_password_client_plugin && +static bool check_plugin_enabled(MYSQL *mysql, mysql_async_auth *ctx) { + if (ctx->auth_plugin == &clear_password_client_plugin && (!libmysql_cleartext_plugin_enabled && (!mysql->options.extension || !mysql->options.extension->enable_cleartext_plugin))) { @@ -3955,6 +5053,12 @@ static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { clear_password_client_plugin.name, "plugin not enabled"); return true; } + if (ctx->non_blocking && !ctx->auth_plugin->authenticate_user_nonblocking) { + set_mysql_extended_error( + mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_CANNOT_LOAD), ctx->auth_plugin->name, + "plugin does not support nonblocking connect"); + } return false; } @@ -3976,58 +5080,166 @@ static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { */ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, const char *data_plugin, const char *db) { - const char *auth_plugin_name; - auth_plugin_t *auth_plugin; - MCPVIO_EXT mpvio; - ulong pkt_length; - int res; - DBUG_ENTER("run_plugin_auth"); + mysql_state_machine_status status; + mysql_async_auth ctx; + memset(&ctx, 0, sizeof(ctx)); + + ctx.mysql = mysql; + ctx.data = data; + ctx.data_len = data_len; + ctx.data_plugin = data_plugin; + ctx.db = db; + ctx.non_blocking = false; + ctx.state_function = authsm_begin_plugin_auth; + + do { + status = ctx.state_function(&ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + DBUG_RETURN(status == STATE_MACHINE_FAILED); +} + +/** + This functions drives the authentication on client side in a nonblocking + way. This function will call differnt modules in a sequence where each + module is responsible to acheive a particular part in entire authentication + phase. + + @note this is used by both the mysql_real_connect_nonblocking + + @param mysql mysql + @param data pointer to the plugin auth data (scramble) in the + handshake packet + @param data_len the length of the data + @param data_plugin a plugin that data were prepared for + or 0 if it's mysql_change_user() + @param db initial db to use, can be 0 + + @retval NET_ASYNC_NOT_READY authentication not yet complete + @retval NET_ASYNC_COMPLETE authentication done +*/ +mysql_state_machine_status run_plugin_auth_nonblocking(MYSQL *mysql, char *data, + uint data_len, + const char *data_plugin, + const char *db) { + DBUG_ENTER(__func__); + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + if (!ctx) { + ctx = static_cast( + my_malloc(key_memory_MYSQL, sizeof(*ctx), MYF(MY_WME | MY_ZEROFILL))); + + ctx->mysql = mysql; + ctx->data = data; + ctx->data_len = data_len; + ctx->data_plugin = data_plugin; + ctx->db = db; + ctx->non_blocking = true; + ctx->state_function = authsm_begin_plugin_auth; + ASYNC_DATA(mysql)->connect_context->auth_context = ctx; + } + + mysql_state_machine_status ret = ctx->state_function(ctx); + if (ret == STATE_MACHINE_FAILED || ret == STATE_MACHINE_DONE) { + my_free(ctx); + ASYNC_DATA(mysql)->connect_context->auth_context = nullptr; + } + + DBUG_RETURN(ret); +} + +/** + Determine the default/initial plugin to use +*/ +static mysql_state_machine_status authsm_begin_plugin_auth( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* determine the default/initial plugin to use */ if (mysql->options.extension && mysql->options.extension->default_auth && mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { - auth_plugin_name = mysql->options.extension->default_auth; - if (!(auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( - mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) - DBUG_RETURN(1); /* oops, not found */ + ctx->auth_plugin_name = mysql->options.extension->default_auth; + if (!(ctx->auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( + mysql, ctx->auth_plugin_name, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN(STATE_MACHINE_FAILED); /* oops, not found */ } else { - auth_plugin = &caching_sha2_password_client_plugin; - auth_plugin_name = auth_plugin->name; + ctx->auth_plugin = &caching_sha2_password_client_plugin; + ctx->auth_plugin_name = ctx->auth_plugin->name; } - if (check_plugin_enabled(mysql, auth_plugin)) DBUG_RETURN(1); + if (check_plugin_enabled(mysql, ctx)) DBUG_RETURN(STATE_MACHINE_FAILED); - DBUG_PRINT("info", ("using plugin %s", auth_plugin_name)); + DBUG_PRINT("info", ("using plugin %s", ctx->auth_plugin_name)); mysql->net.last_errno = 0; /* just in case */ - if (data_plugin && strcmp(data_plugin, auth_plugin_name)) { + if (ctx->data_plugin && strcmp(ctx->data_plugin, ctx->auth_plugin_name)) { /* data was prepared for a different plugin, don't show it to this one */ - data = 0; - data_len = 0; - } + ctx->data = 0; + ctx->data_len = 0; + } + + ctx->mpvio.mysql_change_user = ctx->data_plugin == 0; + ctx->mpvio.cached_server_reply.pkt = (uchar *)ctx->data; + ctx->mpvio.cached_server_reply.pkt_len = ctx->data_len; + ctx->mpvio.read_packet = client_mpvio_read_packet; + ctx->mpvio.write_packet = client_mpvio_write_packet; + ctx->mpvio.read_packet_nonblocking = client_mpvio_read_packet_nonblocking; + ctx->mpvio.write_packet_nonblocking = client_mpvio_write_packet_nonblocking; + ctx->mpvio.info = client_mpvio_info; + ctx->mpvio.mysql = mysql; + ctx->mpvio.packets_read = ctx->mpvio.packets_written = 0; + ctx->mpvio.db = ctx->db; + ctx->mpvio.plugin = ctx->auth_plugin; + ctx->client_auth_plugin_state = + (int)(client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READING_PASSWORD); + + ctx->state_function = authsm_run_first_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - mpvio.mysql_change_user = data_plugin == 0; - mpvio.cached_server_reply.pkt = (uchar *)data; - mpvio.cached_server_reply.pkt_len = data_len; - mpvio.read_packet = client_mpvio_read_packet; - mpvio.write_packet = client_mpvio_write_packet; - mpvio.info = client_mpvio_info; - mpvio.mysql = mysql; - mpvio.packets_read = mpvio.packets_written = 0; - mpvio.db = db; - mpvio.plugin = auth_plugin; +/** + Authentication can have two authenticate_user calls, depending on + what the server responds with; this handles the first. +*/ +static mysql_state_machine_status authsm_run_first_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + MYSQL_TRACE(AUTH_PLUGIN, mysql, (ctx->auth_plugin->name)); + + if (ctx->non_blocking && ctx->auth_plugin->authenticate_user_nonblocking) { + net_async_status status = ctx->auth_plugin->authenticate_user_nonblocking( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql, &ctx->res); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } else { + ctx->res = ctx->auth_plugin->authenticate_user( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql); + } - MYSQL_TRACE(AUTH_PLUGIN, mysql, (auth_plugin->name)); + ctx->state_function = authsm_handle_first_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - res = auth_plugin->authenticate_user((MYSQL_PLUGIN_VIO *)&mpvio, mysql); +/** + Handle the result of the first authenticate_user. +*/ +static mysql_state_machine_status authsm_handle_first_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; DBUG_PRINT("info", ("authenticate_user returned %s", - res == CR_OK ? "CR_OK" - : res == CR_ERROR ? "CR_ERROR" - : res == CR_OK_HANDSHAKE_COMPLETE - ? "CR_OK_HANDSHAKE_COMPLETE" - : "error")); + ctx->res == CR_OK + ? "CR_OK" + : ctx->res == CR_ERROR ? "CR_ERROR" + : ctx->res == CR_OK_HANDSHAKE_COMPLETE + ? "CR_OK_HANDSHAKE_COMPLETE" + : "error")); static_assert(CR_OK == -1, ""); static_assert(CR_ERROR == 0, ""); @@ -4040,7 +5252,7 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, already performed required checks. Further, server side plugin did not really care about plugin used by client in this case. */ - if (res > CR_OK && + if (ctx->res > CR_OK && (!my_net_is_inited(&mysql->net) || (mysql->net.read_pos[0] != 0 && mysql->net.read_pos[0] != 254))) { /* @@ -4048,92 +5260,156 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, unless the error code is CR_ERROR and mysql->net.last_errno is already set (the plugin has done it) */ - DBUG_PRINT("info", ("res=%d", res)); - if (res > CR_ERROR) - set_mysql_error(mysql, res, unknown_sqlstate); + DBUG_PRINT("info", ("res=%d", ctx->res)); + if (ctx->res > CR_ERROR) + set_mysql_error(mysql, ctx->res, unknown_sqlstate); else if (!mysql->net.last_errno) set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); - DBUG_RETURN(1); + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = authsm_read_change_user_result; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} +/** + After the first authenticate_user comes a call to read the result of the + implied change_user. +*/ +static mysql_state_machine_status authsm_read_change_user_result( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* read the OK packet (or use the cached value in mysql->net.read_pos */ - if (res == CR_OK) - pkt_length = (*mysql->methods->read_change_user_result)(mysql); - else /* res == CR_OK_HANDSHAKE_COMPLETE */ - pkt_length = mpvio.last_read_packet_len; + if (ctx->res == CR_OK) { + if (ctx->non_blocking) { + net_async_status status = + (*mysql->methods->read_change_user_result_nonblocking)( + mysql, &ctx->pkt_length); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } else { + ctx->pkt_length = (*mysql->methods->read_change_user_result)(mysql); + } + } else /* res == CR_OK_HANDSHAKE_COMPLETE */ + ctx->pkt_length = ctx->mpvio.last_read_packet_len; + + ctx->state_function = authsm_handle_change_user_result; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - DBUG_PRINT("info", ("OK packet length=%lu", pkt_length)); - if (pkt_length == packet_error) { +/** + Check if server asked to use a different authentication plugin +*/ +static mysql_state_machine_status authsm_handle_change_user_result( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + DBUG_PRINT("info", ("OK packet length=%lu", ctx->pkt_length)); + if (ctx->pkt_length == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "reading authorization packet", errno); - DBUG_RETURN(1); + DBUG_RETURN(STATE_MACHINE_FAILED); } if (mysql->net.read_pos[0] == 254) { - /* The server asked to use a different authentication plugin */ - if (pkt_length < 2) { - set_mysql_error(mysql, CR_MALFORMED_PACKET, - unknown_sqlstate); /* purecov: inspected */ - DBUG_RETURN(1); - } else { - /* "use different plugin" packet */ - uint len; - auth_plugin_name = (char *)mysql->net.read_pos + 1; - len = (uint)strlen( - auth_plugin_name); /* safe as my_net_read always appends \0 */ - mpvio.cached_server_reply.pkt_len = pkt_length - len - 2; - mpvio.cached_server_reply.pkt = mysql->net.read_pos + len + 2; - DBUG_PRINT("info", ("change plugin packet from server for plugin %s", - auth_plugin_name)); - } - - if (!(auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( - mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) - DBUG_RETURN(1); - - if (check_plugin_enabled(mysql, auth_plugin)) DBUG_RETURN(1); + ctx->state_function = authsm_run_second_authenticate_user; + } else + ctx->state_function = authsm_finish_auth; - MYSQL_TRACE(AUTH_PLUGIN, mysql, (auth_plugin->name)); + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - mpvio.plugin = auth_plugin; - res = auth_plugin->authenticate_user((MYSQL_PLUGIN_VIO *)&mpvio, mysql); +/** + Start the authentication process again with the plugin which + server asked for. +*/ +static mysql_state_machine_status authsm_run_second_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + /* The server asked to use a different authentication plugin */ + if (ctx->pkt_length < 2) { + set_mysql_error(mysql, CR_MALFORMED_PACKET, + unknown_sqlstate); /* purecov: inspected */ + DBUG_RETURN(STATE_MACHINE_FAILED); + } else { + /* "use different plugin" packet */ + uint len; + ctx->auth_plugin_name = (char *)mysql->net.read_pos + 1; + len = (uint)strlen( + ctx->auth_plugin_name); /* safe as my_net_read always appends \0 */ + ctx->mpvio.cached_server_reply.pkt_len = ctx->pkt_length - len - 2; + ctx->mpvio.cached_server_reply.pkt = mysql->net.read_pos + len + 2; + DBUG_PRINT("info", ("change plugin packet from server for plugin %s", + ctx->auth_plugin_name)); + } + if (!(ctx->auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( + mysql, ctx->auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN(STATE_MACHINE_FAILED); + + if (check_plugin_enabled(mysql, ctx)) DBUG_RETURN(STATE_MACHINE_FAILED); + + MYSQL_TRACE(AUTH_PLUGIN, mysql, (ctx->auth_plugin->name)); + + ctx->mpvio.plugin = ctx->auth_plugin; + ctx->res = ctx->auth_plugin->authenticate_user( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql); + + ctx->state_function = authsm_handle_second_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - DBUG_PRINT("info", ("second authenticate_user returned %s", - res == CR_OK - ? "CR_OK" - : res == CR_ERROR ? "CR_ERROR" - : res == CR_OK_HANDSHAKE_COMPLETE - ? "CR_OK_HANDSHAKE_COMPLETE" - : "error")); - if (res > CR_OK) { - if (res > CR_ERROR) - set_mysql_error(mysql, res, unknown_sqlstate); - else if (!mysql->net.last_errno) - set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); - DBUG_RETURN(1); - } +/* Now read the results. */ +static mysql_state_machine_status authsm_handle_second_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + DBUG_PRINT("info", + ("second authenticate_user returned %s", + ctx->res == CR_OK + ? "CR_OK" + : ctx->res == CR_ERROR ? "CR_ERROR" + : ctx->res == CR_OK_HANDSHAKE_COMPLETE + ? "CR_OK_HANDSHAKE_COMPLETE" + : "error")); + if (ctx->res > CR_OK) { + if (ctx->res > CR_ERROR) + set_mysql_error(mysql, ctx->res, unknown_sqlstate); + else if (!mysql->net.last_errno) + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + DBUG_RETURN(STATE_MACHINE_FAILED); + } - if (res != CR_OK_HANDSHAKE_COMPLETE) { - /* Read what server thinks about out new auth message report */ - if (cli_safe_read(mysql, NULL) == packet_error) { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER_CLIENT(CR_SERVER_LOST_EXTENDED), - "reading final connect information", errno); - DBUG_RETURN(1); - } + if (ctx->res != CR_OK_HANDSHAKE_COMPLETE) { + /* Read what server thinks about out new auth message report */ + if (cli_safe_read(mysql, NULL) == packet_error) { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "reading final connect information", errno); + DBUG_RETURN(STATE_MACHINE_FAILED); } } + + ctx->state_function = authsm_finish_auth; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/* Final cleanup */ +static mysql_state_machine_status authsm_finish_auth(mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* net->read_pos[0] should always be 0 here if the server implements the protocol correctly */ - res = (mysql->net.read_pos[0] != 0); + ctx->res = (mysql->net.read_pos[0] != 0); MYSQL_TRACE(AUTHENTICATED, mysql, ()); - DBUG_RETURN(res); + DBUG_RETURN(ctx->res ? STATE_MACHINE_FAILED : STATE_MACHINE_DONE); } /** set some default attributes */ @@ -4180,34 +5456,156 @@ 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, ulong client_flag) { - char buff[NAME_LEN + USERNAME_LENGTH + 100]; - int scramble_data_len, pkt_scramble_len = 0; - char *end, *host_info = 0, *server_version_end, *pkt_end; - char *scramble_data; - char *scramble_buffer = NULL; - const char *scramble_plugin; - ulong pkt_length; + DBUG_ENTER("mysql_real_connect"); + mysql_state_machine_status status; + mysql_async_connect ctx; + memset(&ctx, 0, sizeof(ctx)); + + ctx.mysql = mysql; + ctx.host = host; + ctx.port = port; + ctx.db = db; + ctx.user = user; + ctx.passwd = passwd; + ctx.unix_socket = unix_socket; + ctx.client_flag = client_flag; + ctx.state_function = csm_begin_connect; + ctx.ssl_state = SSL_NONE; + + do { + status = ctx.state_function(&ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + if (status == STATE_MACHINE_DONE) { + DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); + DBUG_RETURN(mysql); + } + + DBUG_PRINT("error", ("message: %u/%s (%s)", mysql->net.last_errno, + mysql->net.sqlstate, mysql->net.last_error)); + { + /* Free alloced memory */ + end_server(mysql); + mysql_close_free(mysql); + if (!(client_flag & CLIENT_REMEMBER_OPTIONS)) + mysql_close_free_options(mysql); + if (ctx.scramble_buffer_allocated) my_free(ctx.scramble_buffer); + } + DBUG_RETURN(0); +} + +/** + This API attempts to initialize all the context needed to make an asynchronous + connection followed by establishing a connection to MySQL database. If this + API returns NET_ASYNC_COMPLETE then connection is established else call this + API from the client application until the status returned is + NET_ASYNC_COMPLETE. + + @param[in] mysql connection handle + @param[in] host host name or IP address + @param[in] user login ID used to connect to host + @param[in] passwd password for this login ID + @param[in] db default database to be set after connection + @param[in] port port number to use for connection + @param[in] unix_socket socket file to use for connection + @param[in] client_flag flag to indidcate what client can handle + + @retval NET_ASYNC_COMPLETE Success. + @retval NET_ASYNC_ERROR Error. +*/ +net_async_status STDCALL mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, uint port, const char *unix_socket, ulong client_flag) { + DBUG_ENTER(__func__); + + mysql_state_machine_status status; + mysql_async_connect *ctx = ASYNC_DATA(mysql)->connect_context; + + if (client_flag & MYSQL_OPT_COMPRESS) { + set_mysql_error(mysql, CR_COMPRESSION_NOT_SUPPORTED, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_ERROR); + } + if (!ctx) { + ctx = static_cast( + my_malloc(key_memory_MYSQL, sizeof(*ctx), MYF(MY_WME | MY_ZEROFILL))); + if (!ctx) DBUG_RETURN(NET_ASYNC_ERROR); + + ctx->mysql = mysql; + ctx->host = host; + ctx->port = port; + ctx->db = db; + ctx->user = user; + ctx->passwd = passwd; + ctx->unix_socket = unix_socket; + ctx->client_flag = client_flag; + ctx->non_blocking = true; + ctx->state_function = csm_begin_connect; + ctx->ssl_state = SSL_NONE; + ASYNC_DATA(mysql)->connect_context = ctx; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_CONNECT; + } + + do { + status = ctx->state_function(ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + if (status == STATE_MACHINE_DONE) { + my_free(ASYNC_DATA(mysql)->connect_context); + ASYNC_DATA(mysql)->connect_context = NULL; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_UNSET; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + { + DBUG_PRINT("error", ("message: %u/%s (%s)", mysql->net.last_errno, + mysql->net.sqlstate, mysql->net.last_error)); + /* Free alloced memory */ + end_server(mysql); + mysql_close_free(mysql); + if (!(ctx->client_flag & CLIENT_REMEMBER_OPTIONS)) + mysql_close_free_options(mysql); + if (ctx->scramble_buffer_allocated) { + my_free(ctx->scramble_buffer); + ctx->scramble_buffer = nullptr; + } + my_free(ctx); + DBUG_RETURN(NET_ASYNC_ERROR); + } +} +/** + Begin the connection to the server, including any DNS resolution + necessary, socket configuration, etc. +*/ +static mysql_state_machine_status csm_begin_connect(mysql_async_connect *ctx) { + MYSQL *mysql = ctx->mysql; + const char *host = ctx->host; + const char *user = ctx->user; + const char *passwd = ctx->passwd; + const char *db = ctx->db; + uint port = ctx->port; + const char *unix_socket = ctx->unix_socket; + ulong client_flag = ctx->client_flag; + + DBUG_ENTER(__func__); + + DBUG_PRINT("enter", + ("host: %s db: %s user: %s (client)", host ? host : "(Null)", + db ? db : "(Null)", user ? user : "(Null)")); + NET *net = &mysql->net; - bool scramble_buffer_allocated = false; #ifdef _WIN32 HANDLE hPipe = INVALID_HANDLE_VALUE; #endif #ifdef HAVE_SYS_UN_H struct sockaddr_un UNIXaddr; #endif - DBUG_ENTER("mysql_real_connect"); - - DBUG_PRINT("enter", - ("host: %s db: %s user: %s (client)", host ? host : "(Null)", - db ? db : "(Null)", user ? user : "(Null)")); - /* Test whether we're already connected */ if (net->vio) { set_mysql_error(mysql, CR_ALREADY_CONNECTED, unknown_sqlstate); - DBUG_RETURN(0); + DBUG_RETURN(STATE_MACHINE_FAILED); } - if (set_connect_attributes(mysql, buff, sizeof(buff))) DBUG_RETURN(0); + if (set_connect_attributes(mysql, ctx->buff, sizeof(ctx->buff))) + DBUG_RETURN(STATE_MACHINE_FAILED); mysql->methods = &client_methods; net->vio = 0; /* If something goes wrong */ @@ -4247,9 +5645,6 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, MYSQL_TRACE_STAGE(mysql, CONNECTING); MYSQL_TRACE(CONNECTING, mysql, ()); - /* - Part 0: Grab a socket and connect it to the server - */ #if defined(_WIN32) if ((!mysql->options.protocol || mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) && @@ -4265,8 +5660,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, ("host: '%s' socket: '%s' shared memory: %s have_tcpip: %d", host ? host : "", unix_socket ? unix_socket : "", mysql->options.shared_memory_base_name, (int)have_tcpip)); - if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) goto error; - + if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) + DBUG_RETURN(STATE_MACHINE_FAILED); /* Try also with PIPE or TCP/IP. Clear the error from create_shared_memory(). @@ -4277,7 +5672,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, mysql->options.protocol = MYSQL_PROTOCOL_MEMORY; unix_socket = 0; host = mysql->options.shared_memory_base_name; - snprintf(host_info = buff, sizeof(buff) - 1, + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, ER_CLIENT(CR_SHARED_MEMORY_CONNECTION), host); } } @@ -4293,7 +5688,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (sock == SOCKET_ERROR) { set_mysql_extended_error(mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, ER_CLIENT(CR_SOCKET_CREATE_ERROR), socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } net->vio = @@ -4302,12 +5697,14 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, DBUG_PRINT("error", ("Unknow protocol %d ", mysql->options.protocol)); set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); closesocket(sock); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + if (ctx->non_blocking) vio_set_blocking_flag(net->vio, !ctx->non_blocking); + host = LOCAL_HOST; if (!unix_socket) unix_socket = mysql_unix_port; - host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION); + ctx->host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION); DBUG_PRINT("info", ("Using UNIX sock '%s'", unix_socket)); memset(&UNIXaddr, 0, sizeof(UNIXaddr)); @@ -4318,7 +5715,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, my_net_set_retry_count(net, mysql->options.extension->retry_count); if (vio_socket_connect(net->vio, (struct sockaddr *)&UNIXaddr, - sizeof(UNIXaddr), get_vio_connect_timeout(mysql))) { + sizeof(UNIXaddr), ctx->non_blocking, + get_vio_connect_timeout(mysql))) { DBUG_PRINT("error", ("Got error %d on connect to local server", socket_errno)); set_mysql_extended_error(mysql, CR_CONNECTION_ERROR, unknown_sqlstate, @@ -4326,7 +5724,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, socket_errno); vio_delete(net->vio); net->vio = 0; - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } mysql->options.protocol = MYSQL_PROTOCOL_SOCKET; } @@ -4345,11 +5743,11 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (mysql->options.protocol == MYSQL_PROTOCOL_PIPE || (host && !strcmp(host, LOCAL_HOST_NAMEDPIPE)) || (unix_socket && !strcmp(unix_socket, MYSQL_NAMEDPIPE))) - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); /* Try also with TCP/IP */ } else { net->vio = vio_new_win32pipe(hPipe); - snprintf(host_info = buff, sizeof(buff) - 1, + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, ER_CLIENT(CR_NAMEDPIPE_CONNECTION), unix_socket); } } @@ -4370,8 +5768,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (!host) host = LOCAL_HOST; - snprintf(host_info = buff, sizeof(buff) - 1, ER_CLIENT(CR_TCP_CONNECTION), - host); + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, + ER_CLIENT(CR_TCP_CONNECTION), host); DBUG_PRINT("info", ("Server name: '%s'. TCP sock: %d", host, port)); memset(&hints, 0, sizeof(hints)); @@ -4391,8 +5789,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, DBUG_PRINT("info", ("IPV6 getaddrinfo error %d", gai_errno)); set_mysql_extended_error(mysql, CR_UNKNOWN_HOST, unknown_sqlstate, ER_CLIENT(CR_UNKNOWN_HOST), host, errno); - - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } /* Get address info for client bind name if it is provided */ @@ -4412,7 +5809,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, mysql->options.bind_address, bind_gai_errno); freeaddrinfo(res_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } DBUG_PRINT("info", (" got address info for client bind name")); } @@ -4480,7 +5877,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, closesocket(sock); freeaddrinfo(res_lst); if (client_bind_ai_lst) freeaddrinfo(client_bind_ai_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } /* Just reinitialize if one is already allocated. */ @@ -4489,17 +5886,20 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, closesocket(sock); freeaddrinfo(res_lst); if (client_bind_ai_lst) freeaddrinfo(client_bind_ai_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + if (ctx->non_blocking) + vio_set_blocking_flag(net->vio, !ctx->non_blocking); + DBUG_PRINT("info", ("Connect socket")); if (mysql->options.extension && mysql->options.extension->retry_count) my_net_set_retry_count(net, mysql->options.extension->retry_count); - status = vio_socket_connect(net->vio, t_res->ai_addr, - (socklen_t)t_res->ai_addrlen, - get_vio_connect_timeout(mysql)); + status = vio_socket_connect( + net->vio, t_res->ai_addr, (socklen_t)t_res->ai_addrlen, + ctx->non_blocking, get_vio_connect_timeout(mysql)); /* Here we rely on vio_socket_connect() to return success only if the connect attempt was really successful. Otherwise we would @@ -4526,7 +5926,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (sock == SOCKET_ERROR) { set_mysql_extended_error(mysql, CR_IPSOCK_ERROR, unknown_sqlstate, ER_CLIENT(CR_IPSOCK_ERROR), saved_error); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } if (status) { @@ -4535,22 +5935,41 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, set_mysql_extended_error(mysql, CR_CONN_HOST_ERROR, unknown_sqlstate, ER_CLIENT(CR_CONN_HOST_ERROR), host, saved_error); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } + ctx->state_function = csm_complete_connect; + ctx->host = host; + ctx->user = user; + ctx->passwd = passwd; + ctx->db = db; + ctx->port = port; + ctx->unix_socket = unix_socket; + ctx->client_flag = client_flag; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/** + Complete the connection itself, setting options on the now-connected socket. +*/ +static mysql_state_machine_status csm_complete_connect( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; DBUG_PRINT("info", ("net->vio: %p", net->vio)); if (!net->vio) { DBUG_PRINT("error", ("Unknow protocol %d ", mysql->options.protocol)); set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } if (my_net_init(net, net->vio)) { vio_delete(net->vio); net->vio = 0; set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } vio_keepalive(net->vio, true); @@ -4581,22 +6000,51 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "waiting for initial communication packet", socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_read_greeting; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - /* - Part 1: Connection established, read and parse first packet - */ +/** + Read the greeting from the server that is read the first packet +*/ +static mysql_state_machine_status csm_read_greeting(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; DBUG_PRINT("info", ("Read first packet.")); - if ((pkt_length = cli_safe_read(mysql, NULL)) == packet_error) { + if (!ctx->non_blocking) + ctx->pkt_length = cli_safe_read(mysql, NULL); + else { + if (cli_safe_read_nonblocking(mysql, NULL, &ctx->pkt_length) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } + if (ctx->pkt_length == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "reading initial communication packet", socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_parse_handshake; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/** + Parse the handshake from the server. +*/ +static mysql_state_machine_status csm_parse_handshake( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; + int pkt_length = ctx->pkt_length; + int pkt_scramble_len = 0; + char *end, *server_version_end, *pkt_end; pkt_end = (char *)net->read_pos + pkt_length; /* Check if version of protocol matches current one */ mysql->protocol_version = net->read_pos[0]; @@ -4607,7 +6055,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, set_mysql_extended_error(mysql, CR_VERSION_ERROR, unknown_sqlstate, ER_CLIENT(CR_VERSION_ERROR), mysql->protocol_version, PROTOCOL_VERSION); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } server_version_end = end = strend((char *)net->read_pos + 1); mysql->thread_id = uint4korr((uchar *)end + 1); @@ -4616,10 +6064,10 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, Scramble is split into two parts because old clients do not understand long scrambles; here goes the first part. */ - scramble_data = end; - scramble_data_len = AUTH_PLUGIN_DATA_PART_1_LENGTH + 1; - scramble_plugin = NULL; - end += scramble_data_len; + ctx->scramble_data = end; + ctx->scramble_data_len = AUTH_PLUGIN_DATA_PART_1_LENGTH + 1; + ctx->scramble_plugin = NULL; + end += ctx->scramble_data_len; if (pkt_end >= end + 1) mysql->server_capabilities = uint2korr((uchar *)end); if (pkt_end >= end + 18) { @@ -4631,34 +6079,34 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (pkt_scramble_len < 0) { set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); /* purecov: inspected */ - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } end += 18; - if (mysql_init_character_set(mysql)) goto error; + if (mysql_init_character_set(mysql)) DBUG_RETURN(STATE_MACHINE_FAILED); /* Save connection information */ - if (!my_multi_malloc(key_memory_MYSQL, MYF(0), &mysql->host_info, - (uint)strlen(host_info) + 1, &mysql->host, - (uint)strlen(host) + 1, &mysql->unix_socket, - unix_socket ? (uint)strlen(unix_socket) + 1 : (uint)1, - &mysql->server_version, - (uint)(server_version_end - (char *)net->read_pos + 1), - NullS) || - !(mysql->user = my_strdup(key_memory_MYSQL, user, MYF(0))) || - !(mysql->passwd = my_strdup(key_memory_MYSQL, passwd, MYF(0)))) { + if (!my_multi_malloc( + key_memory_MYSQL, MYF(0), &mysql->host_info, + (uint)strlen(ctx->host_info) + 1, &mysql->host, + (uint)strlen(ctx->host) + 1, &mysql->unix_socket, + ctx->unix_socket ? (uint)strlen(ctx->unix_socket) + 1 : (uint)1, + &mysql->server_version, + (uint)(server_version_end - (char *)net->read_pos + 1), NullS) || + !(mysql->user = my_strdup(key_memory_MYSQL, ctx->user, MYF(0))) || + !(mysql->passwd = my_strdup(key_memory_MYSQL, ctx->passwd, MYF(0)))) { set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } - my_stpcpy(mysql->host_info, host_info); - my_stpcpy(mysql->host, host); - if (unix_socket) - my_stpcpy(mysql->unix_socket, unix_socket); + my_stpcpy(mysql->host_info, ctx->host_info); + my_stpcpy(mysql->host, ctx->host); + if (ctx->unix_socket) + my_stpcpy(mysql->unix_socket, ctx->unix_socket); else mysql->unix_socket = 0; my_stpcpy(mysql->server_version, (char *)net->read_pos + 1); - mysql->port = port; + mysql->port = ctx->port; if (pkt_end >= end + SCRAMBLE_LENGTH - AUTH_PLUGIN_DATA_PART_1_LENGTH + 1) @@ -4668,137 +6116,195 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, to get a full continuous scramble. We've read all the header, and can overwrite it now. */ - memmove(end - AUTH_PLUGIN_DATA_PART_1_LENGTH, scramble_data, + memmove(end - AUTH_PLUGIN_DATA_PART_1_LENGTH, ctx->scramble_data, AUTH_PLUGIN_DATA_PART_1_LENGTH); - scramble_data = end - AUTH_PLUGIN_DATA_PART_1_LENGTH; + ctx->scramble_data = end - AUTH_PLUGIN_DATA_PART_1_LENGTH; if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { - scramble_data_len = pkt_scramble_len; - scramble_plugin = scramble_data + scramble_data_len; - if (scramble_data + scramble_data_len > pkt_end) - scramble_data_len = (int)(pkt_end - scramble_data); + ctx->scramble_data_len = pkt_scramble_len; + ctx->scramble_plugin = ctx->scramble_data + ctx->scramble_data_len; + if (ctx->scramble_data + ctx->scramble_data_len > pkt_end) + ctx->scramble_data_len = (int)(pkt_end - ctx->scramble_data); } else { - scramble_data_len = (int)(pkt_end - scramble_data); - scramble_plugin = caching_sha2_password_plugin_name; + ctx->scramble_data_len = (int)(pkt_end - ctx->scramble_data); + ctx->scramble_plugin = caching_sha2_password_plugin_name; } } else { set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_establish_ssl; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - MYSQL_TRACE(INIT_PACKET_RECEIVED, mysql, (pkt_length, net->read_pos)); - MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); +/** + Establish SSL if needed. +*/ +static mysql_state_machine_status csm_establish_ssl(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + /* This check happens to work for both sync and async. */ + if (ctx->ssl_state == SSL_NONE) { + MYSQL_TRACE(INIT_PACKET_RECEIVED, mysql, + (ctx->pkt_length, mysql->net.read_pos)); + MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); #if defined(_WIN32) - if ((mysql->options.extension && - mysql->options.extension->ssl_mode <= SSL_MODE_PREFERRED) && - (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY || - mysql->options.protocol == MYSQL_PROTOCOL_PIPE)) { - mysql->options.extension->ssl_mode = SSL_MODE_DISABLED; - } + if ((mysql->options.extension && + mysql->options.extension->ssl_mode <= SSL_MODE_PREFERRED) && + (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY || + mysql->options.protocol == MYSQL_PROTOCOL_PIPE)) { + mysql->options.extension->ssl_mode = SSL_MODE_DISABLED; + } #endif - /* try and bring up SSL if possible */ - cli_calculate_client_flag(mysql, db, client_flag); + /* try and bring up SSL if possible */ + cli_calculate_client_flag(mysql, ctx->db, ctx->client_flag); - /* - Allocate separate buffer for scramble data if we are going - to attempt TLS connection. This would prevent a possible - overwrite through my_net_write. - */ - if (scramble_data_len && mysql->options.extension && - mysql->options.extension->ssl_mode != SSL_MODE_DISABLED) { - if (!(scramble_buffer = (char *)my_malloc( - key_memory_MYSQL_HANDSHAKE, scramble_data_len, MYF(MY_WME)))) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + /* + Allocate separate buffer for scramble data if we are going + to attempt TLS connection. This would prevent a possible + overwrite through my_net_write. + */ + if (ctx->scramble_data_len && mysql->options.extension && + mysql->options.extension->ssl_mode != SSL_MODE_DISABLED) { + if (!(ctx->scramble_buffer = + (char *)my_malloc(key_memory_MYSQL_HANDSHAKE, + ctx->scramble_data_len, MYF(MY_WME)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(STATE_MACHINE_FAILED); + } + ctx->scramble_buffer_allocated = true; + memcpy(ctx->scramble_buffer, ctx->scramble_data, ctx->scramble_data_len); + } else { + ctx->scramble_buffer = ctx->scramble_data; + } + } + if (ctx->non_blocking) { + int ret; + if (cli_establish_ssl_nonblocking(mysql, &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + if (ret) { + DBUG_RETURN(STATE_MACHINE_FAILED); } - scramble_buffer_allocated = true; - memcpy(scramble_buffer, scramble_data, scramble_data_len); } else { - scramble_buffer = scramble_data; + if (cli_establish_ssl(mysql)) { + DBUG_RETURN(STATE_MACHINE_FAILED); + } } - if (cli_establish_ssl(mysql)) goto error; - - /* - Part 2: invoke the plugin to send the authentication data to the server - */ + ctx->state_function = csm_authenticate; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - if (run_plugin_auth(mysql, scramble_buffer, scramble_data_len, - scramble_plugin, db)) - goto error; +/** + Invoke the authentication client plugin API to send the authentication + data to the server +*/ +static mysql_state_machine_status csm_authenticate(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + if (ctx->non_blocking) { + mysql_state_machine_status status = run_plugin_auth_nonblocking( + ctx->mysql, ctx->scramble_data, ctx->scramble_data_len, + ctx->scramble_plugin, ctx->db); + if (status != STATE_MACHINE_DONE) { + DBUG_RETURN(status); + } + } else { + if (run_plugin_auth(mysql, ctx->scramble_buffer, ctx->scramble_data_len, + ctx->scramble_plugin, ctx->db)) { + DBUG_RETURN(STATE_MACHINE_FAILED); + } + } - if (scramble_buffer_allocated == true) { - scramble_buffer_allocated = false; - my_free(scramble_buffer); + if (ctx->scramble_buffer_allocated) { + ctx->scramble_buffer_allocated = false; + my_free(ctx->scramble_buffer); + ctx->scramble_buffer = nullptr; } - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + ctx->state_function = csm_prep_select_database; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - /* - Part 3: authenticated, finish the initialization of the connection - */ +/** + Authenticated, set intial database if specified +*/ +static mysql_state_machine_status csm_prep_select_database( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); if (mysql->client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress = 1; #ifdef CHECK_LICENSE - if (check_license(mysql)) goto error; + if (check_license(mysql)) DBUG_RETURN(STATE_MACHINE_FAILED); #endif - if (db && !mysql->db && mysql_select_db(mysql, db)) { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER_CLIENT(CR_SERVER_LOST_EXTENDED), - "Setting intital database", errno); - goto error; - } +#ifdef MYSQL_SERVER + DBUG_RETURN(STATE_MACHINE_DONE); +#else + ctx->state_function = csm_prep_init_commands; +#endif - /* - Using init_commands is not supported when connecting from within the - server. - */ -#ifndef MYSQL_SERVER - if (mysql->options.init_commands) { - char **ptr = mysql->options.init_commands->begin(); - char **end_command = mysql->options.init_commands->end(); + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - bool reconnect = mysql->reconnect; - mysql->reconnect = 0; +#ifndef MYSQL_SERVER +/** + Prepare to send a sequence of init commands. +*/ +static mysql_state_machine_status csm_prep_init_commands( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + if (!mysql->options.init_commands) { + DBUG_RETURN(STATE_MACHINE_DONE); + } - for (; ptr < end_command; ptr++) { - int status; + ctx->saved_reconnect = mysql->reconnect; + mysql->reconnect = 0; + ctx->current_init_command = mysql->options.init_commands->begin(); - if (mysql_real_query(mysql, *ptr, (ulong)strlen(*ptr))) goto error; + ctx->state_function = csm_send_one_init_command; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - do { - if (mysql->fields) { - MYSQL_RES *res; - if (!(res = cli_use_result(mysql))) goto error; - mysql_free_result(res); - } - if ((status = mysql_next_result(mysql)) > 0) goto error; - } while (status == 0); +/** + Send an init command. This is called once per init command until + they've all been run (or a failure occurs). +*/ +static mysql_state_machine_status csm_send_one_init_command( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + + if (mysql_real_query(mysql, *ctx->current_init_command, + (ulong)strlen(*ctx->current_init_command))) + DBUG_RETURN(STATE_MACHINE_FAILED); + int status; + do { + if (mysql->fields) { + MYSQL_RES *res; + if (!(res = cli_use_result(mysql))) DBUG_RETURN(STATE_MACHINE_FAILED); + mysql_free_result(res); } - mysql->reconnect = reconnect; - } -#endif - - DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); - DBUG_RETURN(mysql); + if ((status = mysql_next_result(mysql)) > 0) + DBUG_RETURN(STATE_MACHINE_FAILED); + } while (status == 0); -error: - DBUG_PRINT("error", ("message: %u/%s (%s)", net->last_errno, net->sqlstate, - net->last_error)); - { - /* Free alloced memory */ - end_server(mysql); - mysql_close_free(mysql); - if (!(client_flag & CLIENT_REMEMBER_OPTIONS)) - mysql_close_free_options(mysql); - if (scramble_buffer_allocated) my_free(scramble_buffer); + ++ctx->current_init_command; + if (ctx->current_init_command < mysql->options.init_commands->end()) { + DBUG_RETURN(STATE_MACHINE_CONTINUE); } - DBUG_RETURN(0); + mysql->reconnect = ctx->saved_reconnect; + DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); + DBUG_RETURN(STATE_MACHINE_DONE); } +#endif bool mysql_reconnect(MYSQL *mysql) { MYSQL tmp_mysql; @@ -5223,7 +6729,17 @@ void STDCALL mysql_close(MYSQL *mysql) { if (mysql->net.vio != 0) { free_old_query(mysql); mysql->status = MYSQL_STATUS_READY; /* Force command */ - simple_command(mysql, COM_QUIT, (uchar *)0, 0, 1); + if (vio_is_blocking(mysql->net.vio)) { + simple_command(mysql, COM_QUIT, (uchar *)0, 0, 1); + } else { + /* + Best effort; try to toss a command on the wire, but we can't wait + to hear back. + */ + bool err; /* unused */ + simple_command_nonblocking(mysql, COM_QUIT, (uchar *)0, 0, 1, &err); + } + mysql->reconnect = 0; end_server(mysql); /* Sets mysql->net.vio= 0 */ } @@ -5293,6 +6809,107 @@ static bool cli_read_query_result(MYSQL *mysql) { DBUG_PRINT("exit", ("ok")); DBUG_RETURN(0); } +static net_async_status cli_read_query_result_nonblocking(MYSQL *mysql) { + DBUG_ENTER(__func__); + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + uchar *pos = nullptr; + ulong field_count; + ulong length; + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_IDLE) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT; + } + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT) { + net_async_status status = + cli_safe_read_nonblocking(mysql, nullptr, &length); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (length == packet_error) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + mysql->packet_length = length; + + free_old_query(mysql); /* Free old result */ +#ifndef MYSQL_SERVER /* Avoid warn of unused labels*/ + get_info: +#endif + pos = (uchar *)mysql->net.read_pos; + if ((field_count = net_field_length(&pos)) == 0) { + read_ok_ex(mysql, length); +#if defined(CLIENT_PROTOCOL_TRACING) + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + else + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } +#ifndef MYSQL_SERVER + if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ + { + int error; + + MYSQL_TRACE_STAGE(mysql, FILE_REQUEST); + + if (!(mysql->options.client_flag & CLIENT_LOCAL_FILES)) { + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + + error = handle_local_infile(mysql, (char *)pos); + + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + + /* TODO: Make LOAD DATA LOCAL INFILE asynchronous. */ + if ((length = cli_safe_read(mysql, NULL)) == packet_error || error) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + goto get_info; /* Get info packet */ + } +#endif + if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) + mysql->server_status |= SERVER_STATUS_IN_TRANS; + + mysql->field_count = (uint)field_count; + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO; + } + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO) { + int res; + net_async_status status = read_com_query_metadata_nonblocking( + mysql, pos, mysql->field_count, &res); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (res) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + } + + mysql->status = MYSQL_STATUS_GET_RESULT; + DBUG_PRINT("exit", ("ok, %u", mysql->field_count)); + net_async->async_read_query_result_status = NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* Send the query and return so we can do something else. @@ -5311,6 +6928,38 @@ int STDCALL mysql_send_query(MYSQL *mysql, const char *query, ulong length) { DBUG_RETURN(simple_command(mysql, COM_QUERY, (uchar *)query, length, 1)); } +/** + Executes the SQL statement pointed by query. This API is called by + mysql_real_query_nonblocking to send query to server in asynchronous way. + + @param[in] mysql connection handle + @param[in] query query string to be executed + @param[in] length length of query + + @retval NET_ASYNC_ERROR query execution failed + @retval NET_ASYNC_NOT_READY query not yet completed, call this API again + @retval NET_ASYNC_COMPLETE query execution finished +*/ +net_async_status STDCALL mysql_send_query_nonblocking(MYSQL *mysql, + const char *query, + ulong length) { + DBUG_ENTER(__func__); + STATE_INFO *info; + + if ((info = STATE_DATA(mysql))) + free_state_change_info(static_cast(mysql->extension)); + + bool ret; + if (simple_command_nonblocking(mysql, COM_QUERY, (uchar *)query, length, 1, + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (ret) + DBUG_RETURN(NET_ASYNC_ERROR); + else + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { int retval; DBUG_ENTER("mysql_real_query"); @@ -5327,6 +6976,72 @@ int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { DBUG_RETURN(retval); } +/** + Executes the SQL statement pointed by query. This sql statement length is set + in length parameter. query string can contain multiple sql statements + separated by semicolons. This function can return immediately with status set + to NET_ASYNC_NOT_READY, in this case client application is expected to call + this API until it returns NET_ASYNC_COMPLETE. + + @param[in] mysql connection handle + @param[in] query query string to be executed + @param[in] length length of query + + @retval NET_ASYNC_ERROR query execution failed + @retval NET_ASYNC_NOT_READY query not yet completed, call this API again + @retval NET_ASYNC_COMPLETE query execution finished +*/ +net_async_status STDCALL mysql_real_query_nonblocking(MYSQL *mysql, + const char *query, + ulong length) { + DBUG_ENTER(__func__); + DBUG_PRINT("enter", ("handle: %p", mysql)); + DBUG_PRINT("query", ("Query = '%-.*s'", (int)length, query)); + DBUG_EXECUTE_IF("inject_ER_NET_READ_INTERRUPTED", { + mysql->net.last_errno = ER_NET_READ_INTERRUPTED; + DBUG_SET("-d,inject_ER_NET_READ_INTERRUPTED"); + DBUG_RETURN(NET_ASYNC_ERROR); + }); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + DBUG_ASSERT(async_context->async_op_status == ASYNC_OP_UNSET || + async_context->async_op_status == ASYNC_OP_QUERY); + + net_async_status status = NET_ASYNC_NOT_READY; + /* 1st phase: send query. */ + if (async_context->async_query_state == QUERY_IDLE) { + async_context->async_query_length = length; + async_context->async_op_status = ASYNC_OP_QUERY; + async_context->async_query_state = QUERY_SENDING; + } + + if (async_context->async_query_state == QUERY_SENDING) { + status = mysql_send_query_nonblocking(mysql, query, length); + if (status == NET_ASYNC_NOT_READY) + DBUG_RETURN(NET_ASYNC_NOT_READY); + else if (status == NET_ASYNC_ERROR) + goto end; + async_context->async_query_state = QUERY_READING_RESULT; + } + + /* 2nd phase: read query result (field count, field info) */ + if (async_context->async_query_state == QUERY_READING_RESULT) { + status = (*mysql->methods->read_query_result_nonblocking)(mysql); + if (status == NET_ASYNC_NOT_READY) + DBUG_RETURN(NET_ASYNC_NOT_READY); + else if (status == NET_ASYNC_ERROR) + goto end; + } + +end: + async_context->async_op_status = ASYNC_OP_UNSET; + async_context->async_query_state = QUERY_IDLE; + async_context->async_query_length = 0; + if (status == NET_ASYNC_ERROR) + DBUG_RETURN(NET_ASYNC_ERROR); + else + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /************************************************************************** Alloc result struct for buffered results. All rows are read to buffer. mysql_data_seek may be used. @@ -5381,6 +7096,87 @@ MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql) { DBUG_RETURN(result); /* Data fetched */ } +/** + This API reads all result set sent by server in an asynchronous way + + @param[in] mysql connection handle + @param[in] result buffer which holds all result sets. + + @retval NET_ASYNC_NOT_READY reading of result sets not complete + @retval NET_ASYNC_COMPLETE completed this asynchronous operation +*/ +enum net_async_status STDCALL +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result) { + DBUG_ENTER(__func__); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + *result = nullptr; + + /* + Some queries (e.g. "CALL") may return an empty resultset. + mysql->field_count is 0 in such cases. + */ + if (!mysql->field_count) { + goto end; + } + if (!async_context->async_store_result_result) { + if (mysql->status != MYSQL_STATUS_GET_RESULT) { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + goto end; + } + mysql->status = MYSQL_STATUS_READY; /* server is ready */ + + if (!(async_context->async_store_result_result = (MYSQL_RES *)my_malloc( + key_memory_MYSQL_RES, + (uint)(sizeof(MYSQL_RES) + sizeof(ulong) * mysql->field_count), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto end; + } + if (!(async_context->async_store_result_result->field_alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + my_free(async_context->async_store_result_result); + goto end; + } + async_context->async_store_result_result->methods = mysql->methods; + async_context->async_store_result_result->eof = 1; /* Marker for buffered */ + async_context->async_store_result_result->lengths = + (ulong *)(async_context->async_store_result_result + 1); + } + + if ((*mysql->methods->read_rows_nonblocking)( + mysql, mysql->fields, mysql->field_count, + &async_context->async_store_result_result->data) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!async_context->async_store_result_result->data) { + my_free(async_context->async_store_result_result->field_alloc); + my_free(async_context->async_store_result_result); + goto end; + } + mysql->affected_rows = async_context->async_store_result_result->row_count = + async_context->async_store_result_result->data->rows; + async_context->async_store_result_result->data_cursor = + async_context->async_store_result_result->data->data; + async_context->async_store_result_result->fields = mysql->fields; + *async_context->async_store_result_result->field_alloc = + std::move(*mysql->field_alloc); + async_context->async_store_result_result->field_count = mysql->field_count; + async_context->async_store_result_result->metadata = + mysql->resultset_metadata; + /* The rest of result members is zerofilled in my_malloc */ + mysql->fields = 0; /* fields is now in result */ + /* just in case this was mistakenly called after mysql_stmt_execute() */ + mysql->unbuffered_fetch_owner = 0; + *result = async_context->async_store_result_result; +end: + async_context->async_store_result_result = nullptr; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /************************************************************************** Alloc struct for use with unbuffered reads. Data is fetched by domand when calling to mysql_fetch_row. @@ -5482,6 +7278,73 @@ MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *res) { DBUG_RETURN(res->current_row = tmp); } } +/** + Reads next row of a result set in an asynchronous way. + + @param[in] res buffer in which all rows are stored + @param[out] row return pointer to one row from result set + + @retval NET_ASYNC_NOT_READY fetch operation not complete, retry again + @retval NET_ASYNC_COMPLETE fetch operation complete +*/ +net_async_status STDCALL mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row) { + DBUG_ENTER(__func__); + MYSQL *mysql = res->handle; + *row = nullptr; + + if (!res->data) { /* Unbufferred fetch */ + if (!res->eof) { + if (mysql->status == MYSQL_STATUS_USE_RESULT) { + int ret; + if (read_one_row_nonblocking(mysql, res->field_count, res->row, + res->lengths, + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!ret) { + *row = res->current_row = res->row; + goto end; + } + } + + set_mysql_error(mysql, + res->unbuffered_fetch_cancelled ? CR_FETCH_CANCELED + : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate); + DBUG_PRINT("info", ("end of data")); + res->eof = 1; + mysql->status = MYSQL_STATUS_READY; + /* + Reset only if owner points to us: there is a chance that + somebody started new query after mysql_stmt_close(): + */ + if (mysql->unbuffered_fetch_owner == &res->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner = 0; + /* Don't clear handle in mysql_free_result */ + res->handle = 0; + } + + *row = nullptr; + goto end; + } + { + MYSQL_ROW tmp; + if (!res->data_cursor) { + DBUG_PRINT("info", ("end of data")); + *row = nullptr; + goto end; + } + tmp = res->data_cursor->data; + res->data_cursor = res->data_cursor->next; + *row = res->current_row = tmp; + goto end; + } + +end: + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /************************************************************************** Get column lengths of the current row @@ -6239,6 +8102,95 @@ static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + Client authentication plugin that does native MySQL authentication + in a nonblocking way. + + @param[in] vio the channel to operate on + @param[in] mysql the MYSQL structure to operate on + @param[out] result CR_OK : Success, CR_ERROR : error reading, + CR_SERVER_HANDSHAKE_ERR : malformed handshake data + + @retval NET_ASYNC_NOT_READY authentication not yet complete + @retval NET_ASYNC_COMPLETE authentication done +*/ +static net_async_status native_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result) { + DBUG_ENTER(__func__); + int io_result; + uchar *pkt; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_native_password_plugin_status::NATIVE_READING_PASSWORD: + if (((MCPVIO_EXT *)vio)->mysql_change_user) { + /* mysql_change_user_nonblocking not implemented yet. */ + DBUG_ASSERT(false); + } else { + /* read the scramble */ + net_async_status status = + vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (io_result != SCRAMBLE_LENGTH + 1) { + *result = CR_SERVER_HANDSHAKE_ERR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + /* save it in MYSQL */ + memcpy(mysql->scramble, pkt, SCRAMBLE_LENGTH); + mysql->scramble[SCRAMBLE_LENGTH] = 0; + } + ctx->client_auth_plugin_state = (int) + client_auth_native_password_plugin_status::NATIVE_WRITING_RESPONSE; + + /* fallthrough */ + + case client_auth_native_password_plugin_status::NATIVE_WRITING_RESPONSE: + if (mysql->passwd[0]) { + char scrambled[SCRAMBLE_LENGTH + 1]; + DBUG_PRINT("info", ("sending scramble")); + scramble(scrambled, (char *)pkt, mysql->passwd); + net_async_status status = vio->write_packet_nonblocking( + vio, (uchar *)scrambled, SCRAMBLE_LENGTH, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } else { + DBUG_PRINT("info", ("no password")); + net_async_status status = vio->write_packet_nonblocking( + vio, 0, 0, &io_result); /* no password */ + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + break; + default: + assert(0); + } + + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* clang-format off */ /** @page page_protocol_connection_phase_authentication_methods_clear_text_password Clear text client plugin diff --git a/sql-common/client_async_authentication.h b/sql-common/client_async_authentication.h new file mode 100644 index 000000000000..e5aaa3867a4c --- /dev/null +++ b/sql-common/client_async_authentication.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + 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, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "mysql/plugin_auth_common.h" +#include "mysql_async.h" + +#ifdef HAVE_OPENSSL +#include +#endif + +/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ +struct MCPVIO_EXT { + int (*read_packet)(MYSQL_PLUGIN_VIO *vio, uchar **buf); + int (*write_packet)(MYSQL_PLUGIN_VIO *vio, const uchar *pkt, int pkt_len); + void (*info)(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info); + net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, int *result); + net_async_status (*write_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + const unsigned char *pkt, + int pkt_len, int *result); + + /* -= end of MYSQL_PLUGIN_VIO =- */ + MYSQL *mysql; + auth_plugin_t *plugin; /**< what plugin we're under */ + const char *db; + struct { + uchar *pkt; /**< pointer into NET::buff */ + uint pkt_len; + } cached_server_reply; + int packets_read, packets_written; /**< counters for send/received packets */ + int mysql_change_user; /**< if it's mysql_change_user() */ + int last_read_packet_len; /**< the length of the last *read* packet */ +}; + +/* Our state machines have four simple return codes: */ +enum mysql_state_machine_status { + STATE_MACHINE_FAILED, /* Completion with a failure. */ + STATE_MACHINE_CONTINUE, /* Keep calling the state machine. */ + STATE_MACHINE_WOULD_BLOCK, /* Needs to block to continue. */ + STATE_MACHINE_DONE /* Completion with a success. */ +}; + +/* state machine for native password autheintication API */ +enum client_auth_native_password_plugin_status { + NATIVE_READING_PASSWORD = 1, + NATIVE_WRITING_RESPONSE +}; + +enum client_auth_sha256_password_plugin_status { + SHA256_READING_PASSWORD = 1, + SHA256_REQUEST_PUBLIC_KEY, + SHA256_READ_PUBLIC_KEY, + SHA256_SEND_ENCRYPTED_PASSWORD, + SHA256_SEND_PLAIN_PASSWORD +}; + +enum client_auth_caching_sha2_password_plugin_status { + CACHING_SHA2_READING_PASSWORD = 1, + CACHING_SHA2_WRITING_RESPONSE, + CACHING_SHA2_CHALLENGE_RESPONSE, + CACHING_SHA2_REQUEST_PUBLIC_KEY, + CACHING_SHA2_READ_PUBLIC_KEY, + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD, + CACHING_SHA2_SEND_PLAIN_PASSWORD +}; + +/* A state machine for authentication itself. */ +struct mysql_async_auth; +typedef mysql_state_machine_status (*authsm_function)(mysql_async_auth *); + +struct mysql_async_auth { + MYSQL *mysql; + bool non_blocking; + + char *data; + uint data_len; + const char *data_plugin; + const char *db; + + const char *auth_plugin_name; + auth_plugin_t *auth_plugin; + MCPVIO_EXT mpvio; + ulong pkt_length; + int res; + + char *change_user_buff; + int change_user_buff_len; + + int client_auth_plugin_state; + authsm_function state_function; +}; + +/* + Connection is handled with a state machine. Each state is + represented by a function pointer (csm_function) which returns + a mysql_state_machine_status to indicate the state of the + connection. + This state machine has boundaries around network IO to allow + reuse between blocking and non-blocking clients. +*/ +struct mysql_async_connect; +typedef mysql_state_machine_status (*csm_function)(mysql_async_connect *); + +/* + define different states of an asynchronous SSL connection phase +*/ +enum ssl_exchange_state { + SSL_REQUEST = 8100, + SSL_CONNECT = 8101, + SSL_COMPLETE = 8102, + SSL_NONE = 8103 +}; + +/* + Struct to track the state of a connection being established. Once + the connection is established, the context should be discarded and + relevant values copied out of it. +*/ +struct mysql_async_connect { + /* state for the overall connection process */ + MYSQL *mysql; + const char *host; + const char *user; + const char *passwd; + const char *db; + uint port; + const char *unix_socket; + ulong client_flag; + bool non_blocking; + + ulong pkt_length; + char *host_info; + char buff[NAME_LEN + USERNAME_LENGTH + 100]; + int scramble_data_len; + char *scramble_data; + const char *scramble_plugin; + char *scramble_buffer; + bool scramble_buffer_allocated; + + /* context needed to establish asynchronous authentication */ + struct mysql_async_auth *auth_context; + /* state for running init_commands */ + bool saved_reconnect; + char **current_init_command; + + ssl_exchange_state ssl_state; +#if defined(HAVE_OPENSSL) + SSL *ssl; +#endif + /* state function that will be called next */ + csm_function state_function; +}; diff --git a/sql-common/client_authentication.cc b/sql-common/client_authentication.cc index b04c5db81e48..4ad2e9dd7da7 100644 --- a/sql-common/client_authentication.cc +++ b/sql-common/client_authentication.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -52,6 +52,7 @@ #include #endif #include +#include "client_async_authentication.h" #include "mysql/plugin.h" #include "sha2.h" #include "violite.h" @@ -269,6 +270,164 @@ int sha256_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + Non blocking version of sha256_password_auth_client +*/ +net_async_status sha256_password_auth_client_nonblocking(MYSQL_PLUGIN_VIO *vio, + MYSQL *mysql, + int *result) { + DBUG_ENTER(__func__); + net_async_status status = NET_ASYNC_NOT_READY; +#if !defined(HAVE_WOLFSSL) + unsigned char encrypted_password[MAX_CIPHER_LENGTH]; + static char request_public_key = '\1'; + static RSA *public_key = NULL; + bool got_public_key_from_server = false; +#endif + int io_result; + bool connection_is_secure = (mysql_get_ssl_cipher(mysql) != NULL); + unsigned char scramble_pkt[20]; + unsigned char *pkt; + unsigned int passwd_len = + static_cast(strlen(mysql->passwd) + 1); + + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_sha256_password_plugin_status::SHA256_READING_PASSWORD: + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result != SCRAMBLE_LENGTH + 1) { + DBUG_PRINT("info", ("Scramble is not of correct length.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (pkt[SCRAMBLE_LENGTH] != '\0') { + DBUG_PRINT("info", ("Missing protocol token in scramble data.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH); + if (connection_is_secure) + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_SEND_PLAIN_PASSWORD; + else + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_REQUEST_PUBLIC_KEY; + DBUG_RETURN(NET_ASYNC_NOT_READY); + case client_auth_sha256_password_plugin_status::SHA256_REQUEST_PUBLIC_KEY: { + public_key = rsa_init(mysql); +#if !defined(HAVE_WOLFSSL) + /* If no public key; request one from the server. */ + if (public_key == NULL) { + status = vio->write_packet_nonblocking( + vio, (const unsigned char *)&request_public_key, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } +#else + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), "sha256_password", + "Authentication requires SSL encryption"); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); +#endif + } + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY; + /* FALLTHROUGH */ + case client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY: + if (public_key == NULL) { + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result <= 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + BIO *bio = BIO_new_mem_buf(pkt, io_result); + public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + if (public_key == 0) { + ERR_clear_error(); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + got_public_key_from_server = true; + } + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_SEND_ENCRYPTED_PASSWORD; + /* FALLTHROUGH */ + case client_auth_sha256_password_plugin_status:: + SHA256_SEND_ENCRYPTED_PASSWORD: { + char passwd_scramble[512]; + + if (passwd_len > sizeof(passwd_scramble)) { + /* password too long for the buffer */ + if (got_public_key_from_server) RSA_free(public_key); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memmove(passwd_scramble, mysql->passwd, passwd_len); + + /* Obfuscate the plain text password with the session scramble */ + xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt, + SCRAMBLE_LENGTH); + /* Encrypt the password and send it to the server */ + int cipher_length = RSA_size(public_key); + /* + When using RSA_PKCS1_OAEP_PADDING the password length must be less + than RSA_size(rsa) - 41. + */ + if (passwd_len + 41 >= (unsigned)cipher_length) { + /* password message is to long */ + if (got_public_key_from_server) RSA_free(public_key); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble, + encrypted_password, public_key, + RSA_PKCS1_OAEP_PADDING); + if (got_public_key_from_server) RSA_free(public_key); + status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password, + cipher_length, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + case client_auth_sha256_password_plugin_status:: + SHA256_SEND_PLAIN_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd, + passwd_len, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + default: + assert(0); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* caching_sha2_password */ int caching_sha2_password_init(char *, size_t, int, va_list) { return 0; } @@ -462,6 +621,252 @@ int caching_sha2_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + non blocking version of caching_sha2_password_auth_client +*/ +net_async_status caching_sha2_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result) { + DBUG_ENTER(__func__); + int io_result; + net_async_status status = NET_ASYNC_NOT_READY; + static unsigned char encrypted_password[MAX_CIPHER_LENGTH]; + static RSA *public_key = NULL; + bool connection_is_secure = is_secure_transport(mysql); + bool got_public_key_from_server = false; + static unsigned char scramble_pkt[20]; + static int cipher_length = 0; + static unsigned int passwd_len = 0; + unsigned char *pkt; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READING_PASSWORD: + /* + Get the scramble from the server because we need it when sending + encrypted password. + */ + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result != SCRAMBLE_LENGTH + 1) { + DBUG_PRINT("info", ("Scramble is not of correct length.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (pkt[SCRAMBLE_LENGTH] != '\0') { + DBUG_PRINT("info", ("Missing protocol token in scramble data.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + /* + Copy the scramble to the stack or it will be lost on the next use + of the net buffer. + */ + memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH); + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_WRITING_RESPONSE; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_WRITING_RESPONSE: + if (mysql->passwd[0] == 0) { + /* We're not using a password */ + static const unsigned char zero_byte = '\0'; + status = vio->write_packet_nonblocking(vio, &zero_byte, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } else { + /* Password is a 0-terminated byte array ('\0' character included) */ + passwd_len = static_cast(strlen(mysql->passwd) + 1); + /* First try with SHA2 scramble */ + unsigned char sha2_scramble[SHA2_SCRAMBLE_LENGTH]; + if (generate_sha256_scramble(sha2_scramble, SHA2_SCRAMBLE_LENGTH, + mysql->passwd, passwd_len - 1, + (char *)scramble_pkt, SCRAMBLE_LENGTH)) { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), + "caching_sha2_password", + "Failed to generate scramble"); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + status = vio->write_packet_nonblocking( + vio, sha2_scramble, SHA2_SCRAMBLE_LENGTH, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_CHALLENGE_RESPONSE; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_CHALLENGE_RESPONSE: + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result == -1) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (io_result == 1 && *pkt == fast_auth_success) { + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (io_result != 1 || *pkt != perform_full_authentication) { + DBUG_PRINT("info", ("Unexpected reply from server.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (!connection_is_secure) + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_REQUEST_PUBLIC_KEY; + else + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_PLAIN_PASSWORD; + DBUG_RETURN(NET_ASYNC_NOT_READY); + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_REQUEST_PUBLIC_KEY: + /* If connection isn't secure attempt to get the RSA public key file */ + { + public_key = rsa_init(mysql); + + if (public_key == NULL && mysql->options.extension && + mysql->options.extension->get_server_public_key) { + status = vio->write_packet_nonblocking( + vio, (const unsigned char *)&request_public_key, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READ_PUBLIC_KEY; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READ_PUBLIC_KEY: { + if (public_key == NULL && mysql->options.extension && + mysql->options.extension->get_server_public_key) { + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result <= 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + int pkt_len = 0; + BIO *bio = BIO_new_mem_buf(pkt, pkt_len); + public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + if (public_key == 0) { +#ifndef HAVE_WOLFSSL + ERR_clear_error(); +#endif /* !HAVE_WOLFSSL */ + DBUG_PRINT("info", ("Failed to parse public key")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + got_public_key_from_server = true; + } + if (public_key) { + char passwd_scramble[512]; + if (passwd_len > sizeof(passwd_scramble)) { + /* password too long for the buffer */ + if (got_public_key_from_server) RSA_free(public_key); + DBUG_PRINT("info", ("Password is too long.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memmove(passwd_scramble, mysql->passwd, passwd_len); + /* Obfuscate the plain text password with the session scramble */ + xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt, + SCRAMBLE_LENGTH); + /* Encrypt the password and send it to the server */ + cipher_length = RSA_size(public_key); + /* + When using RSA_PKCS1_OAEP_PADDING the password length + must be less than RSA_size(rsa) - 41. + */ + if (passwd_len + 41 >= (unsigned)cipher_length) { + /* password message is to long */ + if (got_public_key_from_server) RSA_free(public_key); + DBUG_PRINT("info", ("Password is too long to be encrypted using " + "given public key.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble, + encrypted_password, public_key, + RSA_PKCS1_OAEP_PADDING); + if (got_public_key_from_server) RSA_free(public_key); + } else { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), + "caching_sha2_password", + "Authentication requires secure connection."); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password, + cipher_length, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_PLAIN_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd, + passwd_len, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + default: + assert(0); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void STDCALL mysql_reset_server_public_key(void) { DBUG_ENTER("mysql_reset_server_public_key"); mysql_mutex_lock(&g_public_key_mutex); diff --git a/sql-common/net_serv.cc b/sql-common/net_serv.cc index 2ae45667aa51..547faa25a949 100644 --- a/sql-common/net_serv.cc +++ b/sql-common/net_serv.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -50,6 +50,7 @@ #include "my_sys.h" #include "mysql.h" #include "mysql/service_mysql_alloc.h" +#include "mysql_async.h" #include "mysql_com.h" #include "mysqld_error.h" #include "violite.h" @@ -87,10 +88,31 @@ extern void thd_increment_bytes_received(size_t length); #include "mysql_com_server.h" #endif -#define VIO_SOCKET_ERROR ((size_t)-1) - static bool net_write_buff(NET *, const uchar *, size_t); +NET_EXTENSION *net_extension_init() { + NET_EXTENSION *ext = static_cast(my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(NET_EXTENSION), MYF(MY_WME | MY_ZEROFILL))); + ext->net_async_context = static_cast(my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(NET_ASYNC), MYF(MY_WME | MY_ZEROFILL))); + + return ext; +} + +void net_extension_free(NET *net) { + NET_EXTENSION *ext = NET_EXTENSION_PTR(net); + if (ext) { +#ifndef MYSQL_SERVER + if (ext->net_async_context) { + my_free(ext->net_async_context); + ext->net_async_context = nullptr; + } + my_free(ext); + net->extension = 0; +#endif + } +} + /** Init with packet info. */ bool my_net_init(NET *net, Vio *vio) { @@ -114,8 +136,18 @@ bool my_net_init(NET *net, Vio *vio) { net->last_errno = 0; #ifdef MYSQL_SERVER net->extension = NULL; +#else + NET_EXTENSION *ext = net_extension_init(); + ext->net_async_context->cur_pos = net->buff + net->where_b; + ext->net_async_context->read_rows_is_first_read = true; + ext->net_async_context->async_operation = NET_ASYNC_OP_IDLE; + ext->net_async_context->async_send_command_status = + NET_ASYNC_SEND_COMMAND_IDLE; + ext->net_async_context->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + ext->net_async_context->async_packet_read_state = NET_ASYNC_PACKET_READ_IDLE; + net->extension = ext; #endif - if (vio) { /* For perl DBI/DBD. */ net->fd = vio_fd(vio); @@ -168,7 +200,13 @@ bool net_realloc(NET *net, size_t length) { /* In the server the error is reported by MY_WME flag. */ DBUG_RETURN(1); } +#ifdef MYSQL_SERVER + net->buff = net->write_pos = buff; +#else + size_t cur_pos_offset = NET_ASYNC_DATA(net)->cur_pos - net->buff; net->buff = net->write_pos = buff; + NET_ASYNC_DATA(net)->cur_pos = net->buff + cur_pos_offset; +#endif net->buff_end = buff + (net->max_packet = (ulong)pkt_length); DBUG_RETURN(0); } @@ -353,6 +391,8 @@ static bool net_should_retry(NET *net, bool my_net_write(NET *net, const uchar *packet, size_t len) { uchar buff[NET_HEADER_SIZE]; + DBUG_DUMP("net write", packet, len); + if (unlikely(!net->vio)) /* nowhere to write */ return false; @@ -361,6 +401,8 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { return 1; };); + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); /* Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH length. The last packet is always a packet that is < MAX_PACKET_LENGTH. @@ -389,6 +431,287 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { return net_write_buff(net, packet, len); } +static void reset_packet_write_state(NET *net) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + if (net_async->async_write_vector) { + if (net_async->async_write_vector != net_async->inline_async_write_vector) { + my_free(net_async->async_write_vector); + } + net_async->async_write_vector = nullptr; + } + + if (net_async->async_write_headers) { + if (net_async->async_write_headers != + net_async->inline_async_write_header) { + my_free(net_async->async_write_headers); + } + net_async->async_write_headers = nullptr; + } + + net_async->async_write_vector_size = 0; + net_async->async_write_vector_current = 0; + DBUG_VOID_RETURN; +} + +/* + Construct the proper buffers for our nonblocking write. What we do + here is we make an iovector for the entire write (header, command, + and payload). We then continually call writev on this vector, + consuming parts from it as bytes are successfully written. Headers + for the message are all stored inside one buffer, separate from the + payload; this lets us avoid copying the entire query just to insert + the headers every 2**24 bytes. + + The most common case is the query fits in a packet. In that case, + we don't construct the iovector dynamically, instead using one we + pre-allocated inside the net structure. This avoids allocations in + the common path, but requires special casing with our iovec and + header buffer. +*/ +static int begin_packet_write_state(NET *net, uchar command, + const uchar *packet, size_t packet_len, + const uchar *optional_prefix, + size_t prefix_len) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t total_len = packet_len + prefix_len; + bool include_command = (command < COM_END); + if (include_command) { + ++total_len; + } + size_t packet_count = 1 + total_len / MAX_PACKET_LENGTH; + reset_packet_write_state(net); + + struct io_vec *vec; + uchar *headers; + if (total_len < MAX_PACKET_LENGTH) { + /* + Most writes hit this case, ie, less than MAX_PACKET_LENGTH of + query text. + */ + vec = net_async->inline_async_write_vector; + headers = net_async->inline_async_write_header; + } else { + /* Large query, create the vector and header buffer dynamically. */ + vec = (struct io_vec *)my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(struct io_vec) * packet_count * 2 + 1, + MYF(MY_ZEROFILL)); + if (!vec) { + DBUG_RETURN(0); + } + + headers = (uchar *)my_malloc(PSI_NOT_INSTRUMENTED, + packet_count * (NET_HEADER_SIZE + 1), + MYF(MY_ZEROFILL)); + if (!headers) { + my_free(vec); + DBUG_RETURN(0); + } + } + /* + Regardless of where vec and headers come from, these are what we + feed to writev and populate below. + */ + net_async->async_write_vector = vec; + net_async->async_write_headers = headers; + + /* + We sneak the command into the first header, so the special casing + below about packet_num == 0 relates to that. This lets us avoid + an extra allocation and copying the input buffers again. + + Every chunk of MAX_PACKET_LENGTH results in a header and a + payload, so we have twice as many entries in the IO + vector as we have packet_count. The first packet may be prefixed with a + small amount of data, so that one actually might + consume *three* iovec entries. + */ + for (size_t packet_num = 0; packet_num < packet_count; ++packet_num) { + /* First packet, our header. */ + uchar *buf = headers + packet_num * NET_HEADER_SIZE; + if (packet_num > 0) { + /* + First packet stole one extra byte from the header buffer for + the command number, so account for it here. + */ + ++buf; + } + size_t header_len = NET_HEADER_SIZE; + size_t bytes_queued = 0; + + size_t packet_size = min(MAX_PACKET_LENGTH, total_len); + int3store(buf, packet_size); + buf[3] = (uchar)net->pkt_nr++; + /* + We sneak the command byte into the header, even though + technically it is payload. This lets us avoid an allocation + or separate one-byte entry in our iovec. + */ + if (packet_num == 0 && include_command) { + buf[4] = command; + ++header_len; + /* Our command byte counts against the packet size. */ + ++bytes_queued; + } + + (*vec).iov_base = buf; + (*vec).iov_len = header_len; + ++vec; + + /* Second packet, our optional prefix (if any). */ + if (packet_num == 0 && optional_prefix != NULL) { + (*vec).iov_base = (void *)optional_prefix; + (*vec).iov_len = prefix_len; + ++vec; + bytes_queued += prefix_len; + } + /* + Final packet, the payload itself. Send however many bytes from + packet we have left, and advance our packet pointer. + */ + size_t remaining_bytes = packet_size - bytes_queued; + (*vec).iov_base = (void *)packet; + (*vec).iov_len = remaining_bytes; + + bytes_queued += remaining_bytes; + + packet += remaining_bytes; + total_len -= bytes_queued; + + ++vec; + + /* Make sure we sent entire packets. */ + if (total_len > 0) { + DBUG_ASSERT(packet_size == MAX_PACKET_LENGTH); + } + } + + /* Make sure we don't have anything left to send. */ + DBUG_ASSERT(total_len == 0); + + net_async->async_write_vector_size = (vec - net_async->async_write_vector); + net_async->async_write_vector_current = 0; + + DBUG_RETURN(1); +} + +static net_async_status net_write_vector_nonblocking(NET *net, ssize_t *res) { + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + struct io_vec *vec = + net_async->async_write_vector + net_async->async_write_vector_current; + DBUG_ENTER(__func__); + + while (net_async->async_write_vector_current != + net_async->async_write_vector_size) { + if (vio_is_blocking(net->vio)) { + vio_set_blocking_flag(net->vio, false); + } + *res = vio_write(net->vio, (uchar *)vec->iov_base, vec->iov_len); + + if (*res < 0) { + if (errno == SOCKET_EAGAIN || (SOCKET_EAGAIN != SOCKET_EWOULDBLOCK && + errno == SOCKET_EWOULDBLOCK)) { + /* + In the unlikely event that there is a renegotiation and + SSL_ERROR_WANT_READ is returned, set blocking state to read. + */ + if (static_cast(*res) == VIO_SOCKET_WANT_READ) { + net_async->async_blocking_state = NET_NONBLOCKING_READ; + } else { + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + } + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + size_t bytes_written = static_cast(*res); + vec->iov_len -= bytes_written; + vec->iov_base = (char *)vec->iov_base + bytes_written; + + if (vec->iov_len != 0) break; + + ++net_async->async_write_vector_current; + vec++; + } + if (net_async->async_write_vector_current == + net_async->async_write_vector_size) { + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(NET_ASYNC_NOT_READY); +} + +/** + Send a command to the server in asynchronous way. This function will first + populate all headers in NET::async_write_headers, followed by payload in + NET::async_write_vector. Once header and payload is populated in NET, were + call net_write_vector_nonblocking to send the packets to server in an + asynchronous way. +*/ +net_async_status net_write_command_nonblocking(NET *net, uchar command, + const uchar *prefix, + size_t prefix_len, + const uchar *packet, + size_t packet_len, bool *res) { + net_async_status status; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + ssize_t rc; + DBUG_ENTER(__func__); + DBUG_DUMP("net write prefix", prefix, prefix_len); + DBUG_DUMP("net write pkt", packet, packet_len); + if (unlikely(!net->vio)) { + /* nowhere to write */ + *res = false; + goto done; + } + + switch (net_async->async_operation) { + case NET_ASYNC_OP_IDLE: + if (!begin_packet_write_state(net, command, packet, packet_len, prefix, + prefix_len)) { + *res = false; + goto done; + } + net_async->async_operation = NET_ASYNC_OP_WRITING; + /* fallthrough */ + case NET_ASYNC_OP_WRITING: + status = net_write_vector_nonblocking(net, &rc); + if (status == NET_ASYNC_COMPLETE) { + if (rc < 0) { + *res = true; + } else { + *res = false; + } + goto done; + } + DBUG_RETURN(NET_ASYNC_NOT_READY); + net_async->async_operation = NET_ASYNC_OP_COMPLETE; + /* fallthrough */ + case NET_ASYNC_OP_COMPLETE: + *res = false; + goto done; + default: + DBUG_ASSERT(false); + *res = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + +done: + reset_packet_write_state(net); + net_async->async_operation = NET_ASYNC_OP_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/* + Non blocking version of my_net_write(). +*/ +net_async_status my_net_write_nonblocking(NET *net, const uchar *packet, + size_t len, bool *res) { + return net_write_command_nonblocking(net, COM_END, packet, len, NULL, 0, res); +} + /** Send a command to the server. @@ -418,6 +741,9 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { bool net_write_command(NET *net, uchar command, const uchar *header, size_t head_len, const uchar *packet, size_t len) { + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); + size_t length = len + 1 + head_len; /* 1 extra byte for command */ uchar buff[NET_HEADER_SIZE + 1]; uint header_size = NET_HEADER_SIZE + 1; @@ -987,6 +1313,236 @@ static bool net_read_packet_header(NET *net) { return false; } +/* + Helper function to read up to count bytes from the network connection/ + + Returns packet_error (-1) on EOF or other errors, 0 if the read + would block, and otherwise the number of bytes read (which may be + less than the requested amount). + + When 0 is returned the async_blocking_state is set inside this function. + With SSL, the async blocking state can also become NET_NONBLOCKING_WRITE + (when renegotiation occurs). +*/ +static ulong net_read_available(NET *net, size_t count) { + size_t recvcnt; + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + if (net_async->cur_pos + count > net->buff + net->max_packet) { + if (net_realloc(net, net->max_packet + count)) { + DBUG_RETURN(packet_error); + } + } + if (vio_is_blocking(net->vio)) { + vio_set_blocking_flag(net->vio, false); + } + recvcnt = vio_read(net->vio, net_async->cur_pos, count); + /* + When OpenSSL is used in non-blocking mode, it is possible that an + SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE error is returned after a + SSL_read() operation (if a renegotiation takes place). + We are treating this case here and signaling correctly the next expected + operation in the async_blocking_state. + */ + if (recvcnt == VIO_SOCKET_WANT_READ) { + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(0); + } else if (recvcnt == VIO_SOCKET_WANT_WRITE) { + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(0); + } + + /* Call would block, just return with socket_errno set */ + if ((recvcnt == VIO_SOCKET_ERROR) && + (socket_errno == SOCKET_EAGAIN || (SOCKET_EAGAIN != SOCKET_EWOULDBLOCK && + socket_errno == SOCKET_EWOULDBLOCK))) { + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(0); + } + + /* Not EOF and not an error? Return the bytes read.*/ + if (recvcnt != 0 && recvcnt != VIO_SOCKET_ERROR) { + net_async->cur_pos += recvcnt; +#ifdef MYSQL_SERVER + thd_increment_bytes_received(recvcnt); +#endif + DBUG_RETURN(recvcnt); + } + + /* EOF or hard failure; socket should be closed. */ + net->error = 2; + net->last_errno = ER_NET_READ_ERROR; + DBUG_RETURN(packet_error); +} + +/* Read actual data from the packet */ +static net_async_status net_read_data_nonblocking(NET *net, size_t count, + bool *err_ptr) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t bytes_read = 0; + ulong rc; + switch (net_async->async_operation) { + case NET_ASYNC_OP_IDLE: + net_async->async_bytes_wanted = count; + net_async->async_operation = NET_ASYNC_OP_READING; + net_async->cur_pos = net->buff + net->where_b; + /* fallthrough */ + case NET_ASYNC_OP_READING: + rc = net_read_available(net, net_async->async_bytes_wanted); + if (rc == packet_error) { + *err_ptr = rc; + net_async->async_operation = NET_ASYNC_OP_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + bytes_read = (size_t)rc; + net_async->async_bytes_wanted -= bytes_read; + if (net_async->async_bytes_wanted != 0) { + DBUG_PRINT("partial read", ("wanted/remaining: %zu, %zu", count, + net_async->async_bytes_wanted)); + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + net_async->async_operation = NET_ASYNC_OP_COMPLETE; + /* fallthrough */ + case NET_ASYNC_OP_COMPLETE: + net_async->async_bytes_wanted = 0; + net_async->async_operation = NET_ASYNC_OP_IDLE; + *err_ptr = false; + DBUG_PRINT("read complete", ("read: %zu", count)); + DBUG_RETURN(NET_ASYNC_COMPLETE); + default: + /* error, sure wish we could log something here */ + DBUG_ASSERT(false); + net_async->async_bytes_wanted = 0; + net_async->async_operation = NET_ASYNC_OP_IDLE; + *err_ptr = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } +} + +static net_async_status net_read_packet_header_nonblocking(NET *net, + bool *err_ptr) { + DBUG_ENTER(__func__); + uchar pkt_nr; + if (net_read_data_nonblocking(net, NET_HEADER_SIZE, err_ptr) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (*err_ptr) { + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + DBUG_DUMP("packet_header", net->buff + net->where_b, NET_HEADER_SIZE); + + pkt_nr = net->buff[net->where_b + 3]; + + /* + Verify packet serial number against the truncated packet counter. + The local packet counter must be truncated since its not reset. + */ + if (pkt_nr != (uchar)net->pkt_nr) { + /* Not a NET error on the client. XXX: why? */ +#if defined(MYSQL_SERVER) + my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); +#elif defined(EXTRA_DEBUG) + /* + We don't make noise server side, since the client is expected + to break the protocol for e.g. --send LOAD DATA .. LOCAL where + the server expects the client to send a file, but the client + may reply with a new command instead. + */ + fprintf(stderr, "Error: packets out of order (found %u, expected %u)\n", + (uint)pkt_nr, net->pkt_nr); + DBUG_ASSERT(pkt_nr == net->pkt_nr); +#endif + *err_ptr = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net->pkt_nr++; + + *err_ptr = false; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/* + Read packet header followed by packet data in an asynchronous way. +*/ +static net_async_status net_read_packet_nonblocking(NET *net, ulong *ret, + ulong *complen) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t pkt_data_len; + bool err; + + *complen = 0; + + switch (net_async->async_packet_read_state) { + case NET_ASYNC_PACKET_READ_IDLE: + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_HEADER; + net->reading_or_writing = 0; + /* fallthrough */ + case NET_ASYNC_PACKET_READ_HEADER: + if (net_read_packet_header_nonblocking(net, &err) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + /* Retrieve packet length and number. */ + if (err) goto error; + + net->compress_pkt_nr = net->pkt_nr; + + /* The length of the packet that follows. */ + net_async->async_packet_length = uint3korr(net->buff + net->where_b); + DBUG_PRINT("info", + ("async packet len: %zu", net_async->async_packet_length)); + + /* End of big multi-packet. */ + if (!net_async->async_packet_length) goto end; + + pkt_data_len = + max(static_cast(net_async->async_packet_length), *complen) + + net->where_b; + + /* Expand packet buffer if necessary. */ + if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) + goto error; + + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_BODY; + /* fallthrough */ + case NET_ASYNC_PACKET_READ_BODY: + if (net_read_data_nonblocking(net, net_async->async_packet_length, + &err) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (err) goto error; + + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_COMPLETE; + /* fallthrough */ + + case NET_ASYNC_PACKET_READ_COMPLETE: + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_IDLE; + break; + } + +end: + *ret = net_async->async_packet_length; + net->read_pos = net->buff + net->where_b; +#ifdef DEBUG_DATA_PACKETS + DBUG_DUMP("async read output", net->read_pos, *ret); +#endif + + net->read_pos[*ret] = 0; + net->reading_or_writing = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +error: + *ret = packet_error; + net->reading_or_writing = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Read one (variable-length) MySQL protocol packet. A MySQL packet consists of a header and a payload. @@ -1041,6 +1597,7 @@ static size_t net_read_packet(NET *net, size_t *complen) { if (net_read_raw_loop(net, pkt_len)) goto error; end: + DBUG_DUMP("net read", net->buff + net->where_b, pkt_len); net->reading_or_writing = 0; return pkt_len; @@ -1049,6 +1606,29 @@ static size_t net_read_packet(NET *net, size_t *complen) { return packet_error; } +/* + Non blocking version of my_net_read(). +*/ +net_async_status my_net_read_nonblocking(NET *net, ulong *len_ptr, + ulong *complen_ptr) { + if (net_read_packet_nonblocking(net, len_ptr, complen_ptr) == + NET_ASYNC_NOT_READY) { + return NET_ASYNC_NOT_READY; + } + + if (*len_ptr == packet_error) { + return NET_ASYNC_COMPLETE; + } + + DBUG_PRINT("info", ("chunk nb read: %lu", *len_ptr)); + + if (*len_ptr == MAX_PACKET_LENGTH) { + return NET_ASYNC_NOT_READY; + } else { + return NET_ASYNC_COMPLETE; + } +} + /** Read a packet from the client/server and return it without the internal package header. @@ -1068,6 +1648,9 @@ static size_t net_read_packet(NET *net, size_t *complen) { ulong my_net_read(NET *net) { size_t len, complen; + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); + if (!net->compress) { len = net_read_packet(net, &complen); if (len == MAX_PACKET_LENGTH) { diff --git a/testclients/mysql_client_test.cc b/testclients/mysql_client_test.cc index 46b272c8ffaa..3a04b2146e2d 100644 --- a/testclients/mysql_client_test.cc +++ b/testclients/mysql_client_test.cc @@ -20144,6 +20144,290 @@ static void test_bug27443252() { myquery(rc); } +void perform_arithmatic() { fprintf(stdout, "\n Do some other stuff."); } + +static void test_wl11381() { + MYSQL *mysql_local; + MYSQL_RES *result; + MYSQL_ROW row; + net_async_status status; + const char *stmt_text, *stmt_text1, *insert_stmt, *insert_stmt1; + + myheader("test_wl11381"); + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_local = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + status = mysql_real_connect_nonblocking( + mysql_local, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n asynchronous connection estalished"); + } + + mysql_autocommit(mysql_local, 1); + + if (mysql_query(mysql_local, "DROP TABLE IF EXISTS test_table")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, "CREATE TABLE test_table(col1 int)")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, + "INSERT INTO test_table values(10), (20), (30)")) { + fprintf(stderr, "\n insert into table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + stmt_text = "SELECT * FROM test_table"; + /* run query in asynchronous way */ + status = mysql_real_query_nonblocking(mysql_local, stmt_text, + (ulong)strlen(stmt_text)); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, stmt_text, + (ulong)strlen(stmt_text)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n mysql_real_query_nonblocking() passed"); + } + status = mysql_store_result_nonblocking(mysql_local, &result); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_store_result_nonblocking(mysql_local, &result); + } + if (!result) { + fprintf(stdout, "\n mysql_store_result_nonblocking() fetched 0 records"); + exit(1); + } else { + fprintf(stdout, "\n mysql_store_result_nonblocking() passed"); + } + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "10") == 0); + fprintf(stdout, "\n mysql_fetch_row() passed"); + + while ((status = mysql_fetch_row_nonblocking(result, &row)) != + NET_ASYNC_COMPLETE) + ; + /* 2nd row fetched */ + DIE_UNLESS(strcmp(row[0], "20") == 0); + fprintf(stdout, "\n mysql_fetch_row_nonblocking() passed"); + + status = mysql_fetch_row_nonblocking(result, &row); + /* do some other task */ + perform_arithmatic(); + if (status == NET_ASYNC_COMPLETE) { + DIE_UNLESS(strcmp(row[0], "30") == 0); + } else { + while ((status = mysql_fetch_row_nonblocking(result, &row)) != + NET_ASYNC_COMPLETE) + ; + /* 3rd row fetched */ + DIE_UNLESS(strcmp(row[0], "30") == 0); + fprintf(stdout, "\n mysql_fetch_row_nonblocking() passed"); + } + + while ((status = mysql_free_result_nonblocking(result)) != NET_ASYNC_COMPLETE) + ; + fprintf(stdout, "\n mysql_free_result_nonblocking() passed"); + + if (mysql_query(mysql_local, "DROP TABLE IF EXISTS test_table1")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, + "CREATE TABLE test_table1(col1 int, col2 varchar(2048))")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + insert_stmt = + "INSERT INTO test_table1 values(10, repeat('a', 2000)), (2, repeat('bc', " + "1000)), (3, repeat('def', 600))"; + status = NET_ASYNC_NOT_READY; + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, insert_stmt, + (ulong)strlen(insert_stmt)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n INSERT INTO test_table1... failed"); + exit(1); + } + + insert_stmt1 = + "INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;"; + status = mysql_real_query_nonblocking(mysql_local, insert_stmt1, + (ulong)strlen(insert_stmt1)); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, insert_stmt1, + (ulong)strlen(insert_stmt1)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stderr, + "\n INSERT INTO test_table1 " + "SELECT * FROM test_table1 with error; %s ", + mysql_error(mysql_local)); + exit(1); + } + + while (mysql_more_results(mysql_local)) { + status = mysql_next_result_nonblocking(mysql_local); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_next_result_nonblocking() failed"); + exit(1); + } else { + while (status == NET_ASYNC_NOT_READY) { + status = mysql_store_result_nonblocking(mysql_local, &result); + } + } + } + stmt_text1 = "SELECT * FROM test_table1"; + /* run query in asynchronous way */ + status = mysql_real_query_nonblocking(mysql_local, stmt_text1, + (ulong)strlen(stmt_text1)); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, stmt_text1, + (ulong)strlen(stmt_text1)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n SELECT * FROM test_table1 failed"); + exit(1); + } + result = mysql_use_result(mysql_local); + while ((status = mysql_free_result_nonblocking(result)) != NET_ASYNC_COMPLETE) + ; + mysql_close(mysql_local); +} + +static void test_wl11381_qa() { + MYSQL *mysql_con1; + MYSQL *mysql_con2; + net_async_status mysql_con1_status; + net_async_status mysql_con2_status; + const char *stmt_text; + int counter = 0; + + myheader("test_wl11381_qa"); + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_con1 = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_con2 = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + + mysql_con1_status = (mysql_real_connect_nonblocking( + mysql_con1, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS)); + + mysql_con2_status = (mysql_real_connect_nonblocking( + mysql_con2, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS)); + + if (mysql_con1_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else if (mysql_con1_status == NET_ASYNC_COMPLETE) { + fprintf(stdout, "\n asynchronous connection 1 estalished"); + } + + if (mysql_con2_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else if (mysql_con2_status == NET_ASYNC_COMPLETE) { + fprintf(stdout, "\n asynchronous connection 2 estalished"); + } + + mysql_autocommit(mysql_con1, 1); + mysql_autocommit(mysql_con2, 1); + + if (mysql_query(mysql_con1, "DROP TABLE IF EXISTS test_table")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + + if (mysql_query(mysql_con1, "CREATE TABLE test_table(col1 int)")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + + if (mysql_query(mysql_con1, + "INSERT INTO test_table values(10), (20), (30)")) { + fprintf(stderr, "\n insert into table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + if (mysql_query(mysql_con1, "LOCK TABLE test_table WRITE")) { + fprintf(stderr, "\n lock table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + stmt_text = "SELECT * FROM test_table"; + /* run query in asynchronous way */ + mysql_con2_status = mysql_real_query_nonblocking(mysql_con2, stmt_text, + (ulong)strlen(stmt_text)); + if (mysql_con2_status == NET_ASYNC_NOT_READY) + printf("\n Query not finished yet."); + while (mysql_con2_status == NET_ASYNC_NOT_READY) { + /* do some other task */ + perform_arithmatic(); + fprintf(stdout, "\n Operation pending."); + counter++; + if (counter == 4) mysql_query(mysql_con1, "UNLOCK TABLES"); + mysql_con2_status = mysql_real_query_nonblocking(mysql_con2, stmt_text, + (ulong)strlen(stmt_text)); + } + if (mysql_con2_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n mysql_real_query_nonblocking() passed"); + } + mysql_close(mysql_con1); + mysql_close(mysql_con2); +} + static struct my_tests_st my_tests[] = { {"disable_query_logs", disable_query_logs}, {"test_view_sp_list_fields", test_view_sp_list_fields}, @@ -20424,6 +20708,8 @@ static struct my_tests_st my_tests[] = { {"test_skip_metadata", test_skip_metadata}, {"test_bug25701141", test_bug25701141}, {"test_bug27443252", test_bug27443252}, + {"test_wl11381", test_wl11381}, + {"test_wl11381_qa", test_wl11381_qa}, {0, 0}}; static struct my_tests_st *get_my_tests() { return my_tests; } diff --git a/vio/vio.cc b/vio/vio.cc index ef36d57a1957..5730d667c252 100644 --- a/vio/vio.cc +++ b/vio/vio.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -142,6 +142,8 @@ Vio &Vio::operator=(Vio &&vio) { read_pos = vio.read_pos; read_end = vio.read_end; + is_blocking_flag = vio.is_blocking_flag; + #ifdef USE_PPOLL_IN_VIO thread_id = vio.thread_id; signal_mask = vio.signal_mask; @@ -175,6 +177,9 @@ Vio &Vio::operator=(Vio &&vio) { io_wait = vio.io_wait; connect = vio.connect; + is_blocking = vio.is_blocking; + set_blocking = vio.set_blocking; + #ifdef _WIN32 overlapped = vio.overlapped; hPipe = vio.hPipe; @@ -243,6 +248,9 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->io_wait = no_io_wait; vio->is_connected = vio_is_connected_pipe; vio->has_data = has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->is_blocking_flag = true; return false; } if (type == VIO_TYPE_SHARED_MEMORY) { @@ -259,6 +267,9 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->io_wait = no_io_wait; vio->is_connected = vio_is_connected_shared_memory; vio->has_data = has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->is_blocking_flag = true; return false; } #endif /* _WIN32 */ @@ -278,6 +289,10 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->is_connected = vio_is_connected; vio->has_data = vio_ssl_has_data; vio->timeout = vio_socket_timeout; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->set_blocking_flag = vio_set_blocking_flag; + vio->is_blocking_flag = true; return false; } #endif /* HAVE_OPENSSL */ @@ -295,6 +310,10 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->is_connected = vio_is_connected; vio->timeout = vio_socket_timeout; vio->has_data = vio->read_buffer ? vio_buff_has_data : has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->set_blocking_flag = vio_set_blocking_flag; + vio->is_blocking_flag = true; return false; } diff --git a/vio/viosocket.cc b/vio/viosocket.cc index 48da4b9cc0a5..9ed2ddf2fcb1 100644 --- a/vio/viosocket.cc +++ b/vio/viosocket.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -145,18 +145,26 @@ size_t vio_read(Vio *vio, uchar *buf, size_t size) { flags)) == -1) { int error = socket_errno; - /* The operation would block? */ + /* Error encountered that is unrelated to blocking; percolate it up. */ #if SOCKET_EAGAIN == SOCKET_EWOULDBLOCK if (error != SOCKET_EAGAIN) #else if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) #endif break; + /* + Nonblocking with either EAGAIN or EWOULDBLOCK. Don't call + io_wait. 0 bytes are available. + */ + DBUG_ASSERT(error == SOCKET_EAGAIN || error == SOCKET_EWOULDBLOCK); + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_read on nonblocking socket read no bytes")); + DBUG_RETURN(-1); + } /* Wait for input data to become available. */ if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) break; } - DBUG_RETURN(ret); } @@ -219,6 +227,11 @@ size_t vio_write(Vio *vio, const uchar *buf, size_t size) { #endif break; + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_write on nonblocking socket written no bytes")); + DBUG_RETURN(-1); + } + /* Wait for the output buffer to become writable.*/ if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE))) break; } @@ -227,12 +240,11 @@ size_t vio_write(Vio *vio, const uchar *buf, size_t size) { } // WL#4896: Not covered -static int vio_set_blocking(Vio *vio, bool status) { +int vio_set_blocking(Vio *vio, bool status) { DBUG_ENTER("vio_set_blocking"); #ifdef _WIN32 - DBUG_ASSERT(vio->type != VIO_TYPE_NAMEDPIPE); - DBUG_ASSERT(vio->type != VIO_TYPE_SHARED_MEMORY); + { int ret; u_long arg = status ? 0 : 1; @@ -266,6 +278,26 @@ static int vio_set_blocking(Vio *vio, bool status) { DBUG_RETURN(0); } +int vio_set_blocking_flag(Vio *vio, bool status) { + DBUG_ENTER(__func__); + int ret = 0; + /* + Asynchronous communication in client is allowed only for below + types of connections. + */ + if (VIO_TYPE_TCPIP == vio->type || VIO_TYPE_SOCKET == vio->type || + VIO_TYPE_SSL == vio->type) { + vio->is_blocking_flag = status; + ret = vio_set_blocking(vio, status); + } + DBUG_RETURN(ret); +} + +bool vio_is_blocking(Vio *vio) { + DBUG_ENTER(__func__); + DBUG_RETURN(vio->is_blocking_flag); +} + int vio_socket_timeout(Vio *vio, uint which MY_ATTRIBUTE((unused)), bool old_mode) { int ret = 0; @@ -988,6 +1020,7 @@ int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { @param vio A VIO object. @param addr Socket address containing the peer address. @param len Length of socket address. + @param nonblocking flag to represent if socket is blocking or nonblocking @param timeout Interval (in milliseconds) to wait until a connection is established. @@ -996,7 +1029,7 @@ int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { */ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, - int timeout) { + bool nonblocking, int timeout) { int ret, wait; int retry_count = 0; DBUG_ENTER("vio_socket_connect"); @@ -1005,7 +1038,8 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); /* If timeout is not infinite, set socket to non-blocking mode. */ - if ((timeout > -1) && vio_set_blocking(vio, false)) DBUG_RETURN(true); + if (((timeout > -1) || nonblocking) && vio_set_blocking(vio, false)) + DBUG_RETURN(true); /* Initiate the connection. */ do { @@ -1034,7 +1068,8 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, 2. The connection was set up successfully: getsockopt() will return 0 as an error. */ - if (wait && (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) { + if (!nonblocking && wait && + (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) { int error; IF_WIN(int, socklen_t) optlen = sizeof(error); IF_WIN(char, void) *optval = (IF_WIN(char, void) *)&error; @@ -1059,11 +1094,15 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, } /* If necessary, restore the blocking mode, but only if connect succeeded. */ - if ((timeout > -1) && (ret == 0)) { + if (!nonblocking && (timeout > -1) && (ret == 0)) { if (vio_set_blocking(vio, true)) DBUG_RETURN(true); } - DBUG_RETURN(MY_TEST(ret)); + if (nonblocking && wait) { + DBUG_RETURN(false); + } else { + DBUG_RETURN(MY_TEST(ret)); + } } /** diff --git a/vio/viossl.cc b/vio/viossl.cc index 9fa34095082e..6ebbd1086d37 100644 --- a/vio/viossl.cc +++ b/vio/viossl.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -286,6 +286,17 @@ size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size) { /* Process the SSL I/O error. */ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_flag) { + switch (event) { + case VIO_IO_EVENT_READ: + DBUG_RETURN(VIO_SOCKET_WANT_READ); + case VIO_IO_EVENT_WRITE: + DBUG_RETURN(VIO_SOCKET_WANT_WRITE); + default: + DBUG_RETURN(VIO_SOCKET_ERROR); + } + } + /* Attempt to wait for an I/O event. */ if (vio_socket_io_wait(vio, event)) break; } @@ -318,6 +329,17 @@ size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size) { /* Process the SSL I/O error. */ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_flag) { + switch (event) { + case VIO_IO_EVENT_READ: + DBUG_RETURN(VIO_SOCKET_WANT_READ); + case VIO_IO_EVENT_WRITE: + DBUG_RETURN(VIO_SOCKET_WANT_WRITE); + default: + DBUG_RETURN(VIO_SOCKET_ERROR); + } + } + /* Attempt to wait for an I/O event. */ if (vio_socket_io_wait(vio, event)) break; } @@ -397,9 +419,10 @@ typedef int (*ssl_handshake_func_t)(SSL *); @return Return value is 1 on success. */ -static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, - unsigned long *ssl_errno_holder) { - int ret; +static size_t ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, + unsigned long *ssl_errno_holder) { + DBUG_ENTER(__func__); + size_t ret = -1; vio->ssl_arg = ssl; @@ -414,12 +437,27 @@ static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, */ DBUG_ASSERT(ERR_peek_error() == 0); - ret = func(ssl); + int handshake_ret; + handshake_ret = func(ssl); - if (ret >= 1) break; + if (handshake_ret >= 1) { + ret = 0; + break; + } /* Process the SSL I/O error. */ - if (!ssl_should_retry(vio, ret, &event, ssl_errno_holder)) break; + if (!ssl_should_retry(vio, handshake_ret, &event, ssl_errno_holder)) break; + + if (!vio->is_blocking_flag) { + switch (event) { + case VIO_IO_EVENT_READ: + DBUG_RETURN(VIO_SOCKET_WANT_READ); + case VIO_IO_EVENT_WRITE: + DBUG_RETURN(VIO_SOCKET_WANT_WRITE); + default: + DBUG_RETURN(VIO_SOCKET_ERROR); + } + } /* Wait for I/O so that the handshake can proceed. */ if (vio_socket_io_wait(vio, event)) break; @@ -427,14 +465,13 @@ static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, vio->ssl_arg = NULL; - return ret; + DBUG_RETURN(ret); } -static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, - long timeout MY_ATTRIBUTE((unused)), - ssl_handshake_func_t func, unsigned long *ssl_errno_holder) { - int r; - SSL *ssl; +static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, + ssl_handshake_func_t func, unsigned long *ssl_errno_holder, + SSL **sslptr) { + SSL *ssl = nullptr; my_socket sd = mysql_socket_getfd(vio->mysql_socket); /* Declared here to make compiler happy */ @@ -445,61 +482,78 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, DBUG_ENTER("ssl_do"); DBUG_PRINT("enter", ("ptr: %p, sd: %d ctx: %p", ptr, sd, ptr->ssl_context)); - if (!(ssl = SSL_new(ptr->ssl_context))) { - DBUG_PRINT("error", ("SSL_new failure")); - *ssl_errno_holder = ERR_get_error(); - DBUG_RETURN(1); + if (!sslptr) { + sslptr = &ssl; } - DBUG_PRINT("info", ("ssl: %p timeout: %ld", ssl, timeout)); - SSL_clear(ssl); - SSL_set_fd(ssl, sd); + + if (*sslptr == nullptr) { + if (!(ssl = SSL_new(ptr->ssl_context))) { + DBUG_PRINT("error", ("SSL_new failure")); + *ssl_errno_holder = ERR_get_error(); + DBUG_RETURN(1); + } + + DBUG_PRINT("info", ("ssl: %p timeout: %ld", ssl, timeout)); + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout); + SSL_set_fd(ssl, sd); #if !defined(HAVE_WOLFSSL) && OPENSSL_VERSION_NUMBER > 0x00908000L - SSL_clear_options(ssl, SSL_OP_LEGACY_SERVER_CONNECT); + SSL_clear_options(ssl, SSL_OP_LEGACY_SERVER_CONNECT); #endif #if !defined(HAVE_WOLFSSL) && defined(SSL_OP_NO_COMPRESSION) - SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); /* OpenSSL >= 1.0 only */ + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); /* OpenSSL >= 1.0 only */ #elif !defined(HAVE_WOLFSSL) && \ OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */ - sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); + sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); #endif #if !defined(HAVE_WOLFSSL) && !defined(DBUG_OFF) - { - STACK_OF(SSL_COMP) *ssl_comp_methods = NULL; - ssl_comp_methods = SSL_COMP_get_compression_methods(); - n = sk_SSL_COMP_num(ssl_comp_methods); - DBUG_PRINT("info", ("Available compression methods:\n")); - if (n == 0) - DBUG_PRINT("info", ("NONE\n")); - else - for (j = 0; j < n; j++) { - SSL_COMP *c = sk_SSL_COMP_value(ssl_comp_methods, j); + { + STACK_OF(SSL_COMP) *ssl_comp_methods = NULL; + ssl_comp_methods = SSL_COMP_get_compression_methods(); + n = sk_SSL_COMP_num(ssl_comp_methods); + DBUG_PRINT("info", ("Available compression methods:\n")); + if (n == 0) + DBUG_PRINT("info", ("NONE\n")); + else + for (j = 0; j < n; j++) { + SSL_COMP *c = sk_SSL_COMP_value(ssl_comp_methods, j); #if OPENSSL_VERSION_NUMBER < 0x10100000L - DBUG_PRINT("info", (" %d: %s\n", c->id, c->name)); + DBUG_PRINT("info", (" %d: %s\n", c->id, c->name)); #else /* OPENSSL_VERSION_NUMBER < 0x10100000L */ - DBUG_PRINT("info", - (" %d: %s\n", SSL_COMP_get_id(c), SSL_COMP_get0_name(c))); + DBUG_PRINT("info", + (" %d: %s\n", SSL_COMP_get_id(c), SSL_COMP_get0_name(c))); #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ - } - } + } + } #endif - /* - Since yaSSL does not support non-blocking send operations, use - special transport functions that properly handles non-blocking - sockets. These functions emulate the behavior of blocking I/O - operations by waiting for I/O to become available. - */ + /* + Since yaSSL does not support non-blocking send operations, use + special transport functions that properly handles non-blocking + sockets. These functions emulate the behavior of blocking I/O + operations by waiting for I/O to become available. + */ #ifdef HAVE_WOLFSSL - /* Set first argument of the transport functions. */ - wolfSSL_SetIOReadCtx(ssl, vio); - wolfSSL_SetIOWriteCtx(ssl, vio); + /* Set first argument of the transport functions. */ + wolfSSL_SetIOReadCtx(ssl, vio); + wolfSSL_SetIOWriteCtx(ssl, vio); #endif + *sslptr = ssl; + } else { + ssl = *sslptr; + } + + size_t loop_ret; + if ((loop_ret = ssl_handshake_loop(vio, ssl, func, ssl_errno_holder))) { + if (loop_ret != VIO_SOCKET_ERROR) { + DBUG_RETURN((int)loop_ret); // Don't free SSL + } - if ((r = ssl_handshake_loop(vio, ssl, func, ssl_errno_holder)) < 1) { DBUG_PRINT("error", ("SSL_connect/accept failure")); SSL_free(ssl); - DBUG_RETURN(1); + *sslptr = nullptr; + DBUG_RETURN((int)VIO_SOCKET_ERROR); } /* @@ -508,6 +562,9 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, and set pointer to the SSL structure */ if (vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), ssl, 0)) DBUG_RETURN(1); + if (sslptr != &ssl) { + *sslptr = nullptr; + } #ifndef DBUG_OFF { @@ -541,14 +598,14 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout, unsigned long *ssl_errno_holder) { DBUG_ENTER("sslaccept"); - int ret = ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder); + int ret = ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder, nullptr); DBUG_RETURN(ret); } int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout, - unsigned long *ssl_errno_holder) { + unsigned long *ssl_errno_holder, SSL **ssl) { DBUG_ENTER("sslconnect"); - int ret = ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder); + int ret = ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder, ssl); DBUG_RETURN(ret); }