diff options
author | Michael Widenius <monty@askmonty.org> | 2012-08-14 17:23:34 +0300 |
---|---|---|
committer | Michael Widenius <monty@askmonty.org> | 2012-08-14 17:23:34 +0300 |
commit | 60589aeee03949033c66da5c1eae70d4342179fc (patch) | |
tree | 1cd399dbed17c5c7b4ed16eb7b872dc979af1c93 /vio/viosocket.c | |
parent | b39e6e3d093b45f792959ef06fea1c175263ae1a (diff) | |
download | mariadb-git-60589aeee03949033c66da5c1eae70d4342179fc.tar.gz |
Next part of merge. See TODO for details
Diffstat (limited to 'vio/viosocket.c')
-rw-r--r-- | vio/viosocket.c | 1080 |
1 files changed, 526 insertions, 554 deletions
diff --git a/vio/viosocket.c b/vio/viosocket.c index baefa1c6d06..96cf5c195e3 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> @@ -39,21 +39,92 @@ 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; } -size_t vio_read(Vio * vio, uchar* buf, size_t size) +/** + 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) { - size_t r; + 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; +} + + +/* + 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) +{ + 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 +136,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 +171,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 +206,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 +241,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 +273,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,21 +309,25 @@ 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)); @@ -235,16 +336,16 @@ int vio_blocking(Vio * vio __attribute__((unused)), my_bool set_blocking_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,7 +372,7 @@ 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; @@ -291,6 +392,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 +474,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,7 +486,7 @@ 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)); @@ -334,78 +505,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 +565,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,13 +577,19 @@ 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); } const char *vio_description(Vio * vio) { + if (!vio->desc[0]) + { + my_snprintf(vio->desc, VIO_DESCRIPTION_SIZE, + (vio->type == VIO_TYPE_SOCKET ? "socket (%d)" : "TCP/IP (%d)"), + mysql_socket_getfd(vio->mysql_socket)); + } return vio->desc; } @@ -445,7 +600,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 +626,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 +742,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 +774,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 +812,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 +820,383 @@ 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"); + int ret; + short revents= 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"); - /* In the presence of errors the socket is assumed to be connected. */ + memset(&pfd, 0, sizeof(pfd)); - /* - The first step of detecting a EOF condition is veryfing - whether there is data to read. Data in this case would - be the EOF. - */ - if (vio_poll_read(vio, 0)) - DBUG_RETURN(TRUE); + pfd.fd= sd; /* - 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. + Set the poll bitmask describing the type of events. + The error flags are only valid in the revents bitmask. */ - 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"); - + switch (event) { -#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)); - + 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 (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); -} + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, 0); -/* - 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; - - DBUG_ENTER("pipe_complete_io"); - - ret= WaitForSingleObjectEx(vio->pipe_overlapped.hEvent, timeout_ms, TRUE); /* - WaitForSingleObjects will normally return WAIT_OBJECT_O (success, IO completed) - or WAIT_TIMEOUT. + Wait for the I/O event and return early in case of + error or timeout. */ - if(ret != WAIT_OBJECT_0) + switch ((ret= poll(&pfd, 1, timeout))) { - CancelIo(vio->hPipe); - DBUG_PRINT("error",("WaitForSingleObject() returned %d", ret)); - DBUG_RETURN((size_t)-1); - } - - if (!GetOverlappedResult(vio->hPipe,&(vio->pipe_overlapped),&length, FALSE)) - { - 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); + MYSQL_END_SOCKET_WAIT(locker, 0); + 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))) - { - retval= bytes_read; - } - else + 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"); + + /* 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); -} + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, 0); -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); + MYSQL_END_SOCKET_WAIT(locker, 0); -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= test(FD_ISSET(fd, &readfds)); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + ret= test(FD_ISSET(fd, &writefds)); + break; } - vio->type= VIO_CLOSED; - vio->sd= -1; - DBUG_RETURN(r); -} + /* Error conditions pending? */ + ret|= 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= 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(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 -/** - Number of bytes in the read buffer. + @remark An EOF condition might count as one readable byte. - @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; + } + + /* + SSL not checked due to a yaSSL bug in SSL_pending that + causes it to attempt to read from the socket. + */ - return 0; + return (ssize_t) bytes; } +#endif /* DBUG_OFF */ /** Checks if the error code, returned by vio_getnameinfo(), means it was the @@ -1237,13 +1215,9 @@ ssize_t vio_pending(Vio *vio) my_bool vio_is_no_name_error(int err_code) { #ifdef _WIN32 - return err_code == WSANO_DATA || err_code == EAI_NONAME; - #else - return err_code == EAI_NONAME; - #endif } @@ -1282,8 +1256,6 @@ int vio_getnameinfo(const struct sockaddr *sa, #endif /* HAVE_IPV6 */ } - return getnameinfo(sa, sa_length, - hostname, hostname_size, - port, port_size, - flags); + return getnameinfo(sa, sa_length, hostname, hostname_size, + port, port_size, flags); } |