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