@ -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) |
@ -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. |
@ -0,0 +1,2 @@ | |||
fcgi-cgi is a FastCGI application to run cgi applications. |
@ -0,0 +1,6 @@ | |||
/* | |||
CMake autogenerated config.h file. Do not edit! | |||
*/ | |||
#define PACKAGE_NAME "${PACKAGE_NAME}" | |||
#define PACKAGE_VERSION "${PACKAGE_VERSION}" |
@ -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); | |||
} | |||
} |
@ -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 |
@ -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; | |||
} |