1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* Watchdog module. 18 */ 19 20#include "apr.h" 21#include "mod_watchdog.h" 22#include "ap_provider.h" 23#include "ap_mpm.h" 24#include "http_core.h" 25#include "util_mutex.h" 26 27#define AP_WATCHDOG_PGROUP "watchdog" 28#define AP_WATCHDOG_PVERSION "parent" 29#define AP_WATCHDOG_CVERSION "child" 30 31typedef struct watchdog_list_t watchdog_list_t; 32 33struct watchdog_list_t 34{ 35 struct watchdog_list_t *next; 36 ap_watchdog_t *wd; 37 apr_status_t status; 38 apr_interval_time_t interval; 39 apr_interval_time_t step; 40 const void *data; 41 ap_watchdog_callback_fn_t *callback_fn; 42}; 43 44struct ap_watchdog_t 45{ 46 apr_thread_mutex_t *startup; 47 apr_proc_mutex_t *mutex; 48 const char *name; 49 watchdog_list_t *callbacks; 50 int is_running; 51 int singleton; 52 int active; 53 apr_interval_time_t step; 54 apr_thread_t *thread; 55 apr_pool_t *pool; 56}; 57 58typedef struct wd_server_conf_t wd_server_conf_t; 59struct wd_server_conf_t 60{ 61 int child_workers; 62 int parent_workers; 63 apr_pool_t *pool; 64 server_rec *s; 65}; 66 67static wd_server_conf_t *wd_server_conf = NULL; 68static apr_interval_time_t wd_interval = AP_WD_TM_INTERVAL; 69static int wd_interval_set = 0; 70static int mpm_is_forked = AP_MPMQ_NOT_SUPPORTED; 71static const char *wd_proc_mutex_type = "watchdog-callback"; 72 73static apr_status_t wd_worker_cleanup(void *data) 74{ 75 apr_status_t rv; 76 ap_watchdog_t *w = (ap_watchdog_t *)data; 77 78 if (w->is_running) { 79 watchdog_list_t *wl = w->callbacks; 80 while (wl) { 81 if (wl->status == APR_SUCCESS) { 82 /* Execute watchdog callback with STOPPING state */ 83 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING, 84 (void *)wl->data, w->pool); 85 wl->status = APR_EOF; 86 } 87 wl = wl->next; 88 } 89 } 90 w->is_running = 0; 91 apr_thread_join(&rv, w->thread); 92 return rv; 93} 94 95/*--------------------------------------------------------------------------*/ 96/* */ 97/* Main watchdog worker thread. */ 98/* For singleton workers child thread that first obtains the process */ 99/* mutex is running. Threads in other child's are locked on mutex. */ 100/* */ 101/*--------------------------------------------------------------------------*/ 102static void* APR_THREAD_FUNC wd_worker(apr_thread_t *thread, void *data) 103{ 104 ap_watchdog_t *w = (ap_watchdog_t *)data; 105 apr_status_t rv; 106 int locked = 0; 107 int probed = 0; 108 int inited = 0; 109 int mpmq_s = 0; 110 111 w->pool = apr_thread_pool_get(thread); 112 w->is_running = 1; 113 114 apr_thread_mutex_unlock(w->startup); 115 if (w->mutex) { 116 while (w->is_running) { 117 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) { 118 w->is_running = 0; 119 break; 120 } 121 if (mpmq_s == AP_MPMQ_STOPPING) { 122 w->is_running = 0; 123 break; 124 } 125 rv = apr_proc_mutex_trylock(w->mutex); 126 if (rv == APR_SUCCESS) { 127 if (probed) { 128 /* Sleep after we were locked 129 * up to 1 second. Httpd can be 130 * in the middle of shutdown, and 131 * our child didn't yet received 132 * the shutdown signal. 133 */ 134 probed = 10; 135 while (w->is_running && probed > 0) { 136 apr_sleep(AP_WD_TM_INTERVAL); 137 probed--; 138 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) { 139 w->is_running = 0; 140 break; 141 } 142 if (mpmq_s == AP_MPMQ_STOPPING) { 143 w->is_running = 0; 144 break; 145 } 146 } 147 } 148 locked = 1; 149 break; 150 } 151 probed = 1; 152 apr_sleep(AP_WD_TM_SLICE); 153 } 154 } 155 if (w->is_running) { 156 watchdog_list_t *wl = w->callbacks; 157 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s, 158 "%sWatchdog (%s) running", 159 w->singleton ? "Singleton" : "", w->name); 160 apr_time_clock_hires(w->pool); 161 if (wl) { 162 apr_pool_t *ctx = NULL; 163 apr_pool_create(&ctx, w->pool); 164 while (wl && w->is_running) { 165 /* Execute watchdog callback */ 166 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_STARTING, 167 (void *)wl->data, ctx); 168 wl = wl->next; 169 } 170 apr_pool_destroy(ctx); 171 } 172 else { 173 ap_run_watchdog_init(wd_server_conf->s, w->name, w->pool); 174 inited = 1; 175 } 176 } 177 178 /* Main execution loop */ 179 while (w->is_running) { 180 apr_pool_t *ctx = NULL; 181 apr_time_t curr; 182 watchdog_list_t *wl = w->callbacks; 183 184 apr_sleep(AP_WD_TM_SLICE); 185 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) { 186 w->is_running = 0; 187 } 188 if (mpmq_s == AP_MPMQ_STOPPING) { 189 w->is_running = 0; 190 } 191 if (!w->is_running) { 192 break; 193 } 194 curr = apr_time_now() - AP_WD_TM_SLICE; 195 while (wl && w->is_running) { 196 if (wl->status == APR_SUCCESS) { 197 wl->step += (apr_time_now() - curr); 198 if (wl->step >= wl->interval) { 199 if (!ctx) 200 apr_pool_create(&ctx, w->pool); 201 wl->step = 0; 202 /* Execute watchdog callback */ 203 wl->status = (*wl->callback_fn)(AP_WATCHDOG_STATE_RUNNING, 204 (void *)wl->data, ctx); 205 if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpmq_s) != APR_SUCCESS) { 206 w->is_running = 0; 207 } 208 if (mpmq_s == AP_MPMQ_STOPPING) { 209 w->is_running = 0; 210 } 211 } 212 } 213 wl = wl->next; 214 } 215 if (w->is_running && w->callbacks == NULL) { 216 /* This is hook mode watchdog 217 * running on WatchogInterval 218 */ 219 w->step += (apr_time_now() - curr); 220 if (w->step >= wd_interval) { 221 if (!ctx) 222 apr_pool_create(&ctx, w->pool); 223 w->step = 0; 224 /* Run watchdog step hook */ 225 ap_run_watchdog_step(wd_server_conf->s, w->name, ctx); 226 } 227 } 228 if (ctx) 229 apr_pool_destroy(ctx); 230 if (!w->is_running) { 231 break; 232 } 233 } 234 if (inited) { 235 /* Run the watchdog exit hooks. 236 * If this was singleton watchdog the init hook 237 * might never been called, so skip the exit hook 238 * in that case as well. 239 */ 240 ap_run_watchdog_exit(wd_server_conf->s, w->name, w->pool); 241 } 242 else { 243 watchdog_list_t *wl = w->callbacks; 244 while (wl) { 245 if (wl->status == APR_SUCCESS) { 246 /* Execute watchdog callback with STOPPING state */ 247 (*wl->callback_fn)(AP_WATCHDOG_STATE_STOPPING, 248 (void *)wl->data, w->pool); 249 } 250 wl = wl->next; 251 } 252 } 253 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd_server_conf->s, 254 "%sWatchdog (%s) stopping", 255 w->singleton ? "Singleton" : "", w->name); 256 257 if (locked) 258 apr_proc_mutex_unlock(w->mutex); 259 apr_thread_exit(w->thread, APR_SUCCESS); 260 261 return NULL; 262} 263 264static apr_status_t wd_startup(ap_watchdog_t *w, apr_pool_t *p) 265{ 266 apr_status_t rc; 267 268 /* Create thread startup mutex */ 269 rc = apr_thread_mutex_create(&w->startup, APR_THREAD_MUTEX_UNNESTED, p); 270 if (rc != APR_SUCCESS) 271 return rc; 272 273 if (w->singleton) { 274 /* Initialize singleton mutex in child */ 275 rc = apr_proc_mutex_child_init(&w->mutex, 276 apr_proc_mutex_lockfile(w->mutex), p); 277 if (rc != APR_SUCCESS) 278 return rc; 279 } 280 281 /* This mutex fixes problems with a fast start/fast end, where the pool 282 * cleanup was being invoked before the thread completely spawned. 283 */ 284 apr_thread_mutex_lock(w->startup); 285 apr_pool_pre_cleanup_register(p, w, wd_worker_cleanup); 286 287 /* Start the newly created watchdog */ 288 rc = apr_thread_create(&w->thread, NULL, wd_worker, w, p); 289 if (rc) { 290 apr_pool_cleanup_kill(p, w, wd_worker_cleanup); 291 } 292 293 apr_thread_mutex_lock(w->startup); 294 apr_thread_mutex_unlock(w->startup); 295 apr_thread_mutex_destroy(w->startup); 296 297 return rc; 298} 299 300static apr_status_t ap_watchdog_get_instance(ap_watchdog_t **watchdog, 301 const char *name, 302 int parent, 303 int singleton, 304 apr_pool_t *p) 305{ 306 ap_watchdog_t *w; 307 const char *pver = parent ? AP_WATCHDOG_PVERSION : AP_WATCHDOG_CVERSION; 308 309 if (parent && mpm_is_forked != AP_MPMQ_NOT_SUPPORTED) { 310 /* Parent threads are not supported for 311 * forked mpm's 312 */ 313 *watchdog = NULL; 314 return APR_ENOTIMPL; 315 } 316 w = ap_lookup_provider(AP_WATCHDOG_PGROUP, name, pver); 317 if (w) { 318 *watchdog = w; 319 return APR_SUCCESS; 320 } 321 w = apr_pcalloc(p, sizeof(ap_watchdog_t)); 322 w->name = name; 323 w->pool = p; 324 w->singleton = parent ? 0 : singleton; 325 *watchdog = w; 326 return ap_register_provider(p, AP_WATCHDOG_PGROUP, name, 327 pver, *watchdog); 328} 329 330static apr_status_t ap_watchdog_set_callback_interval(ap_watchdog_t *w, 331 apr_interval_time_t interval, 332 const void *data, 333 ap_watchdog_callback_fn_t *callback) 334{ 335 watchdog_list_t *c = w->callbacks; 336 apr_status_t rv = APR_EOF; 337 338 while (c) { 339 if (c->data == data && c->callback_fn == callback) { 340 /* We have existing callback. 341 * Update the interval and reset status, so the 342 * callback and continue execution if stopped earlier. 343 */ 344 c->interval = interval; 345 c->step = 0; 346 c->status = APR_SUCCESS; 347 rv = APR_SUCCESS; 348 break; 349 } 350 c = c->next; 351 } 352 return rv; 353} 354 355static apr_status_t ap_watchdog_register_callback(ap_watchdog_t *w, 356 apr_interval_time_t interval, 357 const void *data, 358 ap_watchdog_callback_fn_t *callback) 359{ 360 watchdog_list_t *c = w->callbacks; 361 362 while (c) { 363 if (c->data == data && c->callback_fn == callback) { 364 /* We have already registered callback. 365 * Do not allow callbacks that have the same 366 * function and data pointers. 367 */ 368 return APR_EEXIST; 369 } 370 c = c->next; 371 } 372 c = apr_palloc(w->pool, sizeof(watchdog_list_t)); 373 c->data = data; 374 c->callback_fn = callback; 375 c->interval = interval; 376 c->step = 0; 377 c->status = APR_EINIT; 378 379 c->wd = w; 380 c->next = w->callbacks; 381 w->callbacks = c; 382 w->active++; 383 384 return APR_SUCCESS; 385} 386 387/*--------------------------------------------------------------------------*/ 388/* */ 389/* Pre config hook. */ 390/* Create default watchdogs for parent and child */ 391/* Parent watchdog executes inside parent process so it doesn't need the */ 392/* singleton mutex */ 393/* */ 394/*--------------------------------------------------------------------------*/ 395static int wd_pre_config_hook(apr_pool_t *pconf, apr_pool_t *plog, 396 apr_pool_t *ptemp) 397{ 398 apr_status_t rv; 399 ap_watchdog_t *w; 400 401 ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_is_forked); 402 if ((rv = ap_watchdog_get_instance(&w, 403 AP_WATCHDOG_SINGLETON, 0, 1, pconf)) != APR_SUCCESS) { 404 return rv; 405 } 406 if ((rv = ap_watchdog_get_instance(&w, 407 AP_WATCHDOG_DEFAULT, 0, 0, pconf)) != APR_SUCCESS) { 408 return rv; 409 } 410 if (mpm_is_forked == AP_MPMQ_NOT_SUPPORTED) { 411 /* Create parent process watchdog for 412 * non forked mpm's only. 413 */ 414 if ((rv = ap_watchdog_get_instance(&w, 415 AP_WATCHDOG_DEFAULT, 1, 0, pconf)) != APR_SUCCESS) { 416 return rv; 417 } 418 } 419 420 if ((rv = ap_mutex_register(pconf, wd_proc_mutex_type, NULL, 421 APR_LOCK_DEFAULT, 0)) != APR_SUCCESS) { 422 return rv; 423 } 424 425 return OK; 426} 427 428/*--------------------------------------------------------------------------*/ 429/* */ 430/* Post config hook. */ 431/* Create watchdog thread in parent and initializes Watchdog module */ 432/* */ 433/*--------------------------------------------------------------------------*/ 434static int wd_post_config_hook(apr_pool_t *pconf, apr_pool_t *plog, 435 apr_pool_t *ptemp, server_rec *s) 436{ 437 apr_status_t rv; 438 const char *pk = "watchdog_init_module_tag"; 439 apr_pool_t *pproc = s->process->pool; 440 const apr_array_header_t *wl; 441 442 if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) 443 /* First time config phase -- skip. */ 444 return OK; 445 446 apr_pool_userdata_get((void *)&wd_server_conf, pk, pproc); 447 if (!wd_server_conf) { 448 if (!(wd_server_conf = apr_pcalloc(pproc, sizeof(wd_server_conf_t)))) 449 return APR_ENOMEM; 450 apr_pool_create(&wd_server_conf->pool, pproc); 451 apr_pool_userdata_set(wd_server_conf, pk, apr_pool_cleanup_null, pproc); 452 } 453 wd_server_conf->s = s; 454 if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP, 455 AP_WATCHDOG_PVERSION))) { 456 const ap_list_provider_names_t *wn; 457 int i; 458 459 wn = (ap_list_provider_names_t *)wl->elts; 460 for (i = 0; i < wl->nelts; i++) { 461 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP, 462 wn[i].provider_name, 463 AP_WATCHDOG_PVERSION); 464 if (w) { 465 if (!w->active) { 466 int status = ap_run_watchdog_need(s, w->name, 1, 467 w->singleton); 468 if (status == OK) { 469 /* One of the modules returned OK to this watchog. 470 * Mark it as active 471 */ 472 w->active = 1; 473 } 474 } 475 if (w->active) { 476 /* We have active watchdog. 477 * Create the watchdog thread 478 */ 479 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) { 480 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01571) 481 "Watchdog: Failed to create parent worker thread."); 482 return rv; 483 } 484 wd_server_conf->parent_workers++; 485 } 486 } 487 } 488 } 489 if (wd_server_conf->parent_workers) { 490 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(01572) 491 "Spawned %d parent worker threads.", 492 wd_server_conf->parent_workers); 493 } 494 if ((wl = ap_list_provider_names(pconf, AP_WATCHDOG_PGROUP, 495 AP_WATCHDOG_CVERSION))) { 496 const ap_list_provider_names_t *wn; 497 int i; 498 499 wn = (ap_list_provider_names_t *)wl->elts; 500 for (i = 0; i < wl->nelts; i++) { 501 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP, 502 wn[i].provider_name, 503 AP_WATCHDOG_CVERSION); 504 if (w) { 505 if (!w->active) { 506 int status = ap_run_watchdog_need(s, w->name, 0, 507 w->singleton); 508 if (status == OK) { 509 /* One of the modules returned OK to this watchog. 510 * Mark it as active 511 */ 512 w->active = 1; 513 } 514 } 515 if (w->active) { 516 /* We have some callbacks registered. 517 * Create mutexes for singleton watchdogs 518 */ 519 if (w->singleton) { 520 rv = ap_proc_mutex_create(&w->mutex, NULL, wd_proc_mutex_type, 521 w->name, s, 522 wd_server_conf->pool, 0); 523 if (rv != APR_SUCCESS) { 524 return rv; 525 } 526 } 527 wd_server_conf->child_workers++; 528 } 529 } 530 } 531 } 532 return OK; 533} 534 535/*--------------------------------------------------------------------------*/ 536/* */ 537/* Child init hook. */ 538/* Create watchdog threads and initializes Mutexes in child */ 539/* */ 540/*--------------------------------------------------------------------------*/ 541static void wd_child_init_hook(apr_pool_t *p, server_rec *s) 542{ 543 apr_status_t rv; 544 const apr_array_header_t *wl; 545 546 if (!wd_server_conf->child_workers) { 547 /* We don't have anything configured, bail out. 548 */ 549 return; 550 } 551 if ((wl = ap_list_provider_names(p, AP_WATCHDOG_PGROUP, 552 AP_WATCHDOG_CVERSION))) { 553 const ap_list_provider_names_t *wn; 554 int i; 555 wn = (ap_list_provider_names_t *)wl->elts; 556 for (i = 0; i < wl->nelts; i++) { 557 ap_watchdog_t *w = ap_lookup_provider(AP_WATCHDOG_PGROUP, 558 wn[i].provider_name, 559 AP_WATCHDOG_CVERSION); 560 if (w && w->active) { 561 /* We have some callbacks registered. 562 * Kick of the watchdog 563 */ 564 if ((rv = wd_startup(w, wd_server_conf->pool)) != APR_SUCCESS) { 565 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(01573) 566 "Watchdog: Failed to create worker thread."); 567 /* No point to continue */ 568 return; 569 } 570 } 571 } 572 } 573} 574 575/*--------------------------------------------------------------------------*/ 576/* */ 577/* WatchdogInterval directive */ 578/* */ 579/*--------------------------------------------------------------------------*/ 580static const char *wd_cmd_watchdog_int(cmd_parms *cmd, void *dummy, 581 const char *arg) 582{ 583 int i; 584 const char *errs = ap_check_cmd_context(cmd, GLOBAL_ONLY); 585 586 if (errs != NULL) 587 return errs; 588 if (wd_interval_set) 589 return "Duplicate WatchdogInterval directives are not allowed"; 590 if ((i = atoi(arg)) < 1) 591 return "Invalid WatchdogInterval value"; 592 593 wd_interval = apr_time_from_sec(i); 594 wd_interval_set = 1; 595 return NULL; 596} 597 598/*--------------------------------------------------------------------------*/ 599/* */ 600/* List of directives specific to our module. */ 601/* */ 602/*--------------------------------------------------------------------------*/ 603static const command_rec wd_directives[] = 604{ 605 AP_INIT_TAKE1( 606 "WatchdogInterval", /* directive name */ 607 wd_cmd_watchdog_int, /* config action routine */ 608 NULL, /* argument to include in call */ 609 RSRC_CONF, /* where available */ 610 "Watchdog interval in seconds" 611 ), 612 {NULL} 613}; 614 615/*--------------------------------------------------------------------------*/ 616/* */ 617/* Which functions are responsible for which hooks in the server. */ 618/* */ 619/*--------------------------------------------------------------------------*/ 620static void wd_register_hooks(apr_pool_t *p) 621{ 622 623 /* Only the mpm_winnt has child init hook handler. 624 * Make sure that we are called after the mpm child init handler 625 * initializes. 626 */ 627 static const char *const after_mpm[] = { "mpm_winnt.c", NULL}; 628 629 /* Pre config handling 630 */ 631 ap_hook_pre_config(wd_pre_config_hook, 632 NULL, 633 NULL, 634 APR_HOOK_FIRST); 635 636 /* Post config handling 637 */ 638 ap_hook_post_config(wd_post_config_hook, 639 NULL, 640 NULL, 641 APR_HOOK_LAST); 642 643 /* Child init hook 644 */ 645 ap_hook_child_init(wd_child_init_hook, 646 after_mpm, 647 NULL, 648 APR_HOOK_MIDDLE); 649 650 APR_REGISTER_OPTIONAL_FN(ap_watchdog_get_instance); 651 APR_REGISTER_OPTIONAL_FN(ap_watchdog_register_callback); 652 APR_REGISTER_OPTIONAL_FN(ap_watchdog_set_callback_interval); 653} 654 655/*--------------------------------------------------------------------------*/ 656/* */ 657/* The list of callback routines and data structures that provide */ 658/* the static hooks into our module from the other parts of the server. */ 659/* */ 660/*--------------------------------------------------------------------------*/ 661AP_DECLARE_MODULE(watchdog) = { 662 STANDARD20_MODULE_STUFF, 663 NULL, /* create per-directory config structure */ 664 NULL, /* merge per-directory config structures */ 665 NULL, /* create per-server config structure */ 666 NULL, /* merge per-server config structures */ 667 wd_directives, /* command apr_table_t */ 668 wd_register_hooks /* register hooks */ 669}; 670 671/*--------------------------------------------------------------------------*/ 672/* */ 673/* The list of optional hooks that we provide */ 674/* */ 675/*--------------------------------------------------------------------------*/ 676APR_HOOK_STRUCT( 677 APR_HOOK_LINK(watchdog_need) 678 APR_HOOK_LINK(watchdog_init) 679 APR_HOOK_LINK(watchdog_exit) 680 APR_HOOK_LINK(watchdog_step) 681) 682 683APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, AP_WD, int, watchdog_need, 684 (server_rec *s, const char *name, 685 int parent, int singleton), 686 (s, name, parent, singleton), 687 DECLINED) 688APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_init, 689 (server_rec *s, const char *name, 690 apr_pool_t *pool), 691 (s, name, pool), 692 OK, DECLINED) 693APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_exit, 694 (server_rec *s, const char *name, 695 apr_pool_t *pool), 696 (s, name, pool), 697 OK, DECLINED) 698APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, AP_WD, int, watchdog_step, 699 (server_rec *s, const char *name, 700 apr_pool_t *pool), 701 (s, name, pool), 702 OK, DECLINED) 703