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