diff options
Diffstat (limited to 'modules/slotmem/mod_slotmem_shm.c')
-rw-r--r-- | modules/slotmem/mod_slotmem_shm.c | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/modules/slotmem/mod_slotmem_shm.c b/modules/slotmem/mod_slotmem_shm.c new file mode 100644 index 0000000..f4eaa84 --- /dev/null +++ b/modules/slotmem/mod_slotmem_shm.c @@ -0,0 +1,788 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/* Memory handler for a shared memory divided in slot. + * This one uses shared memory. + * + * Shared memory is cleaned-up for each restart, graceful or + * otherwise. + */ + +#include "ap_slotmem.h" + +#include "httpd.h" +#include "http_main.h" +#include "ap_mpm.h" /* for ap_mpm_query() */ + +#define AP_SLOTMEM_IS_PREGRAB(t) (t->desc->type & AP_SLOTMEM_TYPE_PREGRAB) +#define AP_SLOTMEM_IS_PERSIST(t) (t->desc->type & AP_SLOTMEM_TYPE_PERSIST) +#define AP_SLOTMEM_IS_CLEARINUSE(t) (t->desc->type & AP_SLOTMEM_TYPE_CLEARINUSE) + +/* The description of the slots to reuse the slotmem */ +typedef struct { + apr_size_t size; /* size of each memory slot */ + unsigned int num; /* number of mem slots */ + ap_slotmem_type_t type; /* type-specific flags */ +} sharedslotdesc_t; + +#define AP_SLOTMEM_OFFSET (APR_ALIGN_DEFAULT(sizeof(sharedslotdesc_t))) +#define AP_UNSIGNEDINT_OFFSET (APR_ALIGN_DEFAULT(sizeof(unsigned int))) + +struct ap_slotmem_instance_t { + char *name; /* file based SHM path/name */ + char *pname; /* persisted file path/name */ + int fbased; /* filebased? */ + void *shm; /* ptr to memory segment (apr_shm_t *) */ + void *base; /* data set start */ + apr_pool_t *gpool; /* per segment pool (generation cleared) */ + char *inuse; /* in-use flag table*/ + unsigned int *num_free; /* slot free count for this instance */ + void *persist; /* persist dataset start */ + const sharedslotdesc_t *desc; /* per slot desc */ + struct ap_slotmem_instance_t *next; /* location of next allocated segment */ +}; + +/* + * Layout for SHM and persisted file : + * + * +-------------------------------------------------------------+~> + * | desc | num_free | base (slots) | inuse (array) | md5 | desc | compat.. + * +------+-----------------------------------------+------------+~> + * ^ ^ ^ \ / ^ : + * |______|_____________ SHM (mem->@) ______________| | _____|__/ + * | |/ | + * | ^ v | + * |_____________________ File (mem->persist + [meta]) __| + */ + +/* global pool and list of slotmem we are handling */ +static struct ap_slotmem_instance_t *globallistmem = NULL; +static apr_pool_t *gpool = NULL; + +#define DEFAULT_SLOTMEM_PREFIX "slotmem-shm-" +#define DEFAULT_SLOTMEM_SUFFIX ".shm" +#define DEFAULT_SLOTMEM_PERSIST_SUFFIX ".persist" + +/* + * Persist the slotmem in a file + * slotmem name and file name. + * none : no persistent data + * rel_name : $server_root/rel_name + * /abs_name : $abs_name + * + */ +static int slotmem_filenames(apr_pool_t *pool, + const char *slotname, + const char **filename, + const char **persistname) +{ + const char *fname = NULL, *pname = NULL; + + if (slotname && *slotname && strcasecmp(slotname, "none") != 0) { + if (slotname[0] != '/') { + /* Each generation needs its own file name. */ + int generation = 0; + ap_mpm_query(AP_MPMQ_GENERATION, &generation); + fname = apr_psprintf(pool, "%s%s_%x%s", DEFAULT_SLOTMEM_PREFIX, + slotname, generation, DEFAULT_SLOTMEM_SUFFIX); + fname = ap_runtime_dir_relative(pool, fname); + } + else { + /* Don't mangle the file name if given an absolute path, it's + * up to the caller to provide a unique name when necessary. + */ + fname = slotname; + } + + if (persistname) { + /* Persisted file names are immutable... */ + if (slotname[0] != '/') { + pname = apr_pstrcat(pool, DEFAULT_SLOTMEM_PREFIX, + slotname, DEFAULT_SLOTMEM_SUFFIX, + DEFAULT_SLOTMEM_PERSIST_SUFFIX, + NULL); + pname = ap_runtime_dir_relative(pool, pname); + } + else { + pname = apr_pstrcat(pool, slotname, + DEFAULT_SLOTMEM_PERSIST_SUFFIX, + NULL); + } + } + } + + *filename = fname; + if (persistname) { + *persistname = pname; + } + return (fname != NULL); +} + +static void slotmem_clearinuse(ap_slotmem_instance_t *slot) +{ + unsigned int i; + char *inuse; + + if (!slot) { + return; + } + + inuse = slot->inuse; + + for (i = 0; i < slot->desc->num; i++, inuse++) { + if (*inuse) { + *inuse = 0; + (*slot->num_free)++; + } + } +} + +static void store_slotmem(ap_slotmem_instance_t *slotmem) +{ + apr_file_t *fp; + apr_status_t rv; + apr_size_t nbytes; + unsigned char digest[APR_MD5_DIGESTSIZE]; + const char *storename = slotmem->pname; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02334) + "storing %s", storename); + + if (storename) { + rv = apr_file_open(&fp, storename, APR_CREATE | APR_READ | APR_WRITE, + APR_OS_DEFAULT, slotmem->gpool); + if (APR_STATUS_IS_EEXIST(rv)) { + apr_file_remove(storename, slotmem->gpool); + rv = apr_file_open(&fp, storename, APR_CREATE | APR_READ | APR_WRITE, + APR_OS_DEFAULT, slotmem->gpool); + } + if (rv != APR_SUCCESS) { + return; + } + if (AP_SLOTMEM_IS_CLEARINUSE(slotmem)) { + slotmem_clearinuse(slotmem); + } + nbytes = (slotmem->desc->size * slotmem->desc->num) + + (slotmem->desc->num * sizeof(char)) + AP_UNSIGNEDINT_OFFSET; + apr_md5(digest, slotmem->persist, nbytes); + rv = apr_file_write_full(fp, slotmem->persist, nbytes, NULL); + if (rv == APR_SUCCESS) { + rv = apr_file_write_full(fp, digest, APR_MD5_DIGESTSIZE, NULL); + } + if (rv == APR_SUCCESS) { + rv = apr_file_write_full(fp, slotmem->desc, AP_SLOTMEM_OFFSET, + NULL); + } + apr_file_close(fp); + if (rv != APR_SUCCESS) { + apr_file_remove(storename, slotmem->gpool); + } + } +} + +static apr_status_t restore_slotmem(sharedslotdesc_t *desc, + const char *storename, apr_size_t size, + apr_pool_t *pool) +{ + apr_file_t *fp; + apr_status_t rv = APR_ENOTIMPL; + void *ptr = (char *)desc + AP_SLOTMEM_OFFSET; + apr_size_t nbytes = size - AP_SLOTMEM_OFFSET; + unsigned char digest[APR_MD5_DIGESTSIZE]; + unsigned char digest2[APR_MD5_DIGESTSIZE]; + char desc_buf[AP_SLOTMEM_OFFSET]; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02335) + "restoring %s", storename); + + if (storename) { + rv = apr_file_open(&fp, storename, APR_READ | APR_WRITE, APR_OS_DEFAULT, + pool); + if (rv == APR_SUCCESS) { + rv = apr_file_read_full(fp, ptr, nbytes, NULL); + if (rv == APR_SUCCESS || rv == APR_EOF) { + /* + * if at EOF, don't bother checking md5 + * - backwards compatibility + * */ + if (apr_file_eof(fp) != APR_EOF) { + rv = apr_file_read_full(fp, digest, APR_MD5_DIGESTSIZE, NULL); + if (rv == APR_SUCCESS || rv == APR_EOF) { + apr_md5(digest2, ptr, nbytes); + if (memcmp(digest, digest2, APR_MD5_DIGESTSIZE)) { + rv = APR_EMISMATCH; + } + /* + * if at EOF, don't bother checking desc + * - backwards compatibility + * */ + else if (apr_file_eof(fp) != APR_EOF) { + rv = apr_file_read_full(fp, desc_buf, sizeof(desc_buf), NULL); + if (rv == APR_SUCCESS || rv == APR_EOF) { + if (memcmp(desc, desc_buf, sizeof(desc_buf))) { + rv = APR_EMISMATCH; + } + else { + rv = APR_SUCCESS; + } + } + else { + rv = APR_INCOMPLETE; + } + } + else { + rv = APR_EOF; + } + } + else { + rv = APR_INCOMPLETE; + } + } + else { + rv = APR_EOF; + } + if (rv == APR_EMISMATCH) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02551) + "persisted slotmem md5/desc mismatch"); + } + else if (rv == APR_EOF) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(02552) + "persisted slotmem at EOF... bypassing md5/desc match check " + "(old persist file?)"); + rv = APR_SUCCESS; + } + } + else { + rv = APR_INCOMPLETE; + } + if (rv == APR_INCOMPLETE) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02553) + "persisted slotmem read had unexpected size"); + } + apr_file_close(fp); + } + } + return rv; +} + +/* + * Whether the module is called from a MPM that re-enter main() and + * pre/post_config phases. + */ +static APR_INLINE int is_child_process(void) +{ +#ifdef WIN32 + return getenv("AP_PARENT_PID") != NULL; +#else + return 0; +#endif +} + +static apr_status_t cleanup_slotmem(void *param) +{ + int is_child = is_child_process(); + ap_slotmem_instance_t *next = globallistmem; + + while (next) { + if (!is_child && AP_SLOTMEM_IS_PERSIST(next)) { + store_slotmem(next); + } + apr_shm_destroy(next->shm); + apr_shm_remove(next->name, next->gpool); + next = next->next; + } + + globallistmem = NULL; + return APR_SUCCESS; +} + +static apr_status_t slotmem_doall(ap_slotmem_instance_t *mem, + ap_slotmem_callback_fn_t *func, + void *data, apr_pool_t *pool) +{ + unsigned int i; + char *ptr; + char *inuse; + apr_status_t retval = APR_SUCCESS; + + if (!mem) { + return APR_ENOSHMAVAIL; + } + + ptr = (char *)mem->base; + inuse = mem->inuse; + for (i = 0; i < mem->desc->num; i++, inuse++) { + if (!AP_SLOTMEM_IS_PREGRAB(mem) || *inuse) { + retval = func((void *) ptr, data, pool); + if (retval != APR_SUCCESS) + break; + } + ptr += mem->desc->size; + } + return retval; +} + +static apr_status_t slotmem_create(ap_slotmem_instance_t **new, + const char *name, apr_size_t item_size, + unsigned int item_num, + ap_slotmem_type_t type, apr_pool_t *pool) +{ + int fbased = 1; + int restored = 0; + char *ptr; + sharedslotdesc_t *desc; + ap_slotmem_instance_t *res; + ap_slotmem_instance_t *next = globallistmem; + const char *fname, *pname = NULL; + apr_shm_t *shm; + apr_size_t basesize = (item_size * item_num); + apr_size_t size = AP_SLOTMEM_OFFSET + AP_UNSIGNEDINT_OFFSET + + (item_num * sizeof(char)) + basesize; + int persist = (type & AP_SLOTMEM_TYPE_PERSIST) != 0; + apr_status_t rv; + + *new = NULL; + if (gpool == NULL) { + return APR_ENOSHMAVAIL; + } + if (slotmem_filenames(pool, name, &fname, persist ? &pname : NULL)) { + /* first try to attach to existing slotmem */ + if (next) { + for (;;) { + if (strcmp(next->name, fname) == 0) { + /* we already have it */ + *new = next; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02603) + "create found %s in global list", fname); + return APR_SUCCESS; + } + if (!next->next) { + break; + } + next = next->next; + } + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02602) + "create didn't find %s in global list", fname); + } + else { + fbased = 0; + fname = "none"; + } + + /* first try to attach to existing shared memory */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02300) + "create %s: %"APR_SIZE_T_FMT"/%u", fname, item_size, + item_num); + + { + /* For MPMs that run pre/post_config() phases in both the parent + * and children processes (e.g. winnt), SHMs created by the + * parent exist in the children already; attach them. + */ + if (fbased) { + if (is_child_process()) { + rv = apr_shm_attach(&shm, fname, gpool); + } + else { + apr_shm_remove(fname, pool); + rv = apr_shm_create(&shm, size, fname, gpool); + } + } + else { + rv = apr_shm_create(&shm, size, NULL, gpool); + } + ap_log_error(APLOG_MARK, rv == APR_SUCCESS ? APLOG_DEBUG : APLOG_ERR, + rv, ap_server_conf, APLOGNO(02611) + "create: apr_shm_%s(%s) %s", + fbased && is_child_process() ? "attach" : "create", + fname, rv == APR_SUCCESS ? "succeeded" : "failed"); + if (rv != APR_SUCCESS) { + return rv; + } + + desc = (sharedslotdesc_t *)apr_shm_baseaddr_get(shm); + memset(desc, 0, size); + desc->size = item_size; + desc->num = item_num; + desc->type = type; + + /* + * TODO: Error check the below... What error makes + * sense if the restore fails? Any? + * For now, we continue with a fresh new slotmem, + * but NOTICE in the log. + */ + if (persist) { + rv = restore_slotmem(desc, pname, size, pool); + if (rv == APR_SUCCESS) { + restored = 1; + } + else { + /* just in case, re-zero */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + APLOGNO(02554) "could not restore %s", fname); + memset((char *)desc + AP_SLOTMEM_OFFSET, 0, + size - AP_SLOTMEM_OFFSET); + } + } + } + + ptr = (char *)desc + AP_SLOTMEM_OFFSET; + + /* For the chained slotmem stuff */ + res = apr_pcalloc(gpool, sizeof(ap_slotmem_instance_t)); + res->name = apr_pstrdup(gpool, fname); + res->pname = apr_pstrdup(gpool, pname); + res->fbased = fbased; + res->shm = shm; + res->persist = (void *)ptr; + res->num_free = (unsigned int *)ptr; + ptr += AP_UNSIGNEDINT_OFFSET; + if (!restored) { + *res->num_free = item_num; + } + res->base = (void *)ptr; + res->desc = desc; + res->gpool = gpool; + res->next = NULL; + res->inuse = ptr + basesize; + if (fbased) { + if (globallistmem == NULL) { + globallistmem = res; + } + else { + next->next = res; + } + } + + *new = res; + return APR_SUCCESS; +} + +static apr_status_t slotmem_attach(ap_slotmem_instance_t **new, + const char *name, apr_size_t *item_size, + unsigned int *item_num, apr_pool_t *pool) +{ + char *ptr; + ap_slotmem_instance_t *res; + ap_slotmem_instance_t *next = globallistmem; + sharedslotdesc_t *desc; + const char *fname; + apr_shm_t *shm; + apr_status_t rv; + + if (gpool == NULL) { + return APR_ENOSHMAVAIL; + } + if (!slotmem_filenames(pool, name, &fname, NULL)) { + return APR_ENOSHMAVAIL; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02301) + "attach looking for %s", fname); + + /* first try to attach to existing slotmem */ + if (next) { + for (;;) { + if (strcmp(next->name, fname) == 0) { + /* we already have it */ + *new = next; + *item_size = next->desc->size; + *item_num = next->desc->num; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + APLOGNO(02302) + "attach found %s: %"APR_SIZE_T_FMT"/%u", fname, + *item_size, *item_num); + return APR_SUCCESS; + } + if (!next->next) { + break; + } + next = next->next; + } + } + + /* next try to attach to existing shared memory */ + rv = apr_shm_attach(&shm, fname, gpool); + if (rv != APR_SUCCESS) { + return rv; + } + + /* Read the description of the slotmem */ + desc = (sharedslotdesc_t *)apr_shm_baseaddr_get(shm); + ptr = (char *)desc + AP_SLOTMEM_OFFSET; + + /* For the chained slotmem stuff */ + res = apr_pcalloc(gpool, sizeof(ap_slotmem_instance_t)); + res->name = apr_pstrdup(gpool, fname); + res->fbased = 1; + res->shm = shm; + res->persist = (void *)ptr; + res->num_free = (unsigned int *)ptr; + ptr += AP_UNSIGNEDINT_OFFSET; + res->base = (void *)ptr; + res->desc = desc; + res->gpool = gpool; + res->inuse = ptr + (desc->size * desc->num); + res->next = NULL; + + *new = res; + *item_size = desc->size; + *item_num = desc->num; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + APLOGNO(02303) + "attach found %s: %"APR_SIZE_T_FMT"/%u", fname, + *item_size, *item_num); + return APR_SUCCESS; +} + +static apr_status_t slotmem_dptr(ap_slotmem_instance_t *slot, + unsigned int id, void **mem) +{ + char *ptr; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + if (id >= slot->desc->num) { + return APR_EINVAL; + } + + ptr = (char *)slot->base + slot->desc->size * id; + if (!ptr) { + return APR_ENOSHMAVAIL; + } + *mem = (void *)ptr; + return APR_SUCCESS; +} + +static apr_status_t slotmem_get(ap_slotmem_instance_t *slot, unsigned int id, + unsigned char *dest, apr_size_t dest_len) +{ + void *ptr; + char *inuse; + apr_status_t ret; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + + inuse = slot->inuse + id; + if (id >= slot->desc->num) { + return APR_EINVAL; + } + if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) { + return APR_NOTFOUND; + } + ret = slotmem_dptr(slot, id, &ptr); + if (ret != APR_SUCCESS) { + return ret; + } + *inuse = 1; + memcpy(dest, ptr, dest_len); /* bounds check? */ + return APR_SUCCESS; +} + +static apr_status_t slotmem_put(ap_slotmem_instance_t *slot, unsigned int id, + unsigned char *src, apr_size_t src_len) +{ + void *ptr; + char *inuse; + apr_status_t ret; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + + inuse = slot->inuse + id; + if (id >= slot->desc->num) { + return APR_EINVAL; + } + if (AP_SLOTMEM_IS_PREGRAB(slot) && !*inuse) { + return APR_NOTFOUND; + } + ret = slotmem_dptr(slot, id, &ptr); + if (ret != APR_SUCCESS) { + return ret; + } + *inuse=1; + memcpy(ptr, src, src_len); /* bounds check? */ + return APR_SUCCESS; +} + +static unsigned int slotmem_num_slots(ap_slotmem_instance_t *slot) +{ + return slot->desc->num; +} + +static unsigned int slotmem_num_free_slots(ap_slotmem_instance_t *slot) +{ + if (AP_SLOTMEM_IS_PREGRAB(slot)) + return *slot->num_free; + else { + unsigned int i, counter=0; + char *inuse = slot->inuse; + for (i=0; i<slot->desc->num; i++, inuse++) { + if (!*inuse) + counter++; + } + return counter; + } +} + +static apr_size_t slotmem_slot_size(ap_slotmem_instance_t *slot) +{ + return slot->desc->size; +} + +static apr_status_t slotmem_grab(ap_slotmem_instance_t *slot, unsigned int *id) +{ + unsigned int i; + char *inuse; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + + inuse = slot->inuse; + + for (i = 0; i < slot->desc->num; i++, inuse++) { + if (!*inuse) { + break; + } + } + if (i >= slot->desc->num) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02293) + "slotmem(%s) grab failed. Num %u/num_free %u", + slot->name, slotmem_num_slots(slot), + slotmem_num_free_slots(slot)); + return APR_EINVAL; + } + *inuse = 1; + *id = i; + (*slot->num_free)--; + return APR_SUCCESS; +} + +static apr_status_t slotmem_fgrab(ap_slotmem_instance_t *slot, unsigned int id) +{ + char *inuse; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + + if (id >= slot->desc->num) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02397) + "slotmem(%s) fgrab failed. Num %u/num_free %u", + slot->name, slotmem_num_slots(slot), + slotmem_num_free_slots(slot)); + return APR_EINVAL; + } + inuse = slot->inuse + id; + + if (!*inuse) { + *inuse = 1; + (*slot->num_free)--; + } + return APR_SUCCESS; +} + +static apr_status_t slotmem_release(ap_slotmem_instance_t *slot, + unsigned int id) +{ + char *inuse; + + if (!slot) { + return APR_ENOSHMAVAIL; + } + + inuse = slot->inuse; + + if (id >= slot->desc->num || !inuse[id] ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(02294) + "slotmem(%s) release failed. Num %u/inuse[%u] %d", + slot->name, slotmem_num_slots(slot), + id, (int)inuse[id]); + if (id >= slot->desc->num) { + return APR_EINVAL; + } else { + return APR_NOTFOUND; + } + } + inuse[id] = 0; + (*slot->num_free)++; + return APR_SUCCESS; +} + +static const ap_slotmem_provider_t storage = { + "sharedmem", + &slotmem_doall, + &slotmem_create, + &slotmem_attach, + &slotmem_dptr, + &slotmem_get, + &slotmem_put, + &slotmem_num_slots, + &slotmem_num_free_slots, + &slotmem_slot_size, + &slotmem_grab, + &slotmem_release, + &slotmem_fgrab +}; + +/* make the storage usable from outside */ +static const ap_slotmem_provider_t *slotmem_shm_getstorage(void) +{ + return (&storage); +} + +/* + * Make sure the shared memory is cleaned + */ +static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, + server_rec *s) +{ + apr_pool_cleanup_register(p, NULL, cleanup_slotmem, apr_pool_cleanup_null); + return OK; +} + +static int pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) +{ + gpool = p; + globallistmem = NULL; + return OK; +} + +static void ap_slotmem_shm_register_hook(apr_pool_t *p) +{ + const ap_slotmem_provider_t *storage = slotmem_shm_getstorage(); + ap_register_provider(p, AP_SLOTMEM_PROVIDER_GROUP, "shm", + AP_SLOTMEM_PROVIDER_VERSION, storage); + ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_LAST); + ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(slotmem_shm) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + NULL, /* command apr_table_t */ + ap_slotmem_shm_register_hook /* register hooks */ +}; |