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