From: Joe Orton Date: Sun, 18 Apr 2004 16:02:57 +0000 (+0000) Subject: Implement the "CGI bucket" in mod_cgi to handle stderr output during X-Git-Tag: pre_ajp_proxy~357 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d4cb879b8f929dc2590e8ea0c67fddbec7709d41;p=apache Implement the "CGI bucket" in mod_cgi to handle stderr output during script execution, preventing deadlock if stderr output fills the pipe buffer: * modules/generators/mod_cgi.c (log_script_err): Return a read error. (cgi_bucket_create, cgi_bucket_dup, cgi_read_stdout, cgi_bucket_read): New functions. (cgi_handler): Use new CGI bucket rather than a pipe bucket if APR_FILES_AS_SOCKETS; use zero read timeout from stdout/stderr during script execution. PR: 22030 Submitted by: Joe Orton, Jeff Trawick git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@103437 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index e820ae34c9..309892a265 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,10 @@ Changes with Apache 2.1.0-dev [Remove entries to the current 2.0 section below, when backported] + *) mod_cgi: Handle output on stderr during script execution on Unix + platforms; preventing deadlock when stderr output fills pipe buffer. + PR 22030. [Joe Orton, Jeff Trawick] + *) mod_deflate: New option for DEFLATE output file (force-gzip), new output filter 'INFLATE' for uncompressing responses. [Nick Kew , Ian Holsman] diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c index 1c828a3a74..cdebfcb9e9 100644 --- a/modules/generators/mod_cgi.c +++ b/modules/generators/mod_cgi.c @@ -32,6 +32,7 @@ #include "apr_optional.h" #include "apr_buckets.h" #include "apr_lib.h" +#include "apr_poll.h" #define APR_WANT_STRFUNC #define APR_WANT_MEMFUNC @@ -192,13 +193,14 @@ static int log_scripterror(request_rec *r, cgi_server_conf * conf, int ret, /* Soak up stderr from a script and redirect it to the error log. */ -static void log_script_err(request_rec *r, apr_file_t *script_err) +static apr_status_t log_script_err(request_rec *r, apr_file_t *script_err) { char argsbuffer[HUGE_STRING_LEN]; char *newline; + apr_status_t rv; - while (apr_file_gets(argsbuffer, HUGE_STRING_LEN, - script_err) == APR_SUCCESS) { + while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN, + script_err)) == APR_SUCCESS) { newline = strchr(argsbuffer, '\n'); if (newline) { *newline = '\0'; @@ -206,6 +208,8 @@ static void log_script_err(request_rec *r, apr_file_t *script_err) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", argsbuffer); } + + return rv; } static int log_script(request_rec *r, cgi_server_conf * conf, int ret, @@ -539,6 +543,172 @@ static void discard_script_output(apr_bucket_brigade *bb) } } +#if APR_FILES_AS_SOCKETS + +/* A CGI bucket type is needed to catch any output to stderr from the + * script; see PR 22030. */ +static const apr_bucket_type_t bucket_type_cgi; + +struct cgi_bucket_data { + apr_pollset_t *pollset; + request_rec *r; +}; + +/* Create a CGI bucket using pipes from script stdout 'out' + * and stderr 'err', for request 'r'. */ +static apr_bucket *cgi_bucket_create(request_rec *r, + apr_file_t *out, apr_file_t *err, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + apr_status_t rv; + apr_pollfd_t fd; + struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data); + + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + b->type = &bucket_type_cgi; + b->length = (apr_size_t)(-1); + b->start = -1; + + /* Create the pollset */ + rv = apr_pollset_create(&data->pollset, 2, r->pool, 0); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + fd.desc_type = APR_POLL_FILE; + fd.reqevents = APR_POLLIN; + fd.p = r->pool; + fd.desc.f = out; /* script's stdout */ + fd.client_data = (void *)1; + rv = apr_pollset_add(data->pollset, &fd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + fd.desc.f = err; /* script's stderr */ + fd.client_data = (void *)2; + rv = apr_pollset_add(data->pollset, &fd); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + data->r = r; + b->data = data; + return b; +} + +/* Create a duplicate CGI bucket using given bucket data */ +static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data, + apr_bucket_alloc_t *list) +{ + apr_bucket *b = apr_bucket_alloc(sizeof(*b), list); + APR_BUCKET_INIT(b); + b->free = apr_bucket_free; + b->list = list; + b->type = &bucket_type_cgi; + b->length = (apr_size_t)(-1); + b->start = -1; + b->data = data; + return b; +} + +/* Handle stdout from CGI child. Duplicate of logic from the _read + * method of the real APR pipe bucket implementation. */ +static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out, + const char **str, apr_size_t *len) +{ + char *buf; + apr_status_t rv; + + *str = NULL; + *len = APR_BUCKET_BUFF_SIZE; + buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */ + + rv = apr_file_read(out, buf, len); + + if (rv != APR_SUCCESS && rv != APR_EOF) { + apr_bucket_free(buf); + return rv; + } + + if (*len > 0) { + struct cgi_bucket_data *data = a->data; + apr_bucket_heap *h; + + /* Change the current bucket to refer to what we read */ + a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free); + h = a->data; + h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */ + *str = buf; + APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list)); + } + else { + apr_bucket_free(buf); + a = apr_bucket_immortal_make(a, "", 0); + *str = a->data; + } + return rv; +} + +/* Read method of CGI bucket: polls on stderr and stdout of the child, + * sending any stderr output immediately away to the error log. */ +static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str, + apr_size_t *len, apr_read_type_e block) +{ + struct cgi_bucket_data *data = b->data; + apr_interval_time_t timeout; + apr_status_t rv; + int gotdata = 0; + + timeout = block == APR_NONBLOCK_READ ? 0 : data->r->server->timeout; + + do { + const apr_pollfd_t *results; + apr_int32_t num; + + rv = apr_pollset_poll(data->pollset, timeout, &num, &results); + if (APR_STATUS_IS_TIMEUP(rv)) { + return timeout == 0 ? APR_EAGAIN : rv; + } + else if (APR_STATUS_IS_EINTR(rv)) { + continue; + } + else if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, + "poll failed waiting for CGI child"); + return rv; + } + + for (; num; num--, results++) { + if (results[0].client_data == (void *)1) { + /* stdout */ + rv = cgi_read_stdout(b, results[0].desc.f, str, len); + if (APR_STATUS_IS_EOF(rv)) { + rv = APR_SUCCESS; + } + gotdata = 1; + } else { + /* stderr */ + apr_status_t rv2 = log_script_err(data->r, results[0].desc.f); + if (APR_STATUS_IS_EOF(rv2)) { + apr_pollset_remove(data->pollset, &results[0]); + } + } + } + + } while (!gotdata); + + return rv; +} + +static const apr_bucket_type_t bucket_type_cgi = { + "CGI", 5, APR_BUCKET_DATA, + apr_bucket_destroy_noop, + cgi_bucket_read, + apr_bucket_setaside_notimpl, + apr_bucket_split_notimpl, + apr_bucket_copy_notimpl +}; + +#endif + static int cgi_handler(request_rec *r) { int nph; @@ -720,7 +890,14 @@ static int cgi_handler(request_rec *r) char sbuf[MAX_STRING_LEN]; int ret; +#if APR_FILES_AS_SOCKETS + apr_file_pipe_timeout_set(script_in, 0); + apr_file_pipe_timeout_set(script_err, 0); + + b = cgi_bucket_create(r, script_in, script_err, c->bucket_alloc); +#else b = apr_bucket_pipe_create(script_in, c->bucket_alloc); +#endif APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); @@ -734,6 +911,7 @@ static int cgi_handler(request_rec *r) if (location && location[0] == '/' && r->status == 200) { discard_script_output(bb); apr_brigade_destroy(bb); + apr_file_pipe_timeout_set(script_err, r->server->timeout); log_script_err(r, script_err); /* This redirect needs to be a GET no matter what the original * method was. @@ -768,6 +946,7 @@ static int cgi_handler(request_rec *r) * reason */ if (rv == APR_SUCCESS && !r->connection->aborted) { + apr_file_pipe_timeout_set(script_err, r->server->timeout); log_script_err(r, script_err); }