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