/* ==================================================================== * Copyright (c) 2002 Aaron Bannert * All rights reserved. * * Redistribution and use in source an binary forms, with or without * modification, are perimtted. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== */ /** * $Id: mod_shm_counter.c,v 1.4 2002/06/18 04:42:48 aaron Exp $ * * This module generates a simple shared-memory counter * for each hook phase, to illustrate both the use of * shared memory (anonymous and name-based) and global * mutexes. It simply counts the number of times each * of this modules' hooks are run. * * Inspired by Ken Coar's mod_magic_mushroom.c. */ #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "apr_global_mutex.h" #include "apr_shm.h" /* -------------------------------------------------------------------- * Global definitions * -------------------------------------------------------------------- */ #define SHM_COUNTER_HANDLER "shm-counter-handler" #define SHM_COUNTER_FILE "logs/ShmCounterFile" #define SHM_COUNTER_LOCKFILE "logs/ShmCounterLockfile" /* The prototype for our module structure, defined at the bottom */ module AP_MODULE_DECLARE_DATA shm_counter_module; /* The structure that is stored in shared memory */ typedef struct { unsigned int fixups; unsigned int handler; } shm_counters_t; /* The per-server configuration */ typedef struct { char *shmcounterfile; char *shmcounterlockfile; apr_global_mutex_t *mutex; /* the cross-thread/cross-process mutex */ apr_shm_t *counters_shm; /* the APR shared segment object */ shm_counters_t *counters; /* the per-process address of the segment */ } shm_counter_scfg_t; /* -------------------------------------------------------------------- * Configuration handling routines * -------------------------------------------------------------------- */ static void *shm_counter_create_server_config(apr_pool_t *p, server_rec *s) { shm_counter_scfg_t *scfg; scfg = apr_palloc(p, sizeof(*scfg)); scfg->shmcounterfile = ap_server_root_relative(p, SHM_COUNTER_FILE); scfg->shmcounterlockfile = ap_server_root_relative(p, SHM_COUNTER_LOCKFILE); scfg->mutex = NULL; scfg->counters_shm = NULL; scfg->counters = NULL; return (void *)scfg; } static void *shm_counter_merge_server_config(apr_pool_t *p, void *base_, void *vhost_) { /* FIXME: how can we merge if we don't support vhosts? */ return base_; } static const char *set_shm_counter_lockfile(cmd_parms *cmd, void *mconfig, const char *arg) { shm_counter_scfg_t *scfg = mconfig; scfg->shmcounterlockfile = ap_server_root_relative(cmd->pool, arg); return NULL; } static const char *set_shm_counter_file(cmd_parms *cmd, void *mconfig, const char *arg) { shm_counter_scfg_t *scfg = mconfig; scfg->shmcounterfile = ap_server_root_relative(cmd->pool, arg); return NULL; } /* -------------------------------------------------------------------- * Hook processing * -------------------------------------------------------------------- */ static int shm_counter_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { apr_status_t rv; shm_counter_scfg_t *scfg; void *data = NULL; const char *userdata_key = "shm_counter_post_config"; /* Apache loads DSO modules twice. We want to wait until the second * load before setting up our global mutex and shared memory segment. * To avoid the first call to the post_config hook, we set some * dummy userdata in a pool that lives longer than the first DSO * load, and only run if that data is set on subsequent calls to * this hook. */ apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (data == NULL) { /* WARNING: This must *not* be apr_pool_userdata_setn(). The * reason for this is because the static symbol section of the * DSO may not be at the same address offset when it is reloaded. * Since setn() does not make a copy and only compares addresses, * the get() will be unable to find the original userdata. */ apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; /* This would be the first time through */ } /* If we made it this far, we can safely initialize the module */ scfg = ap_get_module_config(s->module_config, &shm_counter_module); ap_add_version_component(pconf, "mod_shm_counter/$Revision $"); /* Since we are still in the parent before any children have been * created, it is safe to create the shared lock and shared segment. * If we waited until a child had been created, we run in to a race * condition. */ rv = apr_global_mutex_create(&scfg->mutex, scfg->shmcounterlockfile, APR_LOCK_DEFAULT, pconf); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "Failed to create " "mod_shm_counter global mutex file '%s'", scfg->shmcounterlockfile); return HTTP_INTERNAL_SERVER_ERROR; } rv = apr_shm_create(&scfg->counters_shm, sizeof(*scfg->counters), scfg->shmcounterfile, pconf); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "Failed to create " "mod_shm_counter shared segment file '%s'", scfg->shmcounterfile ? /* Just in case the file was NULL. */ scfg->shmcounterfile : "NULL"); return HTTP_INTERNAL_SERVER_ERROR; } /* The pointer to the shm_counters_t structure is only valid * in the current process, since in another process it may * not be mapped in the same address-space. This is especially * likely on Windows or when accessing the segment from an * external process. */ scfg->counters = apr_shm_baseaddr_get(scfg->counters_shm); /* Clear all the counters in the structure. */ memset(scfg->counters, 0, sizeof(*scfg->counters)); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "mod_shm_counter initialized"); return OK; } static void shm_counter_child_init(apr_pool_t *p, server_rec *s) { apr_status_t rv; shm_counter_scfg_t *scfg = ap_get_module_config(s->module_config, &shm_counter_module); /* Now that we are in a child process, we have to reconnect * to the global mutex and the shared segment. We also * have to find out the base address of the segment, in case * it moved to a new address. */ rv = apr_global_mutex_child_init(&scfg->mutex, scfg->shmcounterlockfile, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "Failed to attach to " "mod_shm_counter global mutex file '%s'", scfg->shmcounterlockfile); return; } /* We only need to attach to the segment if we didn't inherit * it from the parent process (ie. Windows) */ if (!scfg->counters_shm) { rv = apr_shm_attach(&scfg->counters_shm, scfg->shmcounterfile, p); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, "Failed to attach to " "mod_shm_counter shared memory file '%s'", scfg->shmcounterfile ? /* Just in case the file was NULL. */ scfg->shmcounterfile : "NULL"); return; } } scfg->counters = apr_shm_baseaddr_get(scfg->counters_shm); } static int shm_counter_fixups(request_rec *r) { apr_status_t rv; shm_counter_scfg_t *scfg = ap_get_module_config(r->server->module_config, &shm_counter_module); /* Increment the count for this handler in a protected section. */ rv = apr_global_mutex_lock(scfg->mutex); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, "apr_global_mutex_lock " "failed"); return HTTP_INTERNAL_SERVER_ERROR; } scfg->counters->fixups++; rv = apr_global_mutex_unlock(scfg->mutex); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, "apr_global_mutex_unlock " "failed"); return HTTP_INTERNAL_SERVER_ERROR; } /* Always return DECLINED, since we're not really going to do any work. */ return DECLINED; } static int shm_counter_handler(request_rec *r) { apr_status_t rv; shm_counter_scfg_t *scfg = ap_get_module_config(r->server->module_config, &shm_counter_module); /* Increment the count for this handler in a protected section. */ rv = apr_global_mutex_lock(scfg->mutex); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, "apr_global_mutex_lock " "failed"); return HTTP_INTERNAL_SERVER_ERROR; } scfg->counters->handler++; rv = apr_global_mutex_unlock(scfg->mutex); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_CRIT, rv, r, "apr_global_mutex_unlock " "failed"); return HTTP_INTERNAL_SERVER_ERROR; } /* Determine if we are the handler for this request. */ if (r->handler && strcmp(r->handler, SHM_COUNTER_HANDLER)) { return DECLINED; } /* Disallow any method except GET */ if (r->method_number != M_GET) { return DECLINED; } /* Display the count information */ ap_set_content_type(r, "text/html"); if (r->header_only) { return OK; } ap_rputs(DOCTYPE_HTML_3_2 "" "Server Hook Usage Example (mod_shm_counter)" "\n

Server Hook Usage Example " "(mod_shm_counter)

\n", r); ap_rprintf(r, "fixups = %d
\nhandler = %d
\n", scfg->counters->fixups, scfg->counters->handler); ap_rputs("\n", r); return OK; } /* -------------------------------------------------------------------- * Module internals * -------------------------------------------------------------------- */ static void shm_counter_register_hooks(apr_pool_t *p) { ap_hook_post_config(shm_counter_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_child_init(shm_counter_child_init, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_fixups(shm_counter_fixups, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_handler(shm_counter_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); } static const command_rec shm_counter_cmds[] = { AP_INIT_TAKE1("ShmCounterLockfile", set_shm_counter_lockfile, NULL, RSRC_CONF, "Filename of global mutex"), AP_INIT_TAKE1("ShmCounterFile", set_shm_counter_file, NULL, RSRC_CONF, "Filename of shared segment, or NULL for anonymous shared " "memory"), {NULL} }; module AP_MODULE_DECLARE_DATA shm_counter_module = { STANDARD20_MODULE_STUFF, NULL, /* per-directory config creator */ NULL, /* dir config merger */ shm_counter_create_server_config, /* server config creator */ shm_counter_merge_server_config, /* server config merger */ shm_counter_cmds, /* command table */ shm_counter_register_hooks, /* set up other request processing hooks */ };