From 06eaf7232e9a920468c0f8d74dcf2fe8b555501c Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sat, 13 Apr 2024 14:24:36 +0200
Subject: Adding upstream version 1:10.11.6.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 vio/viosocket.c | 1301 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1301 insertions(+)
 create mode 100644 vio/viosocket.c

(limited to 'vio/viosocket.c')

diff --git a/vio/viosocket.c b/vio/viosocket.c
new file mode 100644
index 00000000..002ff274
--- /dev/null
+++ b/vio/viosocket.c
@@ -0,0 +1,1301 @@
+/*
+   Copyright (c) 2001, 2012, Oracle and/or its affiliates
+   Copyright (c) 2012, Monty Program Ab
+
+   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-1335  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 "vio_priv.h"
+#ifdef _WIN32
+  #include <winsock2.h>
+  #include <MSWSock.h>
+  #include <mstcpip.h>
+  #pragma comment(lib, "ws2_32.lib")
+#endif
+
+#ifdef FIONREAD_IN_SYS_FILIO
+# 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)))
+{
+  /* These transport types are not Winsock based. */
+#ifdef _WIN32
+  if (vio->type == VIO_TYPE_NAMEDPIPE)
+    return GetLastError();
+#endif
+
+  /* Mapped to WSAGetLastError() on Win32. */
+  return socket_errno;
+}
+
+static int vio_set_linger(my_socket s, unsigned short timeout_sec)
+{
+  struct linger s_linger;
+  int ret;
+  s_linger.l_onoff = 1;
+  s_linger.l_linger = timeout_sec;
+  ret = setsockopt(s, SOL_SOCKET, SO_LINGER, (const char *)&s_linger, (int)sizeof(s_linger));
+  return ret;
+}
+
+
+/**
+  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;
+    vio_set_linger(vio->mysql_socket.fd, 0);
+    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
+
+#ifndef SOCKET_EAGAIN
+#define SOCKET_EAGAIN SOCKET_EWOULDBLOCK
+#endif
+
+/*
+  returns number of bytes read or -1 in case of an error
+*/
+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: %p  size: %zu",
+                       (int)mysql_socket_getfd(vio->mysql_socket), buf,
+                       size));
+
+  /* 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;
+
+    /* 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 (ret == -1)
+  {
+    DBUG_PRINT("vio_error", ("Got error %d during read", errno));
+  }
+#ifndef DEBUG_DATA_PACKETS
+  else
+  {
+    DBUG_DUMP("read_data", buf, ret);
+  }
+#endif /* DEBUG_DATA_PACKETS */
+#endif /* DBUG_OFF */
+  DBUG_PRINT("exit", ("%d", (int) ret));
+  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:%zu",
+                       (int)mysql_socket_getfd(vio->mysql_socket),
+                       buf, 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
+}
+
+
+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)
+{
+  ssize_t ret;
+  int flags= 0;
+  DBUG_ENTER("vio_write");
+  DBUG_PRINT("enter", ("sd: %d  buf: %p  size: %zu",
+                       (int)mysql_socket_getfd(vio->mysql_socket), buf,
+                       size));
+
+  /* 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 (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 (ret == -1)
+  {
+    DBUG_PRINT("vio_error", ("Got error on write: %d",socket_errno));
+  }
+#endif /* DBUG_OFF */
+  DBUG_PRINT("exit", ("%d", (int) ret));
+  DBUG_RETURN(ret);
+}
+
+int vio_socket_shutdown(Vio *vio, int 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) CancelIoEx((HANDLE)mysql_socket_getfd(vio->mysql_socket), NULL);
+#endif
+  return ret;
+}
+
+
+int vio_blocking(Vio *vio, my_bool set_blocking_mode, my_bool *old_mode)
+{
+  int r= 0;
+#if defined(_WIN32) || !defined(NO_FCNTL_NONBLOCK)
+  my_socket sd= mysql_socket_getfd(vio->mysql_socket);
+#endif
+  DBUG_ENTER("vio_blocking");
+
+  *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(_WIN32)
+#if !defined(NO_FCNTL_NONBLOCK)
+  if (sd >= 0)
+  {
+    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(sd, F_SETFL, vio->fcntl_mode);
+      if (r == -1)
+      {
+        DBUG_PRINT("info", ("fcntl failed, errno %d", errno));
+        vio->fcntl_mode= old_fcntl;
+      }
+    }
+  }
+#else
+  r= set_blocking_mode ? 0 : 1;
+#endif /* !defined(NO_FCNTL_NONBLOCK) */
+#else /* !defined(_WIN32) */
+  if (vio->type != VIO_TYPE_NAMEDPIPE)
+  { 
+    ulong arg;
+    int old_fcntl=vio->fcntl_mode;
+    if (set_blocking_mode)
+    {
+      arg = 0;
+      vio->fcntl_mode &= ~O_NONBLOCK; /* clear bit */
+    }
+    else
+    {
+      arg = 1;
+      vio->fcntl_mode |= O_NONBLOCK; /* set bit */
+    }
+    if (old_fcntl != vio->fcntl_mode)
+      r = ioctlsocket(sd,FIONBIO,(void*) &arg);
+  }
+  else
+    r=  MY_TEST(!(vio->fcntl_mode & O_NONBLOCK)) != set_blocking_mode;
+#endif /* !defined(_WIN32) */
+  DBUG_PRINT("exit", ("%d", r));
+  DBUG_RETURN(r);
+}
+
+/*
+  Check if vio is blocking
+
+  @retval 0  is not blocking
+  @retval 1  is blocking
+*/
+
+my_bool
+vio_is_blocking(Vio * vio)
+{
+  my_bool r;
+  DBUG_ENTER("vio_is_blocking");
+  r = !(vio->fcntl_mode & O_NONBLOCK);
+  DBUG_PRINT("exit", ("%d", (int) r));
+  DBUG_RETURN(r);
+}
+
+
+int vio_socket_timeout(Vio *vio,
+                       uint which __attribute__((unused)),
+                       my_bool old_mode __attribute__((unused)))
+{
+  int ret= 0;
+  DBUG_ENTER("vio_socket_timeout");
+  /*
+    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, &not_used);
+  }
+
+  DBUG_RETURN(ret);
+}
+
+/* Set TCP_NODELAY (disable Nagle's algorithm */
+int vio_nodelay(Vio *vio, my_bool on)
+{
+  int r;
+  int no_delay= MY_TEST(on);
+  DBUG_ENTER("vio_nodelay");
+
+  if (vio->type == VIO_TYPE_NAMEDPIPE || vio->type == VIO_TYPE_SOCKET)
+  {
+    DBUG_RETURN(0);
+  }
+
+  r = mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_NODELAY,
+    IF_WIN((const char*), (void*)) &no_delay,
+    sizeof(no_delay));
+
+  if (r)
+  {
+    DBUG_PRINT("warning",
+     ("Couldn't set socket option for fast send, error %d",
+      socket_errno));
+     r = -1;
+  }
+  DBUG_PRINT("exit", ("%d", r));
+  DBUG_RETURN(r);
+}
+
+int vio_fastsend(Vio * vio)
+{
+  int r=0;
+  DBUG_ENTER("vio_fastsend");
+
+  if (vio->type == VIO_TYPE_NAMEDPIPE)
+  {
+    DBUG_RETURN(0);
+  }
+
+#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)
+    r = vio_nodelay(vio, TRUE);
+  if (r)
+  {
+    DBUG_PRINT("warning",
+               ("Couldn't set socket option for fast send, error %d",
+                socket_errno));
+    r= -1;
+  }
+  DBUG_PRINT("exit", ("%d", r));
+  DBUG_RETURN(r);
+}
+
+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",
+                       (int)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);
+}
+
+/*
+  Set socket options for keepalive e.g., TCP_KEEPCNT, TCP_KEEPIDLE/TCP_KEEPALIVE, TCP_KEEPINTVL
+*/
+int vio_set_keepalive_options(Vio* vio, const struct vio_keepalive_opts *opts)
+{
+#if defined _WIN32
+  struct tcp_keepalive s;
+  DWORD  nbytes;
+
+  if (vio->type == VIO_TYPE_NAMEDPIPE)
+    return 0;
+
+  if (!opts->idle && !opts->interval)
+    return 0;
+
+  s.onoff= 1;
+  s.keepalivetime= opts->idle? opts->idle * 1000 : 7200;
+  s.keepaliveinterval= opts->interval?opts->interval * 1000 : 1;
+
+  return WSAIoctl(vio->mysql_socket.fd, SIO_KEEPALIVE_VALS, (LPVOID) &s, sizeof(s),
+           NULL, 0, &nbytes, NULL, NULL);
+
+#elif defined (TCP_KEEPIDLE) || defined (TCP_KEEPALIVE)
+
+  int ret= 0;
+  if (opts->idle)
+  {
+#ifdef TCP_KEEPIDLE // Linux only
+    ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPIDLE, (char *)&opts->idle, sizeof(opts->idle));
+#elif defined (TCP_KEEPALIVE)
+    ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPALIVE, (char *)&opts->idle, sizeof(opts->idle));
+#endif
+    if(ret)
+      return ret;
+  }
+
+#ifdef TCP_KEEPCNT // Linux only
+  if(opts->probes)
+  {
+    ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPCNT, (char *)&opts->probes, sizeof(opts->probes));
+    if(ret)
+      return ret;
+  }
+#endif
+
+#ifdef TCP_KEEPINTVL  // Linux only
+  if(opts->interval)
+  {
+    ret= mysql_socket_setsockopt(vio->mysql_socket, IPPROTO_TCP, TCP_KEEPINTVL, (char *)&opts->interval, sizeof(opts->interval));
+  }
+#endif
+  return ret;
+#else /*TCP_KEEPIDLE || TCP_KEEPALIVE */
+  return -1; 
+#endif
+}
+
+
+/**
+  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_should_retry(Vio *vio)
+{
+  DBUG_ENTER("vio_should_retry");
+  DBUG_PRINT("info", ("vio_errno: %d", vio_errno(vio)));
+  DBUG_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.
+*/
+
+my_bool
+vio_was_timeout(Vio *vio)
+{
+  return (vio_errno(vio) == SOCKET_ETIMEDOUT);
+}
+
+
+int vio_close(Vio *vio)
+{
+  int r=0;
+  DBUG_ENTER("vio_close");
+  DBUG_PRINT("enter", ("sd: %d", (int)mysql_socket_getfd(vio->mysql_socket)));
+
+  if (vio->type != VIO_CLOSED)
+  {
+    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_close(vio->mysql_socket))
+      r= -1;
+  }
+  if (r)
+  {
+    DBUG_PRINT("vio_error", ("close() failed, error: %d",socket_errno));
+    /* FIXME: error handling (not critical for MySQL) */
+  }
+  vio->type= VIO_CLOSED;
+  vio->mysql_socket= MYSQL_INVALID_SOCKET;
+  DBUG_RETURN(r);
+}
+
+
+const char *vio_description(Vio * vio)
+{
+  return vio->desc;
+}
+
+enum enum_vio_type vio_type(Vio* vio)
+{
+  return vio->type;
+}
+
+static const LEX_CSTRING vio_type_names[] =
+{
+  { STRING_WITH_LEN("") }, // internal threads
+  { STRING_WITH_LEN("TCP/IP") },
+  { STRING_WITH_LEN("Socket") },
+  { STRING_WITH_LEN("Named Pipe") },
+  { STRING_WITH_LEN("SSL/TLS") },
+  { STRING_WITH_LEN("Shared Memory") }
+};
+
+const char *vio_type_name(enum enum_vio_type vio_type, size_t *len)
+{
+  int index= vio_type >= FIRST_VIO_TYPE && vio_type <= LAST_VIO_TYPE
+             ?  vio_type : 0;
+
+  *len= vio_type_names[index].length;
+  return vio_type_names[index].str;
+}
+
+
+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: ::a.b.c.d;
+    - IPv4-mapped IPv4: ::ffff:a.b.c.d;
+
+  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 src        [in] source IP address (AF_INET or AF_INET6).
+  @param src_length [in] length of the src.
+  @param dst        [out] a buffer to store normalized IP address
+                          (sockaddr_storage).
+  @param dst_length [out] optional - actual length of the normalized IP address.
+*/
+
+void vio_get_normalized_ip(const struct sockaddr *src, size_t src_length,
+                                  struct sockaddr *dst)
+{
+  switch (src->sa_family) {
+  case AF_INET:
+    memcpy(dst, src, src_length);
+    break;
+
+#ifdef HAVE_IPV6
+  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= (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.
+      */
+
+      memset(dst_ip4, 0, sizeof (struct sockaddr_in));
+      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);
+    }
+
+    break;
+  }
+#endif /* HAVE_IPV6 */
+  }
+}
+
+
+/**
+  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 addr           [in]  sockaddr object (AF_INET or AF_INET6).
+  @param addr_length    [in]  length of the addr.
+  @param ip_string      [out] buffer to write normalized IP address.
+  @param ip_string_size [in]  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.
+*/
+
+my_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;
+  int err_code;
+
+  vio_get_normalized_ip(addr, addr_length, norm_addr);
+
+  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.
+*/
+
+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)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;
+
+    ip4->s_addr= htonl(INADDR_LOOPBACK);
+
+    /* Initialize ip_buffer and port. */
+
+    strmov(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;
+    size_socket 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);
+
+    /* 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 bytes[out]   The amount of bytes available.
+
+  @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)
+  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 /*_WIN32*/
+}
+
+#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 /* _WIN32 */
+
+/**
+  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 socket_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.
+*/
+
+#ifndef _WIN32
+int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout)
+{
+  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");
+  DBUG_PRINT("enter", ("timeout: %d", timeout));
+
+  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;
+    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;
+  }
+
+  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)))
+  {
+  case -1:
+    DBUG_PRINT("error", ("poll returned -1"));
+    /* On error, -1 is returned. */
+    break;
+  case 0:
+    /*
+      Set errno to indicate a timeout error.
+      (This is not compiled in on WIN32.)
+    */
+    DBUG_PRINT("info", ("poll timeout"));
+    errno= SOCKET_ETIMEDOUT;
+    break;
+  default:
+    /* Ensure that the requested I/O event has completed. */
+    DBUG_ASSERT(pfd.revents & revents);
+    break;
+  }
+
+  END_SOCKET_WAIT(locker, timeout);
+  DBUG_RETURN(ret);
+}
+
+#else
+
+int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout)
+{
+  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)
+  {
+    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;
+  }
+
+  START_SOCKET_WAIT(locker, &state, vio->mysql_socket, PSI_SOCKET_SELECT, timeout);
+
+  /* The first argument is ignored on Windows. */
+  ret= select(0, &readfds, &writefds, &exceptfds, (timeout >= 0) ? &tm : NULL);
+
+  END_SOCKET_WAIT(locker, timeout);
+
+  /* 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);
+}
+
+#endif /* _WIN32 */
+
+
+/**
+  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 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.
+*/
+
+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");
+
+  /* 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) && vio_blocking(vio, FALSE, &not_used))
+    DBUG_RETURN(TRUE);
+
+  /* Initiate the connection. */
+  ret= mysql_socket_connect(vio->mysql_socket, addr, len);
+
+#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 (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 ((timeout > -1) && (ret == 0))
+  {
+    my_bool not_used;
+    if (vio_blocking(vio, TRUE, &not_used))
+      DBUG_RETURN(TRUE);
+  }
+
+  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.
+*/
+
+my_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);
+}
+
+
+/**
+  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. */
+  switch (vio->type)
+  {
+  case VIO_TYPE_TCPIP:
+    /* fallthrough */
+  case VIO_TYPE_SOCKET:
+    /* Obtain number of readable bytes in the socket buffer. */
+    if (socket_peek_read(vio, &bytes))
+      return -1;
+    return bytes;
+
+  case VIO_TYPE_SSL:
+    bytes= (uint) SSL_pending(vio->ssl_arg);
+    if (bytes)
+      return bytes;
+    if (socket_peek_read(vio, &bytes))
+      return -1;
+    return bytes;
+
+#ifdef _WIN32
+  case VIO_TYPE_NAMEDPIPE:
+    bytes= vio_pending_pipe(vio);
+    return bytes;
+#endif
+  default:
+    return -1;
+  }
+}
+
+
+/**
+  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.
+*/
+
+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
+}
+
+
+/**
+  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;
+
+#ifdef HAVE_IPV6
+  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;
+#endif /* HAVE_IPV6 */
+  }
+
+  return getnameinfo(sa, sa_length,
+                     hostname, (uint)hostname_size,
+                     port, (uint)port_size,
+                     flags);
+}
-- 
cgit v1.2.3