/* * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "dispatcher.h" #include "request.h" #include "session.h" #include #include #include #include #include #include #include #include #include /** Intervall to check for expired sessions, in seconds */ #define CLEANUP_INTERVAL 30 typedef struct private_dispatcher_t private_dispatcher_t; /** * private data of the task manager */ struct private_dispatcher_t { /** * public functions */ dispatcher_t public; /** * fcgi socket fd */ int fd; /** * thread list */ thread_t **threads; /** * number of threads in "threads" */ int thread_count; /** * session locking mutex */ mutex_t *mutex; /** * Hahstable with active sessions */ hashtable_t *sessions; /** * session timeout */ time_t timeout; /** * timestamp of last session cleanup round */ time_t last_cleanup; /** * running in debug mode? */ bool debug; /** * List of controllers controller_constructor_t */ linked_list_t *controllers; /** * List of filters filter_constructor_t */ linked_list_t *filters; /** * constructor function to create session context (in controller_entry_t) */ context_constructor_t context_constructor; /** * user param to context constructor */ void *param; }; typedef struct { /** constructor function */ controller_constructor_t constructor; /** parameter to constructor */ void *param; } controller_entry_t; typedef struct { /** constructor function */ filter_constructor_t constructor; /** parameter to constructor */ void *param; } filter_entry_t; typedef struct { /** session instance */ session_t *session; /** condvar to wait for session */ condvar_t *cond; /** client host address, to prevent session hijacking */ char *host; /** TRUE if session is in use */ bool in_use; /** last use of the session */ time_t used; /** has the session been closed by the handler? */ bool closed; } session_entry_t; /** * create a session and instanciate controllers */ static session_t* load_session(private_dispatcher_t *this) { enumerator_t *enumerator; controller_entry_t *centry; filter_entry_t *fentry; session_t *session; context_t *context = NULL; controller_t *controller; filter_t *filter; if (this->context_constructor) { context = this->context_constructor(this->param); } session = session_create(context); enumerator = this->controllers->create_enumerator(this->controllers); while (enumerator->enumerate(enumerator, ¢ry)) { controller = centry->constructor(context, centry->param); session->add_controller(session, controller); } enumerator->destroy(enumerator); enumerator = this->filters->create_enumerator(this->filters); while (enumerator->enumerate(enumerator, &fentry)) { filter = fentry->constructor(context, fentry->param); session->add_filter(session, filter); } enumerator->destroy(enumerator); return session; } /** * create a new session entry */ static session_entry_t *session_entry_create(private_dispatcher_t *this, char *host) { session_entry_t *entry; session_t *session; session = load_session(this); if (!session) { return NULL; } INIT(entry, .cond = condvar_create(CONDVAR_TYPE_DEFAULT), .session = session, .host = strdup(host), .used = time_monotonic(NULL), ); return entry; } /** * destroy a session */ static void session_entry_destroy(session_entry_t *entry) { entry->session->destroy(entry->session); entry->cond->destroy(entry->cond); free(entry->host); free(entry); } METHOD(dispatcher_t, add_controller, void, private_dispatcher_t *this, controller_constructor_t constructor, void *param) { controller_entry_t *entry; INIT(entry, .constructor = constructor, .param = param, ); this->controllers->insert_last(this->controllers, entry); } METHOD(dispatcher_t, add_filter, void, private_dispatcher_t *this, filter_constructor_t constructor, void *param) { filter_entry_t *entry; INIT(entry, .constructor = constructor, .param = param, ); this->filters->insert_last(this->filters, entry); } /** * Hashtable hash function */ static u_int session_hash(char *sid) { return chunk_hash(chunk_create(sid, strlen(sid))); } /** * Hashtable equals function */ static bool session_equals(char *sid1, char *sid2) { return streq(sid1, sid2); } /** * Cleanup unused sessions */ static void cleanup_sessions(private_dispatcher_t *this, time_t now) { if (this->last_cleanup < now - CLEANUP_INTERVAL) { char *sid; session_entry_t *entry; enumerator_t *enumerator; linked_list_t *remove; this->last_cleanup = now; remove = linked_list_create(); enumerator = this->sessions->create_enumerator(this->sessions); while (enumerator->enumerate(enumerator, &sid, &entry)) { /* check all sessions for timeout or close flag */ if (!entry->in_use && (entry->used < now - this->timeout || entry->closed)) { remove->insert_last(remove, sid); } } enumerator->destroy(enumerator); while (remove->remove_last(remove, (void**)&sid) == SUCCESS) { entry = this->sessions->remove(this->sessions, sid); if (entry) { session_entry_destroy(entry); } } remove->destroy(remove); } } /** * Actual dispatching code */ static void dispatch(private_dispatcher_t *this) { thread_cancelability(FALSE); while (TRUE) { request_t *request; session_entry_t *found = NULL; time_t now; char *sid; thread_cancelability(TRUE); request = request_create(this->fd, this->debug); thread_cancelability(FALSE); if (request == NULL) { continue; } now = time_monotonic(NULL); sid = request->get_cookie(request, "SID"); this->mutex->lock(this->mutex); if (sid) { found = this->sessions->get(this->sessions, sid); } if (found && !streq(found->host, request->get_host(request))) { found = NULL; } if (found) { /* wait until session is unused */ while (found->in_use) { found->cond->wait(found->cond, this->mutex); } } else { /* create a new session if not found */ found = session_entry_create(this, request->get_host(request)); if (!found) { request->destroy(request); this->mutex->unlock(this->mutex); continue; } sid = found->session->get_sid(found->session); this->sessions->put(this->sessions, sid, found); } found->in_use = TRUE; this->mutex->unlock(this->mutex); /* start processing */ found->session->process(found->session, request); found->used = time_monotonic(NULL); /* release session */ this->mutex->lock(this->mutex); found->in_use = FALSE; found->closed = request->session_closed(request); found->cond->signal(found->cond); cleanup_sessions(this, now); this->mutex->unlock(this->mutex); request->destroy(request); } } METHOD(dispatcher_t, run, void, private_dispatcher_t *this, int threads) { this->thread_count = threads; this->threads = malloc(sizeof(thread_t*) * threads); while (threads) { this->threads[threads - 1] = thread_create((thread_main_t)dispatch, this); if (this->threads[threads - 1]) { threads--; } } } METHOD(dispatcher_t, waitsignal, void, private_dispatcher_t *this) { sigset_t set; int sig; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); sigaddset(&set, SIGHUP); sigprocmask(SIG_BLOCK, &set, NULL); sigwait(&set, &sig); } METHOD(dispatcher_t, destroy, void, private_dispatcher_t *this) { char *sid; session_entry_t *entry; enumerator_t *enumerator; FCGX_ShutdownPending(); while (this->thread_count--) { thread_t *thread = this->threads[this->thread_count]; thread->cancel(thread); thread->join(thread); } enumerator = this->sessions->create_enumerator(this->sessions); while (enumerator->enumerate(enumerator, &sid, &entry)) { session_entry_destroy(entry); } enumerator->destroy(enumerator); this->sessions->destroy(this->sessions); this->controllers->destroy_function(this->controllers, free); this->filters->destroy_function(this->filters, free); this->mutex->destroy(this->mutex); free(this->threads); free(this); } /* * see header file */ dispatcher_t *dispatcher_create(char *socket, bool debug, int timeout, context_constructor_t constructor, void *param) { private_dispatcher_t *this; INIT(this, .public = { .add_controller = _add_controller, .add_filter = _add_filter, .run = _run, .waitsignal = _waitsignal, .destroy = _destroy, }, .sessions = hashtable_create((void*)session_hash, (void*)session_equals, 4096), .controllers = linked_list_create(), .filters = linked_list_create(), .context_constructor = constructor, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .param = param, .timeout = timeout, .last_cleanup = time_monotonic(NULL), .debug = debug, ); FCGX_Init(); if (socket) { unlink(socket); this->fd = FCGX_OpenSocket(socket, 10); } return &this->public; }