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