move
[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  * - need to test auto-restart code on configuration changes;
32  * - should refine restart code to check if *relevant* parts of the
33  *   configuration were changed (anything in the section for the service)
34  * - should have a way to specify dependencies between services and
35  *   manage restarts of groups of services
36  */
37 #include "platform.h"
38 #include "gnunet_client_lib.h"
39 #include "gnunet_getopt_lib.h"
40 #include "gnunet_os_lib.h"
41 #include "gnunet_protocols.h"
42 #include "gnunet_service_lib.h"
43 #include "gnunet_signal_lib.h"
44 #include "arm.h"
45
46
47 /**
48  * Check for configuration file changes every 5s.
49  */
50 #define MAINT_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
51
52 /**
53  * Threshold after which exponential backoff shouldn't increase (in ms); 30m
54  */
55 #define EXPONENTIAL_BACKOFF_THRESHOLD (1000 * 60 * 30)
56
57
58 /**
59  * List of our services.
60  */
61 struct ServiceList;
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    * Client to notify upon kill completion (waitpid), NULL
90    * if we should simply restart the process.
91    */
92   struct GNUNET_SERVER_Client *killing_client;
93
94   /**
95    * Process ID of the child.
96    */
97   pid_t pid;
98
99   /**
100    * Last time the config of this service was
101    * modified.
102    */
103   time_t mtime;
104
105   /* Process exponential backoff time */
106   struct GNUNET_TIME_Relative backoff;
107
108   /* Absolute time at which the process is scheduled to restart in case of death */
109   struct GNUNET_TIME_Absolute restartAt;
110
111   /**
112    * Reference counter (counts how many times we've been
113    * asked to start the service).  We only actually stop
114    * it once rc hits zero.
115    */
116   unsigned int rc;
117
118 };
119
120 /**
121  * List of running services.
122  */
123 static struct ServiceList *running;
124
125 /**
126  * Our configuration
127  */
128 static const struct GNUNET_CONFIGURATION_Handle *cfg;
129
130 /**
131  * Our scheduler.
132  */
133 static struct GNUNET_SCHEDULER_Handle *sched;
134
135 /**
136  * Command to prepend to each actual command.
137  */
138 static char *prefix_command;
139
140 /**
141  * ID of task called whenever we get a SIGCHILD.
142  */
143 static GNUNET_SCHEDULER_TaskIdentifier child_death_task;
144
145 /**
146  * ID of task called whenever the timeout for restarting a child
147  * expires.
148  */
149 static GNUNET_SCHEDULER_TaskIdentifier child_restart_task;
150
151 /**
152  * Context for our SIGCHILD handler.
153  */
154 static struct GNUNET_SIGNAL_Context *shc_chld;
155
156 /**
157  * Pipe used to communicate shutdown via signal.
158  */
159 static struct GNUNET_DISK_PipeHandle *sigpipe;
160
161 /**
162  * Reading end of the signal pipe.
163  */
164 static const struct GNUNET_DISK_FileHandle *pr;
165
166 /**
167  * Are we in shutdown mode?
168  */
169 static int in_shutdown;
170
171
172 /**
173  * Handle to our server instance.  Our server is a bit special in that
174  * its service is not immediately stopped once we get a shutdown
175  * request (since we need to continue service until all of our child
176  * processes are dead).  This handle is used to shut down the server
177  * (and thus trigger process termination) once all child processes are
178  * also dead.  A special option in the ARM configuration modifies the
179  * behaviour of the service implementation to not do the shutdown
180  * immediately.
181  */
182 static struct GNUNET_SERVER_Handle *server;
183
184
185 /**
186  * If the configuration file changes, restart tasks that depended on that
187  * option.
188  *
189  * @param cls closure, NULL if we need to self-restart
190  * @param tc context
191  */
192 static void 
193 config_change_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
194 {
195   struct ServiceList *pos;
196   struct stat sbuf;
197
198   pos = running;
199   while (pos != NULL)
200     {
201       /* FIXME: this test for config change is a bit too coarse grained */
202       if ( (0 == STAT (pos->config, &sbuf)) && 
203            (pos->mtime < sbuf.st_mtime) &&
204            (pos->pid != 0) )
205         {
206           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
207                       _("Restarting service `%s' due to configuration file change.\n"));
208           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
209             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
210           else
211             pos->backoff = GNUNET_TIME_UNIT_MILLISECONDS;
212         }
213       pos = pos->next;
214     }
215 }
216
217
218
219 /**
220  * Transmit a status result message.
221  *
222  * @param cls pointer to "unit16_t*" with message type
223  * @param size number of bytes available in buf
224  * @param buf where to copy the message, NULL on error
225  * @return number of bytes copied to buf
226  */
227 static size_t
228 write_result (void *cls, size_t size, void *buf)
229 {
230   uint16_t *res = cls;
231   struct GNUNET_MessageHeader *msg;
232
233   if (buf == NULL)
234     {
235       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
236                   _("Could not send status result to client\n"));
237       return 0;                 /* error, not much we can do */
238     }
239   GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader));
240   msg = buf;
241   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
242   msg->type = htons (*res);
243   GNUNET_free (res);
244   return sizeof (struct GNUNET_MessageHeader);
245 }
246
247
248
249 /**
250  * Signal our client that we will start or stop the
251  * service.
252  *
253  * @param client who is being signalled
254  * @param name name of the service
255  * @param result message type to send
256  * @return NULL if it was not found
257  */
258 static void
259 signal_result (struct GNUNET_SERVER_Client *client,
260                const char *name, uint16_t result)
261 {
262   uint16_t *res;
263
264   if (NULL == client)
265     {
266       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
267                   _
268                   ("Not sending status result to client: no client known\n"));
269       return;
270     }
271 #if DEBUG_ARM
272   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
273               "Telling client that service `%s' is now %s\n",
274               name,
275               result == GNUNET_MESSAGE_TYPE_ARM_IS_DOWN ? "down" : "up");
276 #endif
277   res = GNUNET_malloc (sizeof (uint16_t));
278   *res = result;
279   GNUNET_SERVER_notify_transmit_ready (client,
280                                        sizeof (struct GNUNET_MessageHeader),
281                                        GNUNET_TIME_UNIT_FOREVER_REL,
282                                        &write_result, res);
283 }
284
285
286 /**
287  * Find the process with the given service
288  * name in the given list, remove it and return it.
289  *
290  * @param name which service entry to look up
291  * @return NULL if it was not found
292  */
293 static struct ServiceList *
294 find_name (const char *name)
295 {
296   struct ServiceList *pos;
297   struct ServiceList *prev;
298
299   pos = running;
300   prev = NULL;
301   while (pos != NULL)
302     {
303       if (0 == strcmp (pos->name, name))
304         {
305           if (prev == NULL)
306             running = pos->next;
307           else
308             prev->next = pos->next;
309           pos->next = NULL;
310           return pos;
311         }
312       prev = pos;
313       pos = pos->next;
314     }
315   return NULL;
316 }
317
318
319 /**
320  * Free an entry in the service list.
321  *
322  * @param pos entry to free
323  */
324 static void
325 free_entry (struct ServiceList *pos)
326 {
327   GNUNET_free_non_null (pos->config);
328   GNUNET_free_non_null (pos->binary);
329   GNUNET_free (pos->name);
330   GNUNET_free (pos);
331 }
332
333
334 /**
335  * Actually start the process for the given service.
336  *
337  * @param sl identifies service to start
338  */
339 static void
340 start_process (struct ServiceList *sl)
341 {
342   char *loprefix;
343   char *options;
344   char **argv;
345   unsigned int argv_size;
346   char *lopos;
347   char *optpos;
348   const char *firstarg;
349   int use_debug;
350
351   /* start service */
352   if (GNUNET_OK !=
353       GNUNET_CONFIGURATION_get_value_string (cfg,
354                                              sl->name, "PREFIX", &loprefix))
355     loprefix = GNUNET_strdup (prefix_command);
356   if (GNUNET_OK !=
357       GNUNET_CONFIGURATION_get_value_string (cfg,
358                                              sl->name, "OPTIONS", &options))
359     options = GNUNET_strdup ("");
360   use_debug = GNUNET_CONFIGURATION_get_value_yesno (cfg, sl->name, "DEBUG");
361
362   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Starting service `%s'\n"), sl->name);
363 #if DEBUG_ARM
364   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
365               "Starting service `%s' using binary `%s' and configuration `%s'\n",
366               sl->name, sl->binary, sl->config);
367 #endif
368   argv_size = 6;
369   if (use_debug)
370     argv_size += 2;
371   lopos = loprefix;
372   while ('\0' != *lopos)
373     {
374       if (*lopos == ' ')
375         argv_size++;
376       lopos++;
377     }
378   optpos = options;
379   while ('\0' != *optpos)
380     {
381       if (*optpos == ' ')
382         argv_size++;
383       optpos++;
384     }
385   firstarg = NULL;
386   argv = GNUNET_malloc (argv_size * sizeof (char *));
387   argv_size = 0;
388   lopos = loprefix;
389
390   while ('\0' != *lopos)
391     {
392       while (*lopos == ' ')
393         lopos++;
394       if (*lopos == '\0')
395         continue;
396       if (argv_size == 0)
397         firstarg = lopos;
398       argv[argv_size++] = lopos;
399       while (('\0' != *lopos) && (' ' != *lopos))
400         lopos++;
401       if ('\0' == *lopos)
402         continue;
403       *lopos = '\0';
404       lopos++;
405     }
406   if (argv_size == 0)
407     firstarg = sl->binary;
408   argv[argv_size++] = sl->binary;
409   argv[argv_size++] = "-c";
410   argv[argv_size++] = sl->config;
411   if (GNUNET_YES == use_debug)
412     {
413       argv[argv_size++] = "-L";
414       argv[argv_size++] = "DEBUG";
415     }
416   optpos = options;
417   while ('\0' != *optpos)
418     {
419       while (*optpos == ' ')
420         optpos++;
421       if (*optpos == '\0')
422         continue;
423       argv[argv_size++] = optpos;
424       while (('\0' != *optpos) && (' ' != *optpos))
425         optpos++;
426       if ('\0' == *optpos)
427         continue;
428       *optpos = '\0';
429       optpos++;
430     }
431   argv[argv_size++] = NULL;
432   sl->pid = GNUNET_OS_start_process_v (firstarg, argv);
433   /* FIXME: should check sl->pid */
434   GNUNET_free (argv);
435   GNUNET_free (loprefix);
436   GNUNET_free (options);
437 }
438
439
440 /**
441  * Start the specified service.
442  *
443  * @param client who is asking for this
444  * @param servicename name of the service to start
445  */
446 static void
447 start_service (struct GNUNET_SERVER_Client *client, const char *servicename)
448 {
449   struct ServiceList *sl;
450   char *binary;
451   char *config;
452   struct stat sbuf;
453
454   if (GNUNET_YES == in_shutdown)
455     {
456       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
457                   _("ARM is shutting down, service `%s' not started.\n"),
458                   servicename);
459       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
460       return;
461     }
462   sl = find_name (servicename);
463   if (sl != NULL)
464     {
465       /* already running, just increment RC */
466       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
467                   _("Service `%s' already running.\n"), servicename);
468       sl->rc++;
469       sl->next = running;
470       running = sl;
471       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
472       return;
473     }
474   if (GNUNET_OK !=
475       GNUNET_CONFIGURATION_get_value_string (cfg,
476                                              servicename, "BINARY", &binary))
477     {
478       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
479                   _("Binary implementing service `%s' not known!\n"),
480                   servicename);
481       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
482       return;
483     }
484   if ((GNUNET_OK !=
485        GNUNET_CONFIGURATION_get_value_filename (cfg,
486                                                 servicename,
487                                                 "CONFIG",
488                                                 &config)) ||
489       (0 != STAT (config, &sbuf)))
490     {
491       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
492                   _("Configuration file `%s' for service `%s' not known!\n"),
493                   config, servicename);
494       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
495       GNUNET_free (binary);
496       GNUNET_free_non_null (config);
497       return;
498     }
499   sl = GNUNET_malloc (sizeof (struct ServiceList));
500   sl->name = GNUNET_strdup (servicename);
501   sl->next = running;
502   sl->rc = 1;
503   sl->binary = binary;
504   sl->config = config;
505   sl->mtime = sbuf.st_mtime;
506   sl->backoff = GNUNET_TIME_UNIT_MILLISECONDS;
507   sl->restartAt = GNUNET_TIME_UNIT_FOREVER_ABS;
508
509   running = sl;
510   start_process (sl);
511   if (NULL != client)
512     signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
513 }
514
515
516 /**
517  * Stop the specified service.
518  *
519  * @param client who is asking for this
520  * @param servicename name of the service to stop
521  */
522 static void
523 stop_service (struct GNUNET_SERVER_Client *client, const char *servicename)
524 {
525   struct ServiceList *pos;
526
527   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
528               _("Preparing to stop `%s'\n"), servicename);
529   pos = find_name (servicename);
530   if (pos == NULL)
531     {
532       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN);
533       GNUNET_SERVER_receive_done (client, GNUNET_OK);
534       return;
535     }
536   if (pos->rc > 1)
537     {
538       /* RC>1, just decrement RC */
539       pos->rc--;
540       pos->next = running;
541       running = pos;
542 #if DEBUG_ARM
543       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
544                   "Service `%s' still used by %u clients, will keep it running!\n",
545                   servicename, pos->rc);
546 #endif
547       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP);
548       GNUNET_SERVER_receive_done (client, GNUNET_OK);
549       return;
550     }
551   if (pos->rc == 1)
552     pos->rc--;                  /* decrement RC to zero */
553   if (pos->killing_client != NULL)
554     {
555       /* killing already in progress */
556 #if DEBUG_ARM
557       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
558                   "Service `%s' is already down\n", servicename);
559 #endif
560       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
561       GNUNET_SERVER_receive_done (client, GNUNET_OK);
562       return;
563     }
564
565   if (GNUNET_YES == in_shutdown)
566     {
567 #if DEBUG_ARM
568       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
569                   "Termination request already sent to `%s' (since ARM is in shutdown).\n",
570                   servicename);
571 #endif
572       signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
573       GNUNET_SERVER_receive_done (client, GNUNET_OK);
574       return;
575     }
576 #if DEBUG_ARM
577   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
578               "Sending kill signal to service `%s', waiting for process to die.\n",
579               servicename);
580 #endif
581   if (0 != PLIBC_KILL (pos->pid, SIGTERM))
582     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
583   pos->next = running;
584   running = pos;
585   pos->killing_client = client;
586   GNUNET_SERVER_client_keep (client);
587 }
588
589
590 /**
591  * Handle START-message.
592  *
593  * @param cls closure (always NULL)
594  * @param client identification of the client
595  * @param message the actual message
596  * @return GNUNET_OK to keep the connection open,
597  *         GNUNET_SYSERR to close it (signal serious error)
598  */
599 static void
600 handle_start (void *cls,
601               struct GNUNET_SERVER_Client *client,
602               const struct GNUNET_MessageHeader *message)
603 {
604   const char *servicename;
605   uint16_t size;
606
607   size = ntohs (message->size);
608   size -= sizeof (struct GNUNET_MessageHeader);
609   servicename = (const char *) &message[1];
610   if ((size == 0) || (servicename[size - 1] != '\0'))
611     {
612       GNUNET_break (0);
613       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
614       return;
615     }
616   start_service (client, servicename);
617   GNUNET_SERVER_receive_done (client, GNUNET_OK);
618 }
619
620
621 /**
622  * Handle STOP-message.
623  *
624  * @param cls closure (always NULL)
625  * @param client identification of the client
626  * @param message the actual message
627  * @return GNUNET_OK to keep the connection open,
628  *         GNUNET_SYSERR to close it (signal serious error)
629  */
630 static void
631 handle_stop (void *cls,
632              struct GNUNET_SERVER_Client *client,
633              const struct GNUNET_MessageHeader *message)
634 {
635   const char *servicename;
636   uint16_t size;
637
638   size = ntohs (message->size);
639   size -= sizeof (struct GNUNET_MessageHeader);
640   servicename = (const char *) &message[1];
641   if ((size == 0) || (servicename[size - 1] != '\0'))
642     {
643       GNUNET_break (0);
644       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
645       return;
646     }
647   stop_service (client, servicename);
648 }
649
650
651 /**
652  * Remove all entries for tasks that are not running
653  * (pid = 0) from the running list (they will no longer
654  * be restarted since we are shutting down).
655  */
656 static void
657 clean_up_running ()
658 {
659   struct ServiceList *pos;
660   struct ServiceList *next;
661   struct ServiceList *prev;
662  
663   pos = running;
664   prev = NULL;
665   while (NULL != pos)
666     {
667       next = pos->next;
668       if (pos->pid == 0)
669         {
670           if (prev == NULL)
671             running = next;
672           else
673             prev->next = next;
674           free_entry (pos);
675         }
676       else
677         prev = pos;
678       pos = next;
679     }
680 }
681
682
683 /**
684  * We are done with everything.  Stop remaining 
685  * tasks, signal handler and the server. 
686  */
687 static void
688 do_shutdown ()
689 {
690   GNUNET_SERVER_destroy (server);
691   server = NULL;
692   GNUNET_SIGNAL_handler_uninstall (shc_chld);
693   shc_chld = NULL;
694   GNUNET_SCHEDULER_cancel (sched, child_death_task);
695   child_death_task = GNUNET_SCHEDULER_NO_TASK;
696 }
697
698
699 /**
700  * Task run for shutdown.
701  *
702  * @param cls closure, NULL if we need to self-restart
703  * @param tc context
704  */
705 static void
706 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
707 {
708   struct ServiceList *pos;
709  
710 #if DEBUG_ARM
711   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Stopping all services\n"));
712 #endif
713   in_shutdown = GNUNET_YES;
714   pos = running;
715   while (NULL != pos)
716     {
717       if (pos->pid != 0)
718         {
719 #if DEBUG_ARM
720           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
721                       "Sending SIGTERM to `%s'\n", pos->name);
722 #endif
723           if (0 != PLIBC_KILL (pos->pid, SIGTERM))
724             GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
725         }
726       pos = pos->next;
727     }
728   if (running == NULL)
729     do_shutdown ();
730 }
731
732
733 /**
734  * Task run whenever it is time to restart a child that died.
735  *
736  * @param cls closure, always NULL
737  * @param tc context
738  */
739 static void
740 delayed_restart_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
741 {
742   struct ServiceList *pos;
743   struct GNUNET_TIME_Relative lowestRestartDelay;
744
745   child_restart_task = GNUNET_SCHEDULER_NO_TASK;
746   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
747     {
748       clean_up_running ();
749       if (NULL == running)
750         do_shutdown ();
751       return;
752     }
753   lowestRestartDelay = GNUNET_TIME_UNIT_FOREVER_REL;
754
755   /* check for services that need to be restarted due to
756      configuration changes or because the last restart failed */
757   pos = running;
758   while (pos != NULL)
759     {
760       if ( (pos->pid == 0) && 
761            (GNUNET_YES != in_shutdown) )
762         {
763           if (GNUNET_TIME_absolute_get_remaining (pos->restartAt).value == 0)
764             {
765               GNUNET_log (GNUNET_ERROR_TYPE_INFO,
766                           _("Restarting service `%s'.\n"), pos->name);
767               start_process (pos);
768             }
769           else
770             {
771               lowestRestartDelay 
772                 = GNUNET_TIME_relative_min (lowestRestartDelay,
773                                             GNUNET_TIME_absolute_get_remaining
774                                             (pos->restartAt));
775             }
776         }
777       pos = pos->next;
778     }  
779   if (lowestRestartDelay.value != GNUNET_TIME_UNIT_FOREVER_REL.value)
780     {
781 #if DEBUG_ARM
782       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
783                   "Will restart process in %llums\n",
784                   (unsigned long long) lowestRestartDelay.value);
785 #endif
786       child_restart_task
787         = GNUNET_SCHEDULER_add_delayed (sched,
788                                         lowestRestartDelay,
789                                         &delayed_restart_task,
790                                         NULL);
791     }
792 }
793
794
795 /**
796  * Task triggered whenever we receive a SIGCHLD (child
797  * process died).  
798  *
799  * @param cls closure, NULL if we need to self-restart
800  * @param tc context
801  */
802 static void
803 maint_child_death (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
804 {
805   struct ServiceList *pos;
806   struct ServiceList *prev;
807   struct ServiceList *next;
808   const char *statstr;
809   int statcode;
810   int ret;
811   char c[16];
812   enum GNUNET_OS_ProcessStatusType statusType;
813   unsigned long statusCode;
814
815   child_death_task = GNUNET_SCHEDULER_NO_TASK;
816   if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
817     {
818       child_death_task =
819         GNUNET_SCHEDULER_add_read_file (sched, GNUNET_TIME_UNIT_FOREVER_REL, pr,
820                                         &maint_child_death, NULL);
821       return;    
822     }
823   /* consume the signal */
824   GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
825
826   /* check for services that died (WAITPID) */
827   prev = NULL;
828   next = running;
829   while (NULL != (pos = next))
830     {
831       next = pos->next;
832       if (pos->pid == 0) 
833         {
834           prev = pos;
835           continue;
836         }
837       if ((GNUNET_SYSERR == (ret = GNUNET_OS_process_status (pos->pid,
838                                                              &statusType,
839                                                              &statusCode))) ||
840           ( (ret == GNUNET_NO) ||
841             (statusType == GNUNET_OS_PROCESS_STOPPED) ||
842             (statusType == GNUNET_OS_PROCESS_RUNNING)) )
843         {
844           prev = pos;
845           continue;
846         }
847       if (statusType == GNUNET_OS_PROCESS_EXITED)
848         {
849           statstr = _( /* process termination method */ "exit");
850           statcode = statusCode;
851         }
852       else if (statusType == GNUNET_OS_PROCESS_SIGNALED)
853         {
854           statstr = _( /* process termination method */ "signal");
855           statcode = statusCode;
856         }
857       else
858         {
859           statstr = _( /* process termination method */ "unknown");
860           statcode = 0;
861         }
862       pos->pid = 0;
863       if (NULL != pos->killing_client) 
864         {
865           if (prev == NULL)
866             running = next;
867           else
868             prev->next = next;
869           GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
870                       "Service `%s' stopped\n",
871                       pos->name);
872           signal_result (pos->killing_client, 
873                          pos->name, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN);
874           GNUNET_SERVER_receive_done (pos->killing_client, GNUNET_OK);
875           GNUNET_SERVER_client_drop (pos->killing_client);
876           free_entry (pos);
877           continue;
878         }
879       if (GNUNET_YES != in_shutdown)
880         {
881           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
882                       _
883                       ("Service `%s' terminated with status %s/%d, will try to restart it!\n"),
884                       pos->name, statstr, statcode);
885           /* schedule restart */
886           pos->restartAt
887             = GNUNET_TIME_relative_to_absolute (pos->backoff);
888           if (pos->backoff.value < EXPONENTIAL_BACKOFF_THRESHOLD)
889             pos->backoff 
890               = GNUNET_TIME_relative_multiply (pos->backoff, 2);
891           if (GNUNET_SCHEDULER_NO_TASK != child_restart_task)
892             GNUNET_SCHEDULER_cancel (sched, child_restart_task);
893           child_restart_task 
894             = GNUNET_SCHEDULER_add_with_priority (sched,
895                                                   GNUNET_SCHEDULER_PRIORITY_IDLE,
896                                                   &delayed_restart_task,
897                                                   NULL);
898         }
899 #if DEBUG_ARM
900       else
901         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
902                     "Service `%s' terminated with status %s/%d\n",
903                     pos->name, statstr, statcode);
904 #endif
905       prev = pos;
906     }
907   if (in_shutdown)
908     clean_up_running ();
909   if ( (running == NULL) &&
910        (in_shutdown) )
911     {
912       GNUNET_SERVER_destroy (server);
913       GNUNET_SIGNAL_handler_uninstall (shc_chld);
914       shc_chld = NULL;
915     }
916   else
917     {
918       child_death_task =
919         GNUNET_SCHEDULER_add_read_file (sched, GNUNET_TIME_UNIT_FOREVER_REL, pr,
920                                         &maint_child_death, NULL);
921     }
922 }
923
924
925 /**
926  * List of handlers for the messages understood by this service.
927  */
928 static struct GNUNET_SERVER_MessageHandler handlers[] = {
929   {&handle_start, NULL, GNUNET_MESSAGE_TYPE_ARM_START, 0},
930   {&handle_stop, NULL, GNUNET_MESSAGE_TYPE_ARM_STOP, 0},
931   {NULL, NULL, 0, 0}
932 };
933
934 /**
935  * Signal handler called for SIGCHLD.  Triggers the
936  * respective handler by writing to the trigger pipe.
937  */
938 static void
939 sighandler_child_death ()
940 {
941   static char c;
942
943   GNUNET_break (1 == 
944                 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
945                                         (sigpipe, GNUNET_DISK_PIPE_END_WRITE), &c,
946                                         sizeof (c)));
947 }
948
949
950 /**
951  * Process arm requests.
952  *
953  * @param cls closure
954  * @param s scheduler to use
955  * @param serv the initialized server
956  * @param c configuration to use
957  */
958 static void
959 run (void *cls,
960      struct GNUNET_SCHEDULER_Handle *s,
961      struct GNUNET_SERVER_Handle *serv,
962      const struct GNUNET_CONFIGURATION_Handle *c)
963 {
964   char *defaultservices;
965   char *pos;
966
967   cfg = c;
968   sched = s;
969   server = serv;
970   GNUNET_assert (serv != NULL);
971   shc_chld = GNUNET_SIGNAL_handler_install (SIGCHLD, &sighandler_child_death);
972   GNUNET_assert (sigpipe == NULL);
973   sigpipe = GNUNET_DISK_pipe (GNUNET_NO);
974   GNUNET_assert (sigpipe != NULL);
975   pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
976   GNUNET_assert (pr != NULL);
977   GNUNET_SERVER_ignore_shutdown (serv, GNUNET_YES);
978   GNUNET_SCHEDULER_add_delayed (sched,
979                                 GNUNET_TIME_UNIT_FOREVER_REL,
980                                 &shutdown_task,
981                                 NULL);
982   child_death_task =
983     GNUNET_SCHEDULER_add_read_file (sched, GNUNET_TIME_UNIT_FOREVER_REL, pr,
984                                     &maint_child_death, NULL);
985
986   if (GNUNET_OK !=
987       GNUNET_CONFIGURATION_get_value_string (cfg,
988                                              "ARM",
989                                              "GLOBAL_PREFIX",
990                                              &prefix_command))
991     prefix_command = GNUNET_strdup ("");
992   /* start default services... */
993   if (GNUNET_OK ==
994       GNUNET_CONFIGURATION_get_value_string (cfg,
995                                              "ARM",
996                                              "DEFAULTSERVICES",
997                                              &defaultservices))
998     {
999 #if DEBUG_ARM
1000       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1001                   "Starting default services `%s'\n", defaultservices);
1002 #endif
1003       pos = strtok (defaultservices, " ");
1004       while (pos != NULL)
1005         {
1006           start_service (NULL, pos);
1007           pos = strtok (NULL, " ");
1008         }
1009       GNUNET_free (defaultservices);
1010     }
1011   else
1012     {
1013 #if DEBUG_ARM
1014       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1015                   "No default services configured.\n");
1016 #endif
1017     }
1018
1019   /* process client requests */
1020   GNUNET_SERVER_add_handlers (server, handlers);
1021
1022   /* manage services */
1023   GNUNET_SCHEDULER_add_with_priority (sched,
1024                                       GNUNET_SCHEDULER_PRIORITY_IDLE,
1025                                       &config_change_task, NULL);
1026 }
1027
1028
1029 /**
1030  * The main function for the arm service.
1031  *
1032  * @param argc number of arguments from the command line
1033  * @param argv command line arguments
1034  * @return 0 ok, 1 on error
1035  */
1036 int
1037 main (int argc, char *const *argv)
1038 {
1039   return (GNUNET_OK ==
1040           GNUNET_SERVICE_run (argc,
1041                               argv, "arm", GNUNET_YES, &run, NULL)) ? 0 : 1;
1042 }
1043
1044 /* end of gnunet-service-arm.c */