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