1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

lws_spawn_piped: break out from cgi

The vfork optimized spawn, stdxxx and terminal handling in the cgi
implementation is quite mature and sophisticated, and useful for
other things unrelated to cgi.  Break it out into its own public
api under LWS_WITH_SPAWN, off by default.

Expand it so the parent wsi is optional, and the role and protocol
bindings for stdxxx pipes can be set.  Allow optional sul timeout
and external lws_dll2 owner for extant children.

Remove inline style from minimal http-server-cgi
This commit is contained in:
Andy Green 2020-02-12 10:12:39 +00:00
parent 1194b4ddfd
commit 8a7e0edb7d
16 changed files with 783 additions and 304 deletions

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -22,6 +22,15 @@
* IN THE SOFTWARE.
*/
#if defined(LWS_WITH_SPAWN)
#if defined(WIN32) || defined(_WIN32)
#else
#include <sys/wait.h>
#include <sys/times.h>
#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

View file

@ -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

View file

@ -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;
}

View file

@ -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,

View file

@ -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 <sys/wait.h>
#include <sys/times.h>
#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);

511
lib/misc/spawn.c Normal file
View file

@ -0,0 +1,511 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
*
* 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 <unistd.h>
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;
}

View file

@ -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

View file

@ -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",

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -1,7 +1,7 @@
/*
* lws-minimal-http-server-cgi
*
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
* Written in 2010-2020 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.

View file

@ -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 "<html><body>"
echo "<html><meta charset="UTF-8"><body>"
echo "<h1>lwstest script stdout</h1>"
>&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 "<table>"
echo "<tr><td colspan=\"2\" style=\"font-size:120%;text-align:center\">/proc/meminfo</td></tr>"
echo "<tr><td colspan=\"2\">/proc/meminfo</td></tr>"
cat /proc/meminfo | while read line ; do
A=`echo "$line" | cut -d: -f1`
B=`echo "$line" | tr -s ' ' | cut -d' ' -f2-`
echo -e "<tr><td style=\"background-color:#f0e8c0\">$A</td>"
echo -e "<td style=\"text-align:right\">$B</td></tr>"
echo -e "<tr><td>$A</td>"
echo -e "<td>$B</td></tr>"
done
echo "</table>"
fi