/* 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, <imeout)) ) 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 */