Bug fixes and minor improvements.
[egate.git] / kernel / monitor.c
1 /*
2  * Copyright (c) 1995, the EUROPAGATE consortium (see below).
3  *
4  * The EUROPAGATE consortium members are:
5  *
6  *    University College Dublin
7  *    Danmarks Teknologiske Videnscenter
8  *    An Chomhairle Leabharlanna
9  *    Consejo Superior de Investigaciones Cientificas
10  *
11  * Permission to use, copy, modify, distribute, and sell this software and
12  * its documentation, in whole or in part, for any purpose, is hereby granted,
13  * provided that:
14  *
15  * 1. This copyright and permission notice appear in all copies of the
16  * software and its documentation. Notices of copyright or attribution
17  * which appear at the beginning of any file must remain unchanged.
18  *
19  * 2. The names of EUROPAGATE or the project partners may not be used to
20  * endorse or promote products derived from this software without specific
21  * prior written permission.
22  *
23  * 3. Users of this software (implementors and gateway operators) agree to
24  * inform the EUROPAGATE consortium of their use of the software. This
25  * information will be used to evaluate the EUROPAGATE project and the
26  * software, and to plan further developments. The consortium may use
27  * the information in later publications.
28  * 
29  * 4. Users of this software agree to make their best efforts, when
30  * documenting their use of the software, to acknowledge the EUROPAGATE
31  * consortium, and the role played by the software in their work.
32  *
33  * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND,
34  * EXPRESS, IMPLIED, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
35  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
36  * IN NO EVENT SHALL THE EUROPAGATE CONSORTIUM OR ITS MEMBERS BE LIABLE
37  * FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF
38  * ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
39  * OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND
40  * ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
41  * USE OR PERFORMANCE OF THIS SOFTWARE.
42  *
43  */
44 /* Gateway Resource Monitor
45  * Europagate, 1995
46  *
47  * $Log: monitor.c,v $
48  * Revision 1.10  1995/05/18 12:03:09  adam
49  * Bug fixes and minor improvements.
50  *
51  * Revision 1.9  1995/05/17  10:51:32  adam
52  * Added a few more error checks to the show command.
53  *
54  * Revision 1.8  1995/05/16  09:40:42  adam
55  * LICENSE. Setting of CCL token names (and/or/not/set) in read_kernel_res.
56  *
57  * Revision 1.7  1995/05/03  12:18:46  adam
58  * This code ran on dtbsun. Minor changes.
59  *
60  * Revision 1.6  1995/05/03  09:16:17  adam
61  * Minor changes.
62  *
63  * Revision 1.5  1995/05/03  07:37:42  adam
64  * CCL commands stop/continue implemented. New functions gw_res_{int,bool}
65  * are used when possible.
66  *
67  * Revision 1.4  1995/05/02  15:26:00  adam
68  * Monitor observes death of child (email kernel). The number
69  * of simultanous processes is controlled now. Email requests are
70  * queued if necessary. This scheme should only be forced if no kernels
71  * are idle.
72  *
73  * Revision 1.3  1995/05/02  07:20:10  adam
74  * Use pid of exited child to close fifos.
75  *
76  * Revision 1.2  1995/05/01  16:26:57  adam
77  * More work on resource monitor.
78  *
79  * Revision 1.1  1995/05/01  12:43:36  adam
80  * First work on resource monitor program.
81  *
82  */
83
84 #include <stdio.h>
85 #include <stdlib.h>
86 #include <assert.h>
87 #include <ctype.h>
88 #include <string.h>
89 #include <unistd.h>
90 #include <fcntl.h>
91 #include <setjmp.h>
92 #include <signal.h>
93 #include <errno.h>
94
95 #include <sys/file.h>
96 #include <sys/stat.h>
97 #include <sys/types.h>
98 #include <sys/time.h>
99 #include <sys/wait.h>
100
101 #include <gw-log.h>
102 #include <gw-res.h>
103 #include <gip.h>
104 #include <strqueue.h>
105 #include <lgets.h>
106
107 #define LINE_MAX 1024
108
109 #define MONITOR_FIFO_S "fifo.s.m"
110 #define MONITOR_FIFO_C "fifo.c.m"
111
112 static char *module = "monitor";
113 static jmp_buf retry_jmp;
114
115 static GwRes monitor_res = NULL;
116 static int no_process = 0;
117 static int max_process = 1;
118 static int got_sighup = 0;
119 static int got_term = 0;
120 static int got_int = 0;
121 const char *default_res = "default.res";
122
123 /*
124  * reread_resources: reread monitor resources. The static variable, 
125  *      max_process, is updated.
126  */
127 static void reread_resources (void)
128 {
129     if (monitor_res)
130         gw_res_close (monitor_res);
131     monitor_res = gw_res_init ();
132     if (gw_res_merge (monitor_res, default_res))
133     {
134         gw_log (GW_LOG_WARN, module, "Couldn't read resource file %s",
135                 default_res);
136         exit (1);
137     }
138     max_process = gw_res_int (monitor_res, "gw.max.process", 10);
139 }
140
141 struct ke_info {
142     int id;                     /* email user-id */
143     pid_t pid;                  /* pid of email kernel child */
144     GIP gip;                    /* fifo information */
145     struct str_queue *queue;    /* message queue */
146     struct ke_info *next;       /* link to next */
147 };
148
149 /* list of email kernel infos */
150 static struct ke_info *ke_info_list = NULL;
151
152 /*
153  * ke_info_add: add/lookup of email kernel info.
154  * id:      email user-id to search for.
155  * return:  pointer to info structure.
156  */
157 struct ke_info *ke_info_add (int id)
158 {
159     struct ke_info **kip;
160
161     for (kip = &ke_info_list; *kip; kip= &(*kip)->next)
162         if ((*kip)->id == id)
163             return *kip;
164     *kip = malloc (sizeof(**kip));
165     assert (*kip);
166     (*kip)->next = NULL;
167     (*kip)->id = id;
168     (*kip)->gip = NULL;
169     (*kip)->queue = NULL;
170     return *kip;
171 }
172
173 static void ke_info_del (void)
174 {
175     struct ke_info *ki;
176
177     assert (ke_info_list);
178     ki = ke_info_list;
179     str_queue_rm (&ki->queue);
180     ke_info_list = ki->next;
181     free (ki);
182 }
183
184 /*
185  * catch_child: catch SIGCHLD. Set email kernel pid to -1
186  *              to indicate that child has exited
187  */
188 static void catch_child (int num)
189 {
190     pid_t pid;
191     struct ke_info *ki;
192
193     while ((pid=waitpid (-1, 0, WNOHANG)) > 0)
194     {
195         for (ki = ke_info_list; ki; ki = ki->next)
196             if (ki->pid == pid)
197                 ki->pid = -1;
198         --no_process;
199     }
200     signal (SIGCHLD, catch_child);
201 }
202
203 /* 
204  * catch_int: catch SIGHUP.
205  */
206 static void catch_hup (int num)
207 {
208     got_sighup = 1;
209     signal (SIGHUP, catch_hup);
210 }
211
212 /* 
213  * catch_int: catch SIGTERM.
214  */
215 static void catch_term (int num)
216 {
217     got_term = 1;
218     signal (SIGTERM, catch_term);
219 }
220
221 /* 
222  * catch_int: catch SIGINT.
223  */
224 static void catch_int (int num)
225 {
226     got_int = 1;
227     signal (SIGINT, catch_int);
228 }
229
230 /*
231  * pipe_handle: handle SIGPIPE when transferring message to kernel
232  */
233 static void pipe_handle (int dummy)
234 {
235     longjmp (retry_jmp, 1);
236 }
237
238 /*
239  * start_kernel: start email kernel.
240  * argc:    argc of email kernel
241  * argv:    argv of email kernel
242  * id:      email user-id
243  * return:  pid of email kernel child
244  */
245 static pid_t start_kernel (int argc, char **argv, int id)
246 {
247     pid_t pid;
248     int i;
249     char **argv_p;
250     char userid_option[20];
251
252     argv_p = malloc (sizeof(*argv_p)*(argc+2));
253     if (!argv_p)
254     {
255         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module, "malloc fail");
256         exit (1);
257     }
258     argv_p[0] = "kernel";
259     for (i = 1; i<argc; i++)
260         argv_p[i] = argv[i];
261     sprintf (userid_option, "-i%d", id);
262     argv_p[i++] = userid_option;
263     argv_p[i++] = NULL;
264
265     gw_log (GW_LOG_DEBUG, module, "Starting kernel");
266     pid = fork ();
267     if (pid == -1)
268     {
269         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module, "fork");
270         exit (1);
271     }
272     if (!pid)
273     {
274         execv ("kernel", argv_p);
275         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module, "execvp");
276         exit (1);
277     }
278     return pid;
279 }
280
281 /*
282  * deliver: deliver message to child (email kernel).
283  * argc:      exec argc to child (if it need to be started)
284  * argv:      exec argv to child (if it need to be started)
285  * id:        email userid
286  * queue:     message queue to be transferred
287  * gip:       pointer to FIFO info. if *gip is NULL prior invocation
288  *            it will be created (initialized) and the pointer will be
289  *            updated.
290  * pidp:      pointer to pid. Will hold process-id of child (if it need to
291  *            be started)
292  * dont_exec: if non-zero a child will never be started; otherwise child
293  *            will be started if not already running.
294  * return:    0 if message couldn't be transferred, i.e. dont_exec is non-zero
295  *              and the child is not already running. 
296  *            1 if message was transferred and the child was already running.
297  *            2 if message was transferred and the child was started and
298  *              dont_exec was zero.
299  *            3 serious error. Permissions denied or kernel couldn't be
300  *              started at all.
301  */
302 static int deliver (int argc, char **argv, int id, struct str_queue *queue,
303                     GIP *gip, pid_t *pidp, int dont_exec)
304 {
305     int pass = 0;
306     int r;
307     int index;
308     char fifo_server_name[128];
309     char fifo_client_name[128];
310     void (*oldsig)();
311     const char *msg;
312
313     sprintf (fifo_server_name, "fifo.s.%d", id);
314     sprintf (fifo_client_name, "fifo.c.%d", id);
315
316     assert (gip);
317     if (!*gip)
318         *gip = gipc_initialize (fifo_client_name);
319
320     oldsig = signal (SIGPIPE, pipe_handle);
321     setjmp (retry_jmp);
322     ++pass;
323     if (pass == 1)
324     {                                  /* assume child is running */
325         gipc_close (*gip);             /* shut down existing FIFOs */
326         r = gipc_open (*gip, fifo_server_name, 0);  /* try re-open ... */
327     }
328     else if (pass == 2)
329     {                                  /* assume child is NOT running */
330         pid_t pid;
331
332         if (dont_exec)
333         {                              /* we aren't allowed to start */
334             signal (SIGPIPE, oldsig);
335             return 0;
336         }
337         mknod (fifo_server_name, S_IFIFO|0666, 0);
338         pid = start_kernel (argc, argv, id);
339         if (pidp)                      /* set pid of child */
340             *pidp = pid;
341         r = gipc_open (*gip, fifo_server_name, 1);
342     }
343     else
344     {                                  /* message couldn't be transferred */
345         signal (SIGPIPE, oldsig);
346         gw_log (GW_LOG_WARN, module, "Cannot start kernel");
347         return 3;
348     }
349     if (r < 0)                         /* gipc_open fail? */
350     {
351         if (r == -2)
352             gw_log (GW_LOG_DEBUG|GW_LOG_ERRNO, module, "r==-2");
353         else if (r == -1)
354             gw_log (GW_LOG_DEBUG|GW_LOG_ERRNO, module, "r==-1");
355         else
356             gw_log (GW_LOG_WARN|GW_LOG_ERRNO, module, "gipc_open");
357         longjmp (retry_jmp, 1);        /* yet another pass */
358     }
359     index = 0;                         /* transfer. may be interrupted */
360     while ((msg = str_queue_get (queue, index++)))
361         gip_wline (*gip, msg);
362     signal (SIGPIPE, oldsig);
363     return pass;                       /* successful transfer */
364 }
365
366 /* 
367  * monitor_events: Event loop of monitor
368  * argc:    argc of monitor (used in exec of Email kernel children)
369  * argv:    argv of monitor (used in exec of Email kernel children)
370  */
371 static void monitor_events (int argc, char **argv)
372 {
373     GIP gip_m;
374     int r, gip_m_fd, too_many;
375     char line_buf[1024];
376     fd_set set_r;
377     char command[128], *cp;
378
379     gip_m = gips_initialize (MONITOR_FIFO_S);
380     r = gips_open (gip_m, MONITOR_FIFO_C);
381     gip_m_fd = gip_infileno (gip_m);
382     open (MONITOR_FIFO_S, O_WRONLY);
383
384     while (1)
385     {
386         int fd_max;
387         struct ke_info *ki;
388
389         while (1)
390         {
391             if (got_sighup)
392             {
393                 gw_log (GW_LOG_STAT, module, "Got SIGHUP. Reading resources");
394                 reread_resources ();
395                 got_sighup = 0;
396             }
397             if (got_term)
398             {
399                 gw_log (GW_LOG_STAT, module, "Got SIGTERM. Exiting...");
400                 unlink (MONITOR_FIFO_S);
401                 unlink (MONITOR_FIFO_C);
402                 exit (0);
403             }
404             if (got_int)
405             {
406                 gw_log (GW_LOG_STAT, module, "Got SIGINT. Exiting...");
407                 unlink (MONITOR_FIFO_S);
408                 unlink (MONITOR_FIFO_C);
409                 exit (0);
410             }
411             /* deliver any unsent messages to Email kernels */
412             too_many = 0;
413             for (ki = ke_info_list; ki; ki = ki->next)
414             {
415                 if (!ki->queue)
416                     continue;
417                 gw_log (GW_LOG_DEBUG, module, "Transfer mail to %d", ki->id);
418                 r = deliver (argc, argv, ki->id, ki->queue, &ki->gip, &ki->pid,
419                              no_process >= max_process);
420                 if (r == 2)             /* new child was spawned? */
421                 {
422                     ++no_process;
423                     gw_log (GW_LOG_DEBUG, module, "Start of %d", ki->id);
424                 }
425                 if (r == 1 || r == 2)   /* transfer at all? */
426                     str_queue_rm (&ki->queue);
427                 if (r == 0)             /* too many pending? */
428                     too_many++;
429             }
430 #if 0
431             if (too_many)
432             {
433                 gw_log (GW_LOG_DEBUG, module, "%d too many pending",
434                         too_many);
435                 for (ki = ke_info_list; ki; ki = ki->next)
436                 {
437                     if (!ki->queue && ki->pid != -1)
438                     {
439                         if (!(ki->queue = str_queue_mk ()))
440                         {
441                             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module,
442                                     "str_queue_mk");
443                             exit (1);
444                         }
445                         str_queue_enq (ki->queue, "stop\n");
446                         str_queue_enq (ki->queue, "\001");
447                         gw_log (GW_LOG_DEBUG, module, "Stopping %d", ki->id);
448                         deliver (argc, argv, ki->id, ki->queue, &ki->gip,
449                                  &ki->pid, 1);
450                         str_queue_rm (&ki->queue);
451                         break;
452                     }
453                 }
454             }
455 #endif
456             FD_ZERO (&set_r);
457             FD_SET (gip_m_fd, &set_r);
458             gw_log (GW_LOG_DEBUG, module, "set gip_m_fd %d", gip_m_fd);
459             fd_max = gip_m_fd;
460             
461             for (ki = ke_info_list; ki; ki = ki->next)
462             {
463                 int fd;
464                 if (ki->gip)
465                 {
466                     if (ki->pid == -1)
467                     {                    /* child has exited */
468                         gw_log (GW_LOG_DEBUG, module, "Close of %d", ki->id);
469                         gipc_close (ki->gip);
470                         gipc_destroy (ki->gip);
471                         ki->gip = NULL;
472                     }
473                     else if ((fd = gip_infileno (ki->gip)) != -1)
474                     {                    /* read select on child FIFO */
475                         gw_log (GW_LOG_DEBUG, module, "set fd %d", fd);
476                         FD_SET (fd, &set_r);
477                         if (fd > fd_max)
478                             fd_max = fd;
479                     }
480                 }
481             }
482             gw_log (GW_LOG_DEBUG, module, "Cur/Max processes %d/%d",
483                     no_process, max_process);
484             gw_log (GW_LOG_DEBUG, module, "IPC select");
485             r = select (fd_max+1, &set_r, NULL, NULL, NULL);
486             if (r != -1)
487                 break;
488             if (errno != EINTR)
489             {   /* select aborted. And it was not due to interrupt */
490                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module, "select");
491                 exit (1);
492             }
493             /* select was interrupted. Probably child has died */
494             gw_log (GW_LOG_DEBUG|GW_LOG_ERRNO, module, "select");
495         }
496         /* go through list of Email kernels. See if any message has arrived */
497         gw_log (GW_LOG_DEBUG, module, "Testing ke_info_list");
498         for (ki = ke_info_list; ki; ki = ki->next)
499         {
500             int fd;
501             if (ki->gip && (fd = gip_infileno (ki->gip)) != -1)
502             {
503                 gw_log (GW_LOG_DEBUG, module, "Test of %d", fd);
504                 if (FD_ISSET (fd, &set_r))
505                 {
506                     if (lgets (line_buf, sizeof(line_buf)-1, fd))
507                     {
508                         gw_log (GW_LOG_DEBUG, module, "IPC: %s", line_buf);
509                     }
510                     else
511                     {
512                         gw_log (GW_LOG_DEBUG, module, "Close of %d", ki->id);
513                         gipc_close (ki->gip);
514                         gipc_destroy (ki->gip);
515                         ki->gip = NULL;
516                     }
517                 }
518             }
519         }
520         /* see if any message from eti has arrived */
521         gw_log (GW_LOG_DEBUG, module, "Testing gip_m_fd %d", gip_m_fd);
522         if (FD_ISSET (gip_m_fd, &set_r))
523         {
524             gw_log (GW_LOG_DEBUG, module, "Reading from %d", gip_m_fd);
525             if (!(lgets (command, sizeof(command)-1, gip_m_fd)))
526             {
527                 gw_log (GW_LOG_FATAL, module, "Unexpected close");
528                 exit (1);
529             }
530             gw_log (GW_LOG_DEBUG, module, "Done");
531             if ((cp = strchr (command, '\n')))
532                 *cp = '\0';
533             gw_log (GW_LOG_DEBUG, module, "IPC: %s", command);
534             if (!memcmp (command, "eti ", 4))
535             {
536                 int id = atoi (command+4);
537                 struct ke_info *new_k;
538                 
539                 new_k = ke_info_add (id);
540                 gw_log (GW_LOG_DEBUG, module, "Incoming mail %d", id);
541                 
542                 if (!new_k->queue)
543                 {
544                     if (!(new_k->queue = str_queue_mk ()))
545                     {
546                         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, module,
547                                 "str_queue_mk");
548                         exit (1);
549                     }
550                 }
551                 str_queue_enq (new_k->queue, "mail\n");
552                 while (lgets (line_buf, sizeof(line_buf)-1, gip_m_fd))
553                     str_queue_enq (new_k->queue, line_buf);
554                 str_queue_enq (new_k->queue, "\001");
555             }
556         }
557     }
558 }
559
560 /*
561  * main: main of monitor
562  */
563 int main (int argc, char **argv)
564 {
565     int argno = 0;
566
567     gw_log_init (*argv);
568     while (++argno < argc)
569     {
570         if (argv[argno][0] == '-')
571         {
572             switch (argv[argno][1])
573             {
574             case 'H':
575                 fprintf (stderr, "monitor [option..] [resource]\n");
576                 fprintf (stderr, "If no resource file is given");
577                 fprintf (stderr, " default.res is used\n");
578                 fprintf (stderr, "Options are transferred to kernel\n");
579                 exit (1);
580             case 'd':
581                 gw_log_level (GW_LOG_ALL & ~RES_DEBUG);
582                 break;
583             case 'D':
584                 gw_log_level (GW_LOG_ALL);
585                 break;
586             }
587         }
588         else
589             default_res = argv[argno];
590     }
591     reread_resources ();
592     signal (SIGCHLD, catch_child);
593     signal (SIGHUP, catch_hup);
594     signal (SIGTERM, catch_term);
595     signal (SIGINT, catch_int);
596 #if 1
597     gw_log_level (GW_LOG_ALL & ~RES_DEBUG);
598     gw_log_file (GW_LOG_ALL, "monitor.log");
599 #endif
600     monitor_events (argc, argv);
601     exit (0);
602 }