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