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