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