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