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/* 18 * httpd.c: simple http daemon for answering WWW file requests 19 * 20 * 21 * 03-21-93 Rob McCool wrote original code (up to NCSA HTTPd 1.3) 22 * 23 * 03-06-95 blong 24 * changed server number for child-alone processes to 0 and changed name 25 * of processes 26 * 27 * 03-10-95 blong 28 * Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) 29 * including set group before fork, and call gettime before to fork 30 * to set up libraries. 31 * 32 * 04-14-95 rst / rh 33 * Brandon's code snarfed from NCSA 1.4, but tinkered to work with the 34 * Apache server, and also to have child processes do accept() directly. 35 * 36 * April-July '95 rst 37 * Extensive rework for Apache. 38 */ 39 40#include "apr.h" 41#include "apr_portable.h" 42#include "apr_strings.h" 43#include "apr_thread_proc.h" 44#include "apr_signal.h" 45#include "apr_tables.h" 46#include "apr_getopt.h" 47#include "apr_thread_mutex.h" 48 49#define APR_WANT_STDIO 50#define APR_WANT_STRFUNC 51#include "apr_want.h" 52 53#if APR_HAVE_UNISTD_H 54#include <unistd.h> 55#endif 56#if APR_HAVE_SYS_TYPES_H 57#include <sys/types.h> 58#endif 59 60#ifndef USE_WINSOCK 61#include <sys/select.h> 62#endif 63 64#include "ap_config.h" 65#include "httpd.h" 66#include "mpm_default.h" 67#include "http_main.h" 68#include "http_log.h" 69#include "http_config.h" 70#include "http_core.h" /* for get_remote_host */ 71#include "http_connection.h" 72#include "scoreboard.h" 73#include "ap_mpm.h" 74#include "mpm_common.h" 75#include "ap_listen.h" 76#include "ap_mmn.h" 77 78#ifdef HAVE_TIME_H 79#include <time.h> 80#endif 81 82#include <signal.h> 83 84#include <netware.h> 85#include <nks/netware.h> 86#include <library.h> 87#include <screen.h> 88 89int nlmUnloadSignaled(int wait); 90 91/* Limit on the total --- clients will be locked out if more servers than 92 * this are needed. It is intended solely to keep the server from crashing 93 * when things get out of hand. 94 * 95 * We keep a hard maximum number of servers, for two reasons --- first off, 96 * in case something goes seriously wrong, we want to stop the fork bomb 97 * short of actually crashing the machine we're running on by filling some 98 * kernel table. Secondly, it keeps the size of the scoreboard file small 99 * enough that we can read the whole thing without worrying too much about 100 * the overhead. 101 */ 102#ifndef HARD_SERVER_LIMIT 103#define HARD_SERVER_LIMIT 1 104#endif 105 106#define WORKER_DEAD SERVER_DEAD 107#define WORKER_STARTING SERVER_STARTING 108#define WORKER_READY SERVER_READY 109#define WORKER_IDLE_KILL SERVER_IDLE_KILL 110 111#define MPM_HARD_LIMITS_FILE "/mpm_default.h" 112 113/* *Non*-shared http_main globals... */ 114 115static int ap_threads_per_child=0; /* Worker threads per child */ 116static int ap_threads_to_start=0; 117static int ap_threads_min_free=0; 118static int ap_threads_max_free=0; 119static int ap_threads_limit=0; 120static int mpm_state = AP_MPMQ_STARTING; 121 122/* 123 * The max child slot ever assigned, preserved across restarts. Necessary 124 * to deal with MaxRequestWorkers changes across SIGWINCH restarts. We use this 125 * value to optimize routines that have to scan the entire scoreboard. 126 */ 127static int ap_max_workers_limit = -1; 128 129int hold_screen_on_exit = 0; /* Indicates whether the screen should be held open */ 130 131static fd_set listenfds; 132static int listenmaxfd; 133 134static apr_pool_t *pconf; /* Pool for config stuff */ 135static apr_pool_t *pmain; /* Pool for httpd child stuff */ 136 137static pid_t ap_my_pid; /* it seems silly to call getpid all the time */ 138static char *ap_my_addrspace = NULL; 139 140static int die_now = 0; 141 142/* Keep track of the number of worker threads currently active */ 143static unsigned long worker_thread_count; 144static int request_count; 145 146/* Structure used to register/deregister a console handler with the OS */ 147static int InstallConsoleHandler(void); 148static void RemoveConsoleHandler(void); 149static int CommandLineInterpreter(scr_t screenID, const char *commandLine); 150static CommandParser_t ConsoleHandler = {0, NULL, 0}; 151#define HANDLEDCOMMAND 0 152#define NOTMYCOMMAND 1 153 154static int show_settings = 0; 155 156//#define DBINFO_ON 157//#define DBPRINT_ON 158#ifdef DBPRINT_ON 159#define DBPRINT0(s) printf(s) 160#define DBPRINT1(s,v1) printf(s,v1) 161#define DBPRINT2(s,v1,v2) printf(s,v1,v2) 162#else 163#define DBPRINT0(s) 164#define DBPRINT1(s,v1) 165#define DBPRINT2(s,v1,v2) 166#endif 167 168/* volatile just in case */ 169static int volatile shutdown_pending; 170static int volatile restart_pending; 171static int volatile is_graceful; 172static int volatile wait_to_finish=1; 173static ap_generation_t volatile ap_my_generation=0; 174 175/* a clean exit from a child with proper cleanup */ 176static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, 177 apr_bucket_alloc_t *bucket_alloc) __attribute__ ((noreturn)); 178static void clean_child_exit(int code, int worker_num, apr_pool_t *ptrans, 179 apr_bucket_alloc_t *bucket_alloc) 180{ 181 apr_bucket_alloc_destroy(bucket_alloc); 182 if (!shutdown_pending) { 183 apr_pool_destroy(ptrans); 184 } 185 186 atomic_dec (&worker_thread_count); 187 if (worker_num >=0) 188 ap_update_child_status_from_indexes(0, worker_num, WORKER_DEAD, 189 (request_rec *) NULL); 190 NXThreadExit((void*)&code); 191} 192 193/* proper cleanup when returning from ap_mpm_run() */ 194static void mpm_main_cleanup(void) 195{ 196 if (pmain) { 197 apr_pool_destroy(pmain); 198 } 199} 200 201static int netware_query(int query_code, int *result, apr_status_t *rv) 202{ 203 *rv = APR_SUCCESS; 204 switch(query_code){ 205 case AP_MPMQ_MAX_DAEMON_USED: 206 *result = 1; 207 break; 208 case AP_MPMQ_IS_THREADED: 209 *result = AP_MPMQ_DYNAMIC; 210 break; 211 case AP_MPMQ_IS_FORKED: 212 *result = AP_MPMQ_NOT_SUPPORTED; 213 break; 214 case AP_MPMQ_HARD_LIMIT_DAEMONS: 215 *result = HARD_SERVER_LIMIT; 216 break; 217 case AP_MPMQ_HARD_LIMIT_THREADS: 218 *result = HARD_THREAD_LIMIT; 219 break; 220 case AP_MPMQ_MAX_THREADS: 221 *result = ap_threads_limit; 222 break; 223 case AP_MPMQ_MIN_SPARE_DAEMONS: 224 *result = 0; 225 break; 226 case AP_MPMQ_MIN_SPARE_THREADS: 227 *result = ap_threads_min_free; 228 break; 229 case AP_MPMQ_MAX_SPARE_DAEMONS: 230 *result = 0; 231 break; 232 case AP_MPMQ_MAX_SPARE_THREADS: 233 *result = ap_threads_max_free; 234 break; 235 case AP_MPMQ_MAX_REQUESTS_DAEMON: 236 *result = ap_max_requests_per_child; 237 break; 238 case AP_MPMQ_MAX_DAEMONS: 239 *result = 1; 240 break; 241 case AP_MPMQ_MPM_STATE: 242 *result = mpm_state; 243 break; 244 case AP_MPMQ_GENERATION: 245 *result = ap_my_generation; 246 break; 247 default: 248 *rv = APR_ENOTIMPL; 249 break; 250 } 251 return OK; 252} 253 254static const char *netware_get_name(void) 255{ 256 return "NetWare"; 257} 258 259/***************************************************************** 260 * Connection structures and accounting... 261 */ 262 263static void mpm_term(void) 264{ 265 RemoveConsoleHandler(); 266 wait_to_finish = 0; 267 NXThreadYield(); 268} 269 270static void sig_term(int sig) 271{ 272 if (shutdown_pending == 1) { 273 /* Um, is this _probably_ not an error, if the user has 274 * tried to do a shutdown twice quickly, so we won't 275 * worry about reporting it. 276 */ 277 return; 278 } 279 shutdown_pending = 1; 280 281 DBPRINT0 ("waiting for threads\n"); 282 while (wait_to_finish) { 283 apr_thread_yield(); 284 } 285 DBPRINT0 ("goodbye\n"); 286} 287 288/* restart() is the signal handler for SIGHUP and SIGWINCH 289 * in the parent process, unless running in ONE_PROCESS mode 290 */ 291static void restart(void) 292{ 293 if (restart_pending == 1) { 294 /* Probably not an error - don't bother reporting it */ 295 return; 296 } 297 restart_pending = 1; 298 is_graceful = 1; 299} 300 301static void set_signals(void) 302{ 303 apr_signal(SIGTERM, sig_term); 304 apr_signal(SIGABRT, sig_term); 305} 306 307int nlmUnloadSignaled(int wait) 308{ 309 shutdown_pending = 1; 310 311 if (wait) { 312 while (wait_to_finish) { 313 NXThreadYield(); 314 } 315 } 316 317 return 0; 318} 319 320/***************************************************************** 321 * Child process main loop. 322 * The following vars are static to avoid getting clobbered by longjmp(); 323 * they are really private to child_main. 324 */ 325 326 327#define MAX_WB_RETRIES 3 328#ifdef DBINFO_ON 329static int would_block = 0; 330static int retry_success = 0; 331static int retry_fail = 0; 332static int avg_retries = 0; 333#endif 334 335/*static */ 336void worker_main(void *arg) 337{ 338 ap_listen_rec *lr, *first_lr, *last_lr = NULL; 339 apr_pool_t *ptrans; 340 apr_allocator_t *allocator; 341 apr_bucket_alloc_t *bucket_alloc; 342 conn_rec *current_conn; 343 apr_status_t stat = APR_EINIT; 344 ap_sb_handle_t *sbh; 345 apr_thread_t *thd = NULL; 346 apr_os_thread_t osthd; 347 348 int my_worker_num = (int)arg; 349 apr_socket_t *csd = NULL; 350 int requests_this_child = 0; 351 apr_socket_t *sd = NULL; 352 fd_set main_fds; 353 354 int sockdes; 355 int srv; 356 struct timeval tv; 357 int wouldblock_retry; 358 359 osthd = apr_os_thread_current(); 360 apr_os_thread_put(&thd, &osthd, pmain); 361 362 tv.tv_sec = 1; 363 tv.tv_usec = 0; 364 365 apr_allocator_create(&allocator); 366 apr_allocator_max_free_set(allocator, ap_max_mem_free); 367 368 apr_pool_create_ex(&ptrans, pmain, NULL, allocator); 369 apr_allocator_owner_set(allocator, ptrans); 370 apr_pool_tag(ptrans, "transaction"); 371 372 bucket_alloc = apr_bucket_alloc_create_ex(allocator); 373 374 atomic_inc (&worker_thread_count); 375 376 while (!die_now) { 377 /* 378 * (Re)initialize this child to a pre-connection state. 379 */ 380 current_conn = NULL; 381 apr_pool_clear(ptrans); 382 383 if ((ap_max_requests_per_child > 0 384 && requests_this_child++ >= ap_max_requests_per_child)) { 385 DBPRINT1 ("\n**Thread slot %d is shutting down", my_worker_num); 386 clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); 387 } 388 389 ap_update_child_status_from_indexes(0, my_worker_num, WORKER_READY, 390 (request_rec *) NULL); 391 392 /* 393 * Wait for an acceptable connection to arrive. 394 */ 395 396 for (;;) { 397 if (shutdown_pending || restart_pending || (ap_scoreboard_image->servers[0][my_worker_num].status == WORKER_IDLE_KILL)) { 398 DBPRINT1 ("\nThread slot %d is shutting down\n", my_worker_num); 399 clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); 400 } 401 402 /* Check the listen queue on all sockets for requests */ 403 memcpy(&main_fds, &listenfds, sizeof(fd_set)); 404 srv = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); 405 406 if (srv <= 0) { 407 if (srv < 0) { 408 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00217) 409 "select() failed on listen socket"); 410 apr_thread_yield(); 411 } 412 continue; 413 } 414 415 /* remember the last_lr we searched last time around so that 416 we don't end up starving any particular listening socket */ 417 if (last_lr == NULL) { 418 lr = ap_listeners; 419 } 420 else { 421 lr = last_lr->next; 422 if (!lr) 423 lr = ap_listeners; 424 } 425 first_lr = lr; 426 do { 427 apr_os_sock_get(&sockdes, lr->sd); 428 if (FD_ISSET(sockdes, &main_fds)) 429 goto got_listener; 430 lr = lr->next; 431 if (!lr) 432 lr = ap_listeners; 433 } while (lr != first_lr); 434 /* if we get here, something unexpected happened. Go back 435 into the select state and try again. 436 */ 437 continue; 438 got_listener: 439 last_lr = lr; 440 sd = lr->sd; 441 442 wouldblock_retry = MAX_WB_RETRIES; 443 444 while (wouldblock_retry) { 445 if ((stat = apr_socket_accept(&csd, sd, ptrans)) == APR_SUCCESS) { 446 break; 447 } 448 else { 449 /* if the error is a wouldblock then maybe we were too 450 quick try to pull the next request from the listen 451 queue. Try a few more times then return to our idle 452 listen state. */ 453 if (!APR_STATUS_IS_EAGAIN(stat)) { 454 break; 455 } 456 457 if (wouldblock_retry--) { 458 apr_thread_yield(); 459 } 460 } 461 } 462 463 /* If we got a new socket, set it to non-blocking mode and process 464 it. Otherwise handle the error. */ 465 if (stat == APR_SUCCESS) { 466 apr_socket_opt_set(csd, APR_SO_NONBLOCK, 0); 467#ifdef DBINFO_ON 468 if (wouldblock_retry < MAX_WB_RETRIES) { 469 retry_success++; 470 avg_retries += (MAX_WB_RETRIES-wouldblock_retry); 471 } 472#endif 473 break; /* We have a socket ready for reading */ 474 } 475 else { 476#ifdef DBINFO_ON 477 if (APR_STATUS_IS_EAGAIN(stat)) { 478 would_block++; 479 retry_fail++; 480 } 481 else if ( 482#else 483 if (APR_STATUS_IS_EAGAIN(stat) || 484#endif 485 APR_STATUS_IS_ECONNRESET(stat) || 486 APR_STATUS_IS_ETIMEDOUT(stat) || 487 APR_STATUS_IS_EHOSTUNREACH(stat) || 488 APR_STATUS_IS_ENETUNREACH(stat)) { 489 ; 490 } 491#ifdef USE_WINSOCK 492 else if (APR_STATUS_IS_ENETDOWN(stat)) { 493 /* 494 * When the network layer has been shut down, there 495 * is not much use in simply exiting: the parent 496 * would simply re-create us (and we'd fail again). 497 * Use the CHILDFATAL code to tear the server down. 498 * @@@ Martin's idea for possible improvement: 499 * A different approach would be to define 500 * a new APEXIT_NETDOWN exit code, the reception 501 * of which would make the parent shutdown all 502 * children, then idle-loop until it detected that 503 * the network is up again, and restart the children. 504 * Ben Hyde noted that temporary ENETDOWN situations 505 * occur in mobile IP. 506 */ 507 ap_log_error(APLOG_MARK, APLOG_EMERG, stat, ap_server_conf, APLOGNO(00218) 508 "apr_socket_accept: giving up."); 509 clean_child_exit(APEXIT_CHILDFATAL, my_worker_num, ptrans, 510 bucket_alloc); 511 } 512#endif 513 else { 514 ap_log_error(APLOG_MARK, APLOG_ERR, stat, ap_server_conf, APLOGNO(00219) 515 "apr_socket_accept: (client socket)"); 516 clean_child_exit(1, my_worker_num, ptrans, bucket_alloc); 517 } 518 } 519 } 520 521 ap_create_sb_handle(&sbh, ptrans, 0, my_worker_num); 522 /* 523 * We now have a connection, so set it up with the appropriate 524 * socket options, file descriptors, and read/write buffers. 525 */ 526 current_conn = ap_run_create_connection(ptrans, ap_server_conf, csd, 527 my_worker_num, sbh, 528 bucket_alloc); 529 if (current_conn) { 530 current_conn->current_thread = thd; 531 ap_process_connection(current_conn, csd); 532 ap_lingering_close(current_conn); 533 } 534 request_count++; 535 } 536 clean_child_exit(0, my_worker_num, ptrans, bucket_alloc); 537} 538 539 540static int make_child(server_rec *s, int slot) 541{ 542 int tid; 543 int err=0; 544 NXContext_t ctx; 545 546 if (slot + 1 > ap_max_workers_limit) { 547 ap_max_workers_limit = slot + 1; 548 } 549 550 ap_update_child_status_from_indexes(0, slot, WORKER_STARTING, 551 (request_rec *) NULL); 552 553 if (ctx = NXContextAlloc((void (*)(void *)) worker_main, (void*)slot, NX_PRIO_MED, ap_thread_stacksize, NX_CTX_NORMAL, &err)) { 554 char threadName[32]; 555 556 sprintf (threadName, "Apache_Worker %d", slot); 557 NXContextSetName(ctx, threadName); 558 err = NXThreadCreate(ctx, NX_THR_BIND_CONTEXT, &tid); 559 if (err) { 560 NXContextFree (ctx); 561 } 562 } 563 564 if (err) { 565 /* create thread didn't succeed. Fix the scoreboard or else 566 * it will say SERVER_STARTING forever and ever 567 */ 568 ap_update_child_status_from_indexes(0, slot, WORKER_DEAD, 569 (request_rec *) NULL); 570 571 /* In case system resources are maxxed out, we don't want 572 Apache running away with the CPU trying to fork over and 573 over and over again. */ 574 apr_thread_yield(); 575 576 return -1; 577 } 578 579 ap_scoreboard_image->servers[0][slot].tid = tid; 580 581 return 0; 582} 583 584 585/* start up a bunch of worker threads */ 586static void startup_workers(int number_to_start) 587{ 588 int i; 589 590 for (i = 0; number_to_start && i < ap_threads_limit; ++i) { 591 if (ap_scoreboard_image->servers[0][i].status != WORKER_DEAD) { 592 continue; 593 } 594 if (make_child(ap_server_conf, i) < 0) { 595 break; 596 } 597 --number_to_start; 598 } 599} 600 601 602/* 603 * idle_spawn_rate is the number of children that will be spawned on the 604 * next maintenance cycle if there aren't enough idle servers. It is 605 * doubled up to MAX_SPAWN_RATE, and reset only when a cycle goes by 606 * without the need to spawn. 607 */ 608static int idle_spawn_rate = 1; 609#ifndef MAX_SPAWN_RATE 610#define MAX_SPAWN_RATE (64) 611#endif 612static int hold_off_on_exponential_spawning; 613 614static void perform_idle_server_maintenance(apr_pool_t *p) 615{ 616 int i; 617 int idle_count; 618 worker_score *ws; 619 int free_length; 620 int free_slots[MAX_SPAWN_RATE]; 621 int last_non_dead; 622 int total_non_dead; 623 624 /* initialize the free_list */ 625 free_length = 0; 626 627 idle_count = 0; 628 last_non_dead = -1; 629 total_non_dead = 0; 630 631 for (i = 0; i < ap_threads_limit; ++i) { 632 int status; 633 634 if (i >= ap_max_workers_limit && free_length == idle_spawn_rate) 635 break; 636 ws = &ap_scoreboard_image->servers[0][i]; 637 status = ws->status; 638 if (status == WORKER_DEAD) { 639 /* try to keep children numbers as low as possible */ 640 if (free_length < idle_spawn_rate) { 641 free_slots[free_length] = i; 642 ++free_length; 643 } 644 } 645 else if (status == WORKER_IDLE_KILL) { 646 /* If it is already marked to die, skip it */ 647 continue; 648 } 649 else { 650 /* We consider a starting server as idle because we started it 651 * at least a cycle ago, and if it still hasn't finished starting 652 * then we're just going to swamp things worse by forking more. 653 * So we hopefully won't need to fork more if we count it. 654 * This depends on the ordering of SERVER_READY and SERVER_STARTING. 655 */ 656 if (status <= WORKER_READY) { 657 ++ idle_count; 658 } 659 660 ++total_non_dead; 661 last_non_dead = i; 662 } 663 } 664 DBPRINT2("Total: %d Idle Count: %d \r", total_non_dead, idle_count); 665 ap_max_workers_limit = last_non_dead + 1; 666 if (idle_count > ap_threads_max_free) { 667 /* kill off one child... we use the pod because that'll cause it to 668 * shut down gracefully, in case it happened to pick up a request 669 * while we were counting 670 */ 671 idle_spawn_rate = 1; 672 ap_update_child_status_from_indexes(0, last_non_dead, WORKER_IDLE_KILL, 673 (request_rec *) NULL); 674 DBPRINT1("\nKilling idle thread: %d\n", last_non_dead); 675 } 676 else if (idle_count < ap_threads_min_free) { 677 /* terminate the free list */ 678 if (free_length == 0) { 679 /* only report this condition once */ 680 static int reported = 0; 681 682 if (!reported) { 683 ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00220) 684 "server reached MaxRequestWorkers setting, consider" 685 " raising the MaxRequestWorkers setting"); 686 reported = 1; 687 } 688 idle_spawn_rate = 1; 689 } 690 else { 691 if (idle_spawn_rate >= 8) { 692 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00221) 693 "server seems busy, (you may need " 694 "to increase StartServers, or Min/MaxSpareServers), " 695 "spawning %d children, there are %d idle, and " 696 "%d total children", idle_spawn_rate, 697 idle_count, total_non_dead); 698 } 699 DBPRINT0("\n"); 700 for (i = 0; i < free_length; ++i) { 701 DBPRINT1("Spawning additional thread slot: %d\n", free_slots[i]); 702 make_child(ap_server_conf, free_slots[i]); 703 } 704 /* the next time around we want to spawn twice as many if this 705 * wasn't good enough, but not if we've just done a graceful 706 */ 707 if (hold_off_on_exponential_spawning) { 708 --hold_off_on_exponential_spawning; 709 } 710 else if (idle_spawn_rate < MAX_SPAWN_RATE) { 711 idle_spawn_rate *= 2; 712 } 713 } 714 } 715 else { 716 idle_spawn_rate = 1; 717 } 718} 719 720static void display_settings() 721{ 722 int status_array[SERVER_NUM_STATUS]; 723 int i, status, total=0; 724 int reqs = request_count; 725#ifdef DBINFO_ON 726 int wblock = would_block; 727 728 would_block = 0; 729#endif 730 731 request_count = 0; 732 733 ClearScreen (getscreenhandle()); 734 printf("%s \n", ap_get_server_description()); 735 736 for (i=0;i<SERVER_NUM_STATUS;i++) { 737 status_array[i] = 0; 738 } 739 740 for (i = 0; i < ap_threads_limit; ++i) { 741 status = (ap_scoreboard_image->servers[0][i]).status; 742 status_array[status]++; 743 } 744 745 for (i=0;i<SERVER_NUM_STATUS;i++) { 746 switch(i) 747 { 748 case SERVER_DEAD: 749 printf ("Available:\t%d\n", status_array[i]); 750 break; 751 case SERVER_STARTING: 752 printf ("Starting:\t%d\n", status_array[i]); 753 break; 754 case SERVER_READY: 755 printf ("Ready:\t\t%d\n", status_array[i]); 756 break; 757 case SERVER_BUSY_READ: 758 printf ("Busy:\t\t%d\n", status_array[i]); 759 break; 760 case SERVER_BUSY_WRITE: 761 printf ("Busy Write:\t%d\n", status_array[i]); 762 break; 763 case SERVER_BUSY_KEEPALIVE: 764 printf ("Busy Keepalive:\t%d\n", status_array[i]); 765 break; 766 case SERVER_BUSY_LOG: 767 printf ("Busy Log:\t%d\n", status_array[i]); 768 break; 769 case SERVER_BUSY_DNS: 770 printf ("Busy DNS:\t%d\n", status_array[i]); 771 break; 772 case SERVER_CLOSING: 773 printf ("Closing:\t%d\n", status_array[i]); 774 break; 775 case SERVER_GRACEFUL: 776 printf ("Restart:\t%d\n", status_array[i]); 777 break; 778 case SERVER_IDLE_KILL: 779 printf ("Idle Kill:\t%d\n", status_array[i]); 780 break; 781 default: 782 printf ("Unknown Status:\t%d\n", status_array[i]); 783 break; 784 } 785 if (i != SERVER_DEAD) 786 total+=status_array[i]; 787 } 788 printf ("Total Running:\t%d\tout of: \t%d\n", total, ap_threads_limit); 789 printf ("Requests per interval:\t%d\n", reqs); 790 791#ifdef DBINFO_ON 792 printf ("Would blocks:\t%d\n", wblock); 793 printf ("Successful retries:\t%d\n", retry_success); 794 printf ("Failed retries:\t%d\n", retry_fail); 795 printf ("Avg retries:\t%d\n", retry_success == 0 ? 0 : avg_retries / retry_success); 796#endif 797} 798 799static void show_server_data() 800{ 801 ap_listen_rec *lr; 802 module **m; 803 804 printf("%s\n", ap_get_server_description()); 805 if (ap_my_addrspace && (ap_my_addrspace[0] != 'O') && (ap_my_addrspace[1] != 'S')) 806 printf(" Running in address space %s\n", ap_my_addrspace); 807 808 809 /* Display listening ports */ 810 printf(" Listening on port(s):"); 811 lr = ap_listeners; 812 do { 813 printf(" %d", lr->bind_addr->port); 814 lr = lr->next; 815 } while(lr && lr != ap_listeners); 816 817 /* Display dynamic modules loaded */ 818 printf("\n"); 819 for (m = ap_loaded_modules; *m != NULL; m++) { 820 if (((module*)*m)->dynamic_load_handle) { 821 printf(" Loaded dynamic module %s\n", ((module*)*m)->name); 822 } 823 } 824} 825 826 827static int setup_listeners(server_rec *s) 828{ 829 ap_listen_rec *lr; 830 int sockdes; 831 832 if (ap_setup_listeners(s) < 1 ) { 833 ap_log_error(APLOG_MARK, APLOG_ALERT, 0, s, APLOGNO(00222) 834 "no listening sockets available, shutting down"); 835 return -1; 836 } 837 838 listenmaxfd = -1; 839 FD_ZERO(&listenfds); 840 for (lr = ap_listeners; lr; lr = lr->next) { 841 apr_os_sock_get(&sockdes, lr->sd); 842 FD_SET(sockdes, &listenfds); 843 if (sockdes > listenmaxfd) { 844 listenmaxfd = sockdes; 845 } 846 } 847 return 0; 848} 849 850static int shutdown_listeners() 851{ 852 ap_listen_rec *lr; 853 854 for (lr = ap_listeners; lr; lr = lr->next) { 855 apr_socket_close(lr->sd); 856 } 857 ap_listeners = NULL; 858 return 0; 859} 860 861/***************************************************************** 862 * Executive routines. 863 */ 864 865static int netware_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) 866{ 867 apr_status_t status=0; 868 869 pconf = _pconf; 870 ap_server_conf = s; 871 872 if (setup_listeners(s)) { 873 ap_log_error(APLOG_MARK, APLOG_ALERT, status, s, APLOGNO(00223) 874 "no listening sockets available, shutting down"); 875 return -1; 876 } 877 878 restart_pending = shutdown_pending = 0; 879 worker_thread_count = 0; 880 881 if (!is_graceful) { 882 if (ap_run_pre_mpm(s->process->pool, SB_NOT_SHARED) != OK) { 883 return 1; 884 } 885 } 886 887 /* Only set slot 0 since that is all NetWare will ever have. */ 888 ap_scoreboard_image->parent[0].pid = getpid(); 889 ap_run_child_status(ap_server_conf, 890 ap_scoreboard_image->parent[0].pid, 891 ap_my_generation, 892 0, 893 MPM_CHILD_STARTED); 894 895 set_signals(); 896 897 apr_pool_create(&pmain, pconf); 898 ap_run_child_init(pmain, ap_server_conf); 899 900 if (ap_threads_max_free < ap_threads_min_free + 1) /* Don't thrash... */ 901 ap_threads_max_free = ap_threads_min_free + 1; 902 request_count = 0; 903 904 startup_workers(ap_threads_to_start); 905 906 /* Allow the Apache screen to be closed normally on exit() only if it 907 has not been explicitly forced to close on exit(). (ie. the -E flag 908 was specified at startup) */ 909 if (hold_screen_on_exit > 0) { 910 hold_screen_on_exit = 0; 911 } 912 913 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00224) 914 "%s configured -- resuming normal operations", 915 ap_get_server_description()); 916 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00225) 917 "Server built: %s", ap_get_server_built()); 918 ap_log_command_line(plog, s); 919 show_server_data(); 920 921 mpm_state = AP_MPMQ_RUNNING; 922 while (!restart_pending && !shutdown_pending) { 923 perform_idle_server_maintenance(pconf); 924 if (show_settings) 925 display_settings(); 926 apr_thread_yield(); 927 apr_sleep(SCOREBOARD_MAINTENANCE_INTERVAL); 928 } 929 mpm_state = AP_MPMQ_STOPPING; 930 931 ap_run_child_status(ap_server_conf, 932 ap_scoreboard_image->parent[0].pid, 933 ap_my_generation, 934 0, 935 MPM_CHILD_EXITED); 936 937 /* Shutdown the listen sockets so that we don't get stuck in a blocking call. 938 shutdown_listeners();*/ 939 940 if (shutdown_pending) { /* Got an unload from the console */ 941 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00226) 942 "caught SIGTERM, shutting down"); 943 944 while (worker_thread_count > 0) { 945 printf ("\rShutdown pending. Waiting for %u thread(s) to terminate...", 946 worker_thread_count); 947 apr_thread_yield(); 948 } 949 950 mpm_main_cleanup(); 951 return 1; 952 } 953 else { /* the only other way out is a restart */ 954 /* advance to the next generation */ 955 /* XXX: we really need to make sure this new generation number isn't in 956 * use by any of the children. 957 */ 958 ++ap_my_generation; 959 ap_scoreboard_image->global->running_generation = ap_my_generation; 960 961 ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00227) 962 "Graceful restart requested, doing restart"); 963 964 /* Wait for all of the threads to terminate before initiating the restart */ 965 while (worker_thread_count > 0) { 966 printf ("\rRestart pending. Waiting for %u thread(s) to terminate...", 967 worker_thread_count); 968 apr_thread_yield(); 969 } 970 printf ("\nRestarting...\n"); 971 } 972 973 mpm_main_cleanup(); 974 return 0; 975} 976 977static int netware_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) 978{ 979 char *addrname = NULL; 980 981 mpm_state = AP_MPMQ_STARTING; 982 983 is_graceful = 0; 984 ap_my_pid = getpid(); 985 addrname = getaddressspacename (NULL, NULL); 986 if (addrname) { 987 ap_my_addrspace = apr_pstrdup (p, addrname); 988 free (addrname); 989 } 990 991#ifndef USE_WINSOCK 992 /* The following call has been moved to the mod_nw_ssl pre-config handler */ 993 ap_listen_pre_config(); 994#endif 995 996 ap_threads_to_start = DEFAULT_START_THREADS; 997 ap_threads_min_free = DEFAULT_MIN_FREE_THREADS; 998 ap_threads_max_free = DEFAULT_MAX_FREE_THREADS; 999 ap_threads_limit = HARD_THREAD_LIMIT; 1000 ap_extended_status = 0; 1001 1002 /* override core's default thread stacksize */ 1003 ap_thread_stacksize = DEFAULT_THREAD_STACKSIZE; 1004 1005 return OK; 1006} 1007 1008static int netware_check_config(apr_pool_t *p, apr_pool_t *plog, 1009 apr_pool_t *ptemp, server_rec *s) 1010{ 1011 static int restart_num = 0; 1012 int startup = 0; 1013 1014 /* we want this only the first time around */ 1015 if (restart_num++ == 0) { 1016 startup = 1; 1017 } 1018 1019 if (ap_threads_limit > HARD_THREAD_LIMIT) { 1020 if (startup) { 1021 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00228) 1022 "WARNING: MaxThreads of %d exceeds compile-time " 1023 "limit of", ap_threads_limit); 1024 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1025 " %d threads, decreasing to %d.", 1026 HARD_THREAD_LIMIT, HARD_THREAD_LIMIT); 1027 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1028 " To increase, please see the HARD_THREAD_LIMIT" 1029 "define in"); 1030 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1031 " server/mpm/netware%s.", MPM_HARD_LIMITS_FILE); 1032 } else { 1033 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00229) 1034 "MaxThreads of %d exceeds compile-time limit " 1035 "of %d, decreasing to match", 1036 ap_threads_limit, HARD_THREAD_LIMIT); 1037 } 1038 ap_threads_limit = HARD_THREAD_LIMIT; 1039 } 1040 else if (ap_threads_limit < 1) { 1041 if (startup) { 1042 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1043 APLOGNO(00230) "WARNING: MaxThreads of %d not allowed, " 1044 "increasing to 1.", ap_threads_limit); 1045 } else { 1046 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, 1047 "MaxThreads of %d not allowed, increasing to 1", 1048 ap_threads_limit); 1049 } 1050 ap_threads_limit = 1; 1051 } 1052 1053 /* ap_threads_to_start > ap_threads_limit effectively checked in 1054 * call to startup_workers(ap_threads_to_start) in ap_mpm_run() 1055 */ 1056 if (ap_threads_to_start < 0) { 1057 if (startup) { 1058 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00231) 1059 "WARNING: StartThreads of %d not allowed, " 1060 "increasing to 1.", ap_threads_to_start); 1061 } else { 1062 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00232) 1063 "StartThreads of %d not allowed, increasing to 1", 1064 ap_threads_to_start); 1065 } 1066 ap_threads_to_start = 1; 1067 } 1068 1069 if (ap_threads_min_free < 1) { 1070 if (startup) { 1071 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, APLOGNO(00233) 1072 "WARNING: MinSpareThreads of %d not allowed, " 1073 "increasing to 1", ap_threads_min_free); 1074 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1075 " to avoid almost certain server failure."); 1076 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_STARTUP, 0, NULL, 1077 " Please read the documentation."); 1078 } else { 1079 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(00234) 1080 "MinSpareThreads of %d not allowed, increasing to 1", 1081 ap_threads_min_free); 1082 } 1083 ap_threads_min_free = 1; 1084 } 1085 1086 /* ap_threads_max_free < ap_threads_min_free + 1 checked in ap_mpm_run() */ 1087 1088 return OK; 1089} 1090 1091static void netware_mpm_hooks(apr_pool_t *p) 1092{ 1093 /* Run the pre-config hook after core's so that it can override the 1094 * default setting of ThreadStackSize for NetWare. 1095 */ 1096 static const char * const predecessors[] = {"core.c", NULL}; 1097 1098 ap_hook_pre_config(netware_pre_config, predecessors, NULL, APR_HOOK_MIDDLE); 1099 ap_hook_check_config(netware_check_config, NULL, NULL, APR_HOOK_MIDDLE); 1100 //ap_hook_post_config(netware_post_config, NULL, NULL, 0); 1101 //ap_hook_child_init(netware_child_init, NULL, NULL, APR_HOOK_MIDDLE); 1102 //ap_hook_open_logs(netware_open_logs, NULL, aszSucc, APR_HOOK_REALLY_FIRST); 1103 ap_hook_mpm(netware_run, NULL, NULL, APR_HOOK_MIDDLE); 1104 ap_hook_mpm_query(netware_query, NULL, NULL, APR_HOOK_MIDDLE); 1105 ap_hook_mpm_get_name(netware_get_name, NULL, NULL, APR_HOOK_MIDDLE); 1106} 1107 1108static void netware_rewrite_args(process_rec *process) 1109{ 1110 char *def_server_root; 1111 char optbuf[3]; 1112 const char *opt_arg; 1113 apr_getopt_t *opt; 1114 apr_array_header_t *mpm_new_argv; 1115 1116 1117 atexit (mpm_term); 1118 InstallConsoleHandler(); 1119 1120 /* Make sure to hold the Apache screen open if exit() is called */ 1121 hold_screen_on_exit = 1; 1122 1123 /* Rewrite process->argv[]; 1124 * 1125 * add default -d serverroot from the path of this executable 1126 * 1127 * The end result will look like: 1128 * The -d serverroot default from the running executable 1129 */ 1130 if (process->argc > 0) { 1131 char *s = apr_pstrdup (process->pconf, process->argv[0]); 1132 if (s) { 1133 int i, len = strlen(s); 1134 1135 for (i=len; i; i--) { 1136 if (s[i] == '\\' || s[i] == '/') { 1137 s[i] = '\0'; 1138 apr_filepath_merge(&def_server_root, NULL, s, 1139 APR_FILEPATH_TRUENAME, process->pool); 1140 break; 1141 } 1142 } 1143 /* Use process->pool so that the rewritten argv 1144 * lasts for the lifetime of the server process, 1145 * because pconf will be destroyed after the 1146 * initial pre-flight of the config parser. 1147 */ 1148 mpm_new_argv = apr_array_make(process->pool, process->argc + 2, 1149 sizeof(const char *)); 1150 *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; 1151 *(const char **)apr_array_push(mpm_new_argv) = "-d"; 1152 *(const char **)apr_array_push(mpm_new_argv) = def_server_root; 1153 1154 optbuf[0] = '-'; 1155 optbuf[2] = '\0'; 1156 apr_getopt_init(&opt, process->pool, process->argc, process->argv); 1157 while (apr_getopt(opt, AP_SERVER_BASEARGS"n:", optbuf + 1, &opt_arg) == APR_SUCCESS) { 1158 switch (optbuf[1]) { 1159 case 'n': 1160 if (opt_arg) { 1161 renamescreen(opt_arg); 1162 } 1163 break; 1164 case 'E': 1165 /* Don't need to hold the screen open if the output is going to a file */ 1166 hold_screen_on_exit = -1; 1167 default: 1168 *(const char **)apr_array_push(mpm_new_argv) = 1169 apr_pstrdup(process->pool, optbuf); 1170 1171 if (opt_arg) { 1172 *(const char **)apr_array_push(mpm_new_argv) = opt_arg; 1173 } 1174 break; 1175 } 1176 } 1177 process->argc = mpm_new_argv->nelts; 1178 process->argv = (const char * const *) mpm_new_argv->elts; 1179 } 1180 } 1181} 1182 1183static int CommandLineInterpreter(scr_t screenID, const char *commandLine) 1184{ 1185 char *szCommand = "APACHE2 "; 1186 int iCommandLen = 8; 1187 char szcommandLine[256]; 1188 char *pID; 1189 screenID = screenID; 1190 1191 1192 if (commandLine == NULL) 1193 return NOTMYCOMMAND; 1194 if (strlen(commandLine) <= strlen(szCommand)) 1195 return NOTMYCOMMAND; 1196 1197 apr_cpystrn(szcommandLine, commandLine, sizeof(szcommandLine)); 1198 1199 /* All added commands begin with "APACHE2 " */ 1200 1201 if (!strnicmp(szCommand, szcommandLine, iCommandLen)) { 1202 ActivateScreen (getscreenhandle()); 1203 1204 /* If an instance id was not given but the nlm is loaded in 1205 protected space, then the the command belongs to the 1206 OS address space instance to pass it on. */ 1207 pID = strstr (szcommandLine, "-p"); 1208 if ((pID == NULL) && nlmisloadedprotected()) 1209 return NOTMYCOMMAND; 1210 1211 /* If we got an instance id but it doesn't match this 1212 instance of the nlm, pass it on. */ 1213 if (pID) { 1214 pID = &pID[2]; 1215 while (*pID && (*pID == ' ')) 1216 pID++; 1217 } 1218 if (pID && ap_my_addrspace && strnicmp(pID, ap_my_addrspace, strlen(ap_my_addrspace))) 1219 return NOTMYCOMMAND; 1220 1221 /* If we have determined that this command belongs to this 1222 instance of the nlm, then handle it. */ 1223 if (!strnicmp("RESTART",&szcommandLine[iCommandLen],3)) { 1224 printf("Restart Requested...\n"); 1225 restart(); 1226 } 1227 else if (!strnicmp("VERSION",&szcommandLine[iCommandLen],3)) { 1228 printf("Server version: %s\n", ap_get_server_description()); 1229 printf("Server built: %s\n", ap_get_server_built()); 1230 } 1231 else if (!strnicmp("MODULES",&szcommandLine[iCommandLen],3)) { 1232 ap_show_modules(); 1233 } 1234 else if (!strnicmp("DIRECTIVES",&szcommandLine[iCommandLen],3)) { 1235 ap_show_directives(); 1236 } 1237 else if (!strnicmp("SHUTDOWN",&szcommandLine[iCommandLen],3)) { 1238 printf("Shutdown Requested...\n"); 1239 shutdown_pending = 1; 1240 } 1241 else if (!strnicmp("SETTINGS",&szcommandLine[iCommandLen],3)) { 1242 if (show_settings) { 1243 show_settings = 0; 1244 ClearScreen (getscreenhandle()); 1245 show_server_data(); 1246 } 1247 else { 1248 show_settings = 1; 1249 display_settings(); 1250 } 1251 } 1252 else { 1253 show_settings = 0; 1254 if (strnicmp("HELP",&szcommandLine[iCommandLen],3)) 1255 printf("Unknown APACHE2 command %s\n", &szcommandLine[iCommandLen]); 1256 printf("Usage: APACHE2 [command] [-p <instance ID>]\n"); 1257 printf("Commands:\n"); 1258 printf("\tDIRECTIVES - Show directives\n"); 1259 printf("\tHELP - Display this help information\n"); 1260 printf("\tMODULES - Show a list of the loaded modules\n"); 1261 printf("\tRESTART - Reread the configuration file and restart Apache\n"); 1262 printf("\tSETTINGS - Show current thread status\n"); 1263 printf("\tSHUTDOWN - Shutdown Apache\n"); 1264 printf("\tVERSION - Display the server version information\n"); 1265 } 1266 1267 /* Tell NetWare we handled the command */ 1268 return HANDLEDCOMMAND; 1269 } 1270 1271 /* Tell NetWare that the command isn't mine */ 1272 return NOTMYCOMMAND; 1273} 1274 1275static int InstallConsoleHandler(void) 1276{ 1277 /* Our command line handler interfaces the system operator 1278 with this NLM */ 1279 1280 NX_WRAP_INTERFACE(CommandLineInterpreter, 2, (void*)&(ConsoleHandler.parser)); 1281 1282 ConsoleHandler.rTag = AllocateResourceTag(getnlmhandle(), "Command Line Processor", 1283 ConsoleCommandSignature); 1284 if (!ConsoleHandler.rTag) 1285 { 1286 printf("Error on allocate resource tag\n"); 1287 return 1; 1288 } 1289 1290 RegisterConsoleCommand(&ConsoleHandler); 1291 1292 /* The Remove procedure unregisters the console handler */ 1293 1294 return 0; 1295} 1296 1297static void RemoveConsoleHandler(void) 1298{ 1299 UnRegisterConsoleCommand(&ConsoleHandler); 1300 NX_UNWRAP_INTERFACE(ConsoleHandler.parser); 1301} 1302 1303static const char *set_threads_to_start(cmd_parms *cmd, void *dummy, const char *arg) 1304{ 1305 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 1306 if (err != NULL) { 1307 return err; 1308 } 1309 1310 ap_threads_to_start = atoi(arg); 1311 return NULL; 1312} 1313 1314static const char *set_min_free_threads(cmd_parms *cmd, void *dummy, const char *arg) 1315{ 1316 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 1317 if (err != NULL) { 1318 return err; 1319 } 1320 1321 ap_threads_min_free = atoi(arg); 1322 return NULL; 1323} 1324 1325static const char *set_max_free_threads(cmd_parms *cmd, void *dummy, const char *arg) 1326{ 1327 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 1328 if (err != NULL) { 1329 return err; 1330 } 1331 1332 ap_threads_max_free = atoi(arg); 1333 return NULL; 1334} 1335 1336static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) 1337{ 1338 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); 1339 if (err != NULL) { 1340 return err; 1341 } 1342 1343 ap_threads_limit = atoi(arg); 1344 return NULL; 1345} 1346 1347static const command_rec netware_mpm_cmds[] = { 1348LISTEN_COMMANDS, 1349AP_INIT_TAKE1("StartThreads", set_threads_to_start, NULL, RSRC_CONF, 1350 "Number of worker threads launched at server startup"), 1351AP_INIT_TAKE1("MinSpareThreads", set_min_free_threads, NULL, RSRC_CONF, 1352 "Minimum number of idle threads, to handle request spikes"), 1353AP_INIT_TAKE1("MaxSpareThreads", set_max_free_threads, NULL, RSRC_CONF, 1354 "Maximum number of idle threads"), 1355AP_INIT_TAKE1("MaxThreads", set_thread_limit, NULL, RSRC_CONF, 1356 "Maximum number of worker threads alive at the same time"), 1357{ NULL } 1358}; 1359 1360AP_DECLARE_MODULE(mpm_netware) = { 1361 MPM20_MODULE_STUFF, 1362 netware_rewrite_args, /* hook to run before apache parses args */ 1363 NULL, /* create per-directory config structure */ 1364 NULL, /* merge per-directory config structures */ 1365 NULL, /* create per-server config structure */ 1366 NULL, /* merge per-server config structures */ 1367 netware_mpm_cmds, /* command apr_table_t */ 1368 netware_mpm_hooks, /* register hooks */ 1369}; 1370