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