aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Bühler <stbuehler@web.de>2009-03-28 13:24:51 +0100
committerStefan Bühler <stbuehler@web.de>2009-03-28 13:24:51 +0100
commit418a6844a7f666d3504841380ab607be8ab5d0f4 (patch)
treed14a6694fa8c3e8598a2c37498bd0117b4255d92
downloadfcgi-cgi-418a6844a7f666d3504841380ab607be8ab5d0f4.tar.gz
fcgi-cgi-418a6844a7f666d3504841380ab607be8ab5d0f4.zip
Initial commit
-rw-r--r--CMakeLists.txt73
-rw-r--r--COPYING22
-rw-r--r--README2
-rw-r--r--config.h.cmake6
-rw-r--r--fastcgi.c740
-rw-r--r--fastcgi.h160
-rw-r--r--fcgi-cgi.c527
7 files changed, 1530 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..d3f4f3a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,73 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR)
+
+cmake_policy(VERSION 2.6.0)
+
+INCLUDE(CheckIncludeFiles)
+INCLUDE(CheckLibraryExists)
+INCLUDE(FindPkgConfig)
+
+MACRO(ADD_TARGET_PROPERTIES _target _name _properties)
+ SET(_properties ${ARGV})
+ LIST(REMOVE_AT _properties 0)
+ LIST(REMOVE_AT _properties 0)
+ GET_TARGET_PROPERTY(_old_properties ${_target} ${_name})
+ #MESSAGE("adding property to ${_target} ${_name}: ${_properties}")
+ IF(NOT _old_properties)
+ # in case it's NOTFOUND
+ SET(_old_properties)
+ ELSE(NOT _old_properties)
+ SET(_old_properties "${_old_properties} ")
+ ENDIF(NOT _old_properties)
+ SET_TARGET_PROPERTIES(${_target} PROPERTIES ${_name} "${_old_properties}${_properties}")
+ENDMACRO(ADD_TARGET_PROPERTIES)
+
+PROJECT(fcgi-cgi)
+SET(PACKAGE_VERSION 0.1.0)
+IF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+ SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE)
+ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+
+
+# libev
+CHECK_INCLUDE_FILES(ev.h HAVE_EV_H)
+IF(HAVE_EV_H)
+ CHECK_LIBRARY_EXISTS(ev ev_loop "" HAVE_LIBEV)
+ IF(HAVE_LIBEV)
+ SET(EV_LIBRARIES ev)
+ SET(EV_STATIC_LIBRARIES ev;m)
+ CHECK_LIBRARY_EXISTS(rt clock_gettime "" NEED_RT)
+ IF(NEED_RT)
+ SET(EV_STATIC_LIBRARIES ${EV_STATIC_LIBRARIES} rt)
+ ENDIF(NEED_RT)
+ ELSE(HAVE_LIBEV)
+ MESSAGE(FATAL_ERROR "Couldn't find lib ev")
+ ENDIF(HAVE_LIBEV)
+ELSE(HAVE_EV_H)
+ MESSAGE(FATAL_ERROR "Couldn't find <ev.h>")
+ENDIF(HAVE_EV_H)
+
+# GLIB 2
+pkg_check_modules (GLIB2 REQUIRED glib-2.0)
+SET(GLIB_INCLUDES ${GLIB2_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS}/glib-2.0/ ${GLIB2_INCLUDE_DIRS}/glib-2.0/include/)
+INCLUDE_DIRECTORIES(${GLIB_INCLUDES})
+
+SET(MAIN_SOURCE fastcgi.c fcgi-cgi.c)
+
+SET(PACKAGE_NAME ${CMAKE_PROJECT_NAME})
+SET(PACKAGE_VERSION ${PACKAGE_VERSION})
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h ESCAPE_QUOTES)
+ADD_DEFINITIONS(-DHAVE_CONFIG_H)
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_executable(fcgi-cgi ${MAIN_SOURCE})
+
+ADD_TARGET_PROPERTIES(fcgi-cgi COMPILE_FLAGS "-std=gnu99 -Wall -g -Wshadow -W -pedantic -fPIC -D_GNU_SOURCE")
+
+# libev
+TARGET_LINK_LIBRARIES(fcgi-cgi "${EV_LIBRARIES}")
+
+# GLIB 2
+ADD_TARGET_PROPERTIES(fcgi-cgi LINK_FLAGS "${GLIB2_LDFLAGS}")
+ADD_TARGET_PROPERTIES(fcgi-cgi COMPILE_FLAGS "${GLIB2_CFLAGS_OTHER}")
+
+INSTALL(TARGETS fcgi-cgi DESTINATION bin)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..174e4e8
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,22 @@
+
+The MIT License
+
+Copyright (c) 2009 Stefan Bühler
+
+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.
diff --git a/README b/README
new file mode 100644
index 0000000..6919976
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+
+fcgi-cgi is a FastCGI application to run cgi applications.
diff --git a/config.h.cmake b/config.h.cmake
new file mode 100644
index 0000000..69bb180
--- /dev/null
+++ b/config.h.cmake
@@ -0,0 +1,6 @@
+/*
+ CMake autogenerated config.h file. Do not edit!
+*/
+
+#define PACKAGE_NAME "${PACKAGE_NAME}"
+#define PACKAGE_VERSION "${PACKAGE_VERSION}"
diff --git a/fastcgi.c b/fastcgi.c
new file mode 100644
index 0000000..4ecd971
--- /dev/null
+++ b/fastcgi.c
@@ -0,0 +1,740 @@
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fastcgi.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+/* some util functions */
+#define GSTR_LEN(x) (x) ? (x)->str : "", (x) ? (x)->len : 0
+#define UNUSED(x) ((void)(x))
+#define ERROR(...) g_printerr("fastcgi.c:" G_STRINGIFY(__LINE__) ": " __VA_ARGS__)
+
+static void fd_init(int fd) {
+#ifdef _WIN32
+ int i = 1;
+#endif
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef O_NONBLOCK
+ fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#elif defined _WIN32
+ ioctlsocket(fd, FIONBIO, &i);
+#endif
+}
+
+static void ev_io_add_events(struct ev_loop *loop, ev_io *watcher, int events) {
+ if ((watcher->events & events) == events) return;
+ ev_io_stop(loop, watcher);
+ ev_io_set(watcher, watcher->fd, watcher->events | events);
+ ev_io_start(loop, watcher);
+}
+
+static void ev_io_rem_events(struct ev_loop *loop, ev_io *watcher, int events) {
+ if (0 == (watcher->events & events)) return;
+ ev_io_stop(loop, watcher);
+ ev_io_set(watcher, watcher->fd, watcher->events & ~events);
+ ev_io_start(loop, watcher);
+}
+/* end: some util functions */
+
+static const gchar __padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+static void append_padding(GString *s, guint8 padlen) {
+ g_string_append_len(s, __padding, padlen);
+}
+
+/* returns padding length */
+static guint8 stream_build_fcgi_record(GString *buf, guint8 type, guint16 requestid, guint16 datalen) {
+ guint16 w;
+ guint8 padlen = (8 - (datalen & 0x7)) % 8; /* padding must be < 8 */
+
+ g_string_set_size(buf, FCGI_HEADER_LEN);
+ g_string_truncate(buf, 0);
+
+ g_string_append_c(buf, FCGI_VERSION_1);
+ g_string_append_c(buf, type);
+ w = htons(requestid);
+ g_string_append_len(buf, (const gchar*) &w, sizeof(w));
+ w = htons(datalen);
+ g_string_append_len(buf, (const gchar*) &w, sizeof(w));
+ g_string_append_c(buf, padlen);
+ g_string_append_c(buf, 0);
+ return padlen;
+}
+
+/* returns padding length */
+static guint8 stream_send_fcgi_record(fastcgi_gstring_queue *out, guint8 type, guint16 requestid, guint16 datalen) {
+ GString *record = g_string_sized_new(FCGI_HEADER_LEN);
+ guint8 padlen = stream_build_fcgi_record(record, type, requestid, datalen);
+ fastcgi_gstring_queue_append(out, record);
+ return padlen;
+}
+
+static void stream_send_data(fastcgi_gstring_queue *out, guint8 type, guint16 requestid, const gchar *data, size_t datalen) {
+ while (datalen > 0) {
+ guint16 tosend = (datalen > G_MAXUINT16) ? G_MAXUINT16 : datalen;
+ guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend);
+ GString *tmps = g_string_sized_new(tosend + padlen);
+ g_string_append_len(tmps, data, tosend);
+ append_padding(tmps, padlen);
+ fastcgi_gstring_queue_append(out, tmps);
+ data += tosend;
+ datalen -= tosend;
+ }
+}
+
+/* kills string */
+static void stream_send_string(fastcgi_gstring_queue *out, guint8 type, guint16 requestid, GString *data) {
+ if (data->len > G_MAXUINT16) {
+ stream_send_data(out, type, requestid, GSTR_LEN(data));
+ g_string_free(data, TRUE);
+ } else {
+ guint8 padlen = stream_send_fcgi_record(out, type, requestid, data->len);
+ append_padding(data, padlen);
+ fastcgi_gstring_queue_append(out, data);
+ }
+}
+
+static void stream_send_end_request(fastcgi_gstring_queue *out, guint16 requestID, gint32 appStatus, enum FCGI_ProtocolStatus status) {
+ GString *record;
+ record = g_string_sized_new(16);
+ stream_build_fcgi_record(record, FCGI_END_REQUEST, requestID, 8);
+ appStatus = htonl(appStatus);
+ g_string_append_len(record, (const gchar*) &appStatus, sizeof(appStatus));
+ g_string_append_c(record, status);
+ g_string_append_len(record, __padding, 3);
+ fastcgi_gstring_queue_append(out, record);
+}
+
+
+static void write_queue(fastcgi_connection *fcon) {
+ const gssize max_rem_write = 256*1024;
+ gssize rem_write = 256*1024;
+#ifdef TCP_CORK
+ int corked = 0;
+#endif
+
+ if (fcon->closing) return;
+
+#ifdef TCP_CORK
+ /* Linux: put a cork into the socket as we want to combine the write() calls
+ * but only if we really have multiple chunks
+ */
+ if (fcon->write_queue->queue.length > 1) {
+ corked = 1;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+ while (rem_write > 0 && fcon->write_queue.length > 0) {
+ GString *s = g_queue_peek_head(&fcon->write_queue.queue);
+ gssize towrite = s->len - fcon->write_queue.offset, res;
+ if (towrite > max_rem_write) towrite = max_rem_write;
+ res = write(fcon->fd, s->str + fcon->write_queue.offset, towrite);
+ if (-1 == res) {
+#ifdef TCP_CORK
+ if (corked) {
+ corked = 0;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ goto out; /* try again later */
+ case ECONNRESET:
+ case EPIPE:
+ fastcgi_connection_close(fcon);
+ return;
+ default:
+ ERROR("write to fd=%d failed, %s\n", fcon->fd, g_strerror(errno) );
+ fastcgi_connection_close(fcon);
+ return;
+ }
+ } else {
+ fcon->write_queue.offset += res;
+ rem_write -= res;
+ if (fcon->write_queue.offset == s->len) {
+ g_queue_pop_head(&fcon->write_queue.queue);
+ fcon->write_queue.offset = 0;
+ fcon->write_queue.length -= s->len;
+ g_string_free(s, TRUE);
+ }
+ }
+ }
+
+#ifdef TCP_CORK
+ if (corked) {
+ corked = 0;
+ setsockopt(fd, IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked));
+ }
+#endif
+
+out:
+ if (!fcon->closing && rem_write != max_rem_write) {
+ if (fcon->fsrv->callbacks->cb_wrote_data) {
+ fcon->fsrv->callbacks->cb_wrote_data(fcon);
+ }
+ }
+
+ if (!fcon->closing) {
+ if (fcon->write_queue.length > 0) {
+ ev_io_add_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_WRITE);
+ } else {
+ ev_io_rem_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_WRITE);
+ if (0 == fcon->requestID) {
+ if (!(fcon->flags & FCGI_KEEP_CONN)) {
+ fastcgi_connection_close(fcon);
+ }
+ }
+ }
+ }
+}
+
+static GString* read_chunk(fastcgi_connection *fcon, guint maxlen) {
+ gssize res;
+ GString *str;
+ int tmp_errno;
+
+ str = g_string_sized_new(maxlen);
+ g_string_set_size(str, maxlen);
+ res = read(fcon->fd, str->str, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_string_free(str, TRUE);
+ errno = tmp_errno;
+ return NULL;
+ } else if (res == 0) {
+ g_string_free(str, TRUE);
+ errno = ECONNRESET;
+ return NULL;
+ } else {
+ g_string_set_size(str, res);
+ return str;
+ }
+}
+
+static gboolean read_append_chunk(fastcgi_connection *fcon, GString *str) {
+ gssize res;
+ int tmp_errno;
+ guint curlen = str->len;
+ const guint maxlen = fcon->content_remaining;
+ if (0 == maxlen) return TRUE;
+
+ g_string_set_size(str, curlen + maxlen);
+ res = read(fcon->fd, str->str + curlen, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_string_set_size(str, curlen);
+ errno = tmp_errno;
+ return FALSE;
+ } else if (res == 0) {
+ g_string_set_size(str, curlen);
+ errno = ECONNRESET;
+ return FALSE;
+ } else {
+ g_string_set_size(str, curlen + res);
+ fcon->content_remaining -= res;
+ return TRUE;
+ }
+}
+
+static gboolean read_key_value(fastcgi_connection *fcon, GString *buf, guint *pos, gchar **key, guint *keylen, gchar **value, guint *valuelen) {
+ const unsigned char *data = (const unsigned char*) buf->str;
+ guint32 klen, vlen;
+ guint p = *pos, len = buf->len;
+
+ if (len - p < 2) return FALSE;
+
+ klen = data[p++];
+ if (klen & 0x80) {
+ if (len - p < 100) return FALSE;
+ klen = ((klen & 0x7f) << 24) | (data[p] << 16) | (data[p+1] << 8) | data[p+2];
+ p += 3;
+ }
+ vlen = data[p++];
+ if (vlen & 0x80) {
+ if (len - p < 100) return FALSE;
+ vlen = ((vlen & 0x7f) << 24) | (data[p] << 16) | (data[p+1] << 8) | data[p+2];
+ p += 3;
+ }
+ if (klen > FASTCGI_MAX_KEYLEN || vlen > FASTCGI_MAX_VALUELEN) {
+ fastcgi_connection_close(fcon);
+ return FALSE;
+ }
+ if (len - p < klen + vlen) return FALSE;
+ *key = &buf->str[p];
+ *keylen = klen;
+ p += klen;
+ *value = &buf->str[p];
+ *valuelen = vlen;
+ p += vlen;
+ *pos = p;
+ return TRUE;
+}
+
+static void parse_params(const fastcgi_callbacks *fcbs, fastcgi_connection *fcon) {
+ if (!fcon->current_header.contentLength) {
+ fcbs->cb_new_request(fcon);
+ g_string_truncate(fcon->parambuf, 0);
+ } else {
+ guint pos = 0, keylen = 0, valuelen = 0;
+ gchar *key = NULL, *value = NULL;
+ while (read_key_value(fcon, fcon->parambuf, &pos, &key, &keylen, &value, &valuelen)) {
+ gchar *envvar = g_malloc(keylen + valuelen + 2);
+ memcpy(envvar, key, keylen);
+ envvar[keylen] = '=';
+ memcpy(envvar + keylen + 1, value, valuelen);
+ envvar[keylen+valuelen+1] = '\0';
+ g_ptr_array_add(fcon->environ, envvar);
+ }
+ if (!fcon->closing)
+ g_string_erase(fcon->parambuf, 0, pos);
+ }
+}
+
+static void parse_get_values(fastcgi_connection *fcon) {
+ /* just send the request back and don't insert results */
+ GString *tmp = g_string_sized_new(0);
+ stream_send_string(&fcon->write_queue, FCGI_GET_VALUES_RESULT, 0, fcon->buffer);
+ *fcon->buffer = *tmp;
+ /* TODO: provide get-values result */
+}
+
+static void read_queue(fastcgi_connection *fcon) {
+ gssize res;
+ GString *buf;
+ const fastcgi_callbacks *fcbs = fcon->fsrv->callbacks;
+
+ for (;;) {
+ if (fcon->closing || fcon->read_suspended) return;
+
+ if (fcon->headerbuf_used < 8) {
+ const unsigned char *data = fcon->headerbuf;
+ res = read(fcon->fd, fcon->headerbuf + fcon->headerbuf_used, 8 - fcon->headerbuf_used);
+ if (0 == res) { errno = ECONNRESET; goto handle_error; }
+ if (-1 == res) goto handle_error;
+ fcon->headerbuf_used += res;
+ if (fcon->headerbuf_used < 8) return; /* need more data */
+
+ fcon->current_header.version = data[0];
+ fcon->current_header.type = data[1];
+ fcon->current_header.requestID = (data[2] << 8) | (data[3]);
+ fcon->current_header.contentLength = (data[4] << 8) | (data[5]);
+ fcon->current_header.paddingLength = data[6];
+ fcon->content_remaining = fcon->current_header.contentLength;
+ fcon->padding_remaining = fcon->current_header.paddingLength;
+ fcon->first = TRUE;
+ g_string_truncate(fcon->buffer, 0);
+
+ if (fcon->current_header.version != FCGI_VERSION_1) {
+ fastcgi_connection_close(fcon);
+ return;
+ }
+ }
+
+ if (fcon->current_header.type != FCGI_BEGIN_REQUEST &&
+ (0 != fcon->current_header.requestID) && fcon->current_header.requestID != fcon->requestID) {
+ /* ignore packet data */
+ if (0 == fcon->content_remaining + fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ } else {
+ if (NULL == (buf = read_chunk(fcon, fcon->content_remaining + fcon->padding_remaining))) goto handle_error;
+ if (buf->len >= fcon->content_remaining) {
+ fcon->padding_remaining -= buf->len - fcon->content_remaining;
+ if (0 == fcon->padding_remaining) fcon->headerbuf_used = 0;
+ } else {
+ fcon->content_remaining -= buf->len;
+ }
+ g_string_free(buf, TRUE);
+ }
+ }
+
+ if (fcon->first || fcon->content_remaining) {
+ fcon->first = FALSE;
+ switch (fcon->current_header.type) {
+ case FCGI_BEGIN_REQUEST:
+ if (8 != fcon->current_header.contentLength || 0 == fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->buffer)) goto handle_error;
+ if (0 == fcon->content_remaining) {
+ if (fcon->requestID) {
+ stream_send_end_request(&fcon->write_queue, fcon->current_header.requestID, 0, FCGI_CANT_MPX_CONN);
+ } else {
+ unsigned char *data = (unsigned char*) fcon->buffer->str;
+ fcon->requestID = fcon->current_header.requestID;
+ fcon->role = (data[0] << 8) | (data[1]);
+ fcon->flags = data[2];
+ g_string_truncate(fcon->parambuf, 0);
+ }
+ }
+ break;
+ case FCGI_ABORT_REQUEST:
+ if (0 != fcon->current_header.contentLength || 0 == fcon->current_header.requestID) goto error;
+ fcbs->cb_request_aborted(fcon);
+ break;
+ case FCGI_END_REQUEST:
+ goto error; /* invalid type */
+ case FCGI_PARAMS:
+ if (0 == fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->parambuf)) goto handle_error;
+ parse_params(fcbs, fcon);
+ break;
+ case FCGI_STDIN:
+ if (0 == fcon->current_header.requestID) goto error;
+ buf = NULL;
+ if (0 != fcon->content_remaining &&
+ NULL == (buf = read_chunk(fcon, fcon->content_remaining + fcon->padding_remaining))) goto handle_error;
+ if (buf) fcon->content_remaining -= buf->len;
+ if (fcbs->cb_received_stdin) {
+ fcbs->cb_received_stdin(fcon, buf);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+ break;
+ case FCGI_STDOUT:
+ goto error; /* invalid type */
+ case FCGI_STDERR:
+ goto error; /* invalid type */
+ case FCGI_DATA:
+ if (0 == fcon->current_header.requestID) goto error;
+ buf = NULL;
+ if (0 != fcon->content_remaining &&
+ NULL == (buf = read_chunk(fcon, fcon->content_remaining + fcon->padding_remaining))) goto handle_error;
+ if (buf) fcon->content_remaining -= buf->len;
+ if (fcbs->cb_received_data) {
+ fcbs->cb_received_data(fcon, buf);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+ break;
+ case FCGI_GET_VALUES:
+ if (0 != fcon->current_header.requestID) goto error;
+ if (!read_append_chunk(fcon, fcon->buffer)) goto handle_error;
+ if (0 == fcon->content_remaining)
+ parse_get_values(fcon);
+ break;
+ case FCGI_GET_VALUES_RESULT:
+ goto error; /* invalid type */
+ break;
+ case FCGI_UNKNOWN_TYPE:
+ /* we didn't send anything fancy, so this is not expected */
+ goto error; /* invalid type */
+ default:
+ break;
+ }
+ }
+
+ if (0 == fcon->content_remaining) {
+ if (0 == fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ } else {
+ if (NULL == (buf = read_chunk(fcon, fcon->content_remaining + fcon->padding_remaining))) goto handle_error;
+ fcon->padding_remaining -= buf->len;
+ if (0 == fcon->padding_remaining) {
+ fcon->headerbuf_used = 0;
+ }
+ g_string_free(buf, TRUE);
+ }
+ }
+
+ }
+
+ return;
+
+handle_error:
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", fcon->fd, g_strerror(errno) );
+ break;
+ }
+
+error:
+ if (0 != fcon->requestID)
+ fcbs->cb_request_aborted(fcon);
+ fastcgi_connection_close(fcon);
+}
+
+static void fastcgi_connection_fd_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fastcgi_connection *fcon = (fastcgi_connection*) w->data;
+ UNUSED(loop);
+
+ if (revents & EV_READ) {
+ read_queue(fcon);
+ }
+
+ if (revents & EV_WRITE) {
+ write_queue(fcon);
+ }
+}
+
+static fastcgi_connection *fastcgi_connecion_create(fastcgi_server *fsrv, gint fd, guint id) {
+ fastcgi_connection *fcon = g_slice_new0(fastcgi_connection);
+
+ fcon->fsrv = fsrv;
+ fcon->fcon_id = id;
+
+ fcon->buffer = g_string_sized_new(0);
+ fcon->parambuf = g_string_sized_new(0);
+ fcon->environ = g_ptr_array_new();
+
+ fcon->fd = fd;
+ fd_init(fcon->fd);
+ ev_io_init(&fcon->fd_watcher, fastcgi_connection_fd_cb, fcon->fd, EV_READ);
+ fcon->fd_watcher.data = fcon;
+ ev_io_start(fcon->fsrv->loop, &fcon->fd_watcher);
+
+ return fcon;
+}
+
+static void fastcgi_connection_free(fastcgi_connection *fcon) {
+ fcon->fsrv->callbacks->cb_reset_connection(fcon);
+
+ if (fcon->fd != -1) {
+ ev_io_stop(fcon->fsrv->loop, &fcon->fd_watcher);
+ close(fcon->fd);
+ fcon->fd = -1;
+ }
+
+ fastcgi_gstring_queue_clear(&fcon->write_queue);
+ fastcgi_connection_environ_clear(fcon);
+ g_ptr_array_free(fcon->environ, TRUE);
+ g_string_free(fcon->buffer, TRUE);
+ g_string_free(fcon->parambuf, TRUE);
+
+ g_slice_free(fastcgi_connection, fcon);
+}
+
+void fastcgi_connection_close(fastcgi_connection *fcon) {
+ fcon->closing = TRUE;
+ if (fcon->fd != -1) {
+ ev_io_stop(fcon->fsrv->loop, &fcon->fd_watcher);
+ close(fcon->fd);
+ fcon->fd = -1;
+ }
+
+ fastcgi_gstring_queue_clear(&fcon->write_queue);
+
+ g_string_truncate(fcon->buffer, 0);
+ g_string_truncate(fcon->parambuf, 0);
+ fastcgi_connection_environ_clear(fcon);
+
+ ev_prepare_start(fcon->fsrv->loop, &fcon->fsrv->closing_watcher);
+}
+
+static void fastcgi_server_fd_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fastcgi_server *fsrv = (fastcgi_server*) w->data;
+ fastcgi_connection *fcon;
+ void (*cb_new_connection)(fastcgi_connection *fcon) = fsrv->callbacks->cb_new_connection;
+
+ g_assert(revents & EV_READ);
+
+ for (;;) {
+ gint fd = accept(fsrv->fd, NULL, NULL);
+ if (-1 == fd) {
+ switch (errno) {
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ /* we were stopped _before_ we had a connection */
+ case ECONNABORTED: /* this is a FreeBSD thingy */
+ /* we were stopped _after_ we had a connection */
+ return;
+ case EMFILE:
+ if (0 == fsrv->max_connections) {
+ fsrv->max_connections = fsrv->connections->len / 2;
+ } else {
+ fsrv->max_connections = fsrv->max_connections / 2;
+ }
+ ERROR("dropped connection limit to %u as we got EMFILE\n", fsrv->max_connections);
+ ev_io_rem_events(loop, w, EV_READ);
+ return;
+ default:
+ ERROR("accept failed on fd=%d with error: %s\nshutting down\n", fsrv->fd, g_strerror(errno));
+ fastcgi_server_stop(fsrv);
+ return;
+ }
+ }
+
+ fcon = fastcgi_connecion_create(fsrv, fd, fsrv->connections->len);
+ g_ptr_array_add(fsrv->connections, fcon);
+ if (cb_new_connection) {
+ cb_new_connection(fcon);
+ }
+
+ if (fsrv->connections->len >= fsrv->max_connections) {
+ ev_io_rem_events(loop, w, EV_READ);
+ return;
+ }
+
+ if (fsrv->do_shutdown) return;
+ }
+}
+
+static void fastcgi_cleanup_connections(fastcgi_server *fsrv) {
+ guint i;
+
+ for (i = 0; i < fsrv->connections->len; ) {
+ fastcgi_connection *fcon = g_ptr_array_index(fsrv->connections, i);
+ if (fcon->closing) {
+ fastcgi_connection *t_fcon;
+ guint l = fsrv->connections->len-1;
+ t_fcon = g_ptr_array_index(fsrv->connections, i) = g_ptr_array_index(fsrv->connections, l);
+ g_ptr_array_set_size(fsrv->connections, l);
+ t_fcon->fcon_id = i;
+ fastcgi_connection_free(fcon);
+ } else {
+ i++;
+ }
+ }
+}
+
+static void fastcgi_closing_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+ UNUSED(revents);
+ ev_prepare_stop(loop, w);
+ fastcgi_cleanup_connections((fastcgi_server*) w->data);
+}
+
+fastcgi_server *fastcgi_server_create(struct ev_loop *loop, gint socketfd, const fastcgi_callbacks *callbacks, guint max_connections) {
+ fastcgi_server *fsrv = g_slice_new0(fastcgi_server);
+
+ fsrv->callbacks = callbacks;
+
+ fsrv->max_connections = max_connections;
+
+ fsrv->connections = g_ptr_array_sized_new(fsrv->max_connections);
+
+ fsrv->loop = loop;
+ fsrv->fd = socketfd;
+ fd_init(fsrv->fd);
+ ev_io_init(&fsrv->fd_watcher, fastcgi_server_fd_cb, fsrv->fd, EV_READ);
+ fsrv->fd_watcher.data = fsrv;
+ ev_io_start(fsrv->loop, &fsrv->fd_watcher);
+
+ ev_prepare_init(&fsrv->closing_watcher, fastcgi_closing_cb);
+ fsrv->closing_watcher.data = fsrv;
+
+ return fsrv;
+}
+
+void fastcgi_server_stop(fastcgi_server *fsrv) {
+ if (fsrv->do_shutdown) return;
+ fsrv->do_shutdown = TRUE;
+
+ ev_io_stop(fsrv->loop, &fsrv->fd_watcher);
+ close(fsrv->fd);
+ fsrv->fd = -1;
+}
+
+void fastcgi_server_free(fastcgi_server *fsrv) {
+ guint i;
+ void (*cb_request_aborted)(fastcgi_connection *fcon) = fsrv->callbacks->cb_request_aborted;
+ if (!fsrv->do_shutdown) fastcgi_server_stop(fsrv);
+ ev_prepare_stop(fsrv->loop, &fsrv->closing_watcher);
+
+ for (i = 0; i < fsrv->connections->len; i++) {
+ fastcgi_connection *fcon = g_ptr_array_index(fsrv->connections, i);
+ cb_request_aborted(fcon);
+ fcon->closing = TRUE;
+ }
+ fastcgi_cleanup_connections(fsrv);
+ g_ptr_array_free(fsrv->connections, TRUE);
+
+ g_slice_free(fastcgi_server, fsrv);
+}
+
+void fastcgi_end_request(fastcgi_connection *fcon, gint32 appStatus, enum FCGI_ProtocolStatus status) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+
+ if (0 == fcon->requestID) return;
+ stream_send_end_request(&fcon->write_queue, fcon->requestID, appStatus, status);
+ fcon->requestID = 0;
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_suspend_read(fastcgi_connection *fcon) {
+ fcon->read_suspended = TRUE;
+ ev_io_rem_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_READ);
+}
+
+void fastcgi_resume_read(fastcgi_connection *fcon) {
+ fcon->read_suspended = FALSE;
+ ev_io_add_events(fcon->fsrv->loop, &fcon->fd_watcher, EV_READ);
+}
+
+void fastcgi_send_out(fastcgi_connection *fcon, GString *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, 0);
+ } else {
+ stream_send_string(&fcon->write_queue, FCGI_STDOUT, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_send_err(fastcgi_connection *fcon, GString *data) {
+ gboolean had_data = (fcon->write_queue.length > 0);
+ if (!data) {
+ stream_send_fcgi_record(&fcon->write_queue, FCGI_STDERR, fcon->requestID, 0);
+ } else {
+ stream_send_string(&fcon->write_queue, FCGI_STDERR, fcon->requestID, data);
+ }
+ if (!had_data) write_queue(fcon);
+}
+
+void fastcgi_connection_environ_clear(fastcgi_connection *fcon) {
+ guint i;
+ for (i = 0; i < fcon->environ->len; i++) {
+ gchar *s = (gchar*) g_ptr_array_index(fcon->environ, i);
+ if (s) g_free(s);
+ }
+ g_ptr_array_set_size(fcon->environ, 0);
+}
+
+const gchar* fastcgi_connection_environ_lookup(fastcgi_connection *fcon, const gchar* key, gsize keylen) {
+ guint i;
+ for (i = 0; i < fcon->environ->len; i++) {
+ gchar *s = (gchar*) g_ptr_array_index(fcon->environ, i);
+ if (s && 0 == strncmp(s, key, keylen) && s[keylen] == '=') {
+ return &s[keylen+1];
+ }
+ }
+ return NULL;
+}
+
+void fastcgi_gstring_queue_append(fastcgi_gstring_queue *queue, GString *buf) {
+ if (!buf) return;
+ g_queue_push_tail(&queue->queue, buf);
+ queue->length += buf->len;
+}
+
+void fastcgi_gstring_queue_clear(fastcgi_gstring_queue *queue) {
+ GString *s;
+ queue->length = 0;
+ queue->offset = 0;
+ while (NULL != (s = g_queue_pop_head(&queue->queue))) {
+ g_string_free(s, TRUE);
+ }
+}
diff --git a/fastcgi.h b/fastcgi.h
new file mode 100644
index 0000000..7846675
--- /dev/null
+++ b/fastcgi.h
@@ -0,0 +1,160 @@
+#ifndef _FCGI_CGI_FASTCGI_H
+#define _FCGI_CGI_FASTCGI_H
+
+/* NO multiplexing support */
+/* Keep fastcgi.* independent */
+
+#include <glib.h>
+#include <ev.h>
+
+/* FastCGI constants */
+# define FCGI_VERSION_1 1
+# define FCGI_HEADER_LEN 8
+
+ enum FCGI_Type {
+ FCGI_BEGIN_REQUEST = 1,
+ FCGI_ABORT_REQUEST = 2,
+ FCGI_END_REQUEST = 3,
+ FCGI_PARAMS = 4,
+ FCGI_STDIN = 5,
+ FCGI_STDOUT = 6,
+ FCGI_STDERR = 7,
+ FCGI_DATA = 8,
+ FCGI_GET_VALUES = 9,
+ FCGI_GET_VALUES_RESULT = 10,
+ FCGI_UNKNOWN_TYPE = 11
+ };
+# define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+ enum FCGI_Flags {
+ FCGI_KEEP_CONN = 1
+ };
+
+ enum FCGI_Role {
+ FCGI_RESPONDER = 1,
+ FCGI_AUTHORIZER = 2,
+ FCGI_FILTER = 3
+ };
+
+ enum FCGI_ProtocolStatus {
+ FCGI_REQUEST_COMPLETE = 0,
+ FCGI_CANT_MPX_CONN = 1,
+ FCGI_OVERLOADED = 2,
+ FCGI_UNKNOWN_ROLE = 3
+ };
+
+#define FASTCGI_MAX_KEYLEN 1024
+#define FASTCGI_MAX_VALUELEN 64*1024
+/* end FastCGI constants */
+
+struct fastcgi_server;
+typedef struct fastcgi_server fastcgi_server;
+
+struct fastcgi_callbacks;
+typedef struct fastcgi_callbacks fastcgi_callbacks;
+
+struct fastcgi_connection;
+typedef struct fastcgi_connection fastcgi_connection;
+
+struct fastcgi_gstring_queue;
+typedef struct fastcgi_gstring_queue fastcgi_gstring_queue;
+
+struct fastcgi_server {
+/* custom user data */
+ gpointer data;
+
+/* private data */
+ gboolean do_shutdown;
+
+ const fastcgi_callbacks *callbacks;
+
+ guint max_connections;
+ GPtrArray *connections;
+ guint cur_requests;
+
+ gint fd;
+ struct ev_loop *loop;
+ ev_io fd_watcher;
+ ev_prepare closing_watcher;
+};
+
+struct fastcgi_callbacks {
+ void (*cb_new_connection)(fastcgi_connection *fcon); /* new connection accepted */
+ void (*cb_new_request)(fastcgi_connection *fcon); /* new request on connection, env/params are ready */
+ void (*cb_wrote_data)(fastcgi_connection *fcon);
+ void (*cb_received_stdin)(fastcgi_connection *fcon, GString *data); /* data == NULL => eof */
+ void (*cb_received_data)(fastcgi_connection *fcon, GString *data); /* data == NULL => eof */
+ void (*cb_request_aborted)(fastcgi_connection *fcon);
+ void (*cb_reset_connection)(fastcgi_connection *fcon); /* cleanup custom data before fcon is freed, not for keep-alive */
+};
+
+struct fastcgi_gstring_queue {
+ GQueue queue;
+ gsize offset; /* offset in first chunk */
+ gsize length;
+ gboolean closed;
+};
+
+struct fastcgi_connection {
+/* custom user data */
+ gpointer data;
+
+/* read/write */
+ GPtrArray *environ;
+
+/* read only */
+ fastcgi_server *fsrv;
+ guint fcon_id; /* index in server con array */
+ gboolean closing; /* "dead" connection */
+
+ /* current request */
+ guint16 requestID;
+ guint16 role;
+ guint8 flags;
+
+/* private data */
+ unsigned char headerbuf[8];
+ guint headerbuf_used;
+ gboolean first;
+
+ struct {
+ guint8 version;
+ guint8 type;
+ guint16 requestID;
+ guint16 contentLength;
+ guint8 paddingLength;
+ } current_header;
+
+ guint content_remaining, padding_remaining;
+
+ GString *buffer, *parambuf;
+
+ gint fd;
+ ev_io fd_watcher;
+
+ gboolean read_suspended;
+
+ /* write queue */
+ fastcgi_gstring_queue write_queue;
+};
+
+fastcgi_server *fastcgi_server_create(struct ev_loop *loop, gint socketfd, const fastcgi_callbacks *callbacks, guint max_connections);
+void fastcgi_server_stop(fastcgi_server *fsrv); /* stop accepting new connections, closes listening socket */
+void fastcgi_server_free(fastcgi_server *fsrv);
+
+void fastcgi_suspend_read(fastcgi_connection *fcon);
+void fastcgi_resume_read(fastcgi_connection *fcon);
+
+void fastcgi_end_request(fastcgi_connection *fcon, gint32 appStatus, enum FCGI_ProtocolStatus status);
+void fastcgi_send_out(fastcgi_connection *fcon, GString *data);
+void fastcgi_send_err(fastcgi_connection *fcon, GString *data);
+
+void fastcgi_connection_close(fastcgi_connection *fcon); /* shouldn't be needed */
+
+void fastcgi_connection_environ_clear(fastcgi_connection *fcon);
+const gchar* fastcgi_connection_environ_lookup(fastcgi_connection *fcon, const gchar* key, gsize keylen);
+
+void fastcgi_gstring_queue_append(fastcgi_gstring_queue *queue, GString *buf);
+void fastcgi_gstring_queue_clear(fastcgi_gstring_queue *queue);
+
+#endif
diff --git a/fcgi-cgi.c b/fcgi-cgi.c
new file mode 100644
index 0000000..8f7a5d1
--- /dev/null
+++ b/fcgi-cgi.c
@@ -0,0 +1,527 @@
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fastcgi.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stropts.h>
+
+#define MAX_BUFFER_SIZE (64*1024)
+
+#define GSTR_LEN(x) (x) ? (x)->str : "", (x) ? (x)->len : 0
+#define UNUSED(x) ((void)(x))
+
+/* #define ERROR(...) g_printerr(G_STRLOC " (" G_STRFUNC "): " __VA_ARGS__) */
+#define __STR(x) #x
+#define ERROR(...) g_printerr("fcgi-cgi.c:" G_STRINGIFY(__LINE__) ": " __VA_ARGS__)
+
+struct fcgi_cgi_server;
+typedef struct fcgi_cgi_server fcgi_cgi_server;
+
+struct fcgi_cgi_child;
+typedef struct fcgi_cgi_child fcgi_cgi_child;
+
+struct fcgi_cgi_server {
+ struct ev_loop *loop;
+
+ fastcgi_server *fsrv;
+ GPtrArray *aborted_pending_childs;
+
+ ev_signal
+ sig_w_INT,
+ sig_w_TERM,
+ sig_w_HUP;
+};
+
+struct fcgi_cgi_child {
+ fcgi_cgi_server *srv;
+ fastcgi_connection *fcon;
+ gint aborted_id;
+
+ pid_t pid;
+ gint child_status;
+ ev_child child_watcher;
+
+ gint pipe_in, pipe_out, pipe_err;
+ ev_io pipe_in_watcher, pipe_out_watcher, pipe_err_watcher;
+
+ /* write queue */
+ fastcgi_gstring_queue write_queue;
+};
+
+static fcgi_cgi_child* fcgi_cgi_child_create(fcgi_cgi_server *srv, fastcgi_connection *fcon);
+static void fcgi_cgi_child_check_done(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_write(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_read(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_error(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_free(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_error(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_start(fcgi_cgi_child *cld, const gchar *path);
+static void fcgi_cgi_wrote_data(fastcgi_connection *fcon);
+
+/* move a fd to another and close the old one */
+static void move2fd(int srcfd, int dstfd) {
+ if (srcfd != dstfd) {
+ close(dstfd);
+ dup2(srcfd, dstfd);
+ close(srcfd);
+ }
+}
+
+static void fd_init(int fd) {
+#ifdef _WIN32
+ int i = 1;
+#endif
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef O_NONBLOCK
+ fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#elif defined _WIN32
+ ioctlsocket(fd, FIONBIO, &i);
+#endif
+}
+
+static void fcgi_cgi_child_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ UNUSED(revents);
+
+ ev_child_stop(loop, w);
+
+ fcgi_cgi_child_close_write(cld);
+
+ cld->pid = -1;
+ cld->child_status = w->rstatus;
+ fcgi_cgi_child_check_done(cld);
+}
+
+static void write_queue(fcgi_cgi_child *cld) {
+ const gssize max_rem_write = 256*1024;
+ gssize rem_write = 256*1024;
+ if (-1 == cld->pipe_out) return;
+
+ while (rem_write > 0 && cld->write_queue.length > 0) {
+ GString *s = g_queue_peek_head(&cld->write_queue.queue);
+ gssize towrite = s->len - cld->write_queue.offset, res;
+ if (towrite > max_rem_write) towrite = max_rem_write;
+ res = write(cld->pipe_out, s->str + cld->write_queue.offset, towrite);
+ if (-1 == res) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ goto out; /* try again later */
+ case ECONNRESET:
+ case EPIPE:
+ fcgi_cgi_child_close_write(cld);
+ return;
+ default:
+ ERROR("write to fd=%d failed, %s\n", cld->pipe_out, g_strerror(errno));
+ fcgi_cgi_child_close_write(cld);
+ return;
+ }
+ } else {
+ cld->write_queue.offset += res;
+ rem_write -= res;
+ if (cld->write_queue.offset == s->len) {
+ g_queue_pop_head(&cld->write_queue.queue);
+ cld->write_queue.offset = 0;
+ cld->write_queue.length -= s->len;
+ g_string_free(s, TRUE);
+ }
+ }
+ }
+
+out:
+ if (-1 != cld->pipe_out) {
+ if (cld->write_queue.length > 0) {
+ ev_io_start(cld->srv->loop, &cld->pipe_out_watcher);
+ if (cld->write_queue.length > MAX_BUFFER_SIZE) {
+ fastcgi_suspend_read(cld->fcon);
+ } else {
+ fastcgi_resume_read(cld->fcon);
+ }
+ } else {
+ if (cld->write_queue.closed) {
+ fcgi_cgi_child_close_write(cld);
+ } else {
+ ev_io_stop(cld->srv->loop, &cld->pipe_out_watcher);
+ fastcgi_resume_read(cld->fcon);
+ }
+ }
+ }
+}
+
+static GString* read_chunk(gint fd, guint maxlen) {
+ gssize res;
+ GString *str;
+ int tmp_errno;
+
+ str = g_string_sized_new(maxlen);
+ g_string_set_size(str, maxlen);
+ res = read(fd, str->str, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_string_free(str, TRUE);
+ errno = tmp_errno;
+ return NULL;
+ } else if (res == 0) {
+ g_string_free(str, TRUE);
+ errno = ECONNRESET;
+ return NULL;
+ } else {
+ g_string_set_size(str, res);
+ return str;
+ }
+}
+
+static void fcgi_cgi_child_pipe_in_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ GString *buf;
+ UNUSED(loop); UNUSED(revents);
+
+ if (NULL == (buf = read_chunk(cld->pipe_in, 64*1024))) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ fcgi_cgi_child_close_read(cld);
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", cld->pipe_in, g_strerror(errno));
+ fcgi_cgi_child_close_read(cld);
+ break;
+ }
+ } else if (cld->fcon) {
+ fastcgi_send_out(cld->fcon, buf);
+ fcgi_cgi_wrote_data(cld->fcon);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+}
+
+static void fcgi_cgi_child_pipe_out_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ UNUSED(loop); UNUSED(revents);
+
+ write_queue(cld);
+}
+
+static void fcgi_cgi_child_pipe_err_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ GString *buf;
+ UNUSED(loop); UNUSED(revents);
+
+ if (NULL == (buf = read_chunk(cld->pipe_err, 64*1024))) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ fcgi_cgi_child_close_error(cld);
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", cld->pipe_err, g_strerror(errno));
+ fcgi_cgi_child_close_error(cld);
+ break;
+ }
+ } else if (cld->fcon) {
+ fastcgi_send_err(cld->fcon, buf);
+ fcgi_cgi_wrote_data(cld->fcon);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+}
+
+static fcgi_cgi_child* fcgi_cgi_child_create(fcgi_cgi_server *srv, fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = g_slice_new0(fcgi_cgi_child);
+
+ cld->srv = srv;
+ cld->fcon = fcon;
+ cld->aborted_id = -1;
+
+ cld->pid = -1;
+ ev_child_init(&cld->child_watcher, fcgi_cgi_child_child_cb, -1, 0);
+ cld->child_watcher.data = cld;
+
+ cld->pipe_in = cld->pipe_out = -1;
+ ev_io_init(&cld->pipe_in_watcher, fcgi_cgi_child_pipe_in_cb, -1, 0);
+ cld->pipe_in_watcher.data = cld;
+ ev_io_init(&cld->pipe_out_watcher, fcgi_cgi_child_pipe_out_cb, -1, 0);
+ cld->pipe_out_watcher.data = cld;
+ ev_io_init(&cld->pipe_err_watcher, fcgi_cgi_child_pipe_err_cb, -1, 0);
+ cld->pipe_err_watcher.data = cld;
+
+ return cld;
+}
+
+static void fcgi_cgi_child_check_done(fcgi_cgi_child *cld) {
+ if (!cld->fcon) {
+ if (-1 != cld->aborted_id && (cld->pid == -1 || (cld->pipe_out == -1 && cld->pipe_in == -1))) {
+ fcgi_cgi_child *t_cld;
+ GPtrArray *a = cld->srv->aborted_pending_childs;
+ guint i = a->len - 1;
+ t_cld = g_ptr_array_index(a, cld->aborted_id) = g_ptr_array_index(a, i);
+ g_ptr_array_set_size(a, i);
+ t_cld->aborted_id = cld->aborted_id;
+ cld->aborted_id = -1;
+ fcgi_cgi_child_free(cld);
+ }
+ } else {
+ if (cld->pid == -1 && cld->pipe_out == -1 && cld->pipe_in == -1 && cld->pipe_err == -1) {
+ fastcgi_end_request(cld->fcon, cld->child_status, FCGI_REQUEST_COMPLETE);
+ }
+ }
+}
+
+static void fcgi_cgi_child_close_write(fcgi_cgi_child *cld) {
+ if (cld->pipe_out != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_out_watcher);
+ close(cld->pipe_out);
+ cld->pipe_out = -1;
+ fastcgi_gstring_queue_clear(&cld->write_queue);
+ cld->write_queue.closed = TRUE;
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_close_read(fcgi_cgi_child *cld) {
+ if (cld->pipe_in != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_in_watcher);
+ close(cld->pipe_in);
+ cld->pipe_in = -1;
+ if (cld->fcon) fastcgi_send_out(cld->fcon, NULL);
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_close_error(fcgi_cgi_child *cld) {
+ if (cld->pipe_err != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_err_watcher);
+ close(cld->pipe_err);
+ cld->pipe_err = -1;
+ if (cld->fcon) fastcgi_send_err(cld->fcon, NULL);
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_free(fcgi_cgi_child *cld) {
+ if (cld->fcon) cld->fcon->data = NULL;
+ cld->fcon = NULL;
+ fcgi_cgi_child_close_write(cld);
+ fcgi_cgi_child_close_read(cld);
+ fcgi_cgi_child_close_error(cld);
+ ev_child_stop(cld->srv->loop, &cld->child_watcher);
+ g_slice_free(fcgi_cgi_child, cld);
+}
+
+static void fcgi_cgi_child_error(fcgi_cgi_child *cld) {
+ if (cld->fcon) {
+ fastcgi_connection_close(cld->fcon);
+ }
+}
+
+static void fcgi_cgi_child_start(fcgi_cgi_child *cld, const gchar *path) {
+ int pipes_to[2] = {-1, -1}, pipes_from[2] = {-1, -1}, pipes_err[2] = {-1, -1};
+
+ if (-1 == pipe(pipes_to)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ if (-1 == pipe(pipes_from)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ if (-1 == pipe(pipes_err)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ pid_t pid = fork();
+ switch (pid) {
+ case 0: {
+ GPtrArray *enva = cld->fcon->environ;
+ char **newenv;
+ char *const args[] = { (char *) path, NULL };
+
+ close(pipes_to[1]); close(pipes_from[0]); close(pipes_err[0]);
+ move2fd(pipes_to[0], 0);
+ move2fd(pipes_from[1], 1);
+ move2fd(pipes_err[1], 2);
+
+ g_ptr_array_add(enva, NULL);
+ newenv = (char**) g_ptr_array_free(enva, FALSE);
+ execve(path, args, newenv);
+
+ ERROR("couldn't execve '%s': %s\n", path, g_strerror(errno));
+ exit(-1);
+
+ }
+ break;
+ case -1:
+ ERROR("couldn't fork: %s\n", g_strerror(errno));
+ goto error;
+ default:
+ cld->pid = pid;
+ ev_child_set(&cld->child_watcher, cld->pid, 0);
+ ev_child_start(cld->srv->loop, &cld->child_watcher);
+ close(pipes_to[0]); close(pipes_from[1]); close(pipes_err[1]);
+ fd_init(pipes_to[1]); fd_init(pipes_from[0]); fd_init(pipes_err[0]);
+ cld->pipe_out = pipes_to[1];
+ cld->pipe_in = pipes_from[0];
+ cld->pipe_err = pipes_err[0];
+ ev_io_set(&cld->pipe_out_watcher, cld->pipe_out, EV_WRITE);
+ ev_io_set(&cld->pipe_in_watcher, cld->pipe_in, EV_READ);
+ ev_io_set(&cld->pipe_err_watcher, cld->pipe_err, EV_READ);
+ if (cld->write_queue.length > 0) ev_io_start(cld->srv->loop, &cld->pipe_out_watcher);
+ ev_io_start(cld->srv->loop, &cld->pipe_in_watcher);
+ ev_io_start(cld->srv->loop, &cld->pipe_err_watcher);
+ break;
+ }
+
+ return;
+error:
+ close(pipes_to[0]); close(pipes_to[1]);
+ close(pipes_from[0]); close(pipes_from[1]);
+ close(pipes_err[0]); close(pipes_err[1]);
+ fcgi_cgi_child_error(cld);
+}
+
+static void fcgi_cgi_new_request(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (cld) return;
+ cld = fcgi_cgi_child_create(fcon->fsrv->data, fcon);
+ fcon->data = cld;
+ fcgi_cgi_child_start(cld, "/usr/bin/php5-cgi");
+}
+
+static void fcgi_cgi_wrote_data(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ if (cld->fcon->write_queue.length < MAX_BUFFER_SIZE) {
+ if (-1 != cld->pipe_in) ev_io_start(cld->srv->loop, &cld->pipe_in_watcher);
+ if (-1 != cld->pipe_err) ev_io_start(cld->srv->loop, &cld->pipe_err_watcher);
+ } else {
+ if (-1 != cld->pipe_in) ev_io_stop(cld->srv->loop, &cld->pipe_in_watcher);
+ if (-1 != cld->pipe_err) ev_io_stop(cld->srv->loop, &cld->pipe_err_watcher);
+ }
+}
+
+static void fcgi_cgi_received_stdin(fastcgi_connection *fcon, GString *data) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ /* if proc is running but pipe closed -> drop data */
+ if (!cld || cld->write_queue.closed) {
+ if (data) g_string_free(data, TRUE);
+ return;
+ }
+ fastcgi_gstring_queue_append(&cld->write_queue, data);
+ write_queue(cld); /* if we don't call this we have to check the write-queue length */
+}
+
+static void fcgi_cgi_request_aborted(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ fcgi_cgi_child_close_write(cld);
+}
+
+static void fcgi_cgi_reset_connection(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ fcon->data = NULL;
+ cld->fcon = NULL;
+ if (cld->pid == -1 || (cld->pipe_out == -1 && cld->pipe_in == -1)) {
+ fcgi_cgi_child_free(cld);
+ } else {
+ fcgi_cgi_child_close_write(cld);
+ cld->aborted_id = cld->srv->aborted_pending_childs->len;
+ g_ptr_array_add(cld->srv->aborted_pending_childs, cld);
+ }
+}
+
+static const fastcgi_callbacks cgi_callbacks = {
+ /* cb_new_connection: */ NULL,
+ /* cb_new_request: */ fcgi_cgi_new_request,
+ /* cb_wrote_data: */ fcgi_cgi_wrote_data,
+ /* cb_received_stdin: */ fcgi_cgi_received_stdin,
+ /* cb_received_data: */ NULL,
+ /* cb_request_aborted: */ fcgi_cgi_request_aborted,
+ /* cb_reset_connection: */ fcgi_cgi_reset_connection
+};
+
+static fcgi_cgi_server* fcgi_cgi_server_create(struct ev_loop *loop, int fd) {
+ fcgi_cgi_server* srv = g_slice_new0(fcgi_cgi_server);
+ srv->loop = loop;
+ srv->aborted_pending_childs = g_ptr_array_new();
+ srv->fsrv = fastcgi_server_create(loop, fd, &cgi_callbacks, 10);
+ srv->fsrv->data = srv;
+ return srv;
+}
+
+static void fcgi_cgi_server_free(fcgi_cgi_server* srv) {
+ guint i;
+ for (i = 0; i < srv->aborted_pending_childs->len; i++) {
+ fcgi_cgi_child_free(g_ptr_array_index(srv->aborted_pending_childs, i));
+ }
+ fastcgi_server_free(srv->fsrv);
+ g_slice_free(fcgi_cgi_server, srv);
+}
+
+#define CATCH_SIGNAL(loop, cb, n) do {\
+ ev_init(&srv->sig_w_##n, cb); \
+ ev_signal_set(&srv->sig_w_##n, SIG##n); \
+ ev_signal_start(loop, &srv->sig_w_##n); \
+ srv->sig_w_##n.data = srv; \
+ ev_unref(loop); /* Signal watchers shouldn't keep loop alive */ \
+} while (0)
+
+#define UNCATCH_SIGNAL(loop, n) do {\
+ ev_ref(loop); \
+ ev_signal_stop(loop, &srv->sig_w_##n); \
+} while (0)
+
+static void sigint_cb(struct ev_loop *loop, struct ev_signal *w, int revents) {
+ fcgi_cgi_server *srv = (fcgi_cgi_server*) w->data;
+ UNUSED(revents);
+
+ if (!srv->fsrv->do_shutdown) {
+ ERROR("Got signal, shutdown\n");
+ fastcgi_server_stop(srv->fsrv);
+ } else {
+ ERROR("Got second signal, force shutdown\n");
+ ev_unloop(loop, EVUNLOOP_ALL);
+ }
+}
+
+int main(int argc, char **argv) {
+ struct ev_loop *loop;
+ fcgi_cgi_server* srv;
+
+ loop = ev_default_loop(0);
+ srv = fcgi_cgi_server_create(loop, 0);
+
+ signal(SIGPIPE, SIG_IGN);
+ CATCH_SIGNAL(loop, sigint_cb, INT);
+ CATCH_SIGNAL(loop, sigint_cb, TERM);
+ CATCH_SIGNAL(loop, sigint_cb, HUP);
+
+ ev_loop(loop, 0);
+ fcgi_cgi_server_free(srv);
+ return 0;
+}