const-ing of config-handles
[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 const 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 service
187  * name in the given list, remove it and return it.
188  *
189  * @return NULL if it was not found
190  */
191 static struct ServiceList *
192 find_name (const char *name)
193 {
194   struct ServiceList *pos;
195   struct ServiceList *prev;
196
197   pos = running;
198   prev = NULL;
199   while (pos != NULL)
200     {
201       if (0 == strcmp (pos->name, name))
202         {
203           if (prev == NULL)
204             running = pos->next;
205           else
206             prev->next = pos->next;
207           pos->next = NULL;
208           return pos;
209         }
210       prev = pos;
211       pos = pos->next;
212     }
213   return NULL;
214 }
215
216
217 static void
218 free_entry (struct ServiceList *pos)
219 {
220   GNUNET_free_non_null (pos->config);
221   GNUNET_free_non_null (pos->binary);
222   GNUNET_free (pos->name);
223   GNUNET_free (pos);
224 }
225
226
227
228
229 /**
230  * Actually start the process for the given service.
231  *
232  * @param sl identifies service to start
233  */
234 static void
235 start_process (struct ServiceList *sl)
236 {
237   char *loprefix;
238   char *options;
239   char **argv;
240   unsigned int argv_size;
241   char *lopos;
242   char *optpos;
243   const char *firstarg;
244   int use_debug;
245
246   /* start service */
247   if (GNUNET_OK !=
248       GNUNET_CONFIGURATION_get_value_string (cfg,
249                                              sl->name, "PREFIX", &loprefix))
250     loprefix = GNUNET_strdup (prefix_command);
251   if (GNUNET_OK !=
252       GNUNET_CONFIGURATION_get_value_string (cfg,
253                                              sl->name, "OPTIONS", &options))
254     options = GNUNET_strdup ("");
255   use_debug = GNUNET_CONFIGURATION_get_value_yesno (cfg, sl->name, "DEBUG");
256
257   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Starting service `%s'\n"), sl->name);
258 #if DEBUG_ARM
259   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
260               "Starting service `%s' using binary `%s' and configuration `%s'\n",
261               sl->name, sl->binary, sl->config);
262 #endif
263   argv_size = 6;
264   if (use_debug)
265     argv_size += 2;
266   lopos = loprefix;
267   while ('\0' != *lopos)
268     {
269       if (*lopos == ' ')
270         argv_size++;
271       lopos++;
272     }
273   optpos = options;
274   while ('\0' != *optpos)
275     {
276       if (*optpos == ' ')
277         argv_size++;
278       optpos++;
279     }
280   firstarg = NULL;
281   argv = GNUNET_malloc (argv_size * sizeof (char *));
282   argv_size = 0;
283   lopos = loprefix;
284
285   while ('\0' != *lopos)
286     {
287       while (*lopos == ' ')
288         lopos++;
289       if (*lopos == '\0')
290         continue;
291       if (argv_size == 0)
292         firstarg = lopos;
293       argv[argv_size++] = lopos;
294       while (('\0' != *lopos) && (' ' != *lopos))
295         lopos++;
296       if ('\0' == *lopos)
297         continue;
298       *lopos = '\0';
299       lopos++;
300     }
301   if (argv_size == 0)
302     firstarg = sl->binary;
303   argv[argv_size++] = sl->binary;
304   argv[argv_size++] = "-c";
305   argv[argv_size++] = sl->config;
306   if (GNUNET_YES == use_debug)
307     {
308       argv[argv_size++] = "-L";
309       argv[argv_size++] = "DEBUG";
310     }
311   optpos = options;
312   while ('\0' != *optpos)
313     {
314       while (*optpos == ' ')
315         optpos++;
316       if (*optpos == '\0')
317         continue;
318       argv[argv_size++] = optpos;
319       while (('\0' != *optpos) && (' ' != *optpos))
320         optpos++;
321       if ('\0' == *optpos)
322         continue;
323       *optpos = '\0';
324       optpos++;
325     }
326   argv[argv_size++] = NULL;
327   sl->pid = GNUNET_OS_start_process_v (firstarg, argv);
328   GNUNET_free (argv);
329   GNUNET_free (loprefix);
330   GNUNET_free (options);
331 }
332
333
334 /**
335  * Start the specified service.
336  */
337 static void
338 start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
339 {
340   struct ServiceList *sl;
341   char *binary;
342   char *config;
343   struct stat sbuf;
344   sl = find_name (servicename);
345   if (sl != NULL)
346     {
347       /* already running, just increment RC */
348       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
349                   _("Service `%s' already running.\n"), servicename);
350       sl->rc++;
351       sl->next = running;
352       running = sl;
353       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
354       return;
355     }
356   if (GNUNET_OK !=
357       GNUNET_CONFIGURATION_get_value_string (cfg,
358                                              servicename, "BINARY", &binary))
359     {
360       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
361                   _("Binary implementing service `%s' not known!\n"),
362                   servicename);
363       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
364       return;
365     }
366   if ((GNUNET_OK !=
367        GNUNET_CONFIGURATION_get_value_filename (cfg,
368                                                 servicename,
369                                                 "CONFIG",
370                                                 &config)) ||
371       (0 != STAT (config, &sbuf)))
372     {
373       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
374                   _("Configuration file `%s' for service `%s' not known!\n"),
375                   config, servicename);
376       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
377       GNUNET_free (binary);
378       GNUNET_free (config);
379       return;
380     }
381   sl = GNUNET_malloc (sizeof (struct ServiceList));
382   sl->name = GNUNET_strdup (servicename);
383   sl->next = running;
384   sl->rc = 1;
385   sl->binary = binary;
386   sl->config = config;
387   sl->mtime = sbuf.st_mtime;
388   running = sl;
389   start_process (sl);
390   signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
391 }
392
393
394 static void
395 free_and_signal (void *cls, struct ServiceList *pos)
396 {
397   struct GNUNET_SERVER_Client *client = cls;
398   /* find_name will remove "pos" from the list! */
399   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Service `%s' stopped\n", pos->name);
400   signal_result (client, pos->name, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
401   GNUNET_SERVER_receive_done (client, GNUNET_OK);
402   GNUNET_SERVER_client_drop (client);
403   free_entry (pos);
404 }
405
406
407 /**
408  * Stop the specified service.
409  */
410 static void
411 stop_service (struct GNUNET_SERVER_Client *client, const char *servicename)
412 {
413   struct ServiceList *pos;
414   struct GNUNET_CLIENT_Connection *sc;
415   unsigned long long port;
416
417   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
418               "Preparing to stop `%s'\n", servicename);
419   pos = find_name (servicename);
420   if ((pos != NULL) && (pos->kill_continuation != NULL))
421     {
422       /* killing already in progress */
423       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
424       return;
425     }
426   if ((pos != NULL) && (pos->rc > 1))
427     {
428       /* RC>1, just decrement RC */
429       pos->rc--;
430       pos->next = running;
431       running = pos;
432       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
433       GNUNET_SERVER_receive_done (client, GNUNET_OK);
434       return;
435     }
436   if (pos != NULL)
437     {
438       if (0 != PLIBC_KILL (pos->pid, SIGTERM))
439         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
440       pos->next = running;
441       running = pos;
442       pos->kill_continuation = &free_and_signal;
443       pos->kill_continuation_cls = client;
444       GNUNET_SERVER_client_keep (client);
445     }
446   else
447     {
448       if ( (GNUNET_OK ==
449             GNUNET_CONFIGURATION_get_value_number (cfg,
450                                                    servicename,
451                                                    "PORT",
452                                                    &port)) &&
453            (NULL != (sc = GNUNET_CLIENT_connect (sched, servicename, cfg))) )
454         {
455           GNUNET_CLIENT_service_shutdown (sc);
456           GNUNET_CLIENT_disconnect (sc);
457           signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
458         }
459       else
460         {
461           signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN);
462         }
463       GNUNET_SERVER_receive_done (client, GNUNET_OK);
464     }
465 }
466
467
468 /**
469  * Handle START-message.
470  *
471  * @param cls closure (always NULL)
472  * @param client identification of the client
473  * @param message the actual message
474  * @return GNUNET_OK to keep the connection open,
475  *         GNUNET_SYSERR to close it (signal serious error)
476  */
477 static void
478 handle_start (void *cls,
479               struct GNUNET_SERVER_Client *client,
480               const struct GNUNET_MessageHeader *message)
481 {
482   const char *servicename;
483   uint16_t size;
484
485   size = ntohs (message->size);
486   size -= sizeof (struct GNUNET_MessageHeader);
487   servicename = (const char *) &message[1];
488   if ((size == 0) || (servicename[size - 1] != '\0'))
489     {
490       GNUNET_break (0);
491       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
492       return;
493     }
494   start_service (client, servicename);
495   GNUNET_SERVER_receive_done (client, GNUNET_OK);
496 }
497
498
499 /**
500  * Handle STOP-message.
501  *
502  * @param cls closure (always NULL)
503  * @param client identification of the client
504  * @param message the actual message
505  * @return GNUNET_OK to keep the connection open,
506  *         GNUNET_SYSERR to close it (signal serious error)
507  */
508 static void
509 handle_stop (void *cls,
510              struct GNUNET_SERVER_Client *client,
511              const struct GNUNET_MessageHeader *message)
512 {
513   const char *servicename;
514   uint16_t size;
515
516   size = ntohs (message->size);
517   size -= sizeof (struct GNUNET_MessageHeader);
518   servicename = (const char *) &message[1];
519   if ((size == 0) || (servicename[size - 1] != '\0'))
520     {
521       GNUNET_break (0);
522       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
523       return;
524     }
525   stop_service (client, servicename);
526 }
527
528
529
530 /**
531  * Background task doing maintenance.
532  *
533  * @param cls closure
534  * @param tc context
535  */
536 static void
537 maint (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
538 {
539   struct ServiceList *pos;
540   struct ServiceList *prev;
541   struct ServiceList *next;
542   const char *statstr;
543   int statcode;
544   struct stat sbuf;
545   int ret;
546
547   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
548     {
549       GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Stopping all services\n"));
550       while (NULL != (pos = running))
551         {
552           running = pos->next;
553           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
554             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
555           if (GNUNET_OK != GNUNET_OS_process_wait(pos->pid))
556             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
557           free_entry (pos);
558         }
559       return;
560     }
561   GNUNET_SCHEDULER_add_delayed (tc->sched,
562                                 GNUNET_YES,
563                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
564                                 GNUNET_SCHEDULER_NO_TASK,
565                                 MAINT_FREQUENCY, &maint, NULL);
566
567   /* check for services that died (WAITPID) */
568   prev = NULL;
569   next = running;
570   while (NULL != (pos = next))
571     {
572       enum GNUNET_OS_ProcessStatusType statusType;
573       unsigned long statusCode;
574      
575       next = pos->next;
576       if (pos->pid == 0)
577         {
578           if (NULL != pos->kill_continuation)
579             {
580               if (prev == NULL)
581                 running = next;
582               else
583                 prev->next = next;
584               pos->kill_continuation (pos->kill_continuation_cls, pos);     
585             }
586           continue;
587         }
588       if ( (GNUNET_SYSERR == (ret = GNUNET_OS_process_status(pos->pid, 
589                                                              &statusType,
590                                                              &statusCode))) ||
591            ( (ret == GNUNET_NO) ||
592              (statusType == GNUNET_OS_PROCESS_STOPPED) || 
593              (statusType == GNUNET_OS_PROCESS_RUNNING) ) )
594         {
595           prev = pos;
596           continue;
597         }
598       if (statusType == GNUNET_OS_PROCESS_EXITED)
599         {
600           statstr = _( /* process termination method */ "exit");
601           statcode = statusCode;
602         }
603       else if (statusType == GNUNET_OS_PROCESS_SIGNALED)
604         {
605           statstr = _( /* process termination method */ "signal");
606           statcode = statusCode;
607         }
608       else
609         {
610           statstr = _( /* process termination method */ "unknown");
611           statcode = 0;
612         }    
613       if (NULL != pos->kill_continuation)
614         {
615           if (prev == NULL)
616             running = next;
617           else
618             prev->next = next;
619           pos->kill_continuation (pos->kill_continuation_cls, pos);
620           continue;
621         }
622       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
623                   _("Service `%s' terminated with status %s/%d, will try to restart it!\n"),
624                   pos->name, statstr, statcode);
625       /* schedule restart */
626       pos->pid = 0;
627       prev = pos;
628     }
629
630   /* check for services that need to be restarted due to
631      configuration changes or because the last restart failed */
632   pos = running;
633   while (pos != NULL)
634     {
635       if ((0 == STAT (pos->config, &sbuf)) && (pos->mtime < sbuf.st_mtime))
636         {
637           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
638                       _("Restarting service `%s' due to configuration file change.\n"));
639           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
640             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
641         }
642       if (pos->pid == 0)
643         {
644           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
645                       _("Restarting service `%s'.\n"), pos->name);
646           /* FIXME: should have some exponentially
647              increasing timer to avoid tight restart loops */
648           start_process (pos);
649         }
650       pos = pos->next;
651     }
652 }
653
654
655 /**
656  * List of handlers for the messages understood by this
657  * service.
658  */
659 static struct GNUNET_SERVER_MessageHandler handlers[] = {
660   {&handle_start, NULL, GNUNET_MESSAGE_TYPE_ARM_START, 0},
661   {&handle_stop, NULL, GNUNET_MESSAGE_TYPE_ARM_STOP, 0},
662   {NULL, NULL, 0, 0}
663 };
664
665
666 /**
667  * Process arm requests.
668  *
669  * @param cls closure
670  * @param s scheduler to use
671  * @param server the initialized server
672  * @param c configuration to use
673  */
674 static void
675 run (void *cls,
676      struct GNUNET_SCHEDULER_Handle *s,
677      struct GNUNET_SERVER_Handle *server,
678      const struct GNUNET_CONFIGURATION_Handle *c)
679 {
680   char *defaultservices;
681   char *pos;
682
683   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting...\n");
684   cfg = c;
685   sched = s;
686   if (GNUNET_OK !=
687       GNUNET_CONFIGURATION_get_value_string (cfg,
688                                              "ARM",
689                                              "GLOBAL_PREFIX",
690                                              &prefix_command))
691     prefix_command = GNUNET_strdup ("");
692   /* start default services... */
693   if (GNUNET_OK ==
694       GNUNET_CONFIGURATION_get_value_string (cfg,
695                                              "ARM",
696                                              "DEFAULTSERVICES",
697                                              &defaultservices))
698     {
699 #if DEBUG_ARM
700       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
701                   "Starting default services `%s'\n", defaultservices);
702 #endif
703       pos = strtok (defaultservices, " ");
704       while (pos != NULL)
705         {
706           start_service (NULL, pos);
707           pos = strtok (NULL, " ");
708         }
709       GNUNET_free (defaultservices);
710     }
711   else
712     {
713 #if DEBUG_ARM
714       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
715                   "No default services configured.\n");
716 #endif
717     }
718
719   /* process client requests */
720   GNUNET_SERVER_add_handlers (server, handlers);
721
722   /* manage services */
723   GNUNET_SCHEDULER_add_delayed (sched,
724                                 GNUNET_YES,
725                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
726                                 GNUNET_SCHEDULER_NO_TASK,
727                                 MAINT_FREQUENCY, &maint, NULL);
728 }
729
730
731 /**
732  * The main function for the arm service.
733  *
734  * @param argc number of arguments from the command line
735  * @param argv command line arguments
736  * @return 0 ok, 1 on error
737  */
738 int
739 main (int argc, char *const *argv)
740 {
741   return (GNUNET_OK ==
742           GNUNET_SERVICE_run (argc,
743                               argv, "arm", &run, NULL, NULL, NULL)) ? 0 : 1;
744 }
745
746 /* end of gnunet-service-arm.c */