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