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