/************************************************************************************
   Copyright (C) 2015,2016 MariaDB Corporation AB,
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library 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
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not see <http://www.gnu.org/licenses>
   or write to the Free Software Foundation, Inc., 
   51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/

/* 
   MariaDB virtual IO plugin for socket communication:

   The plugin handles connections via unix and network sockets. it is enabled by
   default and compiled into Connector/C.
*/

#include <ma_global.h>
#include <ma_sys.h>
#include <errmsg.h>
#include <mysql.h>
#include <mysql/client_plugin.h>
#include <ma_context.h>
#include <mariadb_async.h>
#include <ma_common.h>
#include <string.h>
#include <time.h>
#ifndef _WIN32
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_POLL
#include <sys/poll.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <netinet/tcp.h>
#define IS_SOCKET_EINTR(err) (err == SOCKET_EINTR)
#else
#include <ws2tcpip.h>
#define O_NONBLOCK 1
#define MSG_DONTWAIT 0
#define IS_SOCKET_EINTR(err) 0
#endif

#ifndef SOCKET_ERROR
#define SOCKET_ERROR -1
#endif

#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif

#define DNS_TIMEOUT 30

#ifndef O_NONBLOCK
#if defined(O_NDELAY)
#define O_NONBLOCK O_NODELAY
#elif defined (O_FNDELAY)
#define O_NONBLOCK O_FNDELAY
#else
#error socket blocking is not supported on this platform
#endif
#endif

#if SOCKET_EAGAIN != SOCKET_EWOULDBLOCK
#define HAVE_SOCKET_EWOULDBLOCK 1
#endif

/* Function prototypes */
my_bool pvio_socket_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout);
int pvio_socket_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type);
ssize_t pvio_socket_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length);
ssize_t pvio_socket_async_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length);
ssize_t pvio_socket_async_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length);
ssize_t pvio_socket_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length);
int pvio_socket_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout);
int pvio_socket_blocking(MARIADB_PVIO *pvio, my_bool value, my_bool *old_value);
my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo);
my_bool pvio_socket_close(MARIADB_PVIO *pvio);
int pvio_socket_fast_send(MARIADB_PVIO *pvio);
int pvio_socket_keepalive(MARIADB_PVIO *pvio);
my_bool pvio_socket_get_handle(MARIADB_PVIO *pvio, void *handle);
my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio);
my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio);
my_bool pvio_socket_has_data(MARIADB_PVIO *pvio, ssize_t *data_len);
int pvio_socket_shutdown(MARIADB_PVIO *pvio);

static int pvio_socket_init(char *unused1, 
                           size_t unused2, 
                           int unused3, 
                           va_list);
static int pvio_socket_end(void);
static ssize_t ma_send(my_socket socket, const uchar *buffer, size_t length, int flags);
static ssize_t ma_recv(my_socket socket, uchar *buffer, size_t length, int flags);

struct st_ma_pvio_methods pvio_socket_methods= {
  pvio_socket_set_timeout,
  pvio_socket_get_timeout,
  pvio_socket_read,
  pvio_socket_async_read,
  pvio_socket_write,
  pvio_socket_async_write,
  pvio_socket_wait_io_or_timeout,
  pvio_socket_blocking,
  pvio_socket_connect,
  pvio_socket_close,
  pvio_socket_fast_send,
  pvio_socket_keepalive,
  pvio_socket_get_handle,
  pvio_socket_is_blocking,
  pvio_socket_is_alive,
  pvio_socket_has_data,
  pvio_socket_shutdown
};

#ifndef PLUGIN_DYNAMIC
MARIADB_PVIO_PLUGIN pvio_socket_client_plugin=
#else
MARIADB_PVIO_PLUGIN _mysql_client_plugin_declaration_
#endif
{
  MARIADB_CLIENT_PVIO_PLUGIN,
  MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION,
  "pvio_socket",
  "Georg Richter",
  "MariaDB virtual IO plugin for socket communication",
  {1, 0, 0},
  "LGPL",
  NULL,
  &pvio_socket_init,
  &pvio_socket_end,
  NULL,
  &pvio_socket_methods
};

struct st_pvio_socket {
  my_socket socket;
  int fcntl_mode;
  MYSQL *mysql;
};

static my_bool pvio_socket_initialized= FALSE;

static int pvio_socket_init(char *errmsg __attribute__((unused)),
                           size_t errmsg_length __attribute__((unused)), 
                           int unused __attribute__((unused)), 
                           va_list va __attribute__((unused)))
{
  pvio_socket_initialized= TRUE;
  return 0;
}

static int pvio_socket_end(void)
{
  if (!pvio_socket_initialized)
    return 1;
  return 0;
}

my_bool pvio_socket_change_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout)
{
  struct timeval tm;
  int rc= 0;
  struct st_pvio_socket *csock= NULL;
  if (!pvio)
    return 1;
  if (!(csock= (struct st_pvio_socket *)pvio->data))
    return 1;
  tm.tv_sec= timeout / 1000;
  tm.tv_usec= (timeout % 1000) * 1000;
  switch(type)
  {
    case PVIO_WRITE_TIMEOUT:
#ifndef _WIN32
      rc= setsockopt(csock->socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tm, sizeof(tm));
#else
      rc= setsockopt(csock->socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(int));
#endif
    break;
    case PVIO_READ_TIMEOUT:
#ifndef _WIN32
      rc= setsockopt(csock->socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tm, sizeof(tm));
#else
      rc= setsockopt(csock->socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(int));
#endif
    break;
    default:
    break;
  }
  return rc;
}

/* {{{ pvio_socket_set_timeout */
/*
   set timeout value

   SYNOPSIS
     pvio_socket_set_timeout
     pvio            PVIO
     type           timeout type (connect, read, write)
     timeout        timeout in seconds

   DESCRIPTION
     Sets timeout values for connection-, read or write time out.
     PVIO internally stores all timeout values in milliseconds, but 
     accepts and returns all time values in seconds (like api does).

   RETURNS
     0              Success
     1              Error
*/
my_bool pvio_socket_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout)
{
  struct st_pvio_socket *csock= NULL;
  if (!pvio)
    return 1;
  csock= (struct st_pvio_socket *)pvio->data;
  pvio->timeout[type]= (timeout > 0) ? timeout * 1000 : -1;
  if (csock)
    return pvio_socket_change_timeout(pvio, type, timeout * 1000);
  return 0;
}
/* }}} */

/* {{{ pvio_socket_get_timeout */
/*
   get timeout value

   SYNOPSIS
     pvio_socket_get_timeout
     pvio            PVIO
     type           timeout type (connect, read, write)

   DESCRIPTION
     Returns timeout values for connection-, read or write time out.
     PVIO internally stores all timeout values in milliseconds, but 
     accepts and returns all time values in seconds (like api does).

   RETURNS
      0...n         time out value
     -1             error
*/
int pvio_socket_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type)
{
  if (!pvio)
    return -1;
  return pvio->timeout[type] / 1000;
}
/* }}} */

/* {{{ pvio_socket_read */
/*
   read from socket

   SYNOPSIS
   pvio_socket_read()
     pvio             PVIO
     buffer          read buffer
     length          buffer length

   DESCRIPTION
     reads up to length bytes into specified buffer. In the event of an
     error erno is set to indicate it.

   RETURNS
      1..n           number of bytes read
      0              peer has performed shutdown
     -1              on error
                     
*/   
ssize_t pvio_socket_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
{
  ssize_t r;
  int read_flags= MSG_DONTWAIT;
  struct st_pvio_socket *csock;
  int timeout;

  if (!pvio || !pvio->data)
    return -1;

  csock= (struct st_pvio_socket *)pvio->data;
  timeout = pvio->timeout[PVIO_READ_TIMEOUT];

  while ((r = ma_recv(csock->socket, (void *)buffer, length, read_flags)) == -1)
  {
    int err = socket_errno;
    if ((err != SOCKET_EAGAIN
#ifdef HAVE_SOCKET_EWOULDBLOCK
      && err != SOCKET_EWOULDBLOCK
#endif
      ) || timeout == 0)
      return r;

    if (pvio_socket_wait_io_or_timeout(pvio, TRUE, timeout) < 1)
      return -1;
  }
  return r;
}
/* }}} */

/* {{{ pvio_socket_async_read */
/*
   read from socket

   SYNOPSIS
   pvio_socket_async_read()
     pvio             PVIO
     buffer          read buffer
     length          buffer length

   DESCRIPTION
     reads up to length bytes into specified buffer. In the event of an
     error erno is set to indicate it.

   RETURNS
      1..n           number of bytes read
      0              peer has performed shutdown
     -1              on error
                     
*/   
ssize_t pvio_socket_async_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
{
  ssize_t r= -1;
#ifndef _WIN32
  int read_flags= MSG_DONTWAIT;
#endif
  struct st_pvio_socket *csock= NULL;

  if (!pvio || !pvio->data)
    return -1;

  csock= (struct st_pvio_socket *)pvio->data;

#ifndef _WIN32
  r= recv(csock->socket,(void *)buffer, length, read_flags);
#else
  /* Windows doesn't support MSG_DONTWAIT, so we need to set
     socket to non blocking */
  pvio_socket_blocking(pvio, 0, 0);
  r= recv(csock->socket, (char *)buffer, (int)length, 0);
#endif
  return r;
}
/* }}} */

static ssize_t ma_send(my_socket socket, const uchar *buffer, size_t length, int flags)
{
  ssize_t r;
#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE) && !defined(_WIN32)
  struct sigaction act, oldact;
  act.sa_handler= SIG_IGN;
  sigaction(SIGPIPE, &act, &oldact);
#endif
  do {
    r = send(socket, (const char *)buffer, IF_WIN((int)length,length), flags);
  }
  while (r == -1 && IS_SOCKET_EINTR(socket_errno));
#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE) && !defined(_WIN32)
  sigaction(SIGPIPE, &oldact, NULL);
#endif
  return r;
}

static ssize_t ma_recv(my_socket socket, uchar *buffer, size_t length, int flags)
{
  ssize_t r;
  do {
   r = recv(socket, (char*) buffer, IF_WIN((int)length, length), flags);
  }
  while (r == -1 && IS_SOCKET_EINTR(socket_errno));
  return r;
}

/* {{{ pvio_socket_async_write */
/*
   write to socket

   SYNOPSIS
   pvio_socket_async_write()
     pvio             PVIO
     buffer          read buffer
     length          buffer length

   DESCRIPTION
     writes up to length bytes to socket. In the event of an
     error erno is set to indicate it.

   RETURNS
      1..n           number of bytes read
      0              peer has performed shutdown
     -1              on error
                     
*/   
ssize_t pvio_socket_async_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
{
  ssize_t r= -1;
  struct st_pvio_socket *csock= NULL;
#ifndef _WIN32
  int write_flags= MSG_DONTWAIT;
#ifdef MSG_NOSIGNAL
  write_flags|= MSG_NOSIGNAL;
#endif
#endif

  if (!pvio || !pvio->data)
    return -1;

  csock= (struct st_pvio_socket *)pvio->data;

#ifndef WIN32
  r= ma_send(csock->socket, buffer, length, write_flags);
#else
  /* Windows doesn't support MSG_DONTWAIT, so we need to set
     socket to non blocking */
  pvio_socket_blocking(pvio, 0, 0);
  r= send(csock->socket, (const char *)buffer, (int)length, 0);
#endif

  return r;
}
/* }}} */

/* {{{ pvio_socket_write */
/*
   write to socket

   SYNOPSIS
   pvio_socket_write()
     pvio             PVIO
     buffer          read buffer
     length          buffer length

   DESCRIPTION
     writes up to length bytes to socket. In the event of an
     error erno is set to indicate it.

   RETURNS
      1..n           number of bytes read
      0              peer has performed shutdown
     -1              on error
                     
*/
ssize_t pvio_socket_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
{
  ssize_t r;
  struct st_pvio_socket *csock;
  int timeout;
  int send_flags= MSG_DONTWAIT;
#ifdef MSG_NOSIGNAL
  send_flags|= MSG_NOSIGNAL;
#endif
  if (!pvio || !pvio->data)
    return -1;

  csock= (struct st_pvio_socket *)pvio->data;
  timeout = pvio->timeout[PVIO_WRITE_TIMEOUT];

  while ((r = ma_send(csock->socket, (void *)buffer, length,send_flags)) == -1)
  {
    int err = socket_errno;
    if ((err != SOCKET_EAGAIN
#ifdef HAVE_SOCKET_EWOULDBLOCK
      && err != SOCKET_EWOULDBLOCK
#endif
       )|| timeout == 0)
      return r;
    if (pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout) < 1)
      return -1;
  }
  return r;
}
/* }}} */

int pvio_socket_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout)
{
  int rc;
  struct st_pvio_socket *csock= NULL;

#ifndef _WIN32
  struct pollfd p_fd;
#else
  struct timeval tv= {0,0};
  fd_set fds, exc_fds;
#endif

  if (!pvio || !pvio->data)
    return 0;

  csock= (struct st_pvio_socket *)pvio->data;
  {
#ifndef _WIN32
    memset(&p_fd, 0, sizeof(p_fd));
    p_fd.fd= csock->socket;
    p_fd.events= (is_read) ? POLLIN : POLLOUT;

    if (!timeout)
      timeout= -1;

    do {
      rc= poll(&p_fd, 1, timeout);
    } while (rc == -1 && errno == EINTR);

    if (rc == 0)
      errno= ETIMEDOUT;
#else
    FD_ZERO(&fds);
    FD_ZERO(&exc_fds);

    FD_SET(csock->socket, &fds);
    FD_SET(csock->socket, &exc_fds);

    if (timeout >= 0)
    {
      tv.tv_sec= timeout / 1000;
      tv.tv_usec= (timeout % 1000) * 1000;
    }

    rc= select(0, (is_read) ? &fds : NULL,
                  (is_read) ? NULL : &fds,
                  &exc_fds, 
                  (timeout >= 0) ? &tv : NULL);

    if (rc == SOCKET_ERROR)
    {
      errno= WSAGetLastError();
    }
    else if (rc == 0)
    {
      rc= SOCKET_ERROR;
      WSASetLastError(WSAETIMEDOUT);
      errno= ETIMEDOUT;
    }
    else if (FD_ISSET(csock->socket, &exc_fds))
    {
      int err;
      int len = sizeof(int);
      if (getsockopt(csock->socket, SOL_SOCKET, SO_ERROR, (char *)&err, &len) != SOCKET_ERROR)
      {
        WSASetLastError(err);
        errno= err;
      }
      rc= SOCKET_ERROR;
    }
   
#endif
  }
  return rc;
}

int pvio_socket_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode)
{
  my_bool is_blocking;
  struct st_pvio_socket *csock;
  int new_fcntl_mode;

  if (!pvio || !pvio->data)
    return 1;

  csock = (struct st_pvio_socket *)pvio->data;

  is_blocking = !(csock->fcntl_mode & O_NONBLOCK);
  if (previous_mode)
    *previous_mode = is_blocking;

  if (is_blocking == block)
    return 0;

  if (block)
     new_fcntl_mode = csock->fcntl_mode & ~O_NONBLOCK;
  else
     new_fcntl_mode = csock->fcntl_mode | O_NONBLOCK;

#ifdef _WIN32
  {
    ulong arg = block ? 0 : 1;
    if (ioctlsocket(csock->socket, FIONBIO, (void *)&arg))
    {
      return(WSAGetLastError());
    }
  }
#else
  if (fcntl(csock->socket, F_SETFL, new_fcntl_mode) == -1)
  {
    return errno;
  }
#endif
  csock->fcntl_mode = new_fcntl_mode;
  return 0;
}

static int pvio_socket_internal_connect(MARIADB_PVIO *pvio,
                                       const struct sockaddr *name, 
                                       size_t namelen)
{
  int rc= 0;
  struct st_pvio_socket *csock= NULL;
  int timeout;

  if (!pvio || !pvio->data)
    return 1;

  csock= (struct st_pvio_socket *)pvio->data;
  timeout= pvio->timeout[PVIO_CONNECT_TIMEOUT];

  /* set non blocking */
  pvio_socket_blocking(pvio, 0, 0);

#ifndef _WIN32
  do {
    rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen);
  } while (rc == -1 && (errno == EINTR || errno == EAGAIN));
  /* in case a timeout values was set we need to check error values
     EINPROGRESS */
  if (timeout != 0 && rc == -1 && errno == EINPROGRESS)
  {
    rc= pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout);
    if (rc < 1)
      return -1;
    {
      int error;
      socklen_t error_len= sizeof(error);
      if ((rc = getsockopt(csock->socket, SOL_SOCKET, SO_ERROR, 
                           (char *)&error, &error_len)) < 0)
        return errno;
      else if (error) 
        return error;
    }
  }
#ifdef __APPLE__
  if (csock->socket)
  {
    int val= 1;
    setsockopt(csock->socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&val, sizeof(int));
  }
#endif
#else
  rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen);
  if (rc == SOCKET_ERROR)
  {
    if (WSAGetLastError() == WSAEWOULDBLOCK)
    {
      if (pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout) < 0)
        return -1;
      rc= 0;
    }
  }
#endif
  return rc;
}

int pvio_socket_keepalive(MARIADB_PVIO *pvio)
{
  int opt= 1;
  struct st_pvio_socket *csock= NULL;

  if (!pvio || !pvio->data)
    return 1;

  csock= (struct st_pvio_socket *)pvio->data;

  return setsockopt(csock->socket, SOL_SOCKET, SO_KEEPALIVE,
#ifndef _WIN32
               (const void *)&opt, sizeof(opt));
#else
               (char *)&opt, (int)sizeof(opt));
#endif
}

int pvio_socket_fast_send(MARIADB_PVIO *pvio)
{
  int r= 0;
  struct st_pvio_socket *csock= NULL;

  if (!pvio || !pvio->data)
    return 1;

  csock= (struct st_pvio_socket *)pvio->data;

/* Setting IP_TOS is not recommended on Windows. See 
   http://msdn.microsoft.com/en-us/library/windows/desktop/ms738586(v=vs.85).aspx
*/
#if !defined(_WIN32) && defined(IPTOS_THROUGHPUT)
  {
    int tos = IPTOS_THROUGHPUT;
    r= setsockopt(csock->socket, IPPROTO_IP, IP_TOS,
	                         (const void *)&tos, sizeof(tos));
  }
#endif /* !_WIN32 && IPTOS_THROUGHPUT */
  if (!r)
  {
    int opt = 1;
    /* turn off nagle algorithm */
    r= setsockopt(csock->socket, IPPROTO_TCP, TCP_NODELAY,
#ifdef _WIN32
                  (const char *)&opt, (int)sizeof(opt));
#else
                  (const void *)&opt, sizeof(opt));
#endif
  }
  return r;
}

static int
pvio_socket_connect_sync_or_async(MARIADB_PVIO *pvio,
                          const struct sockaddr *name, uint namelen)
{
  MYSQL *mysql= pvio->mysql;
  if (mysql->options.extension && mysql->options.extension->async_context &&
      mysql->options.extension->async_context->active)
  {
    /* even if we are not connected yet, application needs to check socket
     * via mysql_get_socket api call, so we need to assign pvio */
    mysql->options.extension->async_context->pvio= pvio;
    pvio_socket_blocking(pvio, 0, 0);
    return my_connect_async(pvio, name, namelen, pvio->timeout[PVIO_CONNECT_TIMEOUT]);
  }

  return pvio_socket_internal_connect(pvio, name, namelen);
}

my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
{
  struct st_pvio_socket *csock= NULL;
  MYSQL *mysql;

  if (!pvio || !cinfo)
    return 1;

  if (!(csock= (struct st_pvio_socket *)calloc(1, sizeof(struct st_pvio_socket))))
  {
    PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, "");
    return 1;
  }
  pvio->data= (void *)csock;
  csock->socket= INVALID_SOCKET;
  mysql= pvio->mysql= cinfo->mysql;
  pvio->type= cinfo->type;

  if (cinfo->type == PVIO_TYPE_UNIXSOCKET)
  {
#ifndef _WIN32
#ifdef HAVE_SYS_UN_H
    size_t port_length;
    struct sockaddr_un UNIXaddr;
    if ((csock->socket = socket(AF_UNIX,SOCK_STREAM,0)) == INVALID_SOCKET ||
        (port_length=strlen(cinfo->unix_socket)) >= (sizeof(UNIXaddr.sun_path)))
    {
      PVIO_SET_ERROR(cinfo->mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, 0, errno);
      goto error;
    }
    memset((char*) &UNIXaddr, 0, sizeof(UNIXaddr));
    UNIXaddr.sun_family = AF_UNIX;
#if defined(__linux__)
    /* Abstract socket */
    if (cinfo->unix_socket[0] == '@')
    {
      strncpy(UNIXaddr.sun_path + 1, cinfo->unix_socket + 1, 107);
      port_length+= offsetof(struct sockaddr_un, sun_path);
    }
    else
#endif
    {
      strcpy(UNIXaddr.sun_path, cinfo->unix_socket);
      port_length= sizeof(UNIXaddr);
    }
    if (pvio_socket_connect_sync_or_async(pvio, (struct sockaddr *) &UNIXaddr, port_length))
    {
      PVIO_SET_ERROR(cinfo->mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 
                    ER(CR_CONNECTION_ERROR), cinfo->unix_socket, socket_errno);
      goto error;
    }
    if (pvio_socket_blocking(pvio, 1, 0) == SOCKET_ERROR)
    {
      goto error;
    }
#else
/* todo: error, not supported */
#endif
#endif
  } else if (cinfo->type == PVIO_TYPE_SOCKET)
  {
    struct addrinfo hints, *save_res= 0, *bind_res= 0, *res= 0, *bres= 0;
    char server_port[NI_MAXSERV];
    int gai_rc;
    int rc= 0;
    time_t start_t= time(NULL);
#ifdef _WIN32
    DWORD wait_gai;
#else
    unsigned int wait_gai;
#endif

    memset(&server_port, 0, NI_MAXSERV);
    snprintf(server_port, NI_MAXSERV, "%d", cinfo->port);

    /* set hints for getaddrinfo */
    memset(&hints, 0, sizeof(hints));
    hints.ai_protocol= IPPROTO_TCP; /* TCP connections only */
    hints.ai_family= AF_UNSPEC;     /* includes: IPv4, IPv6 or hostname */
    hints.ai_socktype= SOCK_STREAM;

    /* if client has multiple interfaces, we will bind socket to given
     * bind_address */
    if (cinfo->mysql->options.bind_address)
    {
      wait_gai= 1;
      while ((gai_rc= getaddrinfo(cinfo->mysql->options.bind_address, 0,
                          &hints, &bind_res)) == EAI_AGAIN)
      {
        unsigned int timeout= mysql->options.connect_timeout ?
                              mysql->options.connect_timeout : DNS_TIMEOUT;
        if (time(NULL) - start_t > (time_t)timeout)
          break;
#ifndef _WIN32
        usleep(wait_gai);
#else
        Sleep(wait_gai);
#endif
        wait_gai*= 2;
      }
      if (gai_rc != 0 || !bind_res)
      {
        PVIO_SET_ERROR(cinfo->mysql, CR_BIND_ADDR_FAILED, SQLSTATE_UNKNOWN, 
                     CER(CR_BIND_ADDR_FAILED), cinfo->mysql->options.bind_address, gai_rc);
        goto error;
      }
    }
    /* Get the address information for the server using getaddrinfo() */
    wait_gai= 1;
    while ((gai_rc= getaddrinfo(cinfo->host, server_port,
                                &hints, &res)) == EAI_AGAIN)
    {
      unsigned int timeout= mysql->options.connect_timeout ?
                            mysql->options.connect_timeout : DNS_TIMEOUT;
      if (time(NULL) - start_t > (time_t)timeout)
        break;
#ifndef _WIN32
      usleep(wait_gai);
#else
      Sleep(wait_gai);
#endif
      wait_gai*= 2;
    }
    if (gai_rc != 0 || !res)
    {
      PVIO_SET_ERROR(cinfo->mysql, CR_UNKNOWN_HOST, SQLSTATE_UNKNOWN, 
                   ER(CR_UNKNOWN_HOST), cinfo->host, gai_rc);
      if (bind_res)
        freeaddrinfo(bind_res);
      goto error;
    }

    /* res is a linked list of addresses for the given hostname. We loop until
       we are able to connect to one address or all connect attempts failed */
    for (save_res= res; save_res; save_res= save_res->ai_next)
    {
      /* CONC-364: Avoid leak of open sockets */
      if (csock->socket != INVALID_SOCKET)
        closesocket(csock->socket);
      csock->socket= socket(save_res->ai_family, save_res->ai_socktype, 
                            save_res->ai_protocol);
      if (csock->socket == INVALID_SOCKET)
        /* Errors will be handled after loop finished */
        continue;

      if (bind_res)
      {
        for (bres= bind_res; bres; bres= bres->ai_next)
        {
          if (!(rc= bind(csock->socket, bres->ai_addr, (int)bres->ai_addrlen)))
            break;
        }
        if (rc)
        {
          closesocket(csock->socket);
          csock->socket= INVALID_SOCKET;
          continue;
        }
      }

      rc= pvio_socket_connect_sync_or_async(pvio, save_res->ai_addr, (uint)save_res->ai_addrlen);
      if (!rc)
      {
        MYSQL *mysql= pvio->mysql;
        if (mysql->options.extension && mysql->options.extension->async_context &&
             mysql->options.extension->async_context->active)
          break;
        if (pvio_socket_blocking(pvio, 0, 0) == SOCKET_ERROR)
        {
          closesocket(csock->socket);
          csock->socket= INVALID_SOCKET;
          continue;
        }
        break; /* success! */
      }
    }
 
    freeaddrinfo(res);
    if (bind_res)
      freeaddrinfo(bind_res);

    if (csock->socket == INVALID_SOCKET)
    {
      PVIO_SET_ERROR(cinfo->mysql, CR_IPSOCK_ERROR, SQLSTATE_UNKNOWN, ER(CR_IPSOCK_ERROR),
                         socket_errno);
      goto error;
    }

    /* last call to connect 2 failed */
    if (rc)
    {
      PVIO_SET_ERROR(cinfo->mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
                     ER(CR_CONN_HOST_ERROR), cinfo->host,
#ifdef _WIN32
                     errno);
#else
                     socket_errno);
#endif
      goto error;
    }
    if (pvio_socket_blocking(pvio, 1, 0) == SOCKET_ERROR)
      goto error;
  }
  /* apply timeouts */
  if (pvio->timeout[PVIO_CONNECT_TIMEOUT] > 0)
  {
    if (pvio_socket_change_timeout(pvio, PVIO_READ_TIMEOUT, pvio->timeout[PVIO_CONNECT_TIMEOUT]) ||
        pvio_socket_change_timeout(pvio, PVIO_WRITE_TIMEOUT, pvio->timeout[PVIO_CONNECT_TIMEOUT]))
      goto error;
  }
  else
  {
    if (pvio->timeout[PVIO_WRITE_TIMEOUT] > 0)
      if (pvio_socket_change_timeout(pvio, PVIO_WRITE_TIMEOUT, pvio->timeout[PVIO_WRITE_TIMEOUT]))
        goto error;
    if (pvio->timeout[PVIO_READ_TIMEOUT] > 0)
      if (pvio_socket_change_timeout(pvio, PVIO_READ_TIMEOUT, pvio->timeout[PVIO_READ_TIMEOUT]))
        goto error;
  }
  return 0;
error:
  /* close socket: MDEV-10891 */
  if (csock->socket != INVALID_SOCKET)
  {
    closesocket(csock->socket);
    csock->socket= INVALID_SOCKET;
  }
  if (pvio->data)
  {
    free((gptr)pvio->data);
    pvio->data= NULL;
  }
  return 1;
}

/* {{{ my_bool pvio_socket_close() */
my_bool pvio_socket_close(MARIADB_PVIO *pvio)
{
  struct st_pvio_socket *csock= NULL;
  int r= 0;

  if (!pvio)
    return 1;

  if (pvio->data)
  {
    csock= (struct st_pvio_socket *)pvio->data;
    if (csock && csock->socket != INVALID_SOCKET)
    {
      r= closesocket(csock->socket);
      csock->socket= INVALID_SOCKET;
    }
    free((gptr)pvio->data);
    pvio->data= NULL;
  }
  return r;
}
/* }}} */

/* {{{ my_socket pvio_socket_get_handle */
my_bool pvio_socket_get_handle(MARIADB_PVIO *pvio, void *handle)
{
  if (pvio && pvio->data && handle)
  {
    *(my_socket *)handle= ((struct st_pvio_socket *)pvio->data)->socket;
    return 0;
  }
  return 1;
}
/* }}} */

/* {{{ my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio) */
my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio)
{
  struct st_pvio_socket *csock= NULL;
  my_bool r;

  if (!pvio || !pvio->data)
    return 0;

  csock= (struct st_pvio_socket *)pvio->data;
  r = !(csock->fcntl_mode & O_NONBLOCK);
  return r;
}
/* }}} */

/* {{{ my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio) */
my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio)
{
  struct st_pvio_socket *csock= NULL;
 #ifndef _WIN32
  struct pollfd poll_fd;
#else
  FD_SET sfds;
  struct timeval tv= {0,0};
#endif
  int res;

  if (!pvio || !pvio->data)
    return 0;

  csock= (struct st_pvio_socket *)pvio->data;
#ifndef _WIN32
  memset(&poll_fd, 0, sizeof(struct pollfd));
  poll_fd.events= POLLPRI | POLLIN;
  poll_fd.fd= csock->socket;

  res= poll(&poll_fd, 1, 0);
  if (res <= 0) /* timeout or error */
    return FALSE;
  if (!(poll_fd.revents & (POLLIN | POLLPRI)))
    return FALSE;
  return TRUE;
#else
  /* We can't use the WSAPoll function, it's broken :-(
     (see Windows 8 Bugs 309411 - WSAPoll does not report failed connections)
     Instead we need to use select function:
     If TIMEVAL is initialized to {0, 0}, select will return immediately; 
     this is used to poll the state of the selected sockets.
  */
  FD_ZERO(&sfds);
  FD_SET(csock->socket, &sfds);

  res= select((int)csock->socket + 1, &sfds, NULL, NULL, &tv);
  if (res > 0 && FD_ISSET(csock->socket, &sfds))
    return TRUE;
  return FALSE;
#endif
}
/* }}} */

/* {{{ my_boool pvio_socket_has_data */
my_bool pvio_socket_has_data(MARIADB_PVIO *pvio, ssize_t *data_len)
{
  struct st_pvio_socket *csock= NULL;
  char tmp_buf;
  ssize_t len;
  my_bool mode;
 
  if (!pvio || !pvio->data)
    return 0;

  csock= (struct st_pvio_socket *)pvio->data;
  /* MSG_PEEK: Peeks at the incoming data. The data is copied into the buffer,
      but is not removed from the input queue. 
  */
  pvio_socket_blocking(pvio, 0, &mode);
  len= recv(csock->socket, &tmp_buf, sizeof(tmp_buf), MSG_PEEK);
  pvio_socket_blocking(pvio, mode, 0);
  if (len < 0)
    return 1;
  *data_len= len;
  return 0;
}
/* }}} */

int pvio_socket_shutdown(MARIADB_PVIO *pvio)
{
  if (pvio && pvio->data)
  {
    my_socket s = ((struct st_pvio_socket *)pvio->data)->socket;
#ifdef _WIN32
    shutdown(s, SD_BOTH);
    CancelIoEx((HANDLE)s, NULL);
#else
    shutdown(s, SHUT_RDWR);
#endif
  }
  return -1;
}
