Initial commit

This commit is contained in:
Stefan Bühler 2009-03-28 13:24:51 +01:00
commit 418a6844a7
7 changed files with 1530 additions and 0 deletions

73
CMakeLists.txt Normal file
View File

@ -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)

22
COPYING Normal file
View File

@ -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.

2
README Normal file
View File

@ -0,0 +1,2 @@
fcgi-cgi is a FastCGI application to run cgi applications.

6
config.h.cmake Normal file
View File

@ -0,0 +1,6 @@
/*
CMake autogenerated config.h file. Do not edit!
*/
#define PACKAGE_NAME "${PACKAGE_NAME}"
#define PACKAGE_VERSION "${PACKAGE_VERSION}"

740
fastcgi.c Normal file
View File

@ -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);
}
}

160
fastcgi.h Normal file
View File

@ -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

527
fcgi-cgi.c Normal file
View File

@ -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;
}