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