/* 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. */ /* Overview of what this is and does: * http://www.apache.org/~niq/dbd.html * or * http://apache.webthing.com/database/ */ #include "apr_reslist.h" #include "apr_strings.h" #include "apr_hash.h" #include "apr_tables.h" #include "apr_lib.h" #include "apr_dbd.h" #define APR_WANT_MEMFUNC #define APR_WANT_STRFUNC #include "apr_want.h" #include "http_protocol.h" #include "http_config.h" #include "http_log.h" #include "http_request.h" #include "mod_dbd.h" extern module AP_MODULE_DECLARE_DATA dbd_module; /************ svr cfg: manage db connection pool ****************/ #define NMIN_SET 0x1 #define NKEEP_SET 0x2 #define NMAX_SET 0x4 #define EXPTIME_SET 0x8 typedef struct { server_rec *server; const char *name; const char *params; int persist; #if APR_HAS_THREADS int nmin; int nkeep; int nmax; int exptime; int set; #endif apr_hash_t *queries; } dbd_cfg_t; typedef struct dbd_group_t dbd_group_t; struct dbd_group_t { dbd_cfg_t *cfg; dbd_group_t *next; apr_pool_t *pool; #if APR_HAS_THREADS apr_thread_mutex_t *mutex; apr_reslist_t *reslist; int destroyed; #else ap_dbd_t *rec; #endif }; typedef struct { dbd_cfg_t *cfg; dbd_group_t *group; } svr_cfg; typedef enum { cmd_name, cmd_params, cmd_persist, cmd_min, cmd_keep, cmd_max, cmd_exp } cmd_parts; static apr_pool_t *config_pool; static dbd_group_t *group_list; /* a default DBDriver value that'll generate meaningful error messages */ static const char *const no_dbdriver = "[DBDriver unset]"; /* A default nmin of >0 will help with generating meaningful * startup error messages if the database is down. */ #define DEFAULT_NMIN 1 #define DEFAULT_NKEEP 2 #define DEFAULT_NMAX 10 #define DEFAULT_EXPTIME 300 static void *create_dbd_config(apr_pool_t *pool, server_rec *s) { svr_cfg *svr = apr_pcalloc(pool, sizeof(svr_cfg)); dbd_cfg_t *cfg = svr->cfg = apr_pcalloc(pool, sizeof(dbd_cfg_t)); cfg->server = s; cfg->name = no_dbdriver; /* to generate meaningful error messages */ cfg->params = ""; /* don't risk segfault on misconfiguration */ cfg->persist = -1; #if APR_HAS_THREADS cfg->nmin = DEFAULT_NMIN; cfg->nkeep = DEFAULT_NKEEP; cfg->nmax = DEFAULT_NMAX; cfg->exptime = DEFAULT_EXPTIME; #endif cfg->queries = apr_hash_make(pool); return svr; } static void *merge_dbd_config(apr_pool_t *pool, void *basev, void *addv) { dbd_cfg_t *base = ((svr_cfg*) basev)->cfg; dbd_cfg_t *add = ((svr_cfg*) addv)->cfg; svr_cfg *svr = apr_pcalloc(pool, sizeof(svr_cfg)); dbd_cfg_t *new = svr->cfg = apr_pcalloc(pool, sizeof(dbd_cfg_t)); new->server = add->server; new->name = (add->name != no_dbdriver) ? add->name : base->name; new->params = strcmp(add->params, "") ? add->params : base->params; new->persist = (add->persist != -1) ? add->persist : base->persist; #if APR_HAS_THREADS new->nmin = (add->set&NMIN_SET) ? add->nmin : base->nmin; new->nkeep = (add->set&NKEEP_SET) ? add->nkeep : base->nkeep; new->nmax = (add->set&NMAX_SET) ? add->nmax : base->nmax; new->exptime = (add->set&EXPTIME_SET) ? add->exptime : base->exptime; #endif new->queries = apr_hash_overlay(pool, add->queries, base->queries); return svr; } #define ISINT(val) do { \ const char *p; \ \ for (p = val; *p; ++p) { \ if (!apr_isdigit(*p)) { \ return "Argument must be numeric!"; \ } \ } \ } while (0) static const char *dbd_param(cmd_parms *cmd, void *dconf, const char *val) { const apr_dbd_driver_t *driver = NULL; svr_cfg *svr = ap_get_module_config(cmd->server->module_config, &dbd_module); dbd_cfg_t *cfg = svr->cfg; switch ((long) cmd->info) { case cmd_name: cfg->name = val; /* loading the driver involves once-only dlloading that is * best done at server startup. This also guarantees that * we won't return an error later. */ switch (apr_dbd_get_driver(cmd->pool, cfg->name, &driver)) { case APR_ENOTIMPL: return apr_psprintf(cmd->pool, "DBD: No driver for %s", cfg->name); case APR_EDSOOPEN: return apr_psprintf(cmd->pool, #ifdef NETWARE "DBD: Can't load driver file dbd%s.nlm", #else "DBD: Can't load driver file apr_dbd_%s.so", #endif cfg->name); case APR_ESYMNOTFOUND: return apr_psprintf(cmd->pool, "DBD: Failed to load driver apr_dbd_%s_driver", cfg->name); } break; case cmd_params: cfg->params = val; break; #if APR_HAS_THREADS case cmd_min: ISINT(val); cfg->nmin = atoi(val); cfg->set |= NMIN_SET; break; case cmd_keep: ISINT(val); cfg->nkeep = atoi(val); cfg->set |= NKEEP_SET; break; case cmd_max: ISINT(val); cfg->nmax = atoi(val); cfg->set |= NMAX_SET; break; case cmd_exp: ISINT(val); cfg->exptime = atoi(val); cfg->set |= EXPTIME_SET; break; #endif } return NULL; } static const char *dbd_param_flag(cmd_parms *cmd, void *dconf, int flag) { svr_cfg *svr = ap_get_module_config(cmd->server->module_config, &dbd_module); switch ((long) cmd->info) { case cmd_persist: svr->cfg->persist = flag; break; } return NULL; } static const char *dbd_prepare(cmd_parms *cmd, void *dconf, const char *query, const char *label) { if (!label) { label = query; query = ""; } ap_dbd_prepare(cmd->server, query, label); return NULL; } static const command_rec dbd_cmds[] = { AP_INIT_TAKE1("DBDriver", dbd_param, (void*)cmd_name, RSRC_CONF, "SQL Driver"), AP_INIT_TAKE1("DBDParams", dbd_param, (void*)cmd_params, RSRC_CONF, "SQL Driver Params"), AP_INIT_FLAG("DBDPersist", dbd_param_flag, (void*)cmd_persist, RSRC_CONF, "Use persistent connection/pool"), AP_INIT_TAKE12("DBDPrepareSQL", dbd_prepare, NULL, RSRC_CONF, "SQL statement to prepare (or nothing, to override " "statement inherited from main server) and label"), #if APR_HAS_THREADS AP_INIT_TAKE1("DBDMin", dbd_param, (void*)cmd_min, RSRC_CONF, "Minimum number of connections"), /* XXX: note that mod_proxy calls this "smax" */ AP_INIT_TAKE1("DBDKeep", dbd_param, (void*)cmd_keep, RSRC_CONF, "Maximum number of sustained connections"), AP_INIT_TAKE1("DBDMax", dbd_param, (void*)cmd_max, RSRC_CONF, "Maximum number of connections"), /* XXX: note that mod_proxy calls this "ttl" (time to live) */ AP_INIT_TAKE1("DBDExptime", dbd_param, (void*)cmd_exp, RSRC_CONF, "Keepalive time for idle connections"), #endif {NULL} }; static int dbd_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { config_pool = pconf; group_list = NULL; return OK; } DBD_DECLARE_NONSTD(void) ap_dbd_prepare(server_rec *s, const char *query, const char *label) { svr_cfg *svr; svr = ap_get_module_config(s->module_config, &dbd_module); if (!svr) { /* some modules may call from within config directive handlers, and * if these are called in a server context that contains no mod_dbd * config directives, then we have to create our own server config */ svr = create_dbd_config(config_pool, s); ap_set_module_config(s->module_config, &dbd_module, svr); } if (apr_hash_get(svr->cfg->queries, label, APR_HASH_KEY_STRING) && strcmp(query, "")) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, "conflicting SQL statements with label %s", label); } apr_hash_set(svr->cfg->queries, label, APR_HASH_KEY_STRING, query); } typedef struct { const char *label, *query; } dbd_query_t; static int dbd_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { server_rec *sp; apr_array_header_t *add_queries = apr_array_make(ptemp, 10, sizeof(dbd_query_t)); for (sp = s; sp; sp = sp->next) { svr_cfg *svr = ap_get_module_config(sp->module_config, &dbd_module); dbd_cfg_t *cfg = svr->cfg; apr_hash_index_t *hi_first = apr_hash_first(ptemp, cfg->queries); dbd_group_t *group; /* dbd_setup in 2.2.3 and under was causing spurious error messages * when dbd isn't configured. We can stop that with a quick check here * together with a similar check in ap_dbd_open (where being * unconfigured is a genuine error that must be reported). */ if (cfg->name == no_dbdriver || !cfg->persist) { continue; } for (group = group_list; group; group = group->next) { dbd_cfg_t *group_cfg = group->cfg; apr_hash_index_t *hi; int group_ok = 1; if (strcmp(cfg->name, group_cfg->name) || strcmp(cfg->params, group_cfg->params)) { continue; } #if APR_HAS_THREADS if (cfg->nmin != group_cfg->nmin || cfg->nkeep != group_cfg->nkeep || cfg->nmax != group_cfg->nmax || cfg->exptime != group_cfg->exptime) { continue; } #endif add_queries->nelts = 0; for (hi = hi_first; hi; hi = apr_hash_next(hi)) { const char *label, *query; const char *group_query; apr_hash_this(hi, (void*) &label, NULL, (void*) &query); group_query = apr_hash_get(group_cfg->queries, label, APR_HASH_KEY_STRING); if (!group_query) { dbd_query_t *add_query = apr_array_push(add_queries); add_query->label = label; add_query->query = query; } else if (strcmp(query, group_query)) { group_ok = 0; break; } } if (group_ok) { int i; for (i = 0; i < add_queries->nelts; ++i) { dbd_query_t *add_query = ((dbd_query_t*) add_queries->elts) + i; apr_hash_set(group_cfg->queries, add_query->label, APR_HASH_KEY_STRING, add_query->query); } svr->group = group; break; } } if (!svr->group) { svr->group = group = apr_pcalloc(pconf, sizeof(dbd_group_t)); group->cfg = cfg; group->next = group_list; group_list = group; } } return OK; } static apr_status_t dbd_prepared_init(apr_pool_t *pool, dbd_cfg_t *cfg, ap_dbd_t *rec) { apr_hash_index_t *hi; apr_status_t rv = APR_SUCCESS; rec->prepared = apr_hash_make(pool); for (hi = apr_hash_first(pool, cfg->queries); hi; hi = apr_hash_next(hi)) { const char *label, *query; apr_dbd_prepared_t *stmt; apr_hash_this(hi, (void*) &label, NULL, (void*) &query); if (!strcmp(query, "")) { continue; } stmt = NULL; if (apr_dbd_prepare(rec->driver, pool, rec->handle, query, label, &stmt)) { rv = APR_EGENERAL; } else { apr_hash_set(rec->prepared, label, APR_HASH_KEY_STRING, stmt); } } return rv; } static apr_status_t dbd_close(void *data) { ap_dbd_t *rec = data; return apr_dbd_close(rec->driver, rec->handle); } #if APR_HAS_THREADS static apr_status_t dbd_destruct(void *data, void *params, apr_pool_t *pool) { dbd_group_t *group = params; if (!group->destroyed) { ap_dbd_t *rec = data; apr_pool_destroy(rec->pool); } return APR_SUCCESS; } #endif /* an apr_reslist_constructor for SQL connections * Also use this for opening in non-reslist modes, since it gives * us all the error-handling in one place. */ static apr_status_t dbd_construct(void **data_ptr, void *params, apr_pool_t *pool) { dbd_group_t *group = params; dbd_cfg_t *cfg = group->cfg; apr_pool_t *rec_pool, *prepared_pool; ap_dbd_t *rec; apr_status_t rv; rv = apr_pool_create(&rec_pool, pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, cfg->server, "DBD: Failed to create memory pool"); return rv; } rec = apr_pcalloc(rec_pool, sizeof(ap_dbd_t)); rec->pool = rec_pool; /* The driver is loaded at config time now, so this just checks a hash. * If that changes, the driver DSO could be registered to unload against * our pool, which is probably not what we want. Error checking isn't * necessary now, but in case that changes in the future ... */ rv = apr_dbd_get_driver(rec->pool, cfg->name, &rec->driver); if (rv != APR_SUCCESS) { switch (rv) { case APR_ENOTIMPL: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: driver for %s not available", cfg->name); break; case APR_EDSOOPEN: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: can't find driver for %s", cfg->name); break; case APR_ESYMNOTFOUND: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: driver for %s is invalid or corrupted", cfg->name); break; default: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: mod_dbd not compatible with APR in get_driver"); break; } apr_pool_destroy(rec->pool); return rv; } rv = apr_dbd_open(rec->driver, rec->pool, cfg->params, &rec->handle); if (rv != APR_SUCCESS) { switch (rv) { case APR_EGENERAL: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: Can't connect to %s", cfg->name); break; default: ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: mod_dbd not compatible with APR in open"); break; } apr_pool_destroy(rec->pool); return rv; } apr_pool_cleanup_register(rec->pool, rec, dbd_close, apr_pool_cleanup_null); /* we use a sub-pool for the prepared statements for each connection so * that they will be cleaned up first, before the connection is closed */ rv = apr_pool_create(&prepared_pool, rec->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv, cfg->server, "DBD: Failed to create memory pool"); apr_pool_destroy(rec->pool); return rv; } rv = dbd_prepared_init(prepared_pool, cfg, rec); if (rv != APR_SUCCESS) { const char *errmsg = apr_dbd_error(rec->driver, rec->handle, rv); ap_log_error(APLOG_MARK, APLOG_ERR, rv, cfg->server, "DBD: failed to prepare SQL statements: %s", (errmsg ? errmsg : "[???]")); apr_pool_destroy(rec->pool); return rv; } *data_ptr = rec; return APR_SUCCESS; } #if APR_HAS_THREADS static apr_status_t dbd_destroy(void *data) { dbd_group_t *group = data; group->destroyed = 1; return APR_SUCCESS; } static apr_status_t dbd_setup(server_rec *s, dbd_group_t *group) { dbd_cfg_t *cfg = group->cfg; apr_status_t rv; /* We create the reslist using a sub-pool of the pool passed to our * child_init hook. No other threads can be here because we're * either in the child_init phase or dbd_setup_lock() acquired our mutex. * No other threads will use this sub-pool after this, except via * reslist calls, which have an internal mutex. * * We need to short-circuit the cleanup registered internally by * apr_reslist_create(). We do this by registering dbd_destroy() * as a cleanup afterwards, so that it will run before the reslist's * internal cleanup. * * If we didn't do this, then we could free memory twice when the pool * was destroyed. When apr_pool_destroy() runs, it first destroys all * all the per-connection sub-pools created in dbd_construct(), and * then it runs the reslist's cleanup. The cleanup calls dbd_destruct() * on each resource, which would then attempt to destroy the sub-pools * a second time. */ rv = apr_reslist_create(&group->reslist, cfg->nmin, cfg->nkeep, cfg->nmax, apr_time_from_sec(cfg->exptime), dbd_construct, dbd_destruct, group, group->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "DBD: failed to initialise"); return rv; } apr_pool_cleanup_register(group->pool, group, dbd_destroy, apr_pool_cleanup_null); return APR_SUCCESS; } #endif static apr_status_t dbd_setup_init(apr_pool_t *pool, server_rec *s) { dbd_group_t *group; apr_status_t rv = APR_SUCCESS; for (group = group_list; group; group = group->next) { apr_status_t rv2; rv2 = apr_pool_create(&group->pool, pool); if (rv2 != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv2, s, "DBD: Failed to create reslist cleanup memory pool"); return rv2; } #if APR_HAS_THREADS rv2 = dbd_setup(s, group); if (rv2 == APR_SUCCESS) { continue; } else if (rv == APR_SUCCESS) { rv = rv2; } /* we failed, so create a mutex so that subsequent competing callers * to ap_dbd_open can serialize themselves while they retry */ rv2 = apr_thread_mutex_create(&group->mutex, APR_THREAD_MUTEX_DEFAULT, pool); if (rv2 != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_CRIT, rv2, s, "DBD: Failed to create thread mutex"); return rv2; } #endif } return rv; } #if APR_HAS_THREADS static apr_status_t dbd_setup_lock(server_rec *s, dbd_group_t *group) { apr_status_t rv = APR_SUCCESS, rv2; /* several threads could be here at the same time, all trying to * initialize the reslist because dbd_setup_init failed to do so */ if (!group->mutex) { /* we already logged an error when the mutex couldn't be created */ return APR_EGENERAL; } rv2 = apr_thread_mutex_lock(group->mutex); if (rv2 != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv2, s, "DBD: Failed to acquire thread mutex"); return rv2; } if (!group->reslist) { rv = dbd_setup(s, group); } rv2 = apr_thread_mutex_unlock(group->mutex); if (rv2 != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv2, s, "DBD: Failed to release thread mutex"); if (rv == APR_SUCCESS) { rv = rv2; } } return rv; } #endif /* Functions we export for modules to use: - open acquires a connection from the pool (opens one if necessary) - close releases it back in to the pool */ DBD_DECLARE_NONSTD(void) ap_dbd_close(server_rec *s, ap_dbd_t *rec) { svr_cfg *svr = ap_get_module_config(s->module_config, &dbd_module); if (!svr->cfg->persist) { apr_pool_destroy(rec->pool); } #if APR_HAS_THREADS else { apr_reslist_release(svr->group->reslist, rec); } #endif } static apr_status_t dbd_check(apr_pool_t *pool, server_rec *s, ap_dbd_t *rec) { svr_cfg *svr; apr_status_t rv = apr_dbd_check_conn(rec->driver, pool, rec->handle); const char *errmsg; if ((rv == APR_SUCCESS) || (rv == APR_ENOTIMPL)) { return APR_SUCCESS; } errmsg = apr_dbd_error(rec->driver, rec->handle, rv); if (!errmsg) { errmsg = "(unknown)"; } svr = ap_get_module_config(s->module_config, &dbd_module); ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "DBD [%s] Error: %s", svr->cfg->name, errmsg); return rv; } DBD_DECLARE_NONSTD(ap_dbd_t*) ap_dbd_open(apr_pool_t *pool, server_rec *s) { svr_cfg *svr = ap_get_module_config(s->module_config, &dbd_module); dbd_group_t *group = svr->group; dbd_cfg_t *cfg = svr->cfg; ap_dbd_t *rec = NULL; #if APR_HAS_THREADS apr_status_t rv; #endif /* If nothing is configured, we shouldn't be here */ if (cfg->name == no_dbdriver) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "DBD: not configured"); return NULL; } if (!cfg->persist) { /* Return a once-only connection */ group = apr_pcalloc(pool, sizeof(dbd_group_t)); group->cfg = cfg; dbd_construct((void*) &rec, group, pool); return rec; } #if APR_HAS_THREADS if (!group->reslist) { if (dbd_setup_lock(s, group) != APR_SUCCESS) { return NULL; } } rv = apr_reslist_acquire(group->reslist, (void*) &rec); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "Failed to acquire DBD connection from pool!"); return NULL; } if (dbd_check(pool, s, rec) != APR_SUCCESS) { apr_reslist_invalidate(group->reslist, rec); return NULL; } #else /* If we have a persistent connection and it's good, we'll use it; * since this is non-threaded, we can update without a mutex */ rec = group->rec; if (rec) { if (dbd_check(pool, s, rec) != APR_SUCCESS) { apr_pool_destroy(rec->pool); rec = NULL; } } /* We don't have a connection right now, so we'll open one */ if (!rec) { dbd_construct((void*) &rec, group, group->pool); group->rec = rec; } #endif return rec; } #if APR_HAS_THREADS typedef struct { ap_dbd_t *rec; apr_reslist_t *reslist; } dbd_acquire_t; static apr_status_t dbd_release(void *data) { dbd_acquire_t *acq = data; apr_reslist_release(acq->reslist, acq->rec); return APR_SUCCESS; } DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_acquire(request_rec *r) { dbd_acquire_t *acq; while (!ap_is_initial_req(r)) { if (r->prev) { r = r->prev; } else if (r->main) { r = r->main; } } acq = ap_get_module_config(r->request_config, &dbd_module); if (!acq) { acq = apr_palloc(r->pool, sizeof(dbd_acquire_t)); acq->rec = ap_dbd_open(r->pool, r->server); if (acq->rec) { svr_cfg *svr = ap_get_module_config(r->server->module_config, &dbd_module); ap_set_module_config(r->request_config, &dbd_module, acq); if (svr->cfg->persist) { acq->reslist = svr->group->reslist; apr_pool_cleanup_register(r->pool, acq, dbd_release, apr_pool_cleanup_null); } } } return acq->rec; } DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_cacquire(conn_rec *c) { dbd_acquire_t *acq = ap_get_module_config(c->conn_config, &dbd_module); if (!acq) { acq = apr_palloc(c->pool, sizeof(dbd_acquire_t)); acq->rec = ap_dbd_open(c->pool, c->base_server); if (acq->rec) { svr_cfg *svr = ap_get_module_config(c->base_server->module_config, &dbd_module); ap_set_module_config(c->conn_config, &dbd_module, acq); if (svr->cfg->persist) { acq->reslist = svr->group->reslist; apr_pool_cleanup_register(c->pool, acq, dbd_release, apr_pool_cleanup_null); } } } return acq->rec; } #else DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_acquire(request_rec *r) { ap_dbd_t *rec; while (!ap_is_initial_req(r)) { if (r->prev) { r = r->prev; } else if (r->main) { r = r->main; } } rec = ap_get_module_config(r->request_config, &dbd_module); if (!rec) { rec = ap_dbd_open(r->pool, r->server); if (rec) { ap_set_module_config(r->request_config, &dbd_module, rec); } } return rec; } DBD_DECLARE_NONSTD(ap_dbd_t *) ap_dbd_cacquire(conn_rec *c) { ap_dbd_t *rec = ap_get_module_config(c->conn_config, &dbd_module); if (!rec) { rec = ap_dbd_open(c->pool, c->base_server); if (rec) { ap_set_module_config(c->conn_config, &dbd_module, rec); } } return rec; } #endif static void dbd_hooks(apr_pool_t *pool) { ap_hook_pre_config(dbd_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(dbd_post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init((void*)dbd_setup_init, NULL, NULL, APR_HOOK_MIDDLE); APR_REGISTER_OPTIONAL_FN(ap_dbd_prepare); APR_REGISTER_OPTIONAL_FN(ap_dbd_open); APR_REGISTER_OPTIONAL_FN(ap_dbd_close); APR_REGISTER_OPTIONAL_FN(ap_dbd_acquire); APR_REGISTER_OPTIONAL_FN(ap_dbd_cacquire); apr_dbd_init(pool); } module AP_MODULE_DECLARE_DATA dbd_module = { STANDARD20_MODULE_STUFF, NULL, NULL, create_dbd_config, merge_dbd_config, dbd_cmds, dbd_hooks };
.htaccess Tutorial
Find information you are looking for on the AskApache Home Page.