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