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