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