blob: fc1aaa007cdf6bdd5627c44f020b4d7d6084b4e1 [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2017,2020 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 = (0 != (connection->daemon->options
& MHD_USE_THREAD_PER_CONNECTION));
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 */
}