c4a7e9d529aeef7e414c1f18e91d0a5cfcde9082
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995-2003, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * NT threaded server code by
7  *   Chas Woodfield, Fretwell Downing Informatics.
8  *
9  * $Id: statserv.c,v 1.90 2003-01-14 08:21:14 adam Exp $
10  */
11
12 #include <stdio.h>
13 #include <string.h>
14 #ifdef WIN32
15 #include <process.h>
16 #include <winsock.h>
17 #include <direct.h>
18 #include "service.h"
19 #else
20 #include <unistd.h>
21 #include <pwd.h>
22 #endif
23
24 #if YAZ_POSIX_THREADS
25 #include <pthread.h>
26 #elif YAZ_GNU_THREADS
27 #include <pth.h>
28 #endif
29
30 #include <fcntl.h>
31 #include <signal.h>
32 #include <errno.h>
33
34 #include <yaz/comstack.h>
35 #include <yaz/tcpip.h>
36 #include <yaz/options.h>
37 #ifdef USE_XTIMOSI
38 #include <yaz/xmosi.h>
39 #endif
40 #include <yaz/log.h>
41 #include "eventl.h"
42 #include "session.h"
43 #include <yaz/statserv.h>
44
45 static IOCHAN pListener = NULL;
46
47 static char *me = "statserver";
48 /*
49  * default behavior.
50  */
51 int check_options(int argc, char **argv);
52 statserv_options_block control_block = {
53     1,                          /* dynamic mode */
54     0,                          /* threaded mode */
55     0,                          /* one shot (single session) */
56     LOG_DEFAULT_LEVEL,          /* log level */
57     "",                         /* no PDUs */
58     "",                         /* diagnostic output to stderr */
59     "tcp:@:9999",               /* default listener port */
60     PROTO_Z3950,                /* default application protocol */
61     60,                         /* idle timeout (minutes) */
62     1024*1024,                  /* maximum PDU size (approx.) to allow */
63     "default-config",           /* configuration name to pass to backend */
64     "",                         /* set user id */
65     0,                          /* bend_start handler */
66     0,                          /* bend_stop handler */
67     check_options,              /* Default routine, for checking the run-time arguments */
68     check_ip_tcpd,
69     "",
70     0,                          /* default value for inet deamon */
71     0,                          /* handle (for service, etc) */
72     0,                          /* bend_init handle */
73     0                           /* bend_close handle */
74 #ifdef WIN32
75     ,"Z39.50 Server",           /* NT Service Name */
76     "Server",                   /* NT application Name */
77     "",                         /* NT Service Dependencies */
78     "Z39.50 Server"             /* NT Service Display Name */
79 #endif /* WIN32 */
80 };
81
82 /*
83  * handle incoming connect requests.
84  * The dynamic mode is a bit tricky mostly because we want to avoid
85  * doing all of the listening and accepting in the parent - it's
86  * safer that way.
87  */
88 #ifdef WIN32
89
90 typedef struct _ThreadList ThreadList;
91
92 struct _ThreadList
93 {
94     HANDLE hThread;
95     IOCHAN pIOChannel;
96     ThreadList *pNext;
97 };
98
99 static ThreadList *pFirstThread;
100 static CRITICAL_SECTION Thread_CritSect;
101 static BOOL bInitialized = FALSE;
102
103 static void ThreadList_Initialize()
104 {
105     /* Initialize the critical Sections */
106     InitializeCriticalSection(&Thread_CritSect);
107
108      /* Set the first thraed */
109     pFirstThread = NULL;
110
111     /* we have been initialized */
112     bInitialized = TRUE;
113 }
114
115 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
116 {
117     /* Only one thread can go through this section at a time */
118     EnterCriticalSection(&Thread_CritSect);
119
120     {
121         /* Lets create our new object */
122         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
123         pNewThread->hThread = hThread;
124         pNewThread->pIOChannel = pIOChannel;
125         pNewThread->pNext = pFirstThread;
126         pFirstThread = pNewThread;
127
128         /* Lets let somebody else create a new object now */
129         LeaveCriticalSection(&Thread_CritSect);
130     }
131 }
132
133 void statserv_remove(IOCHAN pIOChannel)
134 {
135     /* Only one thread can go through this section at a time */
136     EnterCriticalSection(&Thread_CritSect);
137
138     {
139         ThreadList *pCurrentThread = pFirstThread;
140         ThreadList *pNextThread;
141         ThreadList *pPrevThread =NULL;
142
143         /* Step through alll the threads */
144         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
145         {
146             /* We only need to compare on the IO Channel */
147             if (pCurrentThread->pIOChannel == pIOChannel)
148             {
149                 /* We have found the thread we want to delete */
150                 /* First of all reset the next pointers */
151                 if (pPrevThread == NULL)
152                     pFirstThread = pCurrentThread->pNext;
153                 else
154                     pPrevThread->pNext = pCurrentThread->pNext;
155
156                 /* All we need todo now is delete the memory */
157                 free(pCurrentThread);
158
159                 /* No need to look at any more threads */
160                 pNextThread = NULL;
161             }
162             else
163             {
164                 /* We need to look at another thread */
165                 pNextThread = pCurrentThread->pNext;
166                 pPrevThread = pCurrentThread;
167             }
168         }
169
170         /* Lets let somebody else remove an object now */
171         LeaveCriticalSection(&Thread_CritSect);
172     }
173 }
174
175 /* WIN32 statserv_closedown */
176 void statserv_closedown()
177 {
178     /* Shouldn't do anything if we are not initialized */
179     if (bInitialized)
180     {
181         int iHandles = 0;
182         HANDLE *pThreadHandles = NULL;
183
184         /* We need to stop threads adding and removing while we */
185         /* start the closedown process */
186         EnterCriticalSection(&Thread_CritSect);
187
188         {
189             /* We have exclusive access to the thread stuff now */
190             /* Y didn't i use a semaphore - Oh well never mind */
191             ThreadList *pCurrentThread = pFirstThread;
192
193             /* Before we do anything else, we need to shutdown the listener */
194             if (pListener != NULL)
195                 iochan_destroy(pListener);
196
197             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
198             {
199                 /* Just destroy the IOCHAN, that should do the trick */
200                 iochan_destroy(pCurrentThread->pIOChannel);
201                 closesocket(pCurrentThread->pIOChannel->fd);
202
203                 /* Keep a running count of our handles */
204                 iHandles++;
205             }
206
207             if (iHandles > 0)
208             {
209                 HANDLE *pCurrentHandle ;
210
211                 /* Allocate the thread handle array */
212                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
213                 pCurrentHandle = pThreadHandles; 
214
215                 for (pCurrentThread = pFirstThread;
216                      pCurrentThread != NULL;
217                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
218                 {
219                     /* Just the handle */
220                     *pCurrentHandle = pCurrentThread->hThread;
221                 }
222             }
223
224             /* We can now leave the critical section */
225             LeaveCriticalSection(&Thread_CritSect);
226         }
227
228         /* Now we can really do something */
229         if (iHandles > 0)
230         {
231             logf (LOG_LOG, "waiting for %d to die", iHandles);
232             /* This will now wait, until all the threads close */
233             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
234
235             /* Free the memory we allocated for the handle array */
236             free(pThreadHandles);
237         }
238
239         if (control_block.bend_stop)
240             (*control_block.bend_stop)(&control_block);
241         /* No longer require the critical section, since all threads are dead */
242         DeleteCriticalSection(&Thread_CritSect);
243     }
244 }
245
246 void __cdecl event_loop_thread (IOCHAN iochan)
247 {
248     event_loop (&iochan);
249 }
250
251 /* WIN32 listener */
252 static void listener(IOCHAN h, int event)   
253 {
254     COMSTACK line = (COMSTACK) iochan_getdata(h);
255     association *newas;
256     int res;
257     HANDLE newHandle;
258
259     if (event == EVENT_INPUT)
260     {
261         if ((res = cs_listen(line, 0, 0)) < 0)
262         {
263             yaz_log(LOG_FATAL, "cs_listen failed");
264             return;
265         }
266         else if (res == 1)
267             return;
268         yaz_log(LOG_DEBUG, "listen ok");
269         iochan_setevent(h, EVENT_OUTPUT);
270         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
271     }
272     else if (event == EVENT_OUTPUT)
273     {
274         COMSTACK new_line = cs_accept(line);
275         IOCHAN new_chan;
276         char *a = NULL;
277
278         if (!new_line)
279         {
280             yaz_log(LOG_FATAL, "Accept failed.");
281             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT);
282             return;
283         }
284         yaz_log(LOG_DEBUG, "Accept ok");
285
286         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
287                                        EVENT_INPUT)))
288         {
289             yaz_log(LOG_FATAL, "Failed to create iochan");
290             iochan_destroy(h);
291             return;
292         }
293
294         yaz_log(LOG_DEBUG, "Creating association");
295         if (!(newas = create_association(new_chan, new_line)))
296         {
297             yaz_log(LOG_FATAL, "Failed to create new assoc.");
298             iochan_destroy(h);
299             return;
300         }
301         newas->cs_get_mask = EVENT_INPUT;
302         newas->cs_put_mask = 0;
303         newas->cs_accept_mask = 0;
304
305         yaz_log(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
306         iochan_setdata(new_chan, newas);
307         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
308
309         /* Now what we need todo is create a new thread with this iochan as
310            the parameter */
311         newHandle = (HANDLE) _beginthread(event_loop_thread, 0, new_chan);
312         if (newHandle == (HANDLE) -1)
313         {
314             
315             yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
316             iochan_destroy(h);
317             return;
318         }
319         /* We successfully created the thread, so add it to the list */
320         statserv_add(newHandle, new_chan);
321
322         yaz_log(LOG_DEBUG, "Created new thread, id = %ld iochan %p",(long) newHandle, new_chan);
323         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
324     }
325     else
326     {
327         yaz_log(LOG_FATAL, "Bad event on listener.");
328         iochan_destroy(h);
329         return;
330     }
331 }
332
333 int statserv_must_terminate(void)
334 {
335     return 0;
336 }
337
338 #else /* ! WIN32 */
339
340 static int term_flag = 0;
341 /* To save having an #ifdef in event_loop we need to
342    define this empty function 
343 */
344 int statserv_must_terminate(void)
345 {
346     return term_flag;
347 }
348
349 void statserv_remove(IOCHAN pIOChannel)
350 {
351 }
352
353 void statserv_closedown()
354 {
355     IOCHAN p;
356
357     if (control_block.bend_stop)
358         (*control_block.bend_stop)(&control_block);
359     for (p = pListener; p; p = p->next)
360     {
361         iochan_destroy(p);
362     }
363 }
364
365 void sigterm(int sig)
366 {
367     term_flag = 1;
368 }
369
370 static void *new_session (void *vp);
371 static int no_sessions = 0;
372
373 /* UNIX listener */
374 static void listener(IOCHAN h, int event)
375 {
376     COMSTACK line = (COMSTACK) iochan_getdata(h);
377     static int hand[2];
378     static int child = 0;
379     int res;
380
381     if (event == EVENT_INPUT)
382     {
383         if (control_block.dynamic && !child) 
384         {
385             int res;
386
387             ++no_sessions;
388             if (pipe(hand) < 0)
389             {
390                 yaz_log(LOG_FATAL|LOG_ERRNO, "pipe");
391                 iochan_destroy(h);
392                 return;
393             }
394             if ((res = fork()) < 0)
395             {
396                 yaz_log(LOG_FATAL|LOG_ERRNO, "fork");
397                 iochan_destroy(h);
398                 return;
399             }
400             else if (res == 0) /* child */
401             {
402                 char nbuf[100];
403                 IOCHAN pp;
404
405                 close(hand[0]);
406                 child = 1;
407                 for (pp = pListener; pp; pp = iochan_getnext(pp))
408                 {
409                     if (pp != h)
410                     {
411                         COMSTACK l = (COMSTACK)iochan_getdata(pp);
412                         cs_close(l);
413                         iochan_destroy(pp);
414                     }
415                 }
416                 sprintf(nbuf, "%s(%d)", me, getpid());
417                 yaz_log_init(control_block.loglevel, nbuf, 0);
418                 /* ensure that bend_stop is not called when each child exits -
419                    only for the main process .. 
420                 */
421                 control_block.bend_stop = 0;
422             }
423             else /* parent */
424             {
425                 close(hand[1]);
426                 /* wait for child to take the call */
427                 for (;;)
428                 {
429                     char dummy[1];
430                     int res;
431                     
432                     if ((res = read(hand[0], dummy, 1)) < 0 &&
433                                      yaz_errno() != EINTR)
434                     {
435                         yaz_log(LOG_FATAL|LOG_ERRNO, "handshake read");
436                         return;
437                     }
438                     else if (res >= 0)
439                         break;
440                 }
441                 yaz_log(LOG_DEBUG, "P: Child has taken the call");
442                 close(hand[0]);
443                 return;
444             }
445         }
446         if ((res = cs_listen_check(line, 0, 0, control_block.check_ip,
447                                    control_block.daemon_name)) < 0)
448         {
449             yaz_log(LOG_WARN, "cs_listen failed");
450             return;
451         }
452         else if (res == 1)
453             return;
454         yaz_log(LOG_DEBUG, "listen ok");
455         iochan_setevent(h, EVENT_OUTPUT);
456         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
457     }
458     /* in dynamic mode, only the child ever comes down here */
459     else if (event == EVENT_OUTPUT)
460     {
461         COMSTACK new_line = cs_accept(line);
462
463         if (!new_line)
464         {
465             yaz_log(LOG_FATAL, "Accept failed.");
466             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
467             return;
468         }
469         yaz_log(LOG_DEBUG, "accept ok");
470         if (control_block.dynamic)
471         {
472             IOCHAN pp;
473             /* close our half of the listener socket */
474             for (pp = pListener; pp; pp = iochan_getnext(pp))
475             {
476                 COMSTACK l = (COMSTACK)iochan_getdata(pp);
477                 cs_close(l);
478                 iochan_destroy(pp);
479             }
480             /* release dad */
481             yaz_log(LOG_DEBUG, "Releasing parent");
482             close(hand[1]);
483         }
484         else
485         {
486             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
487             ++no_sessions;
488         }
489 #if YAZ_POSIX_THREADS
490         if (control_block.threads)
491         {
492             pthread_t child_thread;
493             pthread_create (&child_thread, 0, new_session, new_line);
494             pthread_detach (child_thread);
495         }
496         else
497             new_session(new_line);
498 #elif YAZ_GNU_THREADS
499         if (control_block.threads)
500         {
501             pth_attr_t attr;
502             pth_t child_thread;
503
504             attr = pth_attr_new ();
505             pth_attr_set (attr, PTH_ATTR_JOINABLE, FALSE);
506             pth_attr_set (attr, PTH_ATTR_STACK_SIZE, 32*1024);
507             pth_attr_set (attr, PTH_ATTR_NAME, "session");
508             yaz_log (LOG_LOG, "pth_spawn begin");
509             child_thread = pth_spawn (attr, new_session, new_line);
510             yaz_log (LOG_LOG, "pth_spawn finish");
511             pth_attr_destroy (attr);
512         }
513         else
514             new_session(new_line);
515 #else
516         new_session(new_line);
517 #endif
518     }
519     else if (event == EVENT_TIMEOUT)
520     {
521         yaz_log(LOG_LOG, "Shutting down listener.");
522         iochan_destroy(h);
523     }
524     else
525     {
526         yaz_log(LOG_FATAL, "Bad event on listener.");
527         iochan_destroy(h);
528     }
529 }
530
531 static void *new_session (void *vp)
532 {
533     char *a;
534     association *newas;
535     IOCHAN new_chan;
536     COMSTACK new_line = (COMSTACK) vp;
537
538     unsigned cs_get_mask, cs_accept_mask, mask =  
539         ((new_line->io_pending & CS_WANT_WRITE) ? EVENT_OUTPUT : 0) |
540         ((new_line->io_pending & CS_WANT_READ) ? EVENT_INPUT : 0);
541
542     if (mask)    
543     {
544         cs_accept_mask = mask;  /* accept didn't complete */
545         cs_get_mask = 0;
546     }
547     else
548     {
549         cs_accept_mask = 0;     /* accept completed.  */
550         cs_get_mask = mask = EVENT_INPUT;
551     }
552
553     if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session, mask)))
554     {
555         yaz_log(LOG_FATAL, "Failed to create iochan");
556         return 0;
557     }
558     if (!(newas = create_association(new_chan, new_line)))
559     {
560         yaz_log(LOG_FATAL, "Failed to create new assoc.");
561         return 0;
562     }
563     newas->cs_accept_mask = cs_accept_mask;
564     newas->cs_get_mask = cs_get_mask;
565
566     iochan_setdata(new_chan, newas);
567     iochan_settimeout(new_chan, control_block.idle_timeout * 60);
568     a = cs_addrstr(new_line);
569     yaz_log(LOG_LOG, "Starting session %d from %s",
570         no_sessions, a ? a : "[Unknown]");
571     if (control_block.threads)
572     {
573         event_loop(&new_chan);
574     }
575     else
576     {
577         new_chan->next = pListener;
578         pListener = new_chan;
579     }
580     return 0;
581 }
582
583 /* UNIX */
584 #endif
585
586 static void inetd_connection(int what)
587 {
588     COMSTACK line;
589     IOCHAN chan;
590     association *assoc;
591     char *addr;
592
593     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
594     {
595         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
596         {
597             if ((assoc = create_association(chan, line)))
598             {
599                 iochan_setdata(chan, assoc);
600                 iochan_settimeout(chan, control_block.idle_timeout * 60);
601                 addr = cs_addrstr(line);
602                 yaz_log(LOG_LOG, "Inetd association from %s",
603                         addr ? addr : "[UNKNOWN]");
604                 assoc->cs_get_mask = EVENT_INPUT;
605             }
606             else
607             {
608                 yaz_log(LOG_FATAL, "Failed to create association structure");
609             }
610             chan->next = pListener;
611             pListener = chan;
612         }
613         else
614         {
615             yaz_log(LOG_FATAL, "Failed to create iochan");
616         }
617     }
618     else
619     {
620         yaz_log(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
621     }
622 }
623
624 /*
625  * Set up a listening endpoint, and give it to the event-handler.
626  */
627 static int add_listener(char *where, int what)
628 {
629     COMSTACK l;
630     void *ap;
631     IOCHAN lst = NULL;
632     const char *mode;
633
634     if (control_block.dynamic)
635         mode = "dynamic";
636     else if (control_block.threads)
637         mode = "threaded";
638     else
639         mode = "static";
640
641     yaz_log(LOG_LOG, "Adding %s %s listener on %s", mode,
642             what == PROTO_SR ? "SR" : "Z3950", where);
643
644     l = cs_create_host(where, 0, &ap);
645     if (!l)
646     {
647         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to listen on %s", where);
648         return -1;
649     }
650     if (cs_bind(l, ap, CS_SERVER) < 0)
651     {
652         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
653         cs_close (l);
654         return -1;
655     }
656     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
657          EVENT_EXCEPT)))
658     {
659         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
660         cs_close (l);
661         return -1;
662     }
663     iochan_setdata(lst, l);
664
665     /* Ensure our listener chain is setup properly */
666     lst->next = pListener;
667     pListener = lst;
668     return 0; /* OK */
669 }
670
671 #ifndef WIN32
672 /* UNIX only (for windows we don't need to catch the signals) */
673 static void catchchld(int num)
674 {
675     while (waitpid(-1, 0, WNOHANG) > 0)
676         ;
677     signal(SIGCHLD, catchchld);
678 }
679 #endif
680
681 statserv_options_block *statserv_getcontrol(void)
682 {
683     static statserv_options_block cb;
684
685     memcpy(&cb, &control_block, sizeof(cb));
686     return &cb;
687 }
688
689 void statserv_setcontrol(statserv_options_block *block)
690 {
691     memcpy(&control_block, block, sizeof(*block));
692 }
693
694 int statserv_start(int argc, char **argv)
695 {
696     int ret;
697
698 #ifdef WIN32
699     /* We need to initialize the thread list */
700     ThreadList_Initialize();
701 /* WIN32 */
702 #endif
703     
704 #ifdef WIN32
705     if ((me = strrchr (argv[0], '\\')))
706         me++;
707     else
708         me = argv[0];
709 #else
710     me = argv[0];
711 #endif
712     if (control_block.options_func(argc, argv))
713         return(1);
714     
715     if (control_block.bend_start)
716         (*control_block.bend_start)(&control_block);
717 #ifdef WIN32
718     yaz_log (LOG_LOG, "Starting server %s", me);
719 #else
720 /* UNIX */
721     if (control_block.inetd)
722         inetd_connection(control_block.default_proto);
723     else
724     {
725         yaz_log (LOG_LOG, "Starting server %s pid=%d", me, getpid());
726 #if 0
727         sigset_t sigs_to_block;
728         
729         sigemptyset(&sigs_to_block);
730         sigaddset (&sigs_to_block, SIGTERM);
731         pthread_sigmask (SIG_BLOCK, &sigs_to_block, 0);
732         /* missing... */
733 #endif
734         if (control_block.dynamic)
735             signal(SIGCHLD, catchchld);
736     }
737     signal (SIGPIPE, SIG_IGN);
738     signal (SIGTERM, sigterm);
739     if (*control_block.setuid)
740     {
741         struct passwd *pw;
742         
743         if (!(pw = getpwnam(control_block.setuid)))
744         {
745             yaz_log(LOG_FATAL, "%s: Unknown user", control_block.setuid);
746             return(1);
747         }
748         if (setuid(pw->pw_uid) < 0)
749         {
750             yaz_log(LOG_FATAL|LOG_ERRNO, "setuid");
751             exit(1);
752         }
753     }
754 /* UNIX */
755 #endif
756     
757     
758     if ((pListener == NULL) && *control_block.default_listen)
759         add_listener(control_block.default_listen,
760                      control_block.default_proto);
761         
762     if (pListener == NULL)
763         ret = 1;
764     else
765     {
766         yaz_log(LOG_LOG, "Entering event loop.");
767         ret = event_loop(&pListener);
768     }
769     return ret;
770 }
771
772 int check_options(int argc, char **argv)
773 {
774     int ret = 0, r;
775     char *arg;
776
777     while ((ret = options("1a:iszSTl:v:u:c:w:t:k:d:", argv, argc, &arg)) != -2)
778     {
779         switch (ret)
780         {
781         case 0:
782             if (add_listener(arg, control_block.default_proto))
783                 return 1;  /* failed to create listener */
784             break;
785         case '1':        
786             control_block.one_shot = 1;
787             control_block.dynamic = 0;
788             break;
789         case 'z':
790             control_block.default_proto = PROTO_Z3950;
791             break;
792         case 's':
793             fprintf (stderr, "%s: SR protocol no longer supported\n", me);
794             exit (1);
795             break;
796         case 'S':
797             control_block.dynamic = 0;
798             break;
799         case 'T':
800 #if YAZ_POSIX_THREADS
801             control_block.dynamic = 0;
802             control_block.threads = 1;
803 #elif YAZ_GNU_THREADS
804             control_block.dynamic = 0;
805             control_block.threads = 1;
806 #else
807             fprintf(stderr, "%s: Threaded mode not available.\n", me);
808             return 1;
809 #endif
810             break;
811         case 'l':
812             strcpy(control_block.logfile, arg ? arg : "");
813             yaz_log_init(control_block.loglevel, me, control_block.logfile);
814             break;
815         case 'v':
816             control_block.loglevel = yaz_log_mask_str(arg);
817             yaz_log_init(control_block.loglevel, me, control_block.logfile);
818             break;
819         case 'a':
820             strcpy(control_block.apdufile, arg ? arg : "");
821             break;
822         case 'u':
823             strcpy(control_block.setuid, arg ? arg : "");
824             break;
825         case 'c':
826             strcpy(control_block.configname, arg ? arg : "");
827             break;
828         case 'd':
829             strcpy(control_block.daemon_name, arg ? arg : "");
830             break;
831         case 't':
832             if (!arg || !(r = atoi(arg)))
833             {
834                 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
835                 return(1);
836             }
837             control_block.idle_timeout = r;
838             break;
839         case  'k':
840             if (!arg || !(r = atoi(arg)))
841             {
842                 fprintf(stderr, "%s: Specify positive size for -k.\n", me);
843                 return(1);
844             }
845             control_block.maxrecordsize = r * 1024;
846             break;
847         case 'i':
848             control_block.inetd = 1;
849             break;
850         case 'w':
851             if (chdir(arg))
852             {
853                 perror(arg);            
854                 return 1;
855             }
856             break;
857         default:
858             fprintf(stderr, "Usage: %s [ -a <pdufile> -v <loglevel>"
859                     " -l <logfile> -u <user> -c <config> -t <minutes>"
860                     " -k <kilobytes> -d <daemon>"
861                         " -ziST1 -w <directory> <listender-addr>... ]\n", me);
862             return 1;
863         }
864     }
865     return 0;
866 }
867
868 #ifdef WIN32
869 typedef struct _Args
870 {
871     char **argv;
872     int argc;
873 } Args; 
874
875 static Args ArgDetails;
876
877 /* name of the executable */
878 #define SZAPPNAME            "server"
879
880 /* list of service dependencies - "dep1\0dep2\0\0" */
881 #define SZDEPENDENCIES       ""
882
883 int statserv_main(int argc, char **argv,
884                   bend_initresult *(*bend_init)(bend_initrequest *r),
885                   void (*bend_close)(void *handle))
886 {
887     statserv_options_block *cb = statserv_getcontrol();
888     
889     cb->bend_init = bend_init;
890     cb->bend_close = bend_close;
891
892     statserv_setcontrol(cb);
893
894     /* Lets setup the Arg structure */
895     ArgDetails.argc = argc;
896     ArgDetails.argv = argv;
897     
898     /* Now setup the service with the service controller */
899     SetupService(argc, argv, &ArgDetails, SZAPPNAME,
900                  cb->service_name, /* internal service name */
901                  cb->service_display_name, /* displayed name */
902                  SZDEPENDENCIES);
903     return 0;
904 }
905
906 int StartAppService(void *pHandle, int argc, char **argv)
907 {
908     /* Initializes the App */
909     return 1;
910 }
911
912 void RunAppService(void *pHandle)
913 {
914     Args *pArgs = (Args *)pHandle;
915     
916     /* Starts the app running */
917     statserv_start(pArgs->argc, pArgs->argv);
918 }
919
920 void StopAppService(void *pHandle)
921 {
922     /* Stops the app */
923     statserv_closedown();
924 }
925 /* WIN32 */
926 #else
927 /* UNIX */
928 int statserv_main(int argc, char **argv,
929                   bend_initresult *(*bend_init)(bend_initrequest *r),
930                   void (*bend_close)(void *handle))
931 {
932     int ret;
933     statserv_options_block *cb = statserv_getcontrol();
934     
935     cb->bend_init = bend_init;
936     cb->bend_close = bend_close;
937
938     statserv_setcontrol(cb);
939     ret = statserv_start (argc, argv);
940     statserv_closedown ();
941     return ret;
942 }
943 #endif