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