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