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