poll PIDs for status information
[oweals/gnunet.git] / src / arm / gnunet-service-arm.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file arm/gnunet-service-arm.c
23  * @brief the automated restart manager service
24  * @author Christian Grothoff
25  *
26  * TODO:
27  * - multiple start-stop requests with RC>1 can result
28  *   in UP/DOWN signals based on "pending" that are inaccurate...
29  *   => have list of clients waiting for a resolution instead of
30  *      giving instant (but incorrect) replies
31  * - code could go into restart-loop for a service
32  *   if service crashes instantly -- need exponential back-off
33  * - need to test auto-restart code on configuration changes;
34  * - should refine restart code to check if *relevant* parts of the
35  *   configuration were changed (anything in the section for the service)
36  * - should have a way to specify dependencies between services and
37  *   manage restarts of groups of services
38  */
39 #include "platform.h"
40 #include "gnunet_client_lib.h"
41 #include "gnunet_getopt_lib.h"
42 #include "gnunet_os_lib.h"
43 #include "gnunet_protocols.h"
44 #include "gnunet_service_lib.h"
45 #include "arm.h"
46
47
48 /**
49  * Run maintenance every second.
50  */
51 #define MAINT_FREQUENCY GNUNET_TIME_UNIT_SECONDS
52
53 /**
54  * How long do we wait until we decide that a service
55  * did not start?
56  */
57 #define CHECK_TIMEOUT GNUNET_TIME_UNIT_MINUTES
58
59 struct ServiceList;
60
61 typedef void (*CleanCallback) (void *cls, struct ServiceList * pos);
62
63 /**
64  * List of our services.
65  */
66 struct ServiceList
67 {
68   /**
69    * This is a linked list.
70    */
71   struct ServiceList *next;
72
73   /**
74    * Name of the service.
75    */
76   char *name;
77
78   /**
79    * Name of the binary used.
80    */
81   char *binary;
82
83   /**
84    * Name of the configuration file used.
85    */
86   char *config;
87
88   /**
89    * Function to call upon kill completion (waitpid), NULL
90    * if we should simply restart the process.
91    */
92   CleanCallback kill_continuation;
93
94   /**
95    * Closure for kill_continuation.
96    */
97   void *kill_continuation_cls;
98
99   /**
100    * Process ID of the child.
101    */
102   pid_t pid;
103
104   /**
105    * Last time the config of this service was
106    * modified.
107    */
108   time_t mtime;
109
110   /**
111    * Reference counter (counts how many times we've been
112    * asked to start the service).  We only actually stop
113    * it once rc hits zero.
114    */
115   unsigned int rc;
116
117 };
118
119 /**
120  * List of running services.
121  */
122 static struct ServiceList *running;
123
124 /**
125  * Our configuration
126  */
127 static struct GNUNET_CONFIGURATION_Handle *cfg;
128
129 /**
130  * Our scheduler.
131  */
132 static struct GNUNET_SCHEDULER_Handle *sched;
133
134 /**
135  * Command to prepend to each actual command.
136  */
137 static char *prefix_command;
138
139
140 static size_t
141 write_result (void *cls, size_t size, void *buf)
142 {
143   uint16_t *res = cls;
144   struct GNUNET_MessageHeader *msg;
145
146   if (buf == NULL)
147     return 0;                   /* error, not much we can do */
148   GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader));
149   msg = buf;
150   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
151   msg->type = htons (*res);
152   GNUNET_free (res);
153   return sizeof (struct GNUNET_MessageHeader);
154 }
155
156
157
158 /**
159  * Signal our client that we will start or stop the
160  * service.
161  *
162  * @return NULL if it was not found
163  */
164 static void
165 signal_result (struct GNUNET_SERVER_Client *client,
166                const char *name, uint16_t result)
167 {
168   uint16_t *res;
169
170 #if DEBUG_ARM
171   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
172               "Telling client that service `%s' is now %s\n",
173               name,
174               result == GNUNET_MESSAGE_TYPE_ARM_IS_DOWN ? "down" : "up");
175 #endif
176   res = GNUNET_malloc (sizeof (uint16_t));
177   *res = result;
178   GNUNET_SERVER_notify_transmit_ready (client,
179                                        sizeof (struct GNUNET_MessageHeader),
180                                        GNUNET_TIME_UNIT_FOREVER_REL,
181                                        &write_result, res);
182 }
183
184
185 /**
186  * Find the process with the given PID in the
187  * given list.
188  *
189  * @return NULL if it was not found
190  */
191 static struct ServiceList *
192 find_pid (pid_t pid)
193 {
194   struct ServiceList *pos;
195
196   pos = running;
197   while (pos != NULL)
198     {
199       if (pos->pid == pid)
200         return pos;
201       pos = pos->next;
202     }
203   return NULL;
204 }
205
206
207 /**
208  * Find the process with the given service
209  * name in the given list, remove it and return it.
210  *
211  * @return NULL if it was not found
212  */
213 static struct ServiceList *
214 find_name (const char *name)
215 {
216   struct ServiceList *pos;
217   struct ServiceList *prev;
218
219   pos = running;
220   prev = NULL;
221   while (pos != NULL)
222     {
223       if (0 == strcmp (pos->name, name))
224         {
225           if (prev == NULL)
226             running = pos->next;
227           else
228             prev->next = pos->next;
229           pos->next = NULL;
230           return pos;
231         }
232       prev = pos;
233       pos = pos->next;
234     }
235   return NULL;
236 }
237
238
239 static void
240 free_entry (struct ServiceList *pos)
241 {
242   GNUNET_free_non_null (pos->config);
243   GNUNET_free_non_null (pos->binary);
244   GNUNET_free (pos->name);
245   GNUNET_free (pos);
246 }
247
248
249
250
251 /**
252  * Actually start the process for the given service.
253  *
254  * @param sl identifies service to start
255  */
256 static void
257 start_process (struct ServiceList *sl)
258 {
259   char *loprefix;
260   char *options;
261   char **argv;
262   unsigned int argv_size;
263   char *lopos;
264   char *optpos;
265   const char *firstarg;
266   int use_debug;
267
268   /* start service */
269   if (GNUNET_OK !=
270       GNUNET_CONFIGURATION_get_value_string (cfg,
271                                              sl->name, "PREFIX", &loprefix))
272     loprefix = GNUNET_strdup (prefix_command);
273   if (GNUNET_OK !=
274       GNUNET_CONFIGURATION_get_value_string (cfg,
275                                              sl->name, "OPTIONS", &options))
276     options = GNUNET_strdup ("");
277   use_debug = GNUNET_CONFIGURATION_get_value_yesno (cfg, sl->name, "DEBUG");
278
279   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Starting service `%s'\n"), sl->name);
280 #if DEBUG_ARM
281   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
282               "Starting service `%s' using binary `%s' and configuration `%s'\n",
283               sl->name, sl->binary, sl->config);
284 #endif
285   argv_size = 6;
286   if (use_debug)
287     argv_size += 2;
288   lopos = loprefix;
289   while ('\0' != *lopos)
290     {
291       if (*lopos == ' ')
292         argv_size++;
293       lopos++;
294     }
295   optpos = options;
296   while ('\0' != *optpos)
297     {
298       if (*optpos == ' ')
299         argv_size++;
300       optpos++;
301     }
302   firstarg = NULL;
303   argv = GNUNET_malloc (argv_size * sizeof (char *));
304   argv_size = 0;
305   lopos = loprefix;
306
307   while ('\0' != *lopos)
308     {
309       while (*lopos == ' ')
310         lopos++;
311       if (*lopos == '\0')
312         continue;
313       if (argv_size == 0)
314         firstarg = lopos;
315       argv[argv_size++] = lopos;
316       while (('\0' != *lopos) && (' ' != *lopos))
317         lopos++;
318       if ('\0' == *lopos)
319         continue;
320       *lopos = '\0';
321       lopos++;
322     }
323   if (argv_size == 0)
324     firstarg = sl->binary;
325   argv[argv_size++] = sl->binary;
326   argv[argv_size++] = "-c";
327   argv[argv_size++] = sl->config;
328   if (GNUNET_YES == use_debug)
329     {
330       argv[argv_size++] = "-L";
331       argv[argv_size++] = "DEBUG";
332     }
333   optpos = options;
334   while ('\0' != *optpos)
335     {
336       while (*optpos == ' ')
337         optpos++;
338       if (*optpos == '\0')
339         continue;
340       argv[argv_size++] = optpos;
341       while (('\0' != *optpos) && (' ' != *optpos))
342         optpos++;
343       if ('\0' == *optpos)
344         continue;
345       *optpos = '\0';
346       optpos++;
347     }
348   argv[argv_size++] = NULL;
349   sl->pid = GNUNET_OS_start_process_v (firstarg, argv);
350   GNUNET_free (argv);
351   GNUNET_free (loprefix);
352   GNUNET_free (options);
353 }
354
355
356 /**
357  * Start the specified service.
358  */
359 static void
360 start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
361 {
362   struct ServiceList *sl;
363   char *binary;
364   char *config;
365   struct stat sbuf;
366   sl = find_name (servicename);
367   if (sl != NULL)
368     {
369       /* already running, just increment RC */
370       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
371                   _("Service `%s' already running.\n"), servicename);
372       sl->rc++;
373       sl->next = running;
374       running = sl;
375       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
376       return;
377     }
378   if (GNUNET_OK !=
379       GNUNET_CONFIGURATION_get_value_string (cfg,
380                                              servicename, "BINARY", &binary))
381     {
382       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
383                   _("Binary implementing service `%s' not known!\n"),
384                   servicename);
385       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
386       return;
387     }
388   if ((GNUNET_OK !=
389        GNUNET_CONFIGURATION_get_value_filename (cfg,
390                                                 servicename,
391                                                 "CONFIG",
392                                                 &config)) ||
393       (0 != STAT (config, &sbuf)))
394     {
395       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
396                   _("Configuration file `%s' for service `%s' not known!\n"),
397                   config, servicename);
398       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
399       GNUNET_free (binary);
400       GNUNET_free (config);
401       return;
402     }
403   sl = GNUNET_malloc (sizeof (struct ServiceList));
404   sl->name = GNUNET_strdup (servicename);
405   sl->next = running;
406   sl->rc = 1;
407   sl->binary = binary;
408   sl->config = config;
409   sl->mtime = sbuf.st_mtime;
410   running = sl;
411   start_process (sl);
412   signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
413 }
414
415
416 static void
417 free_and_signal (void *cls, struct ServiceList *pos)
418 {
419   struct GNUNET_SERVER_Client *client = cls;
420   /* find_name will remove "pos" from the list! */
421   GNUNET_assert (pos == find_name (pos->name));
422   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Service `%s' stopped\n", pos->name);
423   signal_result (client, pos->name, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
424   GNUNET_SERVER_receive_done (client, GNUNET_OK);
425   GNUNET_SERVER_client_drop (client);
426   free_entry (pos);
427 }
428
429
430 /**
431  * Stop the specified service.
432  */
433 static void
434 stop_service (struct GNUNET_SERVER_Client *client, const char *servicename)
435 {
436   struct ServiceList *pos;
437   struct GNUNET_CLIENT_Connection *sc;
438
439   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
440               "Preparing to stop `%s'\n", servicename);
441   pos = find_name (servicename);
442   if ((pos != NULL) && (pos->kill_continuation != NULL))
443     {
444       /* killing already in progress */
445       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
446       return;
447     }
448   if ((pos != NULL) && (pos->rc > 1))
449     {
450       /* RC>1, just decrement RC */
451       pos->rc--;
452       pos->next = running;
453       running = pos;
454       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
455       GNUNET_SERVER_receive_done (client, GNUNET_OK);
456       return;
457     }
458   if (pos != NULL)
459     {
460       if (0 != PLIBC_KILL (pos->pid, SIGTERM))
461         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
462       pos->next = running;
463       running = pos;
464       pos->kill_continuation = &free_and_signal;
465       pos->kill_continuation_cls = client;
466       GNUNET_SERVER_client_keep (client);
467     }
468   else
469     {
470       sc = GNUNET_CLIENT_connect (sched, servicename, cfg);
471       GNUNET_CLIENT_service_shutdown (sc);
472       GNUNET_CLIENT_disconnect (sc);
473       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
474       GNUNET_SERVER_receive_done (client, GNUNET_OK);
475     }
476 }
477
478
479 /**
480  * Handle START-message.
481  *
482  * @param cls closure (always NULL)
483  * @param client identification of the client
484  * @param message the actual message
485  * @return GNUNET_OK to keep the connection open,
486  *         GNUNET_SYSERR to close it (signal serious error)
487  */
488 static void
489 handle_start (void *cls,
490               struct GNUNET_SERVER_Client *client,
491               const struct GNUNET_MessageHeader *message)
492 {
493   const char *servicename;
494   uint16_t size;
495
496   size = ntohs (message->size);
497   size -= sizeof (struct GNUNET_MessageHeader);
498   servicename = (const char *) &message[1];
499   if ((size == 0) || (servicename[size - 1] != '\0'))
500     {
501       GNUNET_break (0);
502       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
503       return;
504     }
505   start_service (client, servicename);
506   GNUNET_SERVER_receive_done (client, GNUNET_OK);
507 }
508
509
510 /**
511  * Handle STOP-message.
512  *
513  * @param cls closure (always NULL)
514  * @param client identification of the client
515  * @param message the actual message
516  * @return GNUNET_OK to keep the connection open,
517  *         GNUNET_SYSERR to close it (signal serious error)
518  */
519 static void
520 handle_stop (void *cls,
521              struct GNUNET_SERVER_Client *client,
522              const struct GNUNET_MessageHeader *message)
523 {
524   const char *servicename;
525   uint16_t size;
526
527   size = ntohs (message->size);
528   size -= sizeof (struct GNUNET_MessageHeader);
529   servicename = (const char *) &message[1];
530   if ((size == 0) || (servicename[size - 1] != '\0'))
531     {
532       GNUNET_break (0);
533       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
534       return;
535     }
536   stop_service (client, servicename);
537 }
538
539
540
541 /**
542  * Background task doing maintenance.
543  *
544  * @param cls closure
545  * @param tc context
546  */
547 static void
548 maint (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
549 {
550   struct ServiceList *pos;
551   pid_t pid;
552   const char *statstr;
553   int statcode;
554   struct stat sbuf;
555
556   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
557     {
558       GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Stopping all services\n"));
559       while (NULL != (pos = running))
560         {
561           running = pos->next;
562           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
563             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
564           if (GNUNET_OK != GNUNET_OS_process_wait(pos->pid))
565             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
566           free_entry (pos);
567         }
568       return;
569     }
570   GNUNET_SCHEDULER_add_delayed (tc->sched,
571                                 GNUNET_YES,
572                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
573                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
574                                 MAINT_FREQUENCY, &maint, cfg);
575
576   /* check for services that died (WAITPID) */
577   for (pos = running; pos != NULL; pos = pos->next)
578     {
579       enum GNUNET_OS_ProcessStatusType statusType;
580       unsigned long statusCode;
581
582       if (GNUNET_OS_process_status(pos->pid, &statusType, &statusCode) != GNUNET_OK)
583       {
584         GNUNET_log_strerror(GNUNET_ERROR_TYPE_ERROR, "GNUNET_OS_process_status");
585         continue;
586       }
587
588       if (statusType == GNUNET_OS_PROCESS_STOPPED || statusType == GNUNET_OS_PROCESS_RUNNING)
589         continue;
590       else if (statusType == GNUNET_OS_PROCESS_EXITED)
591         {
592           statstr = _( /* process termination method */ "exit");
593           statcode = statusCode;
594         }
595       else if (statusType == GNUNET_OS_PROCESS_SIGNALED)
596         {
597           statstr = _( /* process termination method */ "signal");
598           statcode = statusCode;
599         }
600       else
601         {
602           statstr = _( /* process termination method */ "unknown");
603           statcode = 0;
604         }
605       if (NULL != pos->kill_continuation)
606         {
607           pos->kill_continuation (pos->kill_continuation_cls, pos);
608         }
609       else
610         {
611           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
612                       _
613                       ("Service `%s' terminated with status %s/%d, will try to restart it!\n"),
614                       pos->name, statstr, statcode);
615           /* schedule restart */
616           pos->pid = 0;
617         }
618     }
619
620   /* check for services that need to be restarted due to
621      configuration changes or because the last restart failed */
622   pos = running;
623   while (pos != NULL)
624     {
625       if ((0 == STAT (pos->config, &sbuf)) && (pos->mtime < sbuf.st_mtime))
626         {
627           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
628                       _
629                       ("Restarting service `%s' due to configuration file change.\n"));
630           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
631             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
632         }
633       if (pos->pid == 0)
634         {
635           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
636                       _("Restarting service `%s'.\n"), pos->name);
637           /* FIXME: should have some exponentially
638              increasing timer to avoid tight restart loops */
639           start_process (pos);
640         }
641       pos = pos->next;
642     }
643 }
644
645
646 /**
647  * List of handlers for the messages understood by this
648  * service.
649  */
650 static struct GNUNET_SERVER_MessageHandler handlers[] = {
651   {&handle_start, NULL, GNUNET_MESSAGE_TYPE_ARM_START, 0},
652   {&handle_stop, NULL, GNUNET_MESSAGE_TYPE_ARM_STOP, 0},
653   {NULL, NULL, 0, 0}
654 };
655
656
657 /**
658  * Process arm requests.
659  *
660  * @param cls closure
661  * @param s scheduler to use
662  * @param server the initialized server
663  * @param c configuration to use
664  */
665 static void
666 run (void *cls,
667      struct GNUNET_SCHEDULER_Handle *s,
668      struct GNUNET_SERVER_Handle *server,
669      struct GNUNET_CONFIGURATION_Handle *c)
670 {
671   char *defaultservices;
672   char *pos;
673
674   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting...\n");
675   cfg = c;
676   sched = s;
677   if (GNUNET_OK !=
678       GNUNET_CONFIGURATION_get_value_string (cfg,
679                                              "ARM",
680                                              "GLOBAL_PREFIX",
681                                              &prefix_command))
682     prefix_command = GNUNET_strdup ("");
683   /* start default services... */
684   if (GNUNET_OK ==
685       GNUNET_CONFIGURATION_get_value_string (cfg,
686                                              "ARM",
687                                              "DEFAULTSERVICES",
688                                              &defaultservices))
689     {
690 #if DEBUG_ARM
691       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
692                   "Starting default services `%s'\n", defaultservices);
693 #endif
694       pos = strtok (defaultservices, " ");
695       while (pos != NULL)
696         {
697           start_service (NULL, pos);
698           pos = strtok (NULL, " ");
699         }
700       GNUNET_free (defaultservices);
701     }
702   else
703     {
704 #if DEBUG_ARM
705       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
706                   "No default services configured.\n");
707 #endif
708     }
709
710   /* process client requests */
711   GNUNET_SERVER_add_handlers (server, handlers);
712
713   /* manage services */
714   GNUNET_SCHEDULER_add_delayed (sched,
715                                 GNUNET_YES,
716                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
717                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
718                                 MAINT_FREQUENCY, &maint, NULL);
719 }
720
721
722 /**
723  * The main function for the arm service.
724  *
725  * @param argc number of arguments from the command line
726  * @param argv command line arguments
727  * @return 0 ok, 1 on error
728  */
729 int
730 main (int argc, char *const *argv)
731 {
732   return (GNUNET_OK ==
733           GNUNET_SERVICE_run (argc,
734                               argv, "arm", &run, NULL, NULL, NULL)) ? 0 : 1;
735 }
736
737 /* end of gnunet-service-arm.c */