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