]> granicus.if.org Git - apache/commitdiff
Implement the "CGI bucket" in mod_cgi to handle stderr output during
authorJoe Orton <jorton@apache.org>
Sun, 18 Apr 2004 16:02:57 +0000 (16:02 +0000)
committerJoe Orton <jorton@apache.org>
Sun, 18 Apr 2004 16:02:57 +0000 (16:02 +0000)
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

CHANGES
modules/generators/mod_cgi.c

diff --git a/CHANGES b/CHANGES
index e820ae34c9b9939b02f616c5568eba1ee3d57303..309892a265727bdd61d92b8b5a386d9a0af077c9 100644 (file)
--- 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 <Nick at WebThing dot com>, Ian Holsman]
index 1c828a3a74e6c26a1a08ef5a9c95a027f4e32350..cdebfcb9e95b03bbe8e2de38c6d19c78cf1ace5d 100644 (file)
@@ -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);
         }