commit 418a6844a7f666d3504841380ab607be8ab5d0f4 Author: Stefan Bühler Date: Sat Mar 28 13:24:51 2009 +0100 Initial commit 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 ") +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 +#include +#include +#include +#include +#include + +/* 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 +#include + +/* 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 +#include +#include +#include +#include + +#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; +}