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