/*
   Copyright (c) 2013, 2021, Oracle and/or its affiliates.

   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.

   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
*/

#include "socket_connection.h"

#include "violite.h"                    // Vio
#include "channel_info.h"               // Channel_info
#include "connection_handler_manager.h" // Connection_handler_manager
#include "mysqld.h"                     // key_socket_tcpip
#include "log.h"                        // sql_print_error
#include "sql_class.h"                  // THD

#include <pfs_idle_provider.h>
#include <mysql/psi/mysql_idle.h>

#include <algorithm>
#include <signal.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_LIBWRAP
#ifndef HAVE_LIBWRAP_PROTOTYPES
extern "C" {
#include <tcpd.h>
}
#else
#include <tcpd.h>
#endif
#include <syslog.h>
#endif

using std::max;

ulong Mysqld_socket_listener::connection_errors_select= 0;
ulong Mysqld_socket_listener::connection_errors_accept= 0;
ulong Mysqld_socket_listener::connection_errors_tcpwrap= 0;


void net_before_header_psi(struct st_net *net, void *user_data, size_t /* unused: count */)
{
  THD *thd;
  thd= static_cast<THD*> (user_data);
  assert(thd != NULL);

  if (thd->m_server_idle)
  {
    /*
      The server is IDLE, waiting for the next command.
      Technically, it is a wait on a socket, which may take a long time,
      because the call is blocking.
      Disable the socket instrumentation, to avoid recording a SOCKET event.
      Instead, start explicitly an IDLE event.
    */
    MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_IDLE);
    MYSQL_START_IDLE_WAIT(thd->m_idle_psi, &thd->m_idle_state);
  }
}

void net_after_header_psi(struct st_net *net, void *user_data, size_t /* unused: count */, my_bool rc)
{
  THD *thd;
  thd= static_cast<THD*> (user_data);
  assert(thd != NULL);

  if (thd->m_server_idle)
  {
    /*
      The server just got data for a network packet header,
      from the network layer.
      The IDLE event is now complete, since we now have a message to process.
      We need to:
      - start a new STATEMENT event
      - start a new STAGE event, within this statement,
      - start recording SOCKET WAITS events, within this stage.
      The proper order is critical to get events numbered correctly,
      and nested in the proper parent.
    */
    MYSQL_END_IDLE_WAIT(thd->m_idle_psi);

    if (! rc)
    {
      assert(thd->m_statement_psi == NULL);
      thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
                                                  stmt_info_new_packet.m_key,
                                                  thd->db().str,
                                                  thd->db().length,
                                                  thd->charset(), NULL);

      /*
        Starts a new stage in performance schema, if compiled in and enabled.
        Also sets THD::proc_info (used by SHOW PROCESSLIST, column STATE)
      */
      THD_STAGE_INFO(thd, stage_starting);
    }

    /*
      TODO: consider recording a SOCKET event for the bytes just read,
      by also passing count here.
    */
    MYSQL_SOCKET_SET_STATE(net->vio->mysql_socket, PSI_SOCKET_STATE_ACTIVE);
    thd->m_server_idle= false;
  }
}


static void init_net_server_extension(THD *thd)
{
  /* Start with a clean state for connection events. */
  thd->m_idle_psi= NULL;
  thd->m_statement_psi= NULL;
  thd->m_server_idle= false;

  /* Hook up the NET_SERVER callback in the net layer. */
  thd->m_net_server_extension.m_user_data= thd;
  thd->m_net_server_extension.m_before_header= net_before_header_psi;
  thd->m_net_server_extension.m_after_header= net_after_header_psi;

  /* Activate this private extension for the mysqld server. */
  thd->get_protocol_classic()->get_net()->extension=
    &thd->m_net_server_extension;
}


///////////////////////////////////////////////////////////////////////////
// Channel_info_local_socket implementation
///////////////////////////////////////////////////////////////////////////

/**
  This class abstracts the info. about local socket mode of communication with
  the server.
*/
class Channel_info_local_socket : public Channel_info
{
  // connect socket object
  MYSQL_SOCKET m_connect_sock;

protected:
  virtual Vio* create_and_init_vio() const
  {
    return mysql_socket_vio_new(m_connect_sock, VIO_TYPE_SOCKET, VIO_LOCALHOST);
  }

public:
  /**
    Constructor that sets the connect socket.

    @param connect_socket set connect socket descriptor.
  */
  Channel_info_local_socket(MYSQL_SOCKET connect_socket)
  : m_connect_sock(connect_socket)
  { }

  virtual THD* create_thd()
  {
    THD* thd= Channel_info::create_thd();

    if (thd != NULL)
    {
      init_net_server_extension(thd);
      thd->security_context()->set_host_ptr(my_localhost, strlen(my_localhost));
    }
    return thd;
  }

  virtual void send_error_and_close_channel(uint errorcode,
                                            int error,
                                            bool senderror)
  {
    Channel_info::send_error_and_close_channel(errorcode, error, senderror);

    mysql_socket_shutdown(m_connect_sock, SHUT_RDWR);
    mysql_socket_close(m_connect_sock);
  }
};


///////////////////////////////////////////////////////////////////////////
// Channel_info_tcpip_socket implementation
///////////////////////////////////////////////////////////////////////////

/**
  This class abstracts the info. about TCP/IP socket mode of communication with
  the server.
*/
class Channel_info_tcpip_socket : public Channel_info
{
  // connect socket object
  MYSQL_SOCKET m_connect_sock;

protected:
  virtual Vio* create_and_init_vio() const
  {
    return mysql_socket_vio_new(m_connect_sock, VIO_TYPE_TCPIP, 0);
  }

public:
  /**
    Constructor that sets the connect socket.

    @param connect_socket set connect socket descriptor.
  */
  Channel_info_tcpip_socket(MYSQL_SOCKET connect_socket)
  : m_connect_sock(connect_socket)
  { }

  virtual THD* create_thd()
  {
    THD* thd= Channel_info::create_thd();

    if (thd != NULL)
      init_net_server_extension(thd);
    return thd;
  }

  virtual void send_error_and_close_channel(uint errorcode,
                                            int error,
                                            bool senderror)
  {
    Channel_info::send_error_and_close_channel(errorcode, error, senderror);

    mysql_socket_shutdown(m_connect_sock, SHUT_RDWR);
    mysql_socket_close(m_connect_sock);
  }
};


///////////////////////////////////////////////////////////////////////////
// TCP_socket implementation
///////////////////////////////////////////////////////////////////////////

/**
  MY_BIND_ALL_ADDRESSES defines a special value for the bind-address option,
  which means that the server should listen to all available network addresses,
  both IPv6 (if available) and IPv4.

  Basically, this value instructs the server to make an attempt to bind the
  server socket to '::' address, and rollback to '0.0.0.0' if the attempt fails.
*/
const char *MY_BIND_ALL_ADDRESSES= "*";


/**
  TCP_socket class represents the TCP sockets abstraction. It provides
  the get_listener_socket that setup a TCP listener socket to listen.
*/
class TCP_socket
{
  std::string m_bind_addr_str;  // IP address as string.
  uint m_tcp_port; // TCP port to bind to
  uint m_backlog;  // Backlog length for queue of pending connections.
  uint m_port_timeout; // Port timeout

  MYSQL_SOCKET create_socket(const struct addrinfo *addrinfo_list,
                             int addr_family,
                             struct addrinfo **use_addrinfo)
  {
    for (const struct addrinfo *cur_ai= addrinfo_list; cur_ai != NULL;
         cur_ai= cur_ai->ai_next)
    {
      if (cur_ai->ai_family != addr_family)
        continue;

      MYSQL_SOCKET sock= mysql_socket_socket(key_socket_tcpip,
                                             cur_ai->ai_family,
                                             cur_ai->ai_socktype,
                                             cur_ai->ai_protocol);

      char ip_addr[INET6_ADDRSTRLEN];

      if (vio_getnameinfo(cur_ai->ai_addr, ip_addr, sizeof (ip_addr),
                          NULL, 0, NI_NUMERICHOST))
      {
        ip_addr[0]= 0;
      }

      if (mysql_socket_getfd(sock) == INVALID_SOCKET)
      {
        sql_print_error("Failed to create a socket for %s '%s': errno: %d.",
                        (addr_family == AF_INET) ? "IPv4" : "IPv6",
                        (const char *) ip_addr,
                        (int) socket_errno);
      }
      else
      {
        sql_print_information("Server socket created on IP: '%s'.",
                              (const char *) ip_addr);

        *use_addrinfo= (struct addrinfo *)cur_ai;
        return sock;
      }
    }

    return MYSQL_INVALID_SOCKET;
  }

public:
  /**
    Constructor that takes tcp port and ip address string and other
    related parameters to set up listener tcp to listen for connection
    events.

    @param  tcp_port  tcp port number.
    @param  bind_addr_str  ip address as string value.
    @param  back_log backlog specifying length of pending connection queue.
    @param  m_port_timeout port timeout value
  */
  TCP_socket(std::string bind_addr_str,
             uint tcp_port,
             uint backlog,
             uint port_timeout)
  : m_bind_addr_str(bind_addr_str),
    m_tcp_port(tcp_port),
    m_backlog(backlog),
    m_port_timeout(port_timeout)
  { }

  /**
    Set up a listener to listen for connection events.

    @retval   valid socket if successful else MYSQL_INVALID_SOCKET on failure.
  */
  MYSQL_SOCKET get_listener_socket()
  {
    struct addrinfo *ai;
    const char *bind_address_str= NULL;

    sql_print_information("Server hostname (bind-address): '%s'; port: %d",
                          m_bind_addr_str.c_str(), m_tcp_port);

    // Get list of IP-addresses associated with the bind-address.

    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags= AI_PASSIVE;
    hints.ai_socktype= SOCK_STREAM;
    hints.ai_family= AF_UNSPEC;

    char port_buf[NI_MAXSERV];
    my_snprintf(port_buf, NI_MAXSERV, "%d", m_tcp_port);

    if (native_strcasecmp(my_bind_addr_str, MY_BIND_ALL_ADDRESSES) == 0)
    {
      /*
        That's the case when bind-address is set to a special value ('*'),
        meaning "bind to all available IP addresses". If the box supports
        the IPv6 stack, that means binding to '::'. If only IPv4 is available,
        bind to '0.0.0.0'.
      */

      bool ipv6_available= false;
      const char *ipv6_all_addresses= "::";
      if (!getaddrinfo(ipv6_all_addresses, port_buf, &hints, &ai))
      {
        /*
          IPv6 might be available (the system might be able to resolve an IPv6
          address, but not be able to create an IPv6-socket). Try to create a
          dummy IPv6-socket. Do not instrument that socket by P_S.
        */

        MYSQL_SOCKET s= mysql_socket_socket(0, AF_INET6, SOCK_STREAM, 0);
        ipv6_available= mysql_socket_getfd(s) != INVALID_SOCKET;
        mysql_socket_close(s);
      }
      if (ipv6_available)
      {
        sql_print_information("IPv6 is available.");

        // Address info (ai) for IPv6 address is already set.

        bind_address_str= ipv6_all_addresses;
      }
      else
      {
        sql_print_information("IPv6 is not available.");

        // Retrieve address info (ai) for IPv4 address.

        const char *ipv4_all_addresses= "0.0.0.0";
        if (getaddrinfo(ipv4_all_addresses, port_buf, &hints, &ai))
        {
          sql_print_error("%s: %s", ER_DEFAULT(ER_IPSOCK_ERROR), strerror(errno));

          sql_print_error("Can't start server: cannot resolve hostname!");
          return MYSQL_INVALID_SOCKET;
        }

        bind_address_str= ipv4_all_addresses;
      }
    }
    else
    {
      if (getaddrinfo(m_bind_addr_str.c_str(), port_buf, &hints, &ai))
      {
        sql_print_error("%s: %s", ER_DEFAULT(ER_IPSOCK_ERROR), strerror(errno));
        sql_print_error("Can't start server: cannot resolve hostname!");
        return MYSQL_INVALID_SOCKET;
      }

      bind_address_str= m_bind_addr_str.c_str();
    }

    // Log all the IP-addresses
    for (struct addrinfo *cur_ai= ai; cur_ai != NULL; cur_ai= cur_ai->ai_next)
    {
      char ip_addr[INET6_ADDRSTRLEN];

      if (vio_getnameinfo(cur_ai->ai_addr, ip_addr, sizeof (ip_addr),
                          NULL, 0, NI_NUMERICHOST))
      {
        sql_print_error("Fails to print out IP-address.");
        continue;
      }

      sql_print_information("  - '%s' resolves to '%s';",
                            bind_address_str, ip_addr);
    }

    /*
      If the 'bind-address' option specifies the hostname, which resolves to
      multiple IP-address, use the following rule:
      - if there are IPv4-addresses, use the first IPv4-address
      returned by getaddrinfo();
      - if there are IPv6-addresses, use the first IPv6-address
      returned by getaddrinfo();
    */

    struct addrinfo *a;
    MYSQL_SOCKET listener_socket= create_socket(ai, AF_INET, &a);

    if (mysql_socket_getfd(listener_socket) == INVALID_SOCKET)
      listener_socket= create_socket(ai, AF_INET6, &a);

    // Report user-error if we failed to create a socket.
    if (mysql_socket_getfd(listener_socket) == INVALID_SOCKET)
    {
      sql_print_error("%s: %s", ER_DEFAULT(ER_IPSOCK_ERROR), strerror(errno));
      return MYSQL_INVALID_SOCKET;
    }

    mysql_socket_set_thread_owner(listener_socket);

#ifndef _WIN32
    /*
      We should not use SO_REUSEADDR on windows as this would enable a
      user to open two mysqld servers with the same TCP/IP port.
    */
    {
      int option_flag= 1;
      (void) mysql_socket_setsockopt(listener_socket, SOL_SOCKET, SO_REUSEADDR,
                                     (char*)&option_flag,sizeof(option_flag));
    }
#endif
#ifdef IPV6_V6ONLY
     /*
       For interoperability with older clients, IPv6 socket should
       listen on both IPv6 and IPv4 wildcard addresses.
       Turn off IPV6_V6ONLY option.

       NOTE: this will work starting from Windows Vista only.
       On Windows XP dual stack is not available, so it will not
       listen on the corresponding IPv4-address.
     */
    if (a->ai_family == AF_INET6)
    {
      int option_flag= 0;

      if (mysql_socket_setsockopt(listener_socket, IPPROTO_IPV6, IPV6_V6ONLY,
                                  (char *) &option_flag, sizeof (option_flag)))
      {
        sql_print_warning("Failed to reset IPV6_V6ONLY flag (error: %d). "
                          "The server will listen to IPv6 addresses only.",
                          (int) socket_errno);
      }
    }
#endif
    /*
      Sometimes the port is not released fast enough when stopping and
      restarting the server. This happens quite often with the test suite
      on busy Linux systems. Retry to bind the address at these intervals:
      Sleep intervals: 1, 2, 4,  6,  9, 13, 17, 22, ...
      Retry at second: 1, 3, 7, 13, 22, 35, 52, 74, ...
      Limit the sequence by m_port_timeout (set --port-open-timeout=#).
    */
    uint this_wait= 0;
    int ret= 0;
    for (uint waited= 0, retry= 1; ; retry++, waited+= this_wait)
    {
      if (((ret= mysql_socket_bind(listener_socket, a->ai_addr, a->ai_addrlen)) >= 0 ) ||
          (socket_errno != SOCKET_EADDRINUSE) ||
          (waited >= m_port_timeout))
        break;
      sql_print_information("Retrying bind on TCP/IP port %u", mysqld_port);
      this_wait= retry * retry / 3 + 1;
      sleep(this_wait);
    }
    freeaddrinfo(ai);
    if (ret < 0)
    {
      DBUG_PRINT("error",("Got error: %d from bind",socket_errno));
      sql_print_error("Can't start server: Bind on TCP/IP port: %s",
                      strerror(errno));
      sql_print_error("Do you already have another mysqld server running on port: %d ?",m_tcp_port);
      mysql_socket_close(listener_socket);
      return MYSQL_INVALID_SOCKET;
    }

    if (mysql_socket_listen(listener_socket, static_cast<int>(m_backlog)) < 0)
    {
      sql_print_error("Can't start server: listen() on TCP/IP port: %s",
                      strerror(errno));
      sql_print_error("listen() on TCP/IP failed with error %d",
          socket_errno);
      mysql_socket_close(listener_socket);
      return MYSQL_INVALID_SOCKET;
    }

#if !defined(NO_FCNTL_NONBLOCK)
    (void) mysql_sock_set_nonblocking(listener_socket);
#endif

    return listener_socket;
  }
};


#if defined(HAVE_SYS_UN_H)
///////////////////////////////////////////////////////////////////////////
// Unix_socket implementation
///////////////////////////////////////////////////////////////////////////

/**
  The Unix_socket class represents an abstraction for creating a unix
  socket ready to listen for new connections from clients.
*/
class Unix_socket
{
  std::string m_unix_sockname; // pathname for socket to bind to.
  uint m_backlog; // backlog specifying lenght of pending queue connection.
  /**
    Create a lockfile which contains the pid of the mysqld instance started
    and pathname as name of unix socket pathname appended with .lock

    @retval   FALSE if lockfile creation is successful else TRUE if lockfile
              file could not be created.

  */
  bool create_lockfile();
public:
  /**
    Constructor that takes pathname for unix socket to bind to
    and backlog specifying the length of pending connection queue.

    @param  unix_sockname pointer to pathname for the created unix socket
            to bind.
    @param  backlog   specifying the length of pending connection queue.
  */
  Unix_socket(const std::string *unix_sockname, uint backlog)
  : m_unix_sockname(*unix_sockname),
    m_backlog(backlog)
  { }

  /**
    Set up a listener socket which is ready to listen for connection from
    clients.

    @retval   valid socket if successful else MYSQL_INVALID_SOCKET on failure.
  */
  MYSQL_SOCKET get_listener_socket()
  {
    struct sockaddr_un UNIXaddr;
    DBUG_PRINT("general",("UNIX Socket is %s", m_unix_sockname.c_str()));

    // Check path length, probably move to set unix port?
    if (m_unix_sockname.length() > (sizeof(UNIXaddr.sun_path) - 1))
    {
      sql_print_error("The socket file path is too long (> %u): %s",
                      (uint) sizeof(UNIXaddr.sun_path) - 1,
                      m_unix_sockname.c_str());
      return MYSQL_INVALID_SOCKET;
    }

    if (create_lockfile())
    {
      sql_print_error("Unable to setup unix socket lock file.");
      return MYSQL_INVALID_SOCKET;
    }

    MYSQL_SOCKET listener_socket= mysql_socket_socket(key_socket_unix, AF_UNIX,
                                                      SOCK_STREAM, 0);

    if (mysql_socket_getfd(listener_socket) < 0)
    {
      sql_print_error("Can't start server: UNIX Socket : %s", strerror(errno));
      return MYSQL_INVALID_SOCKET;
    }

    mysql_socket_set_thread_owner(listener_socket);

    memset(&UNIXaddr, 0, sizeof(UNIXaddr));
    UNIXaddr.sun_family= AF_UNIX;
    my_stpcpy(UNIXaddr.sun_path, m_unix_sockname.c_str());
    (void) unlink(m_unix_sockname.c_str());

    // Set socket option SO_REUSEADDR
    int option_enable= 1;
    (void) mysql_socket_setsockopt(listener_socket, SOL_SOCKET, SO_REUSEADDR,
                                   (char*)&option_enable, sizeof(option_enable));
    // bind
    umask(0);
    if (mysql_socket_bind(listener_socket,
                          reinterpret_cast<struct sockaddr *> (&UNIXaddr),
                          sizeof(UNIXaddr)) < 0)
    {
      sql_print_error("Can't start server : Bind on unix socket: %s",
                      strerror(errno));
      sql_print_error("Do you already have another mysqld server running on socket: %s ?",
                      m_unix_sockname.c_str());
      mysql_socket_close(listener_socket);
      return MYSQL_INVALID_SOCKET;
    }
    umask(((~my_umask) & 0666));

    // listen
    if (mysql_socket_listen(listener_socket, (int)m_backlog) < 0)
      sql_print_warning("listen() on Unix socket failed with error %d", socket_errno);

    // set sock fd non blocking.
#if !defined(NO_FCNTL_NONBLOCK)
    (void) mysql_sock_set_nonblocking(listener_socket);
#endif

    return listener_socket;
  }
};


bool Unix_socket::create_lockfile()
{
  int fd;
  char buffer[8];
  pid_t cur_pid= getpid();
  std::string lock_filename= m_unix_sockname + ".lock";

  compile_time_assert(sizeof(pid_t) == 4);
  int retries= 3;
  while (true)
  {
    if (!retries--)
    {
      sql_print_error("Unable to create unix socket lock file %s after retries."
                      ,lock_filename.c_str());
      return true;
    }

    fd= open(lock_filename.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);

    if (fd >= 0)
      break;

    if (errno != EEXIST)
    {
      sql_print_error("Could not create unix socket lock file %s.",
                      lock_filename.c_str());
      return true;
    }

    fd= open(lock_filename.c_str(), O_RDONLY, 0600);
    if (fd < 0)
    {
      sql_print_error("Could not open unix socket lock file %s.",
                      lock_filename.c_str());
      return true;
    }

    ssize_t len;
    if ((len= read(fd, buffer, sizeof(buffer)-1)) < 0)
    {
      sql_print_error("Could not read unix socket lock file %s.",
                       lock_filename.c_str());
      close(fd);
      return true;
    }

    close(fd);

    if (len == 0)
    {
      sql_print_error("Unix socket lock file is empty %s.",
                       lock_filename.c_str());
      return true;
    }
    buffer[len]= '\0';

    pid_t parent_pid= getppid();
    pid_t read_pid= atoi(buffer);

    if (read_pid <= 0)
    {
      sql_print_error("Invalid pid in unix socket lock file %s.",
                      lock_filename.c_str());
      return true;
    }

    if (read_pid != cur_pid && read_pid != parent_pid)
    {
      if (kill(read_pid, 0) == 0)
      {
        sql_print_error("Another process with pid %d is using "
                        "unix socket file.", static_cast<int>(read_pid));
        return true;
      }
    }

    /*
      Unlink the lock file as it is not associated with any process and
      retry.
    */
    if (unlink(lock_filename.c_str()) < 0)
    {
      sql_print_error("Could not remove unix socket lock file %s.",
                      lock_filename.c_str());
      return true;
    }
  }

  snprintf(buffer, sizeof(buffer), "%d\n", static_cast<int>(cur_pid));
  if (write(fd, buffer, strlen(buffer)) !=
      static_cast<signed>(strlen(buffer)))
  {
    close(fd);
    sql_print_error("Could not write unix socket lock file %s errno %d.",
                    lock_filename.c_str(), errno);
    return true;
  }

  if (fsync(fd) != 0)
  {
    close(fd);
    sql_print_error("Could not sync unix socket lock file %s errno %d.",
                    lock_filename.c_str(), errno);
    return true;
  }

  if (close(fd) != 0)
  {
    sql_print_error("Could not close unix socket lock file %s errno %d.",
                    lock_filename.c_str(), errno);
    return true;
  }
  return false;
}

#endif // HAVE_SYS_UN_H


///////////////////////////////////////////////////////////////////////////
// Mysqld_socket_listener implementation
///////////////////////////////////////////////////////////////////////////

Mysqld_socket_listener::Mysqld_socket_listener(std::string bind_addr_str,
                                               uint tcp_port,
                                               uint backlog,
                                               uint port_timeout,
                                               std::string unix_sockname)
  : m_bind_addr_str(bind_addr_str),
    m_tcp_port(tcp_port),
    m_backlog(backlog),
    m_port_timeout(port_timeout),
    m_unix_sockname(unix_sockname),
    m_unlink_sockname(false),
    m_error_count(0)
{
#ifdef HAVE_LIBWRAP
  m_deny_severity = LOG_WARNING;
  m_libwrap_name= my_progname + dirname_length(my_progname);
  if (!opt_log_syslog_enable)
    openlog(m_libwrap_name, LOG_PID, LOG_AUTH);
#endif /* HAVE_LIBWRAP */
}


bool Mysqld_socket_listener::setup_listener()
{
  // Setup tcp socket listener
  if (m_tcp_port)
  {
    TCP_socket tcp_socket(m_bind_addr_str, m_tcp_port,
                          m_backlog, m_port_timeout);

    MYSQL_SOCKET mysql_socket= tcp_socket.get_listener_socket();
    if (mysql_socket.fd == INVALID_SOCKET)
      return true;

    m_socket_map.insert(std::pair<MYSQL_SOCKET,bool>(mysql_socket, false));
  }
#if defined(HAVE_SYS_UN_H)
  // Setup unix socket listener
  if (m_unix_sockname != "")
  {
    Unix_socket unix_socket(&m_unix_sockname, m_backlog);

    MYSQL_SOCKET mysql_socket= unix_socket.get_listener_socket();
    if (mysql_socket.fd == INVALID_SOCKET)
      return true;

    m_socket_map.insert(std::pair<MYSQL_SOCKET,bool>(mysql_socket, true));
    m_unlink_sockname= true;
  }
#endif /* HAVE_SYS_UN_H */

  // Setup for connection events for poll or select
#ifdef HAVE_POLL
  int count= 0;
#endif
  for (socket_map_iterator_t sock_map_iter=  m_socket_map.begin();
       sock_map_iter != m_socket_map.end(); ++sock_map_iter)
  {
    MYSQL_SOCKET listen_socket= sock_map_iter->first;
    mysql_socket_set_thread_owner(listen_socket);
#ifdef HAVE_POLL
    m_poll_info.m_fds[count].fd= mysql_socket_getfd(listen_socket);
    m_poll_info.m_fds[count].events= POLLIN;
    m_poll_info.m_pfs_fds[count]= listen_socket;
    count++;
#else  // HAVE_POLL
    FD_SET(mysql_socket_getfd(listen_socket), &m_select_info.m_client_fds);
    if ((uint) mysql_socket_getfd(listen_socket) > m_select_info.m_max_used_connection)
      m_select_info.m_max_used_connection= mysql_socket_getfd(listen_socket);
#endif // HAVE_POLL
  }
  return false;
}


Channel_info* Mysqld_socket_listener::listen_for_connection_event()
{
#ifdef HAVE_POLL
  int retval= poll(&m_poll_info.m_fds[0], m_socket_map.size(), -1);
#else
  m_select_info.m_read_fds= m_select_info.m_client_fds;
  int retval= select((int) m_select_info.m_max_used_connection,
                     &m_select_info.m_read_fds, 0, 0, 0);
#endif

  if (retval < 0 && socket_errno != SOCKET_EINTR)
  {
    /*
      select(2)/poll(2) failed on the listening port.
      There is not much details to report about the client,
      increment the server global status variable.
    */
    connection_errors_select++;
    if (!select_errors++ && !abort_loop)
      sql_print_error("mysqld: Got error %d from select",socket_errno);
  }

  if (retval < 0 || abort_loop)
    return NULL;


  /* Is this a new connection request ? */
  MYSQL_SOCKET listen_sock= MYSQL_INVALID_SOCKET;
  bool is_unix_socket= false;
#ifdef HAVE_POLL
  for (uint i= 0; i < m_socket_map.size(); ++i)
  {
    if (m_poll_info.m_fds[i].revents & POLLIN)
    {
      listen_sock= m_poll_info.m_pfs_fds[i];
      is_unix_socket= m_socket_map[listen_sock];
      break;
    }
  }
#else // HAVE_POLL
  for (socket_map_iterator_t sock_map_iter=  m_socket_map.begin();
       sock_map_iter != m_socket_map.end(); ++sock_map_iter)
  {
    if (FD_ISSET(mysql_socket_getfd(sock_map_iter->first),
                 &m_select_info.m_read_fds))
    {
      listen_sock= sock_map_iter->first;
      is_unix_socket= sock_map_iter->second;
      break;
    }
  }
#endif // HAVE_POLL

  MYSQL_SOCKET connect_sock;
  struct sockaddr_storage cAddr;
  for (uint retry= 0; retry < MAX_ACCEPT_RETRY; retry++)
  {
    socket_len_t length= sizeof(struct sockaddr_storage);
    connect_sock= mysql_socket_accept(key_socket_client_connection, listen_sock,
                                      (struct sockaddr *)(&cAddr), &length);
    if (mysql_socket_getfd(connect_sock) != INVALID_SOCKET ||
        (socket_errno != SOCKET_EINTR && socket_errno != SOCKET_EAGAIN))
      break;
  }
  if (mysql_socket_getfd(connect_sock) == INVALID_SOCKET)
  {
    /*
      accept(2) failed on the listening port, after many retries.
      There is not much details to report about the client,
      increment the server global status variable.
    */
    connection_errors_accept++;
    if ((m_error_count++ & 255) == 0) // This can happen often
      sql_print_error("Error in accept: %s", strerror(errno));
    if (socket_errno == SOCKET_ENFILE || socket_errno == SOCKET_EMFILE)
      sleep(1);             // Give other threads some time
    return NULL;
  }

#ifdef __APPLE__
  if (mysql_socket_getfd(connect_sock) >= FD_SETSIZE)
  {
    sql_print_warning("File Descriptor %d exceeded FD_SETSIZE=%d",
                      mysql_socket_getfd(connect_sock), FD_SETSIZE);
    connection_errors_internal++;
    (void) mysql_socket_close(connect_sock);
    return NULL;
  }
#endif

#ifdef HAVE_LIBWRAP
  if (!is_unix_socket)
  {
    struct request_info req;
    signal(SIGCHLD, SIG_DFL);
    request_init(&req, RQ_DAEMON, m_libwrap_name, RQ_FILE,
                 mysql_socket_getfd(connect_sock), NULL);
    fromhost(&req);

    if (!hosts_access(&req))
    {
      /*
        This may be stupid but refuse() includes an exit(0)
        which we surely don't want...
        clean_exit() - same stupid thing ...
      */
      syslog(LOG_AUTH | m_deny_severity,
             "refused connect from %s", eval_client(&req));

#ifdef HAVE_LIBWRAP_PROTOTYPES
      // Some distros have patched tcpd.h to have proper prototypes
      if (req.sink)
        (req.sink)(req.fd);
#else
      // Some distros have not patched tcpd.h
      if (req.sink)
        ((void (*)(int))req.sink)(req.fd);
#endif
      /*
        The connection was refused by TCP wrappers.
        There are no details (by client IP) available to update the host_cache.
      */
      mysql_socket_shutdown(connect_sock, SHUT_RDWR);
      mysql_socket_close(connect_sock);

      connection_errors_tcpwrap++;
      return NULL;
    }
  }
#endif // HAVE_LIBWRAP

  Channel_info* channel_info= NULL;
  if (is_unix_socket)
    channel_info= new (std::nothrow) Channel_info_local_socket(connect_sock);
  else
    channel_info= new (std::nothrow) Channel_info_tcpip_socket(connect_sock);
  if (channel_info == NULL)
  {
    (void) mysql_socket_shutdown(connect_sock, SHUT_RDWR);
    (void) mysql_socket_close(connect_sock);
    connection_errors_internal++;
    return NULL;
  }

  return channel_info;
}


void Mysqld_socket_listener::close_listener()
{
  for (socket_map_iterator_t sock_map_iter=  m_socket_map.begin();
       sock_map_iter != m_socket_map.end(); ++sock_map_iter)
  {
    (void) mysql_socket_shutdown(sock_map_iter->first,SHUT_RDWR);
    (void) mysql_socket_close(sock_map_iter->first);
  }

#if defined(HAVE_SYS_UN_H)
  if (m_unix_sockname != "" && m_unlink_sockname)
  {
    std::string lock_filename= m_unix_sockname + ".lock";
    (void) unlink(lock_filename.c_str());
    (void) unlink(m_unix_sockname.c_str());
  }
#endif

  m_socket_map.clear();
}
