blob: 27cdd4ca459c93ebd81ae432228258ba5337d62a [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
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 lib/daemon_poll.c
* @brief functions to run poll-based event loop
* @author Christian Grothoff
*/
#include "internal.h"
#include "connection_add.h"
#include "connection_call_handlers.h"
#include "connection_finish_forward.h"
#include "daemon_poll.h"
#include "upgrade_process.h"
#include "request_resume.h"
#ifdef HAVE_POLL
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
/**
* Set required 'event' members in 'pollfd' elements,
* assuming that @a p[0].fd is MHD side of socketpair
* and @a p[1].fd is TLS connected socket.
*
* @param urh upgrade handle to watch for
* @param p pollfd array to update
*/
static void
urh_update_pollfd (struct MHD_UpgradeResponseHandle *urh,
struct pollfd p[2])
{
p[0].events = 0;
p[1].events = 0;
if (urh->in_buffer_used < urh->in_buffer_size)
p[0].events |= POLLIN;
if (0 != urh->out_buffer_used)
p[0].events |= POLLOUT;
/* Do not monitor again for errors if error was detected before as
* error state is remembered. */
if ((0 == (urh->app.celi & MHD_EPOLL_STATE_ERROR)) &&
((0 != urh->in_buffer_size) ||
(0 != urh->out_buffer_size) ||
(0 != urh->out_buffer_used)))
p[0].events |= MHD_POLL_EVENTS_ERR_DISC;
if (urh->out_buffer_used < urh->out_buffer_size)
p[1].events |= POLLIN;
if (0 != urh->in_buffer_used)
p[1].events |= POLLOUT;
/* Do not monitor again for errors if error was detected before as
* error state is remembered. */
if ((0 == (urh->mhd.celi & MHD_EPOLL_STATE_ERROR)) &&
((0 != urh->out_buffer_size) ||
(0 != urh->in_buffer_size) ||
(0 != urh->in_buffer_used)))
p[1].events |= MHD_POLL_EVENTS_ERR_DISC;
}
/**
* Set @a p to watch for @a urh.
*
* @param urh upgrade handle to watch for
* @param p pollfd array to set
*/
static void
urh_to_pollfd (struct MHD_UpgradeResponseHandle *urh,
struct pollfd p[2])
{
p[0].fd = urh->connection->socket_fd;
p[1].fd = urh->mhd.socket;
urh_update_pollfd (urh,
p);
}
/**
* Update ready state in @a urh based on pollfd.
* @param urh upgrade handle to update
* @param p 'poll()' processed pollfd.
*/
static void
urh_from_pollfd (struct MHD_UpgradeResponseHandle *urh,
struct pollfd p[2])
{
/* Reset read/write ready, preserve error state. */
urh->app.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
urh->mhd.celi &= (~MHD_EPOLL_STATE_READ_READY & ~MHD_EPOLL_STATE_WRITE_READY);
if (0 != (p[0].revents & POLLIN))
urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
if (0 != (p[0].revents & POLLOUT))
urh->app.celi |= MHD_EPOLL_STATE_WRITE_READY;
if (0 != (p[0].revents & POLLHUP))
urh->app.celi |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY;
if (0 != (p[0].revents & MHD_POLL_REVENTS_ERRROR))
urh->app.celi |= MHD_EPOLL_STATE_ERROR;
if (0 != (p[1].revents & POLLIN))
urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
if (0 != (p[1].revents & POLLOUT))
urh->mhd.celi |= MHD_EPOLL_STATE_WRITE_READY;
if (0 != (p[1].revents & POLLHUP))
urh->mhd.celi |= MHD_EPOLL_STATE_ERROR;
if (0 != (p[1].revents & MHD_POLL_REVENTS_ERRROR))
urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY;
}
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
/**
* Process all of our connections and possibly the server
* socket using poll().
*
* @param daemon daemon to run poll loop for
* @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
* @return #MHD_SC_OK on success
*/
enum MHD_StatusCode
MHD_daemon_poll_all_ (struct MHD_Daemon *daemon,
bool may_block)
{
unsigned int num_connections;
struct MHD_Connection *pos;
struct MHD_Connection *prev;
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
struct MHD_UpgradeResponseHandle *urh;
struct MHD_UpgradeResponseHandle *urhn;
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
if ( (! daemon->disallow_suspend_resume) &&
(MHD_resume_suspended_connections_ (daemon)) )
may_block = false;
/* count number of connections and thus determine poll set size */
num_connections = 0;
for (pos = daemon->connections_head; NULL != pos; pos = pos->next)
num_connections++;
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
for (urh = daemon->urh_head; NULL != urh; urh = urh->next)
num_connections += 2;
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
{
MHD_UNSIGNED_LONG_LONG ltimeout;
unsigned int i;
int timeout;
unsigned int poll_server;
int poll_listen;
int poll_itc_idx;
struct pollfd *p;
MHD_socket ls;
p = MHD_calloc_ ((2 + num_connections),
sizeof (struct pollfd));
if (NULL == p)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_POLL_MALLOC_FAILURE,
_ ("Error allocating memory: %s\n"),
MHD_strerror_ (errno));
#endif
return MHD_SC_POLL_MALLOC_FAILURE;
}
poll_server = 0;
poll_listen = -1;
if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
(! daemon->was_quiesced) &&
(daemon->connections < daemon->global_connection_limit) &&
(! daemon->at_limit) )
{
/* only listen if we are not at the connection limit */
p[poll_server].fd = ls;
p[poll_server].events = POLLIN;
p[poll_server].revents = 0;
poll_listen = (int) poll_server;
poll_server++;
}
poll_itc_idx = -1;
if (MHD_ITC_IS_VALID_ (daemon->itc))
{
p[poll_server].fd = MHD_itc_r_fd_ (daemon->itc);
p[poll_server].events = POLLIN;
p[poll_server].revents = 0;
poll_itc_idx = (int) poll_server;
poll_server++;
}
if (! may_block)
timeout = 0;
else if ( (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode) ||
(MHD_SC_OK != /* FIXME: distinguish between NO_TIMEOUT and errors! */
MHD_daemon_get_timeout (daemon,
&ltimeout)) )
timeout = -1;
else
timeout = (ltimeout > INT_MAX) ? INT_MAX : (int) ltimeout;
i = 0;
for (pos = daemon->connections_tail; NULL != pos; pos = pos->prev)
{
p[poll_server + i].fd = pos->socket_fd;
switch (pos->request.event_loop_info)
{
case MHD_EVENT_LOOP_INFO_READ:
p[poll_server + i].events |= POLLIN | MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_WRITE:
p[poll_server + i].events |= POLLOUT | MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_BLOCK:
p[poll_server + i].events |= MHD_POLL_EVENTS_ERR_DISC;
break;
case MHD_EVENT_LOOP_INFO_CLEANUP:
timeout = 0; /* clean up "pos" immediately */
break;
}
i++;
}
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
for (urh = daemon->urh_tail; NULL != urh; urh = urh->prev)
{
urh_to_pollfd (urh,
&(p[poll_server + i]));
i += 2;
}
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
if (0 == poll_server + num_connections)
{
free (p);
return MHD_SC_OK;
}
if (MHD_sys_poll_ (p,
poll_server + num_connections,
timeout) < 0)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EINTR_ (err))
{
free (p);
return MHD_SC_OK;
}
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_UNEXPECTED_POLL_ERROR,
_ ("poll failed: %s\n"),
MHD_socket_strerr_ (err));
#endif
free (p);
return MHD_SC_UNEXPECTED_POLL_ERROR;
}
/* Reset. New value will be set when connections are processed. */
daemon->data_already_pending = false;
/* handle ITC FD */
/* do it before any other processing so
new signals will be processed in next loop */
if ( (-1 != poll_itc_idx) &&
(0 != (p[poll_itc_idx].revents & POLLIN)) )
MHD_itc_clear_ (daemon->itc);
/* handle shutdown */
if (daemon->shutdown)
{
free (p);
return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
}
i = 0;
prev = daemon->connections_tail;
while (NULL != (pos = prev))
{
prev = pos->prev;
/* first, sanity checks */
if (i >= num_connections)
break; /* connection list changed somehow, retry later ... */
if (p[poll_server + i].fd != pos->socket_fd)
continue; /* fd mismatch, something else happened, retry later ... */
MHD_connection_call_handlers_ (pos,
0 != (p[poll_server + i].revents & POLLIN),
0 != (p[poll_server + i].revents
& POLLOUT),
0 != (p[poll_server + i].revents
& MHD_POLL_REVENTS_ERR_DISC));
i++;
}
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
for (urh = daemon->urh_tail; NULL != urh; urh = urhn)
{
if (i >= num_connections)
break; /* connection list changed somehow, retry later ... */
/* Get next connection here as connection can be removed
* from 'daemon->urh_head' list. */
urhn = urh->prev;
/* Check for fd mismatch. FIXME: required for safety? */
if ((p[poll_server + i].fd != urh->connection->socket_fd) ||
(p[poll_server + i + 1].fd != urh->mhd.socket))
break;
urh_from_pollfd (urh,
&p[poll_server + i]);
i += 2;
MHD_upgrade_response_handle_process_ (urh);
/* Finished forwarding? */
if ( (0 == urh->in_buffer_size) &&
(0 == urh->out_buffer_size) &&
(0 == urh->in_buffer_used) &&
(0 == urh->out_buffer_used) )
{
/* MHD_connection_finish_forward_() will remove connection from
* 'daemon->urh_head' list. */
MHD_connection_finish_forward_ (urh->connection);
urh->clean_ready = true;
/* If 'urh->was_closed' already was set to true, connection will be
* moved immediately to cleanup list. Otherwise connection
* will stay in suspended list until 'urh' will be marked
* with 'was_closed' by application. */
MHD_request_resume (&urh->connection->request);
}
}
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
/* handle 'listen' FD */
if ( (-1 != poll_listen) &&
(0 != (p[poll_listen].revents & POLLIN)) )
(void) MHD_accept_connection_ (daemon);
free (p);
}
return MHD_SC_OK;
}
/**
* Process only the listen socket using poll().
*
* @param daemon daemon to run poll loop for
* @param may_block true if blocking, false if non-blocking
* @return #MHD_SC_OK on success
*/
enum MHD_StatusCode
MHD_daemon_poll_listen_socket_ (struct MHD_Daemon *daemon,
bool may_block)
{
struct pollfd p[2];
int timeout;
unsigned int poll_count;
int poll_listen;
int poll_itc_idx;
MHD_socket ls;
memset (&p,
0,
sizeof (p));
poll_count = 0;
poll_listen = -1;
poll_itc_idx = -1;
if ( (MHD_INVALID_SOCKET != (ls = daemon->listen_socket)) &&
(! daemon->was_quiesced) )
{
p[poll_count].fd = ls;
p[poll_count].events = POLLIN;
p[poll_count].revents = 0;
poll_listen = poll_count;
poll_count++;
}
if (MHD_ITC_IS_VALID_ (daemon->itc))
{
p[poll_count].fd = MHD_itc_r_fd_ (daemon->itc);
p[poll_count].events = POLLIN;
p[poll_count].revents = 0;
poll_itc_idx = poll_count;
poll_count++;
}
if (! daemon->disallow_suspend_resume)
(void) MHD_resume_suspended_connections_ (daemon);
if (! may_block)
timeout = 0;
else
timeout = -1;
if (0 == poll_count)
return MHD_SC_OK;
if (MHD_sys_poll_ (p,
poll_count,
timeout) < 0)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EINTR_ (err))
return MHD_SC_OK;
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_UNEXPECTED_POLL_ERROR,
_ ("poll failed: %s\n"),
MHD_socket_strerr_ (err));
#endif
return MHD_SC_UNEXPECTED_POLL_ERROR;
}
if ( (-1 != poll_itc_idx) &&
(0 != (p[poll_itc_idx].revents & POLLIN)) )
MHD_itc_clear_ (daemon->itc);
/* handle shutdown */
if (daemon->shutdown)
return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
if ( (-1 != poll_listen) &&
(0 != (p[poll_listen].revents & POLLIN)) )
(void) MHD_accept_connection_ (daemon);
return MHD_SC_OK;
}
#endif
/**
* Do poll()-based processing.
*
* @param daemon daemon to run poll()-loop for
* @param may_block true if blocking, false if non-blocking
* @return #MHD_SC_OK on success
*/
enum MHD_StatusCode
MHD_daemon_poll_ (struct MHD_Daemon *daemon,
bool may_block)
{
#ifdef HAVE_POLL
if (daemon->shutdown)
return MHD_SC_DAEMON_ALREADY_SHUTDOWN;
if (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode)
return MHD_daemon_poll_all_ (daemon,
may_block);
return MHD_daemon_poll_listen_socket_ (daemon,
may_block);
#else
/* This code should be dead, as we should have checked
this earlier... */
return MHD_SC_POLL_NOT_SUPPORTED;
#endif
}
#ifdef HAVE_POLL
#ifdef HTTPS_SUPPORT
/**
* Process upgraded connection with a poll() loop.
* We are in our own thread, only processing @a con
*
* @param con connection to process
*/
void
MHD_daemon_upgrade_connection_with_poll_ (struct MHD_Connection *con)
{
struct MHD_UpgradeResponseHandle *urh = con->request.urh;
struct pollfd p[2];
memset (p,
0,
sizeof (p));
p[0].fd = urh->connection->socket_fd;
p[1].fd = urh->mhd.socket;
while ( (0 != urh->in_buffer_size) ||
(0 != urh->out_buffer_size) ||
(0 != urh->in_buffer_used) ||
(0 != urh->out_buffer_used) )
{
int timeout;
urh_update_pollfd (urh,
p);
if ( (con->tls_read_ready) &&
(urh->in_buffer_used < urh->in_buffer_size))
timeout = 0; /* No need to wait if incoming data is already pending in TLS buffers. */
else
timeout = -1;
if (MHD_sys_poll_ (p,
2,
timeout) < 0)
{
const int err = MHD_socket_get_error_ ();
if (MHD_SCKT_ERR_IS_EINTR_ (err))
continue;
#ifdef HAVE_MESSAGES
MHD_DLOG (con->daemon,
MHD_SC_UNEXPECTED_POLL_ERROR,
_ ("Error during poll: `%s'\n"),
MHD_socket_strerr_ (err));
#endif
break;
}
urh_from_pollfd (urh,
p);
MHD_upgrade_response_handle_process_ (urh);
}
}
#endif
#endif
/* end of daemon_poll.c */