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