Added support for multi-homed YAZ frontend server. A backend config
[yaz-moved-to-github.git] / src / statserv.c
1 /*
2  * Copyright (C) 1995-2005, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * NT threaded server code by
6  *   Chas Woodfield, Fretwell Downing Informatics.
7  *
8  * $Id: statserv.c,v 1.20 2005-02-01 14:46:47 adam Exp $
9  */
10
11 /**
12  * \file statserv.c
13  * \brief Implements GFS logic
14  */
15
16 #include <stdio.h>
17 #include <string.h>
18 #include <ctype.h>
19 #ifdef WIN32
20 #include <process.h>
21 #include <winsock.h>
22 #include <direct.h>
23 #include "service.h"
24 #endif
25 #if HAVE_SYS_TYPES_H
26 #include <sys/types.h>
27 #endif
28 #if HAVE_SYS_WAIT_H
29 #include <sys/wait.h>
30 #endif
31 #if HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #if HAVE_PWD_H
35 #include <pwd.h>
36 #endif
37
38 #if HAVE_XML2
39 #include <libxml/parser.h>
40 #include <libxml/tree.h>
41 #endif
42
43 #if YAZ_POSIX_THREADS
44 #include <pthread.h>
45 #elif YAZ_GNU_THREADS
46 #include <pth.h>
47 #endif
48
49 #include <fcntl.h>
50 #include <signal.h>
51 #include <errno.h>
52
53 #include <yaz/comstack.h>
54 #include <yaz/tcpip.h>
55 #include <yaz/options.h>
56 #ifdef USE_XTIMOSI
57 #include <yaz/xmosi.h>
58 #endif
59 #include <yaz/log.h>
60 #include "eventl.h"
61 #include "session.h"
62 #include <yaz/statserv.h>
63
64 static IOCHAN pListener = NULL;
65
66 static struct gfs_server *gfs_server_list = 0;
67 static NMEM gfs_nmem = 0;
68
69 static char *me = "statserver"; /* log prefix */
70 static char *programname="statserver"; /* full program name */
71 #if YAZ_POSIX_THREADS
72 static pthread_key_t current_control_tls;
73 static int init_control_tls = 0;
74 #else
75 static statserv_options_block *current_control_block = 0;
76 #endif
77
78 /*
79  * default behavior.
80  */
81 #define STAT_DEFAULT_LOG_LEVEL "none,fatal,warn,log,server,session,request"
82 /* the 'none' clears yaz' own default settings, including [log] */
83
84 int check_options(int argc, char **argv);
85 statserv_options_block control_block = {
86     1,                          /* dynamic mode */
87     0,                          /* threaded mode */
88     0,                          /* one shot (single session) */
89     YLOG_DEFAULT_LEVEL,          /* log level */
90     "",                         /* no PDUs */
91     "",                         /* diagnostic output to stderr */
92     "tcp:@:9999",               /* default listener port */
93     PROTO_Z3950,                /* default application protocol */
94     15,                         /* idle timeout (minutes) */
95     1024*1024,                  /* maximum PDU size (approx.) to allow */
96     "default-config",           /* configuration name to pass to backend */
97     "",                         /* set user id */
98     0,                          /* bend_start handler */
99     0,                          /* bend_stop handler */
100     check_options,              /* Default routine, for checking the run-time arguments */
101     check_ip_tcpd,
102     "",
103     0,                          /* default value for inet deamon */
104     0,                          /* handle (for service, etc) */
105     0,                          /* bend_init handle */
106     0,                          /* bend_close handle */
107 #ifdef WIN32
108     "Z39.50 Server",            /* NT Service Name */
109     "Server",                   /* NT application Name */
110     "",                         /* NT Service Dependencies */
111     "Z39.50 Server",            /* NT Service Display Name */
112 #endif /* WIN32 */
113     0,                          /* SOAP handlers */
114     "",                         /* PID fname */
115     0,                          /* background daemon */
116     "",                         /* SSL certificate filename */
117     "",                         /* XML config filename */
118 };
119
120 static int max_sessions = 0;
121
122 static int logbits_set = 0;
123 static int log_session = 0;
124 static int log_server = 0;
125
126 /** get_logbits sets global loglevel bits */
127 static void get_logbits(int force)
128 { /* needs to be called after parsing cmd-line args that can set loglevels!*/
129     if (force || !logbits_set)
130     {
131         logbits_set = 1;
132         log_session = yaz_log_module_level("session");
133         log_server = yaz_log_module_level("server");
134     }
135 }
136
137
138 static int add_listener(char *where, int listen_id);
139
140 #if HAVE_XML2
141 static xmlDocPtr xml_config_doc = 0;
142 #endif
143
144 #if HAVE_XML2
145 static xmlNodePtr xml_config_get_root()
146 {
147     xmlNodePtr ptr = 0;
148     if (xml_config_doc)
149     {
150         ptr = xmlDocGetRootElement(xml_config_doc);
151         if (!ptr || ptr->type != XML_ELEMENT_NODE ||
152             strcmp((const char *) ptr->name, "yazgfs"))
153         {
154             yaz_log(YLOG_WARN, "Bad/missing root element for config %s",
155                     control_block.xml_config);
156             return 0;
157         
158         }
159     }
160     return ptr;
161 }
162 #endif
163
164 #if HAVE_XML2
165 static char *nmem_dup_xml_content(NMEM n, xmlNodePtr ptr)
166 {
167     unsigned char *cp;
168     xmlNodePtr p;
169     int len = 1;  /* start with 1, because of trailing 0 */
170     char *str;
171     int first = 1; /* whitespace lead flag .. */
172     /* determine length */
173     for (p = ptr; p; p = p->next)
174     {
175         if (p->type == XML_TEXT_NODE)
176             len += strlen(p->content);
177     }
178     /* now allocate for the string */
179     str = nmem_malloc(n, len);
180     *str = '\0'; /* so we can use strcat */
181     for (p = ptr; p; p = p->next)
182     {
183         if (p->type == XML_TEXT_NODE)
184         {
185             cp = p->content;
186             if (first)
187             {
188                 while(*cp && isspace(*cp))
189                     cp++;
190                 if (*cp)
191                     first = 0;  /* reset if we got non-whitespace out */
192             }
193             strcat(str, cp); /* append */
194         }
195     }
196     /* remove trailing whitespace */
197     cp = strlen(str) + str;
198     while ((char*) cp != str && isspace(cp[-1]))
199         cp--;
200     *cp = '\0';
201     /* return resulting string */
202     return str;
203 }
204 #endif
205
206 static struct gfs_server * gfs_server_new()
207 {
208     struct gfs_server *n = nmem_malloc(gfs_nmem, sizeof(*n));
209     memcpy(&n->cb, &control_block, sizeof(control_block));
210     n->next = 0;
211     n->host = 0;
212     n->port = 0;
213     return n;
214 }
215
216 int control_association(association *assoc, const char *host, int force_open)
217 {
218     char vhost[128], *cp;
219     if (host)
220     {
221         strncpy(vhost, host, 127);
222         vhost[127] = '\0';
223         cp = strchr(vhost, ':');
224         if (cp)
225             *cp = '\0';
226         host = vhost;
227     }
228     if (control_block.xml_config[0])
229     {
230         struct gfs_server *gfs;
231         for (gfs = gfs_server_list; gfs; gfs = gfs->next)
232         {
233             int port_match = 0;
234             int host_match = 0;
235             if ( !gfs->host || (host && gfs->host && !strcmp(host, gfs->host)))
236                 host_match = 1;
237             if (assoc->client_chan->port == gfs->port)
238                 port_match= 1;
239             if (port_match && host_match)
240             {
241                 if (force_open ||
242                     (assoc->last_control != &gfs->cb && assoc->backend))
243                 {
244                     statserv_setcontrol(assoc->last_control);
245                     if (assoc->backend && assoc->init)
246                         (assoc->last_control->bend_close)(assoc->backend);
247                     assoc->backend = 0;
248                     xfree(assoc->init);
249                     assoc->init = 0;
250                 }
251                 assoc->last_control = &gfs->cb;
252                 statserv_setcontrol(&gfs->cb);
253                 return 1;
254             }
255         }
256         statserv_setcontrol(0);
257         assoc->last_control = 0;
258         return 0;
259     }
260     else
261     {
262         statserv_setcontrol(&control_block);
263         assoc->last_control = &control_block;
264         return 1;
265     }
266 }
267
268 static void xml_config_read()
269 {
270     struct gfs_server **gfsp = &gfs_server_list;
271 #if HAVE_XML2
272     xmlNodePtr ptr = xml_config_get_root();
273
274     if (!ptr)
275         return;
276     for (ptr = ptr->children; ptr; ptr = ptr->next)
277     {
278         if (ptr->type == XML_ELEMENT_NODE &&
279             !strcmp((const char *) ptr->name, "server"))
280         {
281             xmlNodePtr ptr_children = ptr->children;
282             xmlNodePtr ptr;
283             
284             *gfsp = gfs_server_new();
285             for (ptr = ptr_children; ptr; ptr = ptr->next)
286             {
287                 if (ptr->type == XML_ELEMENT_NODE &&
288                     !strcmp((const char *) ptr->name, "host"))
289                 {
290                     (*gfsp)->host = nmem_dup_xml_content(gfs_nmem,
291                                                          ptr->children);
292                 }
293                 if (ptr->type == XML_ELEMENT_NODE &&
294                     !strcmp((const char *) ptr->name, "port"))
295                 {
296                     (*gfsp)->port = atoi(nmem_dup_xml_content(gfs_nmem,
297                                                          ptr->children));
298                 }
299                 if (ptr->type == XML_ELEMENT_NODE &&
300                     !strcmp((const char *) ptr->name, "config"))
301                 {
302                     strcpy((*gfsp)->cb.configname,
303                            nmem_dup_xml_content(gfs_nmem, ptr->children));
304                 }
305             }
306             gfsp = &(*gfsp)->next;
307         }
308     }
309 #endif
310     *gfsp = 0;
311 }
312
313 static void xml_config_open()
314 {
315     gfs_nmem = nmem_create();
316 #if HAVE_XML2
317     if (control_block.xml_config[0] == '\0')
318         return;
319
320     if (!xml_config_doc)
321     {
322         xml_config_doc = xmlParseFile(control_block.xml_config);
323         if (!xml_config_doc)
324         {
325             yaz_log(YLOG_WARN, "Could not parse %s", control_block.xml_config);
326             return ;
327         }
328     }
329     xml_config_read();
330
331 #endif
332 }
333
334 static void xml_config_close()
335 {
336 #if HAVE_XML2
337     if (xml_config_doc)
338     {
339         xmlFreeDoc(xml_config_doc);
340         xml_config_doc = 0;
341     }
342 #endif
343     gfs_server_list = 0;
344     nmem_destroy(gfs_nmem);
345 }
346
347 static void xml_config_add_listeners()
348 {
349 #define MAX_PORTS 200
350     struct gfs_server *gfs = gfs_server_list;
351     int i, ports[MAX_PORTS];
352     for (i = 0; i<MAX_PORTS; i++)
353         ports[i] = 0;
354
355     for (; gfs; gfs = gfs->next)
356     {
357         int port = gfs->port;
358         if (port)
359         {
360             for (i = 0; i<MAX_PORTS && ports[i] != port; i++)
361                 if (ports[i] == 0)
362                 {
363                     ports[i] = port;
364                     break;
365                 }
366         }
367     }
368     for (i = 0; i<MAX_PORTS && ports[i]; i++)
369     {
370         char where[80];
371         sprintf(where, "@:%d", ports[i]);
372         add_listener(where, ports[i]);
373     }
374 }
375
376 static void xml_config_bend_start()
377 {
378     if (control_block.xml_config[0])
379     {
380         struct gfs_server *gfs = gfs_server_list;
381         for (; gfs; gfs = gfs->next)
382         {
383             yaz_log(YLOG_DEBUG, "xml_config_bend_start config=%s",
384                     gfs->cb.configname);
385             statserv_setcontrol(&gfs->cb);
386             if (control_block.bend_start)
387                 (control_block.bend_start)(&gfs->cb);
388         }
389     }
390     else
391     {
392         yaz_log(YLOG_DEBUG, "xml_config_bend_start default config");
393         statserv_setcontrol(&control_block);
394         if (control_block.bend_start)
395             (*control_block.bend_start)(&control_block);
396     }
397
398 }
399
400 static void xml_config_bend_stop()
401 {
402     if (control_block.xml_config[0])
403     {
404         struct gfs_server *gfs = gfs_server_list;
405         for (; gfs; gfs = gfs->next)
406         {
407             yaz_log(YLOG_DEBUG, "xml_config_bend_stop config=%s",
408                     gfs->cb.configname);
409             statserv_setcontrol(&gfs->cb);
410             if (control_block.bend_stop)
411                 (control_block.bend_stop)(&gfs->cb);
412         }
413     }
414     else
415     {
416         yaz_log(YLOG_DEBUG, "xml_config_bend_stop default config");
417         statserv_setcontrol(&control_block);
418         if (control_block.bend_stop)
419             (*control_block.bend_stop)(&control_block);
420     }
421 }
422
423 /*
424  * handle incoming connect requests.
425  * The dynamic mode is a bit tricky mostly because we want to avoid
426  * doing all of the listening and accepting in the parent - it's
427  * safer that way.
428  */
429 #ifdef WIN32
430
431 typedef struct _ThreadList ThreadList;
432
433 struct _ThreadList
434 {
435     HANDLE hThread;
436     IOCHAN pIOChannel;
437     ThreadList *pNext;
438 };
439
440 static ThreadList *pFirstThread;
441 static CRITICAL_SECTION Thread_CritSect;
442 static BOOL bInitialized = FALSE;
443
444 static void ThreadList_Initialize()
445 {
446     /* Initialize the critical Sections */
447     InitializeCriticalSection(&Thread_CritSect);
448
449      /* Set the first thraed */
450     pFirstThread = NULL;
451
452     /* we have been initialized */
453     bInitialized = TRUE;
454 }
455
456 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
457 {
458     /* Only one thread can go through this section at a time */
459     EnterCriticalSection(&Thread_CritSect);
460
461     {
462         /* Lets create our new object */
463         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
464         pNewThread->hThread = hThread;
465         pNewThread->pIOChannel = pIOChannel;
466         pNewThread->pNext = pFirstThread;
467         pFirstThread = pNewThread;
468
469         /* Lets let somebody else create a new object now */
470         LeaveCriticalSection(&Thread_CritSect);
471     }
472 }
473
474 void statserv_remove(IOCHAN pIOChannel)
475 {
476     /* Only one thread can go through this section at a time */
477     EnterCriticalSection(&Thread_CritSect);
478
479     {
480         ThreadList *pCurrentThread = pFirstThread;
481         ThreadList *pNextThread;
482         ThreadList *pPrevThread =NULL;
483
484         /* Step through all the threads */
485         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
486         {
487             /* We only need to compare on the IO Channel */
488             if (pCurrentThread->pIOChannel == pIOChannel)
489             {
490                 /* We have found the thread we want to delete */
491                 /* First of all reset the next pointers */
492                 if (pPrevThread == NULL)
493                     pFirstThread = pCurrentThread->pNext;
494                 else
495                     pPrevThread->pNext = pCurrentThread->pNext;
496
497                 /* All we need todo now is delete the memory */
498                 free(pCurrentThread);
499
500                 /* No need to look at any more threads */
501                 pNextThread = NULL;
502             }
503             else
504             {
505                 /* We need to look at another thread */
506                 pNextThread = pCurrentThread->pNext;
507                 pPrevThread = pCurrentThread;
508             }
509         }
510
511         /* Lets let somebody else remove an object now */
512         LeaveCriticalSection(&Thread_CritSect);
513     }
514 }
515
516 /* WIN32 statserv_closedown */
517 void statserv_closedown()
518 {
519     /* Shouldn't do anything if we are not initialized */
520     if (bInitialized)
521     {
522         int iHandles = 0;
523         HANDLE *pThreadHandles = NULL;
524
525         /* We need to stop threads adding and removing while we */
526         /* start the closedown process */
527         EnterCriticalSection(&Thread_CritSect);
528
529         {
530             /* We have exclusive access to the thread stuff now */
531             /* Y didn't i use a semaphore - Oh well never mind */
532             ThreadList *pCurrentThread = pFirstThread;
533
534             /* Before we do anything else, we need to shutdown the listener */
535             if (pListener != NULL)
536                 iochan_destroy(pListener);
537
538             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
539             {
540                 /* Just destroy the IOCHAN, that should do the trick */
541                 iochan_destroy(pCurrentThread->pIOChannel);
542                 closesocket(pCurrentThread->pIOChannel->fd);
543
544                 /* Keep a running count of our handles */
545                 iHandles++;
546             }
547
548             if (iHandles > 0)
549             {
550                 HANDLE *pCurrentHandle ;
551
552                 /* Allocate the thread handle array */
553                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
554                 pCurrentHandle = pThreadHandles; 
555
556                 for (pCurrentThread = pFirstThread;
557                      pCurrentThread != NULL;
558                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
559                 {
560                     /* Just the handle */
561                     *pCurrentHandle = pCurrentThread->hThread;
562                 }
563             }
564
565             /* We can now leave the critical section */
566             LeaveCriticalSection(&Thread_CritSect);
567         }
568
569         /* Now we can really do something */
570         if (iHandles > 0)
571         {
572             yaz_log(log_server, "waiting for %d to die", iHandles);
573             /* This will now wait, until all the threads close */
574             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
575
576             /* Free the memory we allocated for the handle array */
577             free(pThreadHandles);
578         }
579
580         xml_config_bend_stop();
581         /* No longer require the critical section, since all threads are dead */
582         DeleteCriticalSection(&Thread_CritSect);
583     }
584     xml_config_close();
585 }
586
587 void __cdecl event_loop_thread (IOCHAN iochan)
588 {
589     event_loop (&iochan);
590 }
591
592 /* WIN32 listener */
593 static void listener(IOCHAN h, int event)   
594 {
595     COMSTACK line = (COMSTACK) iochan_getdata(h);
596     association *newas;
597     int res;
598     HANDLE newHandle;
599
600     if (event == EVENT_INPUT)
601     {
602         if ((res = cs_listen(line, 0, 0)) < 0)
603         {
604             yaz_log(YLOG_FATAL, "cs_listen failed");
605             return;
606         }
607         else if (res == 1)
608             return;
609         yaz_log(YLOG_DEBUG, "listen ok");
610         iochan_setevent(h, EVENT_OUTPUT);
611         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
612     }
613     else if (event == EVENT_OUTPUT)
614     {
615         COMSTACK new_line = cs_accept(line);
616         IOCHAN new_chan;
617         char *a = NULL;
618
619         if (!new_line)
620         {
621             yaz_log(YLOG_FATAL, "Accept failed.");
622             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT);
623             return;
624         }
625         yaz_log(YLOG_DEBUG, "Accept ok");
626
627         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
628                                        EVENT_INPUT)))
629         {
630             yaz_log(YLOG_FATAL, "Failed to create iochan");
631             iochan_destroy(h);
632             return;
633         }
634
635         yaz_log(YLOG_DEBUG, "Creating association");
636         if (!(newas = create_association(new_chan, new_line,
637                                          control_block.apdu_file)))
638         {
639             yaz_log(YLOG_FATAL, "Failed to create new assoc.");
640             iochan_destroy(h);
641             return;
642         }
643         newas->cs_get_mask = EVENT_INPUT;
644         newas->cs_put_mask = 0;
645         newas->cs_accept_mask = 0;
646
647         yaz_log(YLOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
648         iochan_setdata(new_chan, newas);
649         iochan_settimeout(new_chan, 60);
650
651         /* Now what we need todo is create a new thread with this iochan as
652            the parameter */
653         newHandle = (HANDLE) _beginthread(event_loop_thread, 0, new_chan);
654         if (newHandle == (HANDLE) -1)
655         {
656             
657             yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create new thread.");
658             iochan_destroy(h);
659             return;
660         }
661         /* We successfully created the thread, so add it to the list */
662         statserv_add(newHandle, new_chan);
663
664         yaz_log(YLOG_DEBUG, "Created new thread, id = %ld iochan %p",(long) newHandle, new_chan);
665         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
666     }
667     else
668     {
669         yaz_log(YLOG_FATAL, "Bad event on listener.");
670         iochan_destroy(h);
671         return;
672     }
673 }
674
675 int statserv_must_terminate(void)
676 {
677     return 0;
678 }
679
680 #else /* ! WIN32 */
681
682 static int term_flag = 0;
683 /* To save having an #ifdef in event_loop we need to
684    define this empty function 
685 */
686 int statserv_must_terminate(void)
687 {
688     return term_flag;
689 }
690
691 void statserv_remove(IOCHAN pIOChannel)
692 {
693 }
694
695 void statserv_closedown()
696 {
697     IOCHAN p;
698
699     xml_config_bend_stop();
700     for (p = pListener; p; p = p->next)
701     {
702         iochan_destroy(p);
703     }
704     xml_config_close();
705 #if YAZ_POSIX_THREADS
706     pthread_key_delete(current_control_tls);
707 #endif
708 }
709
710 void sigterm(int sig)
711 {
712     term_flag = 1;
713 }
714
715 static void *new_session (void *vp);
716 static int no_sessions = 0;
717
718 /* UNIX listener */
719 static void listener(IOCHAN h, int event)
720 {
721     COMSTACK line = (COMSTACK) iochan_getdata(h);
722     int res;
723
724     if (event == EVENT_INPUT)
725     {
726         COMSTACK new_line;
727         if ((res = cs_listen_check(line, 0, 0, control_block.check_ip,
728                                    control_block.daemon_name)) < 0)
729         {
730             yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_listen failed");
731             return;
732         }
733         else if (res == 1)
734         {
735             yaz_log(YLOG_WARN, "cs_listen incomplete");
736             return;
737         }
738         new_line = cs_accept(line);
739         if (!new_line)
740         {
741             yaz_log(YLOG_FATAL, "Accept failed.");
742             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
743             return;
744         }
745
746         yaz_log(log_session, "Connect from %s", cs_addrstr(new_line));
747
748         no_sessions++;
749         if (control_block.dynamic)
750         {
751             if ((res = fork()) < 0)
752             {
753                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "fork");
754                 iochan_destroy(h);
755                 return;
756             }
757             else if (res == 0) /* child */
758             {
759                 char nbuf[100];
760                 IOCHAN pp;
761
762                 for (pp = pListener; pp; pp = iochan_getnext(pp))
763                 {
764                     COMSTACK l = (COMSTACK)iochan_getdata(pp);
765                     cs_close(l);
766                     iochan_destroy(pp);
767                 }
768                 sprintf(nbuf, "%s(%d)", me, no_sessions);
769                 yaz_log_init(control_block.loglevel, nbuf, 0);
770                 /* ensure that bend_stop is not called when each child exits -
771                    only for the main process ..  */
772                 control_block.bend_stop = 0;
773             }
774             else /* parent */
775             {
776                 cs_close(new_line);
777                 return;
778             }
779         }
780
781         if (control_block.threads)
782         {
783 #if YAZ_POSIX_THREADS
784             pthread_t child_thread;
785             pthread_create (&child_thread, 0, new_session, new_line);
786             pthread_detach (child_thread);
787 #elif YAZ_GNU_THREADS
788             pth_attr_t attr;
789             pth_t child_thread;
790
791             attr = pth_attr_new ();
792             pth_attr_set (attr, PTH_ATTR_JOINABLE, FALSE);
793             pth_attr_set (attr, PTH_ATTR_STACK_SIZE, 32*1024);
794             pth_attr_set (attr, PTH_ATTR_NAME, "session");
795             yaz_log (YLOG_DEBUG, "pth_spawn begin");
796             child_thread = pth_spawn (attr, new_session, new_line);
797             yaz_log (YLOG_DEBUG, "pth_spawn finish");
798             pth_attr_destroy (attr);
799 #else
800             new_session(new_line);
801 #endif
802         }
803         else
804             new_session(new_line);
805     }
806     else if (event == EVENT_TIMEOUT)
807     {
808         yaz_log(log_server, "Shutting down listener.");
809         iochan_destroy(h);
810     }
811     else
812     {
813         yaz_log(YLOG_FATAL, "Bad event on listener.");
814         iochan_destroy(h);
815     }
816 }
817
818 static void *new_session (void *vp)
819 {
820     char *a;
821     association *newas;
822     IOCHAN new_chan;
823     COMSTACK new_line = (COMSTACK) vp;
824     IOCHAN parent_chan = new_line->user;
825
826     unsigned cs_get_mask, cs_accept_mask, mask =  
827         ((new_line->io_pending & CS_WANT_WRITE) ? EVENT_OUTPUT : 0) |
828         ((new_line->io_pending & CS_WANT_READ) ? EVENT_INPUT : 0);
829
830     if (mask)
831     {
832         cs_accept_mask = mask;  /* accept didn't complete */
833         cs_get_mask = 0;
834     }
835     else
836     {
837         cs_accept_mask = 0;     /* accept completed.  */
838         cs_get_mask = mask = EVENT_INPUT;
839     }
840
841     if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session, mask,
842                                    parent_chan->port)))
843     {
844         yaz_log(YLOG_FATAL, "Failed to create iochan");
845         return 0;
846     }
847     if (!(newas = create_association(new_chan, new_line,
848                                      control_block.apdufile)))
849     {
850         yaz_log(YLOG_FATAL, "Failed to create new assoc.");
851         return 0;
852     }
853     newas->cs_accept_mask = cs_accept_mask;
854     newas->cs_get_mask = cs_get_mask;
855
856     iochan_setdata(new_chan, newas);
857     iochan_settimeout(new_chan, 60);
858 #if 1
859     a = cs_addrstr(new_line);
860 #else
861     a = 0;
862 #endif
863     yaz_log(log_session, "Starting session %d from %s (pid=%ld)",
864             no_sessions, a ? a : "[Unknown]", (long) getpid());
865     if (max_sessions && no_sessions >= max_sessions)
866         control_block.one_shot = 1;
867     if (control_block.threads)
868     {
869         event_loop(&new_chan);
870     }
871     else
872     {
873         new_chan->next = pListener;
874         pListener = new_chan;
875     }
876     return 0;
877 }
878
879 /* UNIX */
880 #endif
881
882 static void inetd_connection(int what)
883 {
884     COMSTACK line;
885     IOCHAN chan;
886     association *assoc;
887     char *addr;
888
889     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
890     {
891         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT,
892                                   0)))
893         {
894             if ((assoc = create_association(chan, line,
895                                             control_block.apdufile)))
896             {
897                 iochan_setdata(chan, assoc);
898                 iochan_settimeout(chan, 60);
899                 addr = cs_addrstr(line);
900                 yaz_log(log_session, "Inetd association from %s",
901                         addr ? addr : "[UNKNOWN]");
902                 assoc->cs_get_mask = EVENT_INPUT;
903             }
904             else
905             {
906                 yaz_log(YLOG_FATAL, "Failed to create association structure");
907             }
908             chan->next = pListener;
909             pListener = chan;
910         }
911         else
912         {
913             yaz_log(YLOG_FATAL, "Failed to create iochan");
914         }
915     }
916     else
917     {
918         yaz_log(YLOG_ERRNO|YLOG_FATAL, "Failed to create comstack on socket 0");
919     }
920 }
921
922 /*
923  * Set up a listening endpoint, and give it to the event-handler.
924  */
925 static int add_listener(char *where, int listen_id)
926 {
927     COMSTACK l;
928     void *ap;
929     IOCHAN lst = NULL;
930     const char *mode;
931
932     if (control_block.dynamic)
933         mode = "dynamic";
934     else if (control_block.threads)
935         mode = "threaded";
936     else
937         mode = "static";
938
939     yaz_log(log_server, "Adding %s listener on %s id=%d", mode, where,
940             listen_id);
941
942     l = cs_create_host(where, 2, &ap);
943     if (!l)
944     {
945         yaz_log(YLOG_FATAL, "Failed to listen on %s", where);
946         return -1;
947     }
948     if (*control_block.cert_fname)
949         cs_set_ssl_certificate_file(l, control_block.cert_fname);
950
951     if (cs_bind(l, ap, CS_SERVER) < 0)
952     {
953         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to bind to %s", where);
954         cs_close (l);
955         return -1;
956     }
957     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
958          EVENT_EXCEPT, listen_id)))
959     {
960         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create IOCHAN-type");
961         cs_close (l);
962         return -1;
963     }
964     iochan_setdata(lst, l); /* user-defined data for listener is COMSTACK */
965     l->user = lst;  /* user-defined data for COMSTACK is listener chan */
966
967     /* Add listener to chain */
968     lst->next = pListener;
969     pListener = lst;
970     return 0; /* OK */
971 }
972
973 #ifndef WIN32
974 /* UNIX only (for windows we don't need to catch the signals) */
975 static void catchchld(int num)
976 {
977     while (waitpid(-1, 0, WNOHANG) > 0)
978         ;
979     signal(SIGCHLD, catchchld);
980 }
981 #endif
982
983 statserv_options_block *statserv_getcontrol(void)
984 {
985 #if YAZ_POSIX_THREADS
986     if (init_control_tls)
987         return pthread_getspecific(current_control_tls);
988     else
989         return &control_block;
990 #else
991     return current_control_block;
992 #endif
993 }
994
995 void statserv_setcontrol(statserv_options_block *block)
996 {
997 #if YAZ_POSIX_THREADS
998     if (init_control_tls)
999         pthread_setspecific(current_control_tls, block);
1000 #else
1001     current_control_block = block;
1002 #endif
1003 }
1004
1005 static void statserv_reset(void)
1006 {
1007 }
1008
1009 int statserv_start(int argc, char **argv)
1010 {
1011     char sep;
1012 #ifdef WIN32
1013     /* We need to initialize the thread list */
1014     ThreadList_Initialize();
1015 /* WIN32 */
1016 #endif
1017
1018
1019 #ifdef WIN32
1020     sep = '\\';
1021 #else
1022     sep = '/';
1023 #endif
1024     if ((me = strrchr (argv[0], sep)))
1025         me++; /* get the basename */
1026     else
1027         me = argv[0];
1028     programname = argv[0];
1029
1030     if (control_block.options_func(argc, argv))
1031         return 1;
1032
1033 #if YAZ_POSIX_THREADS
1034     init_control_tls = 1;
1035     pthread_key_create(&current_control_tls, 0);
1036 #endif
1037     
1038     xml_config_open();
1039     
1040     xml_config_bend_start();
1041
1042 #ifdef WIN32
1043     xml_config_add_listeners();
1044
1045     yaz_log (log_server, "Starting server %s", me);
1046     if (!pListener && *control_block.default_listen)
1047         add_listener(control_block.default_listen, 0);
1048 #else
1049 /* UNIX */
1050     if (control_block.inetd)
1051         inetd_connection(control_block.default_proto);
1052     else
1053     {
1054         static int hand[2];
1055         if (control_block.background)
1056         {
1057             /* create pipe so that parent waits until child has created
1058                PID (or failed) */
1059             if (pipe(hand) < 0)
1060             {
1061                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "pipe");
1062                 return 1;
1063             }
1064             switch (fork())
1065             {
1066             case 0: 
1067                 break;
1068             case -1:
1069                 return 1;
1070             default:
1071                 close(hand[1]);
1072                 while(1)
1073                 {
1074                     char dummy[1];
1075                     int res = read(hand[0], dummy, 1);
1076                     if (res < 0 && yaz_errno() != EINTR)
1077                     {
1078                         yaz_log(YLOG_FATAL|YLOG_ERRNO, "read fork handshake");
1079                         break;
1080                     }
1081                     else if (res >= 0)
1082                         break;
1083                 }
1084                 close(hand[0]);
1085                 _exit(0);
1086             }
1087             /* child */
1088             close(hand[0]);
1089             if (setsid() < 0)
1090                 return 1;
1091             
1092             close(0);
1093             close(1);
1094             close(2);
1095             open("/dev/null", O_RDWR);
1096             dup(0); dup(0);
1097         }
1098         xml_config_add_listeners();
1099
1100         if (!pListener && *control_block.default_listen)
1101             add_listener(control_block.default_listen, 0);
1102         
1103         if (!pListener)
1104             return 1;
1105
1106         if (*control_block.pid_fname)
1107         {
1108             FILE *f = fopen(control_block.pid_fname, "w");
1109             if (!f)
1110             {
1111                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Couldn't create %s", 
1112                         control_block.pid_fname);
1113                 exit(0);
1114             }
1115             fprintf(f, "%ld", (long) getpid());
1116             fclose(f);
1117         }
1118         
1119         if (control_block.background)
1120             close(hand[1]);
1121
1122
1123         yaz_log (log_server, "Starting server %s pid=%ld", programname, 
1124                  (long) getpid());
1125 #if 0
1126         sigset_t sigs_to_block;
1127         
1128         sigemptyset(&sigs_to_block);
1129         sigaddset (&sigs_to_block, SIGTERM);
1130         pthread_sigmask (SIG_BLOCK, &sigs_to_block, 0);
1131         /* missing... */
1132 #endif
1133         if (control_block.dynamic)
1134             signal(SIGCHLD, catchchld);
1135     }
1136     signal (SIGPIPE, SIG_IGN);
1137     signal (SIGTERM, sigterm);
1138     if (*control_block.setuid)
1139     {
1140         struct passwd *pw;
1141         
1142         if (!(pw = getpwnam(control_block.setuid)))
1143         {
1144             yaz_log(YLOG_FATAL, "%s: Unknown user", control_block.setuid);
1145             return(1);
1146         }
1147         if (setuid(pw->pw_uid) < 0)
1148         {
1149             yaz_log(YLOG_FATAL|YLOG_ERRNO, "setuid");
1150             exit(1);
1151         }
1152     }
1153 /* UNIX */
1154 #endif
1155     if (pListener == NULL)
1156         return 1;
1157     yaz_log(YLOG_DEBUG, "Entering event loop.");
1158     return event_loop(&pListener);
1159 }
1160
1161 static void option_copy(char *dst, const char *src)
1162 {
1163     strncpy(dst, src ? src : "", 127);
1164     dst[127] = '\0';
1165 }
1166
1167 int check_options(int argc, char **argv)
1168 {
1169     int ret = 0, r;
1170     char *arg;
1171
1172     /* set default log level */
1173     control_block.loglevel = yaz_log_mask_str(STAT_DEFAULT_LOG_LEVEL);
1174     yaz_log_init_level(control_block.loglevel);
1175
1176     while ((ret = options("1a:iszSTl:v:u:c:w:t:k:d:A:p:DC:f:",
1177                           argv, argc, &arg)) != -2)
1178     {
1179         switch (ret)
1180         {
1181         case 0:
1182             if (add_listener(arg, 0))
1183                 return 1;  /* failed to create listener */
1184             break;
1185         case '1':        
1186             control_block.one_shot = 1;
1187             control_block.dynamic = 0;
1188             break;
1189         case 'z':
1190             control_block.default_proto = PROTO_Z3950;
1191             break;
1192         case 's':
1193             fprintf (stderr, "%s: SR protocol no longer supported\n", me);
1194             exit (1);
1195             break;
1196         case 'S':
1197             control_block.dynamic = 0;
1198             break;
1199         case 'T':
1200 #if YAZ_POSIX_THREADS
1201             control_block.dynamic = 0;
1202             control_block.threads = 1;
1203 #elif YAZ_GNU_THREADS
1204             control_block.dynamic = 0;
1205             control_block.threads = 1;
1206 #else
1207             fprintf(stderr, "%s: Threaded mode not available.\n", me);
1208             return 1;
1209 #endif
1210             break;
1211         case 'l':
1212             option_copy(control_block.logfile, arg);
1213             yaz_log_init(control_block.loglevel, me, control_block.logfile);
1214             break;
1215         case 'v':
1216             control_block.loglevel =
1217                 yaz_log_mask_str_x(arg,control_block.loglevel);
1218             yaz_log_init(control_block.loglevel, me, control_block.logfile);
1219             break;
1220         case 'a':
1221             option_copy(control_block.apdufile, arg);
1222             break;
1223         case 'u':
1224             option_copy(control_block.setuid, arg);
1225             break;
1226         case 'c':
1227             option_copy(control_block.configname, arg);
1228             break;
1229         case 'C':
1230             option_copy(control_block.cert_fname, arg);
1231             break;
1232         case 'd':
1233             option_copy(control_block.daemon_name, arg);
1234             break;
1235         case 't':
1236             if (!arg || !(r = atoi(arg)))
1237             {
1238                 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
1239                 return(1);
1240             }
1241             control_block.idle_timeout = r;
1242             break;
1243         case  'k':
1244             if (!arg || !(r = atoi(arg)))
1245             {
1246                 fprintf(stderr, "%s: Specify positive size for -k.\n", me);
1247                 return(1);
1248             }
1249             control_block.maxrecordsize = r * 1024;
1250             break;
1251         case 'i':
1252             control_block.inetd = 1;
1253             break;
1254         case 'w':
1255             if (chdir(arg))
1256             {
1257                 perror(arg);            
1258                 return 1;
1259             }
1260             break;
1261         case 'A':
1262             max_sessions = atoi(arg);
1263             break;
1264         case 'p':
1265             option_copy(control_block.pid_fname, arg);
1266             break;
1267         case 'f':
1268 #if HAVE_XML2
1269             option_copy(control_block.xml_config, arg);
1270 #else
1271             fprintf(stderr, "%s: Option -f unsupported since YAZ is compiled without Libxml2 support\n", me);
1272             exit(1);
1273 #endif
1274             break;
1275         case 'D':
1276             control_block.background = 1;
1277             break;
1278         default:
1279             fprintf(stderr, "Usage: %s [ -a <pdufile> -v <loglevel>"
1280                     " -l <logfile> -u <user> -c <config> -t <minutes>"
1281                     " -k <kilobytes> -d <daemon> -p <pidfile> -C certfile"
1282                         " -ziDST1 -w <directory> <listener-addr>... ]\n", me);
1283             return 1;
1284         }
1285     }
1286     get_logbits(1); 
1287     return 0;
1288 }
1289
1290 #ifdef WIN32
1291 typedef struct _Args
1292 {
1293     char **argv;
1294     int argc;
1295 } Args; 
1296
1297 static Args ArgDetails;
1298
1299 /* name of the executable */
1300 #define SZAPPNAME            "server"
1301
1302 /* list of service dependencies - "dep1\0dep2\0\0" */
1303 #define SZDEPENDENCIES       ""
1304
1305 int statserv_main(int argc, char **argv,
1306                   bend_initresult *(*bend_init)(bend_initrequest *r),
1307                   void (*bend_close)(void *handle))
1308 {
1309     control_block.bend_init = bend_init;
1310     control_block.bend_close = bend_close;
1311
1312     /* Lets setup the Arg structure */
1313     ArgDetails.argc = argc;
1314     ArgDetails.argv = argv;
1315     
1316     /* Now setup the service with the service controller */
1317     SetupService(argc, argv, &ArgDetails, SZAPPNAME,
1318                  cb->service_name, /* internal service name */
1319                  cb->service_display_name, /* displayed name */
1320                  SZDEPENDENCIES);
1321     return 0;
1322 }
1323
1324 int StartAppService(void *pHandle, int argc, char **argv)
1325 {
1326     /* Initializes the App */
1327     return 1;
1328 }
1329
1330 void RunAppService(void *pHandle)
1331 {
1332     Args *pArgs = (Args *)pHandle;
1333     
1334     /* Starts the app running */
1335     statserv_start(pArgs->argc, pArgs->argv);
1336 }
1337
1338 void StopAppService(void *pHandle)
1339 {
1340     /* Stops the app */
1341     statserv_closedown();
1342     statserv_reset();
1343 }
1344 /* WIN32 */
1345 #else
1346 /* UNIX */
1347 int statserv_main(int argc, char **argv,
1348                   bend_initresult *(*bend_init)(bend_initrequest *r),
1349                   void (*bend_close)(void *handle))
1350 {
1351     int ret;
1352
1353     control_block.bend_init = bend_init;
1354     control_block.bend_close = bend_close;
1355
1356     ret = statserv_start (argc, argv);
1357     statserv_closedown ();
1358     statserv_reset();
1359     return ret;
1360 }
1361 #endif