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