/* Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Documentation:
 *
 * mod_whatkilledus is an experimental module for Apache httpd 2.x which
 * tracks the current request and logs a report of the active request
 * when a child process crashes.  You should verify that it works reasonably
 * on your system before putting it in production.
 *
 * mod_whatkilledus is called during request processing to save information
 * about the current request.  It also implements a fatal exception hook
 * that will be called when a child process crashes.
 *
 * Apache httpd requirements for mod_whatkilledus:
 *
 *   Apache httpd >= 2.0.49 must be built with the --enable-exception-hook
 *   configure option and mod_so enabled.
 *
 * Compiling mod_whatkilledus:
 *
 *   apxs -ci -I/path/to/apache/src/main mod_whatkilledus.c
 *
 * Activating mod_whatkilledus:
 *
 *   1. Load it like any other DSO.
 *
 *        LoadModule whatkilledus_module modules/mod_whatkilledus.so
 *
 *   2. Enable exception hooks for modules like mod_whatkilledus:
 *        EnableExceptionHook On
 *
 *   3. Choose where the report on current activity should be written.  If
 *      you want it reported to some place other than the error log, use the
 *      WhatKilledUsLog directive to specify a fully-qualified filename for
 *      the log.  Note that the web server user id (e.g., "nobody") must
 *      be able to create or append to this log file, as the log file is
 *      not opened until a crash occurs.
 */

#include <unistd.h>

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_connection.h"
#include "http_log.h"
#include "ap_mpm.h"

#include "apr_strings.h"
#include "apr_network_io.h"
#include "apr_thread_mutex.h"

module AP_MODULE_DECLARE_DATA whatkilledus_module;

typedef struct wku_req_info_tag {
    struct wku_req_info_tag *next;
    request_rec *r;
} wku_req_info_t;

typedef struct wku_conn_info_tag {
    struct wku_conn_info_tag *next;
    struct wku_conn_info_tag *prev;
    apr_pool_t *p;
    conn_rec *c;
    wku_req_info_t *reqs;
#if APR_HAS_THREADS
    pthread_t tid;
#else
    int tid;
#endif
} wku_conn_info_t;

static wku_conn_info_t *active;
static char *log_fname;

#if APR_HAS_THREADS

static apr_thread_mutex_t *active_mutex;

#define MY_TID pthread_self()
#define LOCK_ACTIVE_LIST \
    apr_thread_mutex_lock(active_mutex)
#define UNLOCK_ACTIVE_LIST \
    apr_thread_mutex_unlock(active_mutex)

#else

#define MY_TID 0
#define LOCK_ACTIVE_LIST
#define UNLOCK_ACTIVE_LIST

#endif

static void wku_child_init(apr_pool_t *p, server_rec *s)
{
#if APR_HAS_THREADS
    apr_status_t rv;

    rv = apr_thread_mutex_create(&active_mutex, APR_THREAD_MUTEX_DEFAULT, p);
    ap_assert(rv == APR_SUCCESS);
#endif    
}

static wku_req_info_t *get_new_ri(wku_conn_info_t *ci)
{
    wku_req_info_t *new = malloc(sizeof(*new));

    memset(new, 0, sizeof(*new));
    new->next = ci->reqs;
    ci->reqs = new;
    return new;
}

static wku_conn_info_t *get_new_ci(conn_rec *c)
{
    wku_conn_info_t *new;

    new = (wku_conn_info_t *)calloc(1, sizeof(*new));
    ap_set_module_config(c->conn_config, &whatkilledus_module, new);
    new->tid = MY_TID;
    
    LOCK_ACTIVE_LIST;
    new->next = active;
    active = new;
    if (new->next) {
        new->next->prev = new;
    }
    UNLOCK_ACTIVE_LIST;
        
    return new;
}

static wku_conn_info_t *get_cur_ci(conn_rec *c)
{
    if (c) {
        return (wku_conn_info_t *)ap_get_module_config(c->conn_config,
                                                       &whatkilledus_module);
    }
    else {
        /* search for saved info! */
        wku_conn_info_t *cur;
        
        LOCK_ACTIVE_LIST;
        cur = active;
        while (cur) {
            if (cur->tid == MY_TID) {
                UNLOCK_ACTIVE_LIST;
                return cur;
            }
            cur = cur->next;
        }
        UNLOCK_ACTIVE_LIST;
    }
    return NULL; /* bad news! */
}

static void free_ci(wku_conn_info_t *ci)
{
    LOCK_ACTIVE_LIST;
    if (ci->prev) {
        ci->prev->next = ci->next;
    }
    else {
        active = ci->next;
    }
    if (ci->next) {
        ci->next->prev = ci->prev;
    }
    UNLOCK_ACTIVE_LIST;
    /* todo: free any chained requests! */
    free(ci);
}

static int wku_fatal_exception(ap_exception_info_t *ei)
{
    wku_conn_info_t *curconn;
    wku_req_info_t *curreq;
    char buf[4096];

    curconn = get_cur_ci(NULL); /* no conn_rec avail */

    if (!curconn) {
        const char *msg = "no active connection at time of fatal exception\n";
        
        write(2, msg, strlen(msg));
        return 0;
    }
    
    curreq = curconn->reqs; /* head of list */

    apr_snprintf(buf, sizeof(buf),
                 "** Fatal signal %d received in pid %" APR_PID_T_FMT ":\n"
                 "** Active request: %s\n"
                 "** Client IP: %s\n"
                 "** Local port: %d\n"
                 "** Connection aborted?  %s\n"
                 "",
                 ei->sig,
                 ei->pid,
                 curreq ? curreq->r->the_request : "no active request",
                 curconn->c->remote_ip,
                 (int)curconn->c->local_addr->port,
                 curconn->c->aborted ? "yes" : "no"
        );

    free_ci(curconn);
    write(2, buf, strlen(buf));
    
    return 0;
}

static apr_status_t wku_connection_end(void *void_ci)
{
    wku_conn_info_t *ci = void_ci;

    free_ci(ci);
    return APR_SUCCESS;
}

static int wku_pre_connection(conn_rec *c, void *vcsd)
{
    wku_conn_info_t *new;

    new = get_new_ci(c);
    new->c = c;
    new->p = c->pool;

    apr_pool_cleanup_register(c->pool, new, wku_connection_end, apr_pool_cleanup_null);

    return DECLINED;
}

static int wku_post_read_request(request_rec *r)
{
    wku_conn_info_t *cur;
    wku_req_info_t *new;
    
    if (r->prev) { /* we were already called for this internal redirect */
        return DECLINED;
    }

    cur = get_cur_ci(r->connection);
    new = get_new_ri(cur);

    new->r = r;

    return DECLINED;
}

static void wku_error_log(const char *file, int line, int level,
                          apr_status_t status, const server_rec *s,
                          const request_rec *r, apr_pool_t *pool,
                          const char *errstr)
{
}

static void wku_register_hooks(apr_pool_t *p)
{
    ap_hook_child_init(wku_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_pre_connection(wku_pre_connection, NULL, NULL,
                           APR_HOOK_REALLY_FIRST); /* precede crashes */
    ap_hook_post_read_request(wku_post_read_request, NULL, NULL,
                              APR_HOOK_REALLY_FIRST); /* precede crashes */
    ap_hook_error_log(wku_error_log, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_fatal_exception(wku_fatal_exception, NULL, NULL, APR_HOOK_MIDDLE);
}

static const char *wku_cmd_file(cmd_parms *cmd, void *dummy, const char *arg)
{
    log_fname = apr_pstrdup(cmd->pool, arg);
    return NULL;
}

static const command_rec wku_cmds[] =
{
    AP_INIT_TAKE1("WhatKilledUsLog", wku_cmd_file, NULL, RSRC_CONF,
                  "the fully-qualified filename of the mod_whatkilledus "
                  "logfile"),
    {NULL}
};

module AP_MODULE_DECLARE_DATA whatkilledus_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    wku_cmds,
    wku_register_hooks
};

