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