aboutsummaryrefslogtreecommitdiff
path: root/fcgi-cgi.c
diff options
context:
space:
mode:
authorStefan Bühler <stbuehler@web.de>2009-03-28 13:24:51 +0100
committerStefan Bühler <stbuehler@web.de>2009-03-28 13:24:51 +0100
commit418a6844a7f666d3504841380ab607be8ab5d0f4 (patch)
treed14a6694fa8c3e8598a2c37498bd0117b4255d92 /fcgi-cgi.c
downloadfcgi-cgi-418a6844a7f666d3504841380ab607be8ab5d0f4.tar.gz
fcgi-cgi-418a6844a7f666d3504841380ab607be8ab5d0f4.zip
Initial commit
Diffstat (limited to 'fcgi-cgi.c')
-rw-r--r--fcgi-cgi.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/fcgi-cgi.c b/fcgi-cgi.c
new file mode 100644
index 0000000..8f7a5d1
--- /dev/null
+++ b/fcgi-cgi.c
@@ -0,0 +1,527 @@
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fastcgi.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stropts.h>
+
+#define MAX_BUFFER_SIZE (64*1024)
+
+#define GSTR_LEN(x) (x) ? (x)->str : "", (x) ? (x)->len : 0
+#define UNUSED(x) ((void)(x))
+
+/* #define ERROR(...) g_printerr(G_STRLOC " (" G_STRFUNC "): " __VA_ARGS__) */
+#define __STR(x) #x
+#define ERROR(...) g_printerr("fcgi-cgi.c:" G_STRINGIFY(__LINE__) ": " __VA_ARGS__)
+
+struct fcgi_cgi_server;
+typedef struct fcgi_cgi_server fcgi_cgi_server;
+
+struct fcgi_cgi_child;
+typedef struct fcgi_cgi_child fcgi_cgi_child;
+
+struct fcgi_cgi_server {
+ struct ev_loop *loop;
+
+ fastcgi_server *fsrv;
+ GPtrArray *aborted_pending_childs;
+
+ ev_signal
+ sig_w_INT,
+ sig_w_TERM,
+ sig_w_HUP;
+};
+
+struct fcgi_cgi_child {
+ fcgi_cgi_server *srv;
+ fastcgi_connection *fcon;
+ gint aborted_id;
+
+ pid_t pid;
+ gint child_status;
+ ev_child child_watcher;
+
+ gint pipe_in, pipe_out, pipe_err;
+ ev_io pipe_in_watcher, pipe_out_watcher, pipe_err_watcher;
+
+ /* write queue */
+ fastcgi_gstring_queue write_queue;
+};
+
+static fcgi_cgi_child* fcgi_cgi_child_create(fcgi_cgi_server *srv, fastcgi_connection *fcon);
+static void fcgi_cgi_child_check_done(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_write(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_read(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_close_error(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_free(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_error(fcgi_cgi_child *cld);
+static void fcgi_cgi_child_start(fcgi_cgi_child *cld, const gchar *path);
+static void fcgi_cgi_wrote_data(fastcgi_connection *fcon);
+
+/* move a fd to another and close the old one */
+static void move2fd(int srcfd, int dstfd) {
+ if (srcfd != dstfd) {
+ close(dstfd);
+ dup2(srcfd, dstfd);
+ close(srcfd);
+ }
+}
+
+static void fd_init(int fd) {
+#ifdef _WIN32
+ int i = 1;
+#endif
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef O_NONBLOCK
+ fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#elif defined _WIN32
+ ioctlsocket(fd, FIONBIO, &i);
+#endif
+}
+
+static void fcgi_cgi_child_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ UNUSED(revents);
+
+ ev_child_stop(loop, w);
+
+ fcgi_cgi_child_close_write(cld);
+
+ cld->pid = -1;
+ cld->child_status = w->rstatus;
+ fcgi_cgi_child_check_done(cld);
+}
+
+static void write_queue(fcgi_cgi_child *cld) {
+ const gssize max_rem_write = 256*1024;
+ gssize rem_write = 256*1024;
+ if (-1 == cld->pipe_out) return;
+
+ while (rem_write > 0 && cld->write_queue.length > 0) {
+ GString *s = g_queue_peek_head(&cld->write_queue.queue);
+ gssize towrite = s->len - cld->write_queue.offset, res;
+ if (towrite > max_rem_write) towrite = max_rem_write;
+ res = write(cld->pipe_out, s->str + cld->write_queue.offset, towrite);
+ if (-1 == res) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ goto out; /* try again later */
+ case ECONNRESET:
+ case EPIPE:
+ fcgi_cgi_child_close_write(cld);
+ return;
+ default:
+ ERROR("write to fd=%d failed, %s\n", cld->pipe_out, g_strerror(errno));
+ fcgi_cgi_child_close_write(cld);
+ return;
+ }
+ } else {
+ cld->write_queue.offset += res;
+ rem_write -= res;
+ if (cld->write_queue.offset == s->len) {
+ g_queue_pop_head(&cld->write_queue.queue);
+ cld->write_queue.offset = 0;
+ cld->write_queue.length -= s->len;
+ g_string_free(s, TRUE);
+ }
+ }
+ }
+
+out:
+ if (-1 != cld->pipe_out) {
+ if (cld->write_queue.length > 0) {
+ ev_io_start(cld->srv->loop, &cld->pipe_out_watcher);
+ if (cld->write_queue.length > MAX_BUFFER_SIZE) {
+ fastcgi_suspend_read(cld->fcon);
+ } else {
+ fastcgi_resume_read(cld->fcon);
+ }
+ } else {
+ if (cld->write_queue.closed) {
+ fcgi_cgi_child_close_write(cld);
+ } else {
+ ev_io_stop(cld->srv->loop, &cld->pipe_out_watcher);
+ fastcgi_resume_read(cld->fcon);
+ }
+ }
+ }
+}
+
+static GString* read_chunk(gint fd, guint maxlen) {
+ gssize res;
+ GString *str;
+ int tmp_errno;
+
+ str = g_string_sized_new(maxlen);
+ g_string_set_size(str, maxlen);
+ res = read(fd, str->str, maxlen);
+ if (res == -1) {
+ tmp_errno = errno;
+ g_string_free(str, TRUE);
+ errno = tmp_errno;
+ return NULL;
+ } else if (res == 0) {
+ g_string_free(str, TRUE);
+ errno = ECONNRESET;
+ return NULL;
+ } else {
+ g_string_set_size(str, res);
+ return str;
+ }
+}
+
+static void fcgi_cgi_child_pipe_in_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ GString *buf;
+ UNUSED(loop); UNUSED(revents);
+
+ if (NULL == (buf = read_chunk(cld->pipe_in, 64*1024))) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ fcgi_cgi_child_close_read(cld);
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", cld->pipe_in, g_strerror(errno));
+ fcgi_cgi_child_close_read(cld);
+ break;
+ }
+ } else if (cld->fcon) {
+ fastcgi_send_out(cld->fcon, buf);
+ fcgi_cgi_wrote_data(cld->fcon);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+}
+
+static void fcgi_cgi_child_pipe_out_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ UNUSED(loop); UNUSED(revents);
+
+ write_queue(cld);
+}
+
+static void fcgi_cgi_child_pipe_err_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) w->data;
+ GString *buf;
+ UNUSED(loop); UNUSED(revents);
+
+ if (NULL == (buf = read_chunk(cld->pipe_err, 64*1024))) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return; /* try again later */
+ case ECONNRESET:
+ fcgi_cgi_child_close_error(cld);
+ break;
+ default:
+ ERROR("read from fd=%d failed, %s\n", cld->pipe_err, g_strerror(errno));
+ fcgi_cgi_child_close_error(cld);
+ break;
+ }
+ } else if (cld->fcon) {
+ fastcgi_send_err(cld->fcon, buf);
+ fcgi_cgi_wrote_data(cld->fcon);
+ } else {
+ g_string_free(buf, TRUE);
+ }
+}
+
+static fcgi_cgi_child* fcgi_cgi_child_create(fcgi_cgi_server *srv, fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = g_slice_new0(fcgi_cgi_child);
+
+ cld->srv = srv;
+ cld->fcon = fcon;
+ cld->aborted_id = -1;
+
+ cld->pid = -1;
+ ev_child_init(&cld->child_watcher, fcgi_cgi_child_child_cb, -1, 0);
+ cld->child_watcher.data = cld;
+
+ cld->pipe_in = cld->pipe_out = -1;
+ ev_io_init(&cld->pipe_in_watcher, fcgi_cgi_child_pipe_in_cb, -1, 0);
+ cld->pipe_in_watcher.data = cld;
+ ev_io_init(&cld->pipe_out_watcher, fcgi_cgi_child_pipe_out_cb, -1, 0);
+ cld->pipe_out_watcher.data = cld;
+ ev_io_init(&cld->pipe_err_watcher, fcgi_cgi_child_pipe_err_cb, -1, 0);
+ cld->pipe_err_watcher.data = cld;
+
+ return cld;
+}
+
+static void fcgi_cgi_child_check_done(fcgi_cgi_child *cld) {
+ if (!cld->fcon) {
+ if (-1 != cld->aborted_id && (cld->pid == -1 || (cld->pipe_out == -1 && cld->pipe_in == -1))) {
+ fcgi_cgi_child *t_cld;
+ GPtrArray *a = cld->srv->aborted_pending_childs;
+ guint i = a->len - 1;
+ t_cld = g_ptr_array_index(a, cld->aborted_id) = g_ptr_array_index(a, i);
+ g_ptr_array_set_size(a, i);
+ t_cld->aborted_id = cld->aborted_id;
+ cld->aborted_id = -1;
+ fcgi_cgi_child_free(cld);
+ }
+ } else {
+ if (cld->pid == -1 && cld->pipe_out == -1 && cld->pipe_in == -1 && cld->pipe_err == -1) {
+ fastcgi_end_request(cld->fcon, cld->child_status, FCGI_REQUEST_COMPLETE);
+ }
+ }
+}
+
+static void fcgi_cgi_child_close_write(fcgi_cgi_child *cld) {
+ if (cld->pipe_out != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_out_watcher);
+ close(cld->pipe_out);
+ cld->pipe_out = -1;
+ fastcgi_gstring_queue_clear(&cld->write_queue);
+ cld->write_queue.closed = TRUE;
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_close_read(fcgi_cgi_child *cld) {
+ if (cld->pipe_in != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_in_watcher);
+ close(cld->pipe_in);
+ cld->pipe_in = -1;
+ if (cld->fcon) fastcgi_send_out(cld->fcon, NULL);
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_close_error(fcgi_cgi_child *cld) {
+ if (cld->pipe_err != -1) {
+ ev_io_stop(cld->srv->loop, &cld->pipe_err_watcher);
+ close(cld->pipe_err);
+ cld->pipe_err = -1;
+ if (cld->fcon) fastcgi_send_err(cld->fcon, NULL);
+ fcgi_cgi_child_check_done(cld);
+ }
+}
+
+static void fcgi_cgi_child_free(fcgi_cgi_child *cld) {
+ if (cld->fcon) cld->fcon->data = NULL;
+ cld->fcon = NULL;
+ fcgi_cgi_child_close_write(cld);
+ fcgi_cgi_child_close_read(cld);
+ fcgi_cgi_child_close_error(cld);
+ ev_child_stop(cld->srv->loop, &cld->child_watcher);
+ g_slice_free(fcgi_cgi_child, cld);
+}
+
+static void fcgi_cgi_child_error(fcgi_cgi_child *cld) {
+ if (cld->fcon) {
+ fastcgi_connection_close(cld->fcon);
+ }
+}
+
+static void fcgi_cgi_child_start(fcgi_cgi_child *cld, const gchar *path) {
+ int pipes_to[2] = {-1, -1}, pipes_from[2] = {-1, -1}, pipes_err[2] = {-1, -1};
+
+ if (-1 == pipe(pipes_to)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ if (-1 == pipe(pipes_from)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ if (-1 == pipe(pipes_err)) {
+ ERROR("couldn't create pipe: %s\n", g_strerror(errno));
+ goto error;
+ }
+
+ pid_t pid = fork();
+ switch (pid) {
+ case 0: {
+ GPtrArray *enva = cld->fcon->environ;
+ char **newenv;
+ char *const args[] = { (char *) path, NULL };
+
+ close(pipes_to[1]); close(pipes_from[0]); close(pipes_err[0]);
+ move2fd(pipes_to[0], 0);
+ move2fd(pipes_from[1], 1);
+ move2fd(pipes_err[1], 2);
+
+ g_ptr_array_add(enva, NULL);
+ newenv = (char**) g_ptr_array_free(enva, FALSE);
+ execve(path, args, newenv);
+
+ ERROR("couldn't execve '%s': %s\n", path, g_strerror(errno));
+ exit(-1);
+
+ }
+ break;
+ case -1:
+ ERROR("couldn't fork: %s\n", g_strerror(errno));
+ goto error;
+ default:
+ cld->pid = pid;
+ ev_child_set(&cld->child_watcher, cld->pid, 0);
+ ev_child_start(cld->srv->loop, &cld->child_watcher);
+ close(pipes_to[0]); close(pipes_from[1]); close(pipes_err[1]);
+ fd_init(pipes_to[1]); fd_init(pipes_from[0]); fd_init(pipes_err[0]);
+ cld->pipe_out = pipes_to[1];
+ cld->pipe_in = pipes_from[0];
+ cld->pipe_err = pipes_err[0];
+ ev_io_set(&cld->pipe_out_watcher, cld->pipe_out, EV_WRITE);
+ ev_io_set(&cld->pipe_in_watcher, cld->pipe_in, EV_READ);
+ ev_io_set(&cld->pipe_err_watcher, cld->pipe_err, EV_READ);
+ if (cld->write_queue.length > 0) ev_io_start(cld->srv->loop, &cld->pipe_out_watcher);
+ ev_io_start(cld->srv->loop, &cld->pipe_in_watcher);
+ ev_io_start(cld->srv->loop, &cld->pipe_err_watcher);
+ break;
+ }
+
+ return;
+error:
+ close(pipes_to[0]); close(pipes_to[1]);
+ close(pipes_from[0]); close(pipes_from[1]);
+ close(pipes_err[0]); close(pipes_err[1]);
+ fcgi_cgi_child_error(cld);
+}
+
+static void fcgi_cgi_new_request(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (cld) return;
+ cld = fcgi_cgi_child_create(fcon->fsrv->data, fcon);
+ fcon->data = cld;
+ fcgi_cgi_child_start(cld, "/usr/bin/php5-cgi");
+}
+
+static void fcgi_cgi_wrote_data(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ if (cld->fcon->write_queue.length < MAX_BUFFER_SIZE) {
+ if (-1 != cld->pipe_in) ev_io_start(cld->srv->loop, &cld->pipe_in_watcher);
+ if (-1 != cld->pipe_err) ev_io_start(cld->srv->loop, &cld->pipe_err_watcher);
+ } else {
+ if (-1 != cld->pipe_in) ev_io_stop(cld->srv->loop, &cld->pipe_in_watcher);
+ if (-1 != cld->pipe_err) ev_io_stop(cld->srv->loop, &cld->pipe_err_watcher);
+ }
+}
+
+static void fcgi_cgi_received_stdin(fastcgi_connection *fcon, GString *data) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ /* if proc is running but pipe closed -> drop data */
+ if (!cld || cld->write_queue.closed) {
+ if (data) g_string_free(data, TRUE);
+ return;
+ }
+ fastcgi_gstring_queue_append(&cld->write_queue, data);
+ write_queue(cld); /* if we don't call this we have to check the write-queue length */
+}
+
+static void fcgi_cgi_request_aborted(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ fcgi_cgi_child_close_write(cld);
+}
+
+static void fcgi_cgi_reset_connection(fastcgi_connection *fcon) {
+ fcgi_cgi_child *cld = (fcgi_cgi_child*) fcon->data;
+ if (!cld) return;
+ fcon->data = NULL;
+ cld->fcon = NULL;
+ if (cld->pid == -1 || (cld->pipe_out == -1 && cld->pipe_in == -1)) {
+ fcgi_cgi_child_free(cld);
+ } else {
+ fcgi_cgi_child_close_write(cld);
+ cld->aborted_id = cld->srv->aborted_pending_childs->len;
+ g_ptr_array_add(cld->srv->aborted_pending_childs, cld);
+ }
+}
+
+static const fastcgi_callbacks cgi_callbacks = {
+ /* cb_new_connection: */ NULL,
+ /* cb_new_request: */ fcgi_cgi_new_request,
+ /* cb_wrote_data: */ fcgi_cgi_wrote_data,
+ /* cb_received_stdin: */ fcgi_cgi_received_stdin,
+ /* cb_received_data: */ NULL,
+ /* cb_request_aborted: */ fcgi_cgi_request_aborted,
+ /* cb_reset_connection: */ fcgi_cgi_reset_connection
+};
+
+static fcgi_cgi_server* fcgi_cgi_server_create(struct ev_loop *loop, int fd) {
+ fcgi_cgi_server* srv = g_slice_new0(fcgi_cgi_server);
+ srv->loop = loop;
+ srv->aborted_pending_childs = g_ptr_array_new();
+ srv->fsrv = fastcgi_server_create(loop, fd, &cgi_callbacks, 10);
+ srv->fsrv->data = srv;
+ return srv;
+}
+
+static void fcgi_cgi_server_free(fcgi_cgi_server* srv) {
+ guint i;
+ for (i = 0; i < srv->aborted_pending_childs->len; i++) {
+ fcgi_cgi_child_free(g_ptr_array_index(srv->aborted_pending_childs, i));
+ }
+ fastcgi_server_free(srv->fsrv);
+ g_slice_free(fcgi_cgi_server, srv);
+}
+
+#define CATCH_SIGNAL(loop, cb, n) do {\
+ ev_init(&srv->sig_w_##n, cb); \
+ ev_signal_set(&srv->sig_w_##n, SIG##n); \
+ ev_signal_start(loop, &srv->sig_w_##n); \
+ srv->sig_w_##n.data = srv; \
+ ev_unref(loop); /* Signal watchers shouldn't keep loop alive */ \
+} while (0)
+
+#define UNCATCH_SIGNAL(loop, n) do {\
+ ev_ref(loop); \
+ ev_signal_stop(loop, &srv->sig_w_##n); \
+} while (0)
+
+static void sigint_cb(struct ev_loop *loop, struct ev_signal *w, int revents) {
+ fcgi_cgi_server *srv = (fcgi_cgi_server*) w->data;
+ UNUSED(revents);
+
+ if (!srv->fsrv->do_shutdown) {
+ ERROR("Got signal, shutdown\n");
+ fastcgi_server_stop(srv->fsrv);
+ } else {
+ ERROR("Got second signal, force shutdown\n");
+ ev_unloop(loop, EVUNLOOP_ALL);
+ }
+}
+
+int main(int argc, char **argv) {
+ struct ev_loop *loop;
+ fcgi_cgi_server* srv;
+
+ loop = ev_default_loop(0);
+ srv = fcgi_cgi_server_create(loop, 0);
+
+ signal(SIGPIPE, SIG_IGN);
+ CATCH_SIGNAL(loop, sigint_cb, INT);
+ CATCH_SIGNAL(loop, sigint_cb, TERM);
+ CATCH_SIGNAL(loop, sigint_cb, HUP);
+
+ ev_loop(loop, 0);
+ fcgi_cgi_server_free(srv);
+ return 0;
+}