7f2274958813678988bd84da56755c44be58eab0
[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\n"));
282 #endif
283   switch (ntohs (msg->type))
284     {
285     case GNUNET_MESSAGE_TYPE_ARM_IS_UP:
286       ret = GNUNET_YES;
287       break;
288     case GNUNET_MESSAGE_TYPE_ARM_IS_DOWN:
289       ret = GNUNET_NO;
290       break;
291     case GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN:
292       ret = GNUNET_SYSERR;
293       break;
294     default:
295       GNUNET_break (0);
296       ret = GNUNET_SYSERR;
297     }
298   if (sc->callback != NULL)
299     sc->callback (sc->cls, ret);
300   GNUNET_free (sc);
301 }
302
303
304 /**
305  * We've failed to transmit the request to the ARM service.
306  * Report our failure and clean up the state.
307  *
308  * @param sctx the state of the (now failed) request
309  */
310 static void
311 report_transmit_failure (struct RequestContext *sctx)
312 {
313   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
314               _("Error while trying to transmit to ARM service\n"));
315   if (sctx->callback != NULL)
316     sctx->callback (sctx->cls, GNUNET_SYSERR);
317   GNUNET_free (sctx);
318 }
319
320
321 /**
322  * Transmit a request for a service status change to the
323  * ARM service.
324  *
325  * @param cls the "struct RequestContext" identifying the request
326  * @param size how many bytes are available in buf
327  * @param buf where to write the request, NULL on error
328  * @return number of bytes written to buf
329  */
330 static size_t
331 send_service_msg (void *cls, size_t size, void *buf);
332
333
334 /**
335  * We've failed to transmit the request to the ARM service but
336  * are now going to try again.
337  * 
338  * @param cls state of the request
339  * @param tc task context (unused)
340  */
341 static void
342 retry_request (void *cls,
343                const struct GNUNET_SCHEDULER_TaskContext *tc)
344 {
345   struct RequestContext *sctx = cls;
346
347   if (NULL ==
348       GNUNET_CLIENT_notify_transmit_ready (sctx->h->client,
349                                            sctx->slen +
350                                            sizeof (struct
351                                                    GNUNET_MessageHeader),
352                                            GNUNET_TIME_absolute_get_remaining (sctx->timeout),
353                                            &send_service_msg, 
354                                            sctx))
355     {
356       report_transmit_failure (sctx);    
357       return;
358     }
359 }
360
361
362 /**
363  * Transmit a request for a service status change to the
364  * ARM service.
365  *
366  * @param cls the "struct RequestContext" identifying the request
367  * @param size how many bytes are available in buf
368  * @param buf where to write the request, NULL on error
369  * @return number of bytes written to buf
370  */
371 static size_t
372 send_service_msg (void *cls, size_t size, void *buf)
373 {
374   struct RequestContext *sctx = cls;
375   struct GNUNET_MessageHeader *msg;
376   struct GNUNET_TIME_Relative rem;
377
378   if (buf == NULL)
379     {
380       GNUNET_CLIENT_disconnect (sctx->h->client);
381       sctx->h->client = GNUNET_CLIENT_connect (sctx->h->sched, 
382                                                "arm", 
383                                                sctx->h->cfg);
384       GNUNET_assert (sctx->h->client != NULL);
385       rem = GNUNET_TIME_absolute_get_remaining (sctx->timeout);
386       if ( (sctx->attempts_left-- > 0) &&
387            (rem.value > 0) )
388         {
389           GNUNET_SCHEDULER_add_delayed (sctx->h->sched,
390                                         GNUNET_NO,
391                                         GNUNET_SCHEDULER_PRIORITY_KEEP,
392                                         GNUNET_SCHEDULER_NO_TASK,
393                                         GNUNET_TIME_relative_min (MIN_RETRY_DELAY,
394                                                                   rem),
395                                         &retry_request,
396                                         sctx);
397           return 0;
398         }
399       report_transmit_failure (sctx);
400       return 0;
401     }
402 #if DEBUG_ARM
403   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
404               _("Transmitting service request to ARM.\n"));
405 #endif
406   GNUNET_assert (size >= sctx->slen);
407   msg = buf;
408   msg->size = htons (sizeof (struct GNUNET_MessageHeader) + sctx->slen);
409   msg->type = htons (sctx->type);
410   memcpy (&msg[1], sctx->service_name, sctx->slen);
411   GNUNET_CLIENT_receive (sctx->h->client,
412                          &handle_response,
413                          sctx,
414                          GNUNET_TIME_absolute_get_remaining (sctx->timeout));
415   return sctx->slen + sizeof (struct GNUNET_MessageHeader);
416 }
417
418
419 /**
420  * Start or stop a service.
421  *
422  * @param h handle to ARM
423  * @param service_name name of the service
424  * @param timeout how long to wait before failing for good
425  * @param cb callback to invoke when service is ready
426  * @param cb_cls closure for callback
427  * @param type type of the request 
428  */
429 static void
430 change_service (struct GNUNET_ARM_Handle *h,
431                 const char *service_name,
432                 struct GNUNET_TIME_Relative timeout,
433                 GNUNET_ARM_Callback cb, void *cb_cls, uint16_t type)
434 {
435   struct RequestContext *sctx;
436   size_t slen;
437
438   slen = strlen (service_name) + 1;
439   if (slen + sizeof (struct GNUNET_MessageHeader) >
440       GNUNET_SERVER_MAX_MESSAGE_SIZE)
441     {
442       GNUNET_break (0);
443       if (cb != NULL)
444         cb (cb_cls, GNUNET_NO);
445       return;
446     }
447 #if DEBUG_ARM
448   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
449               _("ARM requests starting of service `%s'.\n"), service_name);
450 #endif
451   sctx = GNUNET_malloc (sizeof (struct RequestContext) + slen);
452   sctx->h = h;
453   sctx->callback = cb;
454   sctx->cls = cb_cls;
455   sctx->service_name = (const char*) &sctx[1];
456   memcpy (&sctx[1],
457           service_name,
458           slen);
459   sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
460   sctx->slen = slen;
461   sctx->attempts_left = MAX_ATTEMPTS;
462   sctx->type = type;
463   retry_request (sctx, NULL);
464 }
465
466
467 /**
468  * Start a service.
469  *
470  * @param h handle to ARM
471  * @param service_name name of the service
472  * @param timeout how long to wait before failing for good
473  * @param cb callback to invoke when service is ready
474  * @param cb_cls closure for callback
475  */
476 void
477 GNUNET_ARM_start_service (struct GNUNET_ARM_Handle *h,
478                           const char *service_name,
479                           struct GNUNET_TIME_Relative timeout,
480                           GNUNET_ARM_Callback cb, void *cb_cls)
481 {
482   struct RequestContext *sctx;
483
484   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
485               _("Starting service `%s'\n"), service_name);
486   if (0 == strcmp ("arm", service_name))
487     {
488       sctx = GNUNET_malloc (sizeof (struct RequestContext));
489       sctx->h = h;
490       sctx->callback = cb;
491       sctx->cls = cb_cls;
492       sctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
493       GNUNET_CLIENT_service_test (h->sched,
494                                   "arm",
495                                   h->cfg, timeout, &arm_service_report, sctx);
496       return;
497     }
498   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_START);
499 }
500
501
502 /**
503  * Stop a service.
504  *
505  * @param h handle to ARM
506  * @param service_name name of the service
507  * @param timeout how long to wait before failing for good
508  * @param cb callback to invoke when service is ready
509  * @param cb_cls closure for callback
510  */
511 void
512 GNUNET_ARM_stop_service (struct GNUNET_ARM_Handle *h,
513                          const char *service_name,
514                          struct GNUNET_TIME_Relative timeout,
515                          GNUNET_ARM_Callback cb, void *cb_cls)
516 {
517   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
518               _("Stopping service `%s'\n"), service_name);
519   if (0 == strcmp ("arm", service_name))
520     {
521       GNUNET_CLIENT_service_shutdown (h->client);
522       if (cb != NULL)
523         cb (cb_cls, GNUNET_NO);
524       return;
525     }
526   change_service (h, service_name, timeout, cb, cb_cls, GNUNET_MESSAGE_TYPE_ARM_STOP);
527 }
528
529
530 /**
531  * Function to call for each service.
532  *
533  * @param h handle to ARM
534  * @param service_name name of the service
535  * @param timeout how long to wait before failing for good
536  * @param cb callback to invoke when service is ready
537  * @param cb_cls closure for callback
538  */
539 typedef void (*ServiceOperation) (struct GNUNET_ARM_Handle *h,
540                                   const char *service_name,
541                                   struct GNUNET_TIME_Relative timeout,
542                                   GNUNET_ARM_Callback cb, void *cb_cls);
543
544
545 /**
546  * Context for starting or stopping multiple services.
547  */
548 struct MultiContext
549 {
550   /**
551    * NULL-terminated array of services to start or stop.
552    */
553   char **services;
554
555   /**
556    * Our handle to ARM.
557    */
558   struct GNUNET_ARM_Handle *h;
559
560   /**
561    * Identifies the operation (start or stop).
562    */
563   ServiceOperation op;
564
565   /**
566    * Current position in "services".
567    */
568   unsigned int pos;
569 };
570
571
572 /**
573  * Run the operation for the next service in the multi-service
574  * request.
575  *
576  * @param cls the "struct MultiContext" that is being processed
577  * @param success status of the previous operation (ignored)
578  */
579 static void
580 next_operation (void *cls,
581                 int success)
582 {
583   struct MultiContext *mc = cls;
584   char *pos;
585   
586   if (NULL == (pos = mc->services[mc->pos]))
587     {
588       GNUNET_free (mc->services);
589       GNUNET_ARM_disconnect (mc->h);
590       GNUNET_free (mc);
591       return;
592     }
593   mc->pos++;
594   mc->op (mc->h, pos, MULTI_TIMEOUT, &next_operation, mc);
595   GNUNET_free (pos);
596 }
597
598
599 /**
600  * Run a multi-service request.
601  *
602  * @param cfg configuration to use (needed to contact ARM;
603  *        the ARM service may internally use a different
604  *        configuration to determine how to start the service).
605  * @param sched scheduler to use
606  * @param op the operation to perform for each service
607  * @param va NULL-terminated list of services
608  */
609 static void
610 run_multi_request (const struct GNUNET_CONFIGURATION_Handle *cfg,
611                    struct GNUNET_SCHEDULER_Handle *sched,                   
612                    ServiceOperation op,
613                    va_list va)
614 {
615   va_list cp;
616   unsigned int total;
617   struct MultiContext *mc;
618   struct GNUNET_ARM_Handle *h;
619   const char *c;
620   
621   h = GNUNET_ARM_connect (cfg, sched, NULL);
622   if (NULL == h)
623     {
624       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
625                   _("Error while trying to transmit to ARM service\n"));
626       return; 
627     }
628   total = 1;
629   va_copy (cp, va);
630   while (NULL != (va_arg (cp, const char*))) total++;
631   va_end (cp);
632   mc = GNUNET_malloc (sizeof(struct MultiContext));
633   mc->services = GNUNET_malloc (total * sizeof (char*));
634   mc->h = h;
635   mc->op = op;
636   total = 0;
637   va_copy (cp, va);
638   while (NULL != (c = va_arg (cp, const char*))) 
639     mc->services[total++] = GNUNET_strdup (c);
640   va_end (cp);
641   next_operation (mc, GNUNET_YES);
642 }
643
644
645 /**
646  * Start multiple services in the specified order.  Convenience
647  * function.  Works asynchronously, failures are not reported.
648  *
649  * @param cfg configuration to use (needed to contact ARM;
650  *        the ARM service may internally use a different
651  *        configuration to determine how to start the service).
652  * @param sched scheduler to use
653  * @param ... NULL-terminated list of service names (const char*)
654  */
655 void
656 GNUNET_ARM_start_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
657                            struct GNUNET_SCHEDULER_Handle *sched,
658                            ...)
659 {
660   va_list ap;
661
662   va_start (ap, sched);
663   run_multi_request (cfg, sched, &GNUNET_ARM_start_service, ap);
664   va_end (ap);
665 }
666
667
668 /**
669  * Stop multiple services in the specified order.  Convenience
670  * function.  Works asynchronously, failures are not reported.
671  *
672  * @param cfg configuration to use (needed to contact ARM;
673  *        the ARM service may internally use a different
674  *        configuration to determine how to start the service).
675  * @param sched scheduler to use
676  * @param ... NULL-terminated list of service names (const char*)
677  */
678 void
679 GNUNET_ARM_stop_services (const struct GNUNET_CONFIGURATION_Handle *cfg,
680                           struct GNUNET_SCHEDULER_Handle *sched,
681                           ...)
682 {
683   va_list ap;
684
685   va_start (ap, sched);
686   run_multi_request (cfg, sched, &GNUNET_ARM_stop_service, ap);
687   va_end (ap);
688 }
689
690
691 /* end of arm_api.c */