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