|
@@ -2442,6 +2442,9 @@ struct mg_context {
|
|
|
int lua_bg_log_available; /* Use Lua background state for access log */
|
|
|
#endif
|
|
|
|
|
|
+ int user_shutdown_notification_socket; /* mg_stop() will close this socket... */
|
|
|
+ int thread_shutdown_notification_socket; /* to cause poll() in all threads to return immediately */
|
|
|
+
|
|
|
/* Server nonce */
|
|
|
pthread_mutex_t nonce_mutex; /* Protects ssl_ctx, handlers,
|
|
|
* ssl_cert_last_mtime, nonce_count, and
|
|
@@ -6147,12 +6150,15 @@ push_inner(struct mg_context *ctx,
|
|
|
mg_sleep(5);
|
|
|
} else {
|
|
|
/* For sockets, wait for the socket using poll */
|
|
|
- struct mg_pollfd pfd[1];
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int pollres;
|
|
|
|
|
|
pfd[0].fd = sock;
|
|
|
pfd[0].events = POLLOUT;
|
|
|
- pollres = mg_poll(pfd, 1, (int)(ms_wait), &(ctx->stop_flag));
|
|
|
+
|
|
|
+ pfd[1].fd = ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
+ pollres = mg_poll(pfd, 2, (int)(ms_wait), &(ctx->stop_flag));
|
|
|
if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)) {
|
|
|
return -2;
|
|
|
}
|
|
@@ -6260,7 +6266,7 @@ pull_inner(FILE *fp,
|
|
|
|
|
|
#if defined(USE_MBEDTLS)
|
|
|
} else if (conn->ssl != NULL) {
|
|
|
- struct mg_pollfd pfd[1];
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int to_read;
|
|
|
int pollres;
|
|
|
|
|
@@ -6278,10 +6284,13 @@ pull_inner(FILE *fp,
|
|
|
pfd[0].fd = conn->client.sock;
|
|
|
pfd[0].events = POLLIN;
|
|
|
|
|
|
+ pfd[1].fd = conn->phys_ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
+
|
|
|
to_read = len;
|
|
|
|
|
|
pollres = mg_poll(pfd,
|
|
|
- 1,
|
|
|
+ 2,
|
|
|
(int)(timeout * 1000.0),
|
|
|
&(conn->phys_ctx->stop_flag));
|
|
|
|
|
@@ -6316,7 +6325,7 @@ pull_inner(FILE *fp,
|
|
|
#elif !defined(NO_SSL)
|
|
|
} else if (conn->ssl != NULL) {
|
|
|
int ssl_pending;
|
|
|
- struct mg_pollfd pfd[1];
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int pollres;
|
|
|
|
|
|
if ((ssl_pending = SSL_pending(conn->ssl)) > 0) {
|
|
@@ -6330,8 +6339,11 @@ pull_inner(FILE *fp,
|
|
|
} else {
|
|
|
pfd[0].fd = conn->client.sock;
|
|
|
pfd[0].events = POLLIN;
|
|
|
+ pfd[1].fd = conn->phys_ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
+
|
|
|
pollres = mg_poll(pfd,
|
|
|
- 1,
|
|
|
+ 2,
|
|
|
(int)(timeout * 1000.0),
|
|
|
&(conn->phys_ctx->stop_flag));
|
|
|
if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) {
|
|
@@ -6369,13 +6381,17 @@ pull_inner(FILE *fp,
|
|
|
#endif
|
|
|
|
|
|
} else {
|
|
|
- struct mg_pollfd pfd[1];
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int pollres;
|
|
|
|
|
|
pfd[0].fd = conn->client.sock;
|
|
|
pfd[0].events = POLLIN;
|
|
|
+
|
|
|
+ pfd[1].fd = conn->phys_ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
+
|
|
|
pollres = mg_poll(pfd,
|
|
|
- 1,
|
|
|
+ 2,
|
|
|
(int)(timeout * 1000.0),
|
|
|
&(conn->phys_ctx->stop_flag));
|
|
|
if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) {
|
|
@@ -7590,7 +7606,7 @@ extention_matches_template_text(
|
|
|
* Return 1 if index file has been found, 0 if not found.
|
|
|
* If the file is found, it's stats is returned in stp. */
|
|
|
static int
|
|
|
-substitute_index_file(struct mg_connection *conn,
|
|
|
+substitute_index_file_aux(struct mg_connection *conn,
|
|
|
char *path,
|
|
|
size_t path_len,
|
|
|
struct mg_file_stat *filestat)
|
|
@@ -7634,6 +7650,44 @@ substitute_index_file(struct mg_connection *conn,
|
|
|
|
|
|
return found;
|
|
|
}
|
|
|
+
|
|
|
+/* Same as above, except if the first try fails and a fallback-root is configured, we'll try there also */
|
|
|
+static int
|
|
|
+substitute_index_file(struct mg_connection *conn,
|
|
|
+ char *path,
|
|
|
+ size_t path_len,
|
|
|
+ struct mg_file_stat *filestat)
|
|
|
+{
|
|
|
+ int ret = substitute_index_file_aux(conn, path, path_len, filestat);
|
|
|
+ if (ret == 0) {
|
|
|
+ const char * root_prefix = conn->dom_ctx->config[DOCUMENT_ROOT];
|
|
|
+ const char * fallback_root_prefix = conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT];
|
|
|
+ if ((root_prefix)&&(fallback_root_prefix)) {
|
|
|
+ const size_t root_prefix_len = strlen(root_prefix);
|
|
|
+ if ((strncmp(path, root_prefix, root_prefix_len) == 0)) {
|
|
|
+ const size_t fallback_root_prefix_len = strlen(fallback_root_prefix);
|
|
|
+ const char * sub_path = path+root_prefix_len;
|
|
|
+ while(*sub_path == '/') sub_path++;
|
|
|
+ const size_t sub_path_len = strlen(sub_path);
|
|
|
+
|
|
|
+ char scratch_path[UTF8_PATH_MAX]; /* separate storage, to avoid side effects if we fail */
|
|
|
+ if (((fallback_root_prefix_len + 1 + sub_path_len + 1) < sizeof(scratch_path))) {
|
|
|
+ /* The concatenations below are all safe because we pre-verified string lengths above */
|
|
|
+ strcpy(scratch_path, fallback_root_prefix);
|
|
|
+ char * nul = strchr(scratch_path, '\0');
|
|
|
+ if ((nul > scratch_path)&&(*(nul-1) != '/')) {*nul++ = '/'; *nul = '\0';}
|
|
|
+ strcat(scratch_path, sub_path);
|
|
|
+ if (substitute_index_file_aux(conn, scratch_path, sizeof(scratch_path), filestat)) {
|
|
|
+ mg_strlcpy(path, scratch_path, path_len);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
#endif
|
|
|
|
|
|
|
|
@@ -9540,7 +9594,7 @@ connect_socket(
|
|
|
#endif
|
|
|
|
|
|
/* Data for poll */
|
|
|
- struct mg_pollfd pfd[1];
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int pollres;
|
|
|
int ms_wait = 10000; /* 10 second timeout */
|
|
|
stop_flag_t nonstop = 0; /* STOP_FLAG_ASSIGN(&nonstop, 0); */
|
|
@@ -9552,7 +9606,11 @@ connect_socket(
|
|
|
*/
|
|
|
pfd[0].fd = *sock;
|
|
|
pfd[0].events = POLLOUT;
|
|
|
- pollres = mg_poll(pfd, 1, ms_wait, ctx ? &(ctx->stop_flag) : &nonstop);
|
|
|
+
|
|
|
+ pfd[1].fd = ctx ? ctx->thread_shutdown_notification_socket : -1;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
+
|
|
|
+ pollres = mg_poll(pfd, ctx ? 2 : 1, ms_wait, ctx ? &(ctx->stop_flag) : &nonstop);
|
|
|
|
|
|
if (pollres != 1) {
|
|
|
/* Not connected */
|
|
@@ -16045,9 +16103,13 @@ set_ports_option(struct mg_context *phys_ctx)
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ /* The +2 below includes the original +1 (for the socket we're about to add),
|
|
|
+ * plus another +1 for the thread_shutdown_notification_socket that we'll
|
|
|
+ * also want to poll() on so that mg_stop() can return quickly
|
|
|
+ */
|
|
|
if ((pfd = (struct mg_pollfd *)
|
|
|
mg_realloc_ctx(phys_ctx->listening_socket_fds,
|
|
|
- (phys_ctx->num_listening_sockets + 1)
|
|
|
+ (phys_ctx->num_listening_sockets + 2)
|
|
|
* sizeof(phys_ctx->listening_socket_fds[0]),
|
|
|
phys_ctx))
|
|
|
== NULL) {
|
|
@@ -16569,15 +16631,18 @@ sslize(struct mg_connection *conn,
|
|
|
/* Need to retry the function call "later".
|
|
|
* See https://linux.die.net/man/3/ssl_get_error
|
|
|
* This is typical for non-blocking sockets. */
|
|
|
- struct mg_pollfd pfd;
|
|
|
+ struct mg_pollfd pfd[2];
|
|
|
int pollres;
|
|
|
- pfd.fd = conn->client.sock;
|
|
|
- pfd.events = ((err == SSL_ERROR_WANT_CONNECT)
|
|
|
- || (err == SSL_ERROR_WANT_WRITE))
|
|
|
- ? POLLOUT
|
|
|
- : POLLIN;
|
|
|
+ pfd[0].fd = conn->client.sock;
|
|
|
+ pfd[0].events = ((err == SSL_ERROR_WANT_CONNECT)
|
|
|
+ || (err == SSL_ERROR_WANT_WRITE))
|
|
|
+ ? POLLOUT
|
|
|
+ : POLLIN;
|
|
|
+
|
|
|
+ pfd[1].fd = conn->phys_ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[1].events = POLLIN;
|
|
|
pollres =
|
|
|
- mg_poll(&pfd, 1, 50, &(conn->phys_ctx->stop_flag));
|
|
|
+ mg_poll(pfd, 2, 50, &(conn->phys_ctx->stop_flag));
|
|
|
if (pollres < 0) {
|
|
|
/* Break if error occurred (-1)
|
|
|
* or server shutdown (-2) */
|
|
@@ -20161,8 +20226,14 @@ master_thread_run(struct mg_context *ctx)
|
|
|
pfd[i].events = POLLIN;
|
|
|
}
|
|
|
|
|
|
+ /* We listen on this socket just so that mg_stop() can cause mg_poll() to return ASAP.
|
|
|
+ * Don't worry, we did allocate an extra slot at the end of listening_socket_fds[] just to hold this
|
|
|
+ */
|
|
|
+ pfd[ctx->num_listening_sockets].fd = ctx->thread_shutdown_notification_socket;
|
|
|
+ pfd[ctx->num_listening_sockets].events = POLLIN;
|
|
|
+
|
|
|
if (mg_poll(pfd,
|
|
|
- ctx->num_listening_sockets,
|
|
|
+ ctx->num_listening_sockets+1, // +1 for the thread_shutdown_notification_socket
|
|
|
SOCKET_TIMEOUT_QUANTUM,
|
|
|
&(ctx->stop_flag))
|
|
|
> 0) {
|
|
@@ -20320,6 +20391,14 @@ free_context(struct mg_context *ctx)
|
|
|
(void)pthread_mutex_destroy(&ctx->lua_bg_mutex);
|
|
|
#endif
|
|
|
|
|
|
+ /* Deallocate shutdown-triggering socket-pair */
|
|
|
+ if (ctx->user_shutdown_notification_socket >= 0) {
|
|
|
+ closesocket(ctx->user_shutdown_notification_socket);
|
|
|
+ }
|
|
|
+ if (ctx->thread_shutdown_notification_socket >= 0) {
|
|
|
+ closesocket(ctx->thread_shutdown_notification_socket);
|
|
|
+ }
|
|
|
+
|
|
|
/* Deallocate config parameters */
|
|
|
for (i = 0; i < NUM_OPTIONS; i++) {
|
|
|
if (ctx->dd.config[i] != NULL) {
|
|
@@ -20396,6 +20475,10 @@ mg_stop(struct mg_context *ctx)
|
|
|
/* Set stop flag, so all threads know they have to exit. */
|
|
|
STOP_FLAG_ASSIGN(&ctx->stop_flag, 1);
|
|
|
|
|
|
+ /* Closing this socket will cause mg_poll() in all the I/O threads to return immediately */
|
|
|
+ closesocket(ctx->user_shutdown_notification_socket);
|
|
|
+ ctx->user_shutdown_notification_socket = -1; /* to avoid calling closesocket() again in free_context() */
|
|
|
+
|
|
|
/* Join timer thread */
|
|
|
#if defined(USE_TIMERS)
|
|
|
timers_exit(ctx);
|
|
@@ -20493,6 +20576,68 @@ legacy_init(const char **options)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/* we'll assume it's only Windows that doesn't have socketpair() available */
|
|
|
+#if !defined(HAVE_SOCKETPAIR) && !defined(_WIN32)
|
|
|
+# define HAVE_SOCKETPAIR 1
|
|
|
+#endif
|
|
|
+
|
|
|
+static int
|
|
|
+mg_socketpair(int * sockA,
|
|
|
+ int * sockB)
|
|
|
+{
|
|
|
+ int temp[2] = {-1, -1};
|
|
|
+
|
|
|
+ /** Default to unallocated */
|
|
|
+ *sockA = -1;
|
|
|
+ *sockB = -1;
|
|
|
+
|
|
|
+#if defined(HAVE_SOCKETPAIR)
|
|
|
+ int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, temp);
|
|
|
+ if (ret == 0) {
|
|
|
+ *sockA = temp[0];
|
|
|
+ *sockB = temp[1];
|
|
|
+ set_close_on_exec(*sockA, NULL, NULL);
|
|
|
+ set_close_on_exec(*sockB, NULL, NULL);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+#else
|
|
|
+ /** No socketpair() call is available, so we'll have to roll our own implementation */
|
|
|
+ int asock = socket(PF_INET, SOCK_STREAM, 0);
|
|
|
+ if (asock >= 0) {
|
|
|
+ struct sockaddr_in addr;
|
|
|
+ struct sockaddr * pa = (struct sockaddr *) &addr;
|
|
|
+ socklen_t addrLen = sizeof(addr);
|
|
|
+
|
|
|
+ memset(&addr, 0, sizeof(addr));
|
|
|
+ addr.sin_family = AF_INET;
|
|
|
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
+ addr.sin_port = 0;
|
|
|
+
|
|
|
+ if ((bind(asock, pa, sizeof(addr)) == 0)
|
|
|
+ &&(getsockname(asock, pa, &addrLen) == 0)
|
|
|
+ &&(listen(asock, 1) == 0)) {
|
|
|
+ temp[0] = socket(PF_INET, SOCK_STREAM, 0);
|
|
|
+ if ((temp[0] >= 0)&&(connect(temp[0], pa, sizeof(addr)) == 0)) {
|
|
|
+ temp[1] = accept(asock, pa, &addrLen);
|
|
|
+ if (temp[1] >= 0) {
|
|
|
+ closesocket(asock);
|
|
|
+ *sockA = temp[0];
|
|
|
+ *sockB = temp[1];
|
|
|
+ set_close_on_exec(*sockA, NULL, NULL);
|
|
|
+ set_close_on_exec(*sockB, NULL, NULL);
|
|
|
+ return 0; /* success! */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Cleanup */
|
|
|
+ if (asock >= 0) closesocket(asock);
|
|
|
+ if (temp[0] >= 0) closesocket(temp[0]);
|
|
|
+ if (temp[1] >= 0) closesocket(temp[1]);
|
|
|
+ return -1; /* fail! */
|
|
|
+#endif
|
|
|
+}
|
|
|
|
|
|
CIVETWEB_API struct mg_context *
|
|
|
mg_start2(struct mg_init_data *init, struct mg_error_data *error)
|
|
@@ -20576,6 +20721,12 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
|
|
|
#if defined(USE_LUA)
|
|
|
ok &= (0 == pthread_mutex_init(&ctx->lua_bg_mutex, &pthread_mutex_attr));
|
|
|
#endif
|
|
|
+
|
|
|
+ /** mg_stop() will close the user_shutdown_notification_socket, and that will cause poll()
|
|
|
+ * to return immediately in the master-thread, so that mg_stop() can also return immediately.
|
|
|
+ */
|
|
|
+ ok &= (0 == mg_socketpair(&ctx->user_shutdown_notification_socket, &ctx->thread_shutdown_notification_socket));
|
|
|
+
|
|
|
if (!ok) {
|
|
|
unsigned error_id = (unsigned)ERRNO;
|
|
|
const char *err_msg =
|