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