License, documentation, and memory fixes
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: statserv.c,v $
7  * Revision 1.18  1995-05-16 08:51:09  quinn
8  * License, documentation, and memory fixes
9  *
10  * Revision 1.17  1995/05/15  11:56:42  quinn
11  * Asynchronous facilities. Restructuring of seshigh code.
12  *
13  * Revision 1.16  1995/04/10  10:23:40  quinn
14  * Some work to add scan and other things.
15  *
16  * Revision 1.15  1995/03/31  10:16:51  quinn
17  * Fixed logging.
18  *
19  * Revision 1.14  1995/03/31  09:18:58  quinn
20  * Added logging.
21  *
22  * Revision 1.13  1995/03/30  16:08:39  quinn
23  * Little mods.
24  *
25  * Revision 1.12  1995/03/30  13:29:02  quinn
26  * Smallish
27  *
28  * Revision 1.11  1995/03/30  12:18:17  quinn
29  * Fixed bug.
30  *
31  * Revision 1.10  1995/03/29  15:40:16  quinn
32  * Ongoing work. Statserv is now dynamic by default
33  *
34  * Revision 1.9  1995/03/27  08:34:30  quinn
35  * Added dynamic server functionality.
36  * Released bindings to session.c (is now redundant)
37  *
38  * Revision 1.8  1995/03/20  09:46:26  quinn
39  * Added osi support.
40  *
41  * Revision 1.7  1995/03/16  13:29:04  quinn
42  * Partitioned server.
43  *
44  * Revision 1.6  1995/03/15  15:18:52  quinn
45  * Little changes to better support nonblocking I/O
46  * Added backend.h
47  *
48  * Revision 1.5  1995/03/15  08:37:45  quinn
49  * Now we're pretty much set for nonblocking I/O.
50  *
51  * Revision 1.4  1995/03/14  16:59:48  quinn
52  * Bug-fixes
53  *
54  * Revision 1.3  1995/03/14  11:30:15  quinn
55  * Works better now.
56  *
57  * Revision 1.2  1995/03/14  10:28:03  quinn
58  * More work on demo server.
59  *
60  * Revision 1.1  1995/03/10  18:22:45  quinn
61  * The rudiments of an asynchronous server.
62  *
63  */
64
65 /*
66  * Simple, static server. I wouldn't advise a static server unless you
67  * really have to, but it's great for debugging memory management.  :)
68  */
69
70 #include <stdio.h>
71 #include <unistd.h>
72 #include <fcntl.h>
73 #include <sys/wait.h>
74 #include <signal.h>
75 #include <errno.h>
76
77 #include <options.h>
78 #include <eventl.h>
79 #include <session.h>
80 #include <eventl.h>
81 #include <comstack.h>
82 #include <tcpip.h>
83 #ifdef USE_XTIMOSI
84 #include <xmosi.h>
85 #endif
86 #include <dmalloc.h>
87 #include <log.h>
88 #include <statserv.h>
89
90 static char *me = "statserver";
91 /*
92  * default behavior.
93  */
94 static statserv_options_block control_block = {
95     1,                          /* dynamic mode */
96     LOG_DEFAULT_LEVEL,          /* log level */
97     "",                         /* no PDUs */
98     "",                         /* diagnostic output to stderr */
99     "tcp:localhost:9999",       /* default listener port */
100     PROTO_Z3950,                /* application protocol */
101     60,                         /* idle timeout (minutes) */
102     1024*1024*4,                /* maximum PDU size (approx.) to allow */
103     "default-config"            /* configuration name to pass to backend */
104 };
105
106 #define DEFAULT_LISTENER "tcp:localhost:9999"
107
108 /*
109  * handle incoming connect requests.
110  * The dynamic mode is a bit tricky mostly because we want to avoid
111  * doing all of the listening and accepting in the parent - it's
112  * safer that way.
113  */
114 static void listener(IOCHAN h, int event)
115 {
116     COMSTACK line = (COMSTACK) iochan_getdata(h);
117     association *newas;
118     static int hand[2];
119     static int child = 0;
120     int res;
121
122     if (event == EVENT_INPUT)
123     {
124         if (control_block.dynamic && !child) 
125         {
126             int res;
127
128             if (pipe(hand) < 0)
129             {
130                 logf(LOG_FATAL|LOG_ERRNO, "pipe");
131                 exit(1);
132             }
133             if ((res = fork()) < 0)
134             {
135                 logf(LOG_FATAL|LOG_ERRNO, "fork");
136                 exit(1);
137             }
138             else if (res == 0) /* child */
139             {
140                 char nbuf[100];
141
142                 close(hand[0]);
143                 child = 1;
144                 sprintf(nbuf, "%s(%d)", me, getpid());
145                 log_init(control_block.loglevel, nbuf, 0);
146             }
147             else /* parent */
148             {
149                 close(hand[1]);
150                 /* wait for child to take the call */
151                 for (;;)
152                 {
153                     char dummy[1];
154                     int res;
155                     
156                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
157                     {
158                         logf(LOG_FATAL|LOG_ERRNO, "handshake read");
159                         exit(1);
160                     }
161                     else if (res >= 0)
162                         break;
163                 }
164                 logf(LOG_DEBUG, "P: Child has taken the call");
165                 close(hand[0]);
166                 return;
167             }
168         }
169         if ((res = cs_listen(line, 0, 0)) < 0)
170         {
171             logf(LOG_FATAL, "cs_listen failed.");
172             return;
173         }
174         else if (res == 1)
175             return;
176         logf(LOG_DEBUG, "listen ok");
177         iochan_setevent(h, EVENT_OUTPUT);
178         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
179     }
180     /* in dynamic mode, only the child ever comes down here */
181     else if (event == EVENT_OUTPUT)
182     {
183         COMSTACK new_line;
184         IOCHAN new_chan;
185
186         if (!(new_line = cs_accept(line)))
187         {
188             logf(LOG_FATAL, "Accept failed.");
189             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
190             return;
191         }
192         logf(LOG_DEBUG, "accept ok");
193         if (control_block.dynamic)
194         {
195             IOCHAN pp;
196             /* close our half of the listener sockets */
197             for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
198             {
199                 COMSTACK l = iochan_getdata(pp);
200                 cs_close(l);
201                 iochan_destroy(pp);
202             }
203             /* release dad */
204             logf(LOG_DEBUG, "Releasing parent");
205             close(hand[1]);
206         }
207         else
208             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
209
210         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
211             EVENT_INPUT)))
212         {
213             logf(LOG_FATAL, "Failed to create iochan");
214             exit(1);
215         }
216         if (!(newas = create_association(new_chan, new_line)))
217         {
218             logf(LOG_FATAL, "Failed to create new assoc.");
219             exit(1);
220         }
221         iochan_setdata(new_chan, newas);
222         logf(LOG_LOG, "accepted connection");
223     }
224     else
225     {
226         logf(LOG_FATAL, "Bad event on listener.");
227         exit(1);
228     }
229 }
230
231 /*
232  * Set up a listening endpoint, and give it to the event-handler.
233  */
234 static void add_listener(char *where, int what)
235 {
236     COMSTACK l;
237     CS_TYPE type;
238     char mode[100], addr[100];
239     void *ap;
240     IOCHAN lst;
241
242     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
243     {
244         fprintf(stderr, "%s: Address format: ('tcp'|'osi')':'<address>.\n",
245             me);
246         exit(1);
247     }
248     if (!strcmp(mode, "tcp"))
249     {
250         if (!(ap = tcpip_strtoaddr(addr)))
251         {
252             fprintf(stderr, "Address resolution failed for TCP.\n");
253             exit(1);
254         }
255         type = tcpip_type;
256     }
257 #ifdef USE_XTIMOSI
258     else if (!strcmp(mode, "osi"))
259     {
260         if (!(ap = mosi_strtoaddr(addr)))
261         {
262             fprintf(stderr, "Address resolution failed for TCP.\n");
263             exit(1);
264         }
265         type = mosi_type;
266     }
267 #endif
268     else
269     {
270         fprintf(stderr, "You must specify either 'osi:' or 'tcp:'.\n");
271         exit(1);
272     }
273     logf(LOG_LOG, "Adding %s %s listener on %s",
274         control_block.dynamic ? "dynamic" : "static",
275         what == PROTO_SR ? "SR" : "Z3950", where);
276     if (!(l = cs_create(type, 0, what)))
277     {
278         logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
279         exit(1);
280     }
281     if (cs_bind(l, ap, CS_SERVER) < 0)
282     {
283         logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
284         exit(1);
285     }
286     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
287          EVENT_EXCEPT)))
288     {
289         logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
290         exit(1);
291     }
292     iochan_setdata(lst, l);
293 }
294
295 static void catchchld(int num)
296 {
297     while (waitpid(-1, 0, WNOHANG) > 0);
298     signal(SIGCHLD, catchchld);
299 }
300
301 statserv_options_block *statserv_getcontrol(void)
302 {
303     static statserv_options_block cb;
304
305     memcpy(&cb, &control_block, sizeof(cb));
306     return &cb;
307 }
308
309 void statserv_setcontrol(statserv_options_block *block)
310 {
311     memcpy(&control_block, block, sizeof(*block));
312 }
313
314 int statserv_main(int argc, char **argv)
315 {
316     int ret, listeners = 0;
317     char *arg;
318     int protocol = control_block.default_proto;
319
320     me = argv[0];
321     while ((ret = options("a:szSl:v:", argv, argc, &arg)) != -2)
322     {
323         if (!arg)
324             arg = "";
325         switch (ret)
326         {
327             case 0:
328                 add_listener(arg, protocol);
329                 listeners++;
330                 break;
331             case 'z': protocol = CS_Z3950; break;
332             case 's': protocol = CS_SR; break;
333             case 'S': control_block.dynamic = 0; break;
334             case 'l':
335                 strcpy(control_block.logfile, arg);
336                 log_init(control_block.loglevel, me, control_block.logfile);
337                 break;
338             case 'v':
339                 control_block.loglevel = log_mask_str(arg);
340                 log_init(control_block.loglevel, me, control_block.logfile);
341                 break;
342             case 'a':
343                 strcpy(control_block.apdufile, arg); break;
344             default:
345                 fprintf(stderr, "Usage: %s [ -a <apdufile> -v <loglevel> -l <logfile> -zsS <listener-addr> ... ]\n", me);
346                 exit(1);
347         }
348     }
349     if (control_block.dynamic)
350         signal(SIGCHLD, catchchld);
351     if (!listeners && *control_block.default_listen)
352         add_listener(control_block.default_listen, protocol);
353     logf(LOG_LOG, "Entering event loop.");
354             
355     return event_loop();
356 }