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