965d5d32a6c46b2b05774a081f3bfe24a1bb0b17
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995-1999, 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 Datasystems.
8  *
9  * $Log: statserv.c,v $
10  * Revision 1.61  2000-03-15 12:59:49  adam
11  * Added handle member to statserv_control.
12  *
13  * Revision 1.60  2000/03/14 09:06:11  adam
14  * Added POSIX threads support for frontend server.
15  *
16  * Revision 1.59  1999/11/30 13:47:12  adam
17  * Improved installation. Moved header files to include/yaz.
18  *
19  * Revision 1.58  1999/08/27 09:40:32  adam
20  * Renamed logf function to yaz_log. Removed VC++ project files.
21  *
22  * Revision 1.57  1999/07/06 12:17:15  adam
23  * Added option -1 that runs server once (for profiling purposes).
24  *
25  * Revision 1.56  1999/06/10 11:45:30  adam
26  * Added bend_start, bend_stop handlers and removed pre_init.
27  * Handlers bend_start/bend_stop are called when service/daemon is
28  * started/stopped.
29  *
30  * Revision 1.55  1999/06/10 09:18:54  adam
31  * Modified so that pre_init is called when service/server is started.
32  *
33  * Revision 1.54  1999/04/16 14:45:55  adam
34  * Added interface for tcpd wrapper for access control.
35  *
36  * Revision 1.53  1999/02/02 13:57:39  adam
37  * Uses preprocessor define WIN32 instead of WINDOWS to build code
38  * for Microsoft WIN32.
39  *
40  * Revision 1.52  1998/08/21 14:13:34  adam
41  * Added GNU Configure script to build Makefiles.
42  *
43  * Revision 1.51  1998/07/07 15:51:03  adam
44  * Changed server so that it stops if bind fails - "address already in
45  * use" typically causes this.
46  *
47  * Revision 1.50  1998/06/22 11:32:39  adam
48  * Added 'conditional cs_listen' feature.
49  *
50  * Revision 1.49  1998/02/27 14:04:55  adam
51  * Fixed bug in statserv_remove.
52  *
53  * Revision 1.48  1998/02/11 11:53:36  adam
54  * Changed code so that it compiles as C++.
55  *
56  * Revision 1.47  1998/02/10 10:28:57  adam
57  * Added app_name, service_dependencies, service_display_name and
58  * options_func. options_func allows us to specify a different function
59  * to interogate the command line arguments. The other members allow us
60  * to pass the full service details accross to the service manager (CW).
61  *
62  *
63  * Revision 1.46  1998/01/30 15:24:57  adam
64  * Fixed bug in inetd code. The server listened on tcp:@:9999 even
65  * though it was started in inetd mode.
66  *
67  * Revision 1.45  1998/01/29 13:30:23  adam
68  * Better event handle system for NT/Unix.
69  *
70  * Revision 1.44  1997/11/07 13:31:52  adam
71  * Added NT Service name part of statserv_options_block. Moved NT
72  * service utility to server library.
73  *
74  * Revision 1.43  1997/10/31 12:20:09  adam
75  * Improved memory debugging for xmalloc/nmem.c. References to NMEM
76  * instead of ODR in n ESPEC-1 handling in source d1_espec.c.
77  * Bug fix: missing fclose in data1_read_espec1.
78  *
79  * Revision 1.42  1997/10/27 14:03:02  adam
80  * Added new member to statserver_options_block, pre_init, which
81  * specifies a callback to be invoked after command line parsing and
82  * before the server listens for the first time.
83  *
84  * Revision 1.41  1997/09/29 07:19:32  adam
85  * Server library uses nmem_init/nmem_exit. The log prefix no longer
86  * includes leading path on NT.
87  *
88  * Revision 1.40  1997/09/17 12:10:41  adam
89  * YAZ version 1.4.
90  *
91  * Revision 1.39  1997/09/09 10:10:19  adam
92  * Another MSV5.0 port. Changed projects to include proper
93  * library/include paths.
94  * Server starts server in test-mode when no options are given.
95  *
96  * Revision 1.38  1997/09/04 14:19:14  adam
97  * Added credits.
98  *
99  * Revision 1.37  1997/09/01 08:53:01  adam
100  * New windows NT/95 port using MSV5.0. The test server 'ztest' was
101  * moved a separate directory. MSV5.0 project server.dsp created.
102  * As an option, the server can now operate as an NT service.
103  *
104  * Revision 1.36  1996/07/06 19:58:36  quinn
105  * System headerfiles gathered in yconfig
106  *
107  * Revision 1.35  1996/05/29  10:03:28  quinn
108  * Options work
109  *
110  * Revision 1.34  1996/02/21  13:12:07  quinn
111  * *** empty log message ***
112  *
113  * Revision 1.33  1996/02/10  12:23:49  quinn
114  * Enable inetd operations fro TCP/IP stack
115  *
116  * Revision 1.32  1996/01/19  15:41:52  quinn
117  * *** empty log message ***
118  *
119  * Revision 1.31  1995/11/17  11:09:39  adam
120  * Added new option '-c' to specify configuration name in control block.
121  *
122  * Revision 1.30  1995/11/01  13:54:59  quinn
123  * Minor adjustments
124  *
125  * Revision 1.29  1995/10/30  12:41:29  quinn
126  * Added hostname lookup for server.
127  *
128  * Revision 1.28  1995/09/29  17:12:30  quinn
129  * Smallish
130  *
131  * Revision 1.27  1995/09/27  15:03:02  quinn
132  * Modified function heads & prototypes.
133  *
134  * Revision 1.26  1995/08/29  14:44:51  quinn
135  * Reset timeouts.
136  *
137  * Revision 1.25  1995/08/29  11:18:02  quinn
138  * Added code to receive close
139  *
140  * Revision 1.24  1995/06/16  10:31:39  quinn
141  * Added session timeout.
142  *
143  * Revision 1.23  1995/06/15  12:30:48  quinn
144  * Setuid-facility.
145  *
146  * Revision 1.22  1995/06/15  07:45:17  quinn
147  * Moving to v3.
148  *
149  * Revision 1.21  1995/06/06  08:15:40  quinn
150  * Cosmetic.
151  *
152  * Revision 1.20  1995/05/29  08:12:09  quinn
153  * Moved oid to util
154  *
155  * Revision 1.19  1995/05/16  09:37:27  quinn
156  * Fixed bug
157  *
158  * Revision 1.18  1995/05/16  08:51:09  quinn
159  * License, documentation, and memory fixes
160  *
161  * Revision 1.17  1995/05/15  11:56:42  quinn
162  * Asynchronous facilities. Restructuring of seshigh code.
163  *
164  * Revision 1.16  1995/04/10  10:23:40  quinn
165  * Some work to add scan and other things.
166  *
167  * Revision 1.15  1995/03/31  10:16:51  quinn
168  * Fixed logging.
169  *
170  * Revision 1.14  1995/03/31  09:18:58  quinn
171  * Added logging.
172  *
173  * Revision 1.13  1995/03/30  16:08:39  quinn
174  * Little mods.
175  *
176  * Revision 1.12  1995/03/30  13:29:02  quinn
177  * Smallish
178  *
179  * Revision 1.11  1995/03/30  12:18:17  quinn
180  * Fixed bug.
181  *
182  * Revision 1.10  1995/03/29  15:40:16  quinn
183  * Ongoing work. Statserv is now dynamic by default
184  *
185  * Revision 1.9  1995/03/27  08:34:30  quinn
186  * Added dynamic server functionality.
187  * Released bindings to session.c (is now redundant)
188  *
189  * Revision 1.8  1995/03/20  09:46:26  quinn
190  * Added osi support.
191  *
192  * Revision 1.7  1995/03/16  13:29:04  quinn
193  * Partitioned server.
194  *
195  * Revision 1.6  1995/03/15  15:18:52  quinn
196  * Little changes to better support nonblocking I/O
197  * Added backend.h
198  *
199  * Revision 1.5  1995/03/15  08:37:45  quinn
200  * Now we're pretty much set for nonblocking I/O.
201  *
202  * Revision 1.4  1995/03/14  16:59:48  quinn
203  * Bug-fixes
204  *
205  * Revision 1.3  1995/03/14  11:30:15  quinn
206  * Works better now.
207  *
208  * Revision 1.2  1995/03/14  10:28:03  quinn
209  * More work on demo server.
210  *
211  * Revision 1.1  1995/03/10  18:22:45  quinn
212  * The rudiments of an asynchronous server.
213  *
214  */
215
216 #include <stdio.h>
217 #include <string.h>
218 #ifdef WIN32
219 #include <process.h>
220 #include <winsock.h>
221 #include <direct.h>
222 #include "service.h"
223 #else
224 #if HAVE_PTHREAD_H
225 #include <pthread.h>
226 #endif
227 #include <unistd.h>
228 #include <pwd.h>
229 #endif
230 #include <fcntl.h>
231 #include <signal.h>
232 #include <errno.h>
233
234 #include <yaz/comstack.h>
235 #include <yaz/tcpip.h>
236 #include <yaz/options.h>
237 #ifdef USE_XTIMOSI
238 #include <yaz/xmosi.h>
239 #endif
240 #include <yaz/log.h>
241 #include "eventl.h"
242 #include "session.h"
243 #include <yaz/statserv.h>
244
245 static IOCHAN pListener = NULL;
246
247 static char *me = "statserver";
248 /*
249  * default behavior.
250  */
251 int check_options(int argc, char **argv);
252 statserv_options_block control_block = {
253     1,                          /* dynamic mode */
254     0,                          /* threaded mode */
255     0,                          /* one shot (single session) */
256     LOG_DEFAULT_LEVEL,          /* log level */
257     "",                         /* no PDUs */
258     "",                         /* diagnostic output to stderr */
259     "tcp:@:9999",               /* default listener port */
260     PROTO_Z3950,                /* default application protocol */
261     60,                         /* idle timeout (minutes) */
262     1024*1024,                  /* maximum PDU size (approx.) to allow */
263     "default-config",           /* configuration name to pass to backend */
264     "",                         /* set user id */
265     0,                          /* bend_start handler */
266     0,                          /* bend_stop handler */
267     check_options,              /* Default routine, for checking the run-time arguments */
268     check_ip_tcpd,
269     "",
270     0,                          /* default value for inet deamon */
271     0,                          /* handle (for service, etc) */
272
273 #ifdef WIN32
274     ,"Z39.50 Server",           /* NT Service Name */
275     "Server",                   /* NT application Name */
276     "",                         /* NT Service Dependencies */
277     "Z39.50 Server"             /* NT Service Display Name */
278 #endif /* WIN32 */
279 };
280
281 /*
282  * handle incoming connect requests.
283  * The dynamic mode is a bit tricky mostly because we want to avoid
284  * doing all of the listening and accepting in the parent - it's
285  * safer that way.
286  */
287 #ifdef WIN32
288
289 typedef struct _ThreadList ThreadList;
290
291 struct _ThreadList
292 {
293     HANDLE hThread;
294     IOCHAN pIOChannel;
295     ThreadList *pNext;
296 };
297
298 static ThreadList *pFirstThread;
299 static CRITICAL_SECTION Thread_CritSect;
300 static BOOL bInitialized = FALSE;
301
302 static void ThreadList_Initialize()
303 {
304     /* Initialize the critical Sections */
305     InitializeCriticalSection(&Thread_CritSect);
306
307      /* Set the first thraed */
308     pFirstThread = NULL;
309
310     /* we have been initialized */
311     bInitialized = TRUE;
312 }
313
314 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
315 {
316     /* Only one thread can go through this section at a time */
317     EnterCriticalSection(&Thread_CritSect);
318
319     {
320         /* Lets create our new object */
321         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
322         pNewThread->hThread = hThread;
323         pNewThread->pIOChannel = pIOChannel;
324         pNewThread->pNext = pFirstThread;
325         pFirstThread = pNewThread;
326
327         /* Lets let somebody else create a new object now */
328         LeaveCriticalSection(&Thread_CritSect);
329     }
330 }
331
332 void statserv_remove(IOCHAN pIOChannel)
333 {
334     /* Only one thread can go through this section at a time */
335     EnterCriticalSection(&Thread_CritSect);
336
337     {
338         ThreadList *pCurrentThread = pFirstThread;
339         ThreadList *pNextThread;
340         ThreadList *pPrevThread =NULL;
341
342         /* Step through alll the threads */
343         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
344         {
345             /* We only need to compare on the IO Channel */
346             if (pCurrentThread->pIOChannel == pIOChannel)
347             {
348                 /* We have found the thread we want to delete */
349                 /* First of all reset the next pointers */
350                 if (pPrevThread == NULL)
351                     pFirstThread = pCurrentThread->pNext;
352                 else
353                     pPrevThread->pNext = pCurrentThread->pNext;
354
355                 /* All we need todo now is delete the memory */
356                 free(pCurrentThread);
357
358                 /* No need to look at any more threads */
359                 pNextThread = NULL;
360             }
361             else
362             {
363                 /* We need to look at another thread */
364                 pNextThread = pCurrentThread->pNext;
365                 pPrevThread = pCurrentThread;
366             }
367         }
368
369         /* Lets let somebody else remove an object now */
370         LeaveCriticalSection(&Thread_CritSect);
371     }
372 }
373
374 void statserv_closedown()
375 {
376     /* Shouldn't do anything if we are not initialized */
377     if (bInitialized)
378     {
379         int iHandles = 0;
380         HANDLE *pThreadHandles = NULL;
381
382         /* We need to stop threads adding and removing while we */
383         /* start the closedown process */
384         EnterCriticalSection(&Thread_CritSect);
385
386         {
387             /* We have exclusive access to the thread stuff now */
388             /* Y didn't i use a semaphore - Oh well never mind */
389             ThreadList *pCurrentThread = pFirstThread;
390
391             /* Before we do anything else, we need to shutdown the listener */
392             if (pListener != NULL)
393                 iochan_destroy(pListener);
394
395             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
396             {
397                 /* Just destroy the IOCHAN, that should do the trick */
398                 iochan_destroy(pCurrentThread->pIOChannel);
399
400                 /* Keep a running count of our handles */
401                 iHandles++;
402             }
403
404             if (iHandles > 0)
405             {
406                 HANDLE *pCurrentHandle ;
407
408                 /* Allocate the thread handle array */
409                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
410                 pCurrentHandle = pThreadHandles; 
411
412                 for (pCurrentThread = pFirstThread;
413                      pCurrentThread != NULL;
414                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
415                 {
416                     /* Just the handle */
417                     *pCurrentHandle = pCurrentThread->hThread;
418                 }
419             }
420
421             /* We can now leave the critical section */
422             LeaveCriticalSection(&Thread_CritSect);
423         }
424
425         /* Now we can really do something */
426         if (iHandles > 0)
427         {
428             /* This will now wait, until all the threads close */
429             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
430
431             /* Free the memory we allocated for the handle array */
432             free(pThreadHandles);
433         }
434
435         if (control_block.bend_stop)
436             (*control_block.bend_stop)(&control_block);
437         /* No longer require the critical section, since all threads are dead */
438         DeleteCriticalSection(&Thread_CritSect);
439     }
440 }
441
442 int __stdcall event_loop_thread (IOCHAN iochan)
443 {
444     return event_loop (&iochan);
445 }
446
447 static void listener(IOCHAN h, int event)
448 {
449     COMSTACK line = (COMSTACK) iochan_getdata(h);
450     association *newas;
451     int res;
452     HANDLE NewHandle;
453
454     if (event == EVENT_INPUT)
455     {
456         if ((res = cs_listen(line, 0, 0)) < 0)
457         {
458             yaz_log(LOG_FATAL, "cs_listen failed");
459             return;
460         }
461         else if (res == 1)
462             return;
463         yaz_log(LOG_DEBUG, "listen ok");
464         iochan_setevent(h, EVENT_OUTPUT);
465         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
466     }
467     else if (event == EVENT_OUTPUT)
468     {
469         COMSTACK new_line;
470         IOCHAN new_chan;
471         char *a = NULL;
472         DWORD ThreadId;
473
474         if (!(new_line = cs_accept(line)))
475         {
476             yaz_log(LOG_FATAL, "Accept failed.");
477             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
478             return;
479         }
480         yaz_log(LOG_DEBUG, "Accept ok");
481
482         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
483                                        EVENT_INPUT)))
484         {
485             yaz_log(LOG_FATAL, "Failed to create iochan");
486             iochan_destroy(h);
487             return;
488         }
489
490         yaz_log(LOG_DEBUG, "Creating association");
491         if (!(newas = create_association(new_chan, new_line)))
492         {
493             yaz_log(LOG_FATAL, "Failed to create new assoc.");
494             iochan_destroy(h);
495             return;
496         }
497         yaz_log(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
498         iochan_setdata(new_chan, newas);
499         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
500 #ifndef WIN32
501         yaz_log(LOG_DEBUG, "Determining client address");
502         a = cs_addrstr(new_line);
503         yaz_log(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
504 #endif
505         /* Now what we need todo is create a new thread with this iochan as
506            the parameter */
507         /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop_thread,
508            new_chan, 0, &ThreadId) == NULL) */
509         /* Somehow, somewhere we need to store this thread id, otherwise we
510            won't be able to close cleanly */
511         NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop_thread,
512                                            new_chan, 0, &ThreadId);
513         if (NewHandle == (HANDLE)-1)
514         {
515             
516             yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
517             iochan_destroy(h);
518             return;
519         }
520         /* We successfully created the thread, so add it to the list */
521         statserv_add(NewHandle, new_chan);
522
523         yaz_log(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
524         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
525     }
526     else
527     {
528         yaz_log(LOG_FATAL, "Bad event on listener.");
529         iochan_destroy(h);
530         return;
531     }
532 }
533
534 #else /* WIN32 */
535
536 /* To save having an #ifdef in event_loop we need to define this empty function */
537 void statserv_remove(IOCHAN pIOChannel)
538 {
539 }
540
541 void statserv_closedown()
542 {
543     IOCHAN p;
544     for (p = pListener; p; p = p->next)
545         iochan_destroy(p);
546 }
547
548 static void *new_session (void *vp);
549
550 static void listener(IOCHAN h, int event)
551 {
552     COMSTACK line = (COMSTACK) iochan_getdata(h);
553     static int hand[2];
554     static int child = 0;
555     int res;
556
557     if (event == EVENT_INPUT)
558     {
559         if (control_block.dynamic && !child) 
560         {
561             int res;
562
563             if (pipe(hand) < 0)
564             {
565                 yaz_log(LOG_FATAL|LOG_ERRNO, "pipe");
566                 iochan_destroy(h);
567                 return;
568             }
569             if ((res = fork()) < 0)
570             {
571                 yaz_log(LOG_FATAL|LOG_ERRNO, "fork");
572                 iochan_destroy(h);
573                 return;
574             }
575             else if (res == 0) /* child */
576             {
577                 char nbuf[100];
578                 IOCHAN pp;
579
580                 close(hand[0]);
581                 child = 1;
582                 for (pp = pListener; pp; pp = iochan_getnext(pp))
583                 {
584                     if (pp != h)
585                     {
586                         COMSTACK l = (COMSTACK)iochan_getdata(pp);
587                         cs_close(l);
588                         iochan_destroy(pp);
589                     }
590                 }
591                 sprintf(nbuf, "%s(%d)", me, getpid());
592                 log_init(control_block.loglevel, nbuf, 0);
593             }
594             else /* parent */
595             {
596                 close(hand[1]);
597                 /* wait for child to take the call */
598                 for (;;)
599                 {
600                     char dummy[1];
601                     int res;
602                     
603                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
604                     {
605                         yaz_log(LOG_FATAL|LOG_ERRNO, "handshake read");
606                         return;
607                     }
608                     else if (res >= 0)
609                         break;
610                 }
611                 yaz_log(LOG_DEBUG, "P: Child has taken the call");
612                 close(hand[0]);
613                 return;
614             }
615         }
616         if ((res = cs_listen_check(line, 0, 0, control_block.check_ip,
617                                    control_block.daemon_name)) < 0)
618         {
619             yaz_log(LOG_WARN, "cs_listen failed");
620             return;
621         }
622         else if (res == 1)
623             return;
624         yaz_log(LOG_DEBUG, "listen ok");
625         iochan_setevent(h, EVENT_OUTPUT);
626         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
627     }
628     /* in dynamic mode, only the child ever comes down here */
629     else if (event == EVENT_OUTPUT)
630     {
631         COMSTACK new_line;
632
633         if (!(new_line = cs_accept(line)))
634         {
635             yaz_log(LOG_FATAL, "Accept failed.");
636             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
637             return;
638         }
639         yaz_log(LOG_DEBUG, "accept ok");
640         if (control_block.dynamic)
641         {
642             IOCHAN pp;
643             /* close our half of the listener socket */
644             for (pp = pListener; pp; pp = iochan_getnext(pp))
645             {
646                 COMSTACK l = (COMSTACK)iochan_getdata(pp);
647                 cs_close(l);
648                 iochan_destroy(pp);
649             }
650             /* release dad */
651             yaz_log(LOG_DEBUG, "Releasing parent");
652             close(hand[1]);
653         }
654         else
655             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
656
657 #if HAVE_PTHREAD_H
658         if (control_block.threads)
659         {
660             pthread_t child_thread;
661             pthread_create (&child_thread, 0, new_session, new_line);
662             pthread_detach (child_thread);
663         }
664         else
665             new_session(new_line);
666 #else
667         new_session(new_line);
668 #endif
669     }
670     else
671     {
672         yaz_log(LOG_FATAL, "Bad event on listener.");
673         iochan_destroy(h);
674         return;
675     }
676 }
677
678 static void *new_session (void *vp)
679 {
680     char *a;
681     association *newas;
682     IOCHAN new_chan;
683     COMSTACK new_line = (COMSTACK) vp;
684     if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
685                                    EVENT_INPUT)))
686     {
687         yaz_log(LOG_FATAL, "Failed to create iochan");
688         return 0;
689     }
690     if (!(newas = create_association(new_chan, new_line)))
691     {
692         yaz_log(LOG_FATAL, "Failed to create new assoc.");
693         return 0;
694     }
695     iochan_setdata(new_chan, newas);
696     iochan_settimeout(new_chan, control_block.idle_timeout * 60);
697     a = cs_addrstr(new_line);
698     yaz_log(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
699     if (control_block.threads)
700     {
701         event_loop(&new_chan);
702     }
703     else
704     {
705         new_chan->next = pListener;
706         pListener = new_chan;
707     }
708     return 0;
709 }
710
711 #endif /* WIN32 */
712
713 static void inetd_connection(int what)
714 {
715     COMSTACK line;
716     IOCHAN chan;
717     association *assoc;
718     char *addr;
719
720     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
721     {
722         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
723         {
724             if ((assoc = create_association(chan, line)))
725             {
726                 iochan_setdata(chan, assoc);
727                 iochan_settimeout(chan, control_block.idle_timeout * 60);
728                 addr = cs_addrstr(line);
729                 yaz_log(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
730             }
731             else
732             {
733                 yaz_log(LOG_FATAL, "Failed to create association structure");
734             }
735             chan->next = pListener;
736             pListener = chan;
737         }
738         else
739         {
740             yaz_log(LOG_FATAL, "Failed to create iochan");
741         }
742     }
743     else
744     {
745         yaz_log(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
746     }
747 }
748
749 /*
750  * Set up a listening endpoint, and give it to the event-handler.
751  */
752 static void add_listener(char *where, int what)
753 {
754     COMSTACK l;
755     CS_TYPE type;
756     char mode[100], addr[100];
757     void *ap;
758     IOCHAN lst = NULL;
759
760     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
761     {
762         yaz_log (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
763         return;
764     }
765     if (!strcmp(mode, "tcp"))
766         type = tcpip_type;
767     else if (!strcmp(mode, "osi"))
768     {
769 #ifdef USE_XTIMOSI
770         type = mosi_type;
771 #else
772         yaz_log (LOG_WARN, "OSI Transport not allowed by configuration.");
773         return;
774 #endif
775     }
776     else
777     {
778         yaz_log (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
779         return;
780     }
781     yaz_log(LOG_LOG, "Adding %s %s listener on %s",
782             control_block.dynamic ? "dynamic" : 
783             (control_block.threads ? "threaded" : "static"),
784         what == PROTO_SR ? "SR" : "Z3950", where);
785     if (!(l = cs_create(type, 0, what)))
786     {
787         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
788         return;
789     }
790     ap = cs_straddr (l, addr);
791     if (!ap)
792     {
793         fprintf(stderr, "Address resolution failed.\n");
794         cs_close (l);
795         return;
796     }
797     if (cs_bind(l, ap, CS_SERVER) < 0)
798     {
799         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
800         cs_close (l);
801         return;
802     }
803     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
804          EVENT_EXCEPT)))
805     {
806         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
807         cs_close (l);
808         return;
809     }
810     iochan_setdata(lst, l);
811
812     /* Ensure our listener chain is setup properly */
813     lst->next = pListener;
814     pListener = lst;
815 }
816
817 #ifndef WIN32
818 /* For windows we don't need to catch the signals */
819 static void catchchld(int num)
820 {
821     while (waitpid(-1, 0, WNOHANG) > 0)
822         ;
823     signal(SIGCHLD, catchchld);
824 }
825 #endif /* WIN32 */
826
827 statserv_options_block *statserv_getcontrol(void)
828 {
829     static statserv_options_block cb;
830
831     memcpy(&cb, &control_block, sizeof(cb));
832     return &cb;
833 }
834
835 void statserv_setcontrol(statserv_options_block *block)
836 {
837     memcpy(&control_block, block, sizeof(*block));
838 }
839
840 int statserv_start(int argc, char **argv)
841 {
842     int ret;
843
844     nmem_init ();
845 #ifdef WIN32
846     /* We need to initialize the thread list */
847     ThreadList_Initialize();
848 #endif /* WIN32 */
849
850 #ifdef WIN32
851     if ((me = strrchr (argv[0], '\\')))
852         me++;
853     else
854         me = argv[0];
855 #else
856     me = argv[0];
857 #endif
858     if (control_block.options_func(argc, argv))
859         return(1);
860
861     if (control_block.bend_start)
862         (*control_block.bend_start)(&control_block);
863 #ifndef WIN32
864     if (control_block.inetd)
865         inetd_connection(control_block.default_proto);
866     else
867     {
868         if (control_block.dynamic)
869             signal(SIGCHLD, catchchld);
870     }
871     if (*control_block.setuid)
872     {
873         struct passwd *pw;
874         
875         if (!(pw = getpwnam(control_block.setuid)))
876         {
877             yaz_log(LOG_FATAL, "%s: Unknown user", control_block.setuid);
878             return(1);
879         }
880         if (setuid(pw->pw_uid) < 0)
881         {
882             yaz_log(LOG_FATAL|LOG_ERRNO, "setuid");
883             exit(1);
884         }
885     }
886 #endif /* WIN32 */
887
888     if ((pListener == NULL) && *control_block.default_listen)
889         add_listener(control_block.default_listen,
890                      control_block.default_proto);
891         
892     if (pListener == NULL)
893         ret = 1;
894     else
895     {
896         yaz_log(LOG_LOG, "Entering event loop.");
897         ret = event_loop(&pListener);
898     }
899     nmem_exit ();
900     return ret;
901 }
902
903 int check_options(int argc, char **argv)
904 {
905     int ret = 0, r;
906     char *arg;
907
908     while ((ret = options("1a:iszSTl:v:u:c:w:t:k:d:", argv, argc, &arg)) != -2)
909     {
910         switch (ret)
911         {
912         case 0:
913             add_listener(arg, control_block.default_proto);
914             break;
915         case '1':        
916             control_block.one_shot = 1;
917             control_block.dynamic = 0;
918             break;
919         case 'z':
920             control_block.default_proto = PROTO_Z3950;
921             break;
922         case 's':
923             control_block.default_proto = PROTO_SR;
924             break;
925         case 'S':
926             control_block.dynamic = 0;
927             break;
928         case 'T':
929 #if HAVE_PTHREAD_H
930             control_block.dynamic = 0;
931             control_block.threads = 1;
932 #else
933             fprintf(stderr, "%s: Threaded mode not available.\n", me);
934             return 1;
935 #endif
936             break;
937         case 'l':
938             strcpy(control_block.logfile, arg ? arg : "");
939             log_init(control_block.loglevel, me, control_block.logfile);
940             break;
941         case 'v':
942             control_block.loglevel = log_mask_str(arg);
943             log_init(control_block.loglevel, me, control_block.logfile);
944             break;
945         case 'a':
946             strcpy(control_block.apdufile, arg ? arg : "");
947             break;
948         case 'u':
949             strcpy(control_block.setuid, arg ? arg : "");
950             break;
951         case 'c':
952             strcpy(control_block.configname, arg ? arg : "");
953             break;
954         case 'd':
955             strcpy(control_block.daemon_name, arg ? arg : "");
956             break;
957         case 't':
958             if (!arg || !(r = atoi(arg)))
959             {
960                 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
961                 return(1);
962             }
963             control_block.idle_timeout = r;
964             break;
965         case  'k':
966             if (!arg || !(r = atoi(arg)))
967             {
968                 fprintf(stderr, "%s: Specify positive size for -k.\n", me);
969                 return(1);
970             }
971             control_block.maxrecordsize = r * 1024;
972             break;
973         case 'i':
974             control_block.inetd = 1;
975             break;
976         case 'w':
977             if (chdir(arg))
978             {
979                 perror(arg);            
980                 return 1;
981             }
982             break;
983         default:
984             fprintf(stderr, "Usage: %s [ -a <pdufile> -v <loglevel>"
985                     " -l <logfile> -u <user> -c <config> -t <minutes>"
986                     " -k <kilobytes> -d <daemon>"
987                         " -zsiST -w <directory> <listender-addr>... ]\n", me);
988             return 1;
989         }
990     }
991     return 0;
992 }
993
994 #ifdef WIN32
995 typedef struct _Args
996 {
997     char **argv;
998     int argc;
999 } Args; 
1000
1001 static Args ArgDetails;
1002
1003 /* name of the executable */
1004 #define SZAPPNAME            "server"
1005
1006 /* list of service dependencies - "dep1\0dep2\0\0" */
1007 #define SZDEPENDENCIES       ""
1008
1009 int statserv_main(int argc, char **argv)
1010 {
1011     statserv_options_block *cb = statserv_getcontrol();
1012     
1013     /* Lets setup the Arg structure */
1014     ArgDetails.argc = argc;
1015     ArgDetails.argv = argv;
1016     
1017     /* Now setup the service with the service controller */
1018     SetupService(argc, argv, &ArgDetails, SZAPPNAME,
1019                  cb->service_name, /* internal service name */
1020                  cb->service_name, /* displayed name of the service */
1021                  SZDEPENDENCIES);
1022     return 0;
1023 }
1024
1025 int StartAppService(void *pHandle, int argc, char **argv)
1026 {
1027     /* Initializes the App */
1028     return 1;
1029 }
1030
1031 void RunAppService(void *pHandle)
1032 {
1033     Args *pArgs = (Args *)pHandle;
1034     
1035     /* Starts the app running */
1036     statserv_start(pArgs->argc, pArgs->argv);
1037 }
1038
1039 void StopAppService(void *pHandle)
1040 {
1041     /* Stops the app */
1042     statserv_closedown();
1043 }
1044 #else
1045 int statserv_main(int argc, char **argv)
1046 {
1047     int ret = statserv_start (argc, argv);
1048     statserv_closedown ();
1049     return ret;
1050 }
1051 #endif