diff --git a/CMakeLists.txt b/CMakeLists.txt index 86924543b..8d97927b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ option(LWS_CLIENT_HTTP_PROXYING "Support external http proxies for client connec option(LWS_WITH_FILE_OPS "Support file operations vfs" ON) option(LWS_WITH_DETAILED_LATENCY "Record detailed latency stats for each read and write" OFF) option(LWS_WITH_UDP "Platform supports UDP" ON) +option(LWS_WITH_SPAWN "Spawn subprocesses with piped stdin/out/stderr" OFF) # # to use miniz, enable both LWS_WITH_ZLIB and LWS_WITH_MINIZ @@ -195,6 +196,9 @@ if (LWS_FOR_GITOHASHI) set(LWS_WITH_DISKCACHE 1) set(LWS_WITH_LWSAC 1) set(LWS_WITH_LEJP_CONF 1) + set(LWS_WITH_SPAWN 1) + set(LWS_WITH_STRUCT_JSON 1) + set(LWS_WITH_STRUCT_SQLITE3 1) endif() if(LWS_WITH_DISTRO_RECOMMENDED) @@ -225,6 +229,9 @@ if(LWS_WITH_DISTRO_RECOMMENDED) set(LWS_ROLE_RAW_PROXY 1) set(LWS_WITH_GENCRYPTO 1) set(LWS_WITH_JOSE 1) + set(LWS_WITH_STRUCT_JSON 1) + set(LWS_WITH_STRUCT_SQLITE3 1) + set(LWS_WITH_SPAWN 1) endif() if (NOT LWS_WITH_NETWORK) @@ -253,6 +260,10 @@ if (NOT LWS_WITH_NETWORK) set(LWS_WITH_THREADPOOL 0) endif() +if (LWS_WITH_CGI) + set(LWS_WITH_SPAWN 1) +endif() + if (LWS_WITH_STRUCT_SQLITE3) set(LWS_WITH_SQLITE3 1) endif() @@ -1040,6 +1051,11 @@ set(SOURCES lib/misc/lws-ring.c ) +if (LWS_WITH_SPAWN) + list(APPEND SOURCES lib/misc/spawn.c) +endif() + + if (LWS_WITH_FILE_OPS) list(APPEND SOURCES lib/core/vfs.c) endif() diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index a3b3e0bc8..15f5a182d 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -40,6 +40,7 @@ #cmakedefine LWS_HAVE_EVP_aes_256_cfb8 #cmakedefine LWS_HAVE_EVP_aes_256_cfb128 #cmakedefine LWS_HAVE_EVP_aes_128_xts +#cmakedefine LWS_HAVE_EXECVPE #cmakedefine LWS_HAVE_LIBCAP #cmakedefine LWS_HAVE_HMAC_CTX_new #cmakedefine LWS_HAVE_MALLOC_H @@ -73,6 +74,7 @@ #cmakedefine LWS_HAVE_TLS_CLIENT_METHOD #cmakedefine LWS_HAVE_TLSV1_2_CLIENT_METHOD #cmakedefine LWS_HAVE_UV_VERSION_H +#cmakedefine LWS_HAVE_VFORK #cmakedefine LWS_HAVE_X509_get_key_usage #cmakedefine LWS_HAVE_X509_VERIFY_PARAM_set1_host #cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}" @@ -132,6 +134,7 @@ #cmakedefine LWS_WITH_CLIENT #cmakedefine LWS_WITHOUT_EXTENSIONS #cmakedefine LWS_WITH_SERVER +#cmakedefine LWS_WITH_SPAWN #cmakedefine LWS_WITH_PEER_LIMITS #cmakedefine LWS_WITH_PLUGINS #cmakedefine LWS_WITH_POLARSSL diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6052d24a0..09abc5d07 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -347,6 +347,11 @@ struct lws_pollfd { #define LWS_POLLHUP (FD_CLOSE) #define LWS_POLLIN (FD_READ | FD_ACCEPT) #define LWS_POLLOUT (FD_WRITE) + +#if !defined(pid_t) +#define pid_t int +#endif + #else diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 9ceaa913b..ee180f1c5 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -22,6 +22,15 @@ * IN THE SOFTWARE. */ +#if defined(LWS_WITH_SPAWN) + +#if defined(WIN32) || defined(_WIN32) +#else +#include +#include +#endif +#endif + /** \defgroup misc Miscellaneous APIs * ##Miscellaneous APIs * @@ -701,10 +710,127 @@ lws_ser_ru32be(const uint8_t *b); LWS_VISIBLE LWS_EXTERN uint64_t lws_ser_ru64be(const uint8_t *b); -int +LWS_VISIBLE LWS_EXTERN int lws_vbi_encode(uint64_t value, void *buf); -int +LWS_VISIBLE LWS_EXTERN int lws_vbi_decode(const void *buf, uint64_t *value, size_t len); ///@} + +#if defined(LWS_WITH_SPAWN) + +/* opaque internal struct */ +struct lws_spawn_piped; + +typedef void (*lsp_cb_t)(void *opaque, lws_usec_t *accounting, siginfo_t *si, + int we_killed_him); + + +/** + * lws_spawn_piped_info - details given to create a spawned pipe + * + * \p owner: lws_dll2_owner_t that lists all active spawns, or NULL + * \p vh: vhost to bind stdwsi to... from opt_parent if given + * \p opt_parent: optional parent wsi for stdwsi + * \p exec_array: argv for process to spawn + * \p env_array: environment for spawned process, NULL ends env list + * \p protocol_name: NULL, or vhost protocol name to bind stdwsi to + * \p chroot_path: NULL, or chroot patch for child process + * \p wd: working directory to cd to after fork, NULL defaults to /tmp + * \p plsp: NULL, or pointer to the outer lsp pointer so it can be set NULL when destroyed + * \p opaque: pointer passed to the reap callback, if any + * \p timeout: optional us-resolution timeout, or zero + * \p reap_cb: callback when child process has been reaped and the lsp destroyed + * \p tsi: tsi to bind stdwsi to... from opt_parent if given + */ +struct lws_spawn_piped_info { + struct lws_dll2_owner *owner; + struct lws_vhost *vh; + struct lws *opt_parent; + + const char * const *exec_array; + char **env_array; + const char *protocol_name; + const char *chroot_path; + const char *wd; + + struct lws_spawn_piped **plsp; + + void *opaque; + + lsp_cb_t reap_cb; + + lws_usec_t timeout_us; + int max_log_lines; + int tsi; + + const struct lws_role_ops *ops; /* NULL is raw file */ + + uint8_t disable_ctrlc; +}; + +/** + * lws_spawn_piped() - spawn a child process with stdxxx redirected + * + * \p lspi: info struct describing details of spawn to create + * + * This spawns a child process managed in the lsp object and with attributes + * set in the arguments. The stdin/out/err streams are redirected to pipes + * which are instantiated into wsi that become child wsi of \p parent if non- + * NULL. .opaque_user_data on the stdwsi created is set to point to the + * lsp object, so this can be recovered easily in the protocol handler. + * + * If \p owner is non-NULL, successful spawns join the given dll2 owner in the + * original process. + * + * If \p timeout is non-zero, successful spawns register a sul with the us- + * resolution timeout to callback \p timeout_cb, in the original process. + * + * Returns 0 if the spawn went OK or nonzero if it failed and was cleaned up. + * The spawned process continues asynchronously and this will return after + * starting it if all went well. + */ +LWS_VISIBLE LWS_EXTERN struct lws_spawn_piped * +lws_spawn_piped(const struct lws_spawn_piped_info *lspi); + +/* + * lws_spawn_piped_kill_child_process() - attempt to kill child process + * + * \p lsp: child object to kill + * + * Attempts to signal the child process in \p lsp to terminate. + */ +LWS_VISIBLE LWS_EXTERN int +lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp); + +/** + * lws_spawn_stdwsi_closed() - inform the spawn one of its stdxxx pipes closed + * + * \p lsp: the spawn object + * + * When you notice one of the spawn stdxxx pipes closed, inform the spawn + * instance using this api. When it sees all three have closed, it will + * automatically try to reap the child process. + * + * This is the mechanism whereby the spawn object can understand its child + * has closed. + */ +LWS_VISIBLE LWS_EXTERN void +lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp); + +/** + * lws_spawn_get_stdfd() - return std channel index for stdwsi + * + * \p wsi: the wsi + * + * If you know wsi is a stdwsi from a spawn, you can determine its original + * channel index / fd before the pipes replaced the default fds. It will return + * one of 0 (STDIN), 1 (STDOUT) or 2 (STDERR). You can handle all three in the + * same protocol handler and then disambiguate them using this api. + */ +LWS_VISIBLE LWS_EXTERN int +lws_spawn_get_stdfd(struct lws *wsi); + +#endif + diff --git a/lib/core-net/close.c b/lib/core-net/close.c index ac796b8ed..91cae6a67 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -296,7 +296,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, #ifdef LWS_WITH_CGI if (wsi->role_ops == &role_ops_cgi) { - // lwsl_debug("%s: closing stdwsi index %d\n", __func__, (int)wsi->cgi_channel); + // lwsl_debug("%s: closing stdwsi index %d\n", __func__, (int)wsi->lsp_channel); /* we are not a network connection, but a handler for CGI io */ if (wsi->parent && wsi->parent->http.cgi) { @@ -306,7 +306,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, /* end the binding between us and master */ if (wsi->parent->http.cgi) - wsi->parent->http.cgi->stdwsi[(int)wsi->cgi_channel] = + wsi->parent->http.cgi->lsp->stdwsi[(int)wsi->lsp_channel] = NULL; } wsi->socket_is_permanently_unusable = 1; @@ -631,17 +631,7 @@ __lws_close_free_wsi_final(struct lws *wsi) #ifdef LWS_WITH_CGI if (wsi->http.cgi) { - - for (n = 0; n < 3; n++) { - if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] == 0) - lwsl_err("ZERO FD IN CGI CLOSE"); - - if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] >= 0) { - close(wsi->http.cgi->pipe_fds[n][!!(n == 0)]); - wsi->http.cgi->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID; - } - } - + lws_spawn_piped_destroy(&wsi->http.cgi->lsp); lws_free_set_NULL(wsi->http.cgi); } #endif diff --git a/lib/core-net/dummy-callback.c b/lib/core-net/dummy-callback.c index af6492717..2089ffcbd 100644 --- a/lib/core-net/dummy-callback.c +++ b/lib/core-net/dummy-callback.c @@ -314,9 +314,10 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, lwsl_debug("AUX_BF__CGI forcing close\n"); return -1; } - if (!n && wsi->http.cgi && wsi->http.cgi->stdwsi[LWS_STDOUT]) + if (!n && wsi->http.cgi && + wsi->http.cgi->lsp->stdwsi[LWS_STDOUT]) lws_rx_flow_control( - wsi->http.cgi->stdwsi[LWS_STDOUT], 1); + wsi->http.cgi->lsp->stdwsi[LWS_STDOUT], 1); if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) wsi->reason_bf &= @@ -796,7 +797,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, compatible_close(siwsi->desc.sockfd); __lws_free_wsi(siwsi); } - wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; + wsi->http.cgi->lsp->pipe_fds[LWS_STDIN][1] = -1; // args->stdwsi[LWS_STDIN] = NULL; } diff --git a/lib/core-net/pollfd.c b/lib/core-net/pollfd.c index 3efd87273..cf01392d1 100644 --- a/lib/core-net/pollfd.c +++ b/lib/core-net/pollfd.c @@ -140,6 +140,11 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) lws_memory_barrier(); #endif +#if !defined(__linux__) + /* OSX couldn't see close on stdin pipe side otherwise */ + _or |= LWS_POLLHUP; +#endif + pfd = &pt->fds[wsi->position_in_fds_table]; pa->fd = wsi->desc.sockfd; lwsl_debug("%s: wsi %p: fd %d events %d -> %d\n", __func__, wsi, diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 461b94324..a02a8254a 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -826,8 +826,8 @@ struct lws { char redirects; uint8_t rxflow_bitmap; uint8_t bound_vhost_index; + uint8_t lsp_channel; /* which of stdin/out/err */ #ifdef LWS_WITH_CGI - char cgi_channel; /* which of stdin/out/err */ char hdr_state; #endif #if defined(LWS_WITH_CLIENT) @@ -853,6 +853,48 @@ struct lws { #define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap)) +#if defined(LWS_WITH_SPAWN) + +#if defined(WIN32) || defined(_WIN32) +#else +#include +#include +#endif + +struct lws_spawn_piped { + + struct lws_spawn_piped_info info; + + struct lws_dll2 dll; + lws_sorted_usec_list_t sul; + + struct lws *stdwsi[3]; + int pipe_fds[3][2]; + int count_log_lines; + + lws_usec_t created; /* set by lws_spawn_piped() */ + lws_usec_t reaped; + + lws_usec_t accounting[4]; + + pid_t child_pid; + + siginfo_t si; + + uint8_t pipes_alive:2; + uint8_t we_killed_him_timeout:1; + uint8_t we_killed_him_spew:1; + uint8_t ungraceful:1; +}; + +void +lws_spawn_piped_destroy(struct lws_spawn_piped **lsp); + +int +lws_spawn_reap(struct lws_spawn_piped *lsp); + +#endif + void lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt); diff --git a/lib/misc/spawn.c b/lib/misc/spawn.c new file mode 100644 index 000000000..d219a1177 --- /dev/null +++ b/lib/misc/spawn.c @@ -0,0 +1,511 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2020 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#include "private-lib-core.h" +#include + +void +lws_spawn_timeout(struct lws_sorted_usec_list *sul) +{ + struct lws_spawn_piped *lsp = lws_container_of(sul, + struct lws_spawn_piped, sul); + + lwsl_warn("%s: spawn exceeded timeout, killing\n", __func__); + + lws_spawn_piped_kill_child_process(lsp); +} + +static struct lws * +lws_create_basic_wsi(struct lws_context *context, int tsi, + const struct lws_role_ops *ops) +{ + struct lws *new_wsi; + + if (!context->vhost_list) + return NULL; + + if ((unsigned int)context->pt[tsi].fds_count == + context->fd_limit_per_thread - 1) { + lwsl_err("no space for new conn\n"); + return NULL; + } + + new_wsi = lws_zalloc(sizeof(*new_wsi), "new wsi"); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + new_wsi->tsi = tsi; + new_wsi->context = context; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* initialize the instance struct */ + + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, ops); + + new_wsi->hdr_parsing_completed = 0; + new_wsi->position_in_fds_table = LWS_NO_FDS_POS; + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the defauly vhost supported list, so it can look + * for matching ones during the handshake + */ + + new_wsi->user_space = NULL; + new_wsi->desc.sockfd = LWS_SOCK_INVALID; + context->count_wsi_allocated++; + + return new_wsi; +} + +void +lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp) +{ + struct lws_spawn_piped *lsp = *_lsp; + int n; + + if (!lsp) + return; + + for (n = 0; n < 3; n++) { + if (lsp->pipe_fds[n][!!(n == 0)] == 0) + lwsl_err("ZERO FD IN CGI CLOSE"); + + if (lsp->pipe_fds[n][!!(n == 0)] >= 0) { + close(lsp->pipe_fds[n][!!(n == 0)]); + lsp->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID; + } + } + + lws_dll2_remove(&lsp->dll); + + lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul, + NULL, LWS_SET_TIMER_USEC_CANCEL); + + lws_free_set_NULL((*_lsp)); +} + +int +lws_spawn_reap(struct lws_spawn_piped *lsp) +{ + long hz = sysconf(_SC_CLK_TCK); /* accounting Hz */ + void *opaque = lsp->info.opaque; + lsp_cb_t cb = lsp->info.reap_cb; + struct lws_spawn_piped temp; + struct tms tms; + int n; + + if (lsp->child_pid < 1) + return 0; + + /* check if exited, do not reap yet */ + + memset(&lsp->si, 0, sizeof(lsp->si)); + n = waitid(P_PID, lsp->child_pid, &lsp->si, WEXITED | WNOHANG | WNOWAIT); + if (n < 0) { + lwsl_info("%s: child %d still running\n", __func__, lsp->child_pid); + return 0; + } + + if (!lsp->si.si_code) + return 0; + + /* his process has exited... */ + + if (!lsp->reaped) { + /* mark the earliest time we knew he had gone */ + lsp->reaped = lws_now_usecs(); + + /* + * Switch the timeout to restrict the amount of grace time + * to drain stdwsi + */ + + lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, + &lsp->sul, lws_spawn_timeout, + 5 * LWS_US_PER_SEC); + } + + /* + * Stage finalizing our reaction to the process going down until the + * stdwsi flushed whatever is in flight and all noticed they were + * closed. For that reason, each stdwsi close must call lws_spawn_reap + * to check if that was the last one and we can proceed with the reap. + */ + + if (!lsp->ungraceful && lsp->pipes_alive) { + lwsl_debug("%s: stdwsi alive, not reaping\n", __func__); + return 0; + } + + /* we reached the reap point, no need for timeout wait */ + + lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul, NULL, + LWS_SET_TIMER_USEC_CANCEL); + + /* + * All the stdwsi went down, nothing more is coming... it's over + * Collect the final information and then reap the dead process + */ + + if (times(&tms) != (clock_t) -1) { + /* + * Cpu accounting in us + */ + lsp->accounting[0] = ((uint64_t)tms.tms_cstime * 1000000) / hz; + lsp->accounting[1] = ((uint64_t)tms.tms_cutime * 1000000) / hz; + lsp->accounting[2] = ((uint64_t)tms.tms_stime * 1000000) / hz; + lsp->accounting[3] = ((uint64_t)tms.tms_utime * 1000000) / hz; + } + + temp = *lsp; + waitid(P_PID, lsp->child_pid, &lsp->si, WEXITED | WNOHANG); + lsp->child_pid = -1; + + /* destroy the lsp itself first (it's freed and plsp set NULL */ + + if (lsp->info.plsp) + lws_spawn_piped_destroy(lsp->info.plsp); + + /* then do the parent callback informing it's destroyed */ + + if (cb) + cb(opaque, temp.accounting, &temp.si, + temp.we_killed_him_timeout | + (temp.we_killed_him_spew << 1)); + + return 1; /* was reaped */ +} + +int +lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp) +{ + int status, n; + + if (lsp->child_pid <= 0) + return 1; + + lsp->ungraceful = 1; /* don't wait for flushing, just kill it */ + + if (lws_spawn_reap(lsp)) + /* that may have invalidated lsp */ + return 0; + + /* kill the process group */ + n = kill(-lsp->child_pid, SIGTERM); + lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", __func__, + lsp->child_pid, n, errno); + if (n < 0) { + /* + * hum seen errno=3 when process is listed in ps, + * it seems we don't always retain process grouping + * + * Direct these fallback attempt to the exact child + */ + n = kill(lsp->child_pid, SIGTERM); + if (n < 0) { + n = kill(lsp->child_pid, SIGPIPE); + if (n < 0) { + n = kill(lsp->child_pid, SIGKILL); + if (n < 0) + lwsl_info("%s: SIGKILL PID %d " + "failed errno %d " + "(maybe zombie)\n", __func__, + lsp->child_pid, errno); + } + } + } + + /* He could be unkillable because he's a zombie */ + + n = 1; + while (n > 0) { + n = waitpid(-lsp->child_pid, &status, WNOHANG); + if (n > 0) + lwsl_debug("%s: reaped PID %d\n", __func__, n); + if (n <= 0) { + n = waitpid(lsp->child_pid, &status, WNOHANG); + if (n > 0) + lwsl_debug("%s: reaped PID %d\n", __func__, n); + } + } + + lws_spawn_reap(lsp); + /* that may have invalidated lsp */ + + return 0; +} + +/* + * Deals with spawning a subprocess and executing it securely with stdin/out/err + * diverted into pipes + */ + +struct lws_spawn_piped * +lws_spawn_piped(const struct lws_spawn_piped_info *i) +{ + const struct lws_protocols *pcol = i->vh->context->vhost_list->protocols; + struct lws_context *context = i->vh->context; + struct lws_spawn_piped *lsp; + const char *wd; + int n, m; + + if (i->protocol_name) + pcol = lws_vhost_name_to_protocol(i->vh, i->protocol_name); + if (!pcol) { + lwsl_err("%s: unknown protocol %s\n", __func__, + i->protocol_name ? i->protocol_name : "default"); + + return NULL; + } + + lsp = lws_zalloc(sizeof(*lsp), __func__); + if (!lsp) + return NULL; + + /* wholesale take a copy of info */ + lsp->info = *i; + + /* + * Prepare the stdin / out / err pipes + */ + + for (n = 0; n < 3; n++) { + lsp->pipe_fds[n][0] = -1; + lsp->pipe_fds[n][1] = -1; + } + + /* create pipes for [stdin|stdout] and [stderr] */ + + for (n = 0; n < 3; n++) + if (pipe(lsp->pipe_fds[n]) == -1) + goto bail1; + + /* create wsis for each stdin/out/err fd */ + + for (n = 0; n < 3; n++) { + lsp->stdwsi[n] = lws_create_basic_wsi(i->vh->context, i->tsi, + i->ops ? i->ops : &role_ops_raw_file); + if (!lsp->stdwsi[n]) { + lwsl_err("%s: unable to create lsp stdwsi\n", __func__); + goto bail2; + } + lsp->stdwsi[n]->lsp_channel = n; + lws_vhost_bind_wsi(i->vh, lsp->stdwsi[n]); + lsp->stdwsi[n]->protocol = pcol; + lsp->stdwsi[n]->opaque_user_data = i->opaque; + + lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__, + lsp->stdwsi[n], n, lsp->pipe_fds[n][!!(n == 0)], + lsp->pipe_fds[n][!(n == 0)]); + + /* read side is 0, stdin we want the write side, others read */ + + lsp->stdwsi[n]->desc.sockfd = lsp->pipe_fds[n][!!(n == 0)]; + if (fcntl(lsp->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) { + lwsl_err("%s: setting NONBLOCK failed\n", __func__); + goto bail2; + } + } + + for (n = 0; n < 3; n++) { + if (context->event_loop_ops->sock_accept) + if (context->event_loop_ops->sock_accept(lsp->stdwsi[n])) + goto bail3; + + if (__insert_wsi_socket_into_fds(context, lsp->stdwsi[n])) + goto bail3; + if (i->opt_parent) { + lsp->stdwsi[n]->parent = i->opt_parent; + lsp->stdwsi[n]->sibling_list = i->opt_parent->child_list; + i->opt_parent->child_list = lsp->stdwsi[n]; + } + } + + if (lws_change_pollfd(lsp->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT)) + goto bail3; + if (lws_change_pollfd(lsp->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN)) + goto bail3; + if (lws_change_pollfd(lsp->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN)) + goto bail3; + + lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, + lsp->stdwsi[LWS_STDIN]->desc.sockfd, + lsp->stdwsi[LWS_STDOUT]->desc.sockfd, + lsp->stdwsi[LWS_STDERR]->desc.sockfd); + + /* we are ready with the redirection pipes... run the thing */ +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) + lsp->child_pid = fork(); +#else + lsp->child_pid = vfork(); +#endif + if (lsp->child_pid < 0) { + lwsl_err("%s: fork failed, errno %d", __func__, errno); + goto bail3; + } + +#if defined(__linux__) + prctl(PR_SET_PDEATHSIG, SIGTERM); +#endif + + if (lsp->info.disable_ctrlc) + /* stops non-daemonized main processess getting SIGINT + * from TTY */ + setpgrp(); + + if (lsp->child_pid) { + + /* we are the parent process */ + + lwsl_info("%s: lsp %p spawned PID %d\n", __func__, lsp, + lsp->child_pid); + + lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout, + i->timeout_us ? i->timeout_us : + 300 * LWS_US_PER_SEC); + + /* + * close: stdin:r, stdout:w, stderr:w + * hide from other forks: stdin:w, stdout:r, stderr:r + */ + for (n = 0; n < 3; n++) { + lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][!!(n == 0)]); + close(lsp->pipe_fds[n][!(n == 0)]); + } + + lsp->pipes_alive = 3; + lsp->created = lws_now_usecs(); + + if (i->owner) + lws_dll2_add_head(&lsp->dll, i->owner); + + if (i->timeout_us) + lws_sul_schedule(context, i->tsi, &lsp->sul, + lws_spawn_timeout, i->timeout_us); + + return lsp; + } + + /* + * We are the forked process, redirect and kill inherited things. + * + * Because of vfork(), we cannot do anything that changes pages in + * the parent environment. Stuff that changes kernel state for the + * process is OK. Stuff that happens after the execvpe() is OK. + */ + + if (i->chroot_path && chroot(i->chroot_path)) { + lwsl_err("%s: child chroot %s failed, errno %d\n", + __func__, i->chroot_path, errno); + + exit(2); + } + + /* cwd: somewhere we can at least read things and enter it */ + + wd = i->wd; + if (!wd) + wd = "/tmp"; + if (chdir(wd)) + lwsl_notice("%s: Failed to cd to %s\n", __func__, wd); + + for (m = 0; m < 3; m++) { + if (dup2(lsp->pipe_fds[m][!(m == 0)], m) < 0) { + lwsl_err("%s: stdin dup2 failed\n", __func__); + goto bail3; + } + close(lsp->pipe_fds[m][0]); + close(lsp->pipe_fds[m][1]); + } + + // lwsl_notice("%s: child cd %s, exec %s\n", __func__, wd, i->exec_array[0]); + +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) +#if defined(__linux__) + m = 0; + while (i->env_array[m]){ + char *p = strchr(i->env_array[m], '='); + *p++ = '\0'; + setenv(i->env_array[m], p, 1); + m++; + } +#endif + execvp(i->exec_array[0], (char * const *)&i->exec_array[0]); +#else + execvpe(i->exec_array[0], (char * const *)&i->exec_array[0], + &i->env_array[0]); +#endif + + lwsl_err("%s: child exec of %s failed %d\n", __func__, i->exec_array[0], + LWS_ERRNO); + + _exit(1); + +bail3: + + while (--n >= 0) + __remove_wsi_socket_from_fds(lsp->stdwsi[n]); +bail2: + for (n = 0; n < 3; n++) + if (lsp->stdwsi[n]) + __lws_free_wsi(lsp->stdwsi[n]); + +bail1: + for (n = 0; n < 3; n++) { + if (lsp->pipe_fds[n][0] >= 0) + close(lsp->pipe_fds[n][0]); + if (lsp->pipe_fds[n][1] >= 0) + close(lsp->pipe_fds[n][1]); + } + + lws_free(lsp); + + lwsl_err("%s: failed\n", __func__); + + return NULL; +} + +void +lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp) +{ + assert(lsp); + lsp->pipes_alive--; + lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive); + lws_spawn_reap(lsp); +} + +int +lws_spawn_get_stdfd(struct lws *wsi) +{ + return wsi->lsp_channel; +} diff --git a/lib/roles/cgi/cgi-server.c b/lib/roles/cgi/cgi-server.c index ccfe2d487..0e00bce98 100644 --- a/lib/roles/cgi/cgi-server.c +++ b/lib/roles/cgi/cgi-server.c @@ -68,58 +68,13 @@ urlencode(const char *in, int inlen, char *out, int outlen) return out - start; } -static struct lws * -lws_create_basic_wsi(struct lws_context *context, int tsi) -{ - struct lws *new_wsi; - - if (!context->vhost_list) - return NULL; - - if ((unsigned int)context->pt[tsi].fds_count == - context->fd_limit_per_thread - 1) { - lwsl_err("no space for new conn\n"); - return NULL; - } - - new_wsi = lws_zalloc(sizeof(struct lws), "new wsi"); - if (new_wsi == NULL) { - lwsl_err("Out of memory for new connection\n"); - return NULL; - } - - new_wsi->tsi = tsi; - new_wsi->context = context; - new_wsi->pending_timeout = NO_PENDING_TIMEOUT; - new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; - - /* initialize the instance struct */ - - lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, &role_ops_cgi); - - new_wsi->hdr_parsing_completed = 0; - new_wsi->position_in_fds_table = LWS_NO_FDS_POS; - - /* - * these can only be set once the protocol is known - * we set an unestablished connection's protocol pointer - * to the start of the defauly vhost supported list, so it can look - * for matching ones during the handshake - */ - new_wsi->protocol = context->vhost_list->protocols; - new_wsi->user_space = NULL; - new_wsi->desc.sockfd = LWS_SOCK_INVALID; - context->count_wsi_allocated++; - - return new_wsi; -} - int lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len, int timeout_secs, const struct lws_protocol_vhost_options *mp_cgienv) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_spawn_piped_info info; char *env_array[30], cgi_path[500], e[1024], *p = e, *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend; struct lws_cgi *cgi; @@ -142,65 +97,6 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, sum = cgi->summary; sumend = sum + strlen(cgi->summary) - 1; - for (n = 0; n < 3; n++) { - cgi->pipe_fds[n][0] = -1; - cgi->pipe_fds[n][1] = -1; - } - - /* create pipes for [stdin|stdout] and [stderr] */ - - for (n = 0; n < 3; n++) - if (pipe(cgi->pipe_fds[n]) == -1) - goto bail1; - - /* create cgi wsis for each stdin/out/err fd */ - - for (n = 0; n < 3; n++) { - cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi); - if (!cgi->stdwsi[n]) { - lwsl_err("%s: unable to create cgi stdwsi\n", __func__); - goto bail2; - } - cgi->stdwsi[n]->cgi_channel = n; - lws_vhost_bind_wsi(wsi->vhost, cgi->stdwsi[n]); - - lwsl_debug("%s: cgi stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__, - cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)], - cgi->pipe_fds[n][!(n == 0)]); - - /* read side is 0, stdin we want the write side, others read */ - cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)]; - if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, - O_NONBLOCK) < 0) { - lwsl_err("%s: setting NONBLOCK failed\n", __func__); - goto bail2; - } - } - - for (n = 0; n < 3; n++) { - if (wsi->context->event_loop_ops->sock_accept) - if (wsi->context->event_loop_ops->sock_accept(cgi->stdwsi[n])) - goto bail3; - - if (__insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n])) - goto bail3; - cgi->stdwsi[n]->parent = wsi; - cgi->stdwsi[n]->sibling_list = wsi->child_list; - wsi->child_list = cgi->stdwsi[n]; - } - - if (lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT)) - goto bail3; - if (lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN)) - goto bail3; - if (lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN)) - goto bail3; - - lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, - cgi->stdwsi[LWS_STDIN]->desc.sockfd, - cgi->stdwsi[LWS_STDOUT]->desc.sockfd, - cgi->stdwsi[LWS_STDERR]->desc.sockfd); - if (timeout_secs) lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs); @@ -260,7 +156,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, } if (script_uri_path_len < 0 && uritok < 0) - goto bail3; + goto bail; // if (script_uri_path_len < 0) // uritok = 0; @@ -317,7 +213,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, c = lws_hdr_copy(wsi, cgi_path + 12, sizeof(cgi_path) - 12, uritok); if (c < 0) - goto bail3; + goto bail; cgi_path[sizeof(cgi_path) - 1] = '\0'; env_array[n++] = cgi_path; @@ -430,7 +326,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, } env_array[n++] = p; - p += lws_snprintf(p, end - p, "SERVER_SOFTWARE=libwebsockets"); + p += lws_snprintf(p, end - p, "SERVER_SOFTWARE=lws"); p++; env_array[n] = NULL; @@ -440,108 +336,45 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, lwsl_notice(" %s\n", env_array[m]); #endif + memset(&info, 0, sizeof(info)); + info.env_array = env_array; + info.exec_array = exec_array; + info.max_log_lines = 20000; + info.opt_parent = wsi; + info.timeout_us = 5 * 60 * LWS_US_PER_SEC; + info.tsi = wsi->tsi; + info.vh = wsi->vhost; + info.ops = &role_ops_cgi; + info.plsp = &wsi->http.cgi->lsp; + /* * Actually having made the env, as a cgi we don't need the ah * any more */ - if (script_uri_path_len >= 0) + if (script_uri_path_len >= 0) { lws_header_table_detach(wsi, 0); - - /* we are ready with the redirection pipes... run the thing */ -#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) - cgi->pid = fork(); -#else - cgi->pid = vfork(); -#endif - if (cgi->pid < 0) { - lwsl_err("fork failed, errno %d", errno); - goto bail3; + info.disable_ctrlc = 1; } -#if defined(__linux__) - prctl(PR_SET_PDEATHSIG, SIGTERM); -#endif - if (script_uri_path_len >= 0) - /* stops non-daemonized main processess getting SIGINT - * from TTY */ - setpgrp(); - - if (cgi->pid) { - /* we are the parent process */ - wsi->context->count_cgi_spawned++; - lwsl_info("%s: cgi %p spawned PID %d\n", __func__, - cgi, cgi->pid); - - /* - * close: stdin:r, stdout:w, stderr:w - * hide from other forks: stdin:w, stdout:r, stderr:r - */ - for (n = 0; n < 3; n++) { - lws_plat_apply_FD_CLOEXEC(cgi->pipe_fds[n][!!(n == 0)]); - close(cgi->pipe_fds[n][!(n == 0)]); - } - - /* inform cgi owner of the child PID */ - n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, - LWS_CALLBACK_CGI_PROCESS_ATTACH, - wsi->user_space, NULL, cgi->pid); - (void)n; - - return 0; + wsi->http.cgi->lsp = lws_spawn_piped(&info); + if (!wsi->http.cgi->lsp) { + lwsl_err("%s: spawn failed\n", __func__); + goto bail; } - /* somewhere we can at least read things and enter it */ - if (chdir("/tmp")) - lwsl_notice("%s: Failed to chdir\n", __func__); + /* we are the parent process */ - /* We are the forked process, redirect and kill inherited things. - * - * Because of vfork(), we cannot do anything that changes pages in - * the parent environment. Stuff that changes kernel state for the - * process is OK. Stuff that happens after the execvpe() is OK. - */ + wsi->context->count_cgi_spawned++; - for (m = 0; m < 3; m++) { - if (dup2(cgi->pipe_fds[m][!(m == 0)], m) < 0) { - lwsl_err("%s: stdin dup2 failed\n", __func__); - goto bail3; - } - close(cgi->pipe_fds[m][0]); - close(cgi->pipe_fds[m][1]); - } + /* inform cgi owner of the child PID */ + n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_CGI_PROCESS_ATTACH, + wsi->user_space, NULL, cgi->lsp->child_pid); + (void)n; -#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) - for (m = 0; m < n; m++) { - p = strchr(env_array[m], '='); - *p++ = '\0'; - setenv(env_array[m], p, 1); - } - execvp(exec_array[0], (char * const *)&exec_array[0]); -#else - execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]); -#endif - - exit(1); - -bail3: - /* drop us from the pt cgi list */ - pt->http.cgi_list = cgi->cgi_list; - - while (--n >= 0) - __remove_wsi_socket_from_fds(wsi->http.cgi->stdwsi[n]); -bail2: - for (n = 0; n < 3; n++) - if (wsi->http.cgi->stdwsi[n]) - __lws_free_wsi(cgi->stdwsi[n]); - -bail1: - for (n = 0; n < 3; n++) { - if (cgi->pipe_fds[n][0] >= 0) - close(cgi->pipe_fds[n][0]); - if (cgi->pipe_fds[n][1] >= 0) - close(cgi->pipe_fds[n][1]); - } + return 0; +bail: lws_free_set_NULL(wsi->http.cgi); lwsl_err("%s: failed\n", __func__); @@ -773,7 +606,7 @@ post_hpack_recode: } } - n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); + n = lws_get_socket_fd(wsi->http.cgi->lsp->stdwsi[LWS_STDOUT]); if (n < 0) return -1; n = read(n, &c, 1); @@ -924,7 +757,7 @@ agin: m = !wsi->http.cgi->implied_chunked && !wsi->mux_substream && // !wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length; - n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); + n = lws_get_socket_fd(wsi->http.cgi->lsp->stdwsi[LWS_STDOUT]); if (n < 0) return -1; n = read(n, start, sizeof(buf) - LWS_PRE); @@ -1007,71 +840,26 @@ int lws_cgi_kill(struct lws *wsi) { struct lws_cgi_args args; - int status, n; + pid_t pid; + int n, m; lwsl_debug("%s: %p\n", __func__, wsi); - if (!wsi->http.cgi) + if (!wsi->http.cgi || !wsi->http.cgi->lsp) return 0; - if (wsi->http.cgi->pid > 0) { - n = waitpid(wsi->http.cgi->pid, &status, WNOHANG); - if (n > 0) { - lwsl_debug("%s: PID %d reaped\n", __func__, - wsi->http.cgi->pid); - goto handled; - } - /* kill the process group */ - n = kill(-wsi->http.cgi->pid, SIGTERM); - lwsl_debug("%s: SIGTERM child PID %d says %d (errno %d)\n", - __func__, wsi->http.cgi->pid, n, errno); - if (n < 0) { - /* - * hum seen errno=3 when process is listed in ps, - * it seems we don't always retain process grouping - * - * Direct these fallback attempt to the exact child - */ - n = kill(wsi->http.cgi->pid, SIGTERM); - if (n < 0) { - n = kill(wsi->http.cgi->pid, SIGPIPE); - if (n < 0) { - n = kill(wsi->http.cgi->pid, SIGKILL); - if (n < 0) - lwsl_info("%s: SIGKILL PID %d " - "failed errno %d " - "(maybe zombie)\n", - __func__, - wsi->http.cgi->pid, errno); - } - } - } - /* He could be unkillable because he's a zombie */ - n = 1; - while (n > 0) { - n = waitpid(-wsi->http.cgi->pid, &status, WNOHANG); - if (n > 0) - lwsl_debug("%s: reaped PID %d\n", __func__, n); - if (n <= 0) { - n = waitpid(wsi->http.cgi->pid, &status, WNOHANG); - if (n > 0) - lwsl_debug("%s: reaped PID %d\n", - __func__, n); - } - } - } + pid = wsi->http.cgi->lsp->child_pid; -handled: - args.stdwsi = &wsi->http.cgi->stdwsi[0]; + args.stdwsi = &wsi->http.cgi->lsp->stdwsi[0]; + lws_spawn_piped_kill_child_process(wsi->http.cgi->lsp); + /* that has invalidated and NULL'd wsi->http.cgi->lsp */ - if (wsi->http.cgi->pid != -1) { - int m = wsi->http.cgi->being_closed; + if (pid != -1) { + m = wsi->http.cgi->being_closed; n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_CGI_TERMINATED, wsi->user_space, (void *)&args, - wsi->http.cgi->pid); - if (wsi->http.cgi) - wsi->http.cgi->pid = -1; + pid); if (n && !m) lws_close_free_wsi(wsi, 0, "lws_cgi_kill"); } @@ -1100,7 +888,7 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) cgi = *pcgi; pcgi = &(*pcgi)->cgi_list; - if (cgi->pid <= 0) + if (cgi->lsp->child_pid <= 0) continue; /* finish sending cached headers */ @@ -1125,7 +913,7 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) * but we should do the terminated cgi callback * and close him if he's not already closing */ - if (n == cgi->pid) { + if (n == cgi->lsp->child_pid) { lwsl_debug("%s: found PID %d on cgi list\n", __func__, n); @@ -1140,7 +928,7 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) } /* defeat kill() */ - cgi->pid = 0; + cgi->lsp->child_pid = 0; lws_cgi_kill(cgi->wsi); break; @@ -1163,7 +951,7 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) cgi = *pcgi; pcgi = &(*pcgi)->cgi_list; - if (cgi->pid <= 0) + if (cgi->lsp->child_pid <= 0) continue; /* we deferred killing him after reaping his PID */ @@ -1189,7 +977,7 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) (unsigned long long)cgi->content_length_seen); /* reap it */ - if (waitpid(cgi->pid, &status, WNOHANG) > 0) { + if (waitpid(cgi->lsp->child_pid, &status, WNOHANG) > 0) { if (!cgi->content_length) { /* @@ -1202,10 +990,10 @@ lws_cgi_kill_terminated(struct lws_context_per_thread *pt) } finish_him: lwsl_debug("%s: found PID %d on cgi list\n", - __func__, cgi->pid); + __func__, cgi->lsp->child_pid); /* defeat kill() */ - cgi->pid = 0; + cgi->lsp->child_pid = 0; lws_cgi_kill(cgi->wsi); break; @@ -1221,7 +1009,7 @@ lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch) if (!wsi->http.cgi) return NULL; - return wsi->http.cgi->stdwsi[ch]; + return wsi->http.cgi->lsp->stdwsi[ch]; } void diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c index 2855a3bd2..6217335b2 100644 --- a/lib/roles/cgi/ops-cgi.c +++ b/lib/roles/cgi/ops-cgi.c @@ -32,22 +32,22 @@ rops_handle_POLLIN_cgi(struct lws_context_per_thread *pt, struct lws *wsi, assert(wsi->role_ops == &role_ops_cgi); - if (wsi->cgi_channel >= LWS_STDOUT && + if (wsi->lsp_channel >= LWS_STDOUT && !(pollfd->revents & pollfd->events & LWS_POLLIN)) return LWS_HPI_RET_HANDLED; - if (wsi->cgi_channel == LWS_STDIN && + if (wsi->lsp_channel == LWS_STDIN && !(pollfd->revents & pollfd->events & LWS_POLLOUT)) return LWS_HPI_RET_HANDLED; - if (wsi->cgi_channel == LWS_STDIN && + if (wsi->lsp_channel == LWS_STDIN && lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_info("failed at set pollfd\n"); return LWS_HPI_RET_WSI_ALREADY_DIED; } - args.ch = wsi->cgi_channel; - args.stdwsi = &wsi->parent->http.cgi->stdwsi[0]; + args.ch = wsi->lsp_channel; + args.stdwsi = &wsi->parent->http.cgi->lsp->stdwsi[0]; args.hdr_state = wsi->hdr_state; lwsl_debug("CGI LWS_STDOUT %p wsistate 0x%x\n", diff --git a/lib/roles/cgi/private-lib-roles-cgi.h b/lib/roles/cgi/private-lib-roles-cgi.h index 35b71c5d7..027ad1e34 100644 --- a/lib/roles/cgi/private-lib-roles-cgi.h +++ b/lib/roles/cgi/private-lib-roles-cgi.h @@ -54,7 +54,9 @@ struct lws; struct lws_cgi { struct lws_cgi *cgi_list; - struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ + + struct lws_spawn_piped *lsp; + struct lws *wsi; /* owner */ unsigned char *headers_buf; unsigned char *headers_start; @@ -72,10 +74,8 @@ struct lws_cgi { lws_filepos_t content_length; lws_filepos_t content_length_seen; - int pipe_fds[3][2]; int match[SIGNIFICANT_HDR_COUNT]; char l[12]; - int pid; int response_code; int lp; diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index b64397179..1ff208480 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -140,7 +140,7 @@ http_postbody: struct lws_cgi_args args; args.ch = LWS_STDIN; - args.stdwsi = &wsi->http.cgi->stdwsi[0]; + args.stdwsi = &wsi->http.cgi->lsp->stdwsi[0]; args.data = buf; args.len = body_chunk_len; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index fa53788a9..432d17dc1 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -2305,15 +2305,7 @@ lws_http_transaction_completed(struct lws *wsi) lwsl_debug("%s: cleaning cgi\n", __func__); wsi->http.cgi_transaction_complete = 1; lws_cgi_remove_and_kill(wsi); - for (n = 0; n < 3; n++) { - if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] == 0) - lwsl_err("ZERO FD IN CGI CLOSE"); - - if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] >= 0) { - close(wsi->http.cgi->pipe_fds[n][!!(n == 0)]); - wsi->http.cgi->pipe_fds[n][!!(n == 0)] = LWS_SOCK_INVALID; - } - } + lws_spawn_piped_destroy(&wsi->http.cgi->lsp); lws_free_set_NULL(wsi->http.cgi); wsi->http.cgi_transaction_complete = 0; diff --git a/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c b/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c index c67e8ed22..62a453a48 100644 --- a/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c +++ b/minimal-examples/http-server/minimal-http-server-cgi/minimal-http-server.c @@ -1,7 +1,7 @@ /* * lws-minimal-http-server-cgi * - * Written in 2010-2019 by Andy Green + * Written in 2010-2020 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. diff --git a/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh b/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh index 335787022..7437999b6 100755 --- a/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh +++ b/minimal-examples/http-server/minimal-http-server-cgi/my-cgi-script.sh @@ -4,7 +4,7 @@ echo -e -n "content-type: text/html\x0d\x0a" echo -e -n "transfer-encoding: chunked\x0d\x0a" echo -e -n "\x0d\x0a" -echo "" +echo "" echo "

lwstest script stdout

" >&2 echo -n "lwstest script stderr: REQUEST_METHOD was $REQUEST_METHOD" @@ -19,12 +19,12 @@ if [ "$REQUEST_METHOD" = "POST" ] ; then echo "read=\"$line\"" else echo "" - echo "" + echo "" cat /proc/meminfo | while read line ; do A=`echo "$line" | cut -d: -f1` B=`echo "$line" | tr -s ' ' | cut -d' ' -f2-` - echo -e "" - echo -e "" + echo -e "" + echo -e "" done echo "
/proc/meminfo
/proc/meminfo
$A$B
$A$B
" fi