]> granicus.if.org Git - apache/commitdiff
Merge the fcgi-proxy-dev branch to trunk, adding a FastCGI back end for
authorGarrett Rooney <rooneg@apache.org>
Sat, 22 Apr 2006 03:44:05 +0000 (03:44 +0000)
committerGarrett Rooney <rooneg@apache.org>
Sat, 22 Apr 2006 03:44:05 +0000 (03:44 +0000)
mod_proxy.  This log message is just a summary of the changes, for the
full original log messages see r357431:393955 in branches/fcgi-proxy-dev.

* modules/proxy/mod_proxy_fcgi.c: New file, holds the impementation of
  our new fcgi backend for mod_proxy.

* modules/proxy/fcgi_protocol.h: New file, holds constants and structures
  for the fcgi protocol.

* modules/proxy/mod_proxy_balancer.c
  (proxy_balancer_canon): Set up r->path_info, so the PATH_INFO env
   variable is correctly passed on to balancer workers.

* modules/proxy/config.m4: Build the new mod_proxy_fcgi module.

* support: Add fcgistarter to svn:ignore.

* support/Makefile.in: Build the new fcgistarter program.

* support/fcgistarter.c: New program, a helper for starting fcgi worker
  processes.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@396063 13f79535-47bb-0310-9956-ffa450edef68

modules/proxy/config.m4
modules/proxy/fcgi_protocol.h [new file with mode: 0644]
modules/proxy/mod_proxy_balancer.c
modules/proxy/mod_proxy_fcgi.c [new file with mode: 0644]
support/Makefile.in
support/fcgistarter.c [new file with mode: 0644]

index f131ee661111115a2f1c048653d0f60cf87eca02..7746b09569fd87ebdc24240e170a4f2584d1c989 100644 (file)
@@ -16,6 +16,7 @@ APACHE_MODULE(proxy, Apache proxy module, $proxy_objs, , $proxy_mods_enable)
 proxy_connect_objs="mod_proxy_connect.lo"
 proxy_ftp_objs="mod_proxy_ftp.lo"
 proxy_http_objs="mod_proxy_http.lo"
+proxy_fcgi_objs="mod_proxy_fcgi.lo"
 proxy_ajp_objs="mod_proxy_ajp.lo ajp_header.lo ajp_link.lo ajp_msg.lo"
 proxy_balancer_objs="mod_proxy_balancer.lo"
 
@@ -26,6 +27,7 @@ case "$host" in
     proxy_connect_objs="$proxy_connect_objs mod_proxy.la"
     proxy_ftp_objs="$proxy_ftp_objs mod_proxy.la"
     proxy_http_objs="$proxy_http_objs mod_proxy.la"
+    proxy_fcgi_objs="$proxy_fcgi_objs mod_proxy.la"
     proxy_ajp_objs="$proxy_ajp_objs mod_proxy.la"
     proxy_balancer_objs="$proxy_balancer_objs mod_proxy.la"
     ;;
@@ -34,6 +36,7 @@ esac
 APACHE_MODULE(proxy_connect, Apache proxy CONNECT module, $proxy_connect_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_ftp, Apache proxy FTP module, $proxy_ftp_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_http, Apache proxy HTTP module, $proxy_http_objs, , $proxy_mods_enable)
+APACHE_MODULE(proxy_fcgi, Apache proxy FastCGI module, $proxy_fcgi_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_ajp, Apache proxy AJP module, $proxy_ajp_objs, , $proxy_mods_enable)
 APACHE_MODULE(proxy_balancer, Apache proxy BALANCER module, $proxy_balancer_objs, , $proxy_mods_enable)
 
diff --git a/modules/proxy/fcgi_protocol.h b/modules/proxy/fcgi_protocol.h
new file mode 100644 (file)
index 0000000..34e6714
--- /dev/null
@@ -0,0 +1,107 @@
+/* Copyright 2005 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file fcgi_protocol.h
+ * @brief FastCGI protocol defines
+ *
+ * @addtogroup FCGI_defines
+ * @{
+ */
+
+#ifndef FCGI_PROTOCOL_H
+#define FCGI_PROTOCOL_H
+
+
+#define FCGI_VERSION 1
+
+#define FCGI_BEGIN_REQUEST       1
+#define FCGI_ABORT_REQUEST       2
+#define FCGI_END_REQUEST         3
+#define FCGI_PARAMS              4
+#define FCGI_STDIN               5
+#define FCGI_STDOUT              6
+#define FCGI_STDERR              7
+#define FCGI_DATA                8
+#define FCGI_GET_VALUES          9
+#define FCGI_GET_VALUES_RESULT  10
+#define FCGI_UNKNOWN_TYPE       11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+typedef struct {
+    unsigned char version;
+    unsigned char type;
+    unsigned char requestIdB1;
+    unsigned char requestIdB0;
+    unsigned char contentLengthB1;
+    unsigned char contentLengthB0;
+    unsigned char paddingLength;
+    unsigned char reserved;
+} fcgi_header;
+
+#define FCGI_HDR_VERSION_OFFSET         0
+#define FCGI_HDR_TYPE_OFFSET            1
+#define FCGI_HDR_REQUEST_ID_B1_OFFSET   2
+#define FCGI_HDR_REQUEST_ID_B0_OFFSET   3
+#define FCGI_HDR_CONTENT_LEN_B1_OFFSET  4
+#define FCGI_HDR_CONTENT_LEN_B0_OFFSET  5
+#define FCGI_HDR_PADDING_LEN_OFFSET     6
+#define FCGI_HDR_RESERVED_OFFSET        7
+
+#define FCGI_BRB_ROLEB1_OFFSET       0
+#define FCGI_BRB_ROLEB0_OFFSET       1
+#define FCGI_BRB_FLAGS_OFFSET        2
+#define FCGI_BRB_RESERVED0_OFFSET    3
+#define FCGI_BRB_RESERVED1_OFFSET    4
+#define FCGI_BRB_RESERVED2_OFFSET    5
+#define FCGI_BRB_RESERVED3_OFFSET    6
+#define FCGI_BRB_RESERVED4_OFFSET    7
+
+/*
+ * Number of bytes in a fcgi_header.  Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN  8
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN  1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER  1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER     3
+
+typedef struct {
+    unsigned char roleB1;
+    unsigned char roleB0;
+    unsigned char flags;
+    unsigned char reserved[5];
+} fcgi_begin_request_body;
+
+/*
+ * Maximum size of the allowed environment.
+ */
+#define FCGI_MAX_ENV_SIZE  65535
+
+/* #define FCGI_DUMP_ENV_VARS */
+
+
+#endif /* FCGI_PROTOCOL_H */
+/** @} */
index b8cba775b6359b1593446fc997468ac8f67d95d1..de1a15a476c00d61844a91c64e05178228ed29df 100644 (file)
@@ -75,6 +75,9 @@ static int proxy_balancer_canon(request_rec *r, char *url)
 
     r->filename = apr_pstrcat(r->pool, "proxy:balancer://", host,
             "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
+
+    r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+
     return OK;
 }
 
diff --git a/modules/proxy/mod_proxy_fcgi.c b/modules/proxy/mod_proxy_fcgi.c
new file mode 100644 (file)
index 0000000..51fb905
--- /dev/null
@@ -0,0 +1,993 @@
+/* Copyright 2005 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mod_proxy.h"
+#include "fcgi_protocol.h"
+
+module AP_MODULE_DECLARE_DATA proxy_fcgi_module;
+
+/*
+ * The below 3 functions serve to map the FCGI structs
+ * back and forth between an 8 byte array. We do this to avoid
+ * any potential padding issues when we send or read these
+ * structures.
+ *
+ * NOTE: These have specific internal knowledge of the
+ *       layout of the fcgi_header and fcgi_begin_request_body
+ *       structs!
+ */
+static void fcgi_header_to_array(fcgi_header *h, unsigned char a[])
+{
+    a[FCGI_HDR_VERSION_OFFSET]        = h->version;
+    a[FCGI_HDR_TYPE_OFFSET]           = h->type;
+    a[FCGI_HDR_REQUEST_ID_B1_OFFSET]  = h->requestIdB1;
+    a[FCGI_HDR_REQUEST_ID_B0_OFFSET]  = h->requestIdB0;
+    a[FCGI_HDR_CONTENT_LEN_B1_OFFSET] = h->contentLengthB1;
+    a[FCGI_HDR_CONTENT_LEN_B0_OFFSET] = h->contentLengthB0;
+    a[FCGI_HDR_PADDING_LEN_OFFSET]    = h->paddingLength;
+    a[FCGI_HDR_RESERVED_OFFSET]       = h->reserved;
+}
+
+static void fcgi_header_from_array(fcgi_header *h, unsigned char a[])
+{
+    h->version         = a[FCGI_HDR_VERSION_OFFSET];
+    h->type            = a[FCGI_HDR_TYPE_OFFSET];
+    h->requestIdB1     = a[FCGI_HDR_REQUEST_ID_B1_OFFSET];
+    h->requestIdB0     = a[FCGI_HDR_REQUEST_ID_B0_OFFSET];
+    h->contentLengthB1 = a[FCGI_HDR_CONTENT_LEN_B1_OFFSET];
+    h->contentLengthB0 = a[FCGI_HDR_CONTENT_LEN_B0_OFFSET];
+    h->paddingLength   = a[FCGI_HDR_PADDING_LEN_OFFSET];
+    h->reserved        = a[FCGI_HDR_RESERVED_OFFSET];
+}
+
+static void fcgi_begin_request_body_to_array(fcgi_begin_request_body *h,
+                                             unsigned char a[])
+{
+    a[FCGI_BRB_ROLEB1_OFFSET]    = h->roleB1;
+    a[FCGI_BRB_ROLEB0_OFFSET]    = h->roleB0;
+    a[FCGI_BRB_FLAGS_OFFSET]     = h->flags;
+    a[FCGI_BRB_RESERVED0_OFFSET] = h->reserved[0];
+    a[FCGI_BRB_RESERVED1_OFFSET] = h->reserved[1];
+    a[FCGI_BRB_RESERVED2_OFFSET] = h->reserved[2];
+    a[FCGI_BRB_RESERVED3_OFFSET] = h->reserved[3];
+    a[FCGI_BRB_RESERVED4_OFFSET] = h->reserved[4];
+}
+
+/*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+ * url is the URL starting with the first '/'
+ * def_port is the default port for this scheme.
+ */
+static int proxy_fcgi_canon(request_rec *r, char *url)
+{
+    char *host, sport[7];
+    const char *err, *path;
+    apr_port_t port = 8000;
+
+    if (strncasecmp(url, "fcgi://", 7) == 0) {
+        url += 5;
+    }
+    else {
+        return DECLINED;
+    }
+    
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                 "proxy: FCGI: canonicalising URL %s", url);
+
+    err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
+    if (err) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "error parsing URL %s: %s", url, err);
+        return HTTP_BAD_REQUEST;
+    }
+        
+    apr_snprintf(sport, sizeof(sport), ":%d", port);
+        
+    if (ap_strchr_c(host, ':')) {
+        /* if literal IPv6 address */
+        host = apr_pstrcat(r->pool, "[", host, "]", NULL);
+    }
+
+    path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
+                             r->proxyreq);
+    if (path == NULL)
+        return HTTP_BAD_REQUEST;
+
+    r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
+                              path, NULL);
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                  "proxy: FCGI: set r->filename to %s", r->filename);
+
+    r->path_info = apr_pstrcat(r->pool, "/", path, NULL);
+
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                  "proxy: FCGI: set r->path_info to %s", r->path_info);
+
+    return OK;
+}
+
+/*
+ * Fill in a fastcgi request header with the following type, request id,
+ * content length, and padding length.
+ *
+ * The header array must be at least FCGI_HEADER_LEN bytes long.
+ */
+static void fill_in_header(fcgi_header *header,
+                           unsigned char type,
+                           apr_uint16_t request_id,
+                           apr_uint16_t content_len,
+                           unsigned char padding_len)
+{
+    header->version = 1;
+
+    header->type = type;
+
+    header->requestIdB1 = ((request_id >> 8) & 0xff);
+    header->requestIdB0 = ((request_id) & 0xff);
+
+    header->contentLengthB1 = ((content_len >> 8) & 0xff);
+    header->contentLengthB0 = ((content_len) & 0xff);
+
+    header->paddingLength = padding_len;
+
+    header->reserved = 0;
+}
+
+/* Wrapper for apr_socket_sendv that handles updating the worker stats. */
+static apr_status_t send_data(proxy_conn_rec *conn,
+                              struct iovec *vec,
+                              int nvec,
+                              apr_size_t *len,
+                              int blocking)
+{
+    apr_status_t rv = APR_SUCCESS, arv;
+    apr_size_t written = 0, to_write = 0;
+    int i, offset;
+    apr_interval_time_t old_timeout;
+    apr_socket_t *s = conn->sock;
+
+    if (!blocking) {
+        arv = apr_socket_timeout_get(s, &old_timeout);
+        if (arv != APR_SUCCESS) {
+            return arv;
+        }
+        arv = apr_socket_timeout_set(s, 0);
+        if (arv != APR_SUCCESS) {
+            return arv;
+        }
+    }
+
+    for (i = 0; i < nvec; i++) {
+        to_write += vec[i].iov_len;
+    }
+
+    offset = 0;
+    while (to_write) {
+        apr_size_t n = 0;
+        rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
+        if (rv != APR_SUCCESS) {
+            break;
+        }
+        if (n > 0) {
+            written += n;
+            if (written >= to_write)
+                break;                 /* short circuit out */
+            for (i = offset; i < nvec; ) {
+                if (n >= vec[i].iov_len) {
+                    offset++;
+                    n -= vec[i++].iov_len;
+                } else {
+                    vec[i].iov_len -= n;
+                    vec[i].iov_base += n;
+                    break;
+                }
+            }
+        }
+    }
+
+    conn->worker->s->transferred += written;
+    *len = written;
+
+    if (!blocking) {
+        arv = apr_socket_timeout_set(s, old_timeout);
+        if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
+            return arv;
+        }
+    }
+    return rv;
+}
+
+/* Wrapper for apr_socket_recv that handles updating the worker stats. */
+static apr_status_t get_data(proxy_conn_rec *conn,
+                             char *buffer,
+                             apr_size_t *buflen)
+{
+    apr_status_t rv = apr_socket_recv(conn->sock, buffer, buflen);
+
+    if (rv == APR_SUCCESS) {
+        conn->worker->s->read += *buflen;
+    }
+
+    return rv;
+}
+
+static apr_status_t send_begin_request(proxy_conn_rec *conn, int request_id)
+{
+    struct iovec vec[2];
+    fcgi_header header;
+    unsigned char farray[FCGI_HEADER_LEN];
+    fcgi_begin_request_body brb;
+    unsigned char abrb[FCGI_HEADER_LEN];
+    apr_size_t len;
+
+    fill_in_header(&header, FCGI_BEGIN_REQUEST, request_id, sizeof(abrb), 0);
+
+    brb.roleB1 = ((FCGI_RESPONDER >> 8) & 0xff);
+    brb.roleB0 = ((FCGI_RESPONDER) & 0xff); 
+    brb.flags = FCGI_KEEP_CONN;
+
+    fcgi_header_to_array(&header, farray);
+    fcgi_begin_request_body_to_array(&brb, abrb);
+
+    vec[0].iov_base = farray;
+    vec[0].iov_len = sizeof(farray);
+    vec[1].iov_base = abrb;
+    vec[1].iov_len = sizeof(abrb);
+
+    return send_data(conn, vec, 2, &len, 1);
+}
+
+static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, 
+                                     int request_id)
+{
+    const apr_array_header_t *envarr;
+    const apr_table_entry_t *elts;
+    struct iovec vec[2];
+    fcgi_header header;
+    unsigned char farray[FCGI_HEADER_LEN];
+    apr_size_t bodylen, envlen;
+    char *body, *itr;
+    apr_status_t rv;
+    apr_size_t len;
+    int i, numenv;
+
+    ap_add_common_vars(r);
+    ap_add_cgi_vars(r);
+
+    /* XXX are there any FastCGI specific env vars we need to send? */
+
+    bodylen = envlen = 0;
+
+    /* XXX mod_cgi/mod_cgid use ap_create_environment here, which fills in
+     *     the TZ value specially.  We could use that, but it would mean
+     *     parsing the key/value pairs back OUT of the allocated env array,
+     *     not to mention allocating a totally useless array in the first
+     *     place, which would suck. */
+
+    envarr = apr_table_elts(r->subprocess_env);
+
+    elts = (const apr_table_entry_t *) envarr->elts;
+
+    for (i = 0; i < envarr->nelts; ++i) {
+        apr_size_t keylen, vallen;
+
+        if (! elts[i].key) {
+            continue;
+        }
+
+        keylen = strlen(elts[i].key);
+
+        if (keylen >> 7 == 0) {
+            envlen += 1;
+        }
+        else {
+            envlen += 4;
+        }
+
+        envlen += keylen;
+
+        vallen = strlen(elts[i].val);
+
+#ifdef FCGI_DUMP_ENV_VARS
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                      "proxy: FCGI: sending env var '%s' value '%s'",
+                      elts[i].key, elts[i].val);
+#endif
+
+        if (vallen >> 7 == 0) {
+            envlen += 1;
+        }
+        else {
+            envlen += 4;
+        }
+
+        envlen += vallen;
+
+        if (envlen > FCGI_MAX_ENV_SIZE) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "proxy: FCGI: truncating environment to %d bytes and %d elements",
+                          (int)bodylen, i);
+            break;
+        }
+
+        bodylen = envlen;
+    }
+
+    numenv = i;
+
+    body = apr_pcalloc(r->pool, bodylen);
+
+    itr = body;
+
+    for (i = 0; i < numenv; ++i) {
+        apr_size_t keylen, vallen;
+       
+        if (! elts[i].key) {
+            continue;
+        }
+
+        keylen = strlen(elts[i].key);
+
+        if (keylen >> 7 == 0) {
+            itr[0] = keylen & 0xff;
+            itr += 1;
+        }
+        else {
+            itr[0] = ((keylen >> 24) & 0xff) | 0x80;
+            itr[1] = ((keylen >> 16) & 0xff);
+            itr[2] = ((keylen >> 8) & 0xff);
+            itr[3] = ((keylen) & 0xff);
+            itr += 4;
+        }
+
+        vallen = strlen(elts[i].val);
+
+        if (vallen >> 7 == 0) {
+            itr[0] = vallen & 0xff;
+            itr += 1;
+        }
+        else {
+            itr[0] = ((vallen >> 24) & 0xff) | 0x80;
+            itr[1] = ((vallen >> 16) & 0xff);
+            itr[2] = ((vallen >> 8) & 0xff);
+            itr[3] = ((vallen) & 0xff);
+            itr += 4;
+        }
+
+        memcpy(itr, elts[i].key, keylen);
+        itr += keylen;
+
+        memcpy(itr, elts[i].val, vallen);
+        itr += vallen;
+    }
+
+    fill_in_header(&header, FCGI_PARAMS, request_id, bodylen, 0);
+    fcgi_header_to_array(&header, farray);
+
+    vec[0].iov_base = farray;
+    vec[0].iov_len = sizeof(farray);
+    vec[1].iov_base = body;
+    vec[1].iov_len = bodylen;
+
+    rv = send_data(conn, vec, 2, &len, 1);
+    if (rv) {
+        return rv;
+    }
+
+    fill_in_header(&header, FCGI_PARAMS, request_id, 0, 0);
+    fcgi_header_to_array(&header, farray);
+
+    vec[0].iov_base = farray;
+    vec[0].iov_len = sizeof(farray);
+
+    return send_data(conn, vec, 1, &len, 1);
+}
+
+enum {
+  HDR_STATE_READING_HEADERS,
+  HDR_STATE_GOT_CR,
+  HDR_STATE_GOT_CRLF,
+  HDR_STATE_GOT_CRLFCR,
+  HDR_STATE_GOT_LF,
+  HDR_STATE_DONE_WITH_HEADERS
+};
+
+/* Try to parse the script headers in the response from the back end fastcgi
+ * server.  Assumes that the contents of READBUF have already been added to
+ * the end of OB.  STATE holds the current header parsing state for this
+ * request.
+ *
+ * Returns -1 on error, 0 if it can't find the end of the headers, and 1 if
+ * it found the end of the headers and scans them successfully. */
+static int handle_headers(request_rec *r,
+                          int *state,
+                          char *readbuf,
+                          apr_bucket_brigade *ob)
+{
+    conn_rec *c = r->connection;
+    const char *itr = readbuf;
+
+    while (*itr) {
+        if (*itr == '\r') {
+            switch (*state) {
+                case HDR_STATE_GOT_CRLF:
+                    *state = HDR_STATE_GOT_CRLFCR;
+                    break;
+
+                default:
+                    *state = HDR_STATE_GOT_CR;
+                    break;
+            }
+        }
+        else if (*itr == '\n') {
+            switch (*state) {
+                 case HDR_STATE_GOT_LF:
+                     *state = HDR_STATE_DONE_WITH_HEADERS;
+                     break;
+
+                 case HDR_STATE_GOT_CR:
+                     *state = HDR_STATE_GOT_CRLF;
+                     break;
+
+                 case HDR_STATE_GOT_CRLFCR:
+                     *state = HDR_STATE_DONE_WITH_HEADERS;
+                     break;
+
+                 default:
+                     *state = HDR_STATE_GOT_LF;
+                     break;
+            }
+        }
+        else {
+            *state = HDR_STATE_READING_HEADERS;
+        }
+
+        if (*state == HDR_STATE_DONE_WITH_HEADERS)
+            break;
+
+        ++itr;
+    }
+
+    if (*state == HDR_STATE_DONE_WITH_HEADERS) {
+        int status = ap_scan_script_header_err_brigade(r, ob, NULL);
+        if (status != OK) {
+            apr_bucket *b;
+
+            r->status = status;
+
+            apr_brigade_cleanup(ob);
+
+            b = apr_bucket_eos_create(c->bucket_alloc);
+
+            APR_BRIGADE_INSERT_TAIL(ob, b);
+
+            ap_pass_brigade(r->output_filters, ob);
+
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                         "proxy: FCGI: Error parsing script headers");
+
+            return -1;
+        }
+        else {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+static void dump_header_to_log(request_rec *r, unsigned char fheader[],
+                               apr_size_t length)
+{
+#ifdef FCGI_DUMP_HEADERS
+    apr_size_t posn = 0;
+    char asc_line[20];
+    char hex_line[60];
+    int i = 0;
+
+    memset(asc_line, 0, sizeof(asc_line));
+    memset(hex_line, 0, sizeof(hex_line));
+
+    while (posn < length) {
+        unsigned char c = fheader[posn]; 
+        char hexval[3];
+
+        if (i >= 20) {
+            i = 0;
+
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                         "HEADER: %s %s", asc_line, hex_line);
+
+            memset(asc_line, 0, sizeof(asc_line));
+            memset(hex_line, 0, sizeof(hex_line));
+        }
+
+        if (isprint(c)) {
+            asc_line[i] = c;
+        }
+        else {
+            asc_line[i] = '.';
+        }
+
+        if ((c >> 4) >= 10) {
+            hex_line[i * 3] = 'a' + ((c >> 4) - 10);
+        }
+        else {
+            hex_line[i * 3] = '0' + (c >> 4);
+        }
+
+        if ((c & 0x0F) >= 10) {
+            hex_line[i * 3 + 1] = 'a' + ((c & 0x0F) - 10);
+        }
+        else {
+            hex_line[i * 3 + 1] = '0' + (c & 0xF);
+        }
+
+        hex_line[i * 3 + 2] = ' ';
+
+        i++;
+        posn++;
+    }
+
+    if (i != 1) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "HEADER: %s %s",
+                     asc_line, hex_line);
+    }
+
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "HEADER: -EOH-");
+#endif
+}
+
+static apr_status_t dispatch(proxy_conn_rec *conn, request_rec *r,
+                             int request_id)
+{
+    apr_bucket_brigade *ib, *ob;
+    int seen_end_of_headers = 0, done = 0;
+    apr_status_t rv = APR_SUCCESS;
+    conn_rec *c = r->connection;
+    struct iovec vec[2];
+    fcgi_header header;
+    unsigned char farray[FCGI_HEADER_LEN];
+    apr_pollfd_t pfd;
+    int header_state = HDR_STATE_READING_HEADERS;
+    apr_pool_t *setaside_pool;
+
+    apr_pool_create(&setaside_pool, r->pool);
+
+    pfd.desc_type = APR_POLL_SOCKET;
+    pfd.desc.s = conn->sock;
+    pfd.p = r->pool;
+    pfd.reqevents = APR_POLLIN | APR_POLLOUT;
+
+    ib = apr_brigade_create(r->pool, c->bucket_alloc);
+    ob = apr_brigade_create(r->pool, c->bucket_alloc);
+
+    while (! done) {
+        apr_interval_time_t timeout = conn->worker->timeout;
+        apr_size_t len;
+        int n;
+
+        /* We need SOME kind of timeout here, or virtually anything will
+         * cause timeout errors. */
+        if (! conn->worker->timeout_set) {
+            timeout = apr_time_from_sec(30);
+        }
+
+        rv = apr_poll(&pfd, 1, &n, timeout);
+        if (rv != APR_SUCCESS) {
+            break;
+        }
+
+        if (pfd.rtnevents & APR_POLLOUT) {
+            char writebuf[AP_IOBUFSIZE];
+            apr_size_t writebuflen;
+            int last_stdin = 0;
+
+            rv = ap_get_brigade(r->input_filters, ib,
+                                AP_MODE_READBYTES, APR_BLOCK_READ,
+                                sizeof(writebuf));
+            if (rv != APR_SUCCESS) {
+                break;
+            }
+
+            if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib))) {
+                last_stdin = 1;
+            }
+
+            writebuflen = sizeof(writebuf);
+
+            rv = apr_brigade_flatten(ib, writebuf, &writebuflen);
+
+            apr_brigade_cleanup(ib);
+
+            if (rv != APR_SUCCESS) {
+                break;
+            }
+
+            fill_in_header(&header, FCGI_STDIN, request_id,
+                           (apr_uint16_t) writebuflen, 0);
+            fcgi_header_to_array(&header, farray);
+
+            vec[0].iov_base = farray;
+            vec[0].iov_len = sizeof(farray);
+            vec[1].iov_base = writebuf;
+            vec[1].iov_len = writebuflen;
+
+            rv = send_data(conn, vec, 2, &len, 0);
+            if (rv != APR_SUCCESS) {
+                break;
+            }
+
+            if (last_stdin) {
+                pfd.reqevents = APR_POLLIN; /* Done with input data */
+
+                fill_in_header(&header, FCGI_STDIN, request_id, 0, 0);
+                fcgi_header_to_array(&header, farray);
+
+                vec[0].iov_base = farray;
+                vec[0].iov_len = sizeof(farray);
+
+                rv = send_data(conn, vec, 1, &len, 1);
+            }
+        }
+
+        if (pfd.rtnevents & APR_POLLIN) {
+            /* readbuf has one byte on the end that is always 0, so it's
+             * able to work with a strstr when we search for the end of
+             * the headers, even if we fill the entire length in the recv. */
+            char readbuf[AP_IOBUFSIZE + 1];
+            apr_size_t readbuflen;
+            apr_size_t clen;
+            int rid, type;
+            apr_bucket *b;
+            char plen;
+
+            memset(readbuf, 0, sizeof(readbuf));
+            memset(farray, 0, sizeof(farray));
+
+            /* First, we grab the header... */
+            readbuflen = FCGI_HEADER_LEN;
+
+            rv = get_data(conn, (char *) farray, &readbuflen);
+            if (rv != APR_SUCCESS) {
+                break;
+            }
+
+            dump_header_to_log(r, farray, readbuflen);
+            
+            if (readbuflen != FCGI_HEADER_LEN) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                             "proxy: FCGI: Failed to read entire header "
+                             "got %d wanted %d", 
+                             readbuflen, FCGI_HEADER_LEN);
+                rv = APR_EINVAL;
+                break;
+            }
+
+            fcgi_header_from_array(&header, farray);
+
+            if (header.version != FCGI_VERSION) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                             "proxy: FCGI: Got bogus version %d",
+                             (int) header.version);
+                rv = APR_EINVAL;
+                break;
+            }
+
+            type = header.type;
+
+            rid = header.requestIdB1 << 8;
+            rid |= header.requestIdB0;
+
+            if (rid != request_id) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                             "proxy: FCGI: Got bogus rid %d, expected %d",
+                             rid, request_id);
+                rv = APR_EINVAL;
+                break;
+            }
+
+            clen = header.contentLengthB1 << 8;
+            clen |= header.contentLengthB0;
+
+            plen = header.paddingLength;
+
+recv_again:
+            if (clen > sizeof(readbuf) - 1) {
+                readbuflen = sizeof(readbuf) - 1;
+            } else {
+                readbuflen = clen;
+            }
+
+            /* Now get the actual data.  Yes it sucks to do this in a second
+             * recv call, this will eventually change when we move to real
+             * nonblocking recv calls. */
+            if (readbuflen != 0) {
+                rv = get_data(conn, readbuf, &readbuflen);
+                if (rv != APR_SUCCESS) {
+                    break;
+                }
+                readbuf[readbuflen] = 0;
+            }
+
+            switch (type) {
+            case FCGI_STDOUT:
+                if (clen != 0) {
+                    b = apr_bucket_transient_create(readbuf,
+                                                    readbuflen,
+                                                    c->bucket_alloc);
+
+                    APR_BRIGADE_INSERT_TAIL(ob, b);
+
+                    if (! seen_end_of_headers) {
+                        int st = handle_headers(r, &header_state, readbuf, ob);
+
+                        if (st == 1) {
+                            seen_end_of_headers = 1;
+
+                            rv = ap_pass_brigade(r->output_filters, ob);
+                            if (rv != APR_SUCCESS) {
+                                break;
+                            }
+
+                            apr_brigade_cleanup(ob);
+
+                            apr_pool_clear(setaside_pool);
+                        }
+                        else if (st == -1) {
+                            rv = APR_EINVAL;
+                            break;
+                        }
+                        else {
+                            /* We're still looking for the end of the
+                             * headers, so this part of the data will need
+                             * to persist. */
+                            apr_bucket_setaside(b, setaside_pool);
+                        }
+                    } else {
+                        /* we've already passed along the headers, so now pass
+                         * through the content.  we could simply continue to
+                         * setaside the content and not pass until we see the
+                         * 0 content-length (below, where we append the EOS),
+                         * but that could be a huge amount of data; so we pass
+                         * along smaller chunks
+                         */
+                        rv = ap_pass_brigade(r->output_filters, ob);
+                        if (rv != APR_SUCCESS) {
+                            break;
+                        }
+                        apr_brigade_cleanup(ob);
+                    }
+
+                    /* If we didn't read all the data go back and get the
+                     * rest of it. */
+                    if (clen > readbuflen) {
+                        clen -= readbuflen;
+                        goto recv_again;
+                    }
+                } else {
+                    /* XXX what if we haven't seen end of the headers yet? */
+
+                    b = apr_bucket_eos_create(c->bucket_alloc);
+
+                    APR_BRIGADE_INSERT_TAIL(ob, b);
+
+                    rv = ap_pass_brigade(r->output_filters, ob);
+                    if (rv != APR_SUCCESS) {
+                        break;
+                    }
+
+                    /* XXX Why don't we cleanup here?  (logic from AJP) */
+                }
+                break;
+
+            case FCGI_STDERR:
+                /* TODO: Should probably clean up this logging a bit... */
+                if (clen) {
+                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                                 "proxy: FCGI: Got error '%s'", readbuf);
+                }
+
+                if (clen > readbuflen) {
+                    clen -= readbuflen;
+                    goto recv_again;
+                }
+                break;
+
+            case FCGI_END_REQUEST:
+                done = 1;
+                break;
+
+            default:
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                             "proxy: FCGI: Got bogus record %d", type);
+                break;
+            }
+
+            if (plen) {
+                readbuflen = plen;
+
+                rv = get_data(conn, readbuf, &readbuflen);
+                if (rv != APR_SUCCESS) {
+                    break;
+                }
+            }
+        }
+    }
+
+    apr_brigade_destroy(ib);
+    apr_brigade_destroy(ob);
+
+    return rv;
+}
+
+/*
+ * process the request and write the response.
+ */
+static int fcgi_do_request(apr_pool_t *p, request_rec *r,
+                           proxy_conn_rec *conn,
+                           conn_rec *origin,
+                           proxy_dir_conf *conf,
+                           apr_uri_t *uri,
+                           char *url, char *server_portstr)
+{
+    /* Request IDs are arbitrary numbers that we assign to a
+     * single request. This would allow multiplex/pipelinig of 
+     * multiple requests to the same FastCGI connection, but 
+     * we don't support that, and always use a value of '1' to
+     * keep things simple. */
+    int request_id = 1; 
+    apr_status_t rv;
+   
+    /* Step 1: Send FCGI_BEGIN_REQUEST */
+    rv = send_begin_request(conn, request_id);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+                     "proxy: FCGI: Failed Writing Request to %s:",
+                     server_portstr);
+        conn->close = 1;
+        return HTTP_SERVICE_UNAVAILABLE;
+    }
+    
+    /* Step 2: Send Enviroment via FCGI_PARAMS */
+    rv = send_environment(conn, r, request_id);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+                     "proxy: FCGI: Failed writing Environment to %s:",
+                     server_portstr);
+        conn->close = 1;
+        return HTTP_SERVICE_UNAVAILABLE;
+    }
+
+    /* Step 3: Read records from the back end server and handle them. */
+    rv = dispatch(conn, r, request_id);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
+                     "proxy: FCGI: Error dispatching request to %s:",
+                     server_portstr);
+        conn->close = 1;
+        return HTTP_SERVICE_UNAVAILABLE;
+    }
+
+    return OK;
+}
+
+#define FCGI_SCHEME "FCGI"
+
+/*
+ * This handles fcgi:(dest) URLs
+ */
+static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker,
+                              proxy_server_conf *conf,
+                              char *url, const char *proxyname,
+                              apr_port_t proxyport)
+{
+    int status;
+    char server_portstr[32];
+    conn_rec *origin = NULL;
+    proxy_conn_rec *backend = NULL;
+
+    proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+                                                 &proxy_module);
+
+    apr_pool_t *p = r->pool;
+
+    apr_uri_t *uri = apr_palloc(r->pool, sizeof(*uri));
+
+    ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                 "proxy: FCGI: url: %s proxyname: %s proxyport: %d",
+                 url, proxyname, proxyport);
+
+    if (strncasecmp(url, "fcgi://", 7) == 0) {
+        url += 5;
+    }
+    else {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                     "proxy: FCGI: declining URL %s", url);
+        return DECLINED;
+    }
+    
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+                 "proxy: FCGI: serving URL %s", url);
+
+    /* Create space for state information */
+    if (! backend) {
+        status = ap_proxy_acquire_connection(FCGI_SCHEME, &backend, worker,
+                                             r->server);
+        if (status != OK) {
+            if (backend) {
+                backend->close_on_recycle = 1;
+                ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
+            }
+            return status;
+        }
+    }
+
+    backend->is_ssl = 0;
+
+    /* XXX Setting close_on_recycle to 0 is a great way to end up with
+     *     timeouts at this point, since we lack good ways to manage the
+     *     back end fastcgi processes.  This should be revisited when we
+     *     have a better story on that part of things. */
+
+    backend->close_on_recycle = 1;
+
+    /* Step One: Determine Who To Connect To */
+    status = ap_proxy_determine_connection(p, r, conf, worker, backend,
+                                           uri, &url, proxyname, proxyport,
+                                           server_portstr,
+                                           sizeof(server_portstr));
+    if (status != OK) {
+        goto cleanup;
+    }
+
+    /* Step Two: Make the Connection */
+    if (ap_proxy_connect_backend(FCGI_SCHEME, backend, worker, r->server)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
+                     "proxy: FCGI: failed to make connection to backend: %s",
+                     backend->hostname);
+        status = HTTP_SERVICE_UNAVAILABLE;
+        goto cleanup;
+    }
+
+    /* Step Three: Process the Request */
+    status = fcgi_do_request(p, r, backend, origin, dconf, uri, url,
+                             server_portstr);
+
+cleanup:
+    /* Do not close the socket */
+    ap_proxy_release_connection(FCGI_SCHEME, backend, r->server);
+    return status;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+    proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST);
+    proxy_hook_canon_handler(proxy_fcgi_canon, NULL, NULL, APR_HOOK_FIRST);
+}
+
+module AP_MODULE_DECLARE_DATA proxy_fcgi_module = {
+    STANDARD20_MODULE_STUFF,
+    NULL,                       /* create per-directory config structure */
+    NULL,                       /* merge per-directory config structures */
+    NULL,                       /* create per-server config structure */
+    NULL,                       /* merge per-server config structures */
+    NULL,                       /* command apr_table_t */
+    register_hooks              /* register hooks */
+};
+
index 53672e2583acb470533db3950ebeb50d171c9936..670e465d9dc69c293df55de4a867883ac011691c 100644 (file)
@@ -3,7 +3,7 @@ DISTCLEAN_TARGETS = apxs apachectl dbmmanage log_server_status \
 
 CLEAN_TARGETS = suexec
 
-PROGRAMS = htpasswd htdigest rotatelogs logresolve ab checkgid htdbm htcacheclean httxt2dbm
+PROGRAMS = htpasswd htdigest rotatelogs logresolve ab checkgid htdbm htcacheclean httxt2dbm fcgistarter
 TARGETS  = $(PROGRAMS)
 
 PROGRAM_LDADD        = $(UTIL_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS)
@@ -70,3 +70,6 @@ httxt2dbm_OBJECTS = httxt2dbm.lo
 httxt2dbm: $(httxt2dbm_OBJECTS)
        $(LINK) $(httxt2dbm_LTFLAGS) $(httxt2dbm_OBJECTS) $(PROGRAM_LDADD)
 
+fcgistarter_OBJECTS = fcgistarter.lo
+fcgistarter: $(fcgistarter_OBJECTS)
+       $(LINK) $(fcgistarter_LTFLAGS) $(fcgistarter_OBJECTS) $(PROGRAM_LDADD)
diff --git a/support/fcgistarter.c b/support/fcgistarter.c
new file mode 100644 (file)
index 0000000..ad9b9e5
--- /dev/null
@@ -0,0 +1,212 @@
+/* Copyright 2006 The Apache Software Foundation or its licensors, as
+ * applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_network_io.h>
+#include <apr_thread_proc.h>
+#include <apr_getopt.h>
+#include <apr_portable.h>
+
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h> /* For EXIT_SUCCESS, EXIT_FAILURE */
+#endif
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h> /* For execl */
+#endif
+
+static const char *usage_message =
+    "usage: fcgistarter -c <command> -p <port> [-i <interface> -N <num>]\n"
+    "\n"
+    "If an interface is not specified, any available will be used.\n";
+
+static void usage()
+{
+    fprintf(stderr, "%s", usage_message);
+
+    exit(EXIT_FAILURE);
+}
+
+static void exit_error(apr_status_t rv, const char *func)
+{
+    char buffer[1024];
+
+    fprintf(stderr,
+            "%s: %s\n",
+            func,
+            apr_strerror(rv, buffer, sizeof(buffer)));
+
+    exit(EXIT_FAILURE);
+}
+
+int main(int argc, const char *argv[])
+{
+    apr_file_t *infd, *skwrapper;
+    apr_sockaddr_t *skaddr;
+    apr_procattr_t *pattr;
+    apr_getopt_t *gopt;
+    apr_socket_t *skt;
+    apr_pool_t *pool;
+    apr_status_t rv;
+    apr_proc_t proc;
+
+
+    /* Command line arguments */
+    int num_to_start = 1, port = 0;
+    const char *interface = NULL;
+    const char *command = NULL;
+
+    apr_initialize();
+
+    atexit(apr_terminate);
+
+    apr_pool_create(&pool, NULL);
+
+    rv = apr_getopt_init(&gopt, pool, argc, argv);
+    if (rv) {
+        return EXIT_FAILURE;
+    }
+
+    for (;;) {
+        const char *arg;
+        char opt;
+
+        rv = apr_getopt(gopt, "c:p:i:N:", &opt, &arg);
+        if (APR_STATUS_IS_EOF(rv)) {
+            break;
+        } else if (rv) {
+            usage();
+        } else {
+            switch (opt) {
+            case 'c':
+                command = arg;
+                break;
+
+            case 'p':
+                port = atoi(arg);
+                if (! port) {
+                    usage();
+                }
+                break;
+
+            case 'i':
+                interface = arg;
+                break;
+
+            case 'N':
+                num_to_start = atoi(arg);
+                if (! num_to_start) {
+                    usage();
+                }
+                break;
+
+            default:
+                break;
+            }
+        }
+    }
+
+    if (! command || ! port) {
+        usage();
+    }
+
+    rv = apr_socket_create(&skt, APR_INET, SOCK_STREAM, APR_PROTO_TCP, pool);
+    if (rv) {
+        exit_error(rv, "apr_socket_create");
+    }
+
+    rv = apr_sockaddr_info_get(&skaddr, interface, APR_UNSPEC, port, 0, pool);
+    if (rv) {
+        exit_error(rv, "apr_sockaddr_info_get");
+    }
+
+    rv = apr_socket_bind(skt, skaddr);
+    if (rv) {
+        exit_error(rv, "apr_socket_bind");
+    }
+
+    rv = apr_socket_listen(skt, 1024);
+    if (rv) {
+        exit_error(rv, "apr_socket_listen");
+    }
+
+    while (--num_to_start >= 0) {
+        rv = apr_proc_fork(&proc, pool);
+        if (rv == APR_INCHILD) {
+            apr_os_file_t oft = 0;
+            apr_os_sock_t oskt;
+
+            rv = apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
+            if (rv) {
+                exit_error(rv, "apr_proc_detach");
+            }
+
+#if defined(WIN32) || defined(OS2) || defined(NETWARE)
+#error "Please implement me."
+#else
+
+            /* Ok, so we need a file that has file descriptor 0 (which
+             * FastCGI wants), but points to our socket.  This isn't really
+             * possible in APR, so we cheat a bit.  I have no idea how to
+             * do this on a non-unix platform, so for now this is platform
+             * specific.  Ick.
+             *
+             * Note that this has to happen post-detach, otherwise fd 0
+             * gets closed during apr_proc_detach and it's all for nothing.
+             *
+             * Unfortunately, doing this post detach means we have no way
+             * to let anyone know if there's a problem at this point :( */
+
+            rv = apr_os_file_put(&infd, &oft, APR_READ | APR_WRITE, pool);
+            if (rv) {
+                exit(EXIT_FAILURE);
+            }
+
+            rv = apr_os_sock_get(&oskt, skt);
+            if (rv) {
+                exit(EXIT_FAILURE);
+            }
+
+            rv = apr_os_file_put(&skwrapper, &oskt, APR_READ | APR_WRITE,
+                                 pool);
+            if (rv) {
+                exit(EXIT_FAILURE);
+            }
+
+            rv = apr_file_dup2(infd, skwrapper, pool);
+            if (rv) {
+                exit(EXIT_FAILURE);
+            }
+
+            /* XXX Can't use apr_proc_create because there's no way to get
+             *     infd into the procattr without going through another dup2,
+             *     which means by the time it gets to the fastcgi process it
+             *     is no longer fd 0, so it doesn't work.  Sigh. */
+
+            execl(command, NULL);
+#endif
+        } else if (rv == APR_INPARENT) {
+            if (num_to_start == 0) {
+                apr_socket_close(skt);
+            }
+        } else {
+            exit_error(rv, "apr_proc_fork");
+        }
+    }
+
+    return EXIT_SUCCESS;
+}