diff options
Diffstat (limited to 'vio')
-rw-r--r-- | vio/CMakeLists.txt | 2 | ||||
-rw-r--r-- | vio/vio.c | 249 | ||||
-rw-r--r-- | vio/vio_priv.h | 26 | ||||
-rw-r--r-- | vio/viopipe.c | 145 | ||||
-rw-r--r-- | vio/vioshm.c | 217 | ||||
-rw-r--r-- | vio/viosocket.c | 1125 | ||||
-rw-r--r-- | vio/viossl.c | 275 | ||||
-rw-r--r-- | vio/viosslfactories.c | 70 |
8 files changed, 1366 insertions, 743 deletions
diff --git a/vio/CMakeLists.txt b/vio/CMakeLists.txt index b83518cd749..2fb82ef9dd2 100644 --- a/vio/CMakeLists.txt +++ b/vio/CMakeLists.txt @@ -17,7 +17,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${SSL_INCLUDE_DIRS}) ADD_DEFINITIONS(${SSL_DEFINES}) -SET(VIO_SOURCES vio.c viosocket.c viossl.c viosslfactories.c) +SET(VIO_SOURCES vio.c viosocket.c viossl.c viopipe.c vioshm.c viosslfactories.c) ADD_CONVENIENCE_LIBRARY(vio ${VIO_SOURCES}) TARGET_LINK_LIBRARIES(vio ${LIBSOCKET}) diff --git a/vio/vio.c b/vio/vio.c index bdb21077f44..97e3d49be21 100644 --- a/vio/vio.c +++ b/vio/vio.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2012, Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -23,24 +23,26 @@ #include "vio_priv.h" -#if defined(__WIN__) || defined(HAVE_SMEM) +#ifdef _WIN32 /** - Stub poll_read method that defaults to indicate that there - is data to read. + Stub io_wait method that defaults to indicate that + requested I/O event is ready. Used for named pipe and shared memory VIO types. @param vio Unused. + @param event Unused. @param timeout Unused. - @retval FALSE There is data to read. + @retval 1 The requested I/O event has occurred. */ -static my_bool no_poll_read(Vio *vio __attribute__((unused)), - uint timeout __attribute__((unused))) +static int no_io_wait(Vio *vio __attribute__((unused)), + enum enum_vio_io_event event __attribute__((unused)), + int timeout __attribute__((unused))) { - return FALSE; + return 1; } #endif @@ -73,8 +75,8 @@ int vio_pipe_shutdown(Vio *vio, int how) * Helper to fill most of the Vio* with defaults. */ -static void vio_init(Vio* vio, enum enum_vio_type type, - my_socket sd, HANDLE hPipe, uint flags) +static void vio_init(Vio *vio, enum enum_vio_type type, + my_socket sd, uint flags) { DBUG_ENTER("vio_init"); DBUG_PRINT("enter", ("type: %d sd: %d flags: %d", type, sd, flags)); @@ -82,11 +84,12 @@ static void vio_init(Vio* vio, enum enum_vio_type type, #ifndef HAVE_VIO_READ_BUFF flags&= ~VIO_BUFFERED_READ; #endif - bzero((char*) vio, sizeof(*vio)); - vio->type = type; - vio->sd = sd; - vio->hPipe = hPipe; + memset(vio, 0, sizeof(*vio)); + vio->type= type; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + mysql_socket_setfd(&vio->mysql_socket, sd); vio->localhost= flags & VIO_LOCALHOST; + vio->read_timeout= vio->write_timeout= -1; if ((flags & VIO_BUFFERED_READ) && !(vio->read_buffer= (char*)my_malloc(VIO_READ_BUFFER_SIZE, MYF(MY_WME)))) flags&= ~VIO_BUFFERED_READ; @@ -100,26 +103,19 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->fastsend =vio_fastsend; vio->viokeepalive =vio_keepalive; vio->should_retry =vio_should_retry; - vio->was_interrupted=vio_was_interrupted; + vio->was_timeout =vio_was_timeout; vio->vioclose =vio_close_pipe; vio->peer_addr =vio_peer_addr; vio->vioblocking =vio_blocking; vio->is_blocking =vio_is_blocking; - - vio->poll_read =no_poll_read; + vio->io_wait =no_io_wait; vio->is_connected =vio_is_connected_pipe; vio->has_data =has_no_data; vio->shutdown =vio_pipe_shutdown; - - vio->timeout=vio_win32_timeout; - /* Set default timeout */ - vio->read_timeout_ms= INFINITE; - vio->write_timeout_ms= INFINITE; - vio->pipe_overlapped.hEvent= CreateEvent(NULL, TRUE, FALSE, NULL); DBUG_VOID_RETURN; } #endif -#ifdef HAVE_SMEM +#ifdef HAVE_SMEM if (type == VIO_TYPE_SHARED_MEMORY) { vio->viodelete =vio_delete; @@ -129,26 +125,19 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->fastsend =vio_fastsend; vio->viokeepalive =vio_keepalive; vio->should_retry =vio_should_retry; - vio->was_interrupted=vio_was_interrupted; + vio->was_timeout =vio_was_timeout; vio->vioclose =vio_close_shared_memory; vio->peer_addr =vio_peer_addr; vio->vioblocking =vio_blocking; vio->is_blocking =vio_is_blocking; - - vio->poll_read =no_poll_read; + vio->io_wait =no_io_wait; vio->is_connected =vio_is_connected_shared_memory; vio->has_data =vio_shared_memory_has_data; vio->shutdown =vio_shared_memory_shutdown; - - /* Currently, shared memory is on Windows only, hence the below is ok*/ - vio->timeout= vio_win32_timeout; - /* Set default timeout */ - vio->read_timeout_ms= INFINITE; - vio->write_timeout_ms= INFINITE; DBUG_VOID_RETURN; } -#endif -#ifdef HAVE_OPENSSL +#endif +#ifdef HAVE_OPENSSL if (type == VIO_TYPE_SSL) { vio->viodelete =vio_ssl_delete; @@ -158,16 +147,16 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->fastsend =vio_fastsend; vio->viokeepalive =vio_keepalive; vio->should_retry =vio_should_retry; - vio->was_interrupted=vio_was_interrupted; + vio->was_timeout =vio_was_timeout; vio->vioclose =vio_ssl_close; vio->peer_addr =vio_peer_addr; vio->vioblocking =vio_ssl_blocking; vio->is_blocking =vio_is_blocking; - vio->timeout =vio_timeout; - vio->poll_read =vio_poll_read; + vio->io_wait =vio_io_wait; vio->is_connected =vio_is_connected; vio->has_data =vio_ssl_has_data; vio->shutdown =vio_socket_shutdown; + vio->timeout =vio_socket_timeout; DBUG_VOID_RETURN; } #endif /* HAVE_OPENSSL */ @@ -178,77 +167,110 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->fastsend =vio_fastsend; vio->viokeepalive =vio_keepalive; vio->should_retry =vio_should_retry; - vio->was_interrupted =vio_was_interrupted; + vio->was_timeout =vio_was_timeout; vio->vioclose =vio_close; vio->peer_addr =vio_peer_addr; - vio->vioblocking =vio_blocking; - vio->is_blocking =vio_is_blocking; - vio->timeout =vio_timeout; - vio->poll_read =vio_poll_read; + vio->vioblocking =vio_blocking; + vio->is_blocking =vio_is_blocking; + vio->io_wait =vio_io_wait; vio->is_connected =vio_is_connected; vio->shutdown =vio_socket_shutdown; - vio->has_data= (flags & VIO_BUFFERED_READ) ? - vio_buff_has_data : has_no_data; + vio->timeout =vio_socket_timeout; + vio->has_data = ((flags & VIO_BUFFERED_READ) ? + vio_buff_has_data : has_no_data); DBUG_VOID_RETURN; } -/* Reset initialized VIO to use with another transport type */ +/** + Reinitialize an existing Vio object. + + @remark Used to rebind an initialized socket-based Vio object + to another socket-based transport type. For example, + rebind a TCP/IP transport to SSL. + + @param vio A VIO object. + @param type A socket-based transport type. + @param sd The socket. + @param ssl An optional SSL structure. + @param flags Flags passed to vio_init. -void vio_reset(Vio* vio, enum enum_vio_type type, - my_socket sd, HANDLE hPipe, uint flags) + @return Return value is zero on success. +*/ + +my_bool vio_reset(Vio* vio, enum enum_vio_type type, + my_socket sd, void *ssl __attribute__((unused)), uint flags) { + int ret= FALSE; + Vio old_vio= *vio; + DBUG_ENTER("vio_reset"); + + /* The only supported rebind is from a socket-based transport type. */ + DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET); + + /* + Will be reinitialized depending on the flags. + Nonetheless, already buffered inside the SSL layer. + */ my_free(vio->read_buffer); - vio_init(vio, type, sd, hPipe, flags); + + vio_init(vio, type, sd, flags); + + /* Preserve perfschema info for this connection */ + vio->mysql_socket.m_psi= old_vio.mysql_socket.m_psi; + +#ifdef HAVE_OPENSSL + vio->ssl_arg= ssl; +#endif + + /* + Propagate the timeout values. Necessary to also propagate + the underlying proprieties associated with the timeout, + such as the socket blocking mode. + */ + if (old_vio.read_timeout >= 0) + ret|= vio_timeout(vio, 0, old_vio.read_timeout); + + if (old_vio.write_timeout >= 0) + ret|= vio_timeout(vio, 1, old_vio.write_timeout); + + DBUG_RETURN(MY_TEST(ret)); } -/* Open the socket or TCP/IP connection and read the fnctl() status */ +/* Create a new VIO for socket or TCP/IP connection. */ -Vio *vio_new(my_socket sd, enum enum_vio_type type, uint flags) +Vio *mysql_socket_vio_new(MYSQL_SOCKET mysql_socket, enum enum_vio_type type, uint flags) { Vio *vio; - DBUG_ENTER("vio_new"); + my_socket sd= mysql_socket_getfd(mysql_socket); + DBUG_ENTER("mysql_socket_vio_new"); DBUG_PRINT("enter", ("sd: %d", sd)); if ((vio = (Vio*) my_malloc(sizeof(*vio),MYF(MY_WME)))) { - vio_init(vio, type, sd, 0, flags); - sprintf(vio->desc, - (vio->type == VIO_TYPE_SOCKET ? "socket (%d)" : "TCP/IP (%d)"), - vio->sd); -#if !defined(__WIN__) -#if !defined(NO_FCNTL_NONBLOCK) - /* - We call fcntl() to set the flags and then immediately read them back - to make sure that we and the system are in agreement on the state of - things. - - An example of why we need to do this is FreeBSD (and apparently some - other BSD-derived systems, like Mac OS X), where the system sometimes - reports that the socket is set for non-blocking when it really will - block. - */ - fcntl(sd, F_SETFL, 0); - vio->fcntl_mode= fcntl(sd, F_GETFL); -#elif defined(HAVE_SYS_IOCTL_H) /* hpux */ - /* Non blocking sockets doesn't work good on HPUX 11.0 */ - (void) ioctl(sd,FIOSNBIO,0); - vio->fcntl_mode &= ~O_NONBLOCK; -#endif -#else /* !defined(__WIN__) */ - { - /* set to blocking mode by default */ - ulong arg=0, r; - r = ioctlsocket(sd,FIONBIO,(void*) &arg); - vio->fcntl_mode &= ~O_NONBLOCK; - } -#endif + vio_init(vio, type, sd, flags); + vio->desc= (vio->type == VIO_TYPE_SOCKET ? "socket" : "TCP/IP"); + vio->mysql_socket= mysql_socket; } DBUG_RETURN(vio); } +/* Open the socket or TCP/IP connection and read the fnctl() status */ + +Vio *vio_new(my_socket sd, enum enum_vio_type type, uint flags) +{ + Vio *vio; + MYSQL_SOCKET mysql_socket= MYSQL_INVALID_SOCKET; + DBUG_ENTER("vio_new"); + DBUG_PRINT("enter", ("sd: %d", sd)); + + mysql_socket_setfd(&mysql_socket, sd); + vio = mysql_socket_vio_new(mysql_socket, type, flags); -#ifdef __WIN__ + DBUG_RETURN(vio); +} + +#ifdef _WIN32 Vio *vio_new_win32pipe(HANDLE hPipe) { @@ -256,8 +278,16 @@ Vio *vio_new_win32pipe(HANDLE hPipe) DBUG_ENTER("vio_new_handle"); if ((vio = (Vio*) my_malloc(sizeof(Vio),MYF(MY_WME)))) { - vio_init(vio, VIO_TYPE_NAMEDPIPE, 0, hPipe, VIO_LOCALHOST); - strmov(vio->desc, "named pipe"); + vio_init(vio, VIO_TYPE_NAMEDPIPE, 0, VIO_LOCALHOST); + vio->desc= "named pipe"; + /* Create an object for event notification. */ + vio->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL); + if (vio->overlapped.hEvent == NULL) + { + my_free(vio); + DBUG_RETURN(NULL); + } + vio->hPipe= hPipe; } DBUG_RETURN(vio); } @@ -272,7 +302,8 @@ Vio *vio_new_win32shared_memory(HANDLE handle_file_map, HANDLE handle_map, DBUG_ENTER("vio_new_win32shared_memory"); if ((vio = (Vio*) my_malloc(sizeof(Vio),MYF(MY_WME)))) { - vio_init(vio, VIO_TYPE_SHARED_MEMORY, 0, 0, VIO_LOCALHOST); + vio_init(vio, VIO_TYPE_SHARED_MEMORY, 0, VIO_LOCALHOST); + vio->desc= "shared memory"; vio->handle_file_map= handle_file_map; vio->handle_map= handle_map; vio->event_server_wrote= event_server_wrote; @@ -282,7 +313,6 @@ Vio *vio_new_win32shared_memory(HANDLE handle_file_map, HANDLE handle_map, vio->event_conn_closed= event_conn_closed; vio->shared_memory_remain= 0; vio->shared_memory_pos= handle_map; - strmov(vio->desc, "shared memory"); } DBUG_RETURN(vio); } @@ -290,6 +320,49 @@ Vio *vio_new_win32shared_memory(HANDLE handle_file_map, HANDLE handle_map, #endif +/** + Set timeout for a network send or receive operation. + + @remark A non-infinite timeout causes the socket to be + set to non-blocking mode. On infinite timeouts, + the socket is set to blocking mode. + + @remark A negative timeout means an infinite timeout. + + @param vio A VIO object. + @param which Whether timeout is for send (1) or receive (0). + @param timeout Timeout interval in seconds. + + @return FALSE on success, TRUE otherwise. +*/ + +int vio_timeout(Vio *vio, uint which, int timeout_sec) +{ + int timeout_ms; + my_bool old_mode; + + /* + Vio timeouts are measured in milliseconds. Check for a possible + overflow. In case of overflow, set to infinite. + */ + if (timeout_sec > INT_MAX/1000) + timeout_ms= -1; + else + timeout_ms= (int) (timeout_sec * 1000); + + /* Deduce the current timeout status mode. */ + old_mode= vio->write_timeout < 0 && vio->read_timeout < 0; + + if (which) + vio->write_timeout= timeout_ms; + else + vio->read_timeout= timeout_ms; + + /* VIO-specific timeout handling. Might change the blocking mode. */ + return vio->timeout ? vio->timeout(vio, which, old_mode) : 0; +} + + void vio_delete(Vio* vio) { if (!vio) diff --git a/vio/vio_priv.h b/vio/vio_priv.h index 61a8ab150a9..248e1a59b23 100644 --- a/vio/vio_priv.h +++ b/vio/vio_priv.h @@ -1,7 +1,4 @@ -#ifndef VIO_PRIV_INCLUDED -#define VIO_PRIV_INCLUDED - -/* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. Copyright (c) 2012, Monty Program Ab This program is free software; you can redistribute it and/or modify @@ -17,6 +14,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifndef VIO_PRIV_INCLUDED +#define VIO_PRIV_INCLUDED + /* Structures and functions private to the vio package */ #define DONT_MAP_VIO @@ -26,16 +26,7 @@ #include <m_string.h> #include <violite.h> -#ifndef __WIN__ -#include <sys/socket.h> -#include <netdb.h> -#endif - #ifdef _WIN32 -void vio_win32_timeout(Vio *vio, uint which, uint timeout); -#endif - -#ifdef __WIN__ size_t vio_read_pipe(Vio *vio, uchar * buf, size_t size); size_t vio_write_pipe(Vio *vio, const uchar * buf, size_t size); my_bool vio_is_connected_pipe(Vio *vio); @@ -53,9 +44,10 @@ my_bool vio_shared_memory_has_data(Vio *vio); int vio_shutdown_shared_memory(Vio *vio, int how); #endif -int vio_socket_shutdown(Vio *vio, int how); -void vio_timeout(Vio *vio,uint which, uint timeout); -my_bool vio_buff_has_data(Vio *vio); +int vio_socket_shutdown(Vio *vio, int how); +my_bool vio_buff_has_data(Vio *vio); +int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event); +int vio_socket_timeout(Vio *vio, uint which, my_bool old_mode); #ifdef HAVE_OPENSSL #include "my_net.h" /* needed because of struct in_addr */ @@ -66,9 +58,7 @@ size_t vio_ssl_write(Vio *vio,const uchar* buf, size_t size); /* When the workday is over... */ int vio_ssl_close(Vio *vio); void vio_ssl_delete(Vio *vio); - int vio_ssl_blocking(Vio *vio, my_bool set_blocking_mode, my_bool *old_mode); - my_bool vio_ssl_has_data(Vio *vio); #endif /* HAVE_OPENSSL */ diff --git a/vio/viopipe.c b/vio/viopipe.c new file mode 100644 index 00000000000..f9af50bc3c9 --- /dev/null +++ b/vio/viopipe.c @@ -0,0 +1,145 @@ +/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "vio_priv.h" + +#ifdef _WIN32 + +/* + Disable posting IO completion event to the port. + In some cases (synchronous timed IO) we want to skip IOCP notifications. +*/ +static void disable_iocp_notification(OVERLAPPED *overlapped) +{ + HANDLE *handle = &(overlapped->hEvent); + *handle = ((HANDLE)((ULONG_PTR) *handle|1)); +} + +/* Enable posting IO completion event to the port */ +static void enable_iocp_notification(OVERLAPPED *overlapped) +{ + HANDLE *handle = &(overlapped->hEvent); + *handle = (HANDLE)((ULONG_PTR) *handle & ~1); +} + +static size_t wait_overlapped_result(Vio *vio, int timeout) +{ + size_t ret= (size_t) -1; + DWORD transferred, wait_status, timeout_ms; + + timeout_ms= timeout >= 0 ? timeout : INFINITE; + + /* Wait for the overlapped operation to be completed. */ + wait_status= WaitForSingleObject(vio->overlapped.hEvent, timeout_ms); + + /* The operation might have completed, attempt to retrieve the result. */ + if (wait_status == WAIT_OBJECT_0) + { + /* If retrieval fails, a error code will have been set. */ + if (GetOverlappedResult(vio->hPipe, &vio->overlapped, &transferred, FALSE)) + ret= transferred; + } + else + { + /* Error or timeout, cancel the pending I/O operation. */ + CancelIo(vio->hPipe); + + /* + If the wait timed out, set error code to indicate a + timeout error. Otherwise, wait_status is WAIT_FAILED + and extended error information was already set. + */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + } + + return ret; +} + + +size_t vio_read_pipe(Vio *vio, uchar *buf, size_t count) +{ + DWORD transferred; + size_t ret= (size_t) -1; + DBUG_ENTER("vio_read_pipe"); + + disable_iocp_notification(&vio->overlapped); + + /* Attempt to read from the pipe (overlapped I/O). */ + if (ReadFile(vio->hPipe, buf, count, &transferred, &vio->overlapped)) + { + /* The operation completed immediately. */ + ret= transferred; + } + /* Read operation is pending completion asynchronously? */ + else if (GetLastError() == ERROR_IO_PENDING) + ret= wait_overlapped_result(vio, vio->read_timeout); + + enable_iocp_notification(&vio->overlapped); + + DBUG_RETURN(ret); +} + + +size_t vio_write_pipe(Vio *vio, const uchar *buf, size_t count) +{ + DWORD transferred; + size_t ret= (size_t) -1; + DBUG_ENTER("vio_write_pipe"); + + disable_iocp_notification(&vio->overlapped); + /* Attempt to write to the pipe (overlapped I/O). */ + if (WriteFile(vio->hPipe, buf, count, &transferred, &vio->overlapped)) + { + /* The operation completed immediately. */ + ret= transferred; + } + /* Write operation is pending completion asynchronously? */ + else if (GetLastError() == ERROR_IO_PENDING) + ret= wait_overlapped_result(vio, vio->write_timeout); + + enable_iocp_notification(&vio->overlapped); + DBUG_RETURN(ret); +} + + +my_bool vio_is_connected_pipe(Vio *vio) +{ + if (PeekNamedPipe(vio->hPipe, NULL, 0, NULL, NULL, NULL)) + return TRUE; + else + return (GetLastError() != ERROR_BROKEN_PIPE); +} + + +int vio_close_pipe(Vio *vio) +{ + BOOL ret; + DBUG_ENTER("vio_close_pipe"); + + CancelIo(vio->hPipe); + CloseHandle(vio->overlapped.hEvent); + DisconnectNamedPipe(vio->hPipe); + ret= CloseHandle(vio->hPipe); + + vio->type= VIO_CLOSED; + vio->hPipe= NULL; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + + DBUG_RETURN(ret); +} + +#endif + diff --git a/vio/vioshm.c b/vio/vioshm.c new file mode 100644 index 00000000000..acc7d2402c5 --- /dev/null +++ b/vio/vioshm.c @@ -0,0 +1,217 @@ +/* Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "vio_priv.h" + +#if defined(_WIN32) && defined(HAVE_SMEM) + +size_t vio_read_shared_memory(Vio *vio, uchar *buf, size_t size) +{ + size_t length; + size_t remain_local; + char *current_position; + HANDLE events[2]; + DWORD timeout; + DBUG_ENTER("vio_read_shared_memory"); + + remain_local= size; + current_position= buf; + timeout= vio->read_timeout >= 0 ? vio->read_timeout : INFINITE; + + events[0]= vio->event_server_wrote; + events[1]= vio->event_conn_closed; + + do + { + if (vio->shared_memory_remain == 0) + { + DWORD wait_status; + + wait_status= WaitForMultipleObjects(array_elements(events), events, + FALSE, timeout); + + /* + WaitForMultipleObjects can return next values: + WAIT_OBJECT_0+0 - event from vio->event_server_wrote + WAIT_OBJECT_0+1 - event from vio->event_conn_closed. + We can't read anything + WAIT_ABANDONED_0 and WAIT_TIMEOUT - fail. We can't read anything + */ + if (wait_status != WAIT_OBJECT_0) + { + /* + If wait_status is WAIT_TIMEOUT, set error code to indicate a + timeout error. If vio->event_conn_closed was set, use an EOF + condition (return value of zero) to indicate that the operation + has been aborted. + */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + else if (wait_status == (WAIT_OBJECT_0 + 1)) + DBUG_RETURN(0); + + DBUG_RETURN(-1); + } + + vio->shared_memory_pos= vio->handle_map; + vio->shared_memory_remain= uint4korr((ulong*)vio->shared_memory_pos); + vio->shared_memory_pos+= 4; + } + + length= size; + + if (vio->shared_memory_remain < length) + length= vio->shared_memory_remain; + if (length > remain_local) + length= remain_local; + + memcpy(current_position, vio->shared_memory_pos, length); + + vio->shared_memory_remain-= length; + vio->shared_memory_pos+= length; + current_position+= length; + remain_local-= length; + + if (!vio->shared_memory_remain) + { + if (!SetEvent(vio->event_client_read)) + DBUG_RETURN(-1); + } + } while (remain_local); + length= size; + + DBUG_RETURN(length); +} + + +size_t vio_write_shared_memory(Vio *vio, const uchar *buf, size_t size) +{ + size_t length, remain, sz; + HANDLE pos; + const uchar *current_position; + HANDLE events[2]; + DWORD timeout; + DBUG_ENTER("vio_write_shared_memory"); + + remain= size; + current_position= buf; + timeout= vio->write_timeout >= 0 ? vio->write_timeout : INFINITE; + + events[0]= vio->event_server_read; + events[1]= vio->event_conn_closed; + + while (remain != 0) + { + DWORD wait_status; + + wait_status= WaitForMultipleObjects(array_elements(events), events, + FALSE, timeout); + + if (wait_status != WAIT_OBJECT_0) + { + /* Set error code to indicate a timeout error or disconnect. */ + if (wait_status == WAIT_TIMEOUT) + SetLastError(SOCKET_ETIMEDOUT); + else + SetLastError(ERROR_GRACEFUL_DISCONNECT); + + DBUG_RETURN((size_t) -1); + } + + sz= (remain > shared_memory_buffer_length ? shared_memory_buffer_length : + remain); + + int4store(vio->handle_map, sz); + pos= vio->handle_map + 4; + memcpy(pos, current_position, sz); + remain-= sz; + current_position+= sz; + if (!SetEvent(vio->event_client_wrote)) + DBUG_RETURN((size_t) -1); + } + length= size; + + DBUG_RETURN(length); +} + + +my_bool vio_is_connected_shared_memory(Vio *vio) +{ + return (WaitForSingleObject(vio->event_conn_closed, 0) != WAIT_OBJECT_0); +} + + +/** + Close shared memory and DBUG_PRINT any errors that happen on closing. + @return Zero if all closing functions succeed, and nonzero otherwise. +*/ +int vio_close_shared_memory(Vio * vio) +{ + int error_count= 0; + DBUG_ENTER("vio_close_shared_memory"); + if (vio->type != VIO_CLOSED) + { + /* + Set event_conn_closed for notification of both client and server that + connection is closed + */ + SetEvent(vio->event_conn_closed); + /* + Close all handlers. UnmapViewOfFile and CloseHandle return non-zero + result if they are success. + */ + if (UnmapViewOfFile(vio->handle_map) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("UnmapViewOfFile() failed")); + } + if (CloseHandle(vio->event_server_wrote) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->esw) failed")); + } + if (CloseHandle(vio->event_server_read) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->esr) failed")); + } + if (CloseHandle(vio->event_client_wrote) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecw) failed")); + } + if (CloseHandle(vio->event_client_read) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecr) failed")); + } + if (CloseHandle(vio->handle_file_map) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->hfm) failed")); + } + if (CloseHandle(vio->event_conn_closed) == 0) + { + error_count++; + DBUG_PRINT("vio_error", ("CloseHandle(vio->ecc) failed")); + } + } + vio->type= VIO_CLOSED; + vio->mysql_socket= MYSQL_INVALID_SOCKET; + DBUG_RETURN(error_count); +} + +#endif /* #if defined(_WIN32) && defined(HAVE_SMEM) */ + diff --git a/vio/viosocket.c b/vio/viosocket.c index baefa1c6d06..5576dfc48d8 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -24,12 +24,12 @@ the file descriptior. */ +#include "vio_priv.h" #ifdef __WIN__ #include <winsock2.h> #include <MSWSock.h> #pragma comment(lib, "ws2_32.lib") #endif -#include "vio_priv.h" #include "my_context.h" #include <mysql_async.h> @@ -37,23 +37,126 @@ # include <sys/filio.h> #endif +/* Network io wait callbacks for threadpool */ +static void (*before_io_wait)(void)= 0; +static void (*after_io_wait)(void)= 0; + +/* Wait callback macros (both performance schema and threadpool */ +#define START_SOCKET_WAIT(locker, state_ptr, sock, which, timeout) \ +do \ +{ \ + MYSQL_START_SOCKET_WAIT(locker, state_ptr, sock, \ + which, 0); \ + if (timeout && before_io_wait) \ + before_io_wait(); \ +} while(0) + + +#define END_SOCKET_WAIT(locker,timeout) \ +do \ +{ \ + MYSQL_END_SOCKET_WAIT(locker, 0); \ + if (timeout && after_io_wait) \ + after_io_wait(); \ +} while(0) + + + +void vio_set_wait_callback(void (*before_wait)(void), + void (*after_wait)(void)) +{ + before_io_wait= before_wait; + after_io_wait= after_wait; +} + int vio_errno(Vio *vio __attribute__((unused))) { - return socket_errno; /* On Win32 this mapped to WSAGetLastError() */ + /* These transport types are not Winsock based. */ +#ifdef _WIN32 + if (vio->type == VIO_TYPE_NAMEDPIPE || + vio->type == VIO_TYPE_SHARED_MEMORY) + return GetLastError(); +#endif + + /* Mapped to WSAGetLastError() on Win32. */ + return socket_errno; +} + + +/** + Attempt to wait for an I/O event on a socket. + + @param vio VIO object representing a connected socket. + @param event The type of I/O event (read or write) to wait for. + + @return Return value is -1 on failure, 0 on success. +*/ + +int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event) +{ + int timeout, ret; + + DBUG_ASSERT(event == VIO_IO_EVENT_READ || event == VIO_IO_EVENT_WRITE); + + /* Choose an appropriate timeout. */ + if (event == VIO_IO_EVENT_READ) + timeout= vio->read_timeout; + else + timeout= vio->write_timeout; + + /* Wait for input data to become available. */ + switch (vio_io_wait(vio, event, timeout)) + { + case -1: + /* Upon failure, vio_read/write() shall return -1. */ + ret= -1; + break; + case 0: + /* The wait timed out. */ + ret= -1; + break; + default: + /* A positive value indicates an I/O event. */ + ret= 0; + break; + } + + return ret; } -size_t vio_read(Vio * vio, uchar* buf, size_t size) +/* + Define a stub MSG_DONTWAIT if unavailable. In this case, fcntl + (or a equivalent) is used to enable non-blocking operations. + The flag must be supported in both send and recv operations. +*/ +#if defined(__linux__) +#define VIO_USE_DONTWAIT 1 +#define VIO_DONTWAIT MSG_DONTWAIT +#else +#define VIO_DONTWAIT 0 +#endif + +size_t vio_read(Vio *vio, uchar *buf, size_t size) { - size_t r; + ssize_t ret; + int flags= 0; DBUG_ENTER("vio_read"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, (long) buf, - (uint) size)); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %d", + mysql_socket_getfd(vio->mysql_socket), buf, + (int) size)); - /* Ensure nobody uses vio_read_buff and vio_read simultaneously */ + /* Ensure nobody uses vio_read_buff and vio_read simultaneously. */ DBUG_ASSERT(vio->read_end == vio->read_pos); + + /* If timeout is enabled, do not block if data is unavailable. */ + if (vio->read_timeout >= 0) + flags= VIO_DONTWAIT; + if (vio->async_context && vio->async_context->active) - r= my_recv_async(vio->async_context, vio->sd, buf, size, vio->read_timeout); + ret= my_recv_async(vio->async_context, + mysql_socket_getfd(vio->mysql_socket), + buf, size, vio->read_timeout); else { if (vio->async_context) @@ -65,21 +168,28 @@ size_t vio_read(Vio * vio, uchar* buf, size_t size) my_bool old_mode; vio_blocking(vio, TRUE, &old_mode); } -#ifdef __WIN__ - r = recv(vio->sd, buf, size,0); -#else - errno=0; /* For linux */ - r = read(vio->sd, buf, size); -#endif /* __WIN__ */ + while ((ret= mysql_socket_recv(vio->mysql_socket, (SOCKBUF_T *)buf, size, + flags)) == -1) + { + int error= socket_errno; + + /* The operation would block? */ + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) + break; + + /* Wait for input data to become available. */ + if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) + break; + } } #ifndef DBUG_OFF - if (r == (size_t) -1) + if (ret == -1) { - DBUG_PRINT("vio_error", ("Got error %d during read",errno)); + DBUG_PRINT("vio_error", ("Got error %d during read", errno)); } #endif /* DBUG_OFF */ - DBUG_PRINT("exit", ("%ld", (long) r)); - DBUG_RETURN(r); + DBUG_PRINT("exit", ("%d", (int) ret)); + DBUG_RETURN(ret); } @@ -93,12 +203,13 @@ size_t vio_read_buff(Vio *vio, uchar* buf, size_t size) size_t rc; #define VIO_UNBUFFERED_READ_MIN_SIZE 2048 DBUG_ENTER("vio_read_buff"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, (long) buf, - (uint) size)); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %d", + mysql_socket_getfd(vio->mysql_socket), + buf, (int) size)); if (vio->read_pos < vio->read_end) { - rc= min((size_t) (vio->read_end - vio->read_pos), size); + rc= MY_MIN((size_t) (vio->read_end - vio->read_pos), size); memcpy(buf, vio->read_pos, rc); vio->read_pos+= rc; /* @@ -127,19 +238,29 @@ size_t vio_read_buff(Vio *vio, uchar* buf, size_t size) #undef VIO_UNBUFFERED_READ_MIN_SIZE } + my_bool vio_buff_has_data(Vio *vio) { return (vio->read_pos != vio->read_end); } -size_t vio_write(Vio * vio, const uchar* buf, size_t size) + +size_t vio_write(Vio *vio, const uchar* buf, size_t size) { - size_t r; + ssize_t ret; + int flags= 0; DBUG_ENTER("vio_write"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, (long) buf, - (uint) size)); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %d", + mysql_socket_getfd(vio->mysql_socket), buf, + (int) size)); + + /* If timeout is enabled, do not block. */ + if (vio->write_timeout >= 0) + flags= VIO_DONTWAIT; + if (vio->async_context && vio->async_context->active) - r= my_send_async(vio->async_context, vio->sd, buf, size, + ret= my_send_async(vio->async_context, + mysql_socket_getfd(vio->mysql_socket), buf, size, vio->write_timeout); else { @@ -152,20 +273,27 @@ size_t vio_write(Vio * vio, const uchar* buf, size_t size) my_bool old_mode; vio_blocking(vio, TRUE, &old_mode); } -#ifdef __WIN__ - r = send(vio->sd, buf, size,0); -#else - r = write(vio->sd, buf, size); -#endif /* __WIN__ */ + while ((ret= mysql_socket_send(vio->mysql_socket, (SOCKBUF_T *)buf, size, + flags)) == -1) + { + int error= socket_errno; + /* The operation would block? */ + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) + break; + + /* Wait for the output buffer to become writable.*/ + if ((ret= vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE))) + break; + } } #ifndef DBUG_OFF - if (r == (size_t) -1) + if (ret == -1) { DBUG_PRINT("vio_error", ("Got error on write: %d",socket_errno)); } #endif /* DBUG_OFF */ - DBUG_PRINT("exit", ("%u", (uint) r)); - DBUG_RETURN(r); + DBUG_PRINT("exit", ("%d", (int) ret)); + DBUG_RETURN(ret); } #ifdef _WIN32 @@ -177,8 +305,9 @@ static void CALLBACK cancel_io_apc(ULONG_PTR data) /* Cancel IO on Windows. - On XP, issue CancelIo as asynchronous procedure call to the thread that started - IO. On Vista+, simpler cancelation is done with CancelIoEx. + On XP, issue CancelIo as asynchronous procedure call to the thread + that started IO. On Vista+, simpler cancelation is done with + CancelIoEx. */ int cancel_io(HANDLE handle, DWORD thread_id) @@ -212,39 +341,43 @@ int cancel_io(HANDLE handle, DWORD thread_id) } #endif + int vio_socket_shutdown(Vio *vio, int how) { - int ret= shutdown(vio->sd, how); + int ret= shutdown(mysql_socket_getfd(vio->mysql_socket), how); #ifdef _WIN32 /* Cancel possible IO in progress (shutdown does not do that on Windows). */ - (void) cancel_io((HANDLE)vio->sd, vio->thread_id); + (void) cancel_io((HANDLE) mysql_socket_getfd(vio->mysql_socket), + vio->thread_id); #endif return ret; } -int vio_blocking(Vio * vio __attribute__((unused)), my_bool set_blocking_mode, - my_bool *old_mode) +int vio_blocking(Vio *vio, my_bool set_blocking_mode, my_bool *old_mode) { - int r=0; + int r= 0; +#if defined(__WIN__) || !defined(NO_FCNTL_NONBLOCK) + my_socket sd= mysql_socket_getfd(vio->mysql_socket); +#endif DBUG_ENTER("vio_blocking"); - *old_mode= test(!(vio->fcntl_mode & O_NONBLOCK)); + *old_mode= MY_TEST(!(vio->fcntl_mode & O_NONBLOCK)); DBUG_PRINT("enter", ("set_blocking_mode: %d old_mode: %d", (int) set_blocking_mode, (int) *old_mode)); #if !defined(__WIN__) #if !defined(NO_FCNTL_NONBLOCK) - if (vio->sd >= 0) + if (sd >= 0) { - int old_fcntl=vio->fcntl_mode; + int old_fcntl= vio->fcntl_mode; if (set_blocking_mode) vio->fcntl_mode &= ~O_NONBLOCK; /* clear bit */ else vio->fcntl_mode |= O_NONBLOCK; /* set bit */ if (old_fcntl != vio->fcntl_mode) { - r= fcntl(vio->sd, F_SETFL, vio->fcntl_mode); + r= fcntl(sd, F_SETFL, vio->fcntl_mode); if (r == -1) { DBUG_PRINT("info", ("fcntl failed, errno %d", errno)); @@ -271,10 +404,10 @@ int vio_blocking(Vio * vio __attribute__((unused)), my_bool set_blocking_mode, vio->fcntl_mode |= O_NONBLOCK; /* set bit */ } if (old_fcntl != vio->fcntl_mode) - r = ioctlsocket(vio->sd,FIONBIO,(void*) &arg); + r = ioctlsocket(sd,FIONBIO,(void*) &arg); } else - r= test(!(vio->fcntl_mode & O_NONBLOCK)) != set_blocking_mode; + r= MY_TEST(!(vio->fcntl_mode & O_NONBLOCK)) != set_blocking_mode; #endif /* !defined(__WIN__) */ DBUG_PRINT("exit", ("%d", r)); DBUG_RETURN(r); @@ -291,6 +424,75 @@ vio_is_blocking(Vio * vio) } +int vio_socket_timeout(Vio *vio, + uint which __attribute__((unused)), + my_bool old_mode __attribute__((unused))) +{ + int ret= 0; + DBUG_ENTER("vio_socket_timeout"); + +#if defined(_WIN32) + { + int optname; + DWORD timeout= 0; + const char *optval= (const char *) &timeout; + + /* + The default socket timeout value is zero, which means an infinite + timeout. Values less than 500 milliseconds are interpreted to be of + 500 milliseconds. Hence, the VIO behavior for zero timeout, which is + intended to cause the send or receive operation to fail immediately + if no data is available, is not supported on WIN32 and neither is + necessary as it's not possible to set the VIO timeout value to zero. + + Assert that the VIO timeout is either positive or set to infinite. + */ + DBUG_ASSERT(which || vio->read_timeout); + DBUG_ASSERT(!which || vio->write_timeout); + + if (which) + { + optname= SO_SNDTIMEO; + if (vio->write_timeout > 0) + timeout= vio->write_timeout; + } + else + { + optname= SO_RCVTIMEO; + if (vio->read_timeout > 0) + timeout= vio->read_timeout; + } + + ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname, + optval, sizeof(timeout)); + } +#else + /* + The MSG_DONTWAIT trick is not used with SSL sockets as the send and + receive I/O operations are wrapped through SSL-specific functions + (SSL_read and SSL_write) which are not equivalent to the standard + recv(2) and send(2) used in vio_read() and vio_write(). Hence, the + socket blocking mode is changed and vio_io_wait() is used to wait + for I/O or timeout. + */ +#ifdef VIO_USE_DONTWAIT + if (vio->type == VIO_TYPE_SSL) +#endif + { + /* Deduce what should be the new blocking mode of the socket. */ + my_bool new_mode= vio->write_timeout < 0 && vio->read_timeout < 0; + my_bool not_used; + + /* If necessary, update the blocking mode. */ + if (new_mode != old_mode) + ret= vio_blocking(vio, new_mode, ¬_used); + } +#endif + + DBUG_RETURN(ret); +} + + int vio_fastsend(Vio * vio __attribute__((unused))) { int r=0; @@ -304,7 +506,8 @@ int vio_fastsend(Vio * vio __attribute__((unused))) #if defined(IPTOS_THROUGHPUT) { int tos = IPTOS_THROUGHPUT; - r= setsockopt(vio->sd, IPPROTO_IP, IP_TOS, (void *) &tos, sizeof(tos)); + r= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_IP, IP_TOS, + (void *)&tos, sizeof(tos)); } #endif /* IPTOS_THROUGHPUT */ if (!r) @@ -315,14 +518,16 @@ int vio_fastsend(Vio * vio __attribute__((unused))) int nodelay = 1; #endif - r= setsockopt(vio->sd, IPPROTO_TCP, TCP_NODELAY, + r= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_NODELAY, IF_WIN((const char*), (void*)) &nodelay, sizeof(nodelay)); } if (r) { - DBUG_PRINT("warning", ("Couldn't set socket option for fast send")); + DBUG_PRINT("warning", + ("Couldn't set socket option for fast send, error %d", + socket_errno)); r= -1; } DBUG_PRINT("exit", ("%d", r)); @@ -334,78 +539,56 @@ int vio_keepalive(Vio* vio, my_bool set_keep_alive) int r=0; uint opt = 0; DBUG_ENTER("vio_keepalive"); - DBUG_PRINT("enter", ("sd: %d set_keep_alive: %d", vio->sd, (int) - set_keep_alive)); + DBUG_PRINT("enter", ("sd: %d set_keep_alive: %d", + mysql_socket_getfd(vio->mysql_socket), + (int)set_keep_alive)); + if (vio->type != VIO_TYPE_NAMEDPIPE && vio->type != VIO_TYPE_SHARED_MEMORY) { if (set_keep_alive) opt = 1; - r = setsockopt(vio->sd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, - sizeof(opt)); + r = mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, SO_KEEPALIVE, + (char *)&opt, sizeof(opt)); } DBUG_RETURN(r); } -my_bool -vio_should_retry(Vio * vio) -{ - int en = socket_errno; - /* - man 2 read write - EAGAIN or EWOULDBLOCK when a socket is a non-blocking mode means - that the read/write would block. - man 7 socket - EAGAIN or EWOULDBLOCK when a socket is in a blocking mode means - that the corresponding receiving or sending timeout was reached. - */ - return en == SOCKET_EINTR || - (!vio_is_blocking(vio) && - (en == SOCKET_EAGAIN || en == SOCKET_EWOULDBLOCK)); -} +/** + Indicate whether a I/O operation must be retried later. + @param vio A VIO object + + @return Whether a I/O operation should be deferred. + @retval TRUE Temporary failure, retry operation. + @retval FALSE Indeterminate failure. +*/ my_bool -vio_was_interrupted(Vio *vio __attribute__((unused))) +vio_should_retry(Vio *vio) { - int en= socket_errno; - return (en == SOCKET_EAGAIN || en == SOCKET_EINTR || - en == SOCKET_EWOULDBLOCK || en == SOCKET_ETIMEDOUT); + return (vio_errno(vio) == SOCKET_EINTR); } -int -mysql_socket_shutdown(my_socket mysql_socket, int how) -{ - int result; +/** + Indicate whether a I/O operation timed out. -#ifdef __WIN__ - static LPFN_DISCONNECTEX DisconnectEx = NULL; - if (DisconnectEx == NULL) - { - DWORD dwBytesReturned; - GUID guidDisconnectEx = WSAID_DISCONNECTEX; - WSAIoctl(mysql_socket, SIO_GET_EXTENSION_FUNCTION_POINTER, - &guidDisconnectEx, sizeof(GUID), - &DisconnectEx, sizeof(DisconnectEx), - &dwBytesReturned, NULL, NULL); - } -#endif + @param vio A VIO object - /* Non instrumented code */ -#ifdef __WIN__ - if (DisconnectEx) - result= (DisconnectEx(mysql_socket, (LPOVERLAPPED) NULL, - (DWORD) 0, (DWORD) 0) == TRUE) ? 0 : -1; - else -#endif - result= shutdown(mysql_socket, how); + @return Whether a I/O operation timed out. + @retval TRUE Operation timed out. + @retval FALSE Not a timeout failure. +*/ - return result; +my_bool +vio_was_timeout(Vio *vio) +{ + return (vio_errno(vio) == SOCKET_ETIMEDOUT); } -int vio_close(Vio * vio) +int vio_close(Vio *vio) { int r=0; DBUG_ENTER("vio_close"); @@ -416,10 +599,10 @@ int vio_close(Vio * vio) vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_SSL); - DBUG_ASSERT(vio->sd >= 0); - if (mysql_socket_shutdown(vio->sd, SHUT_RDWR)) + DBUG_ASSERT(mysql_socket_getfd(vio->mysql_socket) >= 0); + if (mysql_socket_shutdown(vio->mysql_socket, SHUT_RDWR)) r= -1; - if (closesocket(vio->sd)) + if (mysql_socket_close(vio->mysql_socket)) r= -1; } if (r) @@ -428,7 +611,7 @@ int vio_close(Vio * vio) /* FIXME: error handling (not critical for MySQL) */ } vio->type= VIO_CLOSED; - vio->sd= -1; + vio->mysql_socket= MYSQL_INVALID_SOCKET; DBUG_RETURN(r); } @@ -445,7 +628,7 @@ enum enum_vio_type vio_type(Vio* vio) my_socket vio_fd(Vio* vio) { - return vio->sd; + return mysql_socket_getfd(vio->mysql_socket); } /** @@ -471,6 +654,7 @@ my_socket vio_fd(Vio* vio) (sockaddr_storage). @param dst_length [out] actual length of the normalized IP address. */ + static void vio_get_normalized_ip(const struct sockaddr *src, int src_length, struct sockaddr *dst, @@ -586,7 +770,8 @@ my_bool vio_peer_addr(Vio *vio, char *ip_buffer, uint16 *port, size_t ip_buffer_size) { DBUG_ENTER("vio_peer_addr"); - DBUG_PRINT("enter", ("Client socked fd: %d", (int) vio->sd)); + DBUG_PRINT("enter", ("Client socked fd: %d", + (int)mysql_socket_getfd(vio->mysql_socket))); if (vio->localhost) { @@ -617,7 +802,7 @@ my_bool vio_peer_addr(Vio *vio, char *ip_buffer, uint16 *port, /* Get sockaddr by socked fd. */ - err_code= getpeername(vio->sd, addr, &addr_length); + err_code= mysql_socket_getpeername(vio->mysql_socket, addr, &addr_length); if (err_code) { @@ -655,58 +840,6 @@ my_bool vio_peer_addr(Vio *vio, char *ip_buffer, uint16 *port, /** - Indicate whether there is data to read on a given socket. - - @note An exceptional condition event and/or errors are - interpreted as if there is data to read. - - @param sd A connected socket. - @param timeout Maximum time in seconds to poll. - - @retval FALSE There is data to read. - @retval TRUE There is no data to read. -*/ - -static my_bool socket_poll_read(my_socket sd, uint timeout) -{ -#ifdef __WIN__ - int res; - my_socket fd= sd; - fd_set readfds, errorfds; - struct timeval tm; - DBUG_ENTER("socket_poll_read"); - tm.tv_sec= timeout; - tm.tv_usec= 0; - FD_ZERO(&readfds); - FD_ZERO(&errorfds); - FD_SET(fd, &readfds); - FD_SET(fd, &errorfds); - /* The first argument is ignored on Windows, so a conversion to int is OK */ - if ((res= select((int) fd, &readfds, NULL, &errorfds, &tm) <= 0)) - { - DBUG_RETURN(res < 0 ? 0 : 1); - } - res= FD_ISSET(fd, &readfds) || FD_ISSET(fd, &errorfds); - DBUG_RETURN(!res); -#elif defined(HAVE_POLL) - struct pollfd fds; - int res; - DBUG_ENTER("socket_poll_read"); - fds.fd=sd; - fds.events=POLLIN; - fds.revents=0; - if ((res=poll(&fds,1,(int) timeout*1000)) <= 0) - { - DBUG_RETURN(res < 0 ? 0 : 1); /* Don't return 1 on errors */ - } - DBUG_RETURN(fds.revents & (POLLIN | POLLERR | POLLHUP) ? 0 : 1); -#else - return 0; -#endif -} - - -/** Retrieve the amount of data that can be read from a socket. @param vio A VIO object. @@ -715,510 +848,412 @@ static my_bool socket_poll_read(my_socket sd, uint timeout) @retval FALSE Success. @retval TRUE Failure. */ +// WL#4896: Not covered static my_bool socket_peek_read(Vio *vio, uint *bytes) { + my_socket sd= mysql_socket_getfd(vio->mysql_socket); #if defined(_WIN32) int len; - if (ioctlsocket(vio->sd, FIONREAD, &len)) + if (ioctlsocket(sd, FIONREAD, &len)) return TRUE; *bytes= len; return FALSE; #elif defined(FIONREAD_IN_SYS_IOCTL) || defined(FIONREAD_IN_SYS_FILIO) int len; - if (ioctl(vio->sd, FIONREAD, &len) < 0) + if (ioctl(sd, FIONREAD, &len) < 0) return TRUE; *bytes= len; return FALSE; #else char buf[1024]; - ssize_t res= recv(vio->sd, &buf, sizeof(buf), MSG_PEEK); + ssize_t res= recv(sd, &buf, sizeof(buf), MSG_PEEK); if (res < 0) return TRUE; *bytes= res; return FALSE; -#endif +#endif /*_WIN32*/ } +#ifndef _WIN32 /** - Indicate whether there is data to read on a given socket. - - @remark Errors are interpreted as if there is data to read. - - @param sd A connected socket. - @param timeout Maximum time in seconds to wait. - - @retval FALSE There is data (or EOF) to read. Also FALSE if error. - @retval TRUE There is _NO_ data to read or timed out. + Set of event flags grouped by operations. */ -my_bool vio_poll_read(Vio *vio, uint timeout) -{ - my_socket sd= vio->sd; - DBUG_ENTER("vio_poll_read"); - if (vio->async_context && vio->async_context->active) - DBUG_RETURN(my_poll_read_async(vio->async_context, timeout)); -#ifdef HAVE_OPENSSL - if (vio->type == VIO_TYPE_SSL) - sd= SSL_get_fd((SSL*) vio->ssl_arg); +/* + Linux specific flag used to detect connection shutdown. The flag is + also used for half-closed notification, which here is interpreted as + if there is data available to be read from the socket. +*/ +#ifndef POLLRDHUP +#define POLLRDHUP 0 #endif - DBUG_RETURN(socket_poll_read(sd, timeout)); -} +/* Data may be read. */ +#define MY_POLL_SET_IN (POLLIN | POLLPRI) +/* Data may be written. */ +#define MY_POLL_SET_OUT (POLLOUT) +/* An error or hangup. */ +#define MY_POLL_SET_ERR (POLLERR | POLLHUP | POLLNVAL) + +#endif /* _WIN32 */ /** - Determine if the endpoint of a connection is still available. + Wait for an I/O event on a VIO socket. - @remark The socket is assumed to be disconnected if an EOF - condition is encountered. + @param vio VIO object representing a connected socket. + @param event The type of I/O event to wait for. + @param timeout Interval (in milliseconds) to wait for an I/O event. + A negative timeout value means an infinite timeout. - @param vio The VIO object. + @remark sock_errno is set to SOCKET_ETIMEDOUT on timeout. - @retval TRUE EOF condition not found. - @retval FALSE EOF condition is signaled. + @return A three-state value which indicates the operation status. + @retval -1 Failure, socket_errno indicates the error. + @retval 0 The wait has timed out. + @retval 1 The requested I/O event has occurred. */ -my_bool vio_is_connected(Vio *vio) +#ifndef _WIN32 +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { - uint bytes= 0; - DBUG_ENTER("vio_is_connected"); - - /* In the presence of errors the socket is assumed to be connected. */ + int ret; + short revents __attribute__((unused)) = 0; + struct pollfd pfd; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); /* - The first step of detecting a EOF condition is veryfing - whether there is data to read. Data in this case would - be the EOF. + Note that if zero timeout, then we will not block, so we do not need to + yield to calling application in the async case. */ - if (vio_poll_read(vio, 0)) - DBUG_RETURN(TRUE); - - /* - The second step is read() or recv() from the socket returning - 0 (EOF). Unfortunelly, it's not possible to call read directly - as we could inadvertently read meaningful connection data. - Simulate a read by retrieving the number of bytes available to - read -- 0 meaning EOF. - */ - if (socket_peek_read(vio, &bytes)) - DBUG_RETURN(TRUE); - -#ifdef HAVE_OPENSSL - /* There might be buffered data at the SSL layer. */ - if (!bytes && vio->type == VIO_TYPE_SSL) - bytes= SSL_pending((SSL*) vio->ssl_arg); -#endif - - DBUG_RETURN(bytes ? TRUE : FALSE); -} - - -void vio_timeout(Vio *vio, uint which, uint timeout) -{ -#if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO) - int r; - DBUG_ENTER("vio_timeout"); - + if (timeout != 0 && vio->async_context && vio->async_context->active) { -#ifdef __WIN__ - /* Windows expects time in milliseconds as int */ - int wait_timeout= (int) timeout * 1000; -#else - /* POSIX specifies time as struct timeval. */ - struct timeval wait_timeout; - wait_timeout.tv_sec= timeout; - wait_timeout.tv_usec= 0; -#endif - - r= setsockopt(vio->sd, SOL_SOCKET, which ? SO_SNDTIMEO : SO_RCVTIMEO, - IF_WIN((const char*), (const void*))&wait_timeout, - sizeof(wait_timeout)); - + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, + PSI_SOCKET_SELECT, timeout); + ret= my_io_wait_async(vio->async_context, event, timeout); + if (ret == 0) + errno= SOCKET_ETIMEDOUT; + END_SOCKET_WAIT(locker,timeout); + DBUG_RETURN(ret); } - if (r != 0) - DBUG_PRINT("error", ("setsockopt failed: %d, errno: %d", r, socket_errno)); - - DBUG_VOID_RETURN; -#else -/* - Platforms not suporting setting of socket timeout should either use - thr_alarm or just run without read/write timeout(s) -*/ -#endif - /* Make timeout values available for async operations. */ - if (which) - vio->write_timeout= timeout; - else - vio->read_timeout= timeout; -} - - -#ifdef __WIN__ -/* - Disable posting IO completion event to the port. - In some cases (synchronous timed IO) we want to skip IOCP notifications. -*/ -static void disable_iocp_notification(OVERLAPPED *overlapped) -{ - HANDLE *handle = &(overlapped->hEvent); - *handle = ((HANDLE)((ULONG_PTR) *handle|1)); -} - -/* Enable posting IO completion event to the port */ -static void enable_iocp_notification(OVERLAPPED *overlapped) -{ - HANDLE *handle = &(overlapped->hEvent); - *handle = (HANDLE)((ULONG_PTR) *handle & ~1); -} - -/* - Finish pending IO on pipe. Honor wait timeout -*/ -static size_t pipe_complete_io(Vio* vio, char* buf, size_t size, DWORD timeout_ms) -{ - DWORD length; - DWORD ret; + memset(&pfd, 0, sizeof(pfd)); - DBUG_ENTER("pipe_complete_io"); + pfd.fd= sd; - ret= WaitForSingleObjectEx(vio->pipe_overlapped.hEvent, timeout_ms, TRUE); /* - WaitForSingleObjects will normally return WAIT_OBJECT_O (success, IO completed) - or WAIT_TIMEOUT. + Set the poll bitmask describing the type of events. + The error flags are only valid in the revents bitmask. */ - if(ret != WAIT_OBJECT_0) + switch (event) { - CancelIo(vio->hPipe); - DBUG_PRINT("error",("WaitForSingleObject() returned %d", ret)); - DBUG_RETURN((size_t)-1); + case VIO_IO_EVENT_READ: + pfd.events= MY_POLL_SET_IN; + revents= MY_POLL_SET_IN | MY_POLL_SET_ERR | POLLRDHUP; + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + pfd.events= MY_POLL_SET_OUT; + revents= MY_POLL_SET_OUT | MY_POLL_SET_ERR; + break; } - if (!GetOverlappedResult(vio->hPipe,&(vio->pipe_overlapped),&length, FALSE)) + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, timeout); + /* + Wait for the I/O event and return early in case of + error or timeout. + */ + switch ((ret= poll(&pfd, 1, timeout))) { - DBUG_PRINT("error",("GetOverlappedResult() returned last error %d", - GetLastError())); - DBUG_RETURN((size_t)-1); + case -1: + /* On error, -1 is returned. */ + break; + case 0: + /* + Set errno to indicate a timeout error. + (This is not compiled in on WIN32.) + */ + errno= SOCKET_ETIMEDOUT; + break; + default: + /* Ensure that the requested I/O event has completed. */ + DBUG_ASSERT(pfd.revents & revents); + break; } - DBUG_RETURN(length); + END_SOCKET_WAIT(locker, timeout); + DBUG_RETURN(ret); } +#else -size_t vio_read_pipe(Vio * vio, uchar *buf, size_t size) +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { - DWORD bytes_read; - size_t retval; - DBUG_ENTER("vio_read_pipe"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, (long) buf, - (uint) size)); - - disable_iocp_notification(&vio->pipe_overlapped); - if (ReadFile(vio->hPipe, buf, (DWORD)size, &bytes_read, - &(vio->pipe_overlapped))) + int ret; + struct timeval tm; + my_socket fd= mysql_socket_getfd(vio->mysql_socket); + fd_set readfds, writefds, exceptfds; + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + /* + Note that if zero timeout, then we will not block, so we do not need to + yield to calling application in the async case. + */ + if (timeout != 0 && vio->async_context && vio->async_context->active) { - retval= bytes_read; + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, + PSI_SOCKET_SELECT, timeout); + ret= my_io_wait_async(vio->async_context, event, timeout); + if (ret == 0) + WSASetLastError(SOCKET_ETIMEDOUT); + END_SOCKET_WAIT(locker, timeout); + DBUG_RETURN(ret); } - else + + /* Convert the timeout, in milliseconds, to seconds and microseconds. */ + if (timeout >= 0) { - if (GetLastError() != ERROR_IO_PENDING) - { - enable_iocp_notification(&vio->pipe_overlapped); - DBUG_PRINT("error",("ReadFile() returned last error %d", - GetLastError())); - DBUG_RETURN((size_t)-1); - } - retval= pipe_complete_io(vio, buf, size,vio->read_timeout_ms); + tm.tv_sec= timeout / 1000; + tm.tv_usec= (timeout % 1000) * 1000; } - enable_iocp_notification(&vio->pipe_overlapped); - DBUG_PRINT("exit", ("%lld", (longlong)retval)); - DBUG_RETURN(retval); -} + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); -size_t vio_write_pipe(Vio * vio, const uchar* buf, size_t size) -{ - DWORD bytes_written; - size_t retval; - DBUG_ENTER("vio_write_pipe"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, (long) buf, - (uint) size)); - disable_iocp_notification(&vio->pipe_overlapped); - if (WriteFile(vio->hPipe, buf, (DWORD)size, &bytes_written, - &(vio->pipe_overlapped))) - { - retval= bytes_written; - } - else + /* Always receive notification of exceptions. */ + FD_SET(fd, &exceptfds); + + switch (event) { - enable_iocp_notification(&vio->pipe_overlapped); - if (GetLastError() != ERROR_IO_PENDING) - { - DBUG_PRINT("vio_error",("WriteFile() returned last error %d", - GetLastError())); - DBUG_RETURN((size_t)-1); - } - retval= pipe_complete_io(vio, (char *)buf, size, vio->write_timeout_ms); + case VIO_IO_EVENT_READ: + /* Readiness for reading. */ + FD_SET(fd, &readfds); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + /* Readiness for writing. */ + FD_SET(fd, &writefds); + break; } - enable_iocp_notification(&vio->pipe_overlapped); - DBUG_PRINT("exit", ("%lld", (longlong)retval)); - DBUG_RETURN(retval); -} + START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, timeout); -my_bool vio_is_connected_pipe(Vio *vio) -{ - if (PeekNamedPipe(vio->hPipe, NULL, 0, NULL, NULL, NULL)) - return TRUE; - else - return (GetLastError() != ERROR_BROKEN_PIPE); -} + /* The first argument is ignored on Windows. */ + ret= select(0, &readfds, &writefds, &exceptfds, (timeout >= 0) ? &tm : NULL); + END_SOCKET_WAIT(locker, timeout); -int vio_close_pipe(Vio * vio) -{ - int r; - DBUG_ENTER("vio_close_pipe"); + /* Set error code to indicate a timeout error. */ + if (ret == 0) + WSASetLastError(SOCKET_ETIMEDOUT); - CancelIo(vio->hPipe); - CloseHandle(vio->pipe_overlapped.hEvent); - DisconnectNamedPipe(vio->hPipe); - r= CloseHandle(vio->hPipe); - if (r) + /* Error or timeout? */ + if (ret <= 0) + DBUG_RETURN(ret); + + /* The requested I/O event is ready? */ + switch (event) { - DBUG_PRINT("vio_error", ("close() failed, error: %d",GetLastError())); - /* FIXME: error handling (not critical for MySQL) */ + case VIO_IO_EVENT_READ: + ret= MY_TEST(FD_ISSET(fd, &readfds)); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + ret= MY_TEST(FD_ISSET(fd, &writefds)); + break; } - vio->type= VIO_CLOSED; - vio->sd= -1; - DBUG_RETURN(r); -} + /* Error conditions pending? */ + ret|= MY_TEST(FD_ISSET(fd, &exceptfds)); -void vio_win32_timeout(Vio *vio, uint which , uint timeout_sec) -{ - DWORD timeout_ms; - /* - Windows is measuring timeouts in milliseconds. Check for possible int - overflow. - */ - if (timeout_sec > UINT_MAX/1000) - timeout_ms= INFINITE; - else - timeout_ms= timeout_sec * 1000; + /* Not a timeout, ensure that a condition was met. */ + DBUG_ASSERT(ret); - /* which == 1 means "write", which == 0 means "read".*/ - if(which) - vio->write_timeout_ms= timeout_ms; - else - vio->read_timeout_ms= timeout_ms; + DBUG_RETURN(ret); } +#endif /* _WIN32 */ -#ifdef HAVE_SMEM - -size_t vio_read_shared_memory(Vio * vio, uchar* buf, size_t size) -{ - size_t length; - size_t remain_local; - char *current_position; - HANDLE events[2]; - DBUG_ENTER("vio_read_shared_memory"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %d", vio->sd, (long) buf, - size)); +/** + Connect to a peer address. - remain_local = size; - current_position=buf; + @param vio A VIO object. + @param addr Socket address containing the peer address. + @param len Length of socket address. + @param timeout Interval (in milliseconds) to wait until a + connection is established. - events[0]= vio->event_server_wrote; - events[1]= vio->event_conn_closed; + @retval FALSE A connection was successfully established. + @retval TRUE A fatal error. See socket_errno. +*/ - do - { - if (vio->shared_memory_remain == 0) - { - /* - WaitForMultipleObjects can return next values: - WAIT_OBJECT_0+0 - event from vio->event_server_wrote - WAIT_OBJECT_0+1 - event from vio->event_conn_closed. We can't read - anything - WAIT_ABANDONED_0 and WAIT_TIMEOUT - fail. We can't read anything - */ - if (WaitForMultipleObjects(array_elements(events), events, FALSE, - vio->read_timeout_ms) != WAIT_OBJECT_0) - { - DBUG_RETURN(-1); - }; +my_bool +vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, int timeout) +{ + int ret, wait; + my_bool not_used; + DBUG_ENTER("vio_socket_connect"); - vio->shared_memory_pos = vio->handle_map; - vio->shared_memory_remain = uint4korr((ulong*)vio->shared_memory_pos); - vio->shared_memory_pos+=4; - } + /* Only for socket-based transport types. */ + DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); - length = size; + /* If timeout is not infinite, set socket to non-blocking mode. */ + if ((timeout > -1) && vio_blocking(vio, FALSE, ¬_used)) + DBUG_RETURN(TRUE); - if (vio->shared_memory_remain < length) - length = vio->shared_memory_remain; - if (length > remain_local) - length = remain_local; + /* Initiate the connection. */ + ret= mysql_socket_connect(vio->mysql_socket, addr, len); - memcpy(current_position,vio->shared_memory_pos,length); +#ifdef _WIN32 + wait= (ret == SOCKET_ERROR) && + (WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK); +#else + wait= (ret == -1) && (errno == EINPROGRESS || errno == EALREADY); +#endif - vio->shared_memory_remain-=length; - vio->shared_memory_pos+=length; - current_position+=length; - remain_local-=length; + /* + The connection is in progress. The vio_io_wait() call can be used + to wait up to a specified period of time for the connection to + succeed. + + If vio_io_wait() returns 0 (after waiting however many seconds), + the socket never became writable (host is probably unreachable.) + Otherwise, if vio_io_wait() returns 1, then one of two conditions + exist: + + 1. An error occurred. Use getsockopt() to check for this. + 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)) + { + int error; + IF_WIN(int, socklen_t) optlen= sizeof(error); + IF_WIN(char, void) *optval= (IF_WIN(char, void) *) &error; - if (!vio->shared_memory_remain) + /* + At this point, we know that something happened on the socket. + But this does not means that everything is alright. The connect + might have failed. We need to retrieve the error code from the + socket layer. We must return success only if we are sure that + it was really a success. Otherwise we might prevent the caller + from trying another address to connect to. + */ + if (!(ret= mysql_socket_getsockopt(vio->mysql_socket, SOL_SOCKET, + SO_ERROR, optval, &optlen))) { - if (!SetEvent(vio->event_client_read)) - DBUG_RETURN(-1); +#ifdef _WIN32 + WSASetLastError(error); +#else + errno= error; +#endif + ret= MY_TEST(error); } - } while (remain_local); - length = size; + } - DBUG_PRINT("exit", ("%lu", (ulong) length)); - DBUG_RETURN(length); + /* If necessary, restore the blocking mode, but only if connect succeeded. */ + if ((timeout > -1) && (ret == 0)) + { + my_bool not_used; + if (vio_blocking(vio, TRUE, ¬_used)) + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(MY_TEST(ret)); } -size_t vio_write_shared_memory(Vio * vio, const uchar* buf, size_t size) -{ - size_t length, remain, sz; - HANDLE pos; - const uchar *current_position; - HANDLE events[2]; +/** + Determine if the endpoint of a connection is still available. - DBUG_ENTER("vio_write_shared_memory"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %d", vio->sd, (long) buf, - size)); + @remark The socket is assumed to be disconnected if an EOF + condition is encountered. - remain = size; - current_position = buf; + @param vio The VIO object. - events[0]= vio->event_server_read; - events[1]= vio->event_conn_closed; + @retval TRUE EOF condition not found. + @retval FALSE EOF condition is signaled. +*/ - while (remain != 0) - { - if (WaitForMultipleObjects(array_elements(events), events, FALSE, - vio->write_timeout_ms) != WAIT_OBJECT_0) - { - DBUG_RETURN((size_t) -1); - } +my_bool vio_is_connected(Vio *vio) +{ + uint bytes= 0; + DBUG_ENTER("vio_is_connected"); - sz= (remain > shared_memory_buffer_length ? shared_memory_buffer_length : - remain); + /* + The first step of detecting an EOF condition is verifying + whether there is data to read. Data in this case would be + the EOF. An exceptional condition event and/or errors are + interpreted as if there is data to read. + */ + if (!vio_io_wait(vio, VIO_IO_EVENT_READ, 0)) + DBUG_RETURN(TRUE); - int4store(vio->handle_map,sz); - pos = vio->handle_map + 4; - memcpy(pos,current_position,sz); - remain-=sz; - current_position+=sz; - if (!SetEvent(vio->event_client_wrote)) - DBUG_RETURN((size_t) -1); + /* + The second step is read() or recv() from the socket returning + 0 (EOF). Unfortunately, it's not possible to call read directly + as we could inadvertently read meaningful connection data. + Simulate a read by retrieving the number of bytes available to + read -- 0 meaning EOF. In the presence of unrecoverable errors, + the socket is assumed to be disconnected. + */ + while (socket_peek_read(vio, &bytes)) + { + if (socket_errno != SOCKET_EINTR) + DBUG_RETURN(FALSE); } - length = size; - - DBUG_PRINT("exit", ("%lu", (ulong) length)); - DBUG_RETURN(length); -} +#ifdef HAVE_OPENSSL + /* There might be buffered data at the SSL layer. */ + if (!bytes && vio->type == VIO_TYPE_SSL) + bytes= SSL_pending((SSL*) vio->ssl_arg); +#endif -my_bool vio_is_connected_shared_memory(Vio *vio) -{ - return (WaitForSingleObject(vio->event_conn_closed, 0) != WAIT_OBJECT_0); + DBUG_RETURN(bytes ? TRUE : FALSE); } +#ifndef DBUG_OFF /** - Close shared memory and DBUG_PRINT any errors that happen on closing. - @return Zero if all closing functions succeed, and nonzero otherwise. -*/ -int vio_close_shared_memory(Vio * vio) -{ - int error_count= 0; - DBUG_ENTER("vio_close_shared_memory"); - if (vio->type != VIO_CLOSED) - { - /* - Set event_conn_closed for notification of both client and server that - connection is closed - */ - SetEvent(vio->event_conn_closed); - /* - Close all handlers. UnmapViewOfFile and CloseHandle return non-zero - result if they are success. - */ - if (UnmapViewOfFile(vio->handle_map) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("UnmapViewOfFile() failed")); - } - if (CloseHandle(vio->event_server_wrote) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->esw) failed")); - } - if (CloseHandle(vio->event_server_read) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->esr) failed")); - } - if (CloseHandle(vio->event_client_wrote) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->ecw) failed")); - } - if (CloseHandle(vio->event_client_read) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->ecr) failed")); - } - if (CloseHandle(vio->handle_file_map) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->hfm) failed")); - } - if (CloseHandle(vio->event_conn_closed) == 0) - { - error_count++; - DBUG_PRINT("vio_error", ("CloseHandle(vio->ecc) failed")); - } - } - vio->type= VIO_CLOSED; - vio->sd= -1; - DBUG_RETURN(error_count); -} -#endif /* HAVE_SMEM */ -#endif /* __WIN__ */ + Number of bytes in the read or socket buffer + @remark An EOF condition might count as one readable byte. -/** - Number of bytes in the read buffer. - - @return number of bytes in the read buffer or < 0 if error. + @return number of bytes in one of the buffers or < 0 if error. */ ssize_t vio_pending(Vio *vio) { -#ifdef HAVE_OPENSSL - SSL *ssl= (SSL*) vio->ssl_arg; -#endif + uint bytes= 0; + /* Data pending on the read buffer. */ if (vio->read_pos < vio->read_end) return vio->read_end - vio->read_pos; -#ifdef HAVE_OPENSSL - if (ssl) - return SSL_pending(ssl); -#endif + /* Skip non-socket based transport types. */ + if (vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET) + { + /* Obtain number of readable bytes in the socket buffer. */ + if (socket_peek_read(vio, &bytes)) + return -1; + } - return 0; + /* + SSL not checked due to a yaSSL bug in SSL_pending that + causes it to attempt to read from the socket. + */ + + return (ssize_t) bytes; } +#endif /* DBUG_OFF */ /** Checks if the error code, returned by vio_getnameinfo(), means it was the diff --git a/vio/viossl.c b/vio/viossl.c index 5181f496768..0bc2c263336 100644 --- a/vio/viossl.c +++ b/vio/viossl.c @@ -26,7 +26,7 @@ #ifdef HAVE_OPENSSL -#ifndef HAVE_YASSL +#ifdef HAVE_YASSL /* yassl seem to be different here, SSL_get_error() value can be directly passed to ERR_error_string(), and these errors don't go @@ -35,84 +35,184 @@ namespace, one needs to use ERR_get_error() as an argument for ERR_error_string(). */ -#define SSL_get_error(X,Y) ERR_get_error() +#define SSL_errno(X,Y) SSL_get_error(X,Y) +#else +#define SSL_errno(X,Y) ERR_get_error() #endif -#ifndef DBUG_OFF +/** + Obtain the equivalent system error status for the last SSL I/O operation. -static void -report_errors(SSL* ssl) -{ - unsigned long l; - const char *file; - const char *data; - int line, flags; - char buf[512]; + @param ssl_error The result code of the failed TLS/SSL I/O operation. +*/ - DBUG_ENTER("report_errors"); +static void ssl_set_sys_error(int ssl_error) +{ + int error= 0; - while ((l= ERR_get_error_line_data(&file,&line,&data,&flags))) + switch (ssl_error) + { + case SSL_ERROR_ZERO_RETURN: + error= SOCKET_ECONNRESET; + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: +#ifdef SSL_ERROR_WANT_CONNECT + case SSL_ERROR_WANT_CONNECT: +#endif +#ifdef SSL_ERROR_WANT_ACCEPT + case SSL_ERROR_WANT_ACCEPT: +#endif + error= SOCKET_EWOULDBLOCK; + break; + case SSL_ERROR_SSL: + /* Protocol error. */ +#ifdef EPROTO + error= EPROTO; +#else + error= SOCKET_ECONNRESET; +#endif + break; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_NONE: + default: + break; + }; + + /* Set error status to a equivalent of the SSL error. */ + if (error) { - DBUG_PRINT("error", ("OpenSSL: %s:%s:%d:%s\n", ERR_error_string(l,buf), - file,line,(flags&ERR_TXT_STRING)?data:"")) ; +#ifdef _WIN32 + WSASetLastError(error); +#else + errno= error; +#endif } +} - if (ssl) + +/** + Indicate whether a SSL I/O operation must be retried later. + + @param vio VIO object representing a SSL connection. + @param ret Value returned by a SSL I/O function. + @param event[out] The type of I/O event to wait/retry. + + @return Whether a SSL I/O operation should be deferred. + @retval TRUE Temporary failure, retry operation. + @retval FALSE Indeterminate failure. +*/ + +static my_bool ssl_should_retry(Vio *vio, int ret, enum enum_vio_io_event *event) +{ + int ssl_error; + SSL *ssl= vio->ssl_arg; + my_bool should_retry= TRUE; + + /* Retrieve the result for the SSL I/O operation. */ + ssl_error= SSL_get_error(ssl, ret); + + /* Retrieve the result for the SSL I/O operation. */ + switch (ssl_error) { -#ifndef DBUG_OFF - int error= SSL_get_error(ssl, l); - DBUG_PRINT("error", ("error: %s (%d)", - ERR_error_string(error, buf), error)); -#endif + case SSL_ERROR_WANT_READ: + *event= VIO_IO_EVENT_READ; + break; + case SSL_ERROR_WANT_WRITE: + *event= VIO_IO_EVENT_WRITE; + break; + default: + should_retry= FALSE; + ssl_set_sys_error(ssl_error); + break; } - DBUG_PRINT("info", ("socket_errno: %d", socket_errno)); - DBUG_VOID_RETURN; + return should_retry; } -#endif - -size_t vio_ssl_read(Vio *vio, uchar* buf, size_t size) +size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size) { - size_t r; + int ret; + SSL *ssl= vio->ssl_arg; DBUG_ENTER("vio_ssl_read"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u ssl: 0x%lx", - vio->sd, (long) buf, (uint) size, (long) vio->ssl_arg)); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %d ssl: %p", + mysql_socket_getfd(vio->mysql_socket), buf, (int) size, + vio->ssl_arg)); if (vio->async_context && vio->async_context->active) - r= my_ssl_read_async(vio->async_context, (SSL *)vio->ssl_arg, buf, size); + ret= my_ssl_read_async(vio->async_context, (SSL *)vio->ssl_arg, buf, size); else - r= SSL_read((SSL*) vio->ssl_arg, buf, size); -#ifndef DBUG_OFF - if (r == (size_t) -1) - report_errors((SSL*) vio->ssl_arg); -#endif - DBUG_PRINT("exit", ("%u", (uint) r)); - DBUG_RETURN(r); + { + while ((ret= SSL_read(ssl, buf, size)) < 0) + { + enum enum_vio_io_event event; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event)) + break; + /* Attempt to wait for an I/O event. */ + if (vio_socket_io_wait(vio, event)) + break; + } + } + + DBUG_PRINT("exit", ("%d", (int) ret)); + DBUG_RETURN(ret < 0 ? -1 : ret); + } -size_t vio_ssl_write(Vio *vio, const uchar* buf, size_t size) +size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size) { - size_t r; + int ret; + SSL *ssl= vio->ssl_arg; DBUG_ENTER("vio_ssl_write"); - DBUG_PRINT("enter", ("sd: %d buf: 0x%lx size: %u", vio->sd, - (long) buf, (uint) size)); + DBUG_PRINT("enter", ("sd: %d buf: %p size: %d", + mysql_socket_getfd(vio->mysql_socket), + buf, (int) size)); if (vio->async_context && vio->async_context->active) - r= my_ssl_write_async(vio->async_context, (SSL *)vio->ssl_arg, buf, size); + ret= my_ssl_write_async(vio->async_context, (SSL *)vio->ssl_arg, buf, + size); else - r= SSL_write((SSL*) vio->ssl_arg, buf, size); -#ifndef DBUG_OFF - if (r == (size_t) -1) - report_errors((SSL*) vio->ssl_arg); -#endif - DBUG_PRINT("exit", ("%u", (uint) r)); - DBUG_RETURN(r); + { + while ((ret= SSL_write(ssl, buf, size)) < 0) + { + enum enum_vio_io_event event; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event)) + break; + + /* Attempt to wait for an I/O event. */ + if (vio_socket_io_wait(vio, event)) + break; + } + } + + DBUG_RETURN(ret < 0 ? -1 : ret); +} + +#ifdef HAVE_YASSL + +/* Emulate a blocking recv() call with vio_read(). */ +static long yassl_recv(void *ptr, void *buf, size_t len, + int flag __attribute__((unused))) +{ + return vio_read(ptr, buf, len); } +/* Emulate a blocking send() call with vio_write(). */ +static long yassl_send(void *ptr, const void *buf, size_t len, + int flag __attribute__((unused))) +{ + return vio_write(ptr, buf, len); +} + +#endif + int vio_ssl_close(Vio *vio) { int r= 0; @@ -144,7 +244,7 @@ int vio_ssl_close(Vio *vio) break; default: /* Shutdown failed */ DBUG_PRINT("vio_error", ("SSL_shutdown() failed, error: %d", - (int)SSL_get_error(ssl, r))); + SSL_get_error(ssl, r))); break; } } @@ -170,16 +270,57 @@ void vio_ssl_delete(Vio *vio) } +/** SSL handshake handler. */ +typedef int (*ssl_handshake_func_t)(SSL*); + + +/** + Loop and wait until a SSL handshake is completed. + + @param vio VIO object representing a SSL connection. + @param ssl SSL structure for the connection. + @param func SSL handshake handler. + + @return Return value is 1 on success. +*/ + +static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func) +{ + int ret; + + vio->ssl_arg= ssl; + + /* Initiate the SSL handshake. */ + while ((ret= func(ssl)) < 1) + { + enum enum_vio_io_event event; + + /* Process the SSL I/O error. */ + if (!ssl_should_retry(vio, ret, &event)) + break; + + /* Wait for I/O so that the handshake can proceed. */ + if (vio_socket_io_wait(vio, event)) + break; + } + + vio->ssl_arg= NULL; + + return ret; +} + + static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, - int (*connect_accept_func)(SSL*), unsigned long *errptr) + ssl_handshake_func_t func, unsigned long *errptr) { int r; SSL *ssl; my_bool unused; my_bool was_blocking; + my_socket sd= mysql_socket_getfd(vio->mysql_socket); DBUG_ENTER("ssl_do"); DBUG_PRINT("enter", ("ptr: 0x%lx, sd: %d ctx: 0x%lx", - (long) ptr, vio->sd, (long) ptr->ssl_context)); + (long) ptr, sd, (long) ptr->ssl_context)); /* Set socket to blocking if not already set */ vio_blocking(vio, 1, &was_blocking); @@ -194,15 +335,30 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, DBUG_PRINT("info", ("ssl: 0x%lx timeout: %ld", (long) ssl, timeout)); SSL_clear(ssl); SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout); - SSL_set_fd(ssl, vio->sd); -#if !defined(HAVE_YASSL) && defined(SSL_OP_NO_COMPRESSION) + SSL_set_fd(ssl, sd); + + /* + 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_YASSL + /* Set first argument of the transport functions. */ + yaSSL_transport_set_ptr(ssl, vio); + /* Set functions to use in order to send and receive data. */ + yaSSL_transport_set_recv_function(ssl, yassl_recv); + yaSSL_transport_set_send_function(ssl, yassl_send); +#endif + +#if !defined(HAVE_YASSL) && defined(SSL_OP_NO_COMPRESSION) SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); #endif - if ((r= connect_accept_func(ssl)) < 1) + if ((r= ssl_handshake_loop(vio, ssl, func)) < 1) { DBUG_PRINT("error", ("SSL_connect/accept failure")); - *errptr= SSL_get_error(ssl, r); + *errptr= SSL_errno(ssl, r); SSL_free(ssl); vio_blocking(vio, was_blocking, &unused); DBUG_RETURN(1); @@ -213,8 +369,11 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, change type, set sd to the fd used when connecting and set pointer to the SSL structure */ - vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), 0, 0); - vio->ssl_arg= (void*)ssl; + if (vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), ssl, 0)) + { + vio_blocking(vio, was_blocking, &unused); + DBUG_RETURN(1); + } #ifndef DBUG_OFF { diff --git a/vio/viosslfactories.c b/vio/viosslfactories.c index 41c1dbbd0e4..0f685593f18 100644 --- a/vio/viosslfactories.c +++ b/vio/viosslfactories.c @@ -52,27 +52,6 @@ static DH *get_dh512(void) } -static void -report_errors() -{ - unsigned long l; - const char* file; - const char* data; - int line,flags; - - DBUG_ENTER("report_errors"); - - while ((l=ERR_get_error_line_data(&file,&line,&data,&flags)) != 0) - { -#ifndef DBUG_OFF /* Avoid warning */ - char buf[200]; - DBUG_PRINT("error", ("OpenSSL: %s:%s:%d:%s\n", ERR_error_string(l,buf), - file,line,(flags & ERR_TXT_STRING) ? data : "")) ; -#endif - } - DBUG_VOID_RETURN; -} - static const char* ssl_error_string[] = { @@ -170,7 +149,8 @@ static struct st_VioSSLFd * new_VioSSLFd(const char *key_file, const char *cert_file, const char *ca_file, const char *ca_path, const char *cipher, my_bool is_client_method, - enum enum_ssl_init_error* error) + enum enum_ssl_init_error *error, + const char *crl_file, const char *crl_path) { DH *dh; struct st_VioSSLFd *ssl_fd; @@ -178,12 +158,14 @@ new_VioSSLFd(const char *key_file, const char *cert_file, DBUG_ENTER("new_VioSSLFd"); DBUG_PRINT("enter", ("key_file: '%s' cert_file: '%s' ca_file: '%s' ca_path: '%s' " - "cipher: '%s'", + "cipher: '%s' crl_file: '%s' crl_path: '%s' ", key_file ? key_file : "NULL", cert_file ? cert_file : "NULL", ca_file ? ca_file : "NULL", ca_path ? ca_path : "NULL", - cipher ? cipher : "NULL")); + cipher ? cipher : "NULL", + crl_file ? crl_file : "NULL", + crl_path ? crl_path : "NULL")); check_ssl_init(); @@ -197,7 +179,6 @@ new_VioSSLFd(const char *key_file, const char *cert_file, { *error= SSL_INITERR_MEMFAIL; DBUG_PRINT("error", ("%s", sslGetErrString(*error))); - report_errors(); my_free(ssl_fd); DBUG_RETURN(0); } @@ -214,7 +195,6 @@ new_VioSSLFd(const char *key_file, const char *cert_file, { *error= SSL_INITERR_CIPHERS; DBUG_PRINT("error", ("%s", sslGetErrString(*error))); - report_errors(); SSL_CTX_free(ssl_fd->ssl_context); my_free(ssl_fd); DBUG_RETURN(0); @@ -231,7 +211,6 @@ new_VioSSLFd(const char *key_file, const char *cert_file, *error= SSL_INITERR_BAD_PATHS; DBUG_PRINT("error", ("SSL_CTX_load_verify_locations failed : %s", sslGetErrString(*error))); - report_errors(); SSL_CTX_free(ssl_fd->ssl_context); my_free(ssl_fd); DBUG_RETURN(0); @@ -242,17 +221,38 @@ new_VioSSLFd(const char *key_file, const char *cert_file, { *error= SSL_INITERR_BAD_PATHS; DBUG_PRINT("error", ("%s", sslGetErrString(*error))); - report_errors(); SSL_CTX_free(ssl_fd->ssl_context); my_free(ssl_fd); DBUG_RETURN(0); } } + if (crl_file || crl_path) + { +#ifdef HAVE_YASSL + DBUG_PRINT("warning", ("yaSSL doesn't support CRL")); + DBUG_ASSERT(0); +#else + X509_STORE *store= SSL_CTX_get_cert_store(ssl_fd->ssl_context); + /* Load crls from the trusted ca */ + if (X509_STORE_load_locations(store, crl_file, crl_path) == 0 || + X509_STORE_set_flags(store, + X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL) == 0) + { + DBUG_PRINT("warning", ("X509_STORE_load_locations for CRL failed")); + *error= SSL_INITERR_BAD_PATHS; + DBUG_PRINT("error", ("%s", sslGetErrString(*error))); + SSL_CTX_free(ssl_fd->ssl_context); + my_free(ssl_fd); + DBUG_RETURN(0); + } +#endif + } + if (vio_set_cert_stuff(ssl_fd->ssl_context, cert_file, key_file, error)) { DBUG_PRINT("error", ("vio_set_cert_stuff failed")); - report_errors(); SSL_CTX_free(ssl_fd->ssl_context); my_free(ssl_fd); DBUG_RETURN(0); @@ -273,7 +273,8 @@ new_VioSSLFd(const char *key_file, const char *cert_file, struct st_VioSSLFd * new_VioSSLConnectorFd(const char *key_file, const char *cert_file, const char *ca_file, const char *ca_path, - const char *cipher, enum enum_ssl_init_error* error) + const char *cipher, enum enum_ssl_init_error* error, + const char *crl_file, const char *crl_path) { struct st_VioSSLFd *ssl_fd; int verify= SSL_VERIFY_PEER; @@ -286,7 +287,8 @@ new_VioSSLConnectorFd(const char *key_file, const char *cert_file, verify= SSL_VERIFY_NONE; if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, - ca_path, cipher, TRUE, error))) + ca_path, cipher, TRUE, error, + crl_file, crl_path))) { return 0; } @@ -303,12 +305,14 @@ new_VioSSLConnectorFd(const char *key_file, const char *cert_file, struct st_VioSSLFd * new_VioSSLAcceptorFd(const char *key_file, const char *cert_file, const char *ca_file, const char *ca_path, - const char *cipher, enum enum_ssl_init_error* error) + const char *cipher, enum enum_ssl_init_error* error, + const char *crl_file, const char *crl_path) { struct st_VioSSLFd *ssl_fd; int verify= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, - ca_path, cipher, FALSE, error))) + ca_path, cipher, FALSE, error, + crl_file, crl_path))) { return 0; } |