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