debug messages, disable default services for testing
[oweals/gnunet.git] / src / arm / arm_api.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/arm_api.c
23  * @brief API for accessing the ARM service
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_arm_service.h"
28 #include "gnunet_client_lib.h"
29 #include "gnunet_getopt_lib.h"
30 #include "gnunet_os_lib.h"
31 #include "gnunet_protocols.h"
32 #include "gnunet_server_lib.h"
33 #include "arm.h"
34
35 /**
36  * How often do we re-try tranmsitting requests to ARM before
37  * giving up?  Note that if we succeeded transmitting a request
38  * but failed to read a response, we do NOT re-try (since that
39  * might result in ARM getting a request twice).
40  */
41 #define MAX_ATTEMPTS 4
42
43 /**
44  * Minimum delay between attempts to talk to ARM.
45  */
46 #define MIN_RETRY_DELAY  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100)
47
48
49 /**
50  * How long are we willing to wait for a service operation during the multi-operation
51  * request processing?
52  */
53 #define MULTI_TIMEOUT  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
54
55
56 /**
57  * Handle for interacting with ARM.
58  */ 
59 struct GNUNET_ARM_Handle
60 {
61
62   /**
63    * Our connection to the ARM service.
64    */
65   struct GNUNET_CLIENT_Connection *client;
66
67   /**
68    * The configuration that we are using.
69    */
70   const struct GNUNET_CONFIGURATION_Handle *cfg;
71
72   /**
73    * Scheduler to use.
74    */
75   struct GNUNET_SCHEDULER_Handle *sched;
76
77 };
78
79
80 /**
81  * Setup a context for communicating with ARM.  Note that this
82  * can be done even if the ARM service is not yet running.
83  *
84  * @param cfg configuration to use (needed to contact ARM;
85  *        the ARM service may internally use a different
86  *        configuration to determine how to start the service).
87  * @param sched scheduler to use
88  * @param service service that *this* process is implementing/providing, can be NULL
89  * @return context to use for further ARM operations, NULL on error
90  */
91 struct GNUNET_ARM_Handle *
92 GNUNET_ARM_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
93                     struct GNUNET_SCHEDULER_Handle *sched,
94                     const char *service)
95 {
96   struct GNUNET_ARM_Handle *ret;
97   struct GNUNET_CLIENT_Connection *client;
98
99   client = GNUNET_CLIENT_connect (sched, "arm", cfg);
100   if (client == NULL)
101     return NULL;
102   ret = GNUNET_malloc (sizeof (struct GNUNET_ARM_Handle));
103   ret->cfg = cfg;
104   ret->sched = sched;
105   ret->client = client;
106   return ret;
107 }
108
109
110 /**
111  * Disconnect from the ARM service.
112  *
113  * @param h the handle that was being used
114  */
115 void
116 GNUNET_ARM_disconnect (struct GNUNET_ARM_Handle *h)
117 {
118   if (h->client != NULL)
119     GNUNET_CLIENT_disconnect (h->client);
120   GNUNET_free (h);
121 }
122
123
124 /**
125  * Internal state for a request with ARM.
126  */
127 struct RequestContext
128 {
129
130   /**
131    * Pointer to our handle with ARM.
132    */
133   struct GNUNET_ARM_Handle *h;
134
135   /**
136    * Function to call with a status code for the requested operation.
137    */
138   GNUNET_ARM_Callback callback;
139
140   /**
141    * Closure for "callback".
142    */
143   void *cls;
144
145   /**
146    * The service that is being manipulated.  Do not free.
147    */
148   const char *service_name;
149
150   /**
151    * Timeout for the operation.
152    */
153   struct GNUNET_TIME_Absolute timeout;
154
155   /**
156    * Length of service_name plus one.
157    */
158   size_t slen;
159
160   /**
161    * Number of attempts left for transmitting the request to ARM.
162    * We may fail the first time (say because ARM is not yet up),
163    * in which case we wait a bit and re-try (timeout permitting).
164    */
165   unsigned int attempts_left;
166
167   /**
168    * Type of the request expressed as a message type (start or stop).
169    */
170   uint16_t type;
171
172 };
173
174
175 /**
176  * A client specifically requested starting of ARM itself.
177  * This function is called with information about whether
178  * or not ARM is running; if it is, report success.  If
179  * it is not, start the ARM process.
180  *
181  * @param cls the context for the request that we will report on (struct RequestContext*)
182  * @param tc why were we called (reason says if ARM is running)
183  */
184 static void
185 arm_service_report (void *cls,
186                     const struct GNUNET_SCHEDULER_TaskContext *tc)
187 {
188   struct RequestContext *pos = cls;
189   pid_t pid;
190   char *binary;
191   char *config;
192
193   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE))
194     {
195       /* arm is running! */
196       if (pos->callback != NULL)
197         pos->callback (pos->cls, GNUNET_YES);
198       GNUNET_free (pos);
199       return;
200     }
201   /* FIXME: should we check that HOSTNAME for 'arm' is localhost? */
202   /* start service */
203   if (GNUNET_OK !=
204       GNUNET_CONFIGURATION_get_value_string (pos->h->cfg,
205                                              "arm",
206                                              "BINARY",
207                                              &binary))
208     {
209       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
210                   _("Configuration failes to specify option `%s' in section `%s'!\n"),
211                   "BINARY",
212                   "arm");
213       if (pos->callback != NULL)
214         pos->callback (pos->cls, GNUNET_SYSERR);
215       GNUNET_free (pos);
216       return;
217     }
218   if (GNUNET_OK !=
219       GNUNET_CONFIGURATION_get_value_filename (pos->h->cfg,
220                                                "arm", "CONFIG", &config))
221     {
222       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
223                   _("Configuration fails to specify option `%s' in section `%s'!\n"),
224                   "CONFIG",
225                   "arm");
226       if (pos->callback != NULL)
227         pos->callback (pos->cls, GNUNET_SYSERR);
228       GNUNET_free (binary);
229       GNUNET_free (pos);
230       return;
231     }
232   pid = GNUNET_OS_start_process (binary, binary, "-d", "-c", config,
233 #if DEBUG_ARM
234                                  "-L", "DEBUG",
235 #endif
236                                  NULL);
237   GNUNET_free (binary);
238   GNUNET_free (config);
239   if (pid == -1)
240     {
241       if (pos->callback != NULL)
242         pos->callback (pos->cls, GNUNET_SYSERR);
243       GNUNET_free (pos);
244       return;
245     }
246   if (pos->callback != NULL)
247     pos->callback (pos->cls, GNUNET_YES);
248   GNUNET_free (pos);
249 }
250
251
252 /**
253  * Process a response from ARM to a request for a change in service
254  * status.
255  *
256  * @param cls the request context 
257  * @param msg the response
258  */
259 static void
260 handle_response (void *cls, const struct GNUNET_MessageHeader *msg)
261 {
262   struct RequestContext *sc = cls;
263   int ret;
264
265   if (msg == NULL)
266     {
267       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
268                   _("Error receiving response from ARM service\n"));
269       GNUNET_CLIENT_disconnect (sc->h->client);
270       sc->h->client = GNUNET_CLIENT_connect (sc->h->sched, 
271                                              "arm", 
272                                              sc->h->cfg);
273       GNUNET_assert (NULL != sc->h->client);
274       if (sc->callback != NULL)
275         sc->callback (sc->cls, GNUNET_SYSERR);
276       GNUNET_free (sc);
277       return;
278     }
279 #if DEBUG_ARM
280   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
281               "Received response from ARM service: %u\n",
282               ntohs(msg->type));
283 #endif
284   switch (ntohs (msg->type))
285     {
286     case GNUNET_MESSAGE_TYPE_ARM_IS_UP:
287       ret = GNUNET_YES;
288       break;
289     case GNUNET_MESSAGE_TYPE_ARM_IS_DOWN:
290       ret = GNUNET_NO;
291       break;
292     case GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN:
293       ret = GNUNET_SYSERR;
294       break;
295     default:
296       GNUNET_break (0);
297       ret = GNUNET_SYSERR;
298     }
299   if (sc->callback != NULL)
300     sc->callback (sc->cls, ret);
301   GNUNET_free (sc);
302 }
303
304
305 /**
306  * We've failed to transmit the request to the ARM service.
307  * Report our failure and clean up the state.
308  *
309  * @param sctx the state of the (now failed) request
310  */
311 static void
312 report_transmit_failure (struct RequestContext *sctx)
313 {
314   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
315               _("Error while trying to transmit to ARM service\n"));
316   if (sctx->callback != NULL)
317     sctx->callback (sctx->cls, GNUNET_SYSERR);
318   GNUNET_free (sctx);
319 }
320
321
322 /**
323  * Transmit a request for a service status change to the
324  * ARM service.
325  *
326  * @param cls the "struct RequestContext" identifying the request
327  * @param size how many bytes are available in buf
328  * @param buf where to write the request, NULL on error
329  * @return number of bytes written to buf
330  */
331 static size_t
332 send_service_msg (void *cls, size_t size, void *buf);
333
334
335 /**
336  * We've failed to transmit the request to the ARM service but
337  * are now going to try again.
338  * 
339  * @param cls state of the request
340  * @param tc task context (unused)
341  */
342 static void
343 retry_request (void *cls,
344                const struct GNUNET_SCHEDULER_TaskContext *tc)
345 {
346   struct RequestContext *sctx = cls;
347
348   if (NULL ==
349       GNUNET_CLIENT_notify_transmit_ready (sctx->h->client,
350                                            sctx->slen +
351                                            sizeof (struct
352                                                    GNUNET_MessageHeader),
353                                            GNUNET_TIME_absolute_get_remaining (sctx->timeout),
354                                            &send_service_msg, 
355                                            sctx))
356     {
357       report_transmit_failure (sctx);    
358       return;
359     }
360 }
361
362
363 /**
364  * Transmit a request for a service status change to the
365  * ARM service.
366  *
367  * @param cls the "struct RequestContext" identifying the request
368  * @param size how many bytes are available in buf
369  * @param buf where to write the request, NULL on error
370  * @return number of bytes written to buf
371  */
372 static size_t
373 send_service_msg (void *cls, size_t size, void *buf)
374 {
375   struct RequestContext *sctx = cls;
376   struct GNUNET_MessageHeader *msg;
377   struct GNUNET_TIME_Relative rem;
378
379   if (buf == NULL)
380     {
381       GNUNET_CLIENT_disconnect (sctx->h->client);
382       sctx->h->client = GNUNET_CLIENT_connect (sctx->h->sched, 
383                                                "arm", 
384                                                sctx->h->cfg);
385       GNUNET_assert (sctx->h->client != NULL);
386       rem = GNUNET_TIME_absolute_get_remaining (sctx->timeout);
387       if ( (sctx->attempts_left-- > 0) &&
388            (rem.value > 0) )
389         {
390           GNUNET_SCHEDULER_add_delayed (sctx->h->sched,
391                                         GNUNET_NO,
392                                         GNUNET_SCHEDULER_PRIORITY_KEEP,
393                                         GNUNET_SCHEDULER_NO_TASK,
394                                         GNUNET_TIME_relative_min (MIN_RETRY_DELAY,
395                                                                   rem),
396                                         &retry_request,
397                                         sctx);
398           return 0;
399         }
400       report_transmit_failure (sctx);
401       return 0;
402     }
403 #if DEBUG_ARM
404   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
405               _("Transmitting service request to ARM.\n"));
406 #endif
407   GNUNET_assert (size >= sctx->slen);
408   msg = buf;
409   msg->size = htons (sizeof (struct GNUNET_MessageHeader) + sctx->slen);
410   msg->type = htons (sctx->type);
411   memcpy (&msg[1], sctx->service_name, sctx->slen);
412   GNUNET_CLIENT_receive (sctx->h->client,
413                          &handle_response,
414                          sctx,
415                          GNUNET_TIME_absolute_get_remaining (sctx->timeout));
416   return sctx->slen + sizeof (struct GNUNET_MessageHeader);
417 }
418
419
420 /**
421  * Start or stop a service.
422  *
423  * @param h handle to ARM
424  * @param service_name name of the service
425  * @param timeout how long to wait before failing for good
426  * @param cb callback to invoke when service is ready
427  * @param cb_cls closure for callback
428  * @param type type of the request 
429  */
430 static void
431 change_service (struct GNUNET_ARM_Handle *h,
432                 const char *service_name,
433                 struct GNUNET_TIME_Relative timeout,
434                 GNUNET_ARM_Callback cb, void *cb_cls, uint16_t type)
435 {
436   struct RequestContext *sctx;
437   size_t slen;
438
439   slen = strlen (service_name) + 1;
440   if (slen + sizeof (struct GNUNET_MessageHeader) >
441       GNUNET_SERVER_MAX_MESSAGE_SIZE)
442     {
443       GNUNET_break (0);
444       if (cb != NULL)
445         cb (cb_cls, GNUNET_NO);
446       return;
447     }
448 #if DEBUG_ARM
449   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
450               _("ARM requests starting of service `%s'.\n"), service_name);
451 #endif
452   sctx = GNUNET_malloc (sizeof (struct RequestContext) + slen);
453   sctx->h = h;
454   sctx->callback = cb;
455   sctx->cls = cb_cls;
456   sctx->service_name = (const char*) &sctx[1];
457   memcpy (&sctx[1],
458           service_name,
459           slen);
460   sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
461   sctx->slen = slen;
462   sctx->attempts_left = MAX_ATTEMPTS;
463   sctx->type = type;
464   retry_request (sctx, NULL);
465 }
466
467
468 /**
469  * Start a service.
470  *
471  * @param h handle to ARM
472  * @param service_name name of the service
473  * @param timeout how long to wait before failing for good
474  * @param cb callback to invoke when service is ready
475  * @param cb_cls closure for callback
476  */
477 void
478 GNUNET_ARM_start_service (struct GNUNET_ARM_Handle *h,
479                           const char *service_name,
480                           struct GNUNET_TIME_Relative timeout,
481                           GNUNET_ARM_Callback cb, void *cb_cls)
482 {
483   struct RequestContext *sctx;
484
485   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
486               _("Starting service `%s'\n"), service_name);
487   if (0 == strcmp ("arm", service_name))
488     {
489       sctx = GNUNET_malloc (sizeof (struct RequestContext));
490       sctx->h = h;
491       sctx->callback = cb;
492       sctx->cls = cb_cls;
493       sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
494       GNUNET_CLIENT_service_test (h->sched,
495                                   "arm",
496                                   h->cfg, timeout, &arm_service_report, sctx);
497       return;
498     }
499   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_START);
500 }
501
502
503 /**
504  * Stop a service.
505  *
506  * @param h handle to ARM
507  * @param service_name name of the service
508  * @param timeout how long to wait before failing for good
509  * @param cb callback to invoke when service is ready
510  * @param cb_cls closure for callback
511  */
512 void
513 GNUNET_ARM_stop_service (struct GNUNET_ARM_Handle *h,
514                          const char *service_name,
515                          struct GNUNET_TIME_Relative timeout,
516                          GNUNET_ARM_Callback cb, void *cb_cls)
517 {
518   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
519               _("Stopping service `%s'\n"), service_name);
520   if (0 == strcmp ("arm", service_name))
521     {
522       GNUNET_CLIENT_service_shutdown (h->client);
523       if (cb != NULL)
524         cb (cb_cls, GNUNET_NO);
525       return;
526     }
527   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_STOP);
528 }
529
530
531 /**
532  * Function to call for each service.
533  *
534  * @param h handle to ARM
535  * @param service_name name of the service
536  * @param timeout how long to wait before failing for good
537  * @param cb callback to invoke when service is ready
538  * @param cb_cls closure for callback
539  */
540 typedef void (*ServiceOperation) (struct GNUNET_ARM_Handle *h,
541                                   const char *service_name,
542                                   struct GNUNET_TIME_Relative timeout,
543                                   GNUNET_ARM_Callback cb, void *cb_cls);
544
545
546 /**
547  * Context for starting or stopping multiple services.
548  */
549 struct MultiContext
550 {
551   /**
552    * NULL-terminated array of services to start or stop.
553    */
554   char **services;
555
556   /**
557    * Our handle to ARM.
558    */
559   struct GNUNET_ARM_Handle *h;
560
561   /**
562    * Identifies the operation (start or stop).
563    */
564   ServiceOperation op;
565
566   /**
567    * Current position in "services".
568    */
569   unsigned int pos;
570 };
571
572
573 /**
574  * Run the operation for the next service in the multi-service
575  * request.
576  *
577  * @param cls the "struct MultiContext" that is being processed
578  * @param success status of the previous operation (ignored)
579  */
580 static void
581 next_operation (void *cls,
582                 int success)
583 {
584   struct MultiContext *mc = cls;
585   char *pos;
586   
587   if (NULL == (pos = mc->services[mc->pos]))
588     {
589       GNUNET_free (mc->services);
590       GNUNET_ARM_disconnect (mc->h);
591       GNUNET_free (mc);
592       return;
593     }
594   mc->pos++;
595   mc->op (mc->h, pos, MULTI_TIMEOUT, &next_operation, mc);
596   GNUNET_free (pos);
597 }
598
599
600 /**
601  * Run a multi-service request.
602  *
603  * @param cfg configuration to use (needed to contact ARM;
604  *        the ARM service may internally use a different
605  *        configuration to determine how to start the service).
606  * @param sched scheduler to use
607  * @param op the operation to perform for each service
608  * @param va NULL-terminated list of services
609  */
610 static void
611 run_multi_request (const struct GNUNET_CONFIGURATION_Handle *cfg,
612                    struct GNUNET_SCHEDULER_Handle *sched,                   
613                    ServiceOperation op,
614                    va_list va)
615 {
616   va_list cp;
617   unsigned int total;
618   struct MultiContext *mc;
619   struct GNUNET_ARM_Handle *h;
620   const char *c;
621   
622   h = GNUNET_ARM_connect (cfg, sched, NULL);
623   if (NULL == h)
624     {
625       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
626                   _("Error while trying to transmit to ARM service\n"));
627       return; 
628     }
629   total = 1;
630   va_copy (cp, va);
631   while (NULL != (va_arg (cp, const char*))) total++;
632   va_end (cp);
633   mc = GNUNET_malloc (sizeof(struct MultiContext));
634   mc->services = GNUNET_malloc (total * sizeof (char*));
635   mc->h = h;
636   mc->op = op;
637   total = 0;
638   va_copy (cp, va);
639   while (NULL != (c = va_arg (cp, const char*))) 
640     mc->services[total++] = GNUNET_strdup (c);
641   va_end (cp);
642   next_operation (mc, GNUNET_YES);
643 }
644
645
646 /**
647  * Start multiple services in the specified order.  Convenience
648  * function.  Works asynchronously, failures are not reported.
649  *
650  * @param cfg configuration to use (needed to contact ARM;
651  *        the ARM service may internally use a different
652  *        configuration to determine how to start the service).
653  * @param sched scheduler to use
654  * @param ... NULL-terminated list of service names (const char*)
655  */
656 void
657 GNUNET_ARM_start_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
658                            struct GNUNET_SCHEDULER_Handle *sched,
659                            ...)
660 {
661   va_list ap;
662
663   va_start (ap, sched);
664   run_multi_request (cfg, sched, &GNUNET_ARM_start_service, ap);
665   va_end (ap);
666 }
667
668
669 /**
670  * Stop multiple services in the specified order.  Convenience
671  * function.  Works asynchronously, failures are not reported.
672  *
673  * @param cfg configuration to use (needed to contact ARM;
674  *        the ARM service may internally use a different
675  *        configuration to determine how to start the service).
676  * @param sched scheduler to use
677  * @param ... NULL-terminated list of service names (const char*)
678  */
679 void
680 GNUNET_ARM_stop_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
681                           struct GNUNET_SCHEDULER_Handle *sched,
682                           ...)
683 {
684   va_list ap;
685
686   va_start (ap, sched);
687   run_multi_request (cfg, sched, &GNUNET_ARM_stop_service, ap);
688   va_end (ap);
689 }
690
691
692 /* end of arm_api.c */