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