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