Improved memory debugging for xmalloc/nmem.c. References to NMEM
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995-1997, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * NT server based on threads by
7  *   Chas Woodfield, Fretwell Downing Datasystem.
8  *
9  * $Log: statserv.c,v $
10  * Revision 1.43  1997-10-31 12:20:09  adam
11  * Improved memory debugging for xmalloc/nmem.c. References to NMEM
12  * instead of ODR in n ESPEC-1 handling in source d1_espec.c.
13  * Bug fix: missing fclose in data1_read_espec1.
14  *
15  * Revision 1.42  1997/10/27 14:03:02  adam
16  * Added new member to statserver_options_block, pre_init, which
17  * specifies a callback to be invoked after command line parsing and
18  * before the server listens for the first time.
19  *
20  * Revision 1.41  1997/09/29 07:19:32  adam
21  * Server library uses nmem_init/nmem_exit. The log prefix no longer
22  * includes leading path on NT.
23  *
24  * Revision 1.40  1997/09/17 12:10:41  adam
25  * YAZ version 1.4.
26  *
27  * Revision 1.39  1997/09/09 10:10:19  adam
28  * Another MSV5.0 port. Changed projects to include proper
29  * library/include paths.
30  * Server starts server in test-mode when no options are given.
31  *
32  * Revision 1.38  1997/09/04 14:19:14  adam
33  * Added credits.
34  *
35  * Revision 1.37  1997/09/01 08:53:01  adam
36  * New windows NT/95 port using MSV5.0. The test server 'ztest' was
37  * moved a separate directory. MSV5.0 project server.dsp created.
38  * As an option, the server can now operate as an NT service.
39  *
40  * Revision 1.36  1996/07/06 19:58:36  quinn
41  * System headerfiles gathered in yconfig
42  *
43  * Revision 1.35  1996/05/29  10:03:28  quinn
44  * Options work
45  *
46  * Revision 1.34  1996/02/21  13:12:07  quinn
47  * *** empty log message ***
48  *
49  * Revision 1.33  1996/02/10  12:23:49  quinn
50  * Enable inetd operations fro TCP/IP stack
51  *
52  * Revision 1.32  1996/01/19  15:41:52  quinn
53  * *** empty log message ***
54  *
55  * Revision 1.31  1995/11/17  11:09:39  adam
56  * Added new option '-c' to specify configuration name in control block.
57  *
58  * Revision 1.30  1995/11/01  13:54:59  quinn
59  * Minor adjustments
60  *
61  * Revision 1.29  1995/10/30  12:41:29  quinn
62  * Added hostname lookup for server.
63  *
64  * Revision 1.28  1995/09/29  17:12:30  quinn
65  * Smallish
66  *
67  * Revision 1.27  1995/09/27  15:03:02  quinn
68  * Modified function heads & prototypes.
69  *
70  * Revision 1.26  1995/08/29  14:44:51  quinn
71  * Reset timeouts.
72  *
73  * Revision 1.25  1995/08/29  11:18:02  quinn
74  * Added code to receive close
75  *
76  * Revision 1.24  1995/06/16  10:31:39  quinn
77  * Added session timeout.
78  *
79  * Revision 1.23  1995/06/15  12:30:48  quinn
80  * Setuid-facility.
81  *
82  * Revision 1.22  1995/06/15  07:45:17  quinn
83  * Moving to v3.
84  *
85  * Revision 1.21  1995/06/06  08:15:40  quinn
86  * Cosmetic.
87  *
88  * Revision 1.20  1995/05/29  08:12:09  quinn
89  * Moved oid to util
90  *
91  * Revision 1.19  1995/05/16  09:37:27  quinn
92  * Fixed bug
93  *
94  * Revision 1.18  1995/05/16  08:51:09  quinn
95  * License, documentation, and memory fixes
96  *
97  * Revision 1.17  1995/05/15  11:56:42  quinn
98  * Asynchronous facilities. Restructuring of seshigh code.
99  *
100  * Revision 1.16  1995/04/10  10:23:40  quinn
101  * Some work to add scan and other things.
102  *
103  * Revision 1.15  1995/03/31  10:16:51  quinn
104  * Fixed logging.
105  *
106  * Revision 1.14  1995/03/31  09:18:58  quinn
107  * Added logging.
108  *
109  * Revision 1.13  1995/03/30  16:08:39  quinn
110  * Little mods.
111  *
112  * Revision 1.12  1995/03/30  13:29:02  quinn
113  * Smallish
114  *
115  * Revision 1.11  1995/03/30  12:18:17  quinn
116  * Fixed bug.
117  *
118  * Revision 1.10  1995/03/29  15:40:16  quinn
119  * Ongoing work. Statserv is now dynamic by default
120  *
121  * Revision 1.9  1995/03/27  08:34:30  quinn
122  * Added dynamic server functionality.
123  * Released bindings to session.c (is now redundant)
124  *
125  * Revision 1.8  1995/03/20  09:46:26  quinn
126  * Added osi support.
127  *
128  * Revision 1.7  1995/03/16  13:29:04  quinn
129  * Partitioned server.
130  *
131  * Revision 1.6  1995/03/15  15:18:52  quinn
132  * Little changes to better support nonblocking I/O
133  * Added backend.h
134  *
135  * Revision 1.5  1995/03/15  08:37:45  quinn
136  * Now we're pretty much set for nonblocking I/O.
137  *
138  * Revision 1.4  1995/03/14  16:59:48  quinn
139  * Bug-fixes
140  *
141  * Revision 1.3  1995/03/14  11:30:15  quinn
142  * Works better now.
143  *
144  * Revision 1.2  1995/03/14  10:28:03  quinn
145  * More work on demo server.
146  *
147  * Revision 1.1  1995/03/10  18:22:45  quinn
148  * The rudiments of an asynchronous server.
149  *
150  */
151
152 #include <yconfig.h>
153 #include <stdio.h>
154 #include <string.h>
155 #ifdef WINDOWS
156 #include <process.h>
157 #include <winsock.h>
158 #include <direct.h>
159 #else
160 #include <unistd.h>
161 #include <pwd.h>
162 #endif
163 #include <fcntl.h>
164 #include <signal.h>
165 #include <errno.h>
166
167 #include <options.h>
168 #include "eventl.h"
169 #include "session.h"
170 #include <comstack.h>
171 #include <tcpip.h>
172 #ifdef USE_XTIMOSI
173 #include <xmosi.h>
174 #endif
175 #include <log.h>
176 #include <statserv.h>
177
178 static IOCHAN pListener;
179
180 static char *me = "statserver";
181 /*
182  * default behavior.
183  */
184 static statserv_options_block control_block = {
185     1,                          /* dynamic mode */
186     LOG_DEFAULT_LEVEL,          /* log level */
187     "",                         /* no PDUs */
188     "",                         /* diagnostic output to stderr */
189     "tcp:@:9999",               /* default listener port */
190     PROTO_Z3950,                /* default application protocol */
191     60,                         /* idle timeout (minutes) */
192     1024*1024,                  /* maximum PDU size (approx.) to allow */
193     "default-config",           /* configuration name to pass to backend */
194     "",                         /* set user id */
195     NULL
196 };
197
198 /*
199  * handle incoming connect requests.
200  * The dynamic mode is a bit tricky mostly because we want to avoid
201  * doing all of the listening and accepting in the parent - it's
202  * safer that way.
203  */
204 #ifdef WINDOWS
205
206 typedef struct _ThreadList ThreadList;
207
208 typedef struct _ThreadList
209 {
210     HANDLE hThread;
211     IOCHAN pIOChannel;
212     ThreadList *pNext;
213 } ThreadList;
214
215 static ThreadList *pFirstThread;
216 static CRITICAL_SECTION Thread_CritSect;
217 static BOOL bInitialized = FALSE;
218
219 static void ThreadList_Initialize()
220 {
221     /* Initialize the critical Sections */
222      InitializeCriticalSection(&Thread_CritSect);
223
224      /* Set the first thraed */
225      pFirstThread = NULL;
226
227      /* we have been initialized */
228      bInitialized = TRUE;
229 }
230
231 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
232 {
233     /* Only one thread can go through this section at a time */
234     EnterCriticalSection(&Thread_CritSect);
235
236     {
237         /* Lets create our new object */
238         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
239         pNewThread->hThread = hThread;
240         pNewThread->pIOChannel = pIOChannel;
241         pNewThread->pNext = pFirstThread;
242         pFirstThread = pNewThread;
243
244         /* Lets let somebody else create a new object now */
245         LeaveCriticalSection(&Thread_CritSect);
246     }
247 }
248
249 void statserv_remove(IOCHAN pIOChannel)
250 {
251     /* Only one thread can go through this section at a time */
252     EnterCriticalSection(&Thread_CritSect);
253
254     {
255         ThreadList *pCurrentThread = pFirstThread;
256         ThreadList *pNextThread;
257         ThreadList *pPrevThread =NULL;
258
259         /* Step through alll the threads */
260         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
261         {
262             /* We only need to compare on the IO Channel */
263             if (pCurrentThread->pIOChannel == pIOChannel)
264             {
265                 /* We have found the thread we want to delete */
266                 /* First of all reset the next pointers */
267                 if (pPrevThread == NULL)
268                     pFirstThread = pCurrentThread->pNext;
269                 else
270                     pPrevThread->pNext = pCurrentThread->pNext;
271
272                 /* All we need todo now is delete the memory */
273                 free(pCurrentThread);
274
275                 /* No need to look at any more threads */
276                 pNextThread = NULL;
277             }
278             else
279             {
280                 /* We need to look at another thread */
281                 pNextThread = pCurrentThread->pNext;
282             }
283         }
284
285         /* Lets let somebody else remove an object now */
286         LeaveCriticalSection(&Thread_CritSect);
287     }
288 }
289
290 void statserv_closedown()
291 {
292     /* Shouldn't do anything if we are not initialized */
293     if (bInitialized)
294     {
295         int iHandles = 0;
296         HANDLE *pThreadHandles = NULL;
297
298         /* We need to stop threads adding and removing while we */
299         /* start the closedown process */
300         EnterCriticalSection(&Thread_CritSect);
301
302         {
303             /* We have exclusive access to the thread stuff now */
304             /* Y didn't i use a semaphore - Oh well never mind */
305             ThreadList *pCurrentThread = pFirstThread;
306
307             /* Before we do anything else, we need to shutdown the listener */
308             if (pListener != NULL)
309                 iochan_destroy(pListener);
310
311             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
312             {
313                 /* Just destroy the IOCHAN, that should do the trick */
314                 iochan_destroy(pCurrentThread->pIOChannel);
315
316                 /* Keep a running count of our handles */
317                 iHandles++;
318             }
319
320             if (iHandles > 0)
321             {
322                 HANDLE *pCurrentHandle ;
323
324                 /* Allocate the thread handle array */
325                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
326                 pCurrentHandle = pThreadHandles; 
327
328                 for (pCurrentThread = pFirstThread;
329                      pCurrentThread != NULL;
330                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
331                 {
332                     /* Just the handle */
333                     *pCurrentHandle = pCurrentThread->hThread;
334                 }
335             }
336
337             /* We can now leave the critical section */
338             LeaveCriticalSection(&Thread_CritSect);
339         }
340
341         /* Now we can really do something */
342         if (iHandles > 0)
343         {
344             /* This will now wait, until all the threads close */
345             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
346
347             /* Free the memory we allocated for the handle array */
348             free(pThreadHandles);
349         }
350
351         /* No longer require the critical section, since all threads are dead */
352         DeleteCriticalSection(&Thread_CritSect);
353     }
354 }
355
356 static void listener(IOCHAN h, int event)
357 {
358     COMSTACK line = (COMSTACK) iochan_getdata(h);
359     association *newas;
360     int res;
361     HANDLE NewHandle;
362
363     if (event == EVENT_INPUT)
364     {
365         if ((res = cs_listen(line, 0, 0)) < 0)
366         {
367             logf(LOG_FATAL, "cs_listen failed.");
368             return;
369         }
370         else if (res == 1)
371             return;
372         logf(LOG_DEBUG, "listen ok");
373         iochan_setevent(h, EVENT_OUTPUT);
374         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
375     }
376     else if (event == EVENT_OUTPUT)
377     {
378         COMSTACK new_line;
379         IOCHAN new_chan;
380         char *a = NULL;
381         DWORD ThreadId;
382
383         if (!(new_line = cs_accept(line)))
384         {
385             logf(LOG_FATAL, "Accept failed.");
386             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
387             return;
388         }
389         logf(LOG_DEBUG, "Accept ok");
390
391         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session, EVENT_INPUT)))
392         {
393             logf(LOG_FATAL, "Failed to create iochan");
394             iochan_destroy(h);
395             return;
396         }
397
398         logf(LOG_DEBUG, "Creating association");
399         if (!(newas = create_association(new_chan, new_line)))
400         {
401             logf(LOG_FATAL, "Failed to create new assoc.");
402             iochan_destroy(h);
403             return;
404         }
405         logf(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
406         iochan_setdata(new_chan, newas);
407         iochan_settimeout(new_chan, control_block.idle_timeout * 60);\r
408 #ifndef WINDOWS
409         logf(LOG_DEBUG, "Determining client address");
410         a = cs_addrstr(new_line);
411         logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");\r
412 #endif
413     /* Now what we need todo is create a new thread with this iochan as
414        the parameter */
415     /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop, new_chan,
416            0, &ThreadId) == NULL) */
417     /* Somehow, somewhere we need to store this thread id, otherwise we won't be
418        able to close cleanly */
419         NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop, new_chan, 0, &ThreadId);
420         if (NewHandle == (HANDLE)-1)
421         {
422
423             logf(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
424             iochan_destroy(h);
425             return;
426         }
427         /* We successfully created the thread, so add it to the list */
428         statserv_add(NewHandle, new_chan);
429
430         logf(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
431         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
432     }
433     else
434     {
435         logf(LOG_FATAL, "Bad event on listener.");
436         iochan_destroy(h);
437         return;
438     }
439 }
440
441 #else /* WINDOWS */
442
443 /* To save having an #ifdef in event_loop we need to define this empty function */
444 void statserv_remove(IOCHAN pIOChannel)
445 {
446 }
447
448 void statserv_closedown()
449 {
450     /* We don't need todoanything here - or do we */
451     if (pListener != NULL)
452         iochan_destroy(pListener);
453 }
454
455 static void listener(IOCHAN h, int event)
456 {
457     COMSTACK line = (COMSTACK) iochan_getdata(h);
458     association *newas;
459     static int hand[2];
460     static int child = 0;
461     int res;
462
463     if (event == EVENT_INPUT)
464     {
465         if (control_block.dynamic && !child) 
466         {
467             int res;
468
469             if (pipe(hand) < 0)
470             {
471                 logf(LOG_FATAL|LOG_ERRNO, "pipe");
472                 iochan_destroy(h);
473                 return;
474             }
475             if ((res = fork()) < 0)
476             {
477                 logf(LOG_FATAL|LOG_ERRNO, "fork");
478                 iochan_destroy(h);
479                 return;
480             }
481             else if (res == 0) /* child */
482             {
483                 char nbuf[100];
484                 IOCHAN pp;
485
486                 close(hand[0]);
487                 child = 1;
488                 for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
489                 {
490                     if (pp != h)
491                     {
492                         COMSTACK l = iochan_getdata(pp);
493                         cs_close(l);
494                         iochan_destroy(pp);
495                     }
496                 }
497                 sprintf(nbuf, "%s(%d)", me, getpid());
498                 log_init(control_block.loglevel, nbuf, 0);
499             }
500             else /* parent */
501             {
502                 close(hand[1]);
503                 /* wait for child to take the call */
504                 for (;;)
505                 {
506                     char dummy[1];
507                     int res;
508                     
509                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
510                     {
511                         logf(LOG_FATAL|LOG_ERRNO, "handshake read");
512                         return;
513                     }
514                     else if (res >= 0)
515                         break;
516                 }
517                 logf(LOG_DEBUG, "P: Child has taken the call");
518                 close(hand[0]);
519                 return;
520             }
521         }
522         if ((res = cs_listen(line, 0, 0)) < 0)
523         {
524             logf(LOG_FATAL, "cs_listen failed.");
525             return;
526         }
527         else if (res == 1)
528             return;
529         logf(LOG_DEBUG, "listen ok");
530         iochan_setevent(h, EVENT_OUTPUT);
531         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
532     }
533     /* in dynamic mode, only the child ever comes down here */
534     else if (event == EVENT_OUTPUT)
535     {
536         COMSTACK new_line;
537         IOCHAN new_chan;
538         char *a;
539
540         if (!(new_line = cs_accept(line)))
541         {
542             logf(LOG_FATAL, "Accept failed.");
543             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
544             return;
545         }
546         logf(LOG_DEBUG, "accept ok");
547         if (control_block.dynamic)
548         {
549             IOCHAN pp;
550             /* close our half of the listener socket */
551             for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
552             {
553                 COMSTACK l = iochan_getdata(pp);
554                 cs_close(l);
555                 iochan_destroy(pp);
556             }
557             /* release dad */
558             logf(LOG_DEBUG, "Releasing parent");
559             close(hand[1]);
560         }
561         else
562             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
563
564         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
565             EVENT_INPUT)))
566         {
567             logf(LOG_FATAL, "Failed to create iochan");
568             iochan_destroy(h);
569             return;
570         }
571         if (!(newas = create_association(new_chan, new_line)))
572         {
573             logf(LOG_FATAL, "Failed to create new assoc.");
574             iochan_destroy(h);
575             return;
576         }
577         iochan_setdata(new_chan, newas);
578         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
579         a = cs_addrstr(new_line);
580         logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
581     }
582     else
583     {
584         logf(LOG_FATAL, "Bad event on listener.");
585         iochan_destroy(h);
586         return;
587     }
588 }
589
590 #endif /* WINDOWS */
591
592 static void inetd_connection(int what)
593 {
594     COMSTACK line;
595     IOCHAN chan;
596     association *assoc;
597     char *addr;
598
599     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
600     {
601         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
602         {
603             if ((assoc = create_association(chan, line)))
604             {
605                 iochan_setdata(chan, assoc);
606                 iochan_settimeout(chan, control_block.idle_timeout * 60);
607                 addr = cs_addrstr(line);
608                 logf(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
609             }
610             else
611             {
612                 logf(LOG_FATAL, "Failed to create association structure");
613             }
614         }
615         else
616         {
617             logf(LOG_FATAL, "Failed to create iochan");
618         }
619     }
620     else
621     {
622         logf(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
623     }
624 }
625
626 /*
627  * Set up a listening endpoint, and give it to the event-handler.
628  */
629 static void add_listener(char *where, int what)
630 {
631     COMSTACK l;
632     CS_TYPE type;
633     char mode[100], addr[100];
634     void *ap;
635     IOCHAN lst = NULL;
636
637     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
638     {
639         logf (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
640         return;
641     }
642     if (!strcmp(mode, "tcp"))
643         type = tcpip_type;
644     else if (!strcmp(mode, "osi"))
645     {
646 #ifdef USE_XTIMOSI
647         type = mosi_type;
648 #else
649         logf (LOG_WARN, "OSI Transport not allowed by configuration.");
650         return;
651 #endif
652     }
653     else
654     {
655         logf (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
656         return;
657     }
658     logf(LOG_LOG, "Adding %s %s listener on %s",
659         control_block.dynamic ? "dynamic" : "static",
660         what == PROTO_SR ? "SR" : "Z3950", where);
661     if (!(l = cs_create(type, 0, what)))
662     {
663         logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
664     }
665     ap = cs_straddr (l, addr);
666     if (!ap)
667     {
668         fprintf(stderr, "Address resolution failed.\n");
669         cs_close (l);
670         return;
671     }
672     if (cs_bind(l, ap, CS_SERVER) < 0)
673     {
674         logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
675     }
676     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
677          EVENT_EXCEPT)))
678     {
679         logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
680     }
681     iochan_setdata(lst, l);
682
683     /* Ensure our listener chain is setup properly */
684     lst->next = pListener;
685     pListener = lst;
686 }
687
688 #ifndef WINDOWS
689 /* For windows we don't need to catch the signals */
690 static void catchchld(int num)
691 {
692     while (waitpid(-1, 0, WNOHANG) > 0)
693         ;
694     signal(SIGCHLD, catchchld);
695 }
696 #endif /* WINDOWS */
697
698 statserv_options_block *statserv_getcontrol(void)
699 {
700     static statserv_options_block cb;
701
702     memcpy(&cb, &control_block, sizeof(cb));
703     return &cb;
704 }
705
706 void statserv_setcontrol(statserv_options_block *block)
707 {
708     memcpy(&control_block, block, sizeof(*block));
709 }
710
711 int statserv_main(int argc, char **argv)
712 {
713     int ret, listeners = 0, inetd = 0, r;
714     char *arg;
715     int protocol = control_block.default_proto;
716
717     nmem_init ();
718 #ifdef WINDOWS
719     /* We need to initialize the thread list */
720     ThreadList_Initialize();
721 #endif /* WINDOWS */
722
723 #ifdef WINDOWS
724     if ((me = strrchr (argv[0], '\\')))
725         me++;
726     else
727         me = argv[0];
728 #else
729     me = argv[0];
730 #endif
731     while ((ret = options("a:iszSl:v:u:c:w:t:k:", argv, argc, &arg)) != -2)
732     {
733         switch (ret)
734         {
735             case 0:
736                     add_listener(arg, protocol);
737                     listeners++;
738                 break;
739             case 'z': protocol = PROTO_Z3950; break;
740             case 's': protocol = PROTO_SR; break;
741             case 'S': control_block.dynamic = 0; break;
742             case 'l':
743                 strcpy(control_block.logfile, arg ? arg : "");
744                 log_init(control_block.loglevel, me, control_block.logfile);
745                 break;
746             case 'v':
747                 control_block.loglevel = log_mask_str(arg);
748                 log_init(control_block.loglevel, me, control_block.logfile);
749                 break;
750             case 'a':
751                 strcpy(control_block.apdufile, arg ? arg : ""); break;
752             case 'u':
753                 strcpy(control_block.setuid, arg ? arg : ""); break;
754             case 'c':
755                 strcpy(control_block.configname, arg ? arg : ""); break;
756             case 't':
757                 if (!arg || !(r = atoi(arg)))
758                 {
759                     fprintf(stderr, "%s: Specify positive timeout for -t.\n",
760                         me);
761                     return(1);
762                 }
763                 control_block.idle_timeout = r;
764                 break;
765             case  'k':
766                 if (!arg || !(r = atoi(arg)))
767                 {
768                     fprintf(stderr, "%s: Specify positive timeout for -t.\n",
769                         me);
770                     return(1);
771                 }
772                 control_block.maxrecordsize = r * 1024;
773                 break;
774             case 'i':
775                 inetd = 1; break;
776             case 'w':
777                 if (chdir(arg))
778                 {
779                     perror(arg);
780
781                     return(1);
782                 }
783                 break;
784             default:
785                 fprintf(stderr, "Usage: %s [ -i -a <pdufile> -v <loglevel>"
786                         " -l <logfile> -u <user> -c <config> -t <minutes>"
787                         " -k <kilobytes>"
788                         " -zsS <listener-addr> -w <directory> ... ]\n", me);
789                 return(1);
790             }
791     }
792
793     if ((pListener == NULL) && *control_block.default_listen)
794             add_listener(control_block.default_listen, protocol);
795
796 #ifndef WINDOWS
797     if (inetd)
798         inetd_connection(protocol);
799     else
800     {
801         if (control_block.pre_init)
802             (*control_block.pre_init)(&control_block);
803         if (control_block.dynamic)
804             signal(SIGCHLD, catchchld);
805     }
806     if (*control_block.setuid)
807     {
808         struct passwd *pw;
809         
810         if (!(pw = getpwnam(control_block.setuid)))
811         {
812             logf(LOG_FATAL, "%s: Unknown user", control_block.setuid);
813             return(1);
814         }
815         if (setuid(pw->pw_uid) < 0)
816         {
817             logf(LOG_FATAL|LOG_ERRNO, "setuid");
818             exit(1);
819         }
820     }
821 #endif /* WINDOWS */
822
823     logf(LOG_LOG, "Entering event loop.");
824         
825     if (pListener == NULL)
826         ret = 1;
827     else
828         ret = event_loop(pListener);
829     nmem_exit ();
830     return ret;
831 }