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