diff options
author | hcpp <hcpp@ydb.tech> | 2023-11-08 12:09:41 +0300 |
---|---|---|
committer | hcpp <hcpp@ydb.tech> | 2023-11-08 12:56:14 +0300 |
commit | a361f5b98b98b44ea510d274f6769164640dd5e1 (patch) | |
tree | c47c80962c6e2e7b06798238752fd3da0191a3f6 /contrib/libs/libmysql_r/vio/viosocket.cc | |
parent | 9478806fde1f4d40bd5a45e7cbe77237dab613e9 (diff) | |
download | ydb-a361f5b98b98b44ea510d274f6769164640dd5e1.tar.gz |
metrics have been added
Diffstat (limited to 'contrib/libs/libmysql_r/vio/viosocket.cc')
-rw-r--r-- | contrib/libs/libmysql_r/vio/viosocket.cc | 1243 |
1 files changed, 1243 insertions, 0 deletions
diff --git a/contrib/libs/libmysql_r/vio/viosocket.cc b/contrib/libs/libmysql_r/vio/viosocket.cc new file mode 100644 index 0000000000..b086d3e21a --- /dev/null +++ b/contrib/libs/libmysql_r/vio/viosocket.cc @@ -0,0 +1,1243 @@ +/* + Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + Without limiting anything contained in the foregoing, this file, + which is part of C Driver for MySQL (Connector/C), is also subject to the + Universal FOSS Exception, version 1.0, a copy of which can be found at + http://oss.oracle.com/licenses/universal-foss-exception. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* + Note that we can't have assertion on file descriptors; The reason for + this is that during mysql shutdown, another thread can close a file + we are working on. In this case we should just return read errors from + the file descriptior. +*/ + +#include "my_config.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <time.h> +#ifndef _WIN32 +#include <netdb.h> +#endif +#include <stdio.h> +#include <stdlib.h> + +#include "my_compiler.h" +#include "my_dbug.h" +#include "my_inttypes.h" +#include "my_io.h" +#include "my_macros.h" +#include "template_utils.h" +#include "vio/vio_priv.h" + +#ifdef FIONREAD_IN_SYS_FILIO +#include <sys/filio.h> +#endif +#ifndef _WIN32 +#include <netinet/tcp.h> +#endif +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#include "mysql/psi/mysql_socket.h" + +int vio_errno(Vio *vio MY_ATTRIBUTE((unused))) { +/* 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; +} + +/* + 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"); + + /* 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; + + while ((ret = mysql_socket_recv(vio->mysql_socket, (SOCKBUF_T *)buf, size, + flags)) == -1) { + int error = socket_errno; + + /* Error encountered that is unrelated to blocking; percolate it up. */ +#if SOCKET_EAGAIN == SOCKET_EWOULDBLOCK + if (error != SOCKET_EAGAIN) +#else + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) +#endif + break; + /* + Nonblocking with either EAGAIN or EWOULDBLOCK. Don't call + io_wait. 0 bytes are available. + */ + DBUG_ASSERT(error == SOCKET_EAGAIN || error == SOCKET_EWOULDBLOCK); + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_read on nonblocking socket read no bytes")); + DBUG_RETURN(-1); + } + + /* Wait for input data to become available. */ + if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) break; + } + DBUG_RETURN(ret); +} + +/* + Buffered read: if average read size is small it may + reduce number of syscalls. +*/ + +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: %p size: %u", + mysql_socket_getfd(vio->mysql_socket), buf, (uint)size)); + + if (vio->read_pos < vio->read_end) { + rc = MY_MIN((size_t)(vio->read_end - vio->read_pos), size); + memcpy(buf, vio->read_pos, rc); + vio->read_pos += rc; + /* + Do not try to read from the socket now even if rc < size: + vio_read can return -1 due to an error or non-blocking mode, and + the safest way to handle it is to move to a separate branch. + */ + } else if (size < VIO_UNBUFFERED_READ_MIN_SIZE) { + rc = vio_read(vio, (uchar *)vio->read_buffer, VIO_READ_BUFFER_SIZE); + if (rc != 0 && rc != (size_t)-1) { + if (rc > size) { + vio->read_pos = vio->read_buffer + size; + vio->read_end = vio->read_buffer + rc; + rc = size; + } + memcpy(buf, vio->read_buffer, rc); + } + } else + rc = vio_read(vio, buf, size); + DBUG_RETURN(rc); +#undef VIO_UNBUFFERED_READ_MIN_SIZE +} + +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) { + ssize_t ret; + int flags = 0; + DBUG_ENTER("vio_write"); + + /* If timeout is enabled, do not block. */ + if (vio->write_timeout >= 0) flags = VIO_DONTWAIT; + + while ((ret = mysql_socket_send(vio->mysql_socket, (SOCKBUF_T *)buf, size, + flags)) == -1) { + int error = socket_errno; + + /* The operation would block? */ +#if SOCKET_EAGAIN == SOCKET_EWOULDBLOCK + if (error != SOCKET_EAGAIN) +#else + if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) +#endif + break; + + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_write on nonblocking socket written no bytes")); + DBUG_RETURN(-1); + } + + /* Wait for the output buffer to become writable.*/ + if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE))) break; + } + + DBUG_RETURN(ret); +} + +// WL#4896: Not covered +int vio_set_blocking(Vio *vio, bool status) { + DBUG_ENTER("vio_set_blocking"); + +#ifdef _WIN32 + + { + int ret; + u_long arg = status ? 0 : 1; + ret = ioctlsocket(mysql_socket_getfd(vio->mysql_socket), FIONBIO, &arg); + DBUG_RETURN(ret); + } +#else + { + int flags; + + if ((flags = fcntl(mysql_socket_getfd(vio->mysql_socket), F_GETFL, NULL)) < + 0) + DBUG_RETURN(-1); + + /* + Always set/clear the flag to avoid inheritance issues. This is + a issue mainly on Mac OS X Tiger (version 10.4) where although + the O_NONBLOCK flag is inherited from the parent socket, the + actual non-blocking behavior is not inherited. + */ + if (status) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(mysql_socket_getfd(vio->mysql_socket), F_SETFL, flags) == -1) + DBUG_RETURN(-1); + } +#endif + + DBUG_RETURN(0); +} + +int vio_set_blocking_flag(Vio *vio, bool status) { + DBUG_ENTER(__func__); + int ret = 0; + /* + Asynchronous communication in client is allowed only for below + types of connections. + */ + if (VIO_TYPE_TCPIP == vio->type || VIO_TYPE_SOCKET == vio->type || + VIO_TYPE_SSL == vio->type) { + vio->is_blocking_flag = status; + ret = vio_set_blocking(vio, status); + } + DBUG_RETURN(ret); +} + +bool vio_is_blocking(Vio *vio) { + DBUG_ENTER(__func__); + DBUG_RETURN(vio->is_blocking_flag); +} + +int vio_socket_timeout(Vio *vio, uint which MY_ATTRIBUTE((unused)), + bool old_mode) { + int ret = 0; + 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. */ + bool new_mode = vio->write_timeout < 0 && vio->read_timeout < 0; + + /* If necessary, update the blocking mode. */ + if (new_mode != old_mode) ret = vio_set_blocking(vio, new_mode); + } +#endif + + DBUG_RETURN(ret); +} + +int vio_fastsend(Vio *vio) { + int r = 0; + DBUG_ENTER("vio_fastsend"); + +#if defined(IPTOS_THROUGHPUT) + { + int tos = IPTOS_THROUGHPUT; + r = mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_IP, IP_TOS, + (void *)&tos, sizeof(tos)); + } +#endif /* IPTOS_THROUGHPUT */ + if (!r) { +#ifdef _WIN32 + BOOL nodelay = 1; +#else + int nodelay = 1; +#endif + + 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")); + r = -1; + } + DBUG_PRINT("exit", ("%d", r)); + DBUG_RETURN(r); +} + +int vio_keepalive(Vio *vio, bool set_keep_alive) { + int r = 0; + uint opt = 0; + DBUG_ENTER("vio_keepalive"); + 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) { + if (set_keep_alive) opt = 1; + r = mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, SO_KEEPALIVE, + (char *)&opt, sizeof(opt)); + } + DBUG_RETURN(r); +} + +/** + 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. +*/ + +bool vio_should_retry(Vio *vio) { return (vio_errno(vio) == SOCKET_EINTR); } + +/** + Indicate whether a I/O operation timed out. + + @param vio A VIO object + + @return Whether a I/O operation timed out. + @retval true Operation timed out. + @retval false Not a timeout failure. +*/ + +bool vio_was_timeout(Vio *vio) { return (vio_errno(vio) == SOCKET_ETIMEDOUT); } + +#ifdef USE_PPOLL_IN_VIO +static void vio_wait_until_woken(Vio *vio) { + while (vio->poll_shutdown_flag.test_and_set()) { + // Wait until the vio is woken up from poll. + } +} +#elif defined HAVE_KQUEUE +static const int WAKEUP_EVENT_ID = 0xFACEFEED; + +static void vio_wait_until_woken(Vio *vio) { + if (vio->kq_fd != -1) { + struct kevent kev; + + EV_SET(&kev, WAKEUP_EVENT_ID, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + int nev = kevent(vio->kq_fd, &kev, 1, nullptr, 0, nullptr); + if (nev != -1) { + while (vio->kevent_wakeup_flag.test_and_set()) { + // Wait until the vio is woken up from kevent. + } + } + } +} +#endif + +int vio_shutdown(Vio *vio) { + int r = 0; + DBUG_ENTER("vio_shutdown"); + + if (vio->inactive == false) { + DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET || + vio->type == VIO_TYPE_SSL); + + DBUG_ASSERT(mysql_socket_getfd(vio->mysql_socket) >= 0); + if (mysql_socket_shutdown(vio->mysql_socket, SHUT_RDWR)) r = -1; + +#ifdef USE_PPOLL_IN_VIO + if (vio->thread_id != 0 && vio->poll_shutdown_flag.test_and_set()) { + // Send signal to wake up from poll. + if (pthread_kill(vio->thread_id, SIGUSR1) == 0) + vio_wait_until_woken(vio); + else + perror("Error in pthread_kill"); + } +#elif defined HAVE_KQUEUE + if (vio->kq_fd != -1 && vio->kevent_wakeup_flag.test_and_set()) + vio_wait_until_woken(vio); +#endif + + if (mysql_socket_close(vio->mysql_socket)) r = -1; +#ifdef HAVE_KQUEUE + if (vio->kq_fd == -1 || close(vio->kq_fd)) r = -1; + vio->kq_fd = -1; +#endif + } + + if (r) { + DBUG_PRINT("vio_error", ("close() failed, error: %d", socket_errno)); + /* FIXME: error handling (not critical for MySQL) */ + } + vio->inactive = true; + vio->mysql_socket = MYSQL_INVALID_SOCKET; + DBUG_RETURN(r); +} + +void vio_description(Vio *vio, char *buf) { + switch (vio->type) { + case VIO_TYPE_SOCKET: + snprintf(buf, VIO_DESCRIPTION_SIZE, "socket (%d)", + mysql_socket_getfd(vio->mysql_socket)); + break; +#ifdef _WIN32 + case VIO_TYPE_NAMEDPIPE: + my_stpcpy(buf, "named pipe"); + break; + case VIO_TYPE_SHARED_MEMORY: + my_stpcpy(buf, "shared memory"); + break; +#endif + default: + snprintf(buf, VIO_DESCRIPTION_SIZE, "TCP/IP (%d)", + mysql_socket_getfd(vio->mysql_socket)); + break; + } +} + +enum enum_vio_type vio_type(const Vio *vio) { return vio->type; } + +my_socket vio_fd(Vio *vio) { return mysql_socket_getfd(vio->mysql_socket); } + +/** + Convert a sock-address (AF_INET or AF_INET6) into the "normalized" form, + which is the IPv4 form for IPv4-mapped or IPv4-compatible IPv6 addresses. + + @note Background: when IPv4 and IPv6 are used simultaneously, IPv4 + addresses may be written in a form of IPv4-mapped or IPv4-compatible IPv6 + addresses. That means, one address (a.b.c.d) can be written in three forms: + - IPv4: a.b.c.d; + - IPv4-compatible IPv6: @code ::a.b.c.d @endcode; + - IPv4-mapped IPv4: @code ::ffff:a.b.c.d @endcode; + + Having three forms of one address makes it a little difficult to compare + addresses with each other (the IPv4-compatible IPv6-address of foo.bar + will be different from the IPv4-mapped IPv6-address of foo.bar). + + @note This function can be made public when it's needed. + + @param [in] src source IP address (AF_INET or AF_INET6). + @param [in] src_length length of the src. + @param [out] dst a buffer to store normalized IP address + (sockaddr_storage). + @param [out] dst_length actual length of the normalized IP address. +*/ +static void vio_get_normalized_ip(const struct sockaddr *src, size_t src_length, + struct sockaddr *dst, size_t *dst_length) { + switch (src->sa_family) { + case AF_INET: + memcpy(dst, src, src_length); + *dst_length = src_length; + break; + + case AF_INET6: { + const struct sockaddr_in6 *src_addr6 = (const struct sockaddr_in6 *)src; + const struct in6_addr *src_ip6 = &(src_addr6->sin6_addr); + const uint32 *src_ip6_int32 = + pointer_cast<const uint32 *>(src_ip6->s6_addr); + + if (IN6_IS_ADDR_V4MAPPED(src_ip6) || IN6_IS_ADDR_V4COMPAT(src_ip6)) { + struct sockaddr_in *dst_ip4 = (struct sockaddr_in *)dst; + + /* + This is an IPv4-mapped or IPv4-compatible IPv6 address. It should + be converted to the IPv4 form. + */ + + *dst_length = sizeof(struct sockaddr_in); + + memset(dst_ip4, 0, *dst_length); + dst_ip4->sin_family = AF_INET; + dst_ip4->sin_port = src_addr6->sin6_port; + + /* + In an IPv4 mapped or compatible address, the last 32 bits represent + the IPv4 address. The byte orders for IPv6 and IPv4 addresses are + the same, so a simple copy is possible. + */ + dst_ip4->sin_addr.s_addr = src_ip6_int32[3]; + } else { + /* This is a "native" IPv6 address. */ + + memcpy(dst, src, src_length); + *dst_length = src_length; + } + + break; + } + } +} + +/** + Return the normalized IP address string for a sock-address. + + The idea is to return an IPv4-address for an IPv4-mapped and + IPv4-compatible IPv6 address. + + The function writes the normalized IP address to the given buffer. + The buffer should have enough space, otherwise error flag is returned. + The system constant INET6_ADDRSTRLEN can be used to reserve buffers of + the right size. + + @param [in] addr sockaddr object (AF_INET or AF_INET6). + @param [in] addr_length length of the addr. + @param [out] ip_string buffer to write normalized IP address. + @param [in] ip_string_size size of the ip_string. + + @return Error status. + @retval true in case of error (the ip_string buffer is not enough). + @retval false on success. +*/ + +bool vio_get_normalized_ip_string(const struct sockaddr *addr, + size_t addr_length, char *ip_string, + size_t ip_string_size) { + struct sockaddr_storage norm_addr_storage; + struct sockaddr *norm_addr = (struct sockaddr *)&norm_addr_storage; + size_t norm_addr_length; + int err_code; + + vio_get_normalized_ip(addr, addr_length, norm_addr, &norm_addr_length); + + err_code = vio_getnameinfo(norm_addr, ip_string, ip_string_size, NULL, 0, + NI_NUMERICHOST); + + if (!err_code) return false; + + DBUG_PRINT("error", ("getnameinfo() failed with %d (%s).", (int)err_code, + (const char *)gai_strerror(err_code))); + return true; +} + +/** + Return IP address and port of a VIO client socket. + + The function returns an IPv4 address if IPv6 support is disabled. + + The function returns an IPv4 address if the client socket is associated + with an IPv4-compatible or IPv4-mapped IPv6 address. Otherwise, the native + IPv6 address is returned. +*/ + +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)mysql_socket_getfd(vio->mysql_socket))); + + if (vio->localhost) { + /* + Initialize vio->remote and vio->addLen. Set vio->remote to IPv4 loopback + address. + */ + struct in_addr *ip4 = &((struct sockaddr_in *)&(vio->remote))->sin_addr; + + vio->remote.ss_family = AF_INET; + vio->addrLen = sizeof(struct sockaddr_in); + + ip4->s_addr = htonl(INADDR_LOOPBACK); + + /* Initialize ip_buffer and port. */ + + my_stpcpy(ip_buffer, "127.0.0.1"); + *port = 0; + } else { + int err_code; + char port_buffer[NI_MAXSERV]; + + struct sockaddr_storage addr_storage; + struct sockaddr *addr = (struct sockaddr *)&addr_storage; + socket_len_t addr_length = sizeof(addr_storage); + + /* Get sockaddr by socked fd. */ + + err_code = mysql_socket_getpeername(vio->mysql_socket, addr, &addr_length); + + if (err_code) { + DBUG_PRINT("exit", ("getpeername() gave error: %d", socket_errno)); + DBUG_RETURN(true); + } + + /* Normalize IP address. */ + + vio_get_normalized_ip(addr, addr_length, (struct sockaddr *)&vio->remote, + &vio->addrLen); + + /* Get IP address & port number. */ + + err_code = vio_getnameinfo((struct sockaddr *)&vio->remote, ip_buffer, + ip_buffer_size, port_buffer, NI_MAXSERV, + NI_NUMERICHOST | NI_NUMERICSERV); + + if (err_code) { + DBUG_PRINT("exit", + ("getnameinfo() gave error: %s", gai_strerror(err_code))); + DBUG_RETURN(true); + } + + *port = (uint16)strtol(port_buffer, NULL, 10); + } + + DBUG_PRINT("exit", ("Client IP address: %s; port: %d", + (const char *)ip_buffer, (int)*port)); + DBUG_RETURN(false); +} + +/** + Retrieve the amount of data that can be read from a socket. + + @param vio A VIO object. + @param [out] bytes The amount of bytes available. + + @retval false Success. + @retval true Failure. +*/ +// WL#4896: Not covered +static bool socket_peek_read(Vio *vio, uint *bytes) { + my_socket sd = mysql_socket_getfd(vio->mysql_socket); +#if defined(_WIN32) + u_long 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(sd, FIONREAD, &len) < 0) return true; + *bytes = len; + return false; +#else + char buf[1024]; + ssize_t res = recv(sd, &buf, sizeof(buf), MSG_PEEK); + if (res < 0) return true; + *bytes = res; + return false; +#endif +} + +#ifndef _WIN32 + +/** + Set of event flags grouped by operations. +*/ + +/* + 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 + +/* 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 + +/** + Wait for an I/O event on a VIO socket. + + @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. + + @remark sock_errno is set to SOCKET_ETIMEDOUT on timeout. + + @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. +*/ + +#if !defined(_WIN32) && !defined(HAVE_KQUEUE) +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { + int ret; + int retry_count = 0; +#ifndef DBUG_OFF + short revents = 0; +#endif + struct pollfd pfd; + my_socket sd = mysql_socket_getfd(vio->mysql_socket); + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + memset(&pfd, 0, sizeof(pfd)); + + pfd.fd = sd; + + /* + Set the poll bitmask describing the type of events. + The error flags are only valid in the revents bitmask. + */ + switch (event) { + case VIO_IO_EVENT_READ: + pfd.events = MY_POLL_SET_IN; +#ifndef DBUG_OFF + revents = MY_POLL_SET_IN | MY_POLL_SET_ERR | POLLRDHUP; +#endif + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + pfd.events = MY_POLL_SET_OUT; +#ifndef DBUG_OFF + revents = MY_POLL_SET_OUT | MY_POLL_SET_ERR; +#endif + break; + } + + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, + 0); + +#ifdef USE_PPOLL_IN_VIO + // Check if shutdown is in progress, if so return -1 + if (vio->poll_shutdown_flag.test_and_set()) DBUG_RETURN(-1); + + timespec ts; + timespec *ts_ptr = nullptr; + + if (timeout >= 0) { + ts = {timeout / 1000, (timeout % 1000) * 1000000}; + ts_ptr = &ts; + } +#endif + + /* + Wait for the I/O event and return early in case of + error or timeout. + */ + do { +#ifdef USE_PPOLL_IN_VIO + /* + vio->signal_mask is only useful when thread_id != 0. + thread_id is only set for servers, so signal_mask is unused for client + libraries. + */ + ret = ppoll(&pfd, 1, ts_ptr, + vio->thread_id != 0 ? &vio->signal_mask : nullptr); +#else + ret = poll(&pfd, 1, timeout); +#endif + } while (ret < 0 && vio_should_retry(vio) && + (retry_count++ < vio->retry_count)); + +#ifdef USE_PPOLL_IN_VIO + vio->poll_shutdown_flag.clear(); +#endif + + switch (ret) { + 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; + } + + MYSQL_END_SOCKET_WAIT(locker, 0); + DBUG_RETURN(ret); +} + +#elif defined(_WIN32) +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { + int ret; + int retry_count = 0; + struct timeval tm; + my_socket fd; + fd_set readfds, writefds, exceptfds; + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + fd = mysql_socket_getfd(vio->mysql_socket); + + if (fd == INVALID_SOCKET) DBUG_RETURN(-1); + + /* Convert the timeout, in milliseconds, to seconds and microseconds. */ + if (timeout >= 0) { + tm.tv_sec = timeout / 1000; + tm.tv_usec = (timeout % 1000) * 1000; + } + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + /* Always receive notification of exceptions. */ + FD_SET(fd, &exceptfds); + + switch (event) { + 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; + } + + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, + 0); + + /* The first argument is ignored on Windows. */ + do { + ret = select((int)(fd + 1), &readfds, &writefds, &exceptfds, + (timeout >= 0) ? &tm : NULL); + } while (ret < 0 && vio_should_retry(vio) && + (retry_count++ < vio->retry_count)); + + MYSQL_END_SOCKET_WAIT(locker, 0); + + /* Set error code to indicate a timeout error. */ + if (ret == 0) WSASetLastError(SOCKET_ETIMEDOUT); + + /* Error or timeout? */ + if (ret <= 0) DBUG_RETURN(ret); + + /* The requested I/O event is ready? */ + switch (event) { + 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; + } + + /* Error conditions pending? */ + ret |= MY_TEST(FD_ISSET(fd, &exceptfds)); + + /* Not a timeout, ensure that a condition was met. */ + DBUG_ASSERT(ret); + + DBUG_RETURN(ret); +} +#elif defined(HAVE_KQUEUE) +int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { + int nev; + static const int MAX_EVENT = 2; + struct kevent kev_set[MAX_EVENT]; + struct kevent kev_event[MAX_EVENT]; + + my_socket fd = mysql_socket_getfd(vio->mysql_socket); + MYSQL_SOCKET_WAIT_VARIABLES(locker, state) /* no ';' */ + DBUG_ENTER("vio_io_wait"); + + if (vio->kq_fd == -1) DBUG_RETURN(-1); + + EV_SET(&kev_set[1], WAKEUP_EVENT_ID, EVFILT_USER, + EV_ADD | EV_ENABLE | EV_DISPATCH | EV_CLEAR, 0, 0, nullptr); + switch (event) { + case VIO_IO_EVENT_READ: + EV_SET(&kev_set[0], fd, EVFILT_READ, + EV_ADD | EV_ENABLE | EV_DISPATCH | EV_CLEAR, 0, 0, nullptr); + break; + case VIO_IO_EVENT_WRITE: + case VIO_IO_EVENT_CONNECT: + EV_SET(&kev_set[0], fd, EVFILT_WRITE, + EV_ADD | EV_ENABLE | EV_DISPATCH | EV_CLEAR, 0, 0, nullptr); + break; + } + MYSQL_START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, + 0); + + timespec ts = {static_cast<long>(timeout / 1000), + (static_cast<long>(timeout) % 1000) * 1000000}; + + // Check if shutdown is in progress, if so return -1. + if (vio->kevent_wakeup_flag.test_and_set()) DBUG_RETURN(-1); + + int retry_count = 0; + do { + nev = kevent(vio->kq_fd, kev_set, MAX_EVENT, kev_event, MAX_EVENT, + timeout >= 0 ? &ts : nullptr); + } while (nev < 0 && vio_should_retry(vio) && + (retry_count++ < vio->retry_count)); + + vio->kevent_wakeup_flag.clear(); + + if (nev == -1) { + // On error, -1 is returned. + DBUG_PRINT("error", ("kevent returned error %d\n", errno)); + } else if (nev == 0) { + // Timeout in kevent. + errno = SOCKET_ETIMEDOUT; + } else { + for (int i = 0; i < nev; i++) { + if (!(kev_event[i].flags & (EV_ERROR | EV_EOF))) { + // Ensure that the requested I/O event has completed. + DBUG_ASSERT(event == VIO_IO_EVENT_READ + ? kev_event[i].filter & EVFILT_READ + : kev_event[i].filter & EVFILT_WRITE); + } + + // Shutdown or kill in progress, indicate error. + if (kev_event[i].filter == EVFILT_USER) { + nev = -1; + break; + } + } + } + + MYSQL_END_SOCKET_WAIT(locker, 0); + DBUG_RETURN(nev); +} +#endif // HAVE_KQUEUE + +/** + Connect to a peer address. + + @param vio A VIO object. + @param addr Socket address containing the peer address. + @param len Length of socket address. + @param nonblocking flag to represent if socket is blocking or nonblocking + @param timeout Interval (in milliseconds) to wait until a + connection is established. + + @retval false A connection was successfully established. + @retval true A fatal error. See socket_errno. +*/ + +bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, + bool nonblocking, int timeout) { + int ret, wait; + int retry_count = 0; + DBUG_ENTER("vio_socket_connect"); + + /* Only for socket-based transport types. */ + DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); + + /* If timeout is not infinite, set socket to non-blocking mode. */ + if (((timeout > -1) || nonblocking) && vio_set_blocking(vio, false)) + DBUG_RETURN(true); + + /* Initiate the connection. */ + do { + ret = mysql_socket_connect(vio->mysql_socket, addr, len); + } while (ret < 0 && vio_should_retry(vio) && + (retry_count++ < vio->retry_count)); + +#ifdef _WIN32 + wait = (ret == SOCKET_ERROR) && (WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK); +#else + wait = (ret == -1) && (errno == EINPROGRESS || errno == EALREADY); +#endif + + /* + 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 (!nonblocking && wait && + (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) { + int error; + IF_WIN(int, socklen_t) optlen = sizeof(error); + IF_WIN(char, void) *optval = (IF_WIN(char, void) *)&error; + + /* + 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))) { +#ifdef _WIN32 + WSASetLastError(error); +#else + errno = error; +#endif + ret = MY_TEST(error); + } + } + + /* If necessary, restore the blocking mode, but only if connect succeeded. */ + if (!nonblocking && (timeout > -1) && (ret == 0)) { + if (vio_set_blocking(vio, true)) DBUG_RETURN(true); + } + + if (nonblocking && wait) { + DBUG_RETURN(false); + } else { + DBUG_RETURN(MY_TEST(ret)); + } +} + +/** + Determine if the endpoint of a connection is still available. + + @remark The socket is assumed to be disconnected if an EOF + condition is encountered. + + @param vio The VIO object. + + @retval true EOF condition not found. + @retval false EOF condition is signaled. +*/ + +bool vio_is_connected(Vio *vio) { + uint bytes = 0; + DBUG_ENTER("vio_is_connected"); + + /* + 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); + + /* + 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); + } + +#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); +} + +#ifndef DBUG_OFF + +/** + Number of bytes in the read or socket buffer + + @remark An EOF condition might count as one readable byte. + + @return number of bytes in one of the buffers or < 0 if error. +*/ + +ssize_t vio_pending(Vio *vio) { + uint bytes = 0; + + /* Data pending on the read buffer. */ + if (vio->read_pos < vio->read_end) return vio->read_end - vio->read_pos; + + /* 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 wolfSSL bug in SSL_pending that + causes it to attempt to read from the socket. + */ + + return (ssize_t)bytes; +} + +#endif + +/** + Checks if the error code, returned by vio_getnameinfo(), means it was the + "No-name" error. + + Windows-specific note: getnameinfo() returns WSANO_DATA instead of + EAI_NODATA or EAI_NONAME when no reverse mapping is available at the host + (i.e. Windows can't get hostname by IP-address). This error should be + treated as EAI_NONAME. + + @return if the error code is actually EAI_NONAME. + @retval true if the error code is EAI_NONAME. + @retval false otherwise. +*/ + +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 +} + +/** + This is a wrapper for the system getnameinfo(), because different OS + differ in the getnameinfo() implementation: + - Solaris 10 requires that the 2nd argument (salen) must match the + actual size of the struct sockaddr_storage passed to it; + - Mac OS X has sockaddr_in::sin_len and sockaddr_in6::sin6_len and + requires them to be filled. +*/ + +int vio_getnameinfo(const struct sockaddr *sa, char *hostname, + size_t hostname_size, char *port, size_t port_size, + int flags) { + int sa_length = 0; + + switch (sa->sa_family) { + case AF_INET: + sa_length = sizeof(struct sockaddr_in); +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *)sa)->sin_len = sa_length; +#endif /* HAVE_SOCKADDR_IN_SIN_LEN */ + break; + + case AF_INET6: + sa_length = sizeof(struct sockaddr_in6); +#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN + ((struct sockaddr_in6 *)sa)->sin6_len = sa_length; +#endif /* HAVE_SOCKADDR_IN6_SIN6_LEN */ + break; + } + + return getnameinfo(sa, sa_length, hostname, hostname_size, port, port_size, + flags); +} |