/*
  This file is part of libmicrohttpd
  Copyright (C) 2017-2023 Karlson2k (Evgeny Grin), Full re-write of buffering and
                     pushing, many bugs fixes, optimisations, sendfile() porting
  Copyright (C) 2019 ng0 <ng0@n0.is>, Initial version of send() wrappers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

 */

/**
 * @file microhttpd/mhd_send.c
 * @brief Implementation of send() wrappers and helper functions.
 * @author Karlson2k (Evgeny Grin)
 * @author ng0 (N. Gillmann)
 * @author Christian Grothoff
 */

/* Worth considering for future improvements and additions:
 * NetBSD has no sendfile or sendfile64. The way to work
 * with this seems to be to mmap the file and write(2) as
 * large a chunk as possible to the socket. Alternatively,
 * use madvise(..., MADV_SEQUENTIAL). */

#include "mhd_send.h"
#ifdef MHD_LINUX_SOLARIS_SENDFILE
#include <sys/sendfile.h>
#endif /* MHD_LINUX_SOLARIS_SENDFILE */
#if defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#endif /* HAVE_FREEBSD_SENDFILE || HAVE_DARWIN_SENDFILE */
#ifdef HAVE_SYS_PARAM_H
/* For FreeBSD version identification */
#include <sys/param.h>
#endif /* HAVE_SYS_PARAM_H */
#ifdef HAVE_SYSCONF
#include <unistd.h>
#endif /* HAVE_SYSCONF */
#include "mhd_assert.h"

#include "mhd_limits.h"

#ifdef MHD_VECT_SEND
#if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL)) && \
  defined(MHD_SEND_SPIPE_SUPPRESS_POSSIBLE) && \
  defined(MHD_SEND_SPIPE_SUPPRESS_NEEDED)
#define _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED 1
#endif /* (!HAVE_SENDMSG || !MSG_NOSIGNAL) &&
          MHD_SEND_SPIPE_SUPPRESS_POSSIBLE && MHD_SEND_SPIPE_SUPPRESS_NEEDED */
#endif /* MHD_VECT_SEND */

/**
 * sendfile() chuck size
 */
#define MHD_SENFILE_CHUNK_         (0x20000)

/**
 * sendfile() chuck size for thread-per-connection
 */
#define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000)

#ifdef HAVE_FREEBSD_SENDFILE
#ifdef SF_FLAGS
/**
 * FreeBSD sendfile() flags
 */
static int freebsd_sendfile_flags_;

/**
 * FreeBSD sendfile() flags for thread-per-connection
 */
static int freebsd_sendfile_flags_thd_p_c_;


/**
 * Initialises variables for FreeBSD's sendfile()
 */
static void
freebsd_sendfile_init_ (void)
{
  long sys_page_size = sysconf (_SC_PAGESIZE);
  if (0 >= sys_page_size)
  {   /* Failed to get page size. */
    freebsd_sendfile_flags_ = SF_NODISKIO;
    freebsd_sendfile_flags_thd_p_c_ = SF_NODISKIO;
  }
  else
  {
    freebsd_sendfile_flags_ =
      SF_FLAGS ((uint16_t) ((MHD_SENFILE_CHUNK_ + sys_page_size - 1)
                            / sys_page_size), SF_NODISKIO);
    freebsd_sendfile_flags_thd_p_c_ =
      SF_FLAGS ((uint16_t) ((MHD_SENFILE_CHUNK_THR_P_C_ + sys_page_size - 1)
                            / sys_page_size), SF_NODISKIO);
  }
}


#endif /* SF_FLAGS */
#endif /* HAVE_FREEBSD_SENDFILE */


#if defined(HAVE_SYSCONF) && defined(_SC_IOV_MAX)
/**
 * Current IOV_MAX system value
 */
static unsigned long mhd_iov_max_ = 0;

static void
iov_max_init_ (void)
{
  long res = sysconf (_SC_IOV_MAX);
  if (res >= 0)
    mhd_iov_max_ = (unsigned long) res;
  else
  {
#if defined(IOV_MAX)
    mhd_iov_max_ = IOV_MAX;
#else  /* ! IOV_MAX */
    mhd_iov_max_ = 8; /* Should be the safe limit */
#endif /* ! IOV_MAX */
  }
}


/**
 * IOV_MAX (run-time) value
 */
#define _MHD_IOV_MAX    mhd_iov_max_
#elif defined(IOV_MAX)

/**
 * IOV_MAX (static) value
 */
#define _MHD_IOV_MAX    IOV_MAX
#endif /* HAVE_SYSCONF && _SC_IOV_MAX */


/**
 * Initialises static variables
 */
void
MHD_send_init_static_vars_ (void)
{
#ifdef HAVE_FREEBSD_SENDFILE
  /* FreeBSD 11 and later allow to specify read-ahead size
   * and handles SF_NODISKIO differently.
   * SF_FLAGS defined only on FreeBSD 11 and later. */
#ifdef SF_FLAGS
  freebsd_sendfile_init_ ();
#endif /* SF_FLAGS */
#endif /* HAVE_FREEBSD_SENDFILE */
#if defined(HAVE_SYSCONF) && defined(_SC_IOV_MAX)
  iov_max_init_ ();
#endif /* HAVE_SYSCONF && _SC_IOV_MAX */
}


bool
MHD_connection_set_nodelay_state_ (struct MHD_Connection *connection,
                                   bool nodelay_state)
{
#ifdef TCP_NODELAY
  const MHD_SCKT_OPT_BOOL_ off_val = 0;
  const MHD_SCKT_OPT_BOOL_ on_val = 1;
  int err_code;

  if (_MHD_YES == connection->is_nonip)
    return false;

  if (0 == setsockopt (connection->socket_fd,
                       IPPROTO_TCP,
                       TCP_NODELAY,
                       (const void *) (nodelay_state ? &on_val : &off_val),
                       sizeof (off_val)))
  {
    connection->sk_nodelay = nodelay_state;
    return true;
  }

  err_code = MHD_socket_get_error_ ();
  if (MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_EINVAL_) ||
      MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOPROTOOPT_) ||
      MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOTSOCK_))
  {
    if (_MHD_UNKNOWN == connection->is_nonip)
      connection->is_nonip = _MHD_YES;
#ifdef HAVE_MESSAGES
    else
    {
      MHD_DLOG (connection->daemon,
                _ ("Setting %s option to %s state failed "
                   "for TCP/IP socket %d: %s\n"),
                "TCP_NODELAY",
                nodelay_state ? _ ("ON") : _ ("OFF"),
                (int) connection->socket_fd,
                MHD_socket_strerr_ (err_code));
    }
#endif /* HAVE_MESSAGES */
  }
#ifdef HAVE_MESSAGES
  else
  {
    MHD_DLOG (connection->daemon,
              _ ("Setting %s option to %s state failed: %s\n"),
              "TCP_NODELAY",
              nodelay_state ? _ ("ON") : _ ("OFF"),
              MHD_socket_strerr_ (err_code));
  }
#endif /* HAVE_MESSAGES */

#else  /* ! TCP_NODELAY */
  (void) connection; (void) nodelay_state; /* Mute compiler warnings */
#endif /* ! TCP_NODELAY */
  return false;
}


/**
 * Set required cork state for connection socket
 *
 * The function automatically updates sk_corked state.
 *
 * @param connection the connection to manipulate
 * @param cork_state the requested new state of socket
 * @return true if succeed, false if failed or not supported
 *         by the current platform / kernel.
 */
bool
MHD_connection_set_cork_state_ (struct MHD_Connection *connection,
                                bool cork_state)
{
#if defined(MHD_TCP_CORK_NOPUSH)
  const MHD_SCKT_OPT_BOOL_ off_val = 0;
  const MHD_SCKT_OPT_BOOL_ on_val = 1;
  int err_code;

  if (_MHD_YES == connection->is_nonip)
    return false;
  if (0 == setsockopt (connection->socket_fd,
                       IPPROTO_TCP,
                       MHD_TCP_CORK_NOPUSH,
                       (const void *) (cork_state ? &on_val : &off_val),
                       sizeof (off_val)))
  {
    connection->sk_corked = cork_state;
    return true;
  }

  err_code = MHD_socket_get_error_ ();
  if (MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_EINVAL_) ||
      MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOPROTOOPT_) ||
      MHD_SCKT_ERR_IS_ (err_code, MHD_SCKT_ENOTSOCK_))
  {
    if (_MHD_UNKNOWN == connection->is_nonip)
      connection->is_nonip = _MHD_YES;
#ifdef HAVE_MESSAGES
    else
    {
      MHD_DLOG (connection->daemon,
                _ ("Setting %s option to %s state failed "
                   "for TCP/IP socket %d: %s\n"),
#ifdef TCP_CORK
                "TCP_CORK",
#else  /* ! TCP_CORK */
                "TCP_NOPUSH",
#endif /* ! TCP_CORK */
                cork_state ? _ ("ON") : _ ("OFF"),
                (int) connection->socket_fd,
                MHD_socket_strerr_ (err_code));
    }
#endif /* HAVE_MESSAGES */
  }
#ifdef HAVE_MESSAGES
  else
  {
    MHD_DLOG (connection->daemon,
              _ ("Setting %s option to %s state failed: %s\n"),
#ifdef TCP_CORK
              "TCP_CORK",
#else  /* ! TCP_CORK */
              "TCP_NOPUSH",
#endif /* ! TCP_CORK */
              cork_state ? _ ("ON") : _ ("OFF"),
              MHD_socket_strerr_ (err_code));
  }
#endif /* HAVE_MESSAGES */

#else  /* ! MHD_TCP_CORK_NOPUSH */
  (void) connection; (void) cork_state; /* Mute compiler warnings. */
#endif /* ! MHD_TCP_CORK_NOPUSH */
  return false;
}


/**
 * Handle pre-send setsockopt calls.
 *
 * @param connection the MHD_Connection structure
 * @param plain_send set to true if plain send() or sendmsg() will be called,
 *                   set to false if TLS socket send(), sendfile() or
 *                   writev() will be called.
 * @param push_data whether to push data to the network from buffers after
 *                  the next call of send function.
 */
static void
pre_send_setopt (struct MHD_Connection *connection,
                 bool plain_send,
                 bool push_data)
{
  /* Try to buffer data if not sending the final piece.
   * Final piece is indicated by push_data == true. */
  const bool buffer_data = (! push_data);

  if (_MHD_YES == connection->is_nonip)
    return;
  /* The goal is to minimise the total number of additional sys-calls
   * before and after send().
   * The following tricky (over-)complicated algorithm typically use zero,
   * one or two additional sys-calls (depending on OS) for each response. */

  if (buffer_data)
  {
    /* Need to buffer data if possible. */
#ifdef MHD_USE_MSG_MORE
    if (plain_send)
      return; /* Data is buffered by send() with MSG_MORE flag.
               * No need to check or change anything. */
#else  /* ! MHD_USE_MSG_MORE */
    (void) plain_send; /* Mute compiler warning. */
#endif /* ! MHD_USE_MSG_MORE */

#ifdef MHD_TCP_CORK_NOPUSH
    if (_MHD_ON == connection->sk_corked)
      return; /* The connection was already corked. */

    if (MHD_connection_set_cork_state_ (connection, true))
      return; /* The connection has been corked. */

    /* Failed to cork the connection.
     * Really unlikely to happen on TCP connections. */
#endif /* MHD_TCP_CORK_NOPUSH */
    if (_MHD_OFF == connection->sk_nodelay)
      return; /* TCP_NODELAY was not set for the socket.
               * Nagle's algorithm will buffer some data. */

    /* Try to reset TCP_NODELAY state for the socket.
     * Ignore possible error as no other options exist to
     * buffer data. */
    MHD_connection_set_nodelay_state_ (connection, false);
    /* TCP_NODELAY has been (hopefully) reset for the socket.
     * Nagle's algorithm will buffer some data. */
    return;
  }

  /* Need to push data after send() */
  /* If additional sys-call is required prefer to make it after the send()
   * as the next send() may consume only part of the prepared data and
   * more send() calls will be used. */
#ifdef MHD_TCP_CORK_NOPUSH
#ifdef _MHD_CORK_RESET_PUSH_DATA
#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
  /* Data can be pushed immediately by uncorking socket regardless of
   * cork state before. */
  /* This is typical for Linux, no other kernel with
   * such behavior are known so far. */

  /* No need to check the current state of TCP_CORK / TCP_NOPUSH
   * as reset of cork will push the data anyway. */
  return; /* Data may be pushed by resetting of
           * TCP_CORK / TCP_NOPUSH after send() */
#else  /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
  /* Reset of TCP_CORK / TCP_NOPUSH will push the data
   * only if socket is corked. */

#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS
  /* Data can be pushed immediately by setting TCP_NODELAY regardless
   * of TCP_NODDELAY or corking state before. */

  /* Dead code currently, no known kernels with such behavior. */
  return; /* Data may be pushed by setting of TCP_NODELAY after send().
             No need to make extra sys-calls before send().*/
#else  /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */

#ifdef _MHD_NODELAY_SET_PUSH_DATA
  /* Setting of TCP_NODELAY will push the data only if
   * both TCP_NODELAY and TCP_CORK / TCP_NOPUSH were not set. */

  /* Data can be pushed immediately by uncorking socket if
   * socket was corked before or by setting TCP_NODELAY if
   * socket was not corked and TCP_NODELAY was not set before. */

  /* Dead code currently as Linux is the only kernel that push
   * data by setting of TCP_NODELAY and Linux push data always. */
#else  /* ! _MHD_NODELAY_SET_PUSH_DATA */
  /* Data can be pushed immediately by uncorking socket or
   * can be pushed by send() on uncorked socket if
   * TCP_NODELAY was set *before*. */

  /* This is typical FreeBSD behavior. */
#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */

  if (_MHD_ON == connection->sk_corked)
    return; /* Socket is corked. Data can be pushed by resetting of
             * TCP_CORK / TCP_NOPUSH after send() */
  else if (_MHD_OFF == connection->sk_corked)
  {
    /* The socket is not corked. */
    if (_MHD_ON == connection->sk_nodelay)
      return; /* TCP_NODELAY was already set,
               * data will be pushed automatically by the next send() */
#ifdef _MHD_NODELAY_SET_PUSH_DATA
    else if (_MHD_UNKNOWN == connection->sk_nodelay)
    {
      /* Setting TCP_NODELAY may push data.
       * Cork socket here and uncork after send(). */
      if (MHD_connection_set_cork_state_ (connection, true))
        return; /* The connection has been corked.
                 * Data can be pushed by resetting of
                 * TCP_CORK / TCP_NOPUSH after send() */
      else
      {
        /* The socket cannot be corked.
         * Really unlikely to happen on TCP connections */
        /* Have to set TCP_NODELAY.
         * If TCP_NODELAY real system state was OFF then
         * already buffered data may be pushed here, but this is unlikely
         * to happen as it is only a backup solution when corking has failed.
         * Ignore possible error here as no other options exist to
         * push data. */
        MHD_connection_set_nodelay_state_ (connection, true);
        /* TCP_NODELAY has been (hopefully) set for the socket.
         * The data will be pushed by the next send(). */
        return;
      }
    }
#endif /* _MHD_NODELAY_SET_PUSH_DATA */
    else
    {
#ifdef _MHD_NODELAY_SET_PUSH_DATA
      /* TCP_NODELAY was switched off and
       * the socket is not corked. */
#else  /* ! _MHD_NODELAY_SET_PUSH_DATA */
      /* Socket is not corked and TCP_NODELAY was not set or unknown. */
#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */

      /* At least one additional sys-call is required. */
      /* Setting TCP_NODELAY is optimal here as data will be pushed
       * automatically by the next send() and no additional
       * sys-call are needed after the send(). */
      if (MHD_connection_set_nodelay_state_ (connection, true))
        return;
      else
      {
        /* Failed to set TCP_NODELAY for the socket.
         * Really unlikely to happen on TCP connections. */
        /* Cork the socket here and make additional sys-call
         * to uncork the socket after send(). */
        /* Ignore possible error here as no other options exist to
         * push data. */
        MHD_connection_set_cork_state_ (connection, true);
        /* The connection has been (hopefully) corked.
         * Data can be pushed by resetting of TCP_CORK / TCP_NOPUSH
         * after send() */
        return;
      }
    }
  }
  /* Corked state is unknown. Need to make sys-call here otherwise
   * data may not be pushed. */
  if (MHD_connection_set_cork_state_ (connection, true))
    return; /* The connection has been corked.
             * Data can be pushed by resetting of
             * TCP_CORK / TCP_NOPUSH after send() */
  /* The socket cannot be corked.
   * Really unlikely to happen on TCP connections */
  if (_MHD_ON == connection->sk_nodelay)
    return; /* TCP_NODELAY was already set,
             * data will be pushed by the next send() */
  /* Have to set TCP_NODELAY. */
#ifdef _MHD_NODELAY_SET_PUSH_DATA
  /* If TCP_NODELAY state was unknown (external connection) then
   * already buffered data may be pushed here, but this is unlikely
   * to happen as it is only a backup solution when corking has failed. */
#endif /* _MHD_NODELAY_SET_PUSH_DATA */
  /* Ignore possible error here as no other options exist to
   * push data. */
  MHD_connection_set_nodelay_state_ (connection, true);
  /* TCP_NODELAY has been (hopefully) set for the socket.
   * The data will be pushed by the next send(). */
  return;
#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
#else  /* ! _MHD_CORK_RESET_PUSH_DATA */
  /* Neither uncorking the socket or setting TCP_NODELAY
   * push the data immediately. */
  /* The only way to push the data is to use send() on uncorked
   * socket with TCP_NODELAY switched on . */

  /* This is a typical *BSD (except FreeBSD) and Darwin behavior. */

  /* Uncork socket if socket wasn't uncorked. */
  if (_MHD_OFF != connection->sk_corked)
    MHD_connection_set_cork_state_ (connection, false);

  /* Set TCP_NODELAY if it wasn't set. */
  if (_MHD_ON != connection->sk_nodelay)
    MHD_connection_set_nodelay_state_ (connection, true);

  return;
#endif /* ! _MHD_CORK_RESET_PUSH_DATA */
#else  /* ! MHD_TCP_CORK_NOPUSH */
  /* Buffering of data is controlled only by
   * Nagel's algorithm. */
  /* Set TCP_NODELAY if it wasn't set. */
  if (_MHD_ON != connection->sk_nodelay)
    MHD_connection_set_nodelay_state_ (connection, true);
#endif /* ! MHD_TCP_CORK_NOPUSH */
}


#ifndef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
/**
 * Send zero-sized data
 *
 * This function use send of zero-sized data to kick data from the socket
 * buffers to the network. The socket must not be corked and must have
 * TCP_NODELAY switched on.
 * Used only as last resort option, when other options are failed due to
 * some errors.
 * Should not be called on typical data processing.
 * @return true if succeed, false if failed
 */
static bool
zero_send_ (struct MHD_Connection *connection)
{
  int dummy;

  if (_MHD_YES == connection->is_nonip)
    return false;
  mhd_assert (_MHD_OFF == connection->sk_corked);
  mhd_assert (_MHD_ON == connection->sk_nodelay);
  dummy = 0; /* Mute compiler and analyzer warnings */
  if (0 == MHD_send_ (connection->socket_fd, &dummy, 0))
    return true;
#ifdef HAVE_MESSAGES
  MHD_DLOG (connection->daemon,
            _ ("Zero-send failed: %s\n"),
            MHD_socket_last_strerr_ () );
#endif /* HAVE_MESSAGES */
  return false;
}


#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */

/**
 * Handle post-send setsockopt calls.
 *
 * @param connection the MHD_Connection structure
 * @param plain_send_next set to true if plain send() or sendmsg() will be
 *                        called next,
 *                        set to false if TLS socket send(), sendfile() or
 *                        writev() will be called next.
 * @param push_data whether to push data to the network from buffers
 */
static void
post_send_setopt (struct MHD_Connection *connection,
                  bool plain_send_next,
                  bool push_data)
{
  /* Try to buffer data if not sending the final piece.
   * Final piece is indicated by push_data == true. */
  const bool buffer_data = (! push_data);

  if (_MHD_YES == connection->is_nonip)
    return;
  if (buffer_data)
    return; /* Nothing to do after send(). */

#ifndef MHD_USE_MSG_MORE
  (void) plain_send_next; /* Mute compiler warning */
#endif /* ! MHD_USE_MSG_MORE */

  /* Need to push data. */
#ifdef MHD_TCP_CORK_NOPUSH
#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS
#ifdef MHD_USE_MSG_MORE
  if (_MHD_OFF == connection->sk_corked)
  {
    if (_MHD_ON == connection->sk_nodelay)
      return; /* Data was already pushed by send(). */
  }
  /* This is Linux kernel. There are options:
   * * Push the data by setting of TCP_NODELAY (without change
   *   of the cork on the socket),
   * * Push the data by resetting of TCP_CORK.
   * The optimal choice depends on the next final send functions
   * used on the same socket. If TCP_NODELAY wasn't set then push
   * data by setting TCP_NODELAY (TCP_NODELAY will not be removed
   * and is needed to push the data by send() without MSG_MORE).
   * If send()/sendmsg() will be used next than push data by
   * resetting of TCP_CORK so next send without MSG_MORE will push
   * data to the network (without additional sys-call to push data).
   * If next final send function will not support MSG_MORE (like
   * sendfile() or TLS-connection) than push data by setting
   * TCP_NODELAY so socket will remain corked (no additional
   * sys-call before next send()). */
  if ((_MHD_ON != connection->sk_nodelay) ||
      (! plain_send_next))
  {
    if (MHD_connection_set_nodelay_state_ (connection, true))
      return; /* Data has been pushed by TCP_NODELAY. */
    /* Failed to set TCP_NODELAY for the socket.
     * Really unlikely to happen on TCP connections. */
    if (MHD_connection_set_cork_state_ (connection, false))
      return; /* Data has been pushed by uncorking the socket. */
    /* Failed to uncork the socket.
     * Really unlikely to happen on TCP connections. */

    /* The socket cannot be uncorked, no way to push data */
  }
  else
  {
    if (MHD_connection_set_cork_state_ (connection, false))
      return; /* Data has been pushed by uncorking the socket. */
    /* Failed to uncork the socket.
     * Really unlikely to happen on TCP connections. */
    if (MHD_connection_set_nodelay_state_ (connection, true))
      return; /* Data has been pushed by TCP_NODELAY. */
    /* Failed to set TCP_NODELAY for the socket.
     * Really unlikely to happen on TCP connections. */

    /* The socket cannot be uncorked, no way to push data */
  }
#else  /* ! MHD_USE_MSG_MORE */
  /* Use setting of TCP_NODELAY here to avoid sys-call
   * for corking the socket during sending of the next response. */
  if (MHD_connection_set_nodelay_state_ (connection, true))
    return; /* Data was pushed by TCP_NODELAY. */
  /* Failed to set TCP_NODELAY for the socket.
   * Really unlikely to happen on TCP connections. */
  if (MHD_connection_set_cork_state_ (connection, false))
    return; /* Data was pushed by uncorking the socket. */
  /* Failed to uncork the socket.
   * Really unlikely to happen on TCP connections. */

  /* The socket remains corked, no way to push data */
#endif /* ! MHD_USE_MSG_MORE */
#else  /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
  if (MHD_connection_set_cork_state_ (connection, false))
    return; /* Data was pushed by uncorking the socket. */
  /* Failed to uncork the socket.
   * Really unlikely to happen on TCP connections. */
  return; /* Socket remains corked, no way to push data */
#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
#else  /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
  /* This is a typical *BSD or Darwin kernel. */

  if (_MHD_OFF == connection->sk_corked)
  {
    if (_MHD_ON == connection->sk_nodelay)
      return; /* Data was already pushed by send(). */

    /* Unlikely to reach this code.
     * TCP_NODELAY should be turned on before send(). */
    if (MHD_connection_set_nodelay_state_ (connection, true))
    {
      /* TCP_NODELAY has been set on uncorked socket.
       * Use zero-send to push the data. */
      if (zero_send_ (connection))
        return; /* The data has been pushed by zero-send. */
    }

    /* Failed to push the data by all means. */
    /* There is nothing left to try. */
  }
  else
  {
#ifdef _MHD_CORK_RESET_PUSH_DATA
    enum MHD_tristate old_cork_state = connection->sk_corked;
#endif /* _MHD_CORK_RESET_PUSH_DATA */
    /* The socket is corked or cork state is unknown. */

    if (MHD_connection_set_cork_state_ (connection, false))
    {
#ifdef _MHD_CORK_RESET_PUSH_DATA
      /* FreeBSD kernel */
      if (_MHD_OFF == old_cork_state)
        return; /* Data has been pushed by uncorking the socket. */
#endif /* _MHD_CORK_RESET_PUSH_DATA */

      /* Unlikely to reach this code.
       * The data should be pushed by uncorking (FreeBSD) or
       * the socket should be uncorked before send(). */
      if ((_MHD_ON == connection->sk_nodelay) ||
          (MHD_connection_set_nodelay_state_ (connection, true)))
      {
        /* TCP_NODELAY is turned ON on uncorked socket.
         * Use zero-send to push the data. */
        if (zero_send_ (connection))
          return; /* The data has been pushed by zero-send. */
      }
    }
    /* The socket remains corked. Data cannot be pushed. */
  }
#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
#else  /* ! MHD_TCP_CORK_NOPUSH */
  /* Corking is not supported. Buffering is controlled
   * by TCP_NODELAY only. */
  mhd_assert (_MHD_ON != connection->sk_corked);
  if (_MHD_ON == connection->sk_nodelay)
    return; /* Data was already pushed by send(). */

  /* Unlikely to reach this code.
   * TCP_NODELAY should be turned on before send(). */
  if (MHD_connection_set_nodelay_state_ (connection, true))
  {
    /* TCP_NODELAY has been set.
     * Use zero-send to push the data. */
    if (zero_send_ (connection))
      return; /* The data has been pushed by zero-send. */
  }

  /* Failed to push the data. */
#endif /* ! MHD_TCP_CORK_NOPUSH */
#ifdef HAVE_MESSAGES
  MHD_DLOG (connection->daemon,
            _ ("Failed to push the data from buffers to the network. "
               "Client may experience some delay "
               "(usually in range 200ms - 5 sec).\n"));
#endif /* HAVE_MESSAGES */
  return;
}


ssize_t
MHD_send_data_ (struct MHD_Connection *connection,
                const char *buffer,
                size_t buffer_size,
                bool push_data)
{
  MHD_socket s = connection->socket_fd;
  ssize_t ret;
#ifdef HTTPS_SUPPORT
  const bool tls_conn = (connection->daemon->options & MHD_USE_TLS);
#else  /* ! HTTPS_SUPPORT */
  const bool tls_conn = false;
#endif /* ! HTTPS_SUPPORT */

  if ( (MHD_INVALID_SOCKET == s) ||
       (MHD_CONNECTION_CLOSED == connection->state) )
  {
    return MHD_ERR_NOTCONN_;
  }

  if (buffer_size > SSIZE_MAX)
  {
    buffer_size = SSIZE_MAX; /* Max return value */
    push_data = false; /* Incomplete send */
  }

  if (tls_conn)
  {
#ifdef HTTPS_SUPPORT
    pre_send_setopt (connection, (! tls_conn), push_data);
    ret = gnutls_record_send (connection->tls_session,
                              buffer,
                              buffer_size);
    if (GNUTLS_E_AGAIN == ret)
    {
#ifdef EPOLL_SUPPORT
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif
      return MHD_ERR_AGAIN_;
    }
    if (GNUTLS_E_INTERRUPTED == ret)
      return MHD_ERR_AGAIN_;
    if ( (GNUTLS_E_ENCRYPTION_FAILED == ret) ||
         (GNUTLS_E_INVALID_SESSION == ret) ||
         (GNUTLS_E_COMPRESSION_FAILED == ret) ||
         (GNUTLS_E_EXPIRED == ret) ||
         (GNUTLS_E_HASH_FAILED == ret) )
      return MHD_ERR_TLS_;
    if ( (GNUTLS_E_PUSH_ERROR == ret) ||
         (GNUTLS_E_INTERNAL_ERROR == ret) ||
         (GNUTLS_E_CRYPTODEV_IOCTL_ERROR == ret) ||
         (GNUTLS_E_CRYPTODEV_DEVICE_ERROR == ret) )
      return MHD_ERR_PIPE_;
#if defined(GNUTLS_E_PREMATURE_TERMINATION)
    if (GNUTLS_E_PREMATURE_TERMINATION == ret)
      return MHD_ERR_CONNRESET_;
#elif defined(GNUTLS_E_UNEXPECTED_PACKET_LENGTH)
    if (GNUTLS_E_UNEXPECTED_PACKET_LENGTH == ret)
      return MHD_ERR_CONNRESET_;
#endif /* GNUTLS_E_UNEXPECTED_PACKET_LENGTH */
    if (GNUTLS_E_MEMORY_ERROR == ret)
      return MHD_ERR_NOMEM_;
    if (ret < 0)
    {
      /* Treat any other error as hard error. */
      return MHD_ERR_NOTCONN_;
    }
#ifdef EPOLL_SUPPORT
    /* Unlike non-TLS connections, do not reset "write-ready" if
     * sent amount smaller than provided amount, as TLS
     * connections may break data into smaller parts for sending. */
#endif /* EPOLL_SUPPORT */
#else  /* ! HTTPS_SUPPORT  */
    ret = MHD_ERR_NOTCONN_;
#endif /* ! HTTPS_SUPPORT  */
  }
  else
  {
    /* plaintext transmission */
    if (buffer_size > MHD_SCKT_SEND_MAX_SIZE_)
    {
      buffer_size = MHD_SCKT_SEND_MAX_SIZE_; /* send() return value limit */
      push_data = false; /* Incomplete send */
    }

    pre_send_setopt (connection, (! tls_conn), push_data);
#ifdef MHD_USE_MSG_MORE
    ret = MHD_send4_ (s,
                      buffer,
                      buffer_size,
                      push_data ? 0 : MSG_MORE);
#else
    ret = MHD_send4_ (s,
                      buffer,
                      buffer_size,
                      0);
#endif

    if (0 > ret)
    {
      const int err = MHD_socket_get_error_ ();

      if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
      {
#ifdef EPOLL_SUPPORT
        /* EAGAIN, no longer write-ready */
        connection->epoll_state &=
          ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
        return MHD_ERR_AGAIN_;
      }
      if (MHD_SCKT_ERR_IS_EINTR_ (err))
        return MHD_ERR_AGAIN_;
      if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err))
        return MHD_ERR_CONNRESET_;
      if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_))
        return MHD_ERR_PIPE_;
      if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_))
        return MHD_ERR_OPNOTSUPP_;
      if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_))
        return MHD_ERR_NOTCONN_;
      if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_))
        return MHD_ERR_INVAL_;
      if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err))
        return MHD_ERR_NOMEM_;
      if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_))
        return MHD_ERR_BADF_;
      /* Treat any other error as a hard error. */
      return MHD_ERR_NOTCONN_;
    }
#ifdef EPOLL_SUPPORT
    else if (buffer_size > (size_t) ret)
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
  }

  /* If there is a need to push the data from network buffers
   * call post_send_setopt(). */
  /* If TLS connection is used then next final send() will be
   * without MSG_MORE support. If non-TLS connection is used
   * it's unknown whether sendfile() will be used or not so
   * assume that next call will be the same, like this call. */
  if ( (push_data) &&
       (buffer_size == (size_t) ret) )
    post_send_setopt (connection, (! tls_conn), push_data);

  return ret;
}


ssize_t
MHD_send_hdr_and_body_ (struct MHD_Connection *connection,
                        const char *header,
                        size_t header_size,
                        bool never_push_hdr,
                        const char *body,
                        size_t body_size,
                        bool complete_response)
{
  ssize_t ret;
  bool push_hdr;
  bool push_body;
  MHD_socket s = connection->socket_fd;
#ifndef _WIN32
#define _MHD_SEND_VEC_MAX   MHD_SCKT_SEND_MAX_SIZE_
#else  /* ! _WIN32 */
#define _MHD_SEND_VEC_MAX   UINT32_MAX
#endif /* ! _WIN32 */
#ifdef MHD_VECT_SEND
#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
  struct iovec vector[2];
#ifdef HAVE_SENDMSG
  struct msghdr msg;
#endif /* HAVE_SENDMSG */
#endif /* HAVE_SENDMSG || HAVE_WRITEV */
#ifdef _WIN32
  WSABUF vector[2];
  DWORD vec_sent;
#endif /* _WIN32 */
  bool no_vec; /* Is vector-send() disallowed? */

  no_vec = false;
#ifdef HTTPS_SUPPORT
  no_vec = no_vec || (connection->daemon->options & MHD_USE_TLS);
#endif /* HTTPS_SUPPORT */
#if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL) ) && \
  defined(MHD_SEND_SPIPE_SEND_SUPPRESS_POSSIBLE) && \
  defined(MHD_SEND_SPIPE_SUPPRESS_NEEDED)
  no_vec = no_vec || (! connection->daemon->sigpipe_blocked &&
                      ! connection->sk_spipe_suppress);
#endif /* (!HAVE_SENDMSG || ! MSG_NOSIGNAL) &&
          MHD_SEND_SPIPE_SEND_SUPPRESS_POSSIBLE &&
          MHD_SEND_SPIPE_SUPPRESS_NEEDED */
#endif /* MHD_VECT_SEND */

  mhd_assert ( (NULL != body) || (0 == body_size) );

  if ( (MHD_INVALID_SOCKET == s) ||
       (MHD_CONNECTION_CLOSED == connection->state) )
  {
    return MHD_ERR_NOTCONN_;
  }

  push_body = complete_response;

  if (! never_push_hdr)
  {
    if (! complete_response)
      push_hdr = true; /* Push the header as the client may react
                        * on header alone while the body data is
                        * being prepared. */
    else
    {
      if (1400 > (header_size + body_size))
        push_hdr = false;  /* Do not push the header as complete
                           * reply is already ready and the whole
                           * reply most probably will fit into
                           * the single IP packet. */
      else
        push_hdr = true;   /* Push header alone so client may react
                           * on it while reply body is being delivered. */
    }
  }
  else
    push_hdr = false;

  if (complete_response && (0 == body_size))
    push_hdr = true; /* The header alone is equal to the whole response. */

  if (
#ifdef MHD_VECT_SEND
    (no_vec) ||
    (0 == body_size) ||
    ((size_t) SSIZE_MAX <= header_size) ||
    ((size_t) _MHD_SEND_VEC_MAX < header_size)
#ifdef _WIN32
    || ((size_t) UINT_MAX < header_size)
#endif /* _WIN32 */
#else  /* ! MHD_VECT_SEND */
    true
#endif /* ! MHD_VECT_SEND */
    )
  {
    ret = MHD_send_data_ (connection,
                          header,
                          header_size,
                          push_hdr);

    if ( (header_size == (size_t) ret) &&
         ((size_t) SSIZE_MAX > header_size) &&
         (0 != body_size) &&
         (connection->sk_nonblck) )
    {
      ssize_t ret2;
      /* The header has been sent completely.
       * Try to send the reply body without waiting for
       * the next round. */
      /* Make sure that sum of ret + ret2 will not exceed SSIZE_MAX as
       * function needs to return positive value if succeed. */
      if ( (((size_t) SSIZE_MAX) - ((size_t) ret)) <  body_size)
      {
        body_size = (((size_t) SSIZE_MAX) - ((size_t) ret));
        complete_response = false;
        push_body = complete_response;
      }

      ret2 = MHD_send_data_ (connection,
                             body,
                             body_size,
                             push_body);
      if (0 < ret2)
        return ret + ret2; /* Total data sent */
      if (MHD_ERR_AGAIN_ == ret2)
        return ret;

      return ret2; /* Error code */
    }
    return ret;
  }
#ifdef MHD_VECT_SEND

  if ( ((size_t) SSIZE_MAX <= body_size) ||
       ((size_t) SSIZE_MAX < (header_size + body_size)) )
  {
    /* Return value limit */
    body_size = SSIZE_MAX - header_size;
    complete_response = false;
    push_body = complete_response;
  }
#if (SSIZE_MAX != _MHD_SEND_VEC_MAX) || (_MHD_SEND_VEC_MAX + 0 == 0)
  if (((size_t) _MHD_SEND_VEC_MAX <= body_size) ||
      ((size_t) _MHD_SEND_VEC_MAX < (header_size + body_size)))
  {
    /* Send total amount limit */
    body_size = _MHD_SEND_VEC_MAX - header_size;
    complete_response = false;
    push_body = complete_response;
  }
#endif /* SSIZE_MAX != _MHD_SEND_VEC_MAX */

  pre_send_setopt (connection,
#ifdef HAVE_SENDMSG
                   true,
#else  /* ! HAVE_SENDMSG */
                   false,
#endif /* ! HAVE_SENDMSG */
                   push_hdr || push_body);
#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
  vector[0].iov_base = _MHD_DROP_CONST (header);
  vector[0].iov_len = header_size;
  vector[1].iov_base = _MHD_DROP_CONST (body);
  vector[1].iov_len = body_size;

#if defined(HAVE_SENDMSG)
  memset (&msg, 0, sizeof(msg));
  msg.msg_iov = vector;
  msg.msg_iovlen = 2;

  ret = sendmsg (s, &msg, MSG_NOSIGNAL_OR_ZERO);
#elif defined(HAVE_WRITEV)
  ret = writev (s, vector, 2);
#endif /* HAVE_WRITEV */
#endif /* HAVE_SENDMSG || HAVE_WRITEV */
#ifdef _WIN32
  if ((size_t) UINT_MAX < body_size)
  {
    /* Send item size limit */
    body_size = UINT_MAX;
    complete_response = false;
    push_body = complete_response;
  }
  vector[0].buf = (char *) _MHD_DROP_CONST (header);
  vector[0].len = (unsigned long) header_size;
  vector[1].buf = (char *) _MHD_DROP_CONST (body);
  vector[1].len = (unsigned long) body_size;

  ret = WSASend (s, vector, 2, &vec_sent, 0, NULL, NULL);
  if (0 == ret)
    ret = (ssize_t) vec_sent;
  else
    ret = -1;
#endif /* _WIN32 */

  if (0 > ret)
  {
    const int err = MHD_socket_get_error_ ();

    if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
    {
#ifdef EPOLL_SUPPORT
      /* EAGAIN, no longer write-ready */
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
      return MHD_ERR_AGAIN_;
    }
    if (MHD_SCKT_ERR_IS_EINTR_ (err))
      return MHD_ERR_AGAIN_;
    if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err))
      return MHD_ERR_CONNRESET_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_))
      return MHD_ERR_PIPE_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_))
      return MHD_ERR_OPNOTSUPP_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_))
      return MHD_ERR_NOTCONN_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_))
      return MHD_ERR_INVAL_;
    if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err))
      return MHD_ERR_NOMEM_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_))
      return MHD_ERR_BADF_;
    /* Treat any other error as a hard error. */
    return MHD_ERR_NOTCONN_;
  }
#ifdef EPOLL_SUPPORT
  else if ((header_size + body_size) > (size_t) ret)
    connection->epoll_state &=
      ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */

  /* If there is a need to push the data from network buffers
   * call post_send_setopt(). */
  if ( (push_body) &&
       ((header_size + body_size) == (size_t) ret) )
  {
    /* Complete reply has been sent. */
    /* If TLS connection is used then next final send() will be
     * without MSG_MORE support. If non-TLS connection is used
     * it's unknown whether next 'send' will be plain send() / sendmsg() or
     * sendfile() will be used so assume that next final send() will be
     * the same, like for this response. */
    post_send_setopt (connection,
#ifdef HAVE_SENDMSG
                      true,
#else  /* ! HAVE_SENDMSG */
                      false,
#endif /* ! HAVE_SENDMSG */
                      true);
  }
  else if ( (push_hdr) &&
            (header_size <= (size_t) ret))
  {
    /* The header has been sent completely and there is a
     * need to push the header data. */
    /* Luckily the type of send function will be used next is known. */
    post_send_setopt (connection,
#if defined(_MHD_HAVE_SENDFILE)
                      MHD_resp_sender_std == connection->rp.resp_sender,
#else  /* ! _MHD_HAVE_SENDFILE */
                      true,
#endif /* ! _MHD_HAVE_SENDFILE */
                      true);
  }

  return ret;
#else  /* ! MHD_VECT_SEND */
  mhd_assert (false);
  return MHD_ERR_CONNRESET_; /* Unreachable. Mute warnings. */
#endif /* ! MHD_VECT_SEND */
}


#if defined(_MHD_HAVE_SENDFILE)
ssize_t
MHD_send_sendfile_ (struct MHD_Connection *connection)
{
  ssize_t ret;
  const int file_fd = connection->rp.response->fd;
  uint64_t left;
  uint64_t offsetu64;
#ifndef HAVE_SENDFILE64
  const uint64_t max_off_t = (uint64_t) OFF_T_MAX;
#else  /* HAVE_SENDFILE64 */
  const uint64_t max_off_t = (uint64_t) OFF64_T_MAX;
#endif /* HAVE_SENDFILE64 */
#ifdef MHD_LINUX_SOLARIS_SENDFILE
#ifndef HAVE_SENDFILE64
  off_t offset;
#else  /* HAVE_SENDFILE64 */
  off64_t offset;
#endif /* HAVE_SENDFILE64 */
#endif /* MHD_LINUX_SOLARIS_SENDFILE */
#ifdef HAVE_FREEBSD_SENDFILE
  off_t sent_bytes;
  int flags = 0;
#endif
#ifdef HAVE_DARWIN_SENDFILE
  off_t len;
#endif /* HAVE_DARWIN_SENDFILE */
  const bool used_thr_p_c =
    MHD_D_IS_USING_THREAD_PER_CONN_ (connection->daemon);
  const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ :
                            MHD_SENFILE_CHUNK_;
  size_t send_size = 0;
  bool push_data;
  mhd_assert (MHD_resp_sender_sendfile == connection->rp.resp_sender);
  mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS));

  offsetu64 = connection->rp.rsp_write_position
              + connection->rp.response->fd_off;
  if (max_off_t < offsetu64)
  {   /* Retry to send with standard 'send()'. */
    connection->rp.resp_sender = MHD_resp_sender_std;
    return MHD_ERR_AGAIN_;
  }

  left = connection->rp.response->total_size
         - connection->rp.rsp_write_position;

  if ( (uint64_t) SSIZE_MAX < left)
    left = SSIZE_MAX;

  /* Do not allow system to stick sending on single fast connection:
   * use 128KiB chunks (2MiB for thread-per-connection). */
  if (chunk_size < left)
  {
    send_size = chunk_size;
    push_data = false; /* No need to push data, there is more to send. */
  }
  else
  {
    send_size = (size_t) left;
    push_data = true; /* Final piece of data, need to push to the network. */
  }
  pre_send_setopt (connection, false, push_data);

#ifdef MHD_LINUX_SOLARIS_SENDFILE
#ifndef HAVE_SENDFILE64
  offset = (off_t) offsetu64;
  ret = sendfile (connection->socket_fd,
                  file_fd,
                  &offset,
                  send_size);
#else  /* HAVE_SENDFILE64 */
  offset = (off64_t) offsetu64;
  ret = sendfile64 (connection->socket_fd,
                    file_fd,
                    &offset,
                    send_size);
#endif /* HAVE_SENDFILE64 */
  if (0 > ret)
  {
    const int err = MHD_socket_get_error_ ();
    if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
    {
#ifdef EPOLL_SUPPORT
      /* EAGAIN --- no longer write-ready */
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
      return MHD_ERR_AGAIN_;
    }
    if (MHD_SCKT_ERR_IS_EINTR_ (err))
      return MHD_ERR_AGAIN_;
#ifdef HAVE_LINUX_SENDFILE
    if (MHD_SCKT_ERR_IS_ (err,
                          MHD_SCKT_EBADF_))
      return MHD_ERR_BADF_;
    /* sendfile() failed with EINVAL if mmap()-like operations are not
       supported for FD or other 'unusual' errors occurred, so we should try
       to fall back to 'SEND'; see also this thread for info on
       odd libc/Linux behavior with sendfile:
       http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */
    connection->rp.resp_sender = MHD_resp_sender_std;
    return MHD_ERR_AGAIN_;
#else  /* HAVE_SOLARIS_SENDFILE */
    if ( (EAFNOSUPPORT == err) ||
         (EINVAL == err) ||
         (EOPNOTSUPP == err) )
    {     /* Retry with standard file reader. */
      connection->rp.resp_sender = MHD_resp_sender_std;
      return MHD_ERR_AGAIN_;
    }
    if ( (ENOTCONN == err) ||
         (EPIPE == err) )
    {
      return MHD_ERR_CONNRESET_;
    }
    return MHD_ERR_BADF_;   /* Fail hard */
#endif /* HAVE_SOLARIS_SENDFILE */
  }
#ifdef EPOLL_SUPPORT
  else if (send_size > (size_t) ret)
    connection->epoll_state &=
      ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
#elif defined(HAVE_FREEBSD_SENDFILE)
#ifdef SF_FLAGS
  flags = used_thr_p_c ?
          freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_;
#endif /* SF_FLAGS */
  if (0 != sendfile (file_fd,
                     connection->socket_fd,
                     (off_t) offsetu64,
                     send_size,
                     NULL,
                     &sent_bytes,
                     flags))
  {
    const int err = MHD_socket_get_error_ ();
    if (MHD_SCKT_ERR_IS_EAGAIN_ (err) ||
        MHD_SCKT_ERR_IS_EINTR_ (err) ||
        (EBUSY == err) )
    {
      mhd_assert (SSIZE_MAX >= sent_bytes);
      if (0 != sent_bytes)
        return (ssize_t) sent_bytes;

      return MHD_ERR_AGAIN_;
    }
    /* Some unrecoverable error. Possibly file FD is not suitable
     * for sendfile(). Retry with standard send(). */
    connection->rp.resp_sender = MHD_resp_sender_std;
    return MHD_ERR_AGAIN_;
  }
  mhd_assert (0 < sent_bytes);
  mhd_assert (SSIZE_MAX >= sent_bytes);
  ret = (ssize_t) sent_bytes;
#elif defined(HAVE_DARWIN_SENDFILE)
  len = (off_t) send_size; /* chunk always fit */
  if (0 != sendfile (file_fd,
                     connection->socket_fd,
                     (off_t) offsetu64,
                     &len,
                     NULL,
                     0))
  {
    const int err = MHD_socket_get_error_ ();
    if (MHD_SCKT_ERR_IS_EAGAIN_ (err) ||
        MHD_SCKT_ERR_IS_EINTR_ (err))
    {
      mhd_assert (0 <= len);
      mhd_assert (SSIZE_MAX >= len);
      mhd_assert (send_size >= (size_t) len);
      if (0 != len)
        return (ssize_t) len;

      return MHD_ERR_AGAIN_;
    }
    if ((ENOTCONN == err) ||
        (EPIPE == err) )
      return MHD_ERR_CONNRESET_;
    if ((ENOTSUP == err) ||
        (EOPNOTSUPP == err) )
    {     /* This file FD is not suitable for sendfile().
           * Retry with standard send(). */
      connection->rp.resp_sender = MHD_resp_sender_std;
      return MHD_ERR_AGAIN_;
    }
    return MHD_ERR_BADF_;   /* Return hard error. */
  }
  mhd_assert (0 <= len);
  mhd_assert (SSIZE_MAX >= len);
  mhd_assert (send_size >= (size_t) len);
  ret = (ssize_t) len;
#endif /* HAVE_FREEBSD_SENDFILE */

  /* If there is a need to push the data from network buffers
   * call post_send_setopt(). */
  /* It's unknown whether sendfile() will be used in the next
   * response so assume that next response will be the same. */
  if ( (push_data) &&
       (send_size == (size_t) ret) )
    post_send_setopt (connection, false, push_data);

  return ret;
}


#endif /* _MHD_HAVE_SENDFILE */

#if defined(MHD_VECT_SEND)


/**
 * Function sends iov data by system sendmsg or writev function.
 *
 * Connection must be in non-TLS (non-HTTPS) mode.
 *
 * @param connection the MHD connection structure
 * @param r_iov the pointer to iov data structure with tracking
 * @param push_data set to true to force push the data to the network from
 *                  system buffers (usually set for the last piece of data),
 *                  set to false to prefer holding incomplete network packets
 *                  (more data will be send for the same reply).
 * @return actual number of bytes sent
 */
static ssize_t
send_iov_nontls (struct MHD_Connection *connection,
                 struct MHD_iovec_track_ *const r_iov,
                 bool push_data)
{
  ssize_t res;
  size_t items_to_send;
#ifdef HAVE_SENDMSG
  struct msghdr msg;
#elif defined(MHD_WINSOCK_SOCKETS)
  DWORD bytes_sent;
  DWORD cnt_w;
#endif /* MHD_WINSOCK_SOCKETS */

  mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS));

  if ( (MHD_INVALID_SOCKET == connection->socket_fd) ||
       (MHD_CONNECTION_CLOSED == connection->state) )
  {
    return MHD_ERR_NOTCONN_;
  }

  items_to_send = r_iov->cnt - r_iov->sent;
#ifdef _MHD_IOV_MAX
  if (_MHD_IOV_MAX < items_to_send)
  {
    mhd_assert (0 < _MHD_IOV_MAX);
    if (0 == _MHD_IOV_MAX)
      return MHD_ERR_NOTCONN_; /* Should never happen */
    items_to_send = _MHD_IOV_MAX;
    push_data = false; /* Incomplete response */
  }
#endif /* _MHD_IOV_MAX */
#ifdef HAVE_SENDMSG
  memset (&msg, 0, sizeof(struct msghdr));
  msg.msg_iov = r_iov->iov + r_iov->sent;
  msg.msg_iovlen = items_to_send;

  pre_send_setopt (connection, true, push_data);
#ifdef MHD_USE_MSG_MORE
  res = sendmsg (connection->socket_fd, &msg,
                 MSG_NOSIGNAL_OR_ZERO | (push_data ? 0 : MSG_MORE));
#else  /* ! MHD_USE_MSG_MORE */
  res = sendmsg (connection->socket_fd, &msg, MSG_NOSIGNAL_OR_ZERO);
#endif /* ! MHD_USE_MSG_MORE */
#elif defined(HAVE_WRITEV)
  pre_send_setopt (connection, true, push_data);
  res = writev (connection->socket_fd, r_iov->iov + r_iov->sent,
                items_to_send);
#elif defined(MHD_WINSOCK_SOCKETS)
#ifdef _WIN64
  if (items_to_send > UINT32_MAX)
  {
    cnt_w = UINT32_MAX;
    push_data = false; /* Incomplete response */
  }
  else
    cnt_w = (DWORD) items_to_send;
#else  /* ! _WIN64 */
  cnt_w = (DWORD) items_to_send;
#endif /* ! _WIN64 */
  pre_send_setopt (connection, true, push_data);
  if (0 == WSASend (connection->socket_fd,
                    (LPWSABUF) (r_iov->iov + r_iov->sent),
                    cnt_w,
                    &bytes_sent, 0, NULL, NULL))
    res = (ssize_t) bytes_sent;
  else
    res = -1;
#else /* !HAVE_SENDMSG && !HAVE_WRITEV && !MHD_WINSOCK_SOCKETS */
#error No vector-send function available
#endif

  if (0 > res)
  {
    const int err = MHD_socket_get_error_ ();

    if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
    {
#ifdef EPOLL_SUPPORT
      /* EAGAIN --- no longer write-ready */
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
      return MHD_ERR_AGAIN_;
    }
    if (MHD_SCKT_ERR_IS_EINTR_ (err))
      return MHD_ERR_AGAIN_;
    if (MHD_SCKT_ERR_IS_REMOTE_DISCNN_ (err))
      return MHD_ERR_CONNRESET_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EPIPE_))
      return MHD_ERR_PIPE_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EOPNOTSUPP_))
      return MHD_ERR_OPNOTSUPP_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ENOTCONN_))
      return MHD_ERR_NOTCONN_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EINVAL_))
      return MHD_ERR_INVAL_;
    if (MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err))
      return MHD_ERR_NOMEM_;
    if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_EBADF_))
      return MHD_ERR_BADF_;
    /* Treat any other error as a hard error. */
    return MHD_ERR_NOTCONN_;
  }

  /* Some data has been sent */
  if (1)
  {
    size_t track_sent = (size_t) res;
    /* Adjust the internal tracking information for the iovec to
     * take this last send into account. */
    while ((0 != track_sent) && (r_iov->iov[r_iov->sent].iov_len <= track_sent))
    {
      track_sent -= r_iov->iov[r_iov->sent].iov_len;
      r_iov->sent++; /* The iov element has been completely sent */
      mhd_assert ((r_iov->cnt > r_iov->sent) || (0 == track_sent));
    }

    if (r_iov->cnt == r_iov->sent)
      post_send_setopt (connection, true, push_data);
    else
    {
#ifdef EPOLL_SUPPORT
      connection->epoll_state &=
        ~((enum MHD_EpollState) MHD_EPOLL_STATE_WRITE_READY);
#endif /* EPOLL_SUPPORT */
      if (0 != track_sent)
      {
        mhd_assert (r_iov->cnt > r_iov->sent);
        /* The last iov element has been partially sent */
        r_iov->iov[r_iov->sent].iov_base =
          (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + track_sent);
        r_iov->iov[r_iov->sent].iov_len -= (MHD_iov_size_) track_sent;
      }
    }
  }

  return res;
}


#endif /* MHD_VECT_SEND */

#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \
  defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED)


/**
 * Function sends iov data by sending buffers one-by-one by standard
 * data send function.
 *
 * Connection could be in HTTPS or non-HTTPS mode.
 *
 * @param connection the MHD connection structure
 * @param r_iov the pointer to iov data structure with tracking
 * @param push_data set to true to force push the data to the network from
 *                  system buffers (usually set for the last piece of data),
 *                  set to false to prefer holding incomplete network packets
 *                  (more data will be send for the same reply).
 * @return actual number of bytes sent
 */
static ssize_t
send_iov_emu (struct MHD_Connection *connection,
              struct MHD_iovec_track_ *const r_iov,
              bool push_data)
{
  const bool non_blk = connection->sk_nonblck;
  size_t total_sent;
  ssize_t res;

  mhd_assert (NULL != r_iov->iov);
  total_sent = 0;
  do
  {
    if ((size_t) SSIZE_MAX - total_sent < r_iov->iov[r_iov->sent].iov_len)
      return (ssize_t) total_sent; /* return value would overflow */

    res = MHD_send_data_ (connection,
                          r_iov->iov[r_iov->sent].iov_base,
                          r_iov->iov[r_iov->sent].iov_len,
                          push_data && (r_iov->cnt == r_iov->sent + 1));
    if (0 > res)
    {
      /* Result is an error */
      if (0 == total_sent)
        return res; /* Nothing was sent, return result as is */

      if (MHD_ERR_AGAIN_ == res)
        return (ssize_t) total_sent; /* Return the amount of the sent data */

      return res; /* Any kind of a hard error */
    }

    total_sent += (size_t) res;

    if (r_iov->iov[r_iov->sent].iov_len != (size_t) res)
    {
      const size_t sent = (size_t) res;
      /* Incomplete buffer has been sent.
       * Adjust buffer of the last element. */
      r_iov->iov[r_iov->sent].iov_base =
        (void *) ((uint8_t *) r_iov->iov[r_iov->sent].iov_base + sent);
      r_iov->iov[r_iov->sent].iov_len -= (MHD_iov_size_) sent;

      return (ssize_t) total_sent;
    }
    /* The iov element has been completely sent */
    r_iov->sent++;
  } while ((r_iov->cnt > r_iov->sent) && (non_blk));

  return (ssize_t) total_sent;
}


#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT
          || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */


ssize_t
MHD_send_iovec_ (struct MHD_Connection *connection,
                 struct MHD_iovec_track_ *const r_iov,
                 bool push_data)
{
#ifdef MHD_VECT_SEND
#if defined(HTTPS_SUPPORT) || \
  defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED)
  bool use_iov_send = true;
#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */
#endif /* MHD_VECT_SEND */

  mhd_assert (NULL != connection->rp.resp_iov.iov);
  mhd_assert (NULL != connection->rp.response->data_iov);
  mhd_assert (connection->rp.resp_iov.cnt > connection->rp.resp_iov.sent);
#ifdef MHD_VECT_SEND
#if defined(HTTPS_SUPPORT) || \
  defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED)
#ifdef HTTPS_SUPPORT
  use_iov_send = use_iov_send &&
                 (0 == (connection->daemon->options & MHD_USE_TLS));
#endif /* HTTPS_SUPPORT */
#ifdef _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED
  use_iov_send = use_iov_send && (connection->daemon->sigpipe_blocked ||
                                  connection->sk_spipe_suppress);
#endif /* _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */
  if (use_iov_send)
#endif /* HTTPS_SUPPORT || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */
  return send_iov_nontls (connection, r_iov, push_data);
#endif /* MHD_VECT_SEND */

#if ! defined(MHD_VECT_SEND) || defined(HTTPS_SUPPORT) || \
  defined(_MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED)
  return send_iov_emu (connection, r_iov, push_data);
#endif /* !MHD_VECT_SEND || HTTPS_SUPPORT
          || _MHD_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */
}
